blob: 08d0e80a81cf78c59f7f6456b80d6b68bbc8fe41 [file] [log] [blame]
Michael Butler60296322019-01-17 17:54:51 -08001/*
2 * Copyright (C) 2019 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
Michael Butler89e99ba2019-01-24 02:36:37 -080017#define LOG_TAG "ExecutionBurstServer"
18
Michael Butler60296322019-01-17 17:54:51 -080019#include "ExecutionBurstServer.h"
20
21#include <android-base/logging.h>
Michael Butler89e99ba2019-01-24 02:36:37 -080022#include <string>
Michael Butler3db6fe52019-01-29 11:20:30 -080023#include "Tracing.h"
Michael Butler60296322019-01-17 17:54:51 -080024
Michael Butler3db6fe52019-01-29 11:20:30 -080025namespace android::nn {
Michael Butler60296322019-01-17 17:54:51 -080026
Michael Butler238fe722019-03-21 12:17:27 -070027namespace {
Michael Butler60296322019-01-17 17:54:51 -080028
Michael Butler238fe722019-03-21 12:17:27 -070029// DefaultBurstExecutorWithCache adapts an IPreparedModel so that it can be
30// used as an IBurstExecutorWithCache. Specifically, the cache simply stores the
31// hidl_memory object, and the execution forwards calls to the provided
32// IPreparedModel's "executeSynchronously" method. With this class, hidl_memory
33// must be mapped and unmapped for each execution.
34class DefaultBurstExecutorWithCache : public ExecutionBurstServer::IBurstExecutorWithCache {
35 public:
36 DefaultBurstExecutorWithCache(IPreparedModel* preparedModel) : mpPreparedModel(preparedModel) {}
Michael Butler60296322019-01-17 17:54:51 -080037
Michael Butler238fe722019-03-21 12:17:27 -070038 bool isCacheEntryPresent(int32_t slot) const override {
Michael Butler47c988f62019-03-14 17:34:48 -070039 return slot < mMemoryCache.size() && mMemoryCache[slot].valid();
Michael Butler238fe722019-03-21 12:17:27 -070040 }
Michael Butler47c988f62019-03-14 17:34:48 -070041
Michael Butler238fe722019-03-21 12:17:27 -070042 void addCacheEntry(const hidl_memory& memory, int32_t slot) override {
43 if (slot >= mMemoryCache.size()) {
44 mMemoryCache.resize(slot + 1);
45 }
46 mMemoryCache[slot] = memory;
47 }
Michael Butler60296322019-01-17 17:54:51 -080048
Michael Butler238fe722019-03-21 12:17:27 -070049 void removeCacheEntry(int32_t slot) override {
50 if (slot < mMemoryCache.size()) {
51 mMemoryCache[slot] = {};
52 }
53 }
54
55 std::tuple<ErrorStatus, hidl_vec<OutputShape>, Timing> execute(
56 const Request& request, const std::vector<int32_t>& slots,
57 MeasureTiming measure) override {
58 // convert slots to pools
59 hidl_vec<hidl_memory> pools(slots.size());
60 std::transform(slots.begin(), slots.end(), pools.begin(), [this](int32_t slot) {
61 return slot < mMemoryCache.size() ? mMemoryCache[slot] : hidl_memory{};
62 });
63
64 // create full request
65 Request fullRequest = request;
66 fullRequest.pools = std::move(pools);
67
68 // setup execution
69 ErrorStatus returnedStatus = ErrorStatus::GENERAL_FAILURE;
70 hidl_vec<OutputShape> returnedOutputShapes;
71 Timing returnedTiming;
72 auto cb = [&returnedStatus, &returnedOutputShapes, &returnedTiming](
73 ErrorStatus status, const hidl_vec<OutputShape>& outputShapes,
74 const Timing& timing) {
75 returnedStatus = status;
76 returnedOutputShapes = outputShapes;
77 returnedTiming = timing;
Michael Butler47c988f62019-03-14 17:34:48 -070078 };
Michael Butler60296322019-01-17 17:54:51 -080079
Michael Butler238fe722019-03-21 12:17:27 -070080 // execute
81 const Return<void> ret = mpPreparedModel->executeSynchronously(fullRequest, measure, cb);
82 if (!ret.isOk() || returnedStatus != ErrorStatus::NONE) {
83 LOG(ERROR) << "IPreparedModelAdapter::execute -- Error executing";
84 return {ErrorStatus::GENERAL_FAILURE, {}, {}};
Michael Butler89e99ba2019-01-24 02:36:37 -080085 }
Michael Butler60296322019-01-17 17:54:51 -080086
Michael Butler238fe722019-03-21 12:17:27 -070087 return std::make_tuple(returnedStatus, std::move(returnedOutputShapes), returnedTiming);
Michael Butler60296322019-01-17 17:54:51 -080088 }
89
Michael Butler238fe722019-03-21 12:17:27 -070090 private:
91 IPreparedModel* const mpPreparedModel;
92 std::vector<hidl_memory> mMemoryCache;
93};
Michael Butler47c988f62019-03-14 17:34:48 -070094
Michael Butler238fe722019-03-21 12:17:27 -070095} // anonymous namespace
Michael Butler60296322019-01-17 17:54:51 -080096
Michael Butler3db6fe52019-01-29 11:20:30 -080097sp<ExecutionBurstServer> ExecutionBurstServer::create(
98 const sp<IBurstCallback>& callback, const MQDescriptorSync<FmqRequestDatum>& requestChannel,
Michael Butler238fe722019-03-21 12:17:27 -070099 const MQDescriptorSync<FmqResultDatum>& resultChannel,
100 std::shared_ptr<IBurstExecutorWithCache> executorWithCache) {
Michael Butler3db6fe52019-01-29 11:20:30 -0800101 // check inputs
Michael Butler238fe722019-03-21 12:17:27 -0700102 if (callback == nullptr || executorWithCache == nullptr) {
Michael Butler3db6fe52019-01-29 11:20:30 -0800103 LOG(ERROR) << "ExecutionBurstServer::create passed a nullptr";
104 return nullptr;
105 }
106
107 // create FMQ objects
Michael Butler238fe722019-03-21 12:17:27 -0700108 std::unique_ptr<FmqRequestChannel> fmqRequestChannel =
109 std::make_unique<FmqRequestChannel>(requestChannel);
110 std::unique_ptr<FmqResultChannel> fmqResultChannel =
111 std::make_unique<FmqResultChannel>(resultChannel);
Michael Butler3db6fe52019-01-29 11:20:30 -0800112
113 // check FMQ objects
Michael Butler238fe722019-03-21 12:17:27 -0700114 if (!fmqRequestChannel->isValid() || !fmqResultChannel->isValid()) {
Michael Butler3db6fe52019-01-29 11:20:30 -0800115 LOG(ERROR) << "ExecutionBurstServer::create failed to create FastMessageQueue";
116 return nullptr;
117 }
118
119 // make and return context
120 return new ExecutionBurstServer(callback, std::move(fmqRequestChannel),
Michael Butler238fe722019-03-21 12:17:27 -0700121 std::move(fmqResultChannel), std::move(executorWithCache));
Michael Butler3db6fe52019-01-29 11:20:30 -0800122}
123
Michael Butler238fe722019-03-21 12:17:27 -0700124sp<ExecutionBurstServer> ExecutionBurstServer::create(
125 const sp<IBurstCallback>& callback, const MQDescriptorSync<FmqRequestDatum>& requestChannel,
126 const MQDescriptorSync<FmqResultDatum>& resultChannel, IPreparedModel* preparedModel) {
127 // check relevant input
128 if (preparedModel == nullptr) {
129 LOG(ERROR) << "ExecutionBurstServer::create passed a nullptr";
130 return nullptr;
131 }
132
133 // adapt IPreparedModel to have caching
134 const std::shared_ptr<DefaultBurstExecutorWithCache> preparedModelAdapter =
135 std::make_shared<DefaultBurstExecutorWithCache>(preparedModel);
136
137 // make and return context
138 return ExecutionBurstServer::create(callback, requestChannel, resultChannel,
139 preparedModelAdapter);
140}
141
142ExecutionBurstServer::ExecutionBurstServer(
143 const sp<IBurstCallback>& callback, std::unique_ptr<FmqRequestChannel> requestChannel,
144 std::unique_ptr<FmqResultChannel> resultChannel,
145 std::shared_ptr<IBurstExecutorWithCache> executorWithCache)
146 : mCallback(callback),
Michael Butler60296322019-01-17 17:54:51 -0800147 mFmqRequestChannel(std::move(requestChannel)),
148 mFmqResultChannel(std::move(resultChannel)),
Michael Butler238fe722019-03-21 12:17:27 -0700149 mExecutorWithCache(std::move(executorWithCache)),
Michael Butler60296322019-01-17 17:54:51 -0800150 mBlocking(mFmqRequestChannel->getEventFlagWord() != nullptr) {
151 // TODO: highly document the threading behavior of this class
152 mWorker = std::async(std::launch::async, [this] { task(); });
153}
154
155ExecutionBurstServer::~ExecutionBurstServer() {
156 // set teardown flag
157 mTeardown = true;
158
159 // force unblock
Michael Butler89e99ba2019-01-24 02:36:37 -0800160 // ExecutionBurstServer is by default waiting on a request packet. If the
161 // client process destroys its burst object, the server will still be
162 // waiting on the futex (assuming mBlocking is true). This force unblock
163 // wakes up any thread waiting on the futex.
Michael Butler60296322019-01-17 17:54:51 -0800164 if (mBlocking) {
Michael Butler89e99ba2019-01-24 02:36:37 -0800165 // TODO: look for a different/better way to signal/notify the futex to
166 // wake up any thread waiting on it
Michael Butler60296322019-01-17 17:54:51 -0800167 FmqRequestDatum datum;
168 datum.packetInformation({/*.packetSize=*/0, /*.numberOfInputOperands=*/0,
169 /*.numberOfOutputOperands=*/0, /*.numberOfPools=*/0});
170 mFmqRequestChannel->writeBlocking(&datum, 1);
171 }
172
173 // wait for task thread to end
174 mWorker.wait();
175}
176
Michael Butler238fe722019-03-21 12:17:27 -0700177Return<void> ExecutionBurstServer::freeMemory(int32_t slot) {
178 std::lock_guard<std::mutex> hold(mMutex);
179 mExecutorWithCache->removeCacheEntry(slot);
180 return Void();
181}
182
183void ExecutionBurstServer::ensureCacheEntriesArePresentLocked(const std::vector<int32_t>& slots) {
184 const auto slotIsKnown = [this](int32_t slot) {
185 return mExecutorWithCache->isCacheEntryPresent(slot);
186 };
187
188 // find unique unknown slots
189 std::vector<int32_t> unknownSlots = slots;
190 auto unknownSlotsEnd = unknownSlots.end();
191 std::sort(unknownSlots.begin(), unknownSlotsEnd);
192 unknownSlotsEnd = std::unique(unknownSlots.begin(), unknownSlotsEnd);
193 unknownSlotsEnd = std::remove_if(unknownSlots.begin(), unknownSlotsEnd, slotIsKnown);
194 unknownSlots.erase(unknownSlotsEnd, unknownSlots.end());
195
196 // quick-exit if all slots are known
197 if (unknownSlots.empty()) {
198 return;
199 }
200
201 ErrorStatus errorStatus = ErrorStatus::GENERAL_FAILURE;
202 std::vector<hidl_memory> returnedMemories;
203 auto cb = [&errorStatus, &returnedMemories](ErrorStatus status,
204 const hidl_vec<hidl_memory>& memories) {
205 errorStatus = status;
206 returnedMemories = memories;
207 };
208
209 const Return<void> ret = mCallback->getMemories(unknownSlots, cb);
210
211 if (!ret.isOk() || errorStatus != ErrorStatus::NONE ||
212 returnedMemories.size() != unknownSlots.size()) {
213 LOG(ERROR) << "Error retrieving memories";
214 return;
215 }
216
217 // add memories to unknown slots
218 for (size_t i = 0; i < unknownSlots.size(); ++i) {
219 mExecutorWithCache->addCacheEntry(returnedMemories[i], unknownSlots[i]);
220 }
221}
222
Michael Butler60296322019-01-17 17:54:51 -0800223bool ExecutionBurstServer::sendPacket(const std::vector<FmqResultDatum>& packet) {
224 if (mTeardown) {
225 return false;
226 }
227
228 if (mBlocking) {
229 return mFmqResultChannel->writeBlocking(packet.data(), packet.size());
230 } else {
231 return mFmqResultChannel->write(packet.data(), packet.size());
232 }
233}
234
235std::vector<FmqRequestDatum> ExecutionBurstServer::getPacketBlocking() {
236 using discriminator = FmqRequestDatum::hidl_discriminator;
237
238 if (mTeardown) {
239 return {};
240 }
241
Michael Butler89e99ba2019-01-24 02:36:37 -0800242 // wait for request packet and read first element of request packet
243 // TODO: have a more elegant way to wait for data, and read it all at once.
244 // For example, EventFlag can be used to directly wait on the futex, and all
245 // the data can be read at once with a non-blocking call to
246 // MessageQueue::read. For further optimization, MessageQueue::beginRead and
247 // MessageQueue::commitRead can be used to avoid an extra copy of the
248 // metadata.
Michael Butler60296322019-01-17 17:54:51 -0800249 FmqRequestDatum datum;
250 bool success = false;
251 if (mBlocking) {
252 success = mFmqRequestChannel->readBlocking(&datum, 1);
253 } else {
254 while ((success = !mTeardown.load(std::memory_order_relaxed)) &&
255 !mFmqRequestChannel->read(&datum, 1)) {
256 }
257 }
258
259 // terminate loop
260 if (mTeardown) {
261 return {};
262 }
263
264 // validate packet information
265 if (!success || datum.getDiscriminator() != discriminator::packetInformation) {
266 LOG(ERROR) << "FMQ Request packet ill-formed";
267 return {};
268 }
269
Michael Butler3db6fe52019-01-29 11:20:30 -0800270 NNTRACE_FULL(NNTRACE_LAYER_IPC, NNTRACE_PHASE_EXECUTION, "ExecutionBurstServer getting packet");
271
Michael Butler60296322019-01-17 17:54:51 -0800272 // unpack packet information
273 const auto& packetInfo = datum.packetInformation();
274 const size_t count = packetInfo.packetSize;
275
276 // retrieve remaining elements
277 // NOTE: all of the data is already available at this point, so there's no
Michael Butler3db6fe52019-01-29 11:20:30 -0800278 // need to do a blocking wait to wait for more data. This is known because
279 // in FMQ, all writes are published (made available) atomically. Currently,
280 // the producer always publishes the entire packet in one function call, so
281 // if the first element of the packet is available, the remaining elements
282 // are also available.
Michael Butler60296322019-01-17 17:54:51 -0800283 std::vector<FmqRequestDatum> packet(count);
284 packet.front() = datum;
285 success = mFmqRequestChannel->read(packet.data() + 1, packet.size() - 1);
286
287 if (!success) {
288 return {};
289 }
290
291 return packet;
292}
293
294// deserialize request
Michael Butler238fe722019-03-21 12:17:27 -0700295std::tuple<Request, std::vector<int32_t>, MeasureTiming> ExecutionBurstServer::deserialize(
Michael Butler60296322019-01-17 17:54:51 -0800296 const std::vector<FmqRequestDatum>& data) {
297 using discriminator = FmqRequestDatum::hidl_discriminator;
298
Michael Butler60296322019-01-17 17:54:51 -0800299 size_t index = 0;
300
301 // validate packet information
302 if (data[index].getDiscriminator() != discriminator::packetInformation) {
303 LOG(ERROR) << "FMQ Request packet ill-formed";
Michael Butler238fe722019-03-21 12:17:27 -0700304 return {{}, {}, MeasureTiming::NO};
Michael Butler60296322019-01-17 17:54:51 -0800305 }
306
307 // unpackage packet information
308 const FmqRequestDatum::PacketInformation& packetInfo = data[index].packetInformation();
309 index++;
310 const uint32_t packetSize = packetInfo.packetSize;
311 const uint32_t numberOfInputOperands = packetInfo.numberOfInputOperands;
312 const uint32_t numberOfOutputOperands = packetInfo.numberOfOutputOperands;
313 const uint32_t numberOfPools = packetInfo.numberOfPools;
314
315 // unpackage input operands
316 std::vector<RequestArgument> inputs;
317 inputs.reserve(numberOfInputOperands);
318 for (size_t operand = 0; operand < numberOfInputOperands; ++operand) {
319 // validate input operand information
320 if (data[index].getDiscriminator() != discriminator::inputOperandInformation) {
321 LOG(ERROR) << "FMQ Request packet ill-formed";
Michael Butler238fe722019-03-21 12:17:27 -0700322 return {{}, {}, MeasureTiming::NO};
Michael Butler60296322019-01-17 17:54:51 -0800323 }
324
325 // unpackage operand information
326 const FmqRequestDatum::OperandInformation& operandInfo =
327 data[index].inputOperandInformation();
328 index++;
329 const bool hasNoValue = operandInfo.hasNoValue;
330 const DataLocation location = operandInfo.location;
331 const uint32_t numberOfDimensions = operandInfo.numberOfDimensions;
332
333 // unpackage operand dimensions
334 std::vector<uint32_t> dimensions;
335 dimensions.reserve(numberOfDimensions);
336 for (size_t i = 0; i < numberOfDimensions; ++i) {
337 // validate dimension
338 if (data[index].getDiscriminator() != discriminator::inputOperandDimensionValue) {
339 LOG(ERROR) << "FMQ Request packet ill-formed";
Michael Butler238fe722019-03-21 12:17:27 -0700340 return {{}, {}, MeasureTiming::NO};
Michael Butler60296322019-01-17 17:54:51 -0800341 }
342
343 // unpackage dimension
344 const uint32_t dimension = data[index].inputOperandDimensionValue();
345 index++;
346
347 // store result
348 dimensions.push_back(dimension);
349 }
350
351 // store result
352 inputs.push_back(
353 {/*.hasNoValue=*/hasNoValue, /*.location=*/location, /*.dimensions=*/dimensions});
354 }
355
356 // unpackage output operands
357 std::vector<RequestArgument> outputs;
358 outputs.reserve(numberOfOutputOperands);
359 for (size_t operand = 0; operand < numberOfOutputOperands; ++operand) {
360 // validate output operand information
361 if (data[index].getDiscriminator() != discriminator::outputOperandInformation) {
362 LOG(ERROR) << "FMQ Request packet ill-formed";
Michael Butler238fe722019-03-21 12:17:27 -0700363 return {{}, {}, MeasureTiming::NO};
Michael Butler60296322019-01-17 17:54:51 -0800364 }
365
366 // unpackage operand information
367 const FmqRequestDatum::OperandInformation& operandInfo =
368 data[index].outputOperandInformation();
369 index++;
370 const bool hasNoValue = operandInfo.hasNoValue;
371 const DataLocation location = operandInfo.location;
372 const uint32_t numberOfDimensions = operandInfo.numberOfDimensions;
373
374 // unpackage operand dimensions
375 std::vector<uint32_t> dimensions;
376 dimensions.reserve(numberOfDimensions);
377 for (size_t i = 0; i < numberOfDimensions; ++i) {
378 // validate dimension
379 if (data[index].getDiscriminator() != discriminator::outputOperandDimensionValue) {
380 LOG(ERROR) << "FMQ Request packet ill-formed";
Michael Butler238fe722019-03-21 12:17:27 -0700381 return {{}, {}, MeasureTiming::NO};
Michael Butler60296322019-01-17 17:54:51 -0800382 }
383
384 // unpackage dimension
385 const uint32_t dimension = data[index].outputOperandDimensionValue();
386 index++;
387
388 // store result
389 dimensions.push_back(dimension);
390 }
391
392 // store result
393 outputs.push_back(
394 {/*.hasNoValue=*/hasNoValue, /*.location=*/location, /*.dimensions=*/dimensions});
395 }
396
397 // unpackage pools
398 std::vector<int32_t> slots;
399 slots.reserve(numberOfPools);
400 for (size_t pool = 0; pool < numberOfPools; ++pool) {
401 // validate input operand information
402 if (data[index].getDiscriminator() != discriminator::poolIdentifier) {
403 LOG(ERROR) << "FMQ Request packet ill-formed";
Michael Butler238fe722019-03-21 12:17:27 -0700404 return {{}, {}, MeasureTiming::NO};
Michael Butler60296322019-01-17 17:54:51 -0800405 }
406
407 // unpackage operand information
408 const int32_t poolId = data[index].poolIdentifier();
409 index++;
410
411 // store result
412 slots.push_back(poolId);
413 }
Michael Butler60296322019-01-17 17:54:51 -0800414
415 // validate measureTiming
416 if (data[index].getDiscriminator() != discriminator::measureTiming) {
417 LOG(ERROR) << "FMQ Request packet ill-formed";
Michael Butler238fe722019-03-21 12:17:27 -0700418 return {{}, {}, MeasureTiming::NO};
Michael Butler60296322019-01-17 17:54:51 -0800419 }
420
421 // unpackage measureTiming
422 const MeasureTiming measure = data[index].measureTiming();
423 index++;
424
425 // validate packet information
426 if (index != packetSize) {
427 LOG(ERROR) << "FMQ Result packet ill-formed";
Michael Butler238fe722019-03-21 12:17:27 -0700428 return {{}, {}, MeasureTiming::NO};
Michael Butler60296322019-01-17 17:54:51 -0800429 }
430
431 // return request
Michael Butler238fe722019-03-21 12:17:27 -0700432 return {{/*.inputs=*/inputs, /*.outputs=*/outputs, /*.pools=*/{}}, std::move(slots), measure};
Michael Butler60296322019-01-17 17:54:51 -0800433}
434
435// serialize result
436std::vector<FmqResultDatum> ExecutionBurstServer::serialize(
437 ErrorStatus errorStatus, const std::vector<OutputShape>& outputShapes, Timing timing) {
438 // count how many elements need to be sent for a request
439 size_t count = 2 + outputShapes.size();
440 for (const auto& outputShape : outputShapes) {
441 count += outputShape.dimensions.size();
442 }
443
444 // create buffer to temporarily store elements
445 std::vector<FmqResultDatum> data;
446 data.reserve(count);
447
448 // package packetInfo
449 {
450 FmqResultDatum datum;
451 datum.packetInformation({/*.packetSize=*/static_cast<uint32_t>(count),
452 /*.errorStatus=*/errorStatus,
453 /*.numberOfOperands=*/static_cast<uint32_t>(outputShapes.size())});
454 data.push_back(datum);
455 }
456
457 // package output shape data
458 for (const auto& operand : outputShapes) {
459 // package operand information
460 FmqResultDatum datum;
461 datum.operandInformation(
462 {/*.isSufficient=*/operand.isSufficient,
463 /*.numberOfDimensions=*/static_cast<uint32_t>(operand.dimensions.size())});
464 data.push_back(datum);
465
466 // package operand dimensions
467 for (uint32_t dimension : operand.dimensions) {
468 FmqResultDatum datum;
469 datum.operandDimensionValue(dimension);
470 data.push_back(datum);
471 }
472 }
473
474 // package executionTiming
475 {
476 FmqResultDatum datum;
477 datum.executionTiming(timing);
478 data.push_back(datum);
479 }
480
481 // return result
482 return data;
483}
484
Michael Butler60296322019-01-17 17:54:51 -0800485void ExecutionBurstServer::task() {
486 while (!mTeardown) {
487 // receive request
488 const std::vector<FmqRequestDatum> requestData = getPacketBlocking();
489
490 // terminate loop
491 if (mTeardown) {
492 return;
493 }
494
Michael Butler3db6fe52019-01-29 11:20:30 -0800495 NNTRACE_FULL(NNTRACE_LAYER_IPC, NNTRACE_PHASE_EXECUTION,
496 "ExecutionBurstServer processing packet and returning results");
497
Michael Butler238fe722019-03-21 12:17:27 -0700498 // continue processing; types are Request, std::vector<int32_t>, and
499 // MeasureTiming, respectively
500 const auto [requestWithoutPools, slotsOfPools, measure] = deserialize(requestData);
Michael Butler60296322019-01-17 17:54:51 -0800501
Michael Butler238fe722019-03-21 12:17:27 -0700502 // ensure executor with cache has required memory
503 std::lock_guard<std::mutex> hold(mMutex);
504 ensureCacheEntriesArePresentLocked(slotsOfPools);
505
506 // perform computation; types are ErrorStatus, hidl_vec<OutputShape>,
507 // and Timing, respectively
508 const auto [errorStatus, outputShapes, returnedTiming] =
509 mExecutorWithCache->execute(requestWithoutPools, slotsOfPools, measure);
Michael Butler60296322019-01-17 17:54:51 -0800510
511 // return result
512 const std::vector<FmqResultDatum> result =
513 serialize(errorStatus, outputShapes, returnedTiming);
514 sendPacket(result);
515 }
516}
517
Michael Butler3db6fe52019-01-29 11:20:30 -0800518} // namespace android::nn