• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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 // Provides C++ classes to more easily use the Neural Networks API.
18 // TODO(b/117845862): this should be auto generated from NeuralNetworksWrapper.h.
19 
20 #ifndef ANDROID_FRAMEWORKS_ML_NN_RUNTIME_TEST_TEST_NEURAL_NETWORKS_WRAPPER_H
21 #define ANDROID_FRAMEWORKS_ML_NN_RUNTIME_TEST_TEST_NEURAL_NETWORKS_WRAPPER_H
22 
23 #include <math.h>
24 
25 #include <algorithm>
26 #include <memory>
27 #include <optional>
28 #include <string>
29 #include <utility>
30 #include <vector>
31 
32 #include "NeuralNetworks.h"
33 #include "NeuralNetworksWrapper.h"
34 #include "NeuralNetworksWrapperExtensions.h"
35 
36 #ifndef __NNAPI_FL5_MIN_ANDROID_API__
37 #define __NNAPI_FL5_MIN_ANDROID_API__ __ANDROID_API_FUTURE__
38 #endif
39 
40 namespace android {
41 namespace nn {
42 namespace test_wrapper {
43 
44 using wrapper::Event;
45 using wrapper::ExecutePreference;
46 using wrapper::ExecutePriority;
47 using wrapper::ExtensionModel;
48 using wrapper::ExtensionOperandParams;
49 using wrapper::ExtensionOperandType;
50 using wrapper::OperandType;
51 using wrapper::Result;
52 using wrapper::SymmPerChannelQuantParams;
53 using wrapper::Type;
54 
55 class Memory {
56    public:
57     // Takes ownership of a ANeuralNetworksMemory
Memory(ANeuralNetworksMemory * memory)58     Memory(ANeuralNetworksMemory* memory) : mMemory(memory) {}
59 
Memory(size_t size,int protect,int fd,size_t offset)60     Memory(size_t size, int protect, int fd, size_t offset) {
61         mValid = ANeuralNetworksMemory_createFromFd(size, protect, fd, offset, &mMemory) ==
62                  ANEURALNETWORKS_NO_ERROR;
63     }
64 
Memory(AHardwareBuffer * buffer)65     Memory(AHardwareBuffer* buffer) {
66         mValid = ANeuralNetworksMemory_createFromAHardwareBuffer(buffer, &mMemory) ==
67                  ANEURALNETWORKS_NO_ERROR;
68     }
69 
~Memory()70     virtual ~Memory() { ANeuralNetworksMemory_free(mMemory); }
71 
72     // Disallow copy semantics to ensure the runtime object can only be freed
73     // once. Copy semantics could be enabled if some sort of reference counting
74     // or deep-copy system for runtime objects is added later.
75     Memory(const Memory&) = delete;
76     Memory& operator=(const Memory&) = delete;
77 
78     // Move semantics to remove access to the runtime object from the wrapper
79     // object that is being moved. This ensures the runtime object will be
80     // freed only once.
Memory(Memory && other)81     Memory(Memory&& other) { *this = std::move(other); }
82     Memory& operator=(Memory&& other) {
83         if (this != &other) {
84             ANeuralNetworksMemory_free(mMemory);
85             mMemory = other.mMemory;
86             mValid = other.mValid;
87             other.mMemory = nullptr;
88             other.mValid = false;
89         }
90         return *this;
91     }
92 
get()93     ANeuralNetworksMemory* get() const { return mMemory; }
isValid()94     bool isValid() const { return mValid; }
95 
96    private:
97     ANeuralNetworksMemory* mMemory = nullptr;
98     bool mValid = true;
99 };
100 
101 class Model {
102    public:
Model()103     Model() {
104         // TODO handle the value returned by this call
105         ANeuralNetworksModel_create(&mModel);
106     }
~Model()107     ~Model() { ANeuralNetworksModel_free(mModel); }
108 
109     // Disallow copy semantics to ensure the runtime object can only be freed
110     // once. Copy semantics could be enabled if some sort of reference counting
111     // or deep-copy system for runtime objects is added later.
112     Model(const Model&) = delete;
113     Model& operator=(const Model&) = delete;
114 
115     // Move semantics to remove access to the runtime object from the wrapper
116     // object that is being moved. This ensures the runtime object will be
117     // freed only once.
Model(Model && other)118     Model(Model&& other) { *this = std::move(other); }
119     Model& operator=(Model&& other) {
120         if (this != &other) {
121             ANeuralNetworksModel_free(mModel);
122             mModel = other.mModel;
123             mNextOperandId = other.mNextOperandId;
124             mValid = other.mValid;
125             mRelaxed = other.mRelaxed;
126             mFinished = other.mFinished;
127             other.mModel = nullptr;
128             other.mNextOperandId = 0;
129             other.mValid = false;
130             other.mRelaxed = false;
131             other.mFinished = false;
132         }
133         return *this;
134     }
135 
finish()136     Result finish() {
137         if (mValid) {
138             auto result = static_cast<Result>(ANeuralNetworksModel_finish(mModel));
139             if (result != Result::NO_ERROR) {
140                 mValid = false;
141             }
142             mFinished = true;
143             return result;
144         } else {
145             return Result::BAD_STATE;
146         }
147     }
148 
addOperand(const OperandType * type)149     uint32_t addOperand(const OperandType* type) {
150         if (ANeuralNetworksModel_addOperand(mModel, &(type->operandType)) !=
151             ANEURALNETWORKS_NO_ERROR) {
152             mValid = false;
153         }
154         if (type->channelQuant) {
155             if (ANeuralNetworksModel_setOperandSymmPerChannelQuantParams(
156                         mModel, mNextOperandId, &type->channelQuant.value().params) !=
157                 ANEURALNETWORKS_NO_ERROR) {
158                 mValid = false;
159             }
160         }
161         return mNextOperandId++;
162     }
163 
164     template <typename T>
addConstantOperand(const OperandType * type,const T & value)165     uint32_t addConstantOperand(const OperandType* type, const T& value) {
166         static_assert(sizeof(T) <= ANEURALNETWORKS_MAX_SIZE_OF_IMMEDIATELY_COPIED_VALUES,
167                       "Values larger than ANEURALNETWORKS_MAX_SIZE_OF_IMMEDIATELY_COPIED_VALUES "
168                       "not supported");
169         uint32_t index = addOperand(type);
170         setOperandValue(index, &value);
171         return index;
172     }
173 
addModelOperand(const Model * value)174     uint32_t addModelOperand(const Model* value) {
175         OperandType operandType(Type::MODEL, {});
176         uint32_t operand = addOperand(&operandType);
177         setOperandValueFromModel(operand, value);
178         return operand;
179     }
180 
setOperandValue(uint32_t index,const void * buffer,size_t length)181     void setOperandValue(uint32_t index, const void* buffer, size_t length) {
182         if (ANeuralNetworksModel_setOperandValue(mModel, index, buffer, length) !=
183             ANEURALNETWORKS_NO_ERROR) {
184             mValid = false;
185         }
186     }
187 
188     template <typename T>
setOperandValue(uint32_t index,const T * value)189     void setOperandValue(uint32_t index, const T* value) {
190         static_assert(!std::is_pointer<T>(), "No operand may have a pointer as its value");
191         return setOperandValue(index, value, sizeof(T));
192     }
193 
setOperandValueFromMemory(uint32_t index,const Memory * memory,uint32_t offset,size_t length)194     void setOperandValueFromMemory(uint32_t index, const Memory* memory, uint32_t offset,
195                                    size_t length) {
196         if (ANeuralNetworksModel_setOperandValueFromMemory(mModel, index, memory->get(), offset,
197                                                            length) != ANEURALNETWORKS_NO_ERROR) {
198             mValid = false;
199         }
200     }
201 
setOperandValueFromModel(uint32_t index,const Model * value)202     void setOperandValueFromModel(uint32_t index, const Model* value) {
203         if (ANeuralNetworksModel_setOperandValueFromModel(mModel, index, value->mModel) !=
204             ANEURALNETWORKS_NO_ERROR) {
205             mValid = false;
206         }
207     }
208 
addOperation(ANeuralNetworksOperationType type,const std::vector<uint32_t> & inputs,const std::vector<uint32_t> & outputs)209     void addOperation(ANeuralNetworksOperationType type, const std::vector<uint32_t>& inputs,
210                       const std::vector<uint32_t>& outputs) {
211         if (ANeuralNetworksModel_addOperation(mModel, type, static_cast<uint32_t>(inputs.size()),
212                                               inputs.data(), static_cast<uint32_t>(outputs.size()),
213                                               outputs.data()) != ANEURALNETWORKS_NO_ERROR) {
214             mValid = false;
215         }
216     }
identifyInputsAndOutputs(const std::vector<uint32_t> & inputs,const std::vector<uint32_t> & outputs)217     void identifyInputsAndOutputs(const std::vector<uint32_t>& inputs,
218                                   const std::vector<uint32_t>& outputs) {
219         if (ANeuralNetworksModel_identifyInputsAndOutputs(
220                     mModel, static_cast<uint32_t>(inputs.size()), inputs.data(),
221                     static_cast<uint32_t>(outputs.size()),
222                     outputs.data()) != ANEURALNETWORKS_NO_ERROR) {
223             mValid = false;
224         }
225     }
226 
relaxComputationFloat32toFloat16(bool isRelax)227     void relaxComputationFloat32toFloat16(bool isRelax) {
228         if (ANeuralNetworksModel_relaxComputationFloat32toFloat16(mModel, isRelax) ==
229             ANEURALNETWORKS_NO_ERROR) {
230             mRelaxed = isRelax;
231         }
232     }
233 
getHandle()234     ANeuralNetworksModel* getHandle() const { return mModel; }
isValid()235     bool isValid() const { return mValid; }
isRelaxed()236     bool isRelaxed() const { return mRelaxed; }
isFinished()237     bool isFinished() const { return mFinished; }
238 
239    protected:
240     ANeuralNetworksModel* mModel = nullptr;
241     // We keep track of the operand ID as a convenience to the caller.
242     uint32_t mNextOperandId = 0;
243     bool mValid = true;
244     bool mRelaxed = false;
245     bool mFinished = false;
246 };
247 
248 class Compilation {
249    public:
250     // On success, createForDevice(s) will return Result::NO_ERROR and the created compilation;
251     // otherwise, it will return the error code and Compilation object wrapping a nullptr handle.
createForDevice(const Model * model,const ANeuralNetworksDevice * device)252     static std::pair<Result, Compilation> createForDevice(const Model* model,
253                                                           const ANeuralNetworksDevice* device) {
254         return createForDevices(model, {device});
255     }
createForDevices(const Model * model,const std::vector<const ANeuralNetworksDevice * > & devices)256     static std::pair<Result, Compilation> createForDevices(
257             const Model* model, const std::vector<const ANeuralNetworksDevice*>& devices) {
258         ANeuralNetworksCompilation* compilation = nullptr;
259         const Result result = static_cast<Result>(ANeuralNetworksCompilation_createForDevices(
260                 model->getHandle(), devices.empty() ? nullptr : devices.data(), devices.size(),
261                 &compilation));
262         return {result, Compilation(compilation)};
263     }
264 
Compilation(const Model * model)265     Compilation(const Model* model) {
266         int result = ANeuralNetworksCompilation_create(model->getHandle(), &mCompilation);
267         if (result != 0) {
268             // TODO Handle the error
269         }
270     }
271 
Compilation()272     Compilation() {}
273 
~Compilation()274     ~Compilation() { ANeuralNetworksCompilation_free(mCompilation); }
275 
276     // Disallow copy semantics to ensure the runtime object can only be freed
277     // once. Copy semantics could be enabled if some sort of reference counting
278     // or deep-copy system for runtime objects is added later.
279     Compilation(const Compilation&) = delete;
280     Compilation& operator=(const Compilation&) = delete;
281 
282     // Move semantics to remove access to the runtime object from the wrapper
283     // object that is being moved. This ensures the runtime object will be
284     // freed only once.
Compilation(Compilation && other)285     Compilation(Compilation&& other) { *this = std::move(other); }
286     Compilation& operator=(Compilation&& other) {
287         if (this != &other) {
288             ANeuralNetworksCompilation_free(mCompilation);
289             mCompilation = other.mCompilation;
290             other.mCompilation = nullptr;
291         }
292         return *this;
293     }
294 
setPreference(ExecutePreference preference)295     Result setPreference(ExecutePreference preference) {
296         return static_cast<Result>(ANeuralNetworksCompilation_setPreference(
297                 mCompilation, static_cast<int32_t>(preference)));
298     }
299 
setPriority(ExecutePriority priority)300     Result setPriority(ExecutePriority priority) {
301         return static_cast<Result>(ANeuralNetworksCompilation_setPriority(
302                 mCompilation, static_cast<int32_t>(priority)));
303     }
304 
setCaching(const std::string & cacheDir,const std::vector<uint8_t> & token)305     Result setCaching(const std::string& cacheDir, const std::vector<uint8_t>& token) {
306         if (token.size() != ANEURALNETWORKS_BYTE_SIZE_OF_CACHE_TOKEN) {
307             return Result::BAD_DATA;
308         }
309         return static_cast<Result>(ANeuralNetworksCompilation_setCaching(
310                 mCompilation, cacheDir.c_str(), token.data()));
311     }
312 
finish()313     Result finish() { return static_cast<Result>(ANeuralNetworksCompilation_finish(mCompilation)); }
314 
getPreferredMemoryAlignmentForInput(uint32_t index,uint32_t * alignment)315     Result getPreferredMemoryAlignmentForInput(uint32_t index, uint32_t* alignment) const {
316         if (__builtin_available(android __NNAPI_FL5_MIN_ANDROID_API__, *)) {
317             return static_cast<Result>(
318                     NNAPI_CALL(ANeuralNetworksCompilation_getPreferredMemoryAlignmentForInput(
319                             mCompilation, index, alignment)));
320         } else {
321             return Result::FEATURE_LEVEL_TOO_LOW;
322         }
323     };
324 
getPreferredMemoryPaddingForInput(uint32_t index,uint32_t * padding)325     Result getPreferredMemoryPaddingForInput(uint32_t index, uint32_t* padding) const {
326         if (__builtin_available(android __NNAPI_FL5_MIN_ANDROID_API__, *)) {
327             return static_cast<Result>(
328                     NNAPI_CALL(ANeuralNetworksCompilation_getPreferredMemoryPaddingForInput(
329                             mCompilation, index, padding)));
330         } else {
331             return Result::FEATURE_LEVEL_TOO_LOW;
332         }
333     };
334 
getPreferredMemoryAlignmentForOutput(uint32_t index,uint32_t * alignment)335     Result getPreferredMemoryAlignmentForOutput(uint32_t index, uint32_t* alignment) const {
336         if (__builtin_available(android __NNAPI_FL5_MIN_ANDROID_API__, *)) {
337             return static_cast<Result>(
338                     NNAPI_CALL(ANeuralNetworksCompilation_getPreferredMemoryAlignmentForOutput(
339                             mCompilation, index, alignment)));
340         } else {
341             return Result::FEATURE_LEVEL_TOO_LOW;
342         }
343     };
344 
getPreferredMemoryPaddingForOutput(uint32_t index,uint32_t * padding)345     Result getPreferredMemoryPaddingForOutput(uint32_t index, uint32_t* padding) const {
346         if (__builtin_available(android __NNAPI_FL5_MIN_ANDROID_API__, *)) {
347             return static_cast<Result>(
348                     NNAPI_CALL(ANeuralNetworksCompilation_getPreferredMemoryPaddingForOutput(
349                             mCompilation, index, padding)));
350         } else {
351             return Result::FEATURE_LEVEL_TOO_LOW;
352         }
353     };
354 
getHandle()355     ANeuralNetworksCompilation* getHandle() const { return mCompilation; }
356 
357    protected:
358     // Takes the ownership of ANeuralNetworksCompilation.
Compilation(ANeuralNetworksCompilation * compilation)359     Compilation(ANeuralNetworksCompilation* compilation) : mCompilation(compilation) {}
360 
361     ANeuralNetworksCompilation* mCompilation = nullptr;
362 };
363 
364 class Execution {
365    public:
Execution(const Compilation * compilation)366     Execution(const Compilation* compilation) : mCompilation(compilation->getHandle()) {
367         int result = ANeuralNetworksExecution_create(compilation->getHandle(), &mExecution);
368         if (result != 0) {
369             // TODO Handle the error
370         }
371     }
372 
~Execution()373     ~Execution() { ANeuralNetworksExecution_free(mExecution); }
374 
375     // Disallow copy semantics to ensure the runtime object can only be freed
376     // once. Copy semantics could be enabled if some sort of reference counting
377     // or deep-copy system for runtime objects is added later.
378     Execution(const Execution&) = delete;
379     Execution& operator=(const Execution&) = delete;
380 
381     // Move semantics to remove access to the runtime object from the wrapper
382     // object that is being moved. This ensures the runtime object will be
383     // freed only once.
Execution(Execution && other)384     Execution(Execution&& other) { *this = std::move(other); }
385     Execution& operator=(Execution&& other) {
386         if (this != &other) {
387             ANeuralNetworksExecution_free(mExecution);
388             mCompilation = other.mCompilation;
389             other.mCompilation = nullptr;
390             mExecution = other.mExecution;
391             other.mExecution = nullptr;
392         }
393         return *this;
394     }
395 
396     Result setInput(uint32_t index, const void* buffer, size_t length,
397                     const ANeuralNetworksOperandType* type = nullptr) {
398         return static_cast<Result>(
399                 ANeuralNetworksExecution_setInput(mExecution, index, type, buffer, length));
400     }
401 
402     template <typename T>
403     Result setInput(uint32_t index, const T* value,
404                     const ANeuralNetworksOperandType* type = nullptr) {
405         static_assert(!std::is_pointer<T>(), "No operand may have a pointer as its value");
406         return setInput(index, value, sizeof(T), type);
407     }
408 
409     Result setInputFromMemory(uint32_t index, const Memory* memory, uint32_t offset,
410                               uint32_t length, const ANeuralNetworksOperandType* type = nullptr) {
411         return static_cast<Result>(ANeuralNetworksExecution_setInputFromMemory(
412                 mExecution, index, type, memory->get(), offset, length));
413     }
414 
415     Result setOutput(uint32_t index, void* buffer, size_t length,
416                      const ANeuralNetworksOperandType* type = nullptr) {
417         return static_cast<Result>(
418                 ANeuralNetworksExecution_setOutput(mExecution, index, type, buffer, length));
419     }
420 
421     template <typename T>
422     Result setOutput(uint32_t index, T* value, const ANeuralNetworksOperandType* type = nullptr) {
423         static_assert(!std::is_pointer<T>(), "No operand may have a pointer as its value");
424         return setOutput(index, value, sizeof(T), type);
425     }
426 
427     Result setOutputFromMemory(uint32_t index, const Memory* memory, uint32_t offset,
428                                uint32_t length, const ANeuralNetworksOperandType* type = nullptr) {
429         return static_cast<Result>(ANeuralNetworksExecution_setOutputFromMemory(
430                 mExecution, index, type, memory->get(), offset, length));
431     }
432 
setLoopTimeout(uint64_t duration)433     Result setLoopTimeout(uint64_t duration) {
434         return static_cast<Result>(ANeuralNetworksExecution_setLoopTimeout(mExecution, duration));
435     }
436 
enableInputAndOutputPadding(bool enable)437     Result enableInputAndOutputPadding(bool enable) {
438         if (__builtin_available(android __NNAPI_FL5_MIN_ANDROID_API__, *)) {
439             return static_cast<Result>(
440                     ANeuralNetworksExecution_enableInputAndOutputPadding(mExecution, enable));
441         } else {
442             return Result::FEATURE_LEVEL_TOO_LOW;
443         }
444     }
445 
setReusable(bool reusable)446     Result setReusable(bool reusable) {
447         if (__builtin_available(android __NNAPI_FL5_MIN_ANDROID_API__, *)) {
448             return static_cast<Result>(
449                     NNAPI_CALL(ANeuralNetworksExecution_setReusable(mExecution, reusable)));
450         } else {
451             return Result::FEATURE_LEVEL_TOO_LOW;
452         }
453     }
454 
startCompute(Event * event)455     Result startCompute(Event* event) {
456         ANeuralNetworksEvent* ev = nullptr;
457         Result result = static_cast<Result>(ANeuralNetworksExecution_startCompute(mExecution, &ev));
458         event->set(ev);
459         return result;
460     }
461 
startComputeWithDependencies(const std::vector<const Event * > & dependencies,uint64_t duration,Event * event)462     Result startComputeWithDependencies(const std::vector<const Event*>& dependencies,
463                                         uint64_t duration, Event* event) {
464         std::vector<const ANeuralNetworksEvent*> deps(dependencies.size());
465         std::transform(dependencies.begin(), dependencies.end(), deps.begin(),
466                        [](const Event* e) { return e->getHandle(); });
467         ANeuralNetworksEvent* ev = nullptr;
468         Result result = static_cast<Result>(ANeuralNetworksExecution_startComputeWithDependencies(
469                 mExecution, deps.data(), deps.size(), duration, &ev));
470         event->set(ev);
471         return result;
472     }
473 
474     // By default, compute() uses the synchronous API. Either an argument or
475     // setComputeMode() can be used to change the behavior of compute() to
476     // either:
477     // - use the asynchronous or fenced API and then wait for computation to complete
478     // or
479     // - use the burst API
480     // Returns the previous ComputeMode.
481     enum class ComputeMode { SYNC, ASYNC, BURST, FENCED };
setComputeMode(ComputeMode mode)482     static ComputeMode setComputeMode(ComputeMode mode) {
483         ComputeMode oldComputeMode = mComputeMode;
484         mComputeMode = mode;
485         return oldComputeMode;
486     }
getComputeMode()487     static ComputeMode getComputeMode() { return mComputeMode; }
488 
489     Result compute(ComputeMode computeMode = mComputeMode) {
490         switch (computeMode) {
491             case ComputeMode::SYNC: {
492                 return static_cast<Result>(ANeuralNetworksExecution_compute(mExecution));
493             }
494             case ComputeMode::ASYNC: {
495                 ANeuralNetworksEvent* event = nullptr;
496                 Result result = static_cast<Result>(
497                         ANeuralNetworksExecution_startCompute(mExecution, &event));
498                 if (result != Result::NO_ERROR) {
499                     return result;
500                 }
501                 // TODO how to manage the lifetime of events when multiple waiters is not
502                 // clear.
503                 result = static_cast<Result>(ANeuralNetworksEvent_wait(event));
504                 ANeuralNetworksEvent_free(event);
505                 return result;
506             }
507             case ComputeMode::BURST: {
508                 ANeuralNetworksBurst* burst = nullptr;
509                 Result result =
510                         static_cast<Result>(ANeuralNetworksBurst_create(mCompilation, &burst));
511                 if (result != Result::NO_ERROR) {
512                     return result;
513                 }
514                 result = static_cast<Result>(
515                         ANeuralNetworksExecution_burstCompute(mExecution, burst));
516                 ANeuralNetworksBurst_free(burst);
517                 return result;
518             }
519             case ComputeMode::FENCED: {
520                 ANeuralNetworksEvent* event = nullptr;
521                 Result result =
522                         static_cast<Result>(ANeuralNetworksExecution_startComputeWithDependencies(
523                                 mExecution, nullptr, 0, 0, &event));
524                 if (result != Result::NO_ERROR) {
525                     return result;
526                 }
527                 result = static_cast<Result>(ANeuralNetworksEvent_wait(event));
528                 ANeuralNetworksEvent_free(event);
529                 return result;
530             }
531         }
532         return Result::BAD_DATA;
533     }
534 
getOutputOperandDimensions(uint32_t index,std::vector<uint32_t> * dimensions)535     Result getOutputOperandDimensions(uint32_t index, std::vector<uint32_t>* dimensions) {
536         uint32_t rank = 0;
537         Result result = static_cast<Result>(
538                 ANeuralNetworksExecution_getOutputOperandRank(mExecution, index, &rank));
539         dimensions->resize(rank);
540         if ((result != Result::NO_ERROR && result != Result::OUTPUT_INSUFFICIENT_SIZE) ||
541             rank == 0) {
542             return result;
543         }
544         result = static_cast<Result>(ANeuralNetworksExecution_getOutputOperandDimensions(
545                 mExecution, index, dimensions->data()));
546         return result;
547     }
548 
getHandle()549     ANeuralNetworksExecution* getHandle() { return mExecution; };
550 
551    private:
552     ANeuralNetworksCompilation* mCompilation = nullptr;
553     ANeuralNetworksExecution* mExecution = nullptr;
554 
555     // Initialized to ComputeMode::SYNC in TestNeuralNetworksWrapper.cpp.
556     static ComputeMode mComputeMode;
557 };
558 
559 }  // namespace test_wrapper
560 }  // namespace nn
561 }  // namespace android
562 
563 #endif  // ANDROID_FRAMEWORKS_ML_NN_RUNTIME_TEST_TEST_NEURAL_NETWORKS_WRAPPER_H
564