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 "ShimConverter"
18 
19 #include "ShimConverter.h"
20 
21 #include <aidlcommonsupport/NativeHandle.h>
22 #include <android-base/logging.h>
23 #include <android-base/mapped_file.h>
24 #include <android-base/scopeguard.h>
25 #include <android/hardware_buffer.h>
26 #include <cutils/native_handle.h>
27 #include <nnapi/TypeUtils.h>
28 #include <nnapi/hal/aidl/Conversions.h>
29 #include <nnapi/hal/aidl/Utils.h>
30 #include <sys/mman.h>
31 #include <vndk/hardware_buffer.h>
32 
33 #include <algorithm>
34 #include <memory>
35 #include <string>
36 #include <utility>
37 #include <vector>
38 
39 using namespace ::android::nn::sl_wrapper;
40 
41 namespace aidl::android::hardware::neuralnetworks {
42 
43 namespace {
44 
45 // Assumes that isValid(model) holds
convertSubgraphFromHAL(const NnApiSupportLibrary * nnapi,const std::vector<std::unique_ptr<::android::nn::sl_wrapper::Memory>> & memoryPools,const neuralnetworks::Model & model,std::vector<std::optional<::android::nn::sl_wrapper::Model>> * allModels,size_t subgraphIndex,const std::vector<uint8_t> & copiedOperandValues,ErrorStatus * errorStatus)46 ANeuralNetworksModel* convertSubgraphFromHAL(
47         const NnApiSupportLibrary* nnapi,
48         const std::vector<std::unique_ptr<::android::nn::sl_wrapper::Memory>>& memoryPools,
49         const neuralnetworks::Model& model,
50         std::vector<std::optional<::android::nn::sl_wrapper::Model>>* allModels,
51         size_t subgraphIndex, const std::vector<uint8_t>& copiedOperandValues,
52         ErrorStatus* errorStatus) {
53     *errorStatus = ErrorStatus::NONE;
54     if (allModels == nullptr || subgraphIndex >= (*allModels).size()) {
55         *errorStatus = ErrorStatus::INVALID_ARGUMENT;
56         return nullptr;
57     }
58     if ((*allModels)[subgraphIndex].has_value()) {
59         return (*allModels)[subgraphIndex]->getHandle();
60     }
61 
62     const auto& subgraph = subgraphIndex == 0 ? model.main : model.referenced[subgraphIndex - 1];
63     ::android::nn::sl_wrapper::Model resultModel(nnapi);
64 
65     resultModel.relaxComputationFloat32toFloat16(model.relaxComputationFloat32toFloat16);
66 
67     auto getExtensionName = [&](uint16_t prefix) -> const std::string* {
68         for (const auto& nameToPrefix : model.extensionNameToPrefix) {
69             if (prefix == nameToPrefix.prefix) {
70                 return &nameToPrefix.name;
71             }
72         }
73         return nullptr;
74     };
75 
76     for (int i = 0; i < subgraph.operands.size(); ++i) {
77         const auto& operand = subgraph.operands[i];
78 
79         const std::vector<uint32_t> dimensions =
80                 ::android::nn::toUnsigned(operand.dimensions).value();
81 
82         ::android::nn::wrapper::OperandType operandType(
83                 static_cast<::android::nn::wrapper::Type>(operand.type), dimensions, operand.scale,
84                 operand.zeroPoint);
85 
86         if (operand.type == OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL) {
87             const auto& params = operand.extraParams->get<OperandExtraParams::Tag::channelQuant>();
88             operandType.channelQuant = ::android::nn::wrapper::SymmPerChannelQuantParams(
89                     params.scales, static_cast<uint32_t>(params.channelDim));
90         }
91 
92         if (::android::nn::isExtension(static_cast<::android::nn::OperandType>(operand.type))) {
93             uint16_t extensionPrefix =
94                     ::android::nn::getExtensionPrefix(static_cast<uint32_t>(operand.type));
95             uint16_t typeWithinExtension =
96                     ::android::nn::getTypeWithinExtension(static_cast<uint32_t>(operand.type));
97 
98             auto* extensionName = getExtensionName(extensionPrefix);
99             if (extensionName == nullptr) {
100                 LOG(ERROR) << "Unknown extension prefix " << extensionPrefix;
101                 *errorStatus = ErrorStatus::INVALID_ARGUMENT;
102                 return nullptr;
103             }
104             resultModel.getExtensionOperandType(*extensionName, typeWithinExtension,
105                                                 &operandType.operandType.type);
106             if (!resultModel.isValid()) {
107                 LOG(ERROR) << "Failed to get extension operand with index " << i;
108                 *errorStatus = ErrorStatus::INVALID_ARGUMENT;
109                 return nullptr;
110             }
111         }
112 
113         uint32_t operandIndex = resultModel.addOperand(&operandType);
114         if (!resultModel.isValid()) {
115             LOG(ERROR) << "Failed to add operand with index " << i;
116             *errorStatus = ErrorStatus::INVALID_ARGUMENT;
117             return nullptr;
118         }
119 
120         if (operand.extraParams &&
121             operand.extraParams->getTag() == OperandExtraParams::Tag::extension) {
122             const auto& extensionData =
123                     operand.extraParams->get<OperandExtraParams::Tag::extension>();
124             resultModel.setOperandExtensionData(operandIndex, extensionData.data(),
125                                                 extensionData.size());
126             if (!resultModel.isValid()) {
127                 LOG(ERROR) << "Failed to add extension data for operand with index " << i;
128                 *errorStatus = ErrorStatus::INVALID_ARGUMENT;
129                 return nullptr;
130             }
131         }
132 
133         switch (operand.lifetime) {
134             case OperandLifeTime::CONSTANT_COPY: {
135                 if (operand.location.length + operand.location.offset >
136                     model.operandValues.size()) {
137                     *errorStatus = ErrorStatus::INVALID_ARGUMENT;
138                     return nullptr;
139                 }
140 
141                 if (operand.location.length <=
142                     ANEURALNETWORKS_MAX_SIZE_OF_IMMEDIATELY_COPIED_VALUES) {
143                     resultModel.setOperandValue(
144                             i, model.operandValues.data() + operand.location.offset,
145                             operand.location.length);
146                 } else {
147                     // If length is larger than 128 bytes, we are responsible for making sure
148                     // that value outlives the model. If this case exists, then we created
149                     // an internal copy, that is used here:
150                     resultModel.setOperandValue(
151                             i, copiedOperandValues.data() + operand.location.offset,
152                             operand.location.length);
153                 }
154                 break;
155             }
156             case OperandLifeTime::CONSTANT_POOL: {
157                 if (operand.location.poolIndex >= memoryPools.size()) {
158                     *errorStatus = ErrorStatus::INVALID_ARGUMENT;
159                     return nullptr;
160                 }
161                 resultModel.setOperandValueFromMemory(
162                         i, memoryPools[operand.location.poolIndex].get(), operand.location.offset,
163                         operand.location.length);
164                 break;
165             }
166             case OperandLifeTime::SUBGRAPH: {
167                 ErrorStatus otherErrorStatus = ErrorStatus::NONE;
168                 auto subgraph = convertSubgraphFromHAL(nnapi, memoryPools, model, allModels,
169                                                        operand.location.offset + 1,
170                                                        copiedOperandValues, &otherErrorStatus);
171                 if (subgraph) {
172                     resultModel.setOperandValueFromModel(i, subgraph);
173                 } else {
174                     LOG(ERROR) << "Failed to set subgraph operand value";
175                     *errorStatus = otherErrorStatus;
176                     return nullptr;
177                 }
178                 break;
179             }
180             case OperandLifeTime::NO_VALUE: {
181                 resultModel.setOperandValue(i, nullptr, 0);
182                 break;
183             }
184             case OperandLifeTime::TEMPORARY_VARIABLE:
185             case OperandLifeTime::SUBGRAPH_OUTPUT:
186             case OperandLifeTime::SUBGRAPH_INPUT: {
187                 break;
188             }
189             default:
190                 LOG(ERROR) << "Invalid operand type: " << static_cast<int>(operand.lifetime);
191                 *errorStatus = ErrorStatus::INVALID_ARGUMENT;
192                 return nullptr;
193         }
194 
195         if (!resultModel.isValid()) {
196             LOG(ERROR) << "Failed to add operand with index " << i;
197             *errorStatus = ErrorStatus::INVALID_ARGUMENT;
198             return nullptr;
199         }
200     }
201 
202     for (int i = 0; i < subgraph.operations.size(); ++i) {
203         const auto& operation = subgraph.operations[i];
204 
205         std::vector<uint32_t> inputs(operation.inputs.begin(), operation.inputs.end());
206         std::vector<uint32_t> outputs(operation.outputs.begin(), operation.outputs.end());
207 
208         int operationType = static_cast<int>(operation.type);
209         if (::android::nn::isExtension(static_cast<::android::nn::OperationType>(operationType))) {
210             uint16_t extensionPrefix =
211                     ::android::nn::getExtensionPrefix(static_cast<uint32_t>(operationType));
212             uint16_t typeWithinExtension =
213                     ::android::nn::getTypeWithinExtension(static_cast<uint32_t>(operationType));
214             auto* extensionName = getExtensionName(extensionPrefix);
215             if (extensionName == nullptr) {
216                 LOG(ERROR) << "Unknown extension prefix " << extensionPrefix;
217                 *errorStatus = ErrorStatus::INVALID_ARGUMENT;
218                 return nullptr;
219             }
220             resultModel.getExtensionOperationType(*extensionName, typeWithinExtension,
221                                                   &operationType);
222             if (!resultModel.isValid()) {
223                 LOG(ERROR) << "Failed to get extension operation with index " << i;
224                 *errorStatus = ErrorStatus::INVALID_ARGUMENT;
225                 return nullptr;
226             }
227         }
228 
229         resultModel.addOperation(operationType, inputs, outputs);
230 
231         if (!resultModel.isValid()) {
232             LOG(ERROR) << "Failed to add operation with index " << i;
233             *errorStatus = ErrorStatus::INVALID_ARGUMENT;
234             return nullptr;
235         }
236     }
237 
238     std::vector<uint32_t> inputIndexes(subgraph.inputIndexes.begin(), subgraph.inputIndexes.end());
239     std::vector<uint32_t> outputIndexes(subgraph.outputIndexes.begin(),
240                                         subgraph.outputIndexes.end());
241 
242     resultModel.identifyInputsAndOutputs(inputIndexes, outputIndexes);
243     if (!resultModel.isValid()) {
244         LOG(ERROR) << "Model identifyInputsAndOutputs failed";
245         *errorStatus = ErrorStatus::INVALID_ARGUMENT;
246         return nullptr;
247     }
248 
249     if (resultModel.finish() != Result::NO_ERROR) {
250         LOG(ERROR) << "Model finish failed";
251         *errorStatus = ErrorStatus::INVALID_ARGUMENT;
252         return nullptr;
253     }
254 
255     if (!resultModel.isValid()) {
256         LOG(ERROR) << "Invalid model";
257         *errorStatus = ErrorStatus::INVALID_ARGUMENT;
258         return nullptr;
259     }
260 
261     (*allModels)[subgraphIndex] = std::move(resultModel);
262     return (*allModels)[subgraphIndex]->getHandle();
263 }
264 
265 // This is needed for CONSTANT_COPY operands > 128 bytes, we have to
266 // store them in intenal buffer
needsCopiedOperandValues(const neuralnetworks::Model & model)267 bool needsCopiedOperandValues(const neuralnetworks::Model& model) {
268     for (int sindex = 0; sindex < model.referenced.size() + 1; ++sindex) {
269         const auto& subgraph = sindex == 0 ? model.main : model.referenced[sindex - 1];
270         for (int i = 0; i < subgraph.operands.size(); ++i) {
271             const auto& operand = subgraph.operands[i];
272 
273             if (operand.lifetime == OperandLifeTime::CONSTANT_COPY) {
274                 if (operand.location.length >
275                     ANEURALNETWORKS_MAX_SIZE_OF_IMMEDIATELY_COPIED_VALUES) {
276                     return true;
277                 }
278             }
279         }
280     }
281     return false;
282 }
283 
isValid(const Subgraph & subgraph)284 bool isValid(const Subgraph& subgraph) {
285     // Either the operand has a known value before model execution begins, or we've seen a writer
286     // for this operand while walking operands in execution order. Initialize to known operands.
287     std::vector<bool> operandValueKnown;
288     operandValueKnown.reserve(subgraph.operands.size());
289     std::transform(subgraph.operands.begin(), subgraph.operands.end(),
290                    std::back_inserter(operandValueKnown), [](const Operand& operand) {
291                        return operand.lifetime != OperandLifeTime::TEMPORARY_VARIABLE &&
292                               operand.lifetime != OperandLifeTime::SUBGRAPH_OUTPUT;
293                    });
294 
295     // Validate that operations are sorted into execution order.
296     //
297     // If there is a cycle in the graph, the operations will not
298     // appear to be sorted into execution order: Some operation will
299     // have an input for which operandValueKnown[] is false.
300     for (size_t i = 0; i < subgraph.operations.size(); ++i) {
301         const auto& operation = subgraph.operations[i];
302 
303         for (size_t j = 0; j < operation.inputs.size(); ++j) {
304             const uint32_t k = operation.inputs[j];
305             if (!operandValueKnown[k]) {
306                 LOG(ERROR) << "Operation " << i << " input " << j << " (operand " << k
307                            << ") is read before it is written";
308                 return false;
309             }
310         }
311 
312         for (size_t j = 0; j < operation.outputs.size(); ++j) {
313             const uint32_t k = operation.outputs[j];
314             // Assuming validateOperations() has not returned an error, we know that this output is
315             // TEMPORARY_VARIABLE or MODEL_OUTPUT, and so the only way operandValueKnown[k] can be
316             // true is if we've already seen a writer for this operand.
317             if (operandValueKnown[k]) {
318                 LOG(ERROR) << "Operation " << i << " output " << j << " (operand " << k
319                            << ") has already been written";
320                 return false;
321             }
322             operandValueKnown[k] = true;
323         }
324     }
325 
326     // Verify all operands are written.
327     for (size_t i = 0; i < subgraph.operands.size(); ++i) {
328         if (!operandValueKnown[i]) {
329             LOG(ERROR) << "Operand " << i << " is never written";
330             return false;
331         }
332         const auto& operand = subgraph.operands[i];
333 
334         if (operand.lifetime == OperandLifeTime::SUBGRAPH_OUTPUT) {
335             if (std::find(subgraph.outputIndexes.begin(), subgraph.outputIndexes.end(), i) ==
336                 subgraph.outputIndexes.end()) {
337                 LOG(ERROR) << "Op with output liftime, but not on output list: " << i;
338                 return false;
339             }
340         }
341     }
342 
343     // Validate input and output lifetime
344     for (auto index : subgraph.inputIndexes) {
345         if (subgraph.operands[index].lifetime != OperandLifeTime::SUBGRAPH_INPUT) {
346             LOG(ERROR) << "Input with index" << index << " has invalid lifetime";
347             return false;
348         }
349     }
350     for (auto index : subgraph.outputIndexes) {
351         if (subgraph.operands[index].lifetime != OperandLifeTime::SUBGRAPH_OUTPUT) {
352             LOG(ERROR) << "Output with index" << index << " has invalid lifetime";
353             return false;
354         }
355     }
356 
357     // TODO(b/77871786): verify that every operation has at least one output operand that is read?
358     return true;
359 }
360 
361 }  // namespace
362 
isValid(const neuralnetworks::Model & model)363 bool isValid(const neuralnetworks::Model& model) {
364     return (isValid(model.main) &&
365             std::all_of(model.referenced.begin(), model.referenced.end(),
366                         [](const Subgraph& subgraph) { return isValid(subgraph); }));
367 }
368 
convertFromHAL(const NnApiSupportLibrary * nnapi,const neuralnetworks::Model & model,std::vector<uint8_t> * copiedOperandValues,ErrorStatus * errorStatus)369 std::optional<ShimConvertedModel> convertFromHAL(const NnApiSupportLibrary* nnapi,
370                                                  const neuralnetworks::Model& model,
371                                                  std::vector<uint8_t>* copiedOperandValues,
372                                                  ErrorStatus* errorStatus) {
373     CHECK(copiedOperandValues != nullptr);
374 
375     *errorStatus = ErrorStatus::NONE;
376 
377     // Using this pulls in OperationResolver and huge chunk of dependencies.
378     // TODO(172925288): Replace as followup work
379     //    if (!::aidl::android::hardware::neuralnetworks::utils::valid(model)) {
380     if (!isValid(model)) {
381         LOG(ERROR) << "Invalid HAL model, failed to convert into SL model";
382         *errorStatus = ErrorStatus::INVALID_ARGUMENT;
383         return std::nullopt;
384     }
385 
386     std::vector<std::unique_ptr<::android::nn::sl_wrapper::Memory>> memoryPools;
387     memoryPools.reserve(model.pools.size());
388     for (const auto& pool : model.pools) {
389         std::unique_ptr<::android::nn::sl_wrapper::Memory> memory = convertFromHAL(nnapi, pool);
390         if (!memory) {
391             LOG(ERROR) << "Failed to convert HAL memory into SL memory";
392             *errorStatus = ErrorStatus::INVALID_ARGUMENT;
393             return std::nullopt;
394         }
395         memoryPools.push_back(std::move(memory));
396     }
397 
398     std::vector<std::optional<::android::nn::sl_wrapper::Model>> allModels(model.referenced.size() +
399                                                                            1);
400 
401     if (needsCopiedOperandValues(model)) {
402         *copiedOperandValues = model.operandValues;
403     }
404 
405     for (size_t i = 0; i < allModels.size(); ++i) {
406         if (convertSubgraphFromHAL(nnapi, memoryPools, model, &allModels, i, *copiedOperandValues,
407                                    errorStatus) == nullptr) {
408             LOG(ERROR) << "Failed to convert HAL subgraphs into SL subgraphs, index: " << i;
409             // Error status already set by convertSubgraphFromHAL
410             return std::nullopt;
411         }
412     }
413 
414     std::vector<::android::nn::sl_wrapper::Model> result;
415     result.reserve(allModels.size());
416     for (size_t i = 0; i < allModels.size(); ++i) {
417         if (!allModels[i].has_value()) {
418             LOG(ERROR) << "Missing SL subgraph";
419             *errorStatus = ErrorStatus::INVALID_ARGUMENT;
420             return std::nullopt;
421         }
422         result.push_back(std::move(*allModels[i]));
423     }
424 
425     return ShimConvertedModel{.memory = std::move(memoryPools), .models = std::move(result)};
426 }
427 
convertFromHAL(const NnApiSupportLibrary * nnapi,const neuralnetworks::Memory & pool)428 std::unique_ptr<::android::nn::sl_wrapper::Memory> convertFromHAL(
429         const NnApiSupportLibrary* nnapi, const neuralnetworks::Memory& pool) {
430     using Tag = neuralnetworks::Memory::Tag;
431     switch (pool.getTag()) {
432         case Tag::ashmem: {
433             const auto& ashmem = pool.get<Tag::ashmem>();
434             size_t size = ashmem.size;
435             int fd = ashmem.fd.get();
436 
437             auto memory = std::make_unique<::android::nn::sl_wrapper::Memory>(
438                     nnapi, size, PROT_READ | PROT_WRITE, fd, 0, /*ownsFd=*/false);
439             if (!memory->isValid()) {
440                 return nullptr;
441             }
442             return memory;
443         }
444         case Tag::mappableFile: {
445             const auto& mappableFile = pool.get<Tag::mappableFile>();
446             size_t size = mappableFile.length;
447             int fd = mappableFile.fd.get();
448             int prot = mappableFile.prot & (PROT_READ | PROT_WRITE);
449             size_t offset = mappableFile.offset;
450 
451             auto memory = std::make_unique<::android::nn::sl_wrapper::Memory>(
452                     nnapi, size, prot, fd, offset, /*ownsFd=*/false);
453             if (!memory->isValid()) {
454                 return nullptr;
455             }
456             return memory;
457         }
458         case Tag::hardwareBuffer: {
459             const auto& hardwareBuffer = pool.get<Tag::hardwareBuffer>();
460 
461             native_handle_t* handle = ::android::dupFromAidl(hardwareBuffer.handle);
462             if (handle == nullptr) {
463                 LOG(ERROR) << "Dup of the hardware_buffer_blob memory pool failed";
464                 return nullptr;
465             }
466             const auto handleGuard = ::android::base::make_scope_guard([handle] {
467                 native_handle_close(handle);
468                 native_handle_delete(handle);
469             });
470             for (size_t i = 0; i < handle->numFds; ++i) {
471                 if (handle->data[i] == -1) {
472                     LOG(ERROR) << "Dup of the hardware_buffer_blob memory pool failed";
473                     return nullptr;
474                 }
475             }
476 
477             const AHardwareBuffer_Desc desc{
478                     .width = static_cast<uint32_t>(hardwareBuffer.description.width),
479                     .height = static_cast<uint32_t>(hardwareBuffer.description.height),
480                     .layers = static_cast<uint32_t>(hardwareBuffer.description.layers),
481                     .format = static_cast<uint32_t>(hardwareBuffer.description.format),
482                     .usage = static_cast<uint64_t>(hardwareBuffer.description.usage),
483                     .stride = static_cast<uint32_t>(hardwareBuffer.description.stride),
484             };
485             AHardwareBuffer* ahwb = nullptr;
486             const ::android::status_t status = AHardwareBuffer_createFromHandle(
487                     &desc, handle, AHARDWAREBUFFER_CREATE_FROM_HANDLE_METHOD_CLONE, &ahwb);
488             if (status != ::android::NO_ERROR) {
489                 LOG(ERROR) << "createFromHandle failed";
490                 return nullptr;
491             }
492 
493             const bool isBlob = desc.format == AHARDWAREBUFFER_FORMAT_BLOB;
494             const size_t size = isBlob ? desc.width : 0;
495 
496             // Takes ownership of hardwareBuffer, handle gets closed
497             auto memory =
498                     std::make_unique<::android::nn::sl_wrapper::Memory>(nnapi, ahwb,
499                                                                         /*ownAHB=*/true, size);
500             if (!memory->isValid()) {
501                 return nullptr;
502             }
503             return memory;
504         }
505     }
506     LOG(ERROR) << "Can't convert to SL Memory, unknown pool tag: "
507                << utils::underlyingType(pool.getTag());
508     return nullptr;
509 }
510 
511 }  // namespace aidl::android::hardware::neuralnetworks
512