mobile/bind: replace seq serialization with direct calls

The seq serialization machinery is a historic artifact from when Go
mobile code had to run in a separate process. Now that Go code is running
in-process, replace the explicit serialization with direct calls and pass
arguments on the stack.

The benefits are a much smaller bind runtime, much less garbage (and, in
Java, fewer objects with finalizers), less argument copying, and faster
cross-language calls.
The cost is a more complex generator, because some of the work from the
bind runtime is moved to generated code. Generated code now handles
conversion between Go and Java/ObjC types, multiple return values and memory
management of byte slice and string arguments.

To overcome the lack of calling C code between Go packages, all bound
packages now end up in the same (fake) package, "gomobile_bind", instead of
separate packages (go_<pkgname>). To avoid name clashes, the package name is
added as a prefix to generated functions and types.

Also, don't copy byte arrays passed to Go, saving call time and
allowing read([]byte)-style interfaces to foreign callers (#12113).

Finally, add support for nil interfaces and struct pointers to objc.

This is a large CL, but most of the changes stem from changing testdata.

The full benchcmp output on the CL/20095 benchmarks on my Nexus 5 is
reproduced below. Note that the savings for the JavaSlice* benchmarks are
skewed because byte slices are no longer copied before passing them to Go.

benchmark                                 old ns/op     new ns/op     delta
BenchmarkJavaEmpty                        26.0          19.0          -26.92%
BenchmarkJavaEmptyDirect                  23.0          22.0          -4.35%
BenchmarkJavaNoargs                       7685          2339          -69.56%
BenchmarkJavaNoargsDirect                 17405         8041          -53.80%
BenchmarkJavaOnearg                       26887         2366          -91.20%
BenchmarkJavaOneargDirect                 34266         7910          -76.92%
BenchmarkJavaOneret                       38325         2245          -94.14%
BenchmarkJavaOneretDirect                 46265         7708          -83.34%
BenchmarkJavaManyargs                     41720         2535          -93.92%
BenchmarkJavaManyargsDirect               51026         8373          -83.59%
BenchmarkJavaRefjava                      38139         21260         -44.26%
BenchmarkJavaRefjavaDirect                42706         28150         -34.08%
BenchmarkJavaRefgo                        34403         6843          -80.11%
BenchmarkJavaRefgoDirect                  40193         16582         -58.74%
BenchmarkJavaStringShort                  32366         9323          -71.20%
BenchmarkJavaStringShortDirect            41973         19118         -54.45%
BenchmarkJavaStringLong                   127879        94420         -26.16%
BenchmarkJavaStringLongDirect             133776        114760        -14.21%
BenchmarkJavaStringShortUnicode           32562         9221          -71.68%
BenchmarkJavaStringShortUnicodeDirect     41464         19094         -53.95%
BenchmarkJavaStringLongUnicode            131015        89401         -31.76%
BenchmarkJavaStringLongUnicodeDirect      134130        90786         -32.31%
BenchmarkJavaSliceShort                   42462         7538          -82.25%
BenchmarkJavaSliceShortDirect             52940         17017         -67.86%
BenchmarkJavaSliceLong                    138391        8466          -93.88%
BenchmarkJavaSliceLongDirect              205804        15666         -92.39%
BenchmarkGoEmpty                          3.00          3.00          +0.00%
BenchmarkGoEmptyDirect                    3.00          3.00          +0.00%
BenchmarkGoNoarg                          40342         13716         -66.00%
BenchmarkGoNoargDirect                    46691         13569         -70.94%
BenchmarkGoOnearg                         43529         13757         -68.40%
BenchmarkGoOneargDirect                   44867         14078         -68.62%
BenchmarkGoOneret                         45456         13559         -70.17%
BenchmarkGoOneretDirect                   44694         13442         -69.92%
BenchmarkGoRefjava                        55111         28071         -49.06%
BenchmarkGoRefjavaDirect                  60883         26872         -55.86%
BenchmarkGoRefgo                          57038         29223         -48.77%
BenchmarkGoRefgoDirect                    56153         27812         -50.47%
BenchmarkGoManyargs                       67967         17398         -74.40%
BenchmarkGoManyargsDirect                 60617         16998         -71.96%
BenchmarkGoStringShort                    57538         22600         -60.72%
BenchmarkGoStringShortDirect              52627         22704         -56.86%
BenchmarkGoStringLong                     128485        52530         -59.12%
BenchmarkGoStringLongDirect               138377        52079         -62.36%
BenchmarkGoStringShortUnicode             57062         22994         -59.70%
BenchmarkGoStringShortUnicodeDirect       62563         22938         -63.34%
BenchmarkGoStringLongUnicode              139913        55553         -60.29%
BenchmarkGoStringLongUnicodeDirect        150863        57791         -61.69%
BenchmarkGoSliceShort                     59279         20215         -65.90%
BenchmarkGoSliceShortDirect               60160         21136         -64.87%
BenchmarkGoSliceLong                      411225        301870        -26.59%
BenchmarkGoSliceLongDirect                399029        298915        -25.09%

Fixes golang/go#12619
Fixes golang/go#12113
Fixes golang/go#13033

Change-Id: I2b45e9e98a1248e3c23a5137f775f7364908bec7
Reviewed-on: https://go-review.googlesource.com/19821
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
This commit is contained in:
Elias Naur
2016-03-03 15:03:45 +00:00
parent 988d17d203
commit 6fca37c69e
100 changed files with 6574 additions and 6307 deletions
+31 -88
View File
@@ -12,6 +12,12 @@ import java.util.logging.Logger;
public class Seq {
private static Logger log = Logger.getLogger("GoSeq");
// also known to bind/seq/ref.go and bind/objc/seq_darwin.m
private static final int NULL_REFNUM = 41;
// use single Ref for null Seq.Object
public static final Ref nullRef = new Ref(NULL_REFNUM, null);
static {
// Look for the shim class auto-generated by gomobile bind.
// Its only purpose is to call System.loadLibrary.
@@ -26,70 +32,34 @@ public class Seq {
} catch (IllegalAccessException e) {
log.severe("LoadJNI class bad field: " + e);
}
initSeq();
init();
}
@SuppressWarnings("UnusedDeclaration")
private long memptr; // holds C-allocated pointer
private static native void init();
public Seq() {
ensure(64);
// Empty method to run class initializer
public static void touch() {}
private Seq() {
}
private static void throwException(String msg) throws Exception { // JNI helper
throw new Exception(msg);
}
// ctx is an android.context.Context.
static native void setContext(java.lang.Object ctx);
// Ensure that at least size bytes can be written to the Seq.
// Any existing data in the buffer is preserved.
public native void ensure(int size);
// Moves the internal buffer offset back to zero.
// Length and contents are maintained. Data can be read after a reset.
public native void resetOffset();
public native void log(String label);
public native boolean readBool();
public native byte readInt8();
public native short readInt16();
public native int readInt32();
public native long readInt64();
public long readInt() { return readInt64(); }
public native float readFloat32();
public native double readFloat64();
public native String readUTF16();
public String readString() { return readUTF16(); }
public native byte[] readByteArray();
public native void writeBool(boolean v);
public native void writeInt8(byte v);
public native void writeInt16(short v);
public native void writeInt32(int v);
public native void writeInt64(long v);
public void writeInt(long v) { writeInt64(v); }
public native void writeFloat32(float v);
public native void writeFloat64(double v);
public native void writeUTF16(String v);
public void writeString(String v) { writeUTF16(v); }
public native void writeByteArray(byte[] v);
public void writeRef(Ref ref) {
if (ref == null)
ref = RefTracker.nullRef;
public static int incRef(Seq.Object o) {
Ref ref = o.ref();
tracker.inc(ref);
writeInt32(ref.refnum);
return ref.refnum;
}
public Ref readRef() {
int refnum = readInt32();
public static Ref getRef(int refnum) {
return tracker.get(refnum);
}
static native void initSeq();
// Informs the Go ref tracker that Java is done with this ref.
static native void destroyRef(int refnum);
@@ -98,30 +68,9 @@ public class Seq {
return tracker.createRef(o);
}
// sends a function invocation request to Go.
//
// Blocks until the function completes.
// If the request is for a method, the first element in src is
// a Ref to the receiver.
public static native void send(String descriptor, int code, Seq src, Seq dst);
protected void finalize() throws Throwable {
super.finalize();
free();
}
private native void free();
public static Seq recv(Seq in, int code, int refnum) {
Seq out = new Seq();
if (code == -1) {
// Special signal from seq.FinalizeRef.
tracker.dec(refnum);
return out;
}
Ref r = tracker.get(refnum);
r.obj.call(code, in, out);
return out;
// decRef is called from seq.FinalizeRef
static void decRef(int refnum) {
tracker.dec(refnum);
}
// An Object is a Java object that matches a Go object.
@@ -133,7 +82,6 @@ public class Seq {
// generated abstract Stub.
public interface Object {
public Ref ref();
public void call(int code, Seq in, Seq out);
}
// A Ref is an object tagged with an integer for passing back and
@@ -148,11 +96,11 @@ public class Seq {
public static final class Ref {
// refnum < 0: Go object tracked by Java
// refnum > 0: Java object tracked by Go
int refnum;
public final int refnum;
int refcnt; // for Java obj: track how many times sent to Go.
public Seq.Object obj; // for Java obj: pointers to the Java obj.
public final Seq.Object obj; // for Java obj: pointers to the Java obj.
Ref(int refnum, Seq.Object o) {
this.refnum = refnum;
@@ -174,10 +122,6 @@ public class Seq {
static final class RefTracker {
private static final int REF_OFFSET = 42;
private static final int NULL_REFNUM = 41; // also known to bind/seq/ref.go
// use single Ref for null Seq.Object
private static final Ref nullRef = new Ref(NULL_REFNUM, null);
// Next Java object reference number.
//
@@ -200,7 +144,7 @@ public class Seq {
// We don't keep track of the Go object.
return;
}
if (refnum == nullRef.refnum) {
if (refnum == NULL_REFNUM) {
return;
}
// Count how many times this ref's Java object is passed to Go.
@@ -225,7 +169,7 @@ public class Seq {
log.severe("dec request for Go object "+ refnum);
return;
}
if (refnum == nullRef.refnum) {
if (refnum == Seq.nullRef.refnum) {
return;
}
// Java objects are removed on request of Go.
@@ -241,7 +185,7 @@ public class Seq {
synchronized Ref createRef(Seq.Object o) {
if (o == null) {
return nullRef;
return Seq.nullRef;
}
if (next == Integer.MAX_VALUE) {
throw new RuntimeException("createRef overflow for " + o);
@@ -262,10 +206,9 @@ public class Seq {
//
// When we have real code, examine the tradeoffs.
synchronized Ref get(int refnum) {
if (refnum > 0) {
if (refnum == nullRef.refnum) {
return nullRef;
}
if (refnum == NULL_REFNUM) {
return nullRef;
} else if (refnum > 0) {
Ref ref = javaObjs.get(refnum);
if (ref == null) {
throw new RuntimeException("unknown java Ref: "+refnum);
+40 -5
View File
@@ -68,8 +68,6 @@ public class SeqTest extends InstrumentationTestCase {
Testpkg.setStructVar(s1);
assertEquals("var StructVar", s1.String(), Testpkg.getStructVar().String());
// TODO(hyangah): handle nil return value (translate to null)
AnI obj = new AnI();
obj.name = "this is an I";
Testpkg.setInterfaceVar(obj);
@@ -118,9 +116,14 @@ public class SeqTest extends InstrumentationTestCase {
}
public void testUnicode() {
String want = "Hello, 世界";
String got = Testpkg.StrDup(want);
assertEquals("Strings should match", want, got);
String[] tests = new String[]{
"abcxyz09{}",
"Hello, 世界",
"\uffff\uD800\uDC00\uD800\uDC01\uD808\uDF45\uDBFF\uDFFF"};
for (String want : tests) {
String got = Testpkg.StrDup(want);
assertEquals("Strings should match", want, got);
}
}
public void testNilErr() throws Exception {
@@ -420,5 +423,37 @@ public class SeqTest extends InstrumentationTestCase {
return null;
}
}));
assertEquals("Go nil interface is null", null, Testpkg.NewNullInterface());
assertEquals("Go nil struct pointer is null", null, Testpkg.NewNullStruct());
}
public void testPassByteArray() {
Testpkg.PassByteArray(new Testpkg.B.Stub() {
@Override public void B(byte[] b) {
byte[] want = new byte[]{1, 2, 3, 4};
MoreAsserts.assertEquals("bytes should match", want, b);
}
});
}
public void testReader() {
byte[] b = new byte[8];
try {
long n = Testpkg.ReadIntoByteArray(b);
assertEquals("wrote to the entire byte array", b.length, n);
byte[] want = new byte[b.length];
for (int i = 0; i < want.length; i++)
want[i] = (byte)i;
MoreAsserts.assertEquals("bytes should match", want, b);
} catch (Exception e) {
fail("Failed to write: " + e.toString());
}
}
public void testGoroutineCallback() {
Testpkg.GoroutineCallback(new Testpkg.Receiver.Stub() {
@Override public void Hello(String msg) {
}
});
}
}
+15
View File
@@ -0,0 +1,15 @@
// Copyright 2016 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 <jni.h>
#include "seq.h"
JNIEXPORT void JNICALL
Java_go_Seq_setContext(JNIEnv* env, jclass clazz, jobject ctx) {
JavaVM* vm;
if ((*env)->GetJavaVM(env, &vm) != 0) {
LOG_FATAL("failed to get JavaVM");
}
setContext(vm, (*env)->NewGlobalRef(env, ctx));
}
+21
View File
@@ -0,0 +1,21 @@
// Copyright 2016 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.
package java // import "golang.org/x/mobile/bind/java"
// #cgo LDFLAGS: -llog
//
//#include <jni.h>
import "C"
import (
"unsafe"
"golang.org/x/mobile/internal/mobileinit"
)
//export setContext
func setContext(vm *C.JavaVM, ctx C.jobject) {
mobileinit.SetCurrentContext(unsafe.Pointer(vm), unsafe.Pointer(ctx))
}
-2
View File
@@ -5,8 +5,6 @@
// Package java implements the Java language bindings.
//
// See the design document (http://golang.org/s/gobind).
//
// Currently, this works only for android.
package java
import "C"
+45
View File
@@ -0,0 +1,45 @@
// Copyright 2016 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.
#ifndef __GO_SEQ_HDR__
#define __GO_SEQ_HDR__
#include <stdint.h>
#include <android/log.h>
#include <jni.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__)
// Platform specific types
typedef struct nstring {
void *chars; // utf16 encoded
jsize len; // length in bytes
} nstring;
typedef struct nbyteslice {
void *ptr;
jsize len;
} nbyteslice;
typedef jlong nint;
extern void go_seq_dec_ref(int32_t ref);
extern int32_t go_seq_to_refnum(JNIEnv *env, jobject o);
extern jobject go_seq_from_refnum(JNIEnv *env, int32_t refnum, jclass proxy_class, jmethodID proxy_cons);
extern void go_seq_maybe_throw_exception(JNIEnv *env, jobject msg);
extern jstring go_seq_get_exception_message(JNIEnv *env);
extern jbyteArray go_seq_to_java_bytearray(JNIEnv *env, nbyteslice s, int copy);
extern nbyteslice go_seq_from_java_bytearray(JNIEnv *env, jbyteArray s, int copy);
extern jstring go_seq_to_java_string(JNIEnv *env, nstring str);
extern nstring go_seq_from_java_string(JNIEnv *env, jstring s, int copy);
// push_local_frame retrieves or creates the JNIEnv* for the current thread
// and pushes a JNI reference frame. Must be matched with call to pop_local_frame.
extern JNIEnv *go_seq_push_local_frame(jint cap);
// Pop the current local frame, releasing all JNI local references in it
extern void go_seq_pop_local_frame(JNIEnv *env);
#endif // __GO_SEQ_HDR__
File diff suppressed because it is too large Load Diff
+262
View File
@@ -0,0 +1,262 @@
// Copyright 2016 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.
// C support functions for bindings. This file is copied into the
// generated gomobile_bind package and compiled along with the
// generated binding files.
#include <android/log.h>
#include <errno.h>
#include <jni.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include "seq.h"
#include "_cgo_export.h"
#define NULL_REFNUM 41
static JavaVM *jvm;
// jnienvs holds the per-thread JNIEnv* for Go threads where we called AttachCurrentThread.
// A pthread key destructor is supplied to call DetachCurrentThread on exit. This trick is
// documented in http://developer.android.com/training/articles/perf-jni.html under "Threads".
static pthread_key_t jnienvs;
static jclass seq_class;
static jmethodID seq_throw_exc;
static jmethodID seq_getRef;
static jmethodID seq_decRef;
static jmethodID seq_incRef;
static jmethodID throwable_getMessage;
static jfieldID ref_objField;
// env_destructor is registered as a thread data key destructor to
// clean up a Go thread that is attached to the JVM.
static void env_destructor(void *env) {
if ((*jvm)->DetachCurrentThread(jvm) != JNI_OK) {
LOG_INFO("failed to detach current thread");
}
}
static JNIEnv *go_seq_get_thread_env(void) {
JNIEnv *env;
jint ret = (*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_6);
if (ret != JNI_OK) {
if (ret != JNI_EDETACHED) {
LOG_FATAL("failed to get thread env");
return;
}
if ((*jvm)->AttachCurrentThread(jvm, &env, NULL) != JNI_OK) {
LOG_FATAL("failed to attach current thread");
return;
}
pthread_setspecific(jnienvs, env);
}
return env;
}
void go_seq_maybe_throw_exception(JNIEnv *env, jobject msg) {
if (msg != NULL && (*env)->GetStringLength(env, msg) > 0) {
(*env)->CallStaticVoidMethod(env, seq_class, seq_throw_exc, msg);
}
}
jstring go_seq_get_exception_message(JNIEnv *env) {
jthrowable exc = (*env)->ExceptionOccurred(env);
if (!exc) {
return NULL;
}
(*env)->ExceptionClear(env);
return (*env)->CallObjectMethod(env, exc, throwable_getMessage);
}
jbyteArray go_seq_to_java_bytearray(JNIEnv *env, nbyteslice s, int copy) {
if (s.ptr == NULL) {
return NULL;
}
jbyteArray res = (*env)->NewByteArray(env, s.len);
if (res == NULL) {
LOG_FATAL("NewByteArray failed");
return res;
}
(*env)->SetByteArrayRegion(env, res, 0, s.len, s.ptr);
if (copy) {
free(s.ptr);
}
return res;
}
nstring go_seq_from_java_string(JNIEnv *env, jstring str, int copy) {
struct nstring res = {NULL, 0};
if (str == NULL) {
return res;
}
jsize nchars = (*env)->GetStringLength(env, str);
jchar *chars = (jchar *)(*env)->GetStringChars(env, str, NULL);
if (chars == NULL) {
LOG_FATAL("GetStringChars failed");
return res;
}
if (copy) {
void *arr_copy = malloc(nchars*2);
if (arr_copy == NULL) {
LOG_FATAL("malloc failed");
return res;
}
memcpy(arr_copy, chars, nchars*2);
(*env)->ReleaseStringChars(env, str, chars);
chars = (jchar *)arr_copy;
}
res.chars = chars;
res.len = nchars;
return res;
}
nbyteslice go_seq_from_java_bytearray(JNIEnv *env, jbyteArray arr, int copy) {
struct nbyteslice res = {NULL, 0};
if (arr == NULL) {
return res;
}
jsize len = (*env)->GetArrayLength(env, arr);
if (len == 0) {
return res;
}
jbyte *ptr = (*env)->GetByteArrayElements(env, arr, NULL);
if (ptr == NULL) {
LOG_FATAL("GetByteArrayElements failed");
return res;
}
if (copy) {
void *ptr_copy = (void *)malloc(len);
if (ptr_copy == NULL) {
LOG_FATAL("malloc failed");
return res;
}
memcpy(ptr_copy, ptr, len);
(*env)->ReleaseByteArrayElements(env, arr, ptr, JNI_ABORT);
ptr = (jbyte *)ptr_copy;
}
res.ptr = ptr;
res.len = len;
return res;
}
int32_t go_seq_to_refnum(JNIEnv *env, jobject o) {
if (o == NULL) {
return NULL_REFNUM;
}
return (int32_t)(*env)->CallStaticIntMethod(env, seq_class, seq_incRef, o);
}
jobject go_seq_from_refnum(JNIEnv *env, int32_t refnum, jclass proxy_class, jmethodID proxy_cons) {
if (refnum == NULL_REFNUM) {
return NULL;
}
// Seq.Ref ref = Seq.getRef(refnum)
jobject ref = (*env)->CallStaticObjectMethod(env, seq_class, seq_getRef, (jint)refnum);
if (ref == NULL) {
LOG_FATAL("Unknown reference: %d", refnum);
return NULL;
}
if (refnum > 0) { // Java object
// return ref.obj
return (*env)->GetObjectField(env, ref, ref_objField);
} else {
// return new <Proxy>(ref)
return (*env)->NewObject(env, proxy_class, proxy_cons, ref);
}
}
// go_seq_to_java_string converts a nstring to a jstring.
jstring go_seq_to_java_string(JNIEnv *env, nstring str) {
jstring s = (*env)->NewString(env, str.chars, str.len);
if (str.chars != NULL) {
free(str.chars);
}
return s;
}
// go_seq_push_local_frame retrieves or creates the JNIEnv* for the current thread
// and pushes a JNI reference frame. Must be matched with call to go_seq_pop_local_frame.
JNIEnv *go_seq_push_local_frame(jint cap) {
JNIEnv *env = go_seq_get_thread_env();
if ((*env)->PushLocalFrame(env, cap) < 0) {
LOG_FATAL("PushLocalFrame failed");
}
return env;
}
// Pop the current local frame, freeing all JNI local references in it
void go_seq_pop_local_frame(JNIEnv *env) {
(*env)->PopLocalFrame(env, NULL);
}
void go_seq_dec_ref(int32_t ref) {
JNIEnv *env = go_seq_get_thread_env();
(*env)->CallStaticVoidMethod(env, seq_class, seq_decRef, (jint)ref);
}
JNIEXPORT void JNICALL
Java_go_Seq_init(JNIEnv *env, jclass clazz) {
if ((*env)->GetJavaVM(env, &jvm) != 0) {
LOG_FATAL("failed to get JVM");
return;
}
if (pthread_key_create(&jnienvs, env_destructor) != 0) {
LOG_FATAL("failed to initialize jnienvs thread local storage");
return;
}
seq_class = (*env)->NewGlobalRef(env, clazz);
seq_throw_exc = (*env)->GetStaticMethodID(env, seq_class, "throwException", "(Ljava/lang/String;)V");
if (seq_throw_exc == NULL) {
LOG_FATAL("failed to find method Seq.throwException");
return;
}
seq_getRef = (*env)->GetStaticMethodID(env, seq_class, "getRef", "(I)Lgo/Seq$Ref;");
if (seq_getRef == NULL) {
LOG_FATAL("failed to find method Seq.getRef");
return;
}
seq_decRef = (*env)->GetStaticMethodID(env, seq_class, "decRef", "(I)V");
if (seq_decRef == NULL) {
LOG_FATAL("failed to find method Seq.decRef");
return;
}
seq_incRef = (*env)->GetStaticMethodID(env, seq_class, "incRef", "(Lgo/Seq$Object;)I");
if (seq_incRef == NULL) {
LOG_FATAL("failed to find method Seq.incRef");
return;
}
jclass throwable_class = (*env)->FindClass(env, "java/lang/Throwable");
if (throwable_class == NULL) {
LOG_FATAL("failed to find Throwable class");
return;
}
throwable_getMessage = (*env)->GetMethodID(env, throwable_class, "getMessage", "()Ljava/lang/String;");
if (throwable_getMessage == NULL) {
LOG_FATAL("failed to find method Throwable.getMessage");
return;
}
jclass ref_class = (*env)->FindClass(env, "go/Seq$Ref");
if (ref_class == NULL) {
LOG_FATAL("failed to find the Seq.Ref class");
return;
}
ref_objField = (*env)->GetFieldID(env, ref_class, "obj", "Lgo/Seq$Object;");
if (ref_objField == NULL) {
LOG_FATAL("failed to find the Seq.Ref.obj field");
return;
}
}
JNIEXPORT void JNICALL
Java_go_Seq_destroyRef(JNIEnv *env, jclass clazz, jint refnum) {
DestroyRef(refnum);
}
-132
View File
@@ -1,132 +0,0 @@
// 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.
package java // import "golang.org/x/mobile/bind/java"
//#cgo LDFLAGS: -llog
//#include <android/log.h>
//#include <jni.h>
//#include <stdint.h>
//#include <string.h>
//#include "seq_android.h"
import "C"
import (
"fmt"
"unsafe"
"golang.org/x/mobile/bind/seq"
"golang.org/x/mobile/internal/mobileinit"
)
const maxSliceLen = 1<<31 - 1
const debug = false
// Send is called by Java to send a request to run a Go function.
//export Send
func Send(descriptor string, code int, req *C.uint8_t, reqlen C.size_t, res **C.uint8_t, reslen *C.size_t) {
fn := seq.Registry[descriptor][code]
if fn == nil {
panic(fmt.Sprintf("invalid descriptor(%s) and code(0x%x)", descriptor, code))
}
var in, out *seq.Buffer
if req != nil && reqlen > 0 {
in = &seq.Buffer{
Data: (*[maxSliceLen]byte)(unsafe.Pointer(req))[:reqlen],
}
}
if res != nil {
out = new(seq.Buffer)
}
fn(out, in)
if res != nil {
// BUG(hyangah): the function returning a go byte slice (so fn writes a pointer into 'out') is unsafe.
// After fn is complete here, Go runtime is free to collect or move the pointed byte slice
// contents. (Explicitly calling runtime.GC here will surface the problem?)
// Without pinning support from Go side, it will be hard to fix it without extra copying.
seqToBuf(res, reslen, out)
}
}
// DestroyRef is called by Java to inform Go it is done with a reference.
//export DestroyRef
func DestroyRef(refnum C.int32_t) {
seq.Delete(int32(refnum))
}
func seqToBuf(bufptr **C.uint8_t, lenptr *C.size_t, buf *seq.Buffer) {
if debug {
fmt.Printf("seqToBuf tag 1, len(buf.Data)=%d, *lenptr=%d\n", len(buf.Data), *lenptr)
}
if len(buf.Data) == 0 {
*lenptr = 0
return
}
if len(buf.Data) > int(*lenptr) {
// TODO(crawshaw): realloc
C.free(unsafe.Pointer(*bufptr))
m := C.malloc(C.size_t(len(buf.Data)))
if uintptr(m) == 0 {
panic(fmt.Sprintf("malloc failed, size=%d", len(buf.Data)))
}
*bufptr = (*C.uint8_t)(m)
*lenptr = C.size_t(len(buf.Data))
}
C.memcpy(unsafe.Pointer(*bufptr), unsafe.Pointer(&buf.Data[0]), C.size_t(len(buf.Data)))
}
// transact calls a method on a Java object instance.
// It blocks until the call is complete.
func transact(ref *seq.Ref, _ string, code int, inBuf *seq.Buffer) *seq.Buffer {
var (
out *C.uint8_t = nil
outLen C.size_t = 0
in *C.uint8_t = nil
inLen C.size_t = 0
)
if len(inBuf.Data) > 0 {
in = (*C.uint8_t)(unsafe.Pointer(&inBuf.Data[0]))
inLen = C.size_t(len(inBuf.Data))
}
C.recv(C.int32_t(ref.Num), C.int(code), in, inLen, &out, &outLen)
if outLen > 0 {
outBuf := &seq.Buffer{
Data: make([]byte, outLen),
}
copy(outBuf.Data, (*[maxSliceLen]byte)(unsafe.Pointer(out))[:outLen])
return outBuf
}
return nil
}
func encodeString(out *seq.Buffer, v string) {
out.WriteUTF16(v)
}
func decodeString(in *seq.Buffer) string {
return in.ReadUTF16()
}
func init() {
seq.FinalizeRef = func(ref *seq.Ref) {
if ref.Num < 0 {
panic(fmt.Sprintf("not a Java ref: %d", ref.Num))
}
transact(ref, "", -1, new(seq.Buffer))
}
seq.Transact = transact
seq.EncString = encodeString
seq.DecString = decodeString
}
//export setContext
func setContext(vm *C.JavaVM, ctx C.jobject) {
mobileinit.SetCurrentContext(unsafe.Pointer(vm), unsafe.Pointer(ctx))
}
+101
View File
@@ -0,0 +1,101 @@
// Copyright 2016 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.
package gomobile_bind
// Go support functions for bindings. This file is copied into the
// generated gomobile_bind package and compiled along with the
// generated binding files.
//#cgo LDFLAGS: -llog
//#include <jni.h>
//#include <stdint.h>
//#include <stdlib.h>
//#include "seq.h"
import "C"
import (
"unicode/utf16"
"unsafe"
"golang.org/x/mobile/bind/seq"
)
// DestroyRef is called by Java to inform Go it is done with a reference.
//export DestroyRef
func DestroyRef(refnum C.int32_t) {
seq.Delete(int32(refnum))
}
// encodeString encodes a Go string to utf16 and returns a Java string as a nstring
// containing the jstring.
// encodeString uses UTF16 as the intermediate format. Note that UTF8 is an obvious
// alternative, but JNI only supports a C-safe variant of UTF8 (modified UTF8).
// The returned data is always a copy, regardless of cpy, and will be freed in
// go_seq_to_java_string.
func encodeString(s string, cpy bool) C.nstring {
n := C.int(len(s))
if n == 0 {
return C.nstring{}
}
// Allocate enough for the worst case estimate, every character is a surrogate pair
worstCaseLen := 4 * len(s)
utf16buf := C.malloc(C.size_t(worstCaseLen))
if utf16buf == nil {
panic("encodeString: malloc failed")
}
chars := (*[1<<30 - 1]uint16)(unsafe.Pointer(utf16buf))[:worstCaseLen/2 : worstCaseLen/2]
nchars := seq.UTF16Encode(s, chars)
return C.nstring{chars: unsafe.Pointer(utf16buf), len: C.jsize(nchars)}
}
// decodeString decodes a nstring (jstring) to a Go string.
// If cpy is set, the string contains a copy and is freed.
func decodeString(str C.nstring, cpy bool) string {
if str.chars == nil {
return ""
}
chars := (*[1<<30 - 1]uint16)(unsafe.Pointer(str.chars))[:str.len]
s := string(utf16.Decode(chars)) // TODO: avoid the []rune allocation
if cpy {
C.free(str.chars)
}
return s
}
// fromSlice converts a slice to a jbyteArray cast as a nbyteslice. If cpy
// is set, the returned slice is a copy to be free by go_seq_to_java_bytearray.
func fromSlice(s []byte, cpy bool) C.nbyteslice {
if s == nil || len(s) == 0 {
return C.nbyteslice{}
}
var ptr *C.jbyte
n := C.jsize(len(s))
if cpy {
ptr = (*C.jbyte)(C.malloc(C.size_t(n)))
if ptr == nil {
panic("fromSlice: malloc failed")
}
copy((*[1<<31 - 1]byte)(unsafe.Pointer(ptr))[:n], s)
} else {
ptr = (*C.jbyte)(unsafe.Pointer(&s[0]))
}
return C.nbyteslice{ptr: unsafe.Pointer(ptr), len: n}
}
// toSlice takes a nbyteslice (jbyteArray) and returns a byte slice
// with the data. If cpy is set, the slice contains a copy of the data and is
// freed.
func toSlice(s C.nbyteslice, cpy bool) []byte {
if s.ptr == nil || s.len == 0 {
return nil
}
var b []byte
if cpy {
b = C.GoBytes(s.ptr, C.int(s.len))
C.free(s.ptr)
} else {
b = (*[1<<31 - 1]byte)(unsafe.Pointer(s.ptr))[:s.len:s.len]
}
return b
}
-7
View File
@@ -1,7 +0,0 @@
// 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.
void init_seq(void* vm, void* classfinder);
JNIEnv *get_thread_env(void);
void recv(int32_t ref, int code, uint8_t *in_ptr, size_t in_len, uint8_t **out_ptr, size_t *out_len);
+40
View File
@@ -236,6 +236,14 @@ type NullTest interface {
Null() NullTest
}
func NewNullInterface() I {
return nil
}
func NewNullStruct() *S {
return nil
}
func CallWithNull(_null NullTest, nuller NullTest) bool {
return _null == nil && nuller.Null() == nil
}
@@ -243,3 +251,35 @@ func CallWithNull(_null NullTest, nuller NullTest) bool {
type Issue14168 interface {
F(seq int32)
}
func ReadIntoByteArray(s []byte) (int, error) {
if len(s) != cap(s) {
return 0, fmt.Errorf("cap %d != len %d", cap(s), len(s))
}
for i := 0; i < len(s); i++ {
s[i] = byte(i)
}
return len(s), nil
}
type B interface {
B(b []byte)
}
func PassByteArray(b B) {
b.B([]byte{1, 2, 3, 4})
}
func GoroutineCallback(r Receiver) {
done := make(chan struct{})
go func() {
// Run it multiple times to increase the chance that the goroutine
// will use different threads for the call. Use a long argument string to
// make sure the JNI calls take more time.
for i := 0; i < 100000; i++ {
r.Hello("HelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHello")
}
close(done)
}()
<-done
}