blob: 9703c2d765629da5ef81f20e62196937ecda0a5b [file] [log] [blame]
Slava Shklyaevfeb87a92018-09-12 14:52:02 +01001/*
2 * Copyright (C) 2018 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 "neuralnetworks_hidl_hal_test"
18
19#include "VtsHalNeuralnetworks.h"
20
21#include "Callbacks.h"
Michael Butler29471a82019-01-15 11:02:55 -080022#include "ExecutionBurstController.h"
Slava Shklyaevfeb87a92018-09-12 14:52:02 +010023#include "TestHarness.h"
24#include "Utils.h"
25
26#include <android-base/logging.h>
27#include <android/hidl/memory/1.0/IMemory.h>
28#include <hidlmemory/mapping.h>
29
30namespace android {
31namespace hardware {
32namespace neuralnetworks {
33namespace V1_2 {
34namespace vts {
35namespace functional {
36
Xusong Wang1a06e772018-10-31 08:43:12 -070037using ::android::hardware::neuralnetworks::V1_2::implementation::ExecutionCallback;
Slava Shklyaevfeb87a92018-09-12 14:52:02 +010038using ::android::hidl::memory::V1_0::IMemory;
39using test_helper::for_all;
40using test_helper::MixedTyped;
Michael K. Sandersda3bdbc2018-10-19 14:39:09 +010041using test_helper::MixedTypedExample;
Slava Shklyaevfeb87a92018-09-12 14:52:02 +010042
43///////////////////////// UTILITY FUNCTIONS /////////////////////////
44
David Gross55a3d322019-01-23 14:01:52 -080045static bool badTiming(Timing timing) {
46 return timing.timeOnDevice == UINT64_MAX && timing.timeInDriver == UINT64_MAX;
47}
48
Slava Shklyaevfeb87a92018-09-12 14:52:02 +010049// Primary validation function. This function will take a valid request, apply a
50// mutation to it to invalidate the request, then pass it to interface calls
51// that use the request. Note that the request here is passed by value, and any
52// mutation to the request does not leave this function.
53static void validate(const sp<IPreparedModel>& preparedModel, const std::string& message,
54 Request request, const std::function<void(Request*)>& mutation) {
55 mutation(&request);
Slava Shklyaevfeb87a92018-09-12 14:52:02 +010056
David Gross55a3d322019-01-23 14:01:52 -080057 // We'd like to test both with timing requested and without timing
58 // requested. Rather than running each test both ways, we'll decide whether
59 // to request timing by hashing the message. We do not use std::hash because
60 // it is not guaranteed stable across executions.
61 char hash = 0;
62 for (auto c : message) {
63 hash ^= c;
64 };
65 MeasureTiming measure = (hash & 1) ? MeasureTiming::YES : MeasureTiming::NO;
66
Michael Butler29471a82019-01-15 11:02:55 -080067 // asynchronous
David Gross4592ed12018-12-21 11:20:26 -080068 {
69 SCOPED_TRACE(message + " [execute_1_2]");
Slava Shklyaevfeb87a92018-09-12 14:52:02 +010070
David Gross4592ed12018-12-21 11:20:26 -080071 sp<ExecutionCallback> executionCallback = new ExecutionCallback();
72 ASSERT_NE(nullptr, executionCallback.get());
73 Return<ErrorStatus> executeLaunchStatus =
David Gross55a3d322019-01-23 14:01:52 -080074 preparedModel->execute_1_2(request, measure, executionCallback);
David Gross4592ed12018-12-21 11:20:26 -080075 ASSERT_TRUE(executeLaunchStatus.isOk());
76 ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, static_cast<ErrorStatus>(executeLaunchStatus));
77
78 executionCallback->wait();
79 ErrorStatus executionReturnStatus = executionCallback->getStatus();
Xusong Wangb50bc312018-11-07 09:33:59 -080080 const auto& outputShapes = executionCallback->getOutputShapes();
David Gross55a3d322019-01-23 14:01:52 -080081 Timing timing = executionCallback->getTiming();
David Gross4592ed12018-12-21 11:20:26 -080082 ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, executionReturnStatus);
Xusong Wangb50bc312018-11-07 09:33:59 -080083 ASSERT_EQ(outputShapes.size(), 0);
David Gross55a3d322019-01-23 14:01:52 -080084 ASSERT_TRUE(badTiming(timing));
David Gross4592ed12018-12-21 11:20:26 -080085 }
86
Michael Butler29471a82019-01-15 11:02:55 -080087 // synchronous
David Gross4592ed12018-12-21 11:20:26 -080088 {
89 SCOPED_TRACE(message + " [executeSynchronously]");
90
Xusong Wangb50bc312018-11-07 09:33:59 -080091 Return<void> executeStatus = preparedModel->executeSynchronously(
David Gross55a3d322019-01-23 14:01:52 -080092 request, measure,
93 [](ErrorStatus error, const hidl_vec<OutputShape>& outputShapes,
94 const Timing& timing) {
95 ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, error);
96 EXPECT_EQ(outputShapes.size(), 0);
97 EXPECT_TRUE(badTiming(timing));
98 });
David Gross4592ed12018-12-21 11:20:26 -080099 ASSERT_TRUE(executeStatus.isOk());
David Gross4592ed12018-12-21 11:20:26 -0800100 }
Michael Butler29471a82019-01-15 11:02:55 -0800101
102 // burst
103 {
104 SCOPED_TRACE(message + " [burst]");
105
106 // create burst
Michael Butler51c72182019-03-27 10:59:25 -0700107 std::shared_ptr<::android::nn::ExecutionBurstController> burst =
Michael Butler60a22532019-03-28 13:41:14 -0700108 ::android::nn::ExecutionBurstController::create(preparedModel, /*blocking=*/true);
Michael Butler29471a82019-01-15 11:02:55 -0800109 ASSERT_NE(nullptr, burst.get());
110
111 // create memory keys
112 std::vector<intptr_t> keys(request.pools.size());
113 for (size_t i = 0; i < keys.size(); ++i) {
114 keys[i] = reinterpret_cast<intptr_t>(&request.pools[i]);
115 }
116
117 // execute and verify
118 ErrorStatus error;
119 std::vector<OutputShape> outputShapes;
120 Timing timing;
121 std::tie(error, outputShapes, timing) = burst->compute(request, measure, keys);
122 EXPECT_EQ(ErrorStatus::INVALID_ARGUMENT, error);
123 EXPECT_EQ(outputShapes.size(), 0);
124 EXPECT_TRUE(badTiming(timing));
125
126 // additional burst testing
127 if (request.pools.size() > 0) {
128 // valid free
129 burst->freeMemory(keys.front());
130
131 // negative test: invalid free of unknown (blank) memory
132 burst->freeMemory(intptr_t{});
133
134 // negative test: double free of memory
135 burst->freeMemory(keys.front());
136 }
137 }
Slava Shklyaevfeb87a92018-09-12 14:52:02 +0100138}
139
140// Delete element from hidl_vec. hidl_vec doesn't support a "remove" operation,
141// so this is efficiently accomplished by moving the element to the end and
142// resizing the hidl_vec to one less.
143template <typename Type>
144static void hidl_vec_removeAt(hidl_vec<Type>* vec, uint32_t index) {
145 if (vec) {
146 std::rotate(vec->begin() + index, vec->begin() + index + 1, vec->end());
147 vec->resize(vec->size() - 1);
148 }
149}
150
151template <typename Type>
152static uint32_t hidl_vec_push_back(hidl_vec<Type>* vec, const Type& value) {
153 // assume vec is valid
154 const uint32_t index = vec->size();
155 vec->resize(index + 1);
156 (*vec)[index] = value;
157 return index;
158}
159
160///////////////////////// REMOVE INPUT ////////////////////////////////////
161
162static void removeInputTest(const sp<IPreparedModel>& preparedModel, const Request& request) {
163 for (size_t input = 0; input < request.inputs.size(); ++input) {
164 const std::string message = "removeInput: removed input " + std::to_string(input);
165 validate(preparedModel, message, request,
166 [input](Request* request) { hidl_vec_removeAt(&request->inputs, input); });
167 }
168}
169
170///////////////////////// REMOVE OUTPUT ////////////////////////////////////
171
172static void removeOutputTest(const sp<IPreparedModel>& preparedModel, const Request& request) {
173 for (size_t output = 0; output < request.outputs.size(); ++output) {
174 const std::string message = "removeOutput: removed Output " + std::to_string(output);
175 validate(preparedModel, message, request,
176 [output](Request* request) { hidl_vec_removeAt(&request->outputs, output); });
177 }
178}
179
180///////////////////////////// ENTRY POINT //////////////////////////////////
181
Michael K. Sandersda3bdbc2018-10-19 14:39:09 +0100182std::vector<Request> createRequests(const std::vector<MixedTypedExample>& examples) {
Slava Shklyaevfeb87a92018-09-12 14:52:02 +0100183 const uint32_t INPUT = 0;
184 const uint32_t OUTPUT = 1;
185
186 std::vector<Request> requests;
187
188 for (auto& example : examples) {
Michael K. Sandersda3bdbc2018-10-19 14:39:09 +0100189 const MixedTyped& inputs = example.operands.first;
190 const MixedTyped& outputs = example.operands.second;
Slava Shklyaevfeb87a92018-09-12 14:52:02 +0100191
192 std::vector<RequestArgument> inputs_info, outputs_info;
193 uint32_t inputSize = 0, outputSize = 0;
194
195 // This function only partially specifies the metadata (vector of RequestArguments).
196 // The contents are copied over below.
197 for_all(inputs, [&inputs_info, &inputSize](int index, auto, auto s) {
198 if (inputs_info.size() <= static_cast<size_t>(index)) inputs_info.resize(index + 1);
199 RequestArgument arg = {
200 .location = {.poolIndex = INPUT, .offset = 0, .length = static_cast<uint32_t>(s)},
201 .dimensions = {},
202 };
203 RequestArgument arg_empty = {
204 .hasNoValue = true,
205 };
206 inputs_info[index] = s ? arg : arg_empty;
207 inputSize += s;
208 });
209 // Compute offset for inputs 1 and so on
210 {
211 size_t offset = 0;
212 for (auto& i : inputs_info) {
213 if (!i.hasNoValue) i.location.offset = offset;
214 offset += i.location.length;
215 }
216 }
217
218 // Go through all outputs, initialize RequestArgument descriptors
219 for_all(outputs, [&outputs_info, &outputSize](int index, auto, auto s) {
220 if (outputs_info.size() <= static_cast<size_t>(index)) outputs_info.resize(index + 1);
221 RequestArgument arg = {
222 .location = {.poolIndex = OUTPUT, .offset = 0, .length = static_cast<uint32_t>(s)},
223 .dimensions = {},
224 };
225 outputs_info[index] = arg;
226 outputSize += s;
227 });
228 // Compute offset for outputs 1 and so on
229 {
230 size_t offset = 0;
231 for (auto& i : outputs_info) {
232 i.location.offset = offset;
233 offset += i.location.length;
234 }
235 }
236 std::vector<hidl_memory> pools = {nn::allocateSharedMemory(inputSize),
237 nn::allocateSharedMemory(outputSize)};
238 if (pools[INPUT].size() == 0 || pools[OUTPUT].size() == 0) {
239 return {};
240 }
241
242 // map pool
243 sp<IMemory> inputMemory = mapMemory(pools[INPUT]);
244 if (inputMemory == nullptr) {
245 return {};
246 }
247 char* inputPtr = reinterpret_cast<char*>(static_cast<void*>(inputMemory->getPointer()));
248 if (inputPtr == nullptr) {
249 return {};
250 }
251
252 // initialize pool
253 inputMemory->update();
254 for_all(inputs, [&inputs_info, inputPtr](int index, auto p, auto s) {
255 char* begin = (char*)p;
256 char* end = begin + s;
257 // TODO: handle more than one input
258 std::copy(begin, end, inputPtr + inputs_info[index].location.offset);
259 });
260 inputMemory->commit();
261
262 requests.push_back({.inputs = inputs_info, .outputs = outputs_info, .pools = pools});
263 }
264
265 return requests;
266}
267
Michael Butlerd6e38fd2019-04-26 17:46:08 -0700268void ValidationTest::validateRequests(const sp<IPreparedModel>& preparedModel,
269 const std::vector<Request>& requests) {
Slava Shklyaevfeb87a92018-09-12 14:52:02 +0100270 // validate each request
271 for (const Request& request : requests) {
272 removeInputTest(preparedModel, request);
273 removeOutputTest(preparedModel, request);
274 }
275}
276
277} // namespace functional
278} // namespace vts
279} // namespace V1_2
280} // namespace neuralnetworks
281} // namespace hardware
282} // namespace android