When passing a byte array from Java to Go, Seq.writeByteArray JNI call encodes only the array size and the pointer to the array. Go-side receives the (size, ptr) pair info during the subsequent Seq.send JNI call, and copies the elements into a Go byte slice. We must pin the array elements until Go-side completes copying so that they are not moved or collected by Java runtime. This change keeps track of the pinned array info in a 'pinned' linked list, and unpin them as the Seq memory is freed. The jbyteArray argument passed to Seq.writeByteArray is needed to release the pinned byte array elements, but that is a "local reference". It is not guaranteed that the reference is valid after the method returns. Thus, we stash its global reference in the 'pinned' list and delete it later as well. A similar problem can occur on the byte slice returned from a Go function. This change does not address the case yet. Fixes golang/go#9486 Change-Id: I1255aefbc80b21ccbe9b2bf37699faaf0c5f0bae Reviewed-on: https://go-review.googlesource.com/2586 Reviewed-by: David Crawshaw <crawshaw@golang.org>
420 lines
11 KiB
C
420 lines
11 KiB
C
// Copyright 2014 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
#include <android/log.h>
|
|
#include <errno.h>
|
|
#include <jni.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include "seq_android.h"
|
|
#include "_cgo_export.h"
|
|
|
|
#define LOG_INFO(...) __android_log_print(ANDROID_LOG_INFO, "go/Seq", __VA_ARGS__)
|
|
#define LOG_FATAL(...) __android_log_print(ANDROID_LOG_FATAL, "go/Seq", __VA_ARGS__)
|
|
|
|
static jfieldID memptr_id;
|
|
static jfieldID receive_refnum_id;
|
|
static jfieldID receive_code_id;
|
|
static jfieldID receive_handle_id;
|
|
|
|
static jclass jbytearray_clazz;
|
|
|
|
// pinned represents a pinned array to be released at the end of Send call.
|
|
typedef struct pinned {
|
|
jobject ref;
|
|
void* ptr;
|
|
struct pinned* next;
|
|
} pinned;
|
|
|
|
// mem is a simple C equivalent of seq.Buffer.
|
|
//
|
|
// Many of the allocations around mem could be avoided to improve
|
|
// function call performance, but the goal is to start simple.
|
|
typedef struct mem {
|
|
uint8_t *buf;
|
|
uint32_t off;
|
|
uint32_t len;
|
|
uint32_t cap;
|
|
|
|
// TODO(hyangah): have it as a separate field outside mem?
|
|
pinned* pinned;
|
|
} mem;
|
|
|
|
// mem_ensure ensures that m has at least size bytes free.
|
|
// If m is NULL, it is created.
|
|
static mem *mem_ensure(mem *m, uint32_t size) {
|
|
if (m == NULL) {
|
|
m = (mem*)malloc(sizeof(mem));
|
|
if (m == NULL) {
|
|
LOG_FATAL("mem_ensure malloc failed");
|
|
}
|
|
m->cap = 0;
|
|
m->off = 0;
|
|
m->len = 0;
|
|
m->buf = NULL;
|
|
m->pinned = NULL;
|
|
}
|
|
if (m->cap > m->off+size) {
|
|
return m;
|
|
}
|
|
m->buf = (uint8_t*)realloc((void*)m->buf, m->off+size);
|
|
if (m->buf == NULL) {
|
|
LOG_FATAL("mem_ensure realloc failed, off=%d, size=%d", m->off, size);
|
|
}
|
|
m->cap = m->off+size;
|
|
return m;
|
|
}
|
|
|
|
static mem *mem_get(JNIEnv *env, jobject obj) {
|
|
// Storage space for pointer is always 64-bits, even on 32-bit
|
|
// machines. Cast to uintptr_t to avoid -Wint-to-pointer-cast.
|
|
return (mem*)(uintptr_t)(*env)->GetLongField(env, obj, memptr_id);
|
|
}
|
|
|
|
static uint8_t *mem_read(JNIEnv *env, jobject obj, uint32_t size) {
|
|
if (size == 0) {
|
|
return NULL;
|
|
}
|
|
mem *m = mem_get(env, obj);
|
|
if (m == NULL) {
|
|
LOG_FATAL("mem_read on NULL mem");
|
|
}
|
|
if (m->len-m->off < size) {
|
|
LOG_FATAL("short read, size: %d", size);
|
|
}
|
|
uint8_t *res = m->buf+m->off;
|
|
m->off += size;
|
|
return res;
|
|
}
|
|
|
|
uint8_t *mem_write(JNIEnv *env, jobject obj, uint32_t size) {
|
|
mem *m = mem_get(env, obj);
|
|
if (m == NULL) {
|
|
LOG_FATAL("mem_write on NULL mem");
|
|
}
|
|
if (m->off != m->len) {
|
|
LOG_FATAL("write can only append to seq, size: (off=%d, len=%d, size=%d", m->off, m->len, size);
|
|
}
|
|
uint32_t cap = m->cap;
|
|
while (m->off+size > cap) {
|
|
cap *= 2;
|
|
}
|
|
m = mem_ensure(m, cap);
|
|
uint8_t *res = m->buf+m->off;
|
|
m->off += size;
|
|
m->len += size;
|
|
return res;
|
|
}
|
|
|
|
static void *pin_array(JNIEnv *env, jobject obj, jobject arr) {
|
|
mem *m = mem_get(env, obj);
|
|
if (m == NULL) {
|
|
m = mem_ensure(m, 64);
|
|
}
|
|
pinned *p = (pinned*) malloc(sizeof(pinned));
|
|
if (p == NULL) {
|
|
LOG_FATAL("pin_array malloc failed");
|
|
}
|
|
p->ref = (*env)->NewGlobalRef(env, arr);
|
|
|
|
if ((*env)->IsInstanceOf(env, p->ref, jbytearray_clazz)) {
|
|
p->ptr = (*env)->GetByteArrayElements(env, p->ref, NULL);
|
|
} else {
|
|
LOG_FATAL("unsupported array type");
|
|
}
|
|
|
|
p->next = m->pinned;
|
|
m->pinned = p;
|
|
return p->ptr;
|
|
}
|
|
|
|
static void unpin_arrays(JNIEnv *env, mem *m) {
|
|
pinned* p = m->pinned;
|
|
while (p != NULL) {
|
|
if ((*env)->IsInstanceOf(env, p->ref, jbytearray_clazz)) {
|
|
(*env)->ReleaseByteArrayElements(env, p->ref, (jbyte*)p->ptr, JNI_ABORT);
|
|
} else {
|
|
LOG_FATAL("invalid array type");
|
|
}
|
|
|
|
(*env)->DeleteGlobalRef(env, p->ref);
|
|
|
|
pinned* o = p;
|
|
p = p->next;
|
|
free(o);
|
|
}
|
|
m->pinned = NULL;
|
|
}
|
|
|
|
|
|
static jfieldID find_field(JNIEnv *env, const char *class_name, const char *field_name, const char *field_type) {
|
|
jclass clazz = (*env)->FindClass(env, class_name);
|
|
if (clazz == NULL) {
|
|
LOG_FATAL("cannot find %s", class_name);
|
|
return NULL;
|
|
}
|
|
jfieldID id = (*env)->GetFieldID(env, clazz, field_name , field_type);
|
|
if(id == NULL) {
|
|
LOG_FATAL("no %s/%s field", field_name, field_type);
|
|
return NULL;
|
|
}
|
|
return id;
|
|
}
|
|
|
|
static jclass find_class(JNIEnv *env, const char *class_name) {
|
|
jclass clazz = (*env)->FindClass(env, class_name);
|
|
if (clazz == NULL) {
|
|
LOG_FATAL("cannot find %s", class_name);
|
|
return NULL;
|
|
}
|
|
return (*env)->NewGlobalRef(env, clazz);
|
|
}
|
|
|
|
void init_seq(void *javavm) {
|
|
JavaVM *vm = (JavaVM*)javavm;
|
|
JNIEnv *env;
|
|
int res = (*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6);
|
|
if (res == JNI_EDETACHED) {
|
|
JavaVMAttachArgs args;
|
|
args.version = JNI_VERSION_1_6;
|
|
if ((*vm)->AttachCurrentThread(vm, &env, &args) != 0) {
|
|
LOG_FATAL("cannot attach to current_vm");
|
|
}
|
|
} else if (res != 0) {
|
|
LOG_FATAL("bad vm env: %d", res);
|
|
}
|
|
|
|
memptr_id = find_field(env, "go/Seq", "memptr", "J");
|
|
receive_refnum_id = find_field(env, "go/Seq$Receive", "refnum", "I");
|
|
receive_handle_id = find_field(env, "go/Seq$Receive", "handle", "I");
|
|
receive_code_id = find_field(env, "go/Seq$Receive", "code", "I");
|
|
|
|
jbytearray_clazz = find_class(env, "[B");
|
|
|
|
LOG_INFO("loaded go/Seq");
|
|
|
|
if (res == JNI_EDETACHED) {
|
|
(*vm)->DetachCurrentThread(vm);
|
|
}
|
|
}
|
|
|
|
JNIEXPORT void JNICALL
|
|
Java_go_Seq_ensure(JNIEnv *env, jobject obj, jint size) {
|
|
mem *m = mem_get(env, obj);
|
|
if (m == NULL || m->off+size > m->cap) {
|
|
m = mem_ensure(m, size);
|
|
(*env)->SetLongField(env, obj, memptr_id, (jlong)(uintptr_t)m);
|
|
}
|
|
}
|
|
|
|
JNIEXPORT void JNICALL
|
|
Java_go_Seq_free(JNIEnv *env, jobject obj) {
|
|
mem *m = mem_get(env, obj);
|
|
if (m != NULL) {
|
|
unpin_arrays(env, m);
|
|
free((void*)m->buf);
|
|
free((void*)m);
|
|
}
|
|
}
|
|
|
|
#define MEM_READ(obj, ty) ((ty*)mem_read(env, obj, sizeof(ty)))
|
|
|
|
JNIEXPORT jbyte JNICALL
|
|
Java_go_Seq_readInt8(JNIEnv *env, jobject obj) {
|
|
uint8_t *v = MEM_READ(obj, uint8_t);
|
|
if (v == NULL) {
|
|
return 0;
|
|
}
|
|
return *v;
|
|
}
|
|
|
|
JNIEXPORT jshort JNICALL
|
|
Java_go_Seq_readInt16(JNIEnv *env, jobject obj) {
|
|
int16_t *v = MEM_READ(obj, int16_t);
|
|
return v == NULL ? 0 : *v;
|
|
}
|
|
|
|
JNIEXPORT jint JNICALL
|
|
Java_go_Seq_readInt32(JNIEnv *env, jobject obj) {
|
|
int32_t *v = MEM_READ(obj, int32_t);
|
|
return v == NULL ? 0 : *v;
|
|
}
|
|
|
|
JNIEXPORT jlong JNICALL
|
|
Java_go_Seq_readInt64(JNIEnv *env, jobject obj) {
|
|
int64_t *v = MEM_READ(obj, int64_t);
|
|
return v == NULL ? 0 : *v;
|
|
}
|
|
|
|
JNIEXPORT jfloat JNICALL
|
|
Java_go_Seq_readFloat32(JNIEnv *env, jobject obj) {
|
|
float *v = MEM_READ(obj, float);
|
|
return v == NULL ? 0 : *v;
|
|
}
|
|
|
|
JNIEXPORT jdouble JNICALL
|
|
Java_go_Seq_readFloat64(JNIEnv *env, jobject obj) {
|
|
double *v = MEM_READ(obj, double);
|
|
return v == NULL ? 0 : *v;
|
|
}
|
|
|
|
JNIEXPORT jstring JNICALL
|
|
Java_go_Seq_readUTF16(JNIEnv *env, jobject obj) {
|
|
int32_t size = *MEM_READ(obj, int32_t);
|
|
if (size == 0) {
|
|
return NULL;
|
|
}
|
|
return (*env)->NewString(env, (jchar*)mem_read(env, obj, 2*size), size);
|
|
}
|
|
|
|
JNIEXPORT jbyteArray JNICALL
|
|
Java_go_Seq_readByteArray(JNIEnv *env, jobject obj) {
|
|
// Send the (array length, pointer) pair encoded as two int64.
|
|
// The pointer value is omitted if array length is 0.
|
|
jlong size = Java_go_Seq_readInt64(env, obj);
|
|
if (size == 0) {
|
|
return NULL;
|
|
}
|
|
jbyteArray res = (*env)->NewByteArray(env, size);
|
|
jlong ptr = Java_go_Seq_readInt64(env, obj);
|
|
(*env)->SetByteArrayRegion(env, res, 0, size, (jbyte*)(intptr_t)(ptr));
|
|
return res;
|
|
}
|
|
|
|
#define MEM_WRITE(ty) (*(ty*)mem_write(env, obj, sizeof(ty)))
|
|
|
|
JNIEXPORT void JNICALL
|
|
Java_go_Seq_writeInt8(JNIEnv *env, jobject obj, jbyte v) {
|
|
MEM_WRITE(int8_t) = v;
|
|
}
|
|
|
|
JNIEXPORT void JNICALL
|
|
Java_go_Seq_writeInt16(JNIEnv *env, jobject obj, jshort v) {
|
|
MEM_WRITE(int16_t) = v;
|
|
}
|
|
|
|
JNIEXPORT void JNICALL
|
|
Java_go_Seq_writeInt32(JNIEnv *env, jobject obj, jint v) {
|
|
MEM_WRITE(int32_t) = v;
|
|
}
|
|
|
|
JNIEXPORT void JNICALL
|
|
Java_go_Seq_writeInt64(JNIEnv *env, jobject obj, jlong v) {
|
|
MEM_WRITE(int64_t) = v;
|
|
}
|
|
|
|
JNIEXPORT void JNICALL
|
|
Java_go_Seq_writeFloat32(JNIEnv *env, jobject obj, jfloat v) {
|
|
MEM_WRITE(float) = v;
|
|
}
|
|
|
|
JNIEXPORT void JNICALL
|
|
Java_go_Seq_writeFloat64(JNIEnv *env, jobject obj, jdouble v) {
|
|
MEM_WRITE(double) = v;
|
|
}
|
|
|
|
JNIEXPORT void JNICALL
|
|
Java_go_Seq_writeUTF16(JNIEnv *env, jobject obj, jstring v) {
|
|
if (v == NULL) {
|
|
MEM_WRITE(int32_t) = 0;
|
|
return;
|
|
}
|
|
int32_t size = (*env)->GetStringLength(env, v);
|
|
MEM_WRITE(int32_t) = size;
|
|
(*env)->GetStringRegion(env, v, 0, size, (jchar*)mem_write(env, obj, 2*size));
|
|
}
|
|
|
|
JNIEXPORT void JNICALL
|
|
Java_go_Seq_writeByteArray(JNIEnv *env, jobject obj, jbyteArray v) {
|
|
// For Byte array, we pass only the (array length, pointer) pair
|
|
// encoded as two int64 values. If the array length is 0,
|
|
// the pointer value is omitted.
|
|
if (v == NULL) {
|
|
MEM_WRITE(int64_t) = 0;
|
|
return;
|
|
}
|
|
|
|
jsize len = (*env)->GetArrayLength(env, v);
|
|
MEM_WRITE(int64_t) = len;
|
|
if (len == 0) {
|
|
return;
|
|
}
|
|
|
|
jbyte* b = pin_array(env, obj, v);
|
|
MEM_WRITE(int64_t) = (jlong)(uintptr_t)b;
|
|
}
|
|
|
|
JNIEXPORT void JNICALL
|
|
Java_go_Seq_resetOffset(JNIEnv *env, jobject obj) {
|
|
mem *m = mem_get(env, obj);
|
|
if (m == NULL) {
|
|
LOG_FATAL("resetOffset on NULL mem");
|
|
}
|
|
m->off = 0;
|
|
}
|
|
|
|
JNIEXPORT void JNICALL
|
|
Java_go_Seq_log(JNIEnv *env, jobject obj, jstring v) {
|
|
mem *m = mem_get(env, obj);
|
|
const char *label = (*env)->GetStringUTFChars(env, v, NULL);
|
|
if (label == NULL) {
|
|
LOG_FATAL("log GetStringUTFChars failed");
|
|
}
|
|
if (m == NULL) {
|
|
LOG_INFO("%s: mem=NULL", label);
|
|
} else {
|
|
LOG_INFO("%s: mem{off=%d, len=%d, cap=%d}", label, m->off, m->len, m->cap);
|
|
}
|
|
(*env)->ReleaseStringUTFChars(env, v, label);
|
|
}
|
|
|
|
JNIEXPORT void JNICALL
|
|
Java_go_Seq_destroyRef(JNIEnv *env, jclass clazz, jint refnum) {
|
|
DestroyRef(refnum);
|
|
}
|
|
|
|
JNIEXPORT void JNICALL
|
|
Java_go_Seq_send(JNIEnv *env, jclass clazz, jstring descriptor, jint code, jobject src_obj, jobject dst_obj) {
|
|
mem *src = mem_get(env, src_obj);
|
|
if (src == NULL) {
|
|
LOG_FATAL("send src is NULL");
|
|
}
|
|
mem *dst = mem_get(env, dst_obj);
|
|
if (dst == NULL) {
|
|
LOG_FATAL("send dst is NULL");
|
|
}
|
|
|
|
GoString desc;
|
|
desc.p = (char*)(*env)->GetStringUTFChars(env, descriptor, NULL);
|
|
if (desc.p == NULL) {
|
|
LOG_FATAL("send GetStringUTFChars failed");
|
|
}
|
|
desc.n = (*env)->GetStringUTFLength(env, descriptor);
|
|
Send(desc, (GoInt)code, src->buf, src->len, &dst->buf, &dst->len);
|
|
(*env)->ReleaseStringUTFChars(env, descriptor, desc.p);
|
|
unpin_arrays(env, src); // assume 'src' is no longer needed.
|
|
}
|
|
|
|
JNIEXPORT void JNICALL
|
|
Java_go_Seq_recv(JNIEnv *env, jclass clazz, jobject in_obj, jobject receive) {
|
|
mem *in = mem_get(env, in_obj);
|
|
if (in == NULL) {
|
|
LOG_FATAL("recv in is NULL");
|
|
}
|
|
struct Recv_return ret = Recv(&in->buf, &in->len);
|
|
(*env)->SetIntField(env, receive, receive_refnum_id, ret.r0);
|
|
(*env)->SetIntField(env, receive, receive_code_id, ret.r1);
|
|
(*env)->SetIntField(env, receive, receive_handle_id, ret.r2);
|
|
}
|
|
|
|
JNIEXPORT void JNICALL
|
|
Java_go_Seq_recvRes(JNIEnv *env, jclass clazz, jint handle, jobject out_obj) {
|
|
mem *out = mem_get(env, out_obj);
|
|
if (out == NULL) {
|
|
LOG_FATAL("recvRes out is NULL");
|
|
}
|
|
RecvRes((int32_t)handle, out->buf, out->len);
|
|
}
|