diff --git a/CHANGELOG.md b/CHANGELOG.md index 9440c712378a4a0d678d63884198a18463cc9a29..741c90db885865a4b0e9198faa52653b62123690 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## 1.4.3 +* Improve Aparapi native to enforce Kernel and Device max work group size limitations and provide query functions for clGetKernelWorkGroupInfo(...) + ## 1.4.2 * Fixed Potential JVM crash when using multi-dimensional arrays (> 1D) diff --git a/Makefile.am b/Makefile.am index 6f94b38d4a6f01e9fb8faba3d9c9073d18645bdc..b8e873c2a6d7d96b0cb0d09e2187c2510075e75d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,8 +1,8 @@ #ACLOCAL_AMFLAGS="-I m4" AUTOMAKE_OPTIONS = foreign -EXTRA_DIST = include src/cpp/CLHelper.h src/cpp/classtools.h src/cpp/invoke/JavaArgs.h src/cpp/invoke/OpenCLMem.h src/cpp/invoke/OpenCLKernel.h src/cpp/invoke/OpenCLJNI.h src/cpp/invoke/OpenCLArgDescriptor.h src/cpp/invoke/OpenCLProgram.h src/cpp/CLException.h src/cpp/JNIHelper.h src/cpp/Common.h src/cpp/runKernel/KernelArg.h src/cpp/runKernel/Range.h src/cpp/runKernel/ProfileInfo.h src/cpp/runKernel/AparapiBuffer.h src/cpp/runKernel/Config.h src/cpp/runKernel/Aparapi.h src/cpp/runKernel/ArrayBuffer.h src/cpp/runKernel/JNIContext.h src/cpp/runKernel/List.h +EXTRA_DIST = include src/cpp/CLHelper.h src/cpp/classtools.h src/cpp/invoke/JavaArgs.h src/cpp/invoke/OpenCLMem.h src/cpp/invoke/OpenCLKernel.h src/cpp/invoke/OpenCLJNI.h src/cpp/invoke/OpenCLArgDescriptor.h src/cpp/invoke/OpenCLProgram.h src/cpp/CLException.h src/cpp/JNIExceptions.h src/cpp/JNIHelper.h src/cpp/Common.h src/cpp/runKernel/KernelArg.h src/cpp/runKernel/Range.h src/cpp/runKernel/ProfileInfo.h src/cpp/runKernel/AparapiBuffer.h src/cpp/runKernel/Config.h src/cpp/runKernel/Aparapi.h src/cpp/runKernel/ArrayBuffer.h src/cpp/runKernel/JNIContext.h src/cpp/runKernel/List.h lib_LTLIBRARIES = libaparapi.la libaparapi_la_CPPFLAGS = $(AC_CPPFLAGS) libaparapi_la_LDFLAGS = $(AC_LDFLAGS) -libaparapi_la_SOURCES = src/cpp/runKernel/Aparapi.cpp src/cpp/runKernel/ArrayBuffer.cpp src/cpp/runKernel/AparapiBuffer.cpp src/cpp/runKernel/Config.cpp src/cpp/runKernel/JNIContext.cpp src/cpp/runKernel/KernelArg.cpp src/cpp/runKernel/ProfileInfo.cpp src/cpp/runKernel/Range.cpp src/cpp/invoke/OpenCLJNI.cpp src/cpp/invoke/OpenCLArgDescriptor.cpp src/cpp/invoke/OpenCLMem.cpp src/cpp/CLHelper.cpp src/cpp/classtools.cpp src/cpp/JNIHelper.cpp src/cpp/agent.cpp +libaparapi_la_SOURCES = src/cpp/runKernel/Aparapi.cpp src/cpp/runKernel/ArrayBuffer.cpp src/cpp/runKernel/AparapiBuffer.cpp src/cpp/runKernel/Config.cpp src/cpp/runKernel/JNIContext.cpp src/cpp/runKernel/KernelArg.cpp src/cpp/runKernel/ProfileInfo.cpp src/cpp/runKernel/Range.cpp src/cpp/invoke/OpenCLJNI.cpp src/cpp/invoke/OpenCLArgDescriptor.cpp src/cpp/invoke/OpenCLMem.cpp src/cpp/CLHelper.cpp src/cpp/classtools.cpp src/cpp/JNIHelper.cpp src/cpp/agent.cpp src/cpp/JNIExceptions.cpp all-local: diff --git a/build.sh b/build.sh index 11c26f96ff33cbf40584e9d01a596124d2ac3e48..198236ee7ebed674330a5abdc7b959e75c7d65c9 100755 --- a/build.sh +++ b/build.sh @@ -1,4 +1,5 @@ #!/bin/sh +rm -rf .libs64 .libs32 make clean ./prepare.sh libtoolize diff --git a/src/cpp/JNIExceptions.cpp b/src/cpp/JNIExceptions.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9df45bde6556c54cb0974af249a7bdaeeb8151a6 --- /dev/null +++ b/src/cpp/JNIExceptions.cpp @@ -0,0 +1,32 @@ +#include "JNIExceptions.h" + +jint throwNoClassDefError( JNIEnv *env, char *message ) +{ + jclass exClass; + char *className = "java/lang/NoClassDefFoundError"; + + exClass = env->FindClass( className); + if (exClass == NULL) { + //Make JVM crash... + return throwNoClassDefError( env, className ); + } + + return env->ThrowNew( exClass, message ); +} + +jint throwAparapiJNIRuntimeException( JNIEnv *env, std::string message ) { + return throwAparapiJNIRuntimeException(env, (const char *)message.c_str()); +} + +jint throwAparapiJNIRuntimeException( JNIEnv *env, const char *message ) +{ + jclass exClass; + char *className = "com/aparapi/exception/AparapiJNIException"; + + exClass = env->FindClass( className ); + if (exClass == NULL) { + return throwNoClassDefError( env, className ); + } + + return env->ThrowNew( exClass, message ); +} diff --git a/src/cpp/JNIExceptions.h b/src/cpp/JNIExceptions.h new file mode 100644 index 0000000000000000000000000000000000000000..bcb44a076a122f1491d109e81870faf22cfd330a --- /dev/null +++ b/src/cpp/JNIExceptions.h @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2016 - 2018 Syncleus, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include <jni.h> +#include <string> +#include <stdio.h> +#include <exception> +#include "CLHelper.h" + +#ifndef JNI_EXCEPTIONS_H +#define JNI_EXCEPTIONS_H + +class JNIException : public std::exception { + +private: + std::string _message; + +public: + + ~JNIException() throw () { + } + + JNIException(std::string message) { + _message = message; + } + + JNIException(const JNIException& cle) { + _message = cle._message; + } + + JNIException& operator=(const JNIException& cle) { + _message = cle._message; + return *this; + } + + const char* message() { + return _message.c_str(); + } + + void printError() { + if(_message != "") { + fprintf(stderr, "!!!!!!! %s failed %s\n", message()); + } + } + + const char* what() { + return std::string("!!!!!!! " + _message + " failed\n").c_str(); + } +}; + + +jint throwAparapiJNIRuntimeException( JNIEnv *env, std::string message ); + +jint throwAparapiJNIRuntimeException( JNIEnv *env, const char *message ); + +#endif // JNI_EXCEPTIONS_H diff --git a/src/cpp/runKernel/Aparapi.cpp b/src/cpp/runKernel/Aparapi.cpp index 807bf81e437612ae98e6158282c4b1e34c488c34..73d5bc1c250cfd5f2c425755562c840dd1284326 100644 --- a/src/cpp/runKernel/Aparapi.cpp +++ b/src/cpp/runKernel/Aparapi.cpp @@ -61,6 +61,7 @@ #include "ProfileInfo.h" #include "ArrayBuffer.h" #include "AparapiBuffer.h" +#include "JNIExceptions.h" #include "CLHelper.h" #include "List.h" #include "Util.h" @@ -843,6 +844,238 @@ int processArgs(JNIEnv* jenv, JNIContext* jniContext, int& argPos, int& writeEve return status; } +JNI_JAVA(jlong, KernelRunnerJNI, getKernelMinimumPrivateMemSizeInUsePerWorkItemJNI) + (JNIEnv *jEnv, jobject jobj, jlong jniContextHandle) { + long maxPrivateMemSize = -1; + + try { + JNIContext* jniContext = JNIContext::getJNIContext(jniContextHandle); + if (jniContext == NULL){ + throwAparapiJNIRuntimeException(jEnv, "Failed to obtain JNI context from handle"); + throw JNIException("getJNIContext()"); + } + + int length = queryKernelWorkGroupInfo(jEnv, jniContext, CL_KERNEL_PRIVATE_MEM_SIZE, &maxPrivateMemSize); + if (length != 1) { + throwAparapiJNIRuntimeException(jEnv, "getKernelMinimumPrivateMemSizeInUsePerWorkItemJNI() invalid length" + std::to_string(length)); + throw JNIException("queryKernelWorkGroupInfo() invalid length: " + length); + } + + return (jlong)maxPrivateMemSize; + } catch (JNIException &ex) { + //Log or ignore on puropose, as a JNI exception is on its way + } catch (CLException &ex) { + //Log or ignore on puropose, as a JNI exception is on its way + } + + //This value will never be returned, because a JNI exception is on its way + return 0; +} + +JNI_JAVA(jlong, KernelRunnerJNI, getKernelLocalMemSizeInUseJNI) + (JNIEnv *jEnv, jobject jobj, jlong jniContextHandle) { + long maxLocalMemSize = -1; + + try { + JNIContext* jniContext = JNIContext::getJNIContext(jniContextHandle); + if (jniContext == NULL){ + throwAparapiJNIRuntimeException(jEnv, "Failed to obtain JNI context from handle"); + throw JNIException("getJNIContext()"); + } + + int length = queryKernelWorkGroupInfo(jEnv, jniContext, CL_KERNEL_LOCAL_MEM_SIZE, &maxLocalMemSize); + if (length != 1) { + throwAparapiJNIRuntimeException(jEnv, "getKernelLocalMemSizeInUseJNI() invalid length" + std::to_string(length)); + throw JNIException("queryKernelWorkGroupInfo() invalid length: " + length); + } + + return (jlong)maxLocalMemSize; + } catch (JNIException &ex) { + //Log or ignore on puropose, as a JNI exception is on its way + } catch (CLException &ex) { + //Log or ignore on puropose, as a JNI exception is on its way + } + + //This value will never be returned, because a JNI exception is on its way + return 0; +} + +JNI_JAVA(jint, KernelRunnerJNI, getKernelPreferredWorkGroupSizeMultipleJNI) + (JNIEnv *jEnv, jobject jobj, jlong jniContextHandle) { + long preferredWorkGroupSize[3]; + + try { + JNIContext* jniContext = JNIContext::getJNIContext(jniContextHandle); + if (jniContext == NULL){ + throwAparapiJNIRuntimeException(jEnv, "Failed to obtain JNI context from handle"); + throw JNIException("getJNIContext()"); + } + + int length = queryKernelWorkGroupInfo(jEnv, jniContext, CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE, preferredWorkGroupSize); + if (length != 1) { + throwAparapiJNIRuntimeException(jEnv, "getKernelPreferredWorkGroupSizeMultipleJNI() invalid length" + std::to_string(length)); + throw JNIException("queryKernelWorkGroupInfo() invalid length: " + length); + } + + return (jint)preferredWorkGroupSize[0]; + } catch (JNIException &ex) { + //Log or ignore on puropose, as a JNI exception is on its way + } catch (CLException &ex) { + //Log or ignore on puropose, as a JNI exception is on its way + } + + //This value will never be returned, because a JNI exception is on its way + return 0; +} + +JNI_JAVA(jint, KernelRunnerJNI, getKernelMaxWorkGroupSizeJNI) + (JNIEnv *jEnv, jobject jobj, jlong jniContextHandle) { + long maxWorkGroupSize[3]; + + try { + JNIContext* jniContext = JNIContext::getJNIContext(jniContextHandle); + if (jniContext == NULL){ + throwAparapiJNIRuntimeException(jEnv, "Failed to obtain JNI context from handle"); + throw JNIException("getJNIContext()"); + } + + int length = queryKernelWorkGroupInfo(jEnv, jniContext, CL_KERNEL_WORK_GROUP_SIZE, maxWorkGroupSize); + if (length != 1) { + throwAparapiJNIRuntimeException(jEnv, "getKernelMaxWorkGroupSizeJNI() invalid length" + std::to_string(length)); + throw JNIException("queryKernelWorkGroupInfo() invalid length: " + length); + } + + return (jint)maxWorkGroupSize[0]; + } catch (JNIException &ex) { + //Log or ignore on puropose, as a JNI exception is on its way + } catch (CLException &ex) { + //Log or ignore on puropose, as a JNI exception is on its way + } + + //This value will never be returned, because a JNI exception is on its way + return 0; +} + +JNI_JAVA(jintArray, KernelRunnerJNI, getKernelCompileWorkGroupSizeJNI) + (JNIEnv *jEnv, jobject jobj, jlong jniContextHandle) { + long compileWorkGroupSize[3]; + + try { + JNIContext* jniContext = JNIContext::getJNIContext(jniContextHandle); + if (jniContext == NULL){ + throwAparapiJNIRuntimeException(jEnv, "Failed to obtain JNI context from handle"); + throw JNIException("getJNIContext()"); + } + + int length = queryKernelWorkGroupInfo(jEnv, jniContext, CL_KERNEL_COMPILE_WORK_GROUP_SIZE, compileWorkGroupSize); + if (length == 0) { + throwAparapiJNIRuntimeException(jEnv, "getKernelCompileWorkGroupSizeJNI() invalid length: " + std::to_string(length)); + throw JNIException("queryKernelWorkGroupInfo() invalid length: " + length); + } + + jintArray jArr = jEnv->NewIntArray(length); + if (jArr == NULL) { + throwAparapiJNIRuntimeException(jEnv, "Failed to create Java integer array - NewIntArray()"); + throw JNIException("Failed to create Java integer array - NewIntArray()"); + } + + int arr[3]; + for (int i = 0; i < length; i++) { + arr[i] = (jint)compileWorkGroupSize[i]; + } + + jEnv->SetIntArrayRegion(jArr, (jsize)0, (jsize)length, arr); + + return jArr; + } catch (JNIException &ex) { + //Log or ignore on puropose, as a JNI exception is on its way + } catch (CLException& ex) { + //Log or ignore on puropose, as a JNI exception is on its way + } + + //This value will never be returned, because a JNI exception is on its way + return NULL; +} + +/** + * queries OpenCL about a compiled Kernel that is intended to run on a specific device + * + * @param jniContext the context with the arguments + * @param queryParameter the openCL parameter that is to be queried + * @param result a reference to an array of long with size 3 of the result values + * @return the number of entries with valid data in the array, 0 if there was an error + * + * @throws CLException + */ +int queryKernelWorkGroupInfo(JNIEnv *jEnv, JNIContext* jniContext, cl_kernel_work_group_info queryParameter, long *result) { + size_t queryResult[3]; + cl_ulong queryResultULong; + + size_t returnedSize = 0; + void *voidQueryResult; + size_t voidQuerySize; + size_t unitSize; + + int resultLength = 0; + + switch (queryParameter) { + case CL_KERNEL_WORK_GROUP_SIZE: + voidQueryResult = (void *)queryResult; + voidQuerySize = sizeof(size_t); + unitSize = sizeof(size_t); + break; + case CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE: + voidQueryResult = (void *)queryResult; + voidQuerySize = sizeof(size_t); + unitSize = sizeof(size_t); + break; + case CL_KERNEL_LOCAL_MEM_SIZE: + voidQueryResult = (void *)&queryResultULong; + voidQuerySize = sizeof(queryResultULong); + unitSize = sizeof(queryResultULong); + break; + case CL_KERNEL_PRIVATE_MEM_SIZE: + voidQueryResult = (void *)&queryResultULong; + voidQuerySize = sizeof(queryResultULong); + unitSize = sizeof(queryResultULong); + break; + default: + voidQueryResult = (void *)queryResult; + voidQuerySize = sizeof(queryResult); + unitSize = sizeof(size_t); + break; + } + + cl_int status = clGetKernelWorkGroupInfo(jniContext->kernel, (cl_device_id)jniContext->deviceId, queryParameter, + voidQuerySize, voidQueryResult, &returnedSize); + if (status != CL_SUCCESS) { + if (jEnv != NULL) { + throwAparapiJNIRuntimeException(jEnv, "clGetKernelWorkGroupInfo() failed with error: " + std::string(CLHelper::errString(status))); + } + throw CLException(status, "clGetKernelWorkGroupInfo()"); + } + + switch (queryParameter) { + case CL_KERNEL_PRIVATE_MEM_SIZE: + resultLength = 1; + *result = (long)queryResultULong; + break; + case CL_KERNEL_LOCAL_MEM_SIZE: + resultLength = 1; + *result = (long)queryResultULong; + break; + default: + resultLength = returnedSize / unitSize; + + for (int i = 0; i < resultLength; i++) { + result[i] = (long)queryResult[i]; + } + break; + } + + return resultLength; +} + /** * enqueus the current kernel to run on opencl * @@ -879,6 +1112,24 @@ void enqueueKernel(JNIContext* jniContext, Range& range, int passes, int argPos, jbyte* kernelInBytes = jniContext->runKernelInBytes; int* kernelInBytesAsInts = reinterpret_cast<int*>(kernelInBytes); + + //Enforce validation of local work group size as some OpenCL vendor implementations don't do it + long maxKernelWorkGroupSize = 0; + int queryResults = queryKernelWorkGroupInfo(NULL, jniContext, CL_KERNEL_WORK_GROUP_SIZE, &maxKernelWorkGroupSize); + if (queryResults != 1) { + throw JNIException("Failed to retrieve MAX Kernel WorkGroup size: got " + std::to_string(queryResults) + " values instead of 1"); + } + + int targetWorkGroupSize = 1; + for (int i = 0; i < range.dims; i++) { + targetWorkGroupSize *= range.localDims[i]; + } + + if (targetWorkGroupSize > maxKernelWorkGroupSize) { + throw JNIException("Kernel overall local size: " + std::to_string(targetWorkGroupSize) + + " exceeds maximum kernel allowed local size of: " + std::to_string(maxKernelWorkGroupSize)); + } + cl_int status = CL_SUCCESS; for (int passid=0; passid < passes; passid++) { @@ -1219,7 +1470,11 @@ JNI_JAVA(jint, KernelRunnerJNI, runKernelJNI) jniContext->unpinAll(jenv); return cle.status(); } - + catch(JNIException& jnie) { + jnie.printError(); + jniContext->unpinAll(jenv); + return -1; + } diff --git a/src/cpp/runKernel/Aparapi.h b/src/cpp/runKernel/Aparapi.h index 68b82bb1b55ce706b08a007933d7525bcaf9e24d..2321734243d3c716be07d0a98fc7e9af0ca22a4e 100644 --- a/src/cpp/runKernel/Aparapi.h +++ b/src/cpp/runKernel/Aparapi.h @@ -107,5 +107,6 @@ void writeProfile(JNIEnv* jenv, JNIContext* jniContext); KernelArg* getArgForBuffer(JNIEnv* jenv, JNIContext* jniContext, jobject buffer); +int queryKernelWorkGroupInfo(JNIEnv *jEnv, JNIContext* jniContext, cl_kernel_work_group_info queryParameter, long *result); #endif // APARAPI_H