1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "PreparedModel.h"
18 
19 #include <ExecutionBurstServer.h>
20 #include <android-base/logging.h>
21 #include <android/hardware/neuralnetworks/1.0/IExecutionCallback.h>
22 #include <android/hardware/neuralnetworks/1.0/types.h>
23 #include <android/hardware/neuralnetworks/1.2/IBurstCallback.h>
24 #include <android/hardware/neuralnetworks/1.2/IExecutionCallback.h>
25 #include <android/hardware/neuralnetworks/1.2/types.h>
26 #include <android/hardware/neuralnetworks/1.3/IExecutionCallback.h>
27 #include <android/hardware/neuralnetworks/1.3/IFencedExecutionCallback.h>
28 #include <android/hardware/neuralnetworks/1.3/IPreparedModel.h>
29 #include <android/hardware/neuralnetworks/1.3/types.h>
30 #include <hwbinder/IPCThreadState.h>
31 #include <nnapi/IPreparedModel.h>
32 #include <nnapi/TypeUtils.h>
33 #include <nnapi/Types.h>
34 #include <nnapi/Validation.h>
35 #include <nnapi/hal/1.0/Utils.h>
36 #include <nnapi/hal/1.2/Utils.h>
37 #include <nnapi/hal/1.3/Conversions.h>
38 #include <nnapi/hal/1.3/Utils.h>
39 #include <nnapi/hal/HandleError.h>
40 #include <sys/types.h>
41 
42 #include <memory>
43 #include <thread>
44 
45 // See hardware/interfaces/neuralnetworks/utils/README.md for more information on HIDL interface
46 // lifetimes across processes and for protecting asynchronous calls across HIDL.
47 
48 namespace android::hardware::neuralnetworks::adapter {
49 namespace {
50 
51 template <typename Type>
convertInput(const Type & object)52 auto convertInput(const Type& object) -> decltype(nn::convert(std::declval<Type>())) {
53     auto result = nn::convert(object);
54     if (!result.has_value()) {
55         result.error().code = nn::ErrorStatus::INVALID_ARGUMENT;
56     }
57     return result;
58 }
59 
60 class FencedExecutionCallback final : public V1_3::IFencedExecutionCallback {
61   public:
FencedExecutionCallback(const nn::ExecuteFencedInfoCallback & callback)62     explicit FencedExecutionCallback(const nn::ExecuteFencedInfoCallback& callback)
63         : kCallback(callback) {
64         CHECK(callback != nullptr);
65     }
66 
getExecutionInfo(getExecutionInfo_cb cb)67     Return<void> getExecutionInfo(getExecutionInfo_cb cb) override {
68         const auto result = kCallback();
69         if (!result.has_value()) {
70             const auto& [message, code] = result.error();
71             const auto status =
72                     V1_3::utils::convert(code).value_or(V1_3::ErrorStatus::GENERAL_FAILURE);
73             LOG(ERROR) << message;
74             cb(status, V1_2::utils::kNoTiming, V1_2::utils::kNoTiming);
75             return Void();
76         }
77         const auto [timingLaunched, timingFenced] = result.value();
78         const auto hidlTimingLaunched = V1_3::utils::convert(timingLaunched).value();
79         const auto hidlTimingFenced = V1_3::utils::convert(timingFenced).value();
80         cb(V1_3::ErrorStatus::NONE, hidlTimingLaunched, hidlTimingFenced);
81         return Void();
82     }
83 
84   private:
85     const nn::ExecuteFencedInfoCallback kCallback;
86 };
87 
88 using ExecutionResult = nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>;
89 
notify(V1_0::IExecutionCallback * callback,nn::ErrorStatus status,const std::vector<nn::OutputShape> &,const nn::Timing &)90 void notify(V1_0::IExecutionCallback* callback, nn::ErrorStatus status,
91             const std::vector<nn::OutputShape>& /*outputShapes*/, const nn::Timing& /*timing*/) {
92     if (callback != nullptr) {
93         const auto hidlStatus = V1_0::utils::convert(status).value();
94         const auto ret = callback->notify(hidlStatus);
95         if (!ret.isOk()) {
96             LOG(ERROR) << "V1_0::IExecutionCallback::notify failed with " << ret.description();
97         }
98     }
99 }
100 
notify(V1_2::IExecutionCallback * callback,nn::ErrorStatus status,const std::vector<nn::OutputShape> & outputShapes,const nn::Timing & timing)101 void notify(V1_2::IExecutionCallback* callback, nn::ErrorStatus status,
102             const std::vector<nn::OutputShape>& outputShapes, const nn::Timing& timing) {
103     if (callback != nullptr) {
104         const auto hidlStatus = V1_2::utils::convert(status).value();
105         const auto hidlOutputShapes = V1_2::utils::convert(outputShapes).value();
106         const auto hidlTiming = V1_2::utils::convert(timing).value();
107         const auto ret = callback->notify_1_2(hidlStatus, hidlOutputShapes, hidlTiming);
108         if (!ret.isOk()) {
109             LOG(ERROR) << "V1_2::IExecutionCallback::notify_1_2 failed with " << ret.description();
110         }
111     }
112 }
113 
notify(V1_3::IExecutionCallback * callback,nn::ErrorStatus status,const std::vector<nn::OutputShape> & outputShapes,const nn::Timing & timing)114 void notify(V1_3::IExecutionCallback* callback, nn::ErrorStatus status,
115             const std::vector<nn::OutputShape>& outputShapes, const nn::Timing& timing) {
116     if (callback != nullptr) {
117         const auto hidlStatus = V1_3::utils::convert(status).value();
118         const auto hidlOutputShapes = V1_3::utils::convert(outputShapes).value();
119         const auto hidlTiming = V1_3::utils::convert(timing).value();
120         const auto ret = callback->notify_1_3(hidlStatus, hidlOutputShapes, hidlTiming);
121         if (!ret.isOk()) {
122             LOG(ERROR) << "V1_3::IExecutionCallback::notify_1_3 failed with " << ret.description();
123         }
124     }
125 }
126 
127 template <typename CallbackType>
notify(CallbackType * callback,ExecutionResult result)128 void notify(CallbackType* callback, ExecutionResult result) {
129     if (!result.has_value()) {
130         const auto [message, status, outputShapes] = std::move(result).error();
131         LOG(ERROR) << message;
132         notify(callback, status, outputShapes, {});
133     } else {
134         const auto [outputShapes, timing] = std::move(result).value();
135         notify(callback, nn::ErrorStatus::NONE, outputShapes, timing);
136     }
137 }
138 
execute(const nn::SharedPreparedModel & preparedModel,uid_t userId,const Executor & executor,const V1_0::Request & request,const sp<V1_0::IExecutionCallback> & callback)139 nn::GeneralResult<void> execute(const nn::SharedPreparedModel& preparedModel, uid_t userId,
140                                 const Executor& executor, const V1_0::Request& request,
141                                 const sp<V1_0::IExecutionCallback>& callback) {
142     if (callback.get() == nullptr) {
143         return NN_ERROR(nn::ErrorStatus::INVALID_ARGUMENT) << "Invalid callback";
144     }
145 
146     auto nnRequest = NN_TRY(convertInput(request));
147 
148     const std::any resource = preparedModel->getUnderlyingResource();
149     if (const auto* model = std::any_cast<const nn::Model*>(&resource)) {
150         CHECK(*model != nullptr);
151         NN_TRY(utils::makeGeneralFailure(nn::validateRequestForModel(nnRequest, **model),
152                                          nn::ErrorStatus::INVALID_ARGUMENT));
153     }
154 
155     Task task = [preparedModel, nnRequest = std::move(nnRequest), callback] {
156         auto result = preparedModel->execute(nnRequest, nn::MeasureTiming::NO, {}, {});
157         notify(callback.get(), std::move(result));
158     };
159     executor(std::move(task), userId, {});
160 
161     return {};
162 }
163 
execute_1_2(const nn::SharedPreparedModel & preparedModel,uid_t userId,const Executor & executor,const V1_0::Request & request,V1_2::MeasureTiming measure,const sp<V1_2::IExecutionCallback> & callback)164 nn::GeneralResult<void> execute_1_2(const nn::SharedPreparedModel& preparedModel, uid_t userId,
165                                     const Executor& executor, const V1_0::Request& request,
166                                     V1_2::MeasureTiming measure,
167                                     const sp<V1_2::IExecutionCallback>& callback) {
168     if (callback.get() == nullptr) {
169         return NN_ERROR(nn::ErrorStatus::INVALID_ARGUMENT) << "Invalid callback";
170     }
171 
172     auto nnRequest = NN_TRY(convertInput(request));
173     const auto nnMeasure = NN_TRY(convertInput(measure));
174 
175     const std::any resource = preparedModel->getUnderlyingResource();
176     if (const auto* model = std::any_cast<const nn::Model*>(&resource)) {
177         CHECK(*model != nullptr);
178         NN_TRY(utils::makeGeneralFailure(nn::validateRequestForModel(nnRequest, **model),
179                                          nn::ErrorStatus::INVALID_ARGUMENT));
180     }
181 
182     Task task = [preparedModel, nnRequest = std::move(nnRequest), nnMeasure, callback] {
183         auto result = preparedModel->execute(nnRequest, nnMeasure, {}, {});
184         notify(callback.get(), std::move(result));
185     };
186     executor(std::move(task), userId, {});
187 
188     return {};
189 }
190 
execute_1_3(const nn::SharedPreparedModel & preparedModel,uid_t userId,const Executor & executor,const V1_3::Request & request,V1_2::MeasureTiming measure,const V1_3::OptionalTimePoint & deadline,const V1_3::OptionalTimeoutDuration & loopTimeoutDuration,const sp<V1_3::IExecutionCallback> & callback)191 nn::GeneralResult<void> execute_1_3(const nn::SharedPreparedModel& preparedModel, uid_t userId,
192                                     const Executor& executor, const V1_3::Request& request,
193                                     V1_2::MeasureTiming measure,
194                                     const V1_3::OptionalTimePoint& deadline,
195                                     const V1_3::OptionalTimeoutDuration& loopTimeoutDuration,
196                                     const sp<V1_3::IExecutionCallback>& callback) {
197     if (callback.get() == nullptr) {
198         return NN_ERROR(nn::ErrorStatus::INVALID_ARGUMENT) << "Invalid callback";
199     }
200 
201     auto nnRequest = NN_TRY(convertInput(request));
202     const auto nnMeasure = NN_TRY(convertInput(measure));
203     const auto nnDeadline = NN_TRY(convertInput(deadline));
204     const auto nnLoopTimeoutDuration = NN_TRY(convertInput(loopTimeoutDuration));
205 
206     const std::any resource = preparedModel->getUnderlyingResource();
207     if (const auto* model = std::any_cast<const nn::Model*>(&resource)) {
208         CHECK(*model != nullptr);
209         NN_TRY(utils::makeGeneralFailure(nn::validateRequestForModel(nnRequest, **model),
210                                          nn::ErrorStatus::INVALID_ARGUMENT));
211     }
212 
213     Task task = [preparedModel, nnRequest = std::move(nnRequest), nnMeasure, nnDeadline,
214                  nnLoopTimeoutDuration, callback] {
215         auto result =
216                 preparedModel->execute(nnRequest, nnMeasure, nnDeadline, nnLoopTimeoutDuration);
217         notify(callback.get(), std::move(result));
218     };
219     executor(std::move(task), userId, nnDeadline);
220 
221     return {};
222 }
223 
executeSynchronously(const nn::SharedPreparedModel & preparedModel,const V1_0::Request & request,V1_2::MeasureTiming measure)224 nn::ExecutionResult<std::pair<hidl_vec<V1_2::OutputShape>, V1_2::Timing>> executeSynchronously(
225         const nn::SharedPreparedModel& preparedModel, const V1_0::Request& request,
226         V1_2::MeasureTiming measure) {
227     const auto nnRequest = NN_TRY(utils::makeExecutionFailure(convertInput(request)));
228     const auto nnMeasure = NN_TRY(utils::makeExecutionFailure(convertInput(measure)));
229 
230     const auto [outputShapes, timing] =
231             NN_TRY(preparedModel->execute(nnRequest, nnMeasure, {}, {}));
232 
233     auto hidlOutputShapes = NN_TRY(utils::makeExecutionFailure(V1_2::utils::convert(outputShapes)));
234     const auto hidlTiming = NN_TRY(utils::makeExecutionFailure(V1_2::utils::convert(timing)));
235     return std::make_pair(std::move(hidlOutputShapes), hidlTiming);
236 }
237 
executeSynchronously_1_3(const nn::SharedPreparedModel & preparedModel,const V1_3::Request & request,V1_2::MeasureTiming measure,const V1_3::OptionalTimePoint & deadline,const V1_3::OptionalTimeoutDuration & loopTimeoutDuration)238 nn::ExecutionResult<std::pair<hidl_vec<V1_2::OutputShape>, V1_2::Timing>> executeSynchronously_1_3(
239         const nn::SharedPreparedModel& preparedModel, const V1_3::Request& request,
240         V1_2::MeasureTiming measure, const V1_3::OptionalTimePoint& deadline,
241         const V1_3::OptionalTimeoutDuration& loopTimeoutDuration) {
242     const auto nnRequest = NN_TRY(utils::makeExecutionFailure(convertInput(request)));
243     const auto nnMeasure = NN_TRY(utils::makeExecutionFailure(convertInput(measure)));
244     const auto nnDeadline = NN_TRY(utils::makeExecutionFailure(convertInput(deadline)));
245     const auto nnLoopTimeoutDuration =
246             NN_TRY(utils::makeExecutionFailure(convertInput(loopTimeoutDuration)));
247 
248     const auto [outputShapes, timing] =
249             NN_TRY(preparedModel->execute(nnRequest, nnMeasure, nnDeadline, nnLoopTimeoutDuration));
250 
251     auto hidlOutputShapes = NN_TRY(utils::makeExecutionFailure(V1_3::utils::convert(outputShapes)));
252     const auto hidlTiming = NN_TRY(utils::makeExecutionFailure(V1_3::utils::convert(timing)));
253     return std::make_pair(std::move(hidlOutputShapes), hidlTiming);
254 }
255 
convertSyncFences(const hidl_vec<hidl_handle> & handles)256 nn::GeneralResult<std::vector<nn::SyncFence>> convertSyncFences(
257         const hidl_vec<hidl_handle>& handles) {
258     std::vector<nn::SyncFence> syncFences;
259     syncFences.reserve(handles.size());
260     for (const auto& handle : handles) {
261         auto nativeHandle = NN_TRY(convertInput(handle));
262         auto syncFence = NN_TRY(utils::makeGeneralFailure(
263                 nn::SyncFence::create(std::move(nativeHandle)), nn::ErrorStatus::INVALID_ARGUMENT));
264         syncFences.push_back(std::move(syncFence));
265     }
266     return syncFences;
267 }
268 
executeFenced(const nn::SharedPreparedModel & preparedModel,const V1_3::Request & request,const hidl_vec<hidl_handle> & waitFor,V1_2::MeasureTiming measure,const V1_3::OptionalTimePoint & deadline,const V1_3::OptionalTimeoutDuration & loopTimeoutDuration,const V1_3::OptionalTimeoutDuration & duration)269 nn::GeneralResult<std::pair<hidl_handle, sp<V1_3::IFencedExecutionCallback>>> executeFenced(
270         const nn::SharedPreparedModel& preparedModel, const V1_3::Request& request,
271         const hidl_vec<hidl_handle>& waitFor, V1_2::MeasureTiming measure,
272         const V1_3::OptionalTimePoint& deadline,
273         const V1_3::OptionalTimeoutDuration& loopTimeoutDuration,
274         const V1_3::OptionalTimeoutDuration& duration) {
275     const auto nnRequest = NN_TRY(convertInput(request));
276     const auto nnWaitFor = NN_TRY(convertSyncFences(waitFor));
277     const auto nnMeasure = NN_TRY(convertInput(measure));
278     const auto nnDeadline = NN_TRY(convertInput(deadline));
279     const auto nnLoopTimeoutDuration = NN_TRY(convertInput(loopTimeoutDuration));
280     const auto nnDuration = NN_TRY(convertInput(duration));
281 
282     auto [syncFence, executeFencedCallback] = NN_TRY(preparedModel->executeFenced(
283             nnRequest, nnWaitFor, nnMeasure, nnDeadline, nnLoopTimeoutDuration, nnDuration));
284 
285     auto hidlSyncFence = NN_TRY(V1_3::utils::convert(syncFence.getSharedHandle()));
286     auto hidlExecuteFencedCallback = sp<FencedExecutionCallback>::make(executeFencedCallback);
287     return std::make_pair(std::move(hidlSyncFence), std::move(hidlExecuteFencedCallback));
288 }
289 
290 }  // namespace
291 
PreparedModel(nn::SharedPreparedModel preparedModel,Executor executor,uid_t userId)292 PreparedModel::PreparedModel(nn::SharedPreparedModel preparedModel, Executor executor, uid_t userId)
293     : kPreparedModel(std::move(preparedModel)), kExecutor(std::move(executor)), kUserId(userId) {
294     CHECK(kPreparedModel != nullptr);
295     CHECK(kExecutor != nullptr);
296 }
297 
getUnderlyingPreparedModel() const298 nn::SharedPreparedModel PreparedModel::getUnderlyingPreparedModel() const {
299     return kPreparedModel;
300 }
301 
execute(const V1_0::Request & request,const sp<V1_0::IExecutionCallback> & callback)302 Return<V1_0::ErrorStatus> PreparedModel::execute(const V1_0::Request& request,
303                                                  const sp<V1_0::IExecutionCallback>& callback) {
304     auto result = adapter::execute(kPreparedModel, kUserId, kExecutor, request, callback);
305     if (!result.has_value()) {
306         auto [message, code] = std::move(result).error();
307         LOG(ERROR) << "adapter::PreparedModel::execute failed with " << code << ": " << message;
308         notify(callback.get(), code, {}, {});
309         return V1_0::utils::convert(code).value();
310     }
311     return V1_0::ErrorStatus::NONE;
312 }
313 
execute_1_2(const V1_0::Request & request,V1_2::MeasureTiming measure,const sp<V1_2::IExecutionCallback> & callback)314 Return<V1_0::ErrorStatus> PreparedModel::execute_1_2(const V1_0::Request& request,
315                                                      V1_2::MeasureTiming measure,
316                                                      const sp<V1_2::IExecutionCallback>& callback) {
317     auto result =
318             adapter::execute_1_2(kPreparedModel, kUserId, kExecutor, request, measure, callback);
319     if (!result.has_value()) {
320         auto [message, code] = std::move(result).error();
321         LOG(ERROR) << "adapter::PreparedModel::execute_1_2 failed with " << code << ": " << message;
322         notify(callback.get(), code, {}, {});
323         return V1_2::utils::convert(code).value();
324     }
325     return V1_0::ErrorStatus::NONE;
326 }
327 
execute_1_3(const V1_3::Request & request,V1_2::MeasureTiming measure,const V1_3::OptionalTimePoint & deadline,const V1_3::OptionalTimeoutDuration & loopTimeoutDuration,const sp<V1_3::IExecutionCallback> & callback)328 Return<V1_3::ErrorStatus> PreparedModel::execute_1_3(
329         const V1_3::Request& request, V1_2::MeasureTiming measure,
330         const V1_3::OptionalTimePoint& deadline,
331         const V1_3::OptionalTimeoutDuration& loopTimeoutDuration,
332         const sp<V1_3::IExecutionCallback>& callback) {
333     auto result = adapter::execute_1_3(kPreparedModel, kUserId, kExecutor, request, measure,
334                                        deadline, loopTimeoutDuration, callback);
335     if (!result.has_value()) {
336         auto [message, code] = std::move(result).error();
337         LOG(ERROR) << "adapter::PreparedModel::execute_1_3 failed with " << code << ": " << message;
338         notify(callback.get(), code, {}, {});
339         return V1_3::utils::convert(code).value();
340     }
341     return V1_3::ErrorStatus::NONE;
342 }
343 
executeSynchronously(const V1_0::Request & request,V1_2::MeasureTiming measure,executeSynchronously_cb cb)344 Return<void> PreparedModel::executeSynchronously(const V1_0::Request& request,
345                                                  V1_2::MeasureTiming measure,
346                                                  executeSynchronously_cb cb) {
347     auto result = adapter::executeSynchronously(kPreparedModel, request, measure);
348     if (!result.has_value()) {
349         auto [message, code, outputShapes] = std::move(result).error();
350         LOG(ERROR) << "adapter::PreparedModel::executeSynchronously failed with " << code << ": "
351                    << message;
352         cb(V1_2::utils::convert(code).value(), V1_2::utils::convert(outputShapes).value(),
353            V1_2::utils::kNoTiming);
354         return Void();
355     }
356     auto [outputShapes, timing] = std::move(result).value();
357     cb(V1_0::ErrorStatus::NONE, outputShapes, timing);
358     return Void();
359 }
360 
executeSynchronously_1_3(const V1_3::Request & request,V1_2::MeasureTiming measure,const V1_3::OptionalTimePoint & deadline,const V1_3::OptionalTimeoutDuration & loopTimeoutDuration,executeSynchronously_1_3_cb cb)361 Return<void> PreparedModel::executeSynchronously_1_3(
362         const V1_3::Request& request, V1_2::MeasureTiming measure,
363         const V1_3::OptionalTimePoint& deadline,
364         const V1_3::OptionalTimeoutDuration& loopTimeoutDuration, executeSynchronously_1_3_cb cb) {
365     auto result = adapter::executeSynchronously_1_3(kPreparedModel, request, measure, deadline,
366                                                     loopTimeoutDuration);
367     if (!result.has_value()) {
368         auto [message, code, outputShapes] = std::move(result).error();
369         LOG(ERROR) << "adapter::PreparedModel::executeSynchronously_1_3 failed with " << code
370                    << ": " << message;
371         cb(V1_3::utils::convert(code).value(), V1_3::utils::convert(outputShapes).value(),
372            V1_2::utils::kNoTiming);
373         return Void();
374     }
375     auto [outputShapes, timing] = std::move(result).value();
376     cb(V1_3::ErrorStatus::NONE, outputShapes, timing);
377     return Void();
378 }
379 
configureExecutionBurst(const sp<V1_2::IBurstCallback> & callback,const MQDescriptorSync<V1_2::FmqRequestDatum> & requestChannel,const MQDescriptorSync<V1_2::FmqResultDatum> & resultChannel,configureExecutionBurst_cb cb)380 Return<void> PreparedModel::configureExecutionBurst(
381         const sp<V1_2::IBurstCallback>& callback,
382         const MQDescriptorSync<V1_2::FmqRequestDatum>& requestChannel,
383         const MQDescriptorSync<V1_2::FmqResultDatum>& resultChannel,
384         configureExecutionBurst_cb cb) {
385     const sp<V1_2::IBurstContext> burst = nn::ExecutionBurstServer::create(
386             callback, requestChannel, resultChannel, this, std::chrono::microseconds{0});
387 
388     if (burst == nullptr) {
389         cb(V1_0::ErrorStatus::GENERAL_FAILURE, {});
390     } else {
391         cb(V1_0::ErrorStatus::NONE, burst);
392     }
393     return Void();
394 }
395 
executeFenced(const V1_3::Request & request,const hidl_vec<hidl_handle> & waitFor,V1_2::MeasureTiming measure,const V1_3::OptionalTimePoint & deadline,const V1_3::OptionalTimeoutDuration & loopTimeoutDuration,const V1_3::OptionalTimeoutDuration & duration,executeFenced_cb callback)396 Return<void> PreparedModel::executeFenced(const V1_3::Request& request,
397                                           const hidl_vec<hidl_handle>& waitFor,
398                                           V1_2::MeasureTiming measure,
399                                           const V1_3::OptionalTimePoint& deadline,
400                                           const V1_3::OptionalTimeoutDuration& loopTimeoutDuration,
401                                           const V1_3::OptionalTimeoutDuration& duration,
402                                           executeFenced_cb callback) {
403     auto result = adapter::executeFenced(kPreparedModel, request, waitFor, measure, deadline,
404                                          loopTimeoutDuration, duration);
405     if (!result.has_value()) {
406         auto [message, code] = std::move(result).error();
407         LOG(ERROR) << "adapter::PreparedModel::executeFenced failed with " << code << ": "
408                    << message;
409         callback(V1_3::utils::convert(code).value(), {}, nullptr);
410         return Void();
411     }
412     auto [syncFence, executeFencedCallback] = std::move(result).value();
413     callback(V1_3::ErrorStatus::NONE, syncFence, executeFencedCallback);
414     return Void();
415 }
416 
417 }  // namespace android::hardware::neuralnetworks::adapter
418