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