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 #include "Utils.h"
18 
19 #include <aidl/android/hardware/neuralnetworks/IPreparedModelParcel.h>
20 #include <aidl/android/hardware/neuralnetworks/Operand.h>
21 #include <aidl/android/hardware/neuralnetworks/OperandType.h>
22 #include <android-base/logging.h>
23 #include <android/binder_status.h>
24 #include <android/hardware_buffer.h>
25 
26 #include <sys/mman.h>
27 #include <iostream>
28 #include <limits>
29 #include <numeric>
30 
31 #include <MemoryUtils.h>
32 #include <nnapi/SharedMemory.h>
33 #include <nnapi/hal/aidl/Conversions.h>
34 #include <nnapi/hal/aidl/Utils.h>
35 
36 namespace aidl::android::hardware::neuralnetworks {
37 
38 using test_helper::TestBuffer;
39 using test_helper::TestModel;
40 
sizeOfData(OperandType type)41 uint32_t sizeOfData(OperandType type) {
42     switch (type) {
43         case OperandType::FLOAT32:
44         case OperandType::INT32:
45         case OperandType::UINT32:
46         case OperandType::TENSOR_FLOAT32:
47         case OperandType::TENSOR_INT32:
48             return 4;
49         case OperandType::TENSOR_QUANT16_SYMM:
50         case OperandType::TENSOR_FLOAT16:
51         case OperandType::FLOAT16:
52         case OperandType::TENSOR_QUANT16_ASYMM:
53             return 2;
54         case OperandType::TENSOR_QUANT8_ASYMM:
55         case OperandType::BOOL:
56         case OperandType::TENSOR_BOOL8:
57         case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
58         case OperandType::TENSOR_QUANT8_SYMM:
59         case OperandType::TENSOR_QUANT8_ASYMM_SIGNED:
60             return 1;
61         case OperandType::SUBGRAPH:
62             return 0;
63         default:
64             CHECK(false) << "Invalid OperandType " << static_cast<uint32_t>(type);
65             return 0;
66     }
67 }
68 
isTensor(OperandType type)69 static bool isTensor(OperandType type) {
70     switch (type) {
71         case OperandType::FLOAT32:
72         case OperandType::INT32:
73         case OperandType::UINT32:
74         case OperandType::FLOAT16:
75         case OperandType::BOOL:
76         case OperandType::SUBGRAPH:
77             return false;
78         case OperandType::TENSOR_FLOAT32:
79         case OperandType::TENSOR_INT32:
80         case OperandType::TENSOR_QUANT16_SYMM:
81         case OperandType::TENSOR_FLOAT16:
82         case OperandType::TENSOR_QUANT16_ASYMM:
83         case OperandType::TENSOR_QUANT8_ASYMM:
84         case OperandType::TENSOR_BOOL8:
85         case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
86         case OperandType::TENSOR_QUANT8_SYMM:
87         case OperandType::TENSOR_QUANT8_ASYMM_SIGNED:
88             return true;
89         default:
90             CHECK(false) << "Invalid OperandType " << static_cast<uint32_t>(type);
91             return false;
92     }
93 }
94 
sizeOfData(const Operand & operand)95 uint32_t sizeOfData(const Operand& operand) {
96     const uint32_t dataSize = sizeOfData(operand.type);
97     if (isTensor(operand.type) && operand.dimensions.size() == 0) return 0;
98     return std::accumulate(operand.dimensions.begin(), operand.dimensions.end(), dataSize,
99                            std::multiplies<>{});
100 }
101 
create(uint32_t size,bool aidlReadonly)102 std::unique_ptr<TestAshmem> TestAshmem::create(uint32_t size, bool aidlReadonly) {
103     auto ashmem = std::make_unique<TestAshmem>(size, aidlReadonly);
104     return ashmem->mIsValid ? std::move(ashmem) : nullptr;
105 }
106 
107 // This function will create a readonly shared memory with PROT_READ only.
108 // The input shared memory must be either Ashmem or mapped-FD.
convertSharedMemoryToReadonly(const nn::SharedMemory & sharedMemory)109 static nn::SharedMemory convertSharedMemoryToReadonly(const nn::SharedMemory& sharedMemory) {
110     if (std::holds_alternative<nn::Memory::Ashmem>(sharedMemory->handle)) {
111         const auto& memory = std::get<nn::Memory::Ashmem>(sharedMemory->handle);
112         return nn::createSharedMemoryFromFd(memory.size, PROT_READ, memory.fd.get(), /*offset=*/0)
113                 .value();
114     } else if (std::holds_alternative<nn::Memory::Fd>(sharedMemory->handle)) {
115         const auto& memory = std::get<nn::Memory::Fd>(sharedMemory->handle);
116         return nn::createSharedMemoryFromFd(memory.size, PROT_READ, memory.fd.get(), memory.offset)
117                 .value();
118     }
119     CHECK(false) << "Unexpected shared memory type";
120     return sharedMemory;
121 }
122 
initialize(uint32_t size,bool aidlReadonly)123 void TestAshmem::initialize(uint32_t size, bool aidlReadonly) {
124     mIsValid = false;
125     ASSERT_GT(size, 0);
126     const auto sharedMemory = nn::createSharedMemory(size).value();
127     mMappedMemory = nn::map(sharedMemory).value();
128     mPtr = static_cast<uint8_t*>(std::get<void*>(mMappedMemory.pointer));
129     CHECK_NE(mPtr, nullptr);
130     if (aidlReadonly) {
131         mAidlMemory = utils::convert(convertSharedMemoryToReadonly(sharedMemory)).value();
132     } else {
133         mAidlMemory = utils::convert(sharedMemory).value();
134     }
135     mIsValid = true;
136 }
137 
create(uint32_t size)138 std::unique_ptr<TestBlobAHWB> TestBlobAHWB::create(uint32_t size) {
139     auto ahwb = std::make_unique<TestBlobAHWB>(size);
140     return ahwb->mIsValid ? std::move(ahwb) : nullptr;
141 }
142 
initialize(uint32_t size)143 void TestBlobAHWB::initialize(uint32_t size) {
144     mIsValid = false;
145     ASSERT_GT(size, 0);
146     const auto usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN;
147     const AHardwareBuffer_Desc desc = {
148             .width = size,
149             .height = 1,
150             .layers = 1,
151             .format = AHARDWAREBUFFER_FORMAT_BLOB,
152             .usage = usage,
153             .stride = size,
154     };
155 
156     AHardwareBuffer* ahwb = nullptr;
157     ASSERT_EQ(AHardwareBuffer_allocate(&desc, &ahwb), 0);
158     ASSERT_NE(ahwb, nullptr);
159 
160     mMemory = nn::createSharedMemoryFromAHWB(ahwb, /*takeOwnership=*/true).value();
161     mMapping = nn::map(mMemory).value();
162     mPtr = static_cast<uint8_t*>(std::get<void*>(mMapping.pointer));
163     CHECK_NE(mPtr, nullptr);
164     mAidlMemory = utils::convert(mMemory).value();
165 
166     mIsValid = true;
167 }
168 
gtestCompliantName(std::string name)169 std::string gtestCompliantName(std::string name) {
170     // gtest test names must only contain alphanumeric characters
171     std::replace_if(
172             name.begin(), name.end(), [](char c) { return !std::isalnum(c); }, '_');
173     return name;
174 }
175 
operator <<(::std::ostream & os,ErrorStatus errorStatus)176 ::std::ostream& operator<<(::std::ostream& os, ErrorStatus errorStatus) {
177     return os << toString(errorStatus);
178 }
179 
createRequest(const TestModel & testModel,MemoryType memoryType)180 Request ExecutionContext::createRequest(const TestModel& testModel, MemoryType memoryType) {
181     CHECK(memoryType == MemoryType::ASHMEM || memoryType == MemoryType::BLOB_AHWB);
182 
183     // Model inputs.
184     std::vector<RequestArgument> inputs(testModel.main.inputIndexes.size());
185     size_t inputSize = 0;
186     for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) {
187         const auto& op = testModel.main.operands[testModel.main.inputIndexes[i]];
188         if (op.data.size() == 0) {
189             // Omitted input.
190             inputs[i] = {.hasNoValue = true};
191         } else {
192             DataLocation loc = {.poolIndex = kInputPoolIndex,
193                                 .offset = static_cast<int64_t>(inputSize),
194                                 .length = static_cast<int64_t>(op.data.size())};
195             inputSize += op.data.alignedSize();
196             inputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
197         }
198     }
199 
200     // Model outputs.
201     std::vector<RequestArgument> outputs(testModel.main.outputIndexes.size());
202     size_t outputSize = 0;
203     for (uint32_t i = 0; i < testModel.main.outputIndexes.size(); i++) {
204         const auto& op = testModel.main.operands[testModel.main.outputIndexes[i]];
205 
206         // In the case of zero-sized output, we should at least provide a one-byte buffer.
207         // This is because zero-sized tensors are only supported internally to the driver, or
208         // reported in output shapes. It is illegal for the client to pre-specify a zero-sized
209         // tensor as model output. Otherwise, we will have two semantic conflicts:
210         // - "Zero dimension" conflicts with "unspecified dimension".
211         // - "Omitted operand buffer" conflicts with "zero-sized operand buffer".
212         size_t bufferSize = std::max<size_t>(op.data.size(), 1);
213 
214         DataLocation loc = {.poolIndex = kOutputPoolIndex,
215                             .offset = static_cast<int64_t>(outputSize),
216                             .length = static_cast<int64_t>(bufferSize)};
217         outputSize += op.data.size() == 0 ? TestBuffer::kAlignment : op.data.alignedSize();
218         outputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
219     }
220 
221     // Allocate memory pools.
222     if (memoryType == MemoryType::ASHMEM) {
223         mInputMemory = TestAshmem::create(inputSize);
224         mOutputMemory = TestAshmem::create(outputSize);
225     } else {
226         mInputMemory = TestBlobAHWB::create(inputSize);
227         mOutputMemory = TestBlobAHWB::create(outputSize);
228     }
229     CHECK_NE(mInputMemory, nullptr);
230     CHECK_NE(mOutputMemory, nullptr);
231 
232     auto copiedInputMemory = utils::clone(*mInputMemory->getAidlMemory());
233     CHECK(copiedInputMemory.has_value()) << copiedInputMemory.error().message;
234     auto copiedOutputMemory = utils::clone(*mOutputMemory->getAidlMemory());
235     CHECK(copiedOutputMemory.has_value()) << copiedOutputMemory.error().message;
236 
237     std::vector<RequestMemoryPool> pools;
238     pools.push_back(RequestMemoryPool::make<RequestMemoryPool::Tag::pool>(
239             std::move(copiedInputMemory).value()));
240     pools.push_back(RequestMemoryPool::make<RequestMemoryPool::Tag::pool>(
241             std::move(copiedOutputMemory).value()));
242 
243     // Copy input data to the memory pool.
244     uint8_t* inputPtr = mInputMemory->getPointer();
245     for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) {
246         const auto& op = testModel.main.operands[testModel.main.inputIndexes[i]];
247         if (op.data.size() > 0) {
248             const uint8_t* begin = op.data.get<uint8_t>();
249             const uint8_t* end = begin + op.data.size();
250             std::copy(begin, end, inputPtr + inputs[i].location.offset);
251         }
252     }
253 
254     return {.inputs = std::move(inputs), .outputs = std::move(outputs), .pools = std::move(pools)};
255 }
256 
getOutputBuffers(const Request & request) const257 std::vector<TestBuffer> ExecutionContext::getOutputBuffers(const Request& request) const {
258     // Copy out output results.
259     uint8_t* outputPtr = mOutputMemory->getPointer();
260     std::vector<TestBuffer> outputBuffers;
261     for (const auto& output : request.outputs) {
262         outputBuffers.emplace_back(output.location.length, outputPtr + output.location.offset);
263     }
264     return outputBuffers;
265 }
266 
267 }  // namespace aidl::android::hardware::neuralnetworks
268