From 18277d8eb626be97958585854b20031428d33598 Mon Sep 17 00:00:00 2001 From: Mick Jordan <mick.jordan@oracle.com> Date: Thu, 28 Jul 2016 11:20:53 -0700 Subject: [PATCH] FFI: implement R_Preserve/ReleaseObject and add test --- .../fficall/src/jni/Rinternals.c | 8 +- .../fficall/src/jni/rffiutils.c | 108 ++++++++++++------ .../fficall/src/jni/rffiutils.h | 10 +- .../fficall/src/jni/variables.c | 2 +- .../packages/testrffi/testrffi/R/testrffi.R | 9 ++ .../packages/testrffi/testrffi/src/testrffi.c | 12 ++ 6 files changed, 105 insertions(+), 44 deletions(-) diff --git a/com.oracle.truffle.r.native/fficall/src/jni/Rinternals.c b/com.oracle.truffle.r.native/fficall/src/jni/Rinternals.c index 14227cbd32..b9c2e5b5bc 100644 --- a/com.oracle.truffle.r.native/fficall/src/jni/Rinternals.c +++ b/com.oracle.truffle.r.native/fficall/src/jni/Rinternals.c @@ -1610,11 +1610,15 @@ int R_check_class_etc (SEXP x, const char **valid) { } void R_PreserveObject(SEXP x) { - // Not applicable + // convert to a JNI global ref until explicitly released + // N.B. Since this returns void we can't do anything + // about the fact that the "value" of "x" may change from + // a C perspective, so an "==" will fail + SEXP result = createGlobalRef(getEnv(), x, 0); } void R_ReleaseObject(SEXP x) { - // Not applicable + releaseGlobalRef(getEnv(), x); } void R_dot_Last(void) { diff --git a/com.oracle.truffle.r.native/fficall/src/jni/rffiutils.c b/com.oracle.truffle.r.native/fficall/src/jni/rffiutils.c index 43f13021a5..2ec1c0522e 100644 --- a/com.oracle.truffle.r.native/fficall/src/jni/rffiutils.c +++ b/com.oracle.truffle.r.native/fficall/src/jni/rffiutils.c @@ -48,9 +48,14 @@ jmp_buf *callErrorJmpBuf; // default for trace output when enabled FILE *traceFile = NULL; -static int alwaysUseGlobal = 0; +typedef struct globalRefTable_struct { + int permanent; + SEXP gref; // The jobject (SEXP) global ref +} GlobalRefElem; + #define CACHED_GLOBALREFS_INITIAL_SIZE 64 -static SEXP *cachedGlobalRefs; +static GlobalRefElem *cachedGlobalRefs; +static int cachedGlobalRefsHwm; static int cachedGlobalRefsLength; // Data structure for managing the required copying of @@ -113,8 +118,9 @@ void init_utils(JNIEnv *env) { unimplementedMethodID = checkGetMethodID(env, RInternalErrorClass, "unimplemented", "(Ljava/lang/String;)Ljava/lang/RuntimeException;", 1); createSymbolMethodID = checkGetMethodID(env, RDataFactoryClass, "createSymbolInterned", "(Ljava/lang/String;)Lcom/oracle/truffle/r/runtime/data/RSymbol;", 1); validateMethodID = checkGetMethodID(env, CallRFFIHelperClass, "validate", "(Ljava/lang/Object;)Ljava/lang/Object;", 1); - cachedGlobalRefs = calloc(CACHED_GLOBALREFS_INITIAL_SIZE, sizeof(SEXP)); + cachedGlobalRefs = calloc(CACHED_GLOBALREFS_INITIAL_SIZE, sizeof(GlobalRefElem)); cachedGlobalRefsLength = CACHED_GLOBALREFS_INITIAL_SIZE; + cachedGlobalRefsHwm = 0; nativeArrayTable = calloc(NATIVE_ARRAY_TABLE_INITIAL_SIZE, sizeof(NativeArrayElem)); nativeArrayTableLength = NATIVE_ARRAY_TABLE_INITIAL_SIZE; nativeArrayTableHwm = 0; @@ -156,14 +162,13 @@ void callExit(JNIEnv *env) { } void invalidateNativeArray(JNIEnv *env, SEXP oldObj) { - int i; - for (i = 0; i < nativeArrayTableHwm; i++) { + for (int i = 0; i < nativeArrayTableHwm; i++) { NativeArrayElem cv = nativeArrayTable[i]; if ((*env)->IsSameObject(env, cv.obj, oldObj)) { #if TRACE_NATIVE_ARRAYS fprintf(traceFile, "invalidateNativeArray(%p): found\n", oldObj); #endif - releaseNativeArray(env, &cv); + releaseNativeArray(env, i); nativeArrayTable[i].obj = NULL; } } @@ -318,53 +323,80 @@ static void releaseNativeArray(JNIEnv *env, int i) { } } -static SEXP checkCachedGlobalRef(JNIEnv *env, SEXP obj, int useGlobal) { - int i; - for (i = 0; i < cachedGlobalRefsLength; i++) { - SEXP ref = cachedGlobalRefs[i]; - if (ref == NULL) { - break; +static SEXP findCachedGlobalRef(JNIEnv *env, SEXP obj) { + for (int i = 0; i < cachedGlobalRefsHwm; i++) { + GlobalRefElem elem = cachedGlobalRefs[i]; + if (elem.gref == NULL) { + continue; } - if ((*env)->IsSameObject(env, ref, obj)) { + if ((*env)->IsSameObject(env, elem.gref, obj)) { #if TRACE_REF_CACHE fprintf(traceFile, "gref: cache hit: %d\n", i); #endif - return ref; + return elem.gref; } } - SEXP result; - if (useGlobal) { - if (i >= cachedGlobalRefsLength) { - int newLength = cachedGlobalRefsLength * 2; + return NULL; +} + +static SEXP addGlobalRef(JNIEnv *env, SEXP obj, int permanent) { + SEXP gref; + if (cachedGlobalRefsHwm >= cachedGlobalRefsLength) { + int newLength = cachedGlobalRefsLength * 2; #if TRACE_REF_CACHE - fprintf(traceFile, "gref: extending table to %d\n", newLength); + fprintf(traceFile, "gref: extending table to %d\n", newLength); #endif - SEXP newCachedGlobalRefs = calloc(newLength, sizeof(SEXP)); - if (newCachedGlobalRefs == NULL) { - fatalError("FFI global refs table expansion failure"); - } - memcpy(newCachedGlobalRefs, cachedGlobalRefs, cachedGlobalRefsLength * sizeof(SEXP)); - free(cachedGlobalRefs); - cachedGlobalRefs = newCachedGlobalRefs; - cachedGlobalRefsLength = newLength; + SEXP newCachedGlobalRefs = calloc(newLength, sizeof(GlobalRefElem)); + if (newCachedGlobalRefs == NULL) { + fatalError("FFI global refs table expansion failure"); } - result = (*env)->NewGlobalRef(env, obj); - cachedGlobalRefs[i] = result; - } else { - result = obj; + memcpy(newCachedGlobalRefs, cachedGlobalRefs, cachedGlobalRefsLength * sizeof(GlobalRefElem)); + free(cachedGlobalRefs); + cachedGlobalRefs = newCachedGlobalRefs; + cachedGlobalRefsLength = newLength; } - return result; + gref = (*env)->NewGlobalRef(env, obj); + cachedGlobalRefs[cachedGlobalRefsHwm].gref = gref; + cachedGlobalRefs[cachedGlobalRefsHwm].permanent = permanent; +#if TRACE_REF_CACHE + fprintf(traceFile, "gref: add: index %d, ref %p\n", cachedGlobalRefsHwm), gref; +#endif + cachedGlobalRefsHwm++; + return gref; } SEXP checkRef(JNIEnv *env, SEXP obj) { - SEXP result = checkCachedGlobalRef(env, obj, alwaysUseGlobal); - TRACE(TARGp, result); - return result; + SEXP gref = findCachedGlobalRef(env, obj); + TRACE(TARGpp, obj, global); + if (gref == NULL) { + return obj; + } else { + return gref; + } +} + +SEXP createGlobalRef(JNIEnv *env, SEXP obj, int permanent) { + SEXP gref = findCachedGlobalRef(env, obj); + if (gref == NULL) { + gref = addGlobalRef(env, obj, permanent); + } + return gref; } -SEXP mkNamedGlobalRef(JNIEnv *env, SEXP obj) { - SEXP result = checkCachedGlobalRef(env, obj, 1); - return result; +void releaseGlobalRef(JNIEnv *env, SEXP obj) { + for (int i = 0; i < cachedGlobalRefsHwm; i++) { + GlobalRefElem elem = cachedGlobalRefs[i]; + if (elem.gref == NULL || elem.permanent) { + continue; + } + if ((*env)->IsSameObject(env, elem.gref, obj)) { +#if TRACE_REF_CACHE + fprintf(traceFile, "gref: release: index %d, gref: %p\n", i, elem.gref); +#endif + (*env)->DeleteGlobalRef(env, elem.gref); + cachedGlobalRefs[i].gref = NULL; + } + } } void validateRef(JNIEnv *env, SEXP x, const char *msg) { diff --git a/com.oracle.truffle.r.native/fficall/src/jni/rffiutils.h b/com.oracle.truffle.r.native/fficall/src/jni/rffiutils.h index 62da16cdcf..4311b0464a 100644 --- a/com.oracle.truffle.r.native/fficall/src/jni/rffiutils.h +++ b/com.oracle.truffle.r.native/fficall/src/jni/rffiutils.h @@ -46,10 +46,14 @@ void *unimplemented(char *msg); void fatalError(char *msg); // makes a call to the VM with x as an argument (for debugger validation) void validate(SEXP x); -// checks x against the list of canonical (named) refs, returning the canonical version if a match +// checks x against the list of global JNI refs, returning the global version if x matches (IsSameObject) SEXP checkRef(JNIEnv *env, SEXP x); -// creates a canonical (named) JNI global ref from x -SEXP mkNamedGlobalRef(JNIEnv *env, SEXP x); +// creates a global JNI global ref from x. If permanent is non-zero, calls to +// releaseGlobalRef are ignored and the global ref persists for the entire execution +// (used for the R global variables such as R_NilValue). +SEXP createGlobalRef(JNIEnv *env, SEXP x, int permanent); +// release a previously created JNI global ref +void releaseGlobalRef(JNIEnv *env, SEXP x); // validate a JNI reference void validateRef(JNIEnv *env, SEXP x, const char *msg); diff --git a/com.oracle.truffle.r.native/fficall/src/jni/variables.c b/com.oracle.truffle.r.native/fficall/src/jni/variables.c index 328b067bd7..887dcea7bd 100644 --- a/com.oracle.truffle.r.native/fficall/src/jni/variables.c +++ b/com.oracle.truffle.r.native/fficall/src/jni/variables.c @@ -104,7 +104,7 @@ void init_variables(JNIEnv *env, jobjectArray initialValues) { } else if (strcmp(nameChars, "R_NaInt") == 0) { R_NaInt = (*env)->CallIntMethod(env, value, intValueMethodID); } else { - SEXP ref = mkNamedGlobalRef(env, value); + SEXP ref = createGlobalRef(env, value, 1); if (strcmp(nameChars, "R_EmptyEnv") == 0) { R_EmptyEnv = ref; } else if (strcmp(nameChars, "R_NilValue") == 0) { diff --git a/com.oracle.truffle.r.test.native/packages/testrffi/testrffi/R/testrffi.R b/com.oracle.truffle.r.test.native/packages/testrffi/testrffi/R/testrffi.R index 767a8c3cbc..5f3f4ff8c3 100644 --- a/com.oracle.truffle.r.test.native/packages/testrffi/testrffi/R/testrffi.R +++ b/com.oracle.truffle.r.test.native/packages/testrffi/testrffi/R/testrffi.R @@ -95,3 +95,12 @@ rffi.iterate_iptr <- function(x) { .Call("iterate_iptr", x, PACKAGE = "testrffi") } +rffi.preserve_object <- function() { + .Call("preserve_object", PACKAGE = "testrffi") +} + +rffi.release_object <- function(x) { + invisible(.Call("release_object", x, PACKAGE = "testrffi")) +} + + diff --git a/com.oracle.truffle.r.test.native/packages/testrffi/testrffi/src/testrffi.c b/com.oracle.truffle.r.test.native/packages/testrffi/testrffi/src/testrffi.c index 0d6d6674ff..21be9bc239 100644 --- a/com.oracle.truffle.r.test.native/packages/testrffi/testrffi/src/testrffi.c +++ b/com.oracle.truffle.r.test.native/packages/testrffi/testrffi/src/testrffi.c @@ -258,3 +258,15 @@ SEXP iterate_iptr(SEXP x) { UNPROTECT(1); return v; } + +SEXP preserve_object(void) { + SEXP v; + v = allocVector(INTSXP, 1); + R_PreserveObject(v); + return v; +} + +SEXP release_object(SEXP x) { + R_ReleaseObject(x); + return R_NilValue; +} -- GitLab