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 "CommonUtils.h"
18 
19 #include "HandleError.h"
20 
21 #include <android-base/logging.h>
22 #include <android-base/unique_fd.h>
23 #include <android/hardware_buffer.h>
24 #include <hidl/HidlSupport.h>
25 #include <nnapi/Result.h>
26 #include <nnapi/SharedMemory.h>
27 #include <nnapi/TypeUtils.h>
28 #include <nnapi/Types.h>
29 #include <nnapi/Validation.h>
30 #include <vndk/hardware_buffer.h>
31 
32 #include <algorithm>
33 #include <any>
34 #include <functional>
35 #include <optional>
36 #include <variant>
37 #include <vector>
38 
39 namespace android::hardware::neuralnetworks::utils {
40 namespace {
41 
42 bool hasNoPointerData(const nn::Operand& operand);
43 bool hasNoPointerData(const nn::Model::Subgraph& subgraph);
44 bool hasNoPointerData(const nn::Request::Argument& argument);
45 
46 template <typename Type>
hasNoPointerData(const std::vector<Type> & objects)47 bool hasNoPointerData(const std::vector<Type>& objects) {
48     return std::all_of(objects.begin(), objects.end(),
49                        [](const auto& object) { return hasNoPointerData(object); });
50 }
51 
hasNoPointerData(const nn::DataLocation & location)52 bool hasNoPointerData(const nn::DataLocation& location) {
53     return std::visit([](auto ptr) { return ptr == nullptr; }, location.pointer);
54 }
55 
hasNoPointerData(const nn::Operand & operand)56 bool hasNoPointerData(const nn::Operand& operand) {
57     return hasNoPointerData(operand.location);
58 }
59 
hasNoPointerData(const nn::Model::Subgraph & subgraph)60 bool hasNoPointerData(const nn::Model::Subgraph& subgraph) {
61     return hasNoPointerData(subgraph.operands);
62 }
63 
hasNoPointerData(const nn::Request::Argument & argument)64 bool hasNoPointerData(const nn::Request::Argument& argument) {
65     return hasNoPointerData(argument.location);
66 }
67 
copyPointersToSharedMemory(nn::Operand * operand,nn::ConstantMemoryBuilder * memoryBuilder)68 void copyPointersToSharedMemory(nn::Operand* operand, nn::ConstantMemoryBuilder* memoryBuilder) {
69     CHECK(operand != nullptr);
70     CHECK(memoryBuilder != nullptr);
71 
72     if (operand->lifetime != nn::Operand::LifeTime::POINTER) {
73         return;
74     }
75 
76     const void* data = std::visit([](auto ptr) { return static_cast<const void*>(ptr); },
77                                   operand->location.pointer);
78     CHECK(data != nullptr);
79     operand->lifetime = nn::Operand::LifeTime::CONSTANT_REFERENCE;
80     operand->location = memoryBuilder->append(data, operand->location.length);
81 }
82 
copyPointersToSharedMemory(nn::Model::Subgraph * subgraph,nn::ConstantMemoryBuilder * memoryBuilder)83 void copyPointersToSharedMemory(nn::Model::Subgraph* subgraph,
84                                 nn::ConstantMemoryBuilder* memoryBuilder) {
85     CHECK(subgraph != nullptr);
86     std::for_each(subgraph->operands.begin(), subgraph->operands.end(),
87                   [memoryBuilder](auto& operand) {
88                       copyPointersToSharedMemory(&operand, memoryBuilder);
89                   });
90 }
91 
createNativeHandleFrom(base::unique_fd fd,const std::vector<int32_t> & ints)92 nn::GeneralResult<hidl_handle> createNativeHandleFrom(base::unique_fd fd,
93                                                       const std::vector<int32_t>& ints) {
94     constexpr size_t kIntMax = std::numeric_limits<int>::max();
95     CHECK_LE(ints.size(), kIntMax);
96     native_handle_t* nativeHandle = native_handle_create(1, static_cast<int>(ints.size()));
97     if (nativeHandle == nullptr) {
98         return NN_ERROR() << "Failed to create native_handle";
99     }
100 
101     nativeHandle->data[0] = fd.release();
102     std::copy(ints.begin(), ints.end(), nativeHandle->data + 1);
103 
104     hidl_handle handle;
105     handle.setTo(nativeHandle, /*shouldOwn=*/true);
106     return handle;
107 }
108 
createHidlMemoryFrom(const nn::Memory::Ashmem & memory)109 nn::GeneralResult<hidl_memory> createHidlMemoryFrom(const nn::Memory::Ashmem& memory) {
110     auto fd = NN_TRY(nn::dupFd(memory.fd));
111     auto handle = NN_TRY(createNativeHandleFrom(std::move(fd), {}));
112     return hidl_memory("ashmem", std::move(handle), memory.size);
113 }
114 
createHidlMemoryFrom(const nn::Memory::Fd & memory)115 nn::GeneralResult<hidl_memory> createHidlMemoryFrom(const nn::Memory::Fd& memory) {
116     auto fd = NN_TRY(nn::dupFd(memory.fd));
117 
118     const auto [lowOffsetBits, highOffsetBits] = nn::getIntsFromOffset(memory.offset);
119     const std::vector<int> ints = {memory.prot, lowOffsetBits, highOffsetBits};
120 
121     auto handle = NN_TRY(createNativeHandleFrom(std::move(fd), ints));
122     return hidl_memory("mmap_fd", std::move(handle), memory.size);
123 }
124 
createHidlMemoryFrom(const nn::Memory::HardwareBuffer & memory)125 nn::GeneralResult<hidl_memory> createHidlMemoryFrom(const nn::Memory::HardwareBuffer& memory) {
126     const auto* ahwb = memory.handle.get();
127     AHardwareBuffer_Desc bufferDesc;
128     AHardwareBuffer_describe(ahwb, &bufferDesc);
129 
130     const bool isBlob = bufferDesc.format == AHARDWAREBUFFER_FORMAT_BLOB;
131     const size_t size = isBlob ? bufferDesc.width : 0;
132     const char* const name = isBlob ? "hardware_buffer_blob" : "hardware_buffer";
133 
134     const native_handle_t* nativeHandle = AHardwareBuffer_getNativeHandle(ahwb);
135     const hidl_handle hidlHandle(nativeHandle);
136     hidl_handle copiedHandle(hidlHandle);
137 
138     return hidl_memory(name, std::move(copiedHandle), size);
139 }
140 
createHidlMemoryFrom(const nn::Memory::Unknown & memory)141 nn::GeneralResult<hidl_memory> createHidlMemoryFrom(const nn::Memory::Unknown& memory) {
142     return hidl_memory(memory.name, NN_TRY(hidlHandleFromSharedHandle(memory.handle)), memory.size);
143 }
144 
145 }  // anonymous namespace
146 
makeQuantized8PerformanceConsistentWithP(const nn::Capabilities::PerformanceInfo & float32Performance,const nn::Capabilities::PerformanceInfo & quantized8Performance)147 nn::Capabilities::OperandPerformanceTable makeQuantized8PerformanceConsistentWithP(
148         const nn::Capabilities::PerformanceInfo& float32Performance,
149         const nn::Capabilities::PerformanceInfo& quantized8Performance) {
150     // In Android P, most data types are treated as having the same performance as
151     // TENSOR_QUANT8_ASYMM. This collection must be in sorted order.
152     std::vector<nn::Capabilities::OperandPerformance> operandPerformances = {
153             {.type = nn::OperandType::FLOAT32, .info = float32Performance},
154             {.type = nn::OperandType::INT32, .info = quantized8Performance},
155             {.type = nn::OperandType::UINT32, .info = quantized8Performance},
156             {.type = nn::OperandType::TENSOR_FLOAT32, .info = float32Performance},
157             {.type = nn::OperandType::TENSOR_INT32, .info = quantized8Performance},
158             {.type = nn::OperandType::TENSOR_QUANT8_ASYMM, .info = quantized8Performance},
159             {.type = nn::OperandType::OEM, .info = quantized8Performance},
160             {.type = nn::OperandType::TENSOR_OEM_BYTE, .info = quantized8Performance},
161     };
162     return nn::Capabilities::OperandPerformanceTable::create(std::move(operandPerformances))
163             .value();
164 }
165 
hasNoPointerData(const nn::Model & model)166 bool hasNoPointerData(const nn::Model& model) {
167     return hasNoPointerData(model.main) && hasNoPointerData(model.referenced);
168 }
169 
hasNoPointerData(const nn::Request & request)170 bool hasNoPointerData(const nn::Request& request) {
171     return hasNoPointerData(request.inputs) && hasNoPointerData(request.outputs);
172 }
173 
flushDataFromPointerToShared(const nn::Model * model,std::optional<nn::Model> * maybeModelInSharedOut)174 nn::GeneralResult<std::reference_wrapper<const nn::Model>> flushDataFromPointerToShared(
175         const nn::Model* model, std::optional<nn::Model>* maybeModelInSharedOut) {
176     CHECK(model != nullptr);
177     CHECK(maybeModelInSharedOut != nullptr);
178 
179     if (hasNoPointerData(*model)) {
180         return *model;
181     }
182 
183     // Make a copy of the model in order to make modifications. The modified model is returned to
184     // the caller through `maybeModelInSharedOut` if the function succeeds.
185     nn::Model modelInShared = *model;
186 
187     nn::ConstantMemoryBuilder memoryBuilder(modelInShared.pools.size());
188     copyPointersToSharedMemory(&modelInShared.main, &memoryBuilder);
189     std::for_each(modelInShared.referenced.begin(), modelInShared.referenced.end(),
190                   [&memoryBuilder](auto& subgraph) {
191                       copyPointersToSharedMemory(&subgraph, &memoryBuilder);
192                   });
193 
194     if (!memoryBuilder.empty()) {
195         auto memory = NN_TRY(memoryBuilder.finish());
196         modelInShared.pools.push_back(std::move(memory));
197     }
198 
199     *maybeModelInSharedOut = modelInShared;
200     return **maybeModelInSharedOut;
201 }
202 
203 template <>
flush() const204 void InputRelocationTracker::flush() const {
205     // Copy from pointers to shared memory.
206     uint8_t* memoryPtr = static_cast<uint8_t*>(std::get<void*>(kMapping.pointer));
207     for (const auto& [data, length, offset] : kRelocationInfos) {
208         std::memcpy(memoryPtr + offset, data, length);
209     }
210 }
211 
212 template <>
flush() const213 void OutputRelocationTracker::flush() const {
214     // Copy from shared memory to pointers.
215     const uint8_t* memoryPtr = static_cast<const uint8_t*>(
216             std::visit([](auto ptr) { return static_cast<const void*>(ptr); }, kMapping.pointer));
217     for (const auto& [data, length, offset] : kRelocationInfos) {
218         std::memcpy(data, memoryPtr + offset, length);
219     }
220 }
221 
convertRequestFromPointerToShared(const nn::Request * request,uint32_t alignment,uint32_t padding,std::optional<nn::Request> * maybeRequestInSharedOut,RequestRelocation * relocationOut)222 nn::GeneralResult<std::reference_wrapper<const nn::Request>> convertRequestFromPointerToShared(
223         const nn::Request* request, uint32_t alignment, uint32_t padding,
224         std::optional<nn::Request>* maybeRequestInSharedOut, RequestRelocation* relocationOut) {
225     CHECK(request != nullptr);
226     CHECK(maybeRequestInSharedOut != nullptr);
227     CHECK(relocationOut != nullptr);
228 
229     if (hasNoPointerData(*request)) {
230         return *request;
231     }
232 
233     // Make a copy of the request in order to make modifications. The modified request is returned
234     // to the caller through `maybeRequestInSharedOut` if the function succeeds.
235     nn::Request requestInShared = *request;
236 
237     RequestRelocation relocation;
238 
239     // Change input pointers to shared memory.
240     nn::MutableMemoryBuilder inputBuilder(requestInShared.pools.size());
241     std::vector<InputRelocationInfo> inputRelocationInfos;
242     for (auto& input : requestInShared.inputs) {
243         const auto& location = input.location;
244         if (input.lifetime != nn::Request::Argument::LifeTime::POINTER) {
245             continue;
246         }
247 
248         input.lifetime = nn::Request::Argument::LifeTime::POOL;
249         const void* data = std::visit([](auto ptr) { return static_cast<const void*>(ptr); },
250                                       location.pointer);
251         CHECK(data != nullptr);
252         input.location = inputBuilder.append(location.length, alignment, padding);
253         inputRelocationInfos.push_back({data, input.location.length, input.location.offset});
254     }
255 
256     // Allocate input memory.
257     if (!inputBuilder.empty()) {
258         auto memory = NN_TRY(inputBuilder.finish());
259         requestInShared.pools.push_back(memory);
260         relocation.input = NN_TRY(
261                 InputRelocationTracker::create(std::move(inputRelocationInfos), std::move(memory)));
262     }
263 
264     // Change output pointers to shared memory.
265     nn::MutableMemoryBuilder outputBuilder(requestInShared.pools.size());
266     std::vector<OutputRelocationInfo> outputRelocationInfos;
267     for (auto& output : requestInShared.outputs) {
268         const auto& location = output.location;
269         if (output.lifetime != nn::Request::Argument::LifeTime::POINTER) {
270             continue;
271         }
272 
273         output.lifetime = nn::Request::Argument::LifeTime::POOL;
274         void* data = std::get<void*>(location.pointer);
275         CHECK(data != nullptr);
276         output.location = outputBuilder.append(location.length, alignment, padding);
277         outputRelocationInfos.push_back({data, output.location.length, output.location.offset});
278     }
279 
280     // Allocate output memory.
281     if (!outputBuilder.empty()) {
282         auto memory = NN_TRY(outputBuilder.finish());
283         requestInShared.pools.push_back(memory);
284         relocation.output = NN_TRY(OutputRelocationTracker::create(std::move(outputRelocationInfos),
285                                                                    std::move(memory)));
286     }
287 
288     *maybeRequestInSharedOut = requestInShared;
289     *relocationOut = std::move(relocation);
290     return **maybeRequestInSharedOut;
291 }
292 
countNumberOfConsumers(size_t numberOfOperands,const std::vector<nn::Operation> & operations)293 nn::GeneralResult<std::vector<uint32_t>> countNumberOfConsumers(
294         size_t numberOfOperands, const std::vector<nn::Operation>& operations) {
295     return makeGeneralFailure(nn::countNumberOfConsumers(numberOfOperands, operations));
296 }
297 
createHidlMemoryFromSharedMemory(const nn::SharedMemory & memory)298 nn::GeneralResult<hidl_memory> createHidlMemoryFromSharedMemory(const nn::SharedMemory& memory) {
299     if (memory == nullptr) {
300         return NN_ERROR() << "Memory must be non-empty";
301     }
302     return std::visit([](const auto& x) { return createHidlMemoryFrom(x); }, memory->handle);
303 }
304 
roundUpToMultiple(uint32_t value,uint32_t multiple)305 static uint32_t roundUpToMultiple(uint32_t value, uint32_t multiple) {
306     return (value + multiple - 1) / multiple * multiple;
307 }
308 
createSharedMemoryFromHidlMemory(const hidl_memory & memory)309 nn::GeneralResult<nn::SharedMemory> createSharedMemoryFromHidlMemory(const hidl_memory& memory) {
310     CHECK_LE(memory.size(), std::numeric_limits<size_t>::max());
311     if (!memory.valid()) {
312         return NN_ERROR() << "Unable to convert invalid hidl_memory";
313     }
314 
315     if (memory.name() == "ashmem") {
316         if (memory.handle()->numFds != 1) {
317             return NN_ERROR() << "Unable to convert invalid ashmem memory object with "
318                               << memory.handle()->numFds << " numFds, but expected 1";
319         }
320         if (memory.handle()->numInts != 0) {
321             return NN_ERROR() << "Unable to convert invalid ashmem memory object with "
322                               << memory.handle()->numInts << " numInts, but expected 0";
323         }
324         auto handle = nn::Memory::Ashmem{
325                 .fd = NN_TRY(nn::dupFd(memory.handle()->data[0])),
326                 .size = static_cast<size_t>(memory.size()),
327         };
328         return std::make_shared<const nn::Memory>(nn::Memory{.handle = std::move(handle)});
329     }
330 
331     if (memory.name() == "mmap_fd") {
332         if (memory.handle()->numFds != 1) {
333             return NN_ERROR() << "Unable to convert invalid mmap_fd memory object with "
334                               << memory.handle()->numFds << " numFds, but expected 1";
335         }
336         if (memory.handle()->numInts != 3) {
337             return NN_ERROR() << "Unable to convert invalid mmap_fd memory object with "
338                               << memory.handle()->numInts << " numInts, but expected 3";
339         }
340 
341         const int fd = memory.handle()->data[0];
342         const int prot = memory.handle()->data[1];
343         const int lower = memory.handle()->data[2];
344         const int higher = memory.handle()->data[3];
345         const size_t offset = nn::getOffsetFromInts(lower, higher);
346 
347         return nn::createSharedMemoryFromFd(static_cast<size_t>(memory.size()), prot, fd, offset);
348     }
349 
350     if (memory.name() != "hardware_buffer_blob") {
351         auto handle = nn::Memory::Unknown{
352                 .handle = NN_TRY(sharedHandleFromNativeHandle(memory.handle())),
353                 .size = static_cast<size_t>(memory.size()),
354                 .name = memory.name(),
355         };
356         return std::make_shared<const nn::Memory>(nn::Memory{.handle = std::move(handle)});
357     }
358 
359     const auto size = memory.size();
360     const auto format = AHARDWAREBUFFER_FORMAT_BLOB;
361     const auto usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN;
362     const uint32_t width = size;
363     const uint32_t height = 1;  // height is always 1 for BLOB mode AHardwareBuffer.
364     const uint32_t layers = 1;  // layers is always 1 for BLOB mode AHardwareBuffer.
365 
366     // AHardwareBuffer_createFromHandle() might fail because an allocator
367     // expects a specific stride value. In that case, we try to guess it by
368     // aligning the width to small powers of 2.
369     // TODO(b/174120849): Avoid stride assumptions.
370     AHardwareBuffer* hardwareBuffer = nullptr;
371     status_t status = UNKNOWN_ERROR;
372     for (uint32_t alignment : {1, 4, 32, 64, 128, 2, 8, 16}) {
373         const uint32_t stride = roundUpToMultiple(width, alignment);
374         AHardwareBuffer_Desc desc{
375                 .width = width,
376                 .height = height,
377                 .layers = layers,
378                 .format = format,
379                 .usage = usage,
380                 .stride = stride,
381         };
382         status = AHardwareBuffer_createFromHandle(&desc, memory.handle(),
383                                                   AHARDWAREBUFFER_CREATE_FROM_HANDLE_METHOD_CLONE,
384                                                   &hardwareBuffer);
385         if (status == NO_ERROR) {
386             break;
387         }
388     }
389     if (status != NO_ERROR) {
390         return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
391                << "Can't create AHardwareBuffer from handle. Error: " << status;
392     }
393 
394     return nn::createSharedMemoryFromAHWB(hardwareBuffer, /*takeOwnership=*/true);
395 }
396 
hidlHandleFromSharedHandle(const nn::Handle & handle)397 nn::GeneralResult<hidl_handle> hidlHandleFromSharedHandle(const nn::Handle& handle) {
398     std::vector<base::unique_fd> fds;
399     fds.reserve(handle.fds.size());
400     for (const auto& fd : handle.fds) {
401         const int dupFd = dup(fd);
402         if (dupFd == -1) {
403             return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) << "Failed to dup the fd";
404         }
405         fds.emplace_back(dupFd);
406     }
407 
408     constexpr size_t kIntMax = std::numeric_limits<int>::max();
409     CHECK_LE(handle.fds.size(), kIntMax);
410     CHECK_LE(handle.ints.size(), kIntMax);
411     native_handle_t* nativeHandle = native_handle_create(static_cast<int>(handle.fds.size()),
412                                                          static_cast<int>(handle.ints.size()));
413     if (nativeHandle == nullptr) {
414         return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) << "Failed to create native_handle";
415     }
416     for (size_t i = 0; i < fds.size(); ++i) {
417         nativeHandle->data[i] = fds[i].release();
418     }
419     std::copy(handle.ints.begin(), handle.ints.end(), &nativeHandle->data[nativeHandle->numFds]);
420 
421     hidl_handle hidlHandle;
422     hidlHandle.setTo(nativeHandle, /*shouldOwn=*/true);
423     return hidlHandle;
424 }
425 
sharedHandleFromNativeHandle(const native_handle_t * handle)426 nn::GeneralResult<nn::Handle> sharedHandleFromNativeHandle(const native_handle_t* handle) {
427     if (handle == nullptr) {
428         return NN_ERROR() << "sharedHandleFromNativeHandle failed because handle is nullptr";
429     }
430 
431     std::vector<base::unique_fd> fds;
432     fds.reserve(handle->numFds);
433     for (int i = 0; i < handle->numFds; ++i) {
434         const int dupFd = dup(handle->data[i]);
435         if (dupFd == -1) {
436             return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) << "Failed to dup the fd";
437         }
438         fds.emplace_back(dupFd);
439     }
440 
441     std::vector<int> ints(&handle->data[handle->numFds],
442                           &handle->data[handle->numFds + handle->numInts]);
443 
444     return nn::Handle{.fds = std::move(fds), .ints = std::move(ints)};
445 }
446 
convertSyncFences(const std::vector<nn::SyncFence> & syncFences)447 nn::GeneralResult<hidl_vec<hidl_handle>> convertSyncFences(
448         const std::vector<nn::SyncFence>& syncFences) {
449     hidl_vec<hidl_handle> handles(syncFences.size());
450     for (size_t i = 0; i < syncFences.size(); ++i) {
451         const auto& handle = syncFences[i].getSharedHandle();
452         if (handle == nullptr) {
453             return NN_ERROR() << "convertSyncFences failed because sync fence is empty";
454         }
455         handles[i] = NN_TRY(hidlHandleFromSharedHandle(*handle));
456     }
457     return handles;
458 }
459 
460 }  // namespace android::hardware::neuralnetworks::utils
461