1 /*
2 * Copyright (C) 2021 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 #define LOG_TAG "ShimDevice"
18
19 #include "ShimDevice.h"
20
21 #include <NeuralNetworks.h>
22 #include <aidl/android/hardware/neuralnetworks/DataLocation.h>
23 #include <aidl/android/hardware/neuralnetworks/ErrorStatus.h>
24 #include <aidl/android/hardware/neuralnetworks/Extension.h>
25 #include <aidl/android/hardware/neuralnetworks/ExtensionOperandTypeInformation.h>
26 #include <aidl/android/hardware/neuralnetworks/Memory.h>
27 #include <aidl/android/hardware/neuralnetworks/NumberOfCacheFiles.h>
28 #include <aidl/android/hardware/neuralnetworks/OperandLifeTime.h>
29 #include <aidl/android/hardware/neuralnetworks/OperandPerformance.h>
30 #include <android-base/logging.h>
31 #include <android-base/scopeguard.h>
32 #include <android/binder_auto_utils.h>
33 #include <android/binder_manager.h>
34 #include <android/binder_process.h>
35 #include <nnapi/TypeUtils.h>
36 #include <nnapi/hal/aidl/Conversions.h>
37
38 #include <algorithm>
39 #include <limits>
40 #include <memory>
41 #include <optional>
42 #include <string>
43 #include <utility>
44 #include <vector>
45
46 #include "ShimConverter.h"
47 #include "ShimPreparedModel.h"
48 #include "ShimUtils.h"
49 #include "SupportLibrary.h"
50
51 using namespace ::android::nn::sl_wrapper;
52
53 namespace aidl::android::hardware::neuralnetworks {
54
55 namespace {
56
convertToNDKPriority(Priority priority)57 constexpr std::optional<::android::nn::wrapper::ExecutePriority> convertToNDKPriority(
58 Priority priority) {
59 switch (priority) {
60 case Priority::LOW:
61 return ::android::nn::wrapper::ExecutePriority::LOW;
62 case Priority::MEDIUM:
63 return ::android::nn::wrapper::ExecutePriority::MEDIUM;
64 case Priority::HIGH:
65 return ::android::nn::wrapper::ExecutePriority::HIGH;
66 }
67 LOG(ERROR) << "unrecognized priority: " << static_cast<int32_t>(priority);
68 return std::nullopt;
69 }
70
convertToNDKPreference(ExecutionPreference preference)71 constexpr std::optional<::android::nn::wrapper::ExecutePreference> convertToNDKPreference(
72 ExecutionPreference preference) {
73 switch (preference) {
74 case ExecutionPreference::LOW_POWER:
75 return ::android::nn::wrapper::ExecutePreference::PREFER_LOW_POWER;
76 case ExecutionPreference::FAST_SINGLE_ANSWER:
77 return ::android::nn::wrapper::ExecutePreference::PREFER_FAST_SINGLE_ANSWER;
78 case ExecutionPreference::SUSTAINED_SPEED:
79 return ::android::nn::wrapper::ExecutePreference::PREFER_SUSTAINED_SPEED;
80 }
81 LOG(ERROR) << "unrecognized preference: " << static_cast<int32_t>(preference);
82 return std::nullopt;
83 }
84
85 // Safely downcast an IPreparedModel object to ShimPreparedModel.
86 // This function will return nullptr if the IPreparedModel object is not originated from the
87 // shim process.
castToShimPreparedModel(IPreparedModel * preparedModel)88 const ShimPreparedModel* castToShimPreparedModel(IPreparedModel* preparedModel) {
89 if (preparedModel->isRemote()) {
90 return nullptr;
91 }
92 // This static_cast is safe because ShimPreparedModel is the only class that implements
93 // the IPreparedModel interface in the sample driver process.
94 return static_cast<const ShimPreparedModel*>(preparedModel);
95 }
96
convertPerformanceInfo(const SL_ANeuralNetworksPerformanceInfo & info)97 static PerformanceInfo convertPerformanceInfo(const SL_ANeuralNetworksPerformanceInfo& info) {
98 return {.execTime = info.execTime, .powerUsage = info.powerUsage};
99 }
100
getCapabilities(const NnApiSupportLibrary * nnapi,ANeuralNetworksDevice * device)101 Capabilities getCapabilities(const NnApiSupportLibrary* nnapi, ANeuralNetworksDevice* device) {
102 Capabilities capabilities;
103 SL_ANeuralNetworksPerformanceInfo performanceInfo;
104
105 nnapi->SL_ANeuralNetworksDevice_getPerformanceInfo(
106 device, SL_ANEURALNETWORKS_CAPABILITIES_PERFORMANCE_RELAXED_SCALAR, &performanceInfo);
107 capabilities.relaxedFloat32toFloat16PerformanceScalar = convertPerformanceInfo(performanceInfo);
108
109 nnapi->SL_ANeuralNetworksDevice_getPerformanceInfo(
110 device, SL_ANEURALNETWORKS_CAPABILITIES_PERFORMANCE_RELAXED_TENSOR, &performanceInfo);
111 capabilities.relaxedFloat32toFloat16PerformanceTensor = convertPerformanceInfo(performanceInfo);
112
113 nnapi->SL_ANeuralNetworksDevice_getPerformanceInfo(
114 device, SL_ANEURALNETWORKS_CAPABILITIES_PERFORMANCE_IF, &performanceInfo);
115 capabilities.ifPerformance = convertPerformanceInfo(performanceInfo);
116
117 nnapi->SL_ANeuralNetworksDevice_getPerformanceInfo(
118 device, SL_ANEURALNETWORKS_CAPABILITIES_PERFORMANCE_WHILE, &performanceInfo);
119 capabilities.whilePerformance = convertPerformanceInfo(performanceInfo);
120
121 constexpr auto fn = [](SL_ANeuralNetworksOperandPerformanceInfo info, void* context) {
122 auto* out = static_cast<std::vector<OperandPerformance>*>(context);
123 out->push_back(OperandPerformance{
124 .type = static_cast<OperandType>(info.operandType),
125 .info = convertPerformanceInfo(info.performanceInfo),
126 });
127 };
128
129 nnapi->SL_ANeuralNetworksDevice_forEachOperandPerformanceInfo(
130 device, static_cast<void*>(&capabilities.operandPerformance), fn);
131
132 return capabilities;
133 }
134
getNumberOfCacheFilesNeeded(const NnApiSupportLibrary * nnapi,ANeuralNetworksDevice * device)135 NumberOfCacheFiles getNumberOfCacheFilesNeeded(const NnApiSupportLibrary* nnapi,
136 ANeuralNetworksDevice* device) {
137 uint32_t numModelCacheFiles;
138 uint32_t numDataCacheFiles;
139 nnapi->SL_ANeuralNetworksDevice_getNumberOfCacheFilesNeeded(device, &numModelCacheFiles,
140 &numDataCacheFiles);
141 return {
142 .numModelCache = static_cast<int32_t>(numModelCacheFiles),
143 .numDataCache = static_cast<int32_t>(numDataCacheFiles),
144 };
145 }
146
getVendorExtensions(const NnApiSupportLibrary * nnapi,ANeuralNetworksDevice * device)147 std::vector<Extension> getVendorExtensions(const NnApiSupportLibrary* nnapi,
148 ANeuralNetworksDevice* device) {
149 uint32_t vendorExtensionCount;
150 nnapi->SL_ANeuralNetworksDevice_getVendorExtensionCount(device, &vendorExtensionCount);
151
152 std::vector<Extension> extensions(vendorExtensionCount);
153
154 for (uint32_t vendorExtensionIndex = 0; vendorExtensionIndex < vendorExtensionCount;
155 ++vendorExtensionIndex) {
156 auto& extension = extensions[vendorExtensionIndex];
157
158 const char* extensionName;
159 nnapi->SL_ANeuralNetworksDevice_getVendorExtensionName(device, vendorExtensionIndex,
160 &extensionName);
161 extension.name = extensionName;
162
163 constexpr auto fn = [](SL_ANeuralNetworksExtensionOperandTypeInformation info,
164 void* context) {
165 auto* out = static_cast<std::vector<ExtensionOperandTypeInformation>*>(context);
166 out->push_back(ExtensionOperandTypeInformation{
167 .type = info.type,
168 .isTensor = info.isTensor,
169 .byteSize = static_cast<int32_t>(info.byteSize),
170 });
171 };
172 nnapi->SL_ANeuralNetworksDevice_forEachVendorExtensionOperandTypeInformation(
173 device, vendorExtensionIndex, static_cast<void*>(&extension.operandTypes), fn);
174 }
175
176 return extensions;
177 }
178
179 } // namespace
180
ShimDevice(std::shared_ptr<const NnApiSupportLibrary> nnapi,ANeuralNetworksDevice * device,std::string serviceName)181 ShimDevice::ShimDevice(std::shared_ptr<const NnApiSupportLibrary> nnapi,
182 ANeuralNetworksDevice* device, std::string serviceName)
183 : mNnapi(std::move(nnapi)),
184 mBufferTracker(ShimBufferTracker::create()),
185 mServiceName(std::move(serviceName)),
186 mDevice(device),
187 mCapabilities(neuralnetworks::getCapabilities(mNnapi.get(), mDevice)),
188 mNumberOfCacheFiles(neuralnetworks::getNumberOfCacheFilesNeeded(mNnapi.get(), mDevice)),
189 mExtensions(neuralnetworks::getVendorExtensions(mNnapi.get(), mDevice)) {}
190
191 // Manages the data buffer for an operand.
192 class ShimBuffer : public BnBuffer {
193 public:
ShimBuffer(const NnApiSupportLibrary * nnApi,const::android::nn::Dimensions initialDimensions,const::android::nn::OperandType type,std::shared_ptr<::android::nn::sl_wrapper::Memory> memory,std::unique_ptr<ShimBufferTracker::Token> token)194 ShimBuffer(const NnApiSupportLibrary* nnApi, const ::android::nn::Dimensions initialDimensions,
195 const ::android::nn::OperandType type,
196 std::shared_ptr<::android::nn::sl_wrapper::Memory> memory,
197 std::unique_ptr<ShimBufferTracker::Token> token)
198 : kInitialDimensions(initialDimensions),
199 kType(type),
200 mNnApi(nnApi),
201 mMemory(std::move(memory)),
202 kToken(std::move(token)) {}
203
tensorHasUnspecifiedDimensions(::android::nn::OperandType type,const::android::nn::Dimensions & dimensions)204 bool tensorHasUnspecifiedDimensions(::android::nn::OperandType type,
205 const ::android::nn::Dimensions& dimensions) {
206 if (!::android::nn::isExtension(type)) {
207 if (isNonExtensionScalar(type)) {
208 return false;
209 }
210 }
211 return dimensions.size() == 0 || std::any_of(dimensions.begin(), dimensions.end(),
212 [](int32_t dim) { return dim == 0; });
213 }
214
validateDimensions(const::android::nn::Dimensions & dimensions)215 bool validateDimensions(const ::android::nn::Dimensions& dimensions) {
216 if (isNonExtensionScalar(kType)) {
217 if (!dimensions.empty()) {
218 LOG(ERROR) << "ShimBuffer::validateDimensions -- invalid dimensions for scalar "
219 "operand";
220 return false;
221 }
222 return true;
223 }
224
225 if (dimensions.empty()) {
226 if (tensorHasUnspecifiedDimensions(kType, kInitialDimensions)) {
227 LOG(ERROR) << "ShimBuffer::validateDimensions -- the initial dimensions are not "
228 "fully specified and no dimension update is provided: ";
229
230 return false;
231 }
232 } else {
233 if (tensorHasUnspecifiedDimensions(kType, dimensions)) {
234 LOG(ERROR) << "ShimBuffer::validateDimensions -- the updated dimensions are not "
235 "fully specified: ";
236
237 return false;
238 }
239 }
240
241 const auto combined = ::android::nn::combineDimensions(kInitialDimensions, dimensions);
242 if (!combined.has_value()) {
243 LOG(ERROR) << "ShimBuffer::validateDimensions -- incompatible dimensions";
244 return false;
245 }
246 return true;
247 }
248
copyFrom(const aidl::android::hardware::neuralnetworks::Memory & src,const std::vector<int32_t> & dimensions)249 ndk::ScopedAStatus copyFrom(const aidl::android::hardware::neuralnetworks::Memory& src,
250 const std::vector<int32_t>& dimensions) override {
251 auto memory = convertFromHAL(mNnApi, src);
252
253 if (!memory) {
254 LOG(ERROR) << "Failed to convert HAL Memory to SL memory";
255 return toAStatus(ErrorStatus::INVALID_ARGUMENT);
256 }
257 const auto unsignedDimensions = ::android::nn::toUnsigned(dimensions);
258 if (!unsignedDimensions.has_value()) {
259 return toAStatus(aidl_hal::ErrorStatus::INVALID_ARGUMENT,
260 unsignedDimensions.error().message);
261 }
262
263 if (!validateDimensions(unsignedDimensions.value())) {
264 LOG(ERROR) << "Invalid dimensions";
265 return toAStatus(ErrorStatus::INVALID_ARGUMENT);
266 }
267 Result result = memory->copyTo(*mMemory.get());
268
269 // Special case expected error status for uninitialized source memory
270 if (result == Result::BAD_DATA) {
271 // NNAPI Runtime reports both uninitialized memory
272 // and incompatible dimensions as BAD_DATA, but
273 // VTS expects to see INVALID_ARGUMENT for bad dimensions,
274 // and GENERAL_FAILURE for uninitialized memory.
275 if (memory->getSize() != mMemory->getSize()) {
276 return toAStatus(ErrorStatus::INVALID_ARGUMENT, "Incompatible sizes");
277 }
278
279 return toAStatus(ErrorStatus::GENERAL_FAILURE);
280 }
281 SLW2SAS_RETURN_IF_ERROR(result);
282 return ndk::ScopedAStatus::ok();
283 }
284
copyTo(const Memory & dst)285 ndk::ScopedAStatus copyTo(const Memory& dst) override {
286 auto memory = convertFromHAL(mNnApi, dst);
287
288 if (!memory) {
289 LOG(ERROR) << "Failed to convert HAL Memory to SL memory";
290 return toAStatus(ErrorStatus::INVALID_ARGUMENT);
291 }
292
293 Result result = mMemory->copyTo(*memory);
294 // Special case expected error status for uninitialized source memory
295 if (result == Result::BAD_DATA) {
296 // NNAPI Runtime reports both uninitialized memory
297 // and incompatible dimensions as BAD_DATA, but
298 // VTS expects to see INVALID_ARGUMENT for bad dimensions,
299 // and GENERAL_FAILURE for uninitialized memory.
300 if (memory->getSize() != mMemory->getSize()) {
301 return toAStatus(ErrorStatus::INVALID_ARGUMENT, "Incompatible sizes");
302 }
303 return toAStatus(ErrorStatus::GENERAL_FAILURE);
304 }
305 SLW2SAS_RETURN_IF_ERROR(result);
306 return ndk::ScopedAStatus::ok();
307 }
308
309 private:
310 const ::android::nn::Dimensions kInitialDimensions;
311 const ::android::nn::OperandType kType;
312
313 const NnApiSupportLibrary* mNnApi;
314 std::shared_ptr<::android::nn::sl_wrapper::Memory> mMemory;
315 const std::unique_ptr<ShimBufferTracker::Token> kToken;
316 };
317
allocate(const BufferDesc & desc,const std::vector<IPreparedModelParcel> & preparedModels,const std::vector<BufferRole> & inputRoles,const std::vector<BufferRole> & outputRoles,DeviceBuffer * buffer)318 ::ndk::ScopedAStatus ShimDevice::allocate(const BufferDesc& desc,
319 const std::vector<IPreparedModelParcel>& preparedModels,
320 const std::vector<BufferRole>& inputRoles,
321 const std::vector<BufferRole>& outputRoles,
322 DeviceBuffer* buffer) {
323 if (!isValidDimension(desc.dimensions)) {
324 LOG(ERROR) << "ShimDriver::allocate -- passed invalid dimension values.";
325 return toAStatus(ErrorStatus::INVALID_ARGUMENT,
326 "ShimDriver::allocate -- passed invalid dimension values");
327 }
328 ANeuralNetworksMemoryDesc* slDesc = nullptr;
329 mNnapi->ANeuralNetworksMemoryDesc_create(&slDesc);
330 const auto slDescGuard = ::android::base::make_scope_guard(
331 [this, slDesc] { mNnapi->ANeuralNetworksMemoryDesc_free(slDesc); });
332
333 auto unsignedDimensions = ::android::nn::toUnsigned(desc.dimensions).value();
334 if (mNnapi->ANeuralNetworksMemoryDesc_setDimensions(slDesc, desc.dimensions.size(),
335 unsignedDimensions.data()) !=
336 ANEURALNETWORKS_NO_ERROR) {
337 LOG(ERROR) << "ShimDriver::allocate -- ANeuralNetworksMemoryDesc_setDimensions fail.";
338 return toAStatus(ErrorStatus::INVALID_ARGUMENT,
339 "ShimDriver::allocate -- ANeuralNetworksMemoryDesc_setDimensions fail");
340 }
341
342 constexpr auto getCompilation = [](IPreparedModel* preparedModel) -> const ShimPreparedModel* {
343 const auto* samplePreparedModel = castToShimPreparedModel(preparedModel);
344 if (samplePreparedModel == nullptr) {
345 LOG(ERROR) << "ShimDriver::allocate -- unknown remote IPreparedModel.";
346 return nullptr;
347 }
348 return samplePreparedModel;
349 };
350
351 std::optional<::android::nn::OperandType> type;
352 std::vector<uint32_t> dimensions = ::android::nn::toUnsigned(desc.dimensions).value();
353
354 for (const auto& role : inputRoles) {
355 if (role.modelIndex < 0 || role.modelIndex >= preparedModels.size()) {
356 LOG(ERROR) << "Invalid modelIndex value " << role.modelIndex;
357 return toAStatus(ErrorStatus::INVALID_ARGUMENT,
358 "ShimDriver::allocate -- Input role modeIndex with invalid value");
359 }
360 auto preparedModel = preparedModels[role.modelIndex];
361 if (preparedModel.preparedModel == nullptr) {
362 return toAStatus(ErrorStatus::INVALID_ARGUMENT,
363 "ShimDriver::allocate -- nullptr model");
364 }
365
366 auto pmodel = getCompilation(preparedModel.preparedModel.get());
367 if (pmodel == nullptr) {
368 return toAStatus(ErrorStatus::INVALID_ARGUMENT,
369 "ShimDriver::allocate -- nullptr model");
370 }
371
372 auto result = mNnapi->ANeuralNetworksMemoryDesc_addInputRole(
373 slDesc, pmodel->getCompilation().getHandle(), role.ioIndex, role.probability);
374
375 if (result != ANEURALNETWORKS_NO_ERROR) {
376 LOG(ERROR) << "SampleDriver::allocate -- ANeuralNetworksMemoryDesc_addInputRole fail.";
377 return toAStatus(ErrorStatus::INVALID_ARGUMENT,
378 "ShimDriver::allocate -- ANeuralNetworksMemoryDesc_addInputRole fail");
379 }
380
381 const auto& model = pmodel->getMainModel();
382 const auto& op = model.getOperands()[model.getInputs()[role.ioIndex]];
383 auto operandType = static_cast<::android::nn::OperandType>(op.operandType.type);
384 if (!type) {
385 type = operandType;
386 }
387 if (dimensions.empty()) {
388 dimensions = op.dimensions;
389 }
390 }
391
392 for (const auto& role : outputRoles) {
393 if (role.modelIndex < 0 || role.modelIndex >= preparedModels.size()) {
394 LOG(ERROR) << "Invalid modelIndex value " << role.modelIndex;
395 return toAStatus(ErrorStatus::INVALID_ARGUMENT,
396 "ShimDriver::allocate -- Ou0tput role modeIndex with invalid value");
397 }
398 auto preparedModel = preparedModels[role.modelIndex];
399 if (preparedModel.preparedModel == nullptr) {
400 return toAStatus(ErrorStatus::INVALID_ARGUMENT,
401 "ShimDriver::allocate -- nullptr model");
402 }
403
404 auto pmodel = getCompilation(preparedModel.preparedModel.get());
405 if (pmodel == nullptr) {
406 return toAStatus(ErrorStatus::INVALID_ARGUMENT,
407 "ShimDriver::allocate -- nullptr model");
408 }
409
410 auto result = mNnapi->ANeuralNetworksMemoryDesc_addOutputRole(
411 slDesc, pmodel->getCompilation().getHandle(), role.ioIndex, role.probability);
412
413 if (result != ANEURALNETWORKS_NO_ERROR) {
414 LOG(ERROR) << "SampleDriver::allocate -- ANeuralNetworksMemoryDesc_addInputRole fail.";
415 return toAStatus(ErrorStatus::INVALID_ARGUMENT,
416 "ShimDriver::allocate -- ANeuralNetworksMemoryDesc_addInputRole fail");
417 }
418 const auto& model = pmodel->getMainModel();
419 const auto& op = model.getOperands()[model.getInputs()[role.ioIndex]];
420 auto operandType = static_cast<::android::nn::OperandType>(op.operandType.type);
421 if (!type) {
422 type = operandType;
423 }
424 if (dimensions.empty()) {
425 dimensions = op.dimensions;
426 }
427 }
428
429 auto typeSize = ::android::nn::getNonExtensionSize(*type, dimensions);
430 if (!typeSize.has_value()) {
431 return toAStatus(ErrorStatus::INVALID_ARGUMENT,
432 "ShimDriver::allocate -- failed to get underlying type size, "
433 "possibly an extension type");
434 }
435
436 mNnapi->ANeuralNetworksMemoryDesc_finish(slDesc);
437 auto memory =
438 std::make_shared<::android::nn::sl_wrapper::Memory>(mNnapi.get(), slDesc, *typeSize);
439
440 if (!memory->isValid()) {
441 LOG(ERROR) << "ShimDriver::allocate -- ANeuralNetworksMemory_createFromDesc failed.";
442 return toAStatus(ErrorStatus::GENERAL_FAILURE,
443 "ShimDriver::allocate -- ANeuralNetworksMemory_createFromDesc failed");
444 }
445
446 auto token = mBufferTracker->add(memory);
447 if (token == nullptr) {
448 LOG(ERROR) << "ShimDriver::allocate -- ShimBufferTracker returned invalid token.";
449 return toAStatus(ErrorStatus::GENERAL_FAILURE,
450 "ShimDriver::allocate -- ShimBufferTracker returned invalid token.");
451 }
452 const uint32_t tokenValue = token->get();
453 auto shimbuffer = ndk::SharedRefBase::make<ShimBuffer>(mNnapi.get(), dimensions, *type,
454 std::move(memory), std::move(token));
455 buffer->buffer = std::move(shimbuffer);
456 buffer->token = tokenValue;
457
458 return ndk::ScopedAStatus::ok();
459 }
460
getCapabilities(Capabilities * capabilities)461 ndk::ScopedAStatus ShimDevice::getCapabilities(Capabilities* capabilities) {
462 *capabilities = mCapabilities;
463 return ndk::ScopedAStatus::ok();
464 }
465
getNumberOfCacheFilesNeeded(NumberOfCacheFiles * numberOfCacheFiles)466 ndk::ScopedAStatus ShimDevice::getNumberOfCacheFilesNeeded(NumberOfCacheFiles* numberOfCacheFiles) {
467 *numberOfCacheFiles = mNumberOfCacheFiles;
468 return ndk::ScopedAStatus::ok();
469 }
470
getSupportedExtensions(std::vector<Extension> * extensions)471 ndk::ScopedAStatus ShimDevice::getSupportedExtensions(std::vector<Extension>* extensions) {
472 *extensions = mExtensions;
473 return ndk::ScopedAStatus::ok();
474 }
475
getSupportedOperations(const Model & model,std::vector<bool> * supportedOperations)476 ndk::ScopedAStatus ShimDevice::getSupportedOperations(const Model& model,
477 std::vector<bool>* supportedOperations) {
478 const auto numOperations = model.main.operations.size();
479 supportedOperations->resize(numOperations);
480
481 ErrorStatus convertErrorStatus = ErrorStatus::NONE;
482 std::vector<uint8_t> copiedOperandValues;
483 auto modelAndMemory =
484 convertFromHAL(mNnapi.get(), model, &copiedOperandValues, &convertErrorStatus);
485 if (!modelAndMemory || modelAndMemory->models.empty()) {
486 LOG(ERROR) << "Failed to convert HAL model to SL model";
487 return toAStatus(convertErrorStatus);
488 }
489
490 auto annModel = modelAndMemory->models[0].getHandle();
491 auto supportedOps = std::make_unique<bool[]>(numOperations);
492
493 auto result = mNnapi->ANeuralNetworksModel_getSupportedOperationsForDevices(
494 annModel, &mDevice, /*numDevices=*/1, supportedOps.get());
495 SLW2SAS_RETURN_IF_ERROR(result);
496
497 std::copy(supportedOps.get(), supportedOps.get() + numOperations, supportedOperations->begin());
498 return ndk::ScopedAStatus::ok();
499 }
500
getType(DeviceType * type)501 ndk::ScopedAStatus ShimDevice::getType(DeviceType* type) {
502 int32_t deviceType;
503 auto result = mNnapi->ANeuralNetworksDevice_getType(mDevice, &deviceType);
504 SLW2SAS_RETURN_IF_ERROR(result);
505 *type = static_cast<DeviceType>(deviceType);
506 return ndk::ScopedAStatus::ok();
507 }
508
getVersionString(std::string * versionString)509 ndk::ScopedAStatus ShimDevice::getVersionString(std::string* versionString) {
510 const char* buffer;
511 auto result = mNnapi->ANeuralNetworksDevice_getVersion(mDevice, &buffer);
512 SLW2SAS_RETURN_IF_ERROR(result);
513
514 *versionString = std::string(buffer);
515 return ndk::ScopedAStatus::ok();
516 }
517
getIntFds(const std::vector<::ndk::ScopedFileDescriptor> & scopedFds)518 static std::vector<int> getIntFds(const std::vector<::ndk::ScopedFileDescriptor>& scopedFds) {
519 std::vector<int> fds;
520 fds.reserve(scopedFds.size());
521 for (const auto& scopedFd : scopedFds) {
522 fds.push_back(scopedFd.get());
523 }
524 return fds;
525 }
526
prepareModel(const Model & model,ExecutionPreference preference,Priority priority,int64_t deadlineNs,const std::vector<::ndk::ScopedFileDescriptor> & modelCache,const std::vector<::ndk::ScopedFileDescriptor> & dataCache,const std::vector<uint8_t> & token,const std::shared_ptr<IPreparedModelCallback> & callback)527 ndk::ScopedAStatus ShimDevice::prepareModel(
528 const Model& model, ExecutionPreference preference, Priority priority, int64_t deadlineNs,
529 const std::vector<::ndk::ScopedFileDescriptor>& modelCache,
530 const std::vector<::ndk::ScopedFileDescriptor>& dataCache,
531 const std::vector<uint8_t>& token,
532 const std::shared_ptr<IPreparedModelCallback>& callback) {
533 // TODO(183398748): Run model preparation in detached thread.
534 if (callback == nullptr) {
535 return toAStatus(ErrorStatus::INVALID_ARGUMENT);
536 }
537
538 auto ndkPreference = convertToNDKPreference(preference);
539 if (!ndkPreference) {
540 callback->notify(ErrorStatus::INVALID_ARGUMENT, nullptr);
541 return toAStatus(ErrorStatus::INVALID_ARGUMENT);
542 }
543 auto ndkPriority = convertToNDKPriority(priority);
544 if (!ndkPriority) {
545 callback->notify(ErrorStatus::INVALID_ARGUMENT, nullptr);
546 return toAStatus(ErrorStatus::INVALID_ARGUMENT);
547 }
548
549 ErrorStatus convertErrorStatus = ErrorStatus::NONE;
550 std::vector<uint8_t> copiedOperandValues;
551 auto modelAndMemory =
552 convertFromHAL(mNnapi.get(), model, &copiedOperandValues, &convertErrorStatus);
553
554 if (!modelAndMemory || modelAndMemory->models.empty()) {
555 callback->notify(ErrorStatus::INVALID_ARGUMENT, nullptr);
556 return toAStatus(convertErrorStatus);
557 }
558
559 // b/185976051, past this point we pretend that compilation is asynchronous, and in
560 /// case of error we return OK status, but communicate the error through the callback.
561 auto compilation = ::android::nn::sl_wrapper::Compilation::createForDevice(
562 mNnapi.get(), &modelAndMemory->models[0], mDevice);
563
564 SLW2SAS_OK_RETURN_AND_ERROR_CALLBACK_IF_ERROR(compilation.first, callback);
565 SLW2SAS_OK_RETURN_AND_ERROR_CALLBACK_IF_ERROR(compilation.second.setPreference(*ndkPreference),
566 callback);
567 SLW2SAS_OK_RETURN_AND_ERROR_CALLBACK_IF_ERROR(compilation.second.setPriority(*ndkPriority),
568 callback);
569 if (deadlineNs > -1) {
570 std::chrono::time_point<::android::base::boot_clock> deadlinePoint(
571 std::chrono::nanoseconds{deadlineNs});
572 const auto currentTime = ::android::base::boot_clock::now();
573 const auto timeoutDuration = std::chrono::nanoseconds(deadlinePoint - currentTime);
574 if (timeoutDuration <= std::chrono::nanoseconds::zero()) {
575 callback->notify(ErrorStatus::MISSED_DEADLINE_TRANSIENT, nullptr);
576 return ndk::ScopedAStatus::ok();
577 }
578 SLW2SAS_OK_RETURN_AND_ERROR_CALLBACK_IF_ERROR(
579 compilation.second.setTimeout(std::max<uint64_t>(1, timeoutDuration.count())),
580 callback);
581 }
582 if (!modelCache.empty() || !dataCache.empty()) {
583 SLW2SAS_OK_RETURN_AND_ERROR_CALLBACK_IF_ERROR(
584 compilation.second.setCachingFromFds(getIntFds(modelCache), getIntFds(dataCache),
585 token),
586 callback);
587 }
588 SLW2SAS_OK_RETURN_AND_ERROR_CALLBACK_IF_ERROR(compilation.second.finish(), callback);
589
590 const std::shared_ptr<ShimPreparedModel> preparedModel =
591 ndk::SharedRefBase::make<ShimPreparedModel>(
592 mNnapi, mBufferTracker, std::move(compilation.second),
593 std::move(modelAndMemory->models), std::move(modelAndMemory->memory),
594 std::move(copiedOperandValues));
595
596 callback->notify(ErrorStatus::NONE, preparedModel);
597 return ndk::ScopedAStatus::ok();
598 }
599
prepareModelFromCache(int64_t,const std::vector<::ndk::ScopedFileDescriptor> &,const std::vector<::ndk::ScopedFileDescriptor> &,const std::vector<uint8_t> &,const std::shared_ptr<IPreparedModelCallback> & callback)600 ndk::ScopedAStatus ShimDevice::prepareModelFromCache(
601 int64_t /*deadlineNs*/, const std::vector<::ndk::ScopedFileDescriptor>& /*modelCache*/,
602 const std::vector<::ndk::ScopedFileDescriptor>& /*dataCache*/,
603 const std::vector<uint8_t>& /*token*/,
604 const std::shared_ptr<IPreparedModelCallback>& callback) {
605 // The NNAPI runtime will attempt to call this before falling back to
606 // ShimDevice::prepareModel(). This is not a LOG(ERROR) to avoid producing
607 // misleading logcat messages on every compilation request because there is
608 // technically nothing wrong.
609 LOG(DEBUG) << "ShimDevice::prepareModelFromCache() is not supported. Use "
610 "ShimDevice::prepareModel() instead.";
611 const auto ret = callback->notify(ErrorStatus::GENERAL_FAILURE, nullptr);
612 return toAStatus(ErrorStatus::GENERAL_FAILURE);
613 }
614
615 } // namespace aidl::android::hardware::neuralnetworks
616