1 /**
2  * Copyright 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 #include "run_tflite.h"
18 
19 #include <jni.h>
20 #include <string>
21 #include <iomanip>
22 #include <sstream>
23 #include <fcntl.h>
24 
25 #include <android/asset_manager_jni.h>
26 #include <android/log.h>
27 #include <android/sharedmem.h>
28 #include <sys/mman.h>
29 
30 extern "C" JNIEXPORT jboolean JNICALL
Java_com_android_nn_benchmark_core_NNTestBase_hasNnApiDevice(JNIEnv * env,jobject,jstring _nnApiDeviceName)31 Java_com_android_nn_benchmark_core_NNTestBase_hasNnApiDevice(
32     JNIEnv *env, jobject /* this */, jstring _nnApiDeviceName) {
33   bool foundDevice = false;
34   const char *nnApiDeviceName =
35       _nnApiDeviceName == NULL ? NULL
36                                : env->GetStringUTFChars(_nnApiDeviceName, NULL);
37   if (nnApiDeviceName != NULL) {
38     std::string device_name(nnApiDeviceName);
39     uint32_t numDevices = 0;
40     NnApiImplementation()->ANeuralNetworks_getDeviceCount(&numDevices);
41 
42     for (uint32_t i = 0; i < numDevices; i++) {
43       ANeuralNetworksDevice *device = nullptr;
44       const char *buffer = nullptr;
45       NnApiImplementation()->ANeuralNetworks_getDevice(i, &device);
46       NnApiImplementation()->ANeuralNetworksDevice_getName(device, &buffer);
47       if (device_name == buffer) {
48         foundDevice = true;
49         break;
50       }
51     }
52     env->ReleaseStringUTFChars(_nnApiDeviceName, nnApiDeviceName);
53   }
54 
55   return foundDevice;
56 }
57 
58 extern "C"
59 JNIEXPORT jlong
60 JNICALL
Java_com_android_nn_benchmark_core_NNTestBase_initModel(JNIEnv * env,jobject,jstring _modelFileName,jint _tfliteBackend,jboolean _enableIntermediateTensorsDump,jstring _nnApiDeviceName,jboolean _mmapModel,jstring _nnApiCacheDir,jlong _nnApiSlHandle)61 Java_com_android_nn_benchmark_core_NNTestBase_initModel(
62         JNIEnv *env,
63         jobject /* this */,
64         jstring _modelFileName,
65         jint _tfliteBackend,
66         jboolean _enableIntermediateTensorsDump,
67         jstring _nnApiDeviceName,
68         jboolean _mmapModel,
69         jstring _nnApiCacheDir,
70         jlong _nnApiSlHandle) {
71     const char *modelFileName = env->GetStringUTFChars(_modelFileName, NULL);
72     const char *nnApiDeviceName =
73         _nnApiDeviceName == NULL
74             ? NULL
75             : env->GetStringUTFChars(_nnApiDeviceName, NULL);
76     const char *nnApiCacheDir =
77         _nnApiCacheDir == NULL
78             ? NULL
79             : env->GetStringUTFChars(_nnApiCacheDir, NULL);
80      const tflite::nnapi::NnApiSupportLibrary *nnApiSlHandle =
81           (const tflite::nnapi::NnApiSupportLibrary *)_nnApiSlHandle;
82     int nnapiErrno = 0;
83     void *handle = BenchmarkModel::create(
84         modelFileName, _tfliteBackend, _enableIntermediateTensorsDump, &nnapiErrno,
85         nnApiDeviceName, _mmapModel, nnApiCacheDir, nnApiSlHandle);
86     env->ReleaseStringUTFChars(_modelFileName, modelFileName);
87     if (_nnApiDeviceName != NULL) {
88         env->ReleaseStringUTFChars(_nnApiDeviceName, nnApiDeviceName);
89     }
90 
91     if (_tfliteBackend == TFLITE_NNAPI && nnapiErrno != 0) {
92       jclass nnapiFailureClass = env->FindClass(
93           "com/android/nn/benchmark/core/NnApiDelegationFailure");
94       jmethodID constructor =
95           env->GetMethodID(nnapiFailureClass, "<init>", "(I)V");
96       jobject exception =
97           env->NewObject(nnapiFailureClass, constructor, nnapiErrno);
98       env->Throw(static_cast<jthrowable>(exception));
99     }
100 
101     return (jlong)(uintptr_t)handle;
102 }
103 
104 
105 
106 extern "C"
107 JNIEXPORT void
108 JNICALL
Java_com_android_nn_benchmark_core_NNTestBase_destroyModel(JNIEnv * env,jobject,jlong _modelHandle)109 Java_com_android_nn_benchmark_core_NNTestBase_destroyModel(
110         JNIEnv *env,
111         jobject /* this */,
112         jlong _modelHandle) {
113     BenchmarkModel* model = (BenchmarkModel *) _modelHandle;
114     delete(model);
115 }
116 
117 extern "C"
118 JNIEXPORT jboolean
119 JNICALL
Java_com_android_nn_benchmark_core_NNTestBase_resizeInputTensors(JNIEnv * env,jobject,jlong _modelHandle,jintArray _inputShape)120 Java_com_android_nn_benchmark_core_NNTestBase_resizeInputTensors(
121         JNIEnv *env,
122         jobject /* this */,
123         jlong _modelHandle,
124         jintArray _inputShape) {
125     BenchmarkModel* model = (BenchmarkModel *) _modelHandle;
126     jint* shapePtr = env->GetIntArrayElements(_inputShape, nullptr);
127     jsize shapeLen = env->GetArrayLength(_inputShape);
128 
129     std::vector<int> shape(shapePtr, shapePtr + shapeLen);
130     return model->resizeInputTensors(std::move(shape));
131 }
132 
133 /** RAII container for a list of InferenceInOutSequence to handle JNI data release in destructor. */
134 class InferenceInOutSequenceList {
135 public:
136     InferenceInOutSequenceList(JNIEnv *env,
137                                const jobject& inOutDataList,
138                                bool expectGoldenOutputs);
139     ~InferenceInOutSequenceList();
140 
isValid() const141     bool isValid() const { return mValid; }
142 
data() const143     const std::vector<InferenceInOutSequence>& data() const { return mData; }
144 
145 private:
146     JNIEnv *mEnv;  // not owned.
147 
148     std::vector<InferenceInOutSequence> mData;
149     std::vector<jbyteArray> mInputArrays;
150     std::vector<jobjectArray> mOutputArrays;
151     bool mValid;
152 };
153 
InferenceInOutSequenceList(JNIEnv * env,const jobject & inOutDataList,bool expectGoldenOutputs)154 InferenceInOutSequenceList::InferenceInOutSequenceList(JNIEnv *env,
155                                                        const jobject& inOutDataList,
156                                                        bool expectGoldenOutputs)
157     : mEnv(env), mValid(false) {
158 
159     jclass list_class = env->FindClass("java/util/List");
160     if (list_class == nullptr) { return; }
161     jmethodID list_size = env->GetMethodID(list_class, "size", "()I");
162     if (list_size == nullptr) { return; }
163     jmethodID list_get = env->GetMethodID(list_class, "get", "(I)Ljava/lang/Object;");
164     if (list_get == nullptr) { return; }
165     jmethodID list_add = env->GetMethodID(list_class, "add", "(Ljava/lang/Object;)Z");
166     if (list_add == nullptr) { return; }
167 
168     jclass inOutSeq_class = env->FindClass("com/android/nn/benchmark/core/InferenceInOutSequence");
169     if (inOutSeq_class == nullptr) { return; }
170     jmethodID inOutSeq_size = env->GetMethodID(inOutSeq_class, "size", "()I");
171     if (inOutSeq_size == nullptr) { return; }
172     jmethodID inOutSeq_get = env->GetMethodID(inOutSeq_class, "get",
173                                               "(I)Lcom/android/nn/benchmark/core/InferenceInOut;");
174     if (inOutSeq_get == nullptr) { return; }
175 
176     jclass inout_class = env->FindClass("com/android/nn/benchmark/core/InferenceInOut");
177     if (inout_class == nullptr) { return; }
178     jfieldID inout_input = env->GetFieldID(inout_class, "mInput", "[B");
179     if (inout_input == nullptr) { return; }
180     jfieldID inout_expectedOutputs = env->GetFieldID(inout_class, "mExpectedOutputs", "[[B");
181     if (inout_expectedOutputs == nullptr) { return; }
182     jfieldID inout_inputCreator = env->GetFieldID(inout_class, "mInputCreator",
183             "Lcom/android/nn/benchmark/core/InferenceInOut$InputCreatorInterface;");
184     if (inout_inputCreator == nullptr) { return; }
185 
186 
187 
188     // Fetch input/output arrays
189     size_t data_count = mEnv->CallIntMethod(inOutDataList, list_size);
190     if (env->ExceptionCheck()) { return; }
191     mData.reserve(data_count);
192 
193     jclass inputCreator_class = env->FindClass("com/android/nn/benchmark/core/InferenceInOut$InputCreatorInterface");
194     if (inputCreator_class == nullptr) { return; }
195     jmethodID createInput_method = env->GetMethodID(inputCreator_class, "createInput", "(Ljava/nio/ByteBuffer;)V");
196     if (createInput_method == nullptr) { return; }
197 
198     for (int seq_index = 0; seq_index < data_count; ++seq_index) {
199         jobject inOutSeq = mEnv->CallObjectMethod(inOutDataList, list_get, seq_index);
200         if (mEnv->ExceptionCheck()) { return; }
201 
202         size_t seqLen = mEnv->CallIntMethod(inOutSeq, inOutSeq_size);
203         if (mEnv->ExceptionCheck()) { return; }
204 
205         mData.push_back(InferenceInOutSequence{});
206         auto& seq = mData.back();
207         seq.reserve(seqLen);
208         for (int i = 0; i < seqLen; ++i) {
209             jobject inout = mEnv->CallObjectMethod(inOutSeq, inOutSeq_get, i);
210             if (mEnv->ExceptionCheck()) { return; }
211 
212             uint8_t* input_data = nullptr;
213             size_t input_len = 0;
214             std::function<bool(uint8_t*, size_t)> inputCreator;
215             jbyteArray input = static_cast<jbyteArray>(
216                     mEnv->GetObjectField(inout, inout_input));
217             mInputArrays.push_back(input);
218             if (input != nullptr) {
219                 input_data = reinterpret_cast<uint8_t*>(
220                         mEnv->GetByteArrayElements(input, NULL));
221                 input_len = mEnv->GetArrayLength(input);
222             } else {
223                 inputCreator = [env, inout, inout_inputCreator, createInput_method](
224                         uint8_t* buffer, size_t length) {
225                     jobject byteBuffer = env->NewDirectByteBuffer(buffer, length);
226                     if (byteBuffer == nullptr) { return false; }
227                     jobject creator = env->GetObjectField(inout, inout_inputCreator);
228                     if (creator == nullptr) { return false; }
229                     env->CallVoidMethod(creator, createInput_method, byteBuffer);
230                     if (env->ExceptionCheck()) { return false; }
231                     return true;
232                 };
233             }
234 
235             jobjectArray expectedOutputs = static_cast<jobjectArray>(
236                     mEnv->GetObjectField(inout, inout_expectedOutputs));
237             mOutputArrays.push_back(expectedOutputs);
238             seq.push_back({input_data, input_len, {}, inputCreator});
239 
240             // Add expected output to sequence added above
241             if (expectedOutputs != nullptr) {
242                 jsize expectedOutputsLength = mEnv->GetArrayLength(expectedOutputs);
243                 auto& outputs = seq.back().outputs;
244                 outputs.reserve(expectedOutputsLength);
245 
246                 for (jsize j = 0;j < expectedOutputsLength; ++j) {
247                     jbyteArray expectedOutput =
248                             static_cast<jbyteArray>(mEnv->GetObjectArrayElement(expectedOutputs, j));
249                     if (env->ExceptionCheck()) {
250                         return;
251                     }
252                     if (expectedOutput == nullptr) {
253                         jclass iaeClass = mEnv->FindClass("java/lang/IllegalArgumentException");
254                         mEnv->ThrowNew(iaeClass, "Null expected output array");
255                         return;
256                     }
257 
258                     uint8_t *expectedOutput_data = reinterpret_cast<uint8_t*>(
259                                         mEnv->GetByteArrayElements(expectedOutput, NULL));
260                     size_t expectedOutput_len = mEnv->GetArrayLength(expectedOutput);
261                     outputs.push_back({ expectedOutput_data, expectedOutput_len});
262                 }
263             } else {
264                 if (expectGoldenOutputs) {
265                     jclass iaeClass = mEnv->FindClass("java/lang/IllegalArgumentException");
266                     mEnv->ThrowNew(iaeClass, "Expected golden output for every input");
267                     return;
268                 }
269             }
270         }
271     }
272     mValid = true;
273 }
274 
~InferenceInOutSequenceList()275 InferenceInOutSequenceList::~InferenceInOutSequenceList() {
276     // Note that we may land here with a pending JNI exception so cannot call
277     // java objects.
278     int arrayIndex = 0;
279     for (int seq_index = 0; seq_index < mData.size(); ++seq_index) {
280         for (int i = 0; i < mData[seq_index].size(); ++i) {
281             jbyteArray input = mInputArrays[arrayIndex];
282             if (input != nullptr) {
283                 mEnv->ReleaseByteArrayElements(
284                         input, reinterpret_cast<jbyte*>(mData[seq_index][i].input), JNI_ABORT);
285             }
286             jobjectArray expectedOutputs = mOutputArrays[arrayIndex];
287             if (expectedOutputs != nullptr) {
288                 jsize expectedOutputsLength = mEnv->GetArrayLength(expectedOutputs);
289                 if (expectedOutputsLength != mData[seq_index][i].outputs.size()) {
290                     // Should not happen? :)
291                     jclass iaeClass = mEnv->FindClass("java/lang/IllegalStateException");
292                     mEnv->ThrowNew(iaeClass, "Mismatch of the size of expected outputs jni array "
293                                    "and internal array of its bufers");
294                     return;
295                 }
296 
297                 for (jsize j = 0;j < expectedOutputsLength; ++j) {
298                     jbyteArray expectedOutput = static_cast<jbyteArray>(mEnv->GetObjectArrayElement(expectedOutputs, j));
299                     mEnv->ReleaseByteArrayElements(
300                         expectedOutput, reinterpret_cast<jbyte*>(mData[seq_index][i].outputs[j].ptr),
301                         JNI_ABORT);
302                 }
303             }
304             arrayIndex++;
305         }
306     }
307 }
308 
309 extern "C"
310 JNIEXPORT jboolean
311 JNICALL
Java_com_android_nn_benchmark_core_NNTestBase_runBenchmark(JNIEnv * env,jobject,jlong _modelHandle,jobject inOutDataList,jobject resultList,jint inferencesSeqMaxCount,jfloat timeoutSec,jint flags)312 Java_com_android_nn_benchmark_core_NNTestBase_runBenchmark(
313         JNIEnv *env,
314         jobject /* this */,
315         jlong _modelHandle,
316         jobject inOutDataList,
317         jobject resultList,
318         jint inferencesSeqMaxCount,
319         jfloat timeoutSec,
320         jint flags) {
321 
322     BenchmarkModel* model = reinterpret_cast<BenchmarkModel*>(_modelHandle);
323 
324     jclass list_class = env->FindClass("java/util/List");
325     if (list_class == nullptr) { return false; }
326     jmethodID list_add = env->GetMethodID(list_class, "add", "(Ljava/lang/Object;)Z");
327     if (list_add == nullptr) { return false; }
328 
329     jclass result_class = env->FindClass("com/android/nn/benchmark/core/InferenceResult");
330     if (result_class == nullptr) { return false; }
331     jmethodID result_ctor = env->GetMethodID(result_class, "<init>", "(F[F[F[[BII)V");
332     if (result_ctor == nullptr) { return false; }
333 
334     std::vector<InferenceResult> result;
335 
336     const bool expectGoldenOutputs = (flags & FLAG_IGNORE_GOLDEN_OUTPUT) == 0;
337     InferenceInOutSequenceList data(env, inOutDataList, expectGoldenOutputs);
338     if (!data.isValid()) {
339         return false;
340     }
341 
342     // TODO: Remove success boolean from this method and throw an exception in case of problems
343     bool success = model->benchmark(data.data(), inferencesSeqMaxCount, timeoutSec, flags, &result);
344 
345     // Generate results
346     if (success) {
347         for (const InferenceResult &rentry : result) {
348             jobjectArray inferenceOutputs = nullptr;
349             jfloatArray meanSquareErrorArray = nullptr;
350             jfloatArray maxSingleErrorArray = nullptr;
351 
352             if ((flags & FLAG_IGNORE_GOLDEN_OUTPUT) == 0) {
353                 meanSquareErrorArray = env->NewFloatArray(rentry.meanSquareErrors.size());
354                 if (env->ExceptionCheck()) { return false; }
355                 maxSingleErrorArray = env->NewFloatArray(rentry.maxSingleErrors.size());
356                 if (env->ExceptionCheck()) { return false; }
357                 {
358                     jfloat *bytes = env->GetFloatArrayElements(meanSquareErrorArray, nullptr);
359                     memcpy(bytes,
360                            &rentry.meanSquareErrors[0],
361                            rentry.meanSquareErrors.size() * sizeof(float));
362                     env->ReleaseFloatArrayElements(meanSquareErrorArray, bytes, 0);
363                 }
364                 {
365                     jfloat *bytes = env->GetFloatArrayElements(maxSingleErrorArray, nullptr);
366                     memcpy(bytes,
367                            &rentry.maxSingleErrors[0],
368                            rentry.maxSingleErrors.size() * sizeof(float));
369                     env->ReleaseFloatArrayElements(maxSingleErrorArray, bytes, 0);
370                 }
371             }
372 
373             if ((flags & FLAG_DISCARD_INFERENCE_OUTPUT) == 0) {
374                 jclass byteArrayClass = env->FindClass("[B");
375 
376                 inferenceOutputs = env->NewObjectArray(
377                     rentry.inferenceOutputs.size(),
378                     byteArrayClass, nullptr);
379 
380                 for (int i = 0;i < rentry.inferenceOutputs.size();++i) {
381                     jbyteArray inferenceOutput = nullptr;
382                     inferenceOutput = env->NewByteArray(rentry.inferenceOutputs[i].size());
383                     if (env->ExceptionCheck()) { return false; }
384                     jbyte *bytes = env->GetByteArrayElements(inferenceOutput, nullptr);
385                     memcpy(bytes, &rentry.inferenceOutputs[i][0], rentry.inferenceOutputs[i].size());
386                     env->ReleaseByteArrayElements(inferenceOutput, bytes, 0);
387                     env->SetObjectArrayElement(inferenceOutputs, i, inferenceOutput);
388                 }
389             }
390 
391             jobject object = env->NewObject(
392                 result_class, result_ctor, rentry.computeTimeSec,
393                 meanSquareErrorArray, maxSingleErrorArray, inferenceOutputs,
394                 rentry.inputOutputSequenceIndex, rentry.inputOutputIndex);
395             if (env->ExceptionCheck() || object == NULL) { return false; }
396 
397             env->CallBooleanMethod(resultList, list_add, object);
398             if (env->ExceptionCheck()) { return false; }
399 
400             // Releasing local references to objects to avoid local reference table overflow
401             // if tests is set to run for long time.
402             if (meanSquareErrorArray) {
403                 env->DeleteLocalRef(meanSquareErrorArray);
404             }
405             if (maxSingleErrorArray) {
406                 env->DeleteLocalRef(maxSingleErrorArray);
407             }
408             env->DeleteLocalRef(object);
409         }
410     }
411 
412     return success;
413 }
414 
415 extern "C"
416 JNIEXPORT void
417 JNICALL
Java_com_android_nn_benchmark_core_NNTestBase_dumpAllLayers(JNIEnv * env,jobject,jlong _modelHandle,jstring dumpPath,jobject inOutDataList)418 Java_com_android_nn_benchmark_core_NNTestBase_dumpAllLayers(
419         JNIEnv *env,
420         jobject /* this */,
421         jlong _modelHandle,
422         jstring dumpPath,
423         jobject inOutDataList) {
424 
425     BenchmarkModel* model = reinterpret_cast<BenchmarkModel*>(_modelHandle);
426 
427     InferenceInOutSequenceList data(env, inOutDataList, /*expectGoldenOutputs=*/false);
428     if (!data.isValid()) {
429         return;
430     }
431 
432     const char *dumpPathStr = env->GetStringUTFChars(dumpPath, JNI_FALSE);
433     model->dumpAllLayers(dumpPathStr, data.data());
434     env->ReleaseStringUTFChars(dumpPath, dumpPathStr);
435 }
436 
437 extern "C"
438 JNIEXPORT jboolean
439 JNICALL
Java_com_android_nn_benchmark_core_NNTestBase_hasAccelerator()440 Java_com_android_nn_benchmark_core_NNTestBase_hasAccelerator() {
441   uint32_t device_count = 0;
442   NnApiImplementation()->ANeuralNetworks_getDeviceCount(&device_count);
443   // We only consider a real device, not 'nnapi-reference'.
444   return device_count > 1;
445 }
446 
447 extern "C"
448 JNIEXPORT jboolean
449 JNICALL
Java_com_android_nn_benchmark_core_NNTestBase_getAcceleratorNames(JNIEnv * env,jclass,jobject resultList)450 Java_com_android_nn_benchmark_core_NNTestBase_getAcceleratorNames(
451     JNIEnv *env,
452     jclass, /* clazz */
453     jobject resultList
454     ) {
455   uint32_t device_count = 0;
456   auto nnapi_result = NnApiImplementation()->ANeuralNetworks_getDeviceCount(&device_count);
457   if (nnapi_result != 0) {
458     return false;
459   }
460 
461   jclass list_class = env->FindClass("java/util/List");
462   if (list_class == nullptr) { return false; }
463   jmethodID list_add = env->GetMethodID(list_class, "add", "(Ljava/lang/Object;)Z");
464   if (list_add == nullptr) { return false; }
465 
466   for (int i = 0; i < device_count; i++) {
467       ANeuralNetworksDevice* device = nullptr;
468       nnapi_result = NnApiImplementation()->ANeuralNetworks_getDevice(i, &device);
469       if (nnapi_result != 0) {
470           return false;
471        }
472       const char* buffer = nullptr;
473       nnapi_result = NnApiImplementation()->ANeuralNetworksDevice_getName(device, &buffer);
474       if (nnapi_result != 0) {
475         return false;
476       }
477 
478       auto device_name = env->NewStringUTF(buffer);
479 
480       env->CallBooleanMethod(resultList, list_add, device_name);
481       if (env->ExceptionCheck()) { return false; }
482   }
483   return true;
484 }
485 
convertToJfloatArray(JNIEnv * env,const std::vector<float> & from)486 static jfloatArray convertToJfloatArray(JNIEnv* env, const std::vector<float>& from) {
487   jfloatArray to = env->NewFloatArray(from.size());
488   if (env->ExceptionCheck()) {
489     return nullptr;
490   }
491   jfloat* bytes = env->GetFloatArrayElements(to, nullptr);
492   memcpy(bytes, from.data(), from.size() * sizeof(float));
493   env->ReleaseFloatArrayElements(to, bytes, 0);
494   return to;
495 }
496 
497 extern "C" JNIEXPORT jobject JNICALL
Java_com_android_nn_benchmark_core_NNTestBase_runCompilationBenchmark(JNIEnv * env,jobject,jlong _modelHandle,jint maxNumIterations,jfloat warmupTimeoutSec,jfloat runTimeoutSec,jboolean useNnapiSl)498 Java_com_android_nn_benchmark_core_NNTestBase_runCompilationBenchmark(
499     JNIEnv* env,
500     jobject /* this */,
501     jlong _modelHandle,
502     jint maxNumIterations,
503     jfloat warmupTimeoutSec,
504     jfloat runTimeoutSec,
505     jboolean useNnapiSl) {
506   BenchmarkModel* model = reinterpret_cast<BenchmarkModel*>(_modelHandle);
507 
508   jclass result_class = env->FindClass("com/android/nn/benchmark/core/CompilationBenchmarkResult");
509   if (result_class == nullptr) return nullptr;
510   jmethodID result_ctor = env->GetMethodID(result_class, "<init>", "([F[F[FI)V");
511   if (result_ctor == nullptr) return nullptr;
512 
513   CompilationBenchmarkResult result;
514   bool success =
515           model->benchmarkCompilation(maxNumIterations, warmupTimeoutSec,
516                                       runTimeoutSec, useNnapiSl, &result);
517   if (!success) return nullptr;
518 
519   // Convert cpp CompilationBenchmarkResult struct to java.
520   jfloatArray compileWithoutCacheArray =
521           convertToJfloatArray(env, result.compileWithoutCacheTimeSec);
522   if (compileWithoutCacheArray == nullptr) return nullptr;
523 
524   // saveToCache and prepareFromCache results may not exist.
525   jfloatArray saveToCacheArray = nullptr;
526   if (result.saveToCacheTimeSec) {
527     saveToCacheArray = convertToJfloatArray(env, result.saveToCacheTimeSec.value());
528     if (saveToCacheArray == nullptr) return nullptr;
529   }
530   jfloatArray prepareFromCacheArray = nullptr;
531   if (result.prepareFromCacheTimeSec) {
532     prepareFromCacheArray = convertToJfloatArray(env, result.prepareFromCacheTimeSec.value());
533     if (prepareFromCacheArray == nullptr) return nullptr;
534   }
535 
536   jobject object = env->NewObject(result_class, result_ctor, compileWithoutCacheArray,
537                                   saveToCacheArray, prepareFromCacheArray, result.cacheSizeBytes);
538   if (env->ExceptionCheck()) return nullptr;
539   return object;
540 }
541