diff --git a/bind/java/Seq.java b/bind/java/Seq.java new file mode 100644 index 0000000..516a875 --- /dev/null +++ b/bind/java/Seq.java @@ -0,0 +1,251 @@ +package go; + +import android.util.Log; +import android.util.SparseArray; +import android.util.SparseIntArray; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +// Seq is a sequence of machine-dependent encoded values. +// Used by automatically generated language bindings to talk to Go. +public class Seq { + @SuppressWarnings("UnusedDeclaration") + private long memptr; // holds C-allocated pointer + + public Seq() { + ensure(64); + } + + // 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 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 native byte[] readByteArray(); + + 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 native void writeByteArray(byte[] v); + + public void writeRef(Ref ref) { + writeInt32(ref.refnum); + } + + public Ref readRef() { + int refnum = readInt32(); + return tracker.get(refnum); + } + + // Informs the Go ref tracker that Java is done with this ref. + static native void destroyRef(int refnum); + + // createRef creates a Ref to a Java object. + public static Ref createRef(Seq.Object o) { + 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); + + // recv returns the next request from Go for a Java call. + static native void recv(Seq in, Receive params); + + // recvRes sends the result of a Java call back to Go. + static native void recvRes(int handle, Seq out); + + static final class Receive { + int refnum; + int code; + int handle; + } + + protected void finalize() throws Throwable { + super.finalize(); + free(); + } + private native void free(); + + private static final ExecutorService receivePool = Executors.newCachedThreadPool(); + + // receive listens for callback requests from Go, invokes them on a thread + // pool and sends the responses. + public static void receive() { + Seq.Receive params = new Seq.Receive(); + while (true) { + final Seq in = new Seq(); + Seq.recv(in, params); + + final int code = params.code; + final int handle = params.handle; + final int refnum = params.refnum; + + if (code == -1) { + // Special signal from seq.FinalizeRef. + tracker.dec(refnum); + Seq out = new Seq(); + Seq.recvRes(handle, out); + continue; + } + + receivePool.execute(new Runnable() { + public void run() { + Ref r = tracker.get(refnum); + Seq out = new Seq(); + r.obj.call(code, in, out); + Seq.recvRes(handle, out); + } + }); + } + } + + // An Object is a Java object that matches a Go object. + // The implementation of the object may be in either Java or Go, + // with a proxy instance in the other language passing calls + // through to the other language. + // + // Don't implement an Object directly. Instead, look for the + // 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 + // forth across the language boundary. + // + // A Ref may represent either an instance of a Java Object subclass, + // or an instance of a Go object. The explicit allocation of a Ref + // is used to pin Go object instances when they are passed to Java. + // The Go Seq library maintains a reference to the instance in a map + // keyed by the Ref number. When the JVM calls finalize, we ask Go + // to clear the entry in the map. + public static final class Ref { + // ref < 0: Go object tracked by Java + // ref > 0: Java object tracked by Go + int refnum; + public Seq.Object obj; + + private Ref(int refnum, Seq.Object o) { + this.refnum = refnum; + this.obj = o; + tracker.inc(refnum); + } + + @Override + protected void finalize() throws Throwable { + tracker.dec(refnum); + super.finalize(); + } + } + + static final RefTracker tracker = new RefTracker(); + + static final class RefTracker { + // Next Java object reference number. + // + // Reference numbers are positive for Java objects, + // and start, arbitrarily at a different offset to Go + // to make debugging by reading Seq hex a little easier. + private int next = 42; // next Java object ref + + // TODO(crawshaw): We could cut down allocations for frequently + // sent Go objects by maintaining a map to weak references. This + // however, would require allocating two objects per reference + // instead of one. It also introduces weak references, the bane + // of any Java debugging session. + // + // When we have real code, examine the tradeoffs. + + // Number of active references to a Go object. refnum -> count + private SparseIntArray goObjs = new SparseIntArray(); + + // Java objects that have been passed to Go. refnum -> Ref + // The Ref obj field is non-null. + // This map pins Java objects so they don't get GCed while the + // only reference to them is held by Go code. + private SparseArray javaObjs = new SparseArray(); + + // inc increments the reference count to a Go object. + synchronized void inc(int refnum) { + if (refnum > 0) { + return; // we don't count java objects + } + int count = goObjs.get(refnum); + if (count == Integer.MAX_VALUE) { + throw new RuntimeException("refnum " + refnum + " overflow"); + } + goObjs.put(refnum, count+1); + } + + // dec decrements the reference count to a Go object. + // If the count reaches zero, the Go reference tracker is informed. + synchronized void dec(int refnum) { + if (refnum > 0) { + // Java objects are removed on request of Go. + javaObjs.remove(refnum); + return; + } + int count = goObjs.get(refnum); + if (count == 0) { + throw new RuntimeException("refnum " + refnum + " underflow"); + } + count--; + if (count <= 0) { + goObjs.delete(refnum); + Seq.destroyRef(refnum); + } else { + goObjs.put(refnum, count); + } + } + + synchronized Ref createRef(Seq.Object o) { + // TODO(crawshaw): use single Ref for null. + if (next == Integer.MAX_VALUE) { + throw new RuntimeException("createRef overflow for " + o); + } + int refnum = next++; + Ref ref = new Ref(refnum, o); + javaObjs.put(refnum, ref); + return ref; + } + + // get returns an existing Ref to either a Java or Go object. + // It may be the first time we have seen the Go object. + synchronized Ref get(int refnum) { + if (refnum > 0) { + Ref ref = javaObjs.get(refnum); + if (ref == null) { + throw new RuntimeException("unknown java Ref: "+refnum); + } + return ref; + } + return new Ref(refnum, null); + } + } +} diff --git a/bind/java/seq_android.c b/bind/java/seq_android.c new file mode 100644 index 0000000..56ac769 --- /dev/null +++ b/bind/java/seq_android.c @@ -0,0 +1,293 @@ +// 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 +#include +#include +#include +#include +#include +#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; + +// 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; +} mem; + +static mem *mem_resize(mem *m, uint32_t size) { + if (m == NULL) { + m = (mem*)malloc(sizeof(mem)); + if (m == NULL) { + LOG_FATAL("mem_resize malloc failed"); + } + m->off = 0; + m->len = 0; + m->buf = NULL; + } + m->buf = (uint8_t*)realloc((void*)m->buf, size); + if (m->buf == NULL) { + LOG_FATAL("mem_resize realloc failed, size=%d", size); + } + m->cap = 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) { + 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); + } + if (m->off+size > m->cap) { + m = mem_resize(m, 2*m->cap); + } + uint8_t *res = m->buf+m->off; + m->off += size; + m->len += size; + return res; +} + +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; +} + +void init_seq(void *javavm) { + JavaVM *vm = (JavaVM*)javavm; + JNIEnv *env; + if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) { + LOG_FATAL("bad vm env"); + } + + 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"); + + LOG_INFO("loaded go/Seq"); +} + +JNIEXPORT void JNICALL +Java_go_Seq_ensure(JNIEnv *env, jobject obj, jint size) { + mem *m = mem_get(env, obj); + if (m == NULL || size > m->cap - m->off) { + m = mem_resize(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) { + 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); + return (*env)->NewString(env, (jchar*)mem_read(env, obj, 2*size), size); +} + +#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_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); +} + +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); +} diff --git a/bind/java/seq_android.go b/bind/java/seq_android.go new file mode 100644 index 0000000..d51af77 --- /dev/null +++ b/bind/java/seq_android.go @@ -0,0 +1,169 @@ +// 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 + +//#cgo LDFLAGS: -llog +//#include +//#include +//#include +//#include "seq_android.h" +import "C" +import ( + "fmt" + "sync" + "unsafe" + + "code.google.com/p/go.mobile/bind/seq" +) + +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] + in := new(seq.Buffer) + if reqlen > 0 { + in.Data = (*[maxSliceLen]byte)(unsafe.Pointer(req))[:reqlen] + } + out := new(seq.Buffer) + fn(out, in) + 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)) +} + +type request struct { + ref *seq.Ref + handle int32 + code int + in *seq.Buffer +} + +var recv struct { + sync.Mutex + cond sync.Cond // signals req is not empty + req []request + next int32 // next handle value +} + +var res struct { + sync.Mutex + cond sync.Cond // signals a response is filled in + out map[int32]*seq.Buffer // handle -> output +} + +func init() { + recv.cond.L = &recv.Mutex + recv.next = 411 // arbitrary starting point distinct from Go and Java obj ref nums + + res.cond.L = &res.Mutex + res.out = make(map[int32]*seq.Buffer) +} + +// Init initializes the seq JNI logic. This would usually be done in +// JNI_OnLoad, but there can only be one per library and go.mobile/app +// needs it first. +func Init(javaVM unsafe.Pointer) { + C.init_seq(javaVM) +} + +func seqToBuf(bufptr **C.uint8_t, lenptr *C.size_t, buf *seq.Buffer) { + if false { + 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))) +} + +// Recv is called by Java in a loop and blocks until Go requests a callback +// be executed by the JVM. Then a request object is returned, along with a +// handle for the host to respond via RecvRes. +//export Recv +func Recv(in **C.uint8_t, inlen *C.size_t) (ref, code, handle C.int32_t) { + recv.Lock() + for len(recv.req) == 0 { + recv.cond.Wait() + } + req := recv.req[0] + recv.req = recv.req[1:] + seqToBuf(in, inlen, req.in) + recv.Unlock() + + return C.int32_t(req.ref.Num), C.int32_t(req.code), C.int32_t(req.handle) +} + +// RecvRes is called by JNI to return the result of a requested callback. +//export RecvRes +func RecvRes(handle C.int32_t, out *C.uint8_t, outlen C.size_t) { + outBuf := &seq.Buffer{ + Data: make([]byte, outlen), + } + copy(outBuf.Data, (*[maxSliceLen]byte)(unsafe.Pointer(out))[:outlen]) + + res.Lock() + res.out[int32(handle)] = outBuf + res.Unlock() + res.cond.Broadcast() +} + +// transact calls a method on a Java object instance. +// It blocks until the call is complete. +func transact(ref *seq.Ref, code int, in *seq.Buffer) *seq.Buffer { + recv.Lock() + if recv.next == 1<<31-1 { + panic("recv handle overflow") + } + handle := recv.next + recv.next++ + recv.req = append(recv.req, request{ + ref: ref, + code: code, + in: in, + handle: handle, + }) + recv.Unlock() + recv.cond.Signal() + + res.Lock() + for res.out[handle] == nil { + res.cond.Wait() + } + out := res.out[handle] + delete(res.out, handle) + res.Unlock() + + return out +} + +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 +} diff --git a/bind/java/seq_android.h b/bind/java/seq_android.h new file mode 100644 index 0000000..e56f50b --- /dev/null +++ b/bind/java/seq_android.h @@ -0,0 +1,5 @@ +// 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);