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