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