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 "Callbacks.h"
20 #include "Conversions.h"
21 #include "Execution.h"
22 #include "Utils.h"
23 
24 #include <android/hardware/neuralnetworks/1.0/types.h>
25 #include <android/hardware/neuralnetworks/1.1/types.h>
26 #include <android/hardware/neuralnetworks/1.2/types.h>
27 #include <android/hardware/neuralnetworks/1.3/IPreparedModel.h>
28 #include <android/hardware/neuralnetworks/1.3/types.h>
29 #include <nnapi/IPreparedModel.h>
30 #include <nnapi/Result.h>
31 #include <nnapi/TypeUtils.h>
32 #include <nnapi/Types.h>
33 #include <nnapi/hal/1.0/HandleError.h>
34 #include <nnapi/hal/1.0/ProtectCallback.h>
35 #include <nnapi/hal/1.2/Burst.h>
36 #include <nnapi/hal/1.2/BurstUtils.h>
37 #include <nnapi/hal/1.2/Conversions.h>
38 #include <nnapi/hal/CommonUtils.h>
39 
40 #include <memory>
41 #include <tuple>
42 #include <utility>
43 #include <vector>
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::V1_3::utils {
49 namespace {
50 
convertFencedExecutionCallbackResults(ErrorStatus status,const V1_2::Timing & timingLaunched,const V1_2::Timing & timingFenced)51 nn::GeneralResult<std::pair<nn::Timing, nn::Timing>> convertFencedExecutionCallbackResults(
52         ErrorStatus status, const V1_2::Timing& timingLaunched, const V1_2::Timing& timingFenced) {
53     HANDLE_STATUS_HIDL(status) << "fenced execution callback info failed with " << toString(status);
54     return std::make_pair(NN_TRY(nn::convert(timingLaunched)), NN_TRY(nn::convert(timingFenced)));
55 }
56 
fencedExecutionCallback(ErrorStatus status,const hidl_handle & syncFence,const sp<IFencedExecutionCallback> & callback)57 nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>> fencedExecutionCallback(
58         ErrorStatus status, const hidl_handle& syncFence,
59         const sp<IFencedExecutionCallback>& callback) {
60     HANDLE_STATUS_HIDL(status) << "fenced execution failed with " << toString(status);
61 
62     auto resultSyncFence = nn::SyncFence::createAsSignaled();
63     if (syncFence.getNativeHandle() != nullptr) {
64         auto sharedHandle = NN_TRY(nn::convert(syncFence));
65         resultSyncFence = NN_TRY(nn::SyncFence::create(std::move(sharedHandle)));
66     }
67 
68     if (callback == nullptr) {
69         return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) << "callback is null";
70     }
71 
72     // Create callback which can be used to retrieve the execution error status and timings.
73     nn::ExecuteFencedInfoCallback resultCallback =
74             [callback]() -> nn::GeneralResult<std::pair<nn::Timing, nn::Timing>> {
75         auto cb = hal::utils::CallbackValue(convertFencedExecutionCallbackResults);
76 
77         const auto ret = callback->getExecutionInfo(cb);
78         HANDLE_TRANSPORT_FAILURE(ret);
79 
80         return cb.take();
81     };
82 
83     return std::make_pair(std::move(resultSyncFence), std::move(resultCallback));
84 }
85 
86 }  // namespace
87 
create(sp<V1_3::IPreparedModel> preparedModel,bool executeSynchronously)88 nn::GeneralResult<std::shared_ptr<const PreparedModel>> PreparedModel::create(
89         sp<V1_3::IPreparedModel> preparedModel, bool executeSynchronously) {
90     if (preparedModel == nullptr) {
91         return NN_ERROR() << "V1_3::utils::PreparedModel::create must have non-null preparedModel";
92     }
93 
94     auto deathHandler = NN_TRY(hal::utils::DeathHandler::create(preparedModel));
95     return std::make_shared<const PreparedModel>(PrivateConstructorTag{}, executeSynchronously,
96                                                  std::move(preparedModel), std::move(deathHandler));
97 }
98 
PreparedModel(PrivateConstructorTag,bool executeSynchronously,sp<V1_3::IPreparedModel> preparedModel,hal::utils::DeathHandler deathHandler)99 PreparedModel::PreparedModel(PrivateConstructorTag /*tag*/, bool executeSynchronously,
100                              sp<V1_3::IPreparedModel> preparedModel,
101                              hal::utils::DeathHandler deathHandler)
102     : kExecuteSynchronously(executeSynchronously),
103       kPreparedModel(std::move(preparedModel)),
104       kDeathHandler(std::move(deathHandler)) {}
105 
106 nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>
executeSynchronously(const Request & request,V1_2::MeasureTiming measure,const OptionalTimePoint & deadline,const OptionalTimeoutDuration & loopTimeoutDuration) const107 PreparedModel::executeSynchronously(const Request& request, V1_2::MeasureTiming measure,
108                                     const OptionalTimePoint& deadline,
109                                     const OptionalTimeoutDuration& loopTimeoutDuration) const {
110     auto cb = hal::utils::CallbackValue(executionCallback);
111 
112     const auto ret = kPreparedModel->executeSynchronously_1_3(request, measure, deadline,
113                                                               loopTimeoutDuration, cb);
114     HANDLE_TRANSPORT_FAILURE(ret);
115 
116     return cb.take();
117 }
118 
119 nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>
executeAsynchronously(const Request & request,V1_2::MeasureTiming measure,const OptionalTimePoint & deadline,const OptionalTimeoutDuration & loopTimeoutDuration) const120 PreparedModel::executeAsynchronously(const Request& request, V1_2::MeasureTiming measure,
121                                      const OptionalTimePoint& deadline,
122                                      const OptionalTimeoutDuration& loopTimeoutDuration) const {
123     const auto cb = sp<ExecutionCallback>::make();
124     const auto scoped = kDeathHandler.protectCallback(cb.get());
125 
126     const auto ret =
127             kPreparedModel->execute_1_3(request, measure, deadline, loopTimeoutDuration, cb);
128     const auto status = HANDLE_TRANSPORT_FAILURE(ret);
129     if (status != ErrorStatus::OUTPUT_INSUFFICIENT_SIZE) {
130         HANDLE_STATUS_HIDL(status) << "execution failed with " << toString(status);
131     }
132 
133     return cb->get();
134 }
135 
execute(const nn::Request & request,nn::MeasureTiming measure,const nn::OptionalTimePoint & deadline,const nn::OptionalDuration & loopTimeoutDuration,const std::vector<nn::TokenValuePair> &,const std::vector<nn::ExtensionNameAndPrefix> &) const136 nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> PreparedModel::execute(
137         const nn::Request& request, nn::MeasureTiming measure,
138         const nn::OptionalTimePoint& deadline, const nn::OptionalDuration& loopTimeoutDuration,
139         const std::vector<nn::TokenValuePair>& /*hints*/,
140         const std::vector<nn::ExtensionNameAndPrefix>& /*extensionNameToPrefix*/) const {
141     // Ensure that request is ready for IPC.
142     std::optional<nn::Request> maybeRequestInShared;
143     hal::utils::RequestRelocation relocation;
144     const nn::Request& requestInShared = NN_TRY(hal::utils::convertRequestFromPointerToShared(
145             &request, nn::kDefaultRequestMemoryAlignment, nn::kMinMemoryPadding,
146             &maybeRequestInShared, &relocation));
147 
148     const auto hidlRequest = NN_TRY(convert(requestInShared));
149     const auto hidlMeasure = NN_TRY(convert(measure));
150     const auto hidlDeadline = NN_TRY(convert(deadline));
151     const auto hidlLoopTimeoutDuration = NN_TRY(convert(loopTimeoutDuration));
152 
153     return executeInternal(hidlRequest, hidlMeasure, hidlDeadline, hidlLoopTimeoutDuration,
154                            relocation);
155 }
156 
157 nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>
executeInternal(const Request & request,V1_2::MeasureTiming measure,const OptionalTimePoint & deadline,const OptionalTimeoutDuration & loopTimeoutDuration,const hal::utils::RequestRelocation & relocation) const158 PreparedModel::executeInternal(const Request& request, V1_2::MeasureTiming measure,
159                                const OptionalTimePoint& deadline,
160                                const OptionalTimeoutDuration& loopTimeoutDuration,
161                                const hal::utils::RequestRelocation& relocation) const {
162     if (relocation.input) {
163         relocation.input->flush();
164     }
165 
166     auto result = kExecuteSynchronously
167                           ? executeSynchronously(request, measure, deadline, loopTimeoutDuration)
168                           : executeAsynchronously(request, measure, deadline, loopTimeoutDuration);
169     auto [outputShapes, timing] = NN_TRY(std::move(result));
170 
171     if (relocation.output) {
172         relocation.output->flush();
173     }
174     return std::make_pair(std::move(outputShapes), timing);
175 }
176 
177 nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>>
executeFenced(const nn::Request & request,const std::vector<nn::SyncFence> & waitFor,nn::MeasureTiming measure,const nn::OptionalTimePoint & deadline,const nn::OptionalDuration & loopTimeoutDuration,const nn::OptionalDuration & timeoutDurationAfterFence,const std::vector<nn::TokenValuePair> &,const std::vector<nn::ExtensionNameAndPrefix> &) const178 PreparedModel::executeFenced(
179         const nn::Request& request, const std::vector<nn::SyncFence>& waitFor,
180         nn::MeasureTiming measure, const nn::OptionalTimePoint& deadline,
181         const nn::OptionalDuration& loopTimeoutDuration,
182         const nn::OptionalDuration& timeoutDurationAfterFence,
183         const std::vector<nn::TokenValuePair>& /*hints*/,
184         const std::vector<nn::ExtensionNameAndPrefix>& /*extensionNameToPrefix*/) const {
185     // Ensure that request is ready for IPC.
186     std::optional<nn::Request> maybeRequestInShared;
187     hal::utils::RequestRelocation relocation;
188     const nn::Request& requestInShared = NN_TRY(hal::utils::convertRequestFromPointerToShared(
189             &request, nn::kDefaultRequestMemoryAlignment, nn::kMinMemoryPadding,
190             &maybeRequestInShared, &relocation));
191 
192     const auto hidlRequest = NN_TRY(convert(requestInShared));
193     const auto hidlWaitFor = NN_TRY(convertSyncFences(waitFor));
194     const auto hidlMeasure = NN_TRY(convert(measure));
195     const auto hidlDeadline = NN_TRY(convert(deadline));
196     const auto hidlLoopTimeoutDuration = NN_TRY(convert(loopTimeoutDuration));
197     const auto hidlTimeoutDurationAfterFence = NN_TRY(convert(timeoutDurationAfterFence));
198 
199     return executeFencedInternal(hidlRequest, hidlWaitFor, hidlMeasure, hidlDeadline,
200                                  hidlLoopTimeoutDuration, hidlTimeoutDurationAfterFence,
201                                  relocation);
202 }
203 
204 nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>>
executeFencedInternal(const Request & request,const hidl_vec<hidl_handle> & waitFor,V1_2::MeasureTiming measure,const OptionalTimePoint & deadline,const OptionalTimeoutDuration & loopTimeoutDuration,const OptionalTimeoutDuration & timeoutDurationAfterFence,const hal::utils::RequestRelocation & relocation) const205 PreparedModel::executeFencedInternal(const Request& request, const hidl_vec<hidl_handle>& waitFor,
206                                      V1_2::MeasureTiming measure, const OptionalTimePoint& deadline,
207                                      const OptionalTimeoutDuration& loopTimeoutDuration,
208                                      const OptionalTimeoutDuration& timeoutDurationAfterFence,
209                                      const hal::utils::RequestRelocation& relocation) const {
210     if (relocation.input) {
211         relocation.input->flush();
212     }
213 
214     auto cb = hal::utils::CallbackValue(fencedExecutionCallback);
215 
216     const auto ret =
217             kPreparedModel->executeFenced(request, waitFor, measure, deadline, loopTimeoutDuration,
218                                           timeoutDurationAfterFence, cb);
219     HANDLE_TRANSPORT_FAILURE(ret);
220     auto [syncFence, callback] = NN_TRY(cb.take());
221 
222     // If executeFenced required the request memory to be moved into shared memory, block here until
223     // the fenced execution has completed and flush the memory back.
224     if (relocation.output) {
225         const auto state = syncFence.syncWait({});
226         if (state != nn::SyncFence::FenceState::SIGNALED) {
227             return NN_ERROR() << "syncWait failed with " << state;
228         }
229         relocation.output->flush();
230     }
231 
232     return std::make_pair(std::move(syncFence), std::move(callback));
233 }
234 
createReusableExecution(const nn::Request & request,nn::MeasureTiming measure,const nn::OptionalDuration & loopTimeoutDuration,const std::vector<nn::TokenValuePair> &,const std::vector<nn::ExtensionNameAndPrefix> &) const235 nn::GeneralResult<nn::SharedExecution> PreparedModel::createReusableExecution(
236         const nn::Request& request, nn::MeasureTiming measure,
237         const nn::OptionalDuration& loopTimeoutDuration,
238         const std::vector<nn::TokenValuePair>& /*hints*/,
239         const std::vector<nn::ExtensionNameAndPrefix>& /*extensionNameToPrefix*/) const {
240     // Ensure that request is ready for IPC.
241     std::optional<nn::Request> maybeRequestInShared;
242     hal::utils::RequestRelocation relocation;
243     const nn::Request& requestInShared = NN_TRY(hal::utils::convertRequestFromPointerToShared(
244             &request, nn::kDefaultRequestMemoryAlignment, nn::kMinMemoryPadding,
245             &maybeRequestInShared, &relocation));
246 
247     auto hidlRequest = NN_TRY(convert(requestInShared));
248     auto hidlMeasure = NN_TRY(convert(measure));
249     auto hidlLoopTimeoutDuration = NN_TRY(convert(loopTimeoutDuration));
250     return Execution::create(shared_from_this(), std::move(hidlRequest), std::move(relocation),
251                              hidlMeasure, std::move(hidlLoopTimeoutDuration));
252 }
253 
configureExecutionBurst() const254 nn::GeneralResult<nn::SharedBurst> PreparedModel::configureExecutionBurst() const {
255     const auto pollingTimeWindow = V1_2::utils::getBurstControllerPollingTimeWindow();
256     return V1_2::utils::Burst::create(shared_from_this(), kPreparedModel, pollingTimeWindow);
257 }
258 
getUnderlyingResource() const259 std::any PreparedModel::getUnderlyingResource() const {
260     sp<V1_3::IPreparedModel> resource = kPreparedModel;
261     return resource;
262 }
263 
264 }  // namespace android::hardware::neuralnetworks::V1_3::utils
265