// 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 go; import java.util.Arrays; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.logging.Logger; // Seq is a sequence of machine-dependent encoded values. // Used by automatically generated language bindings to talk to Go. public class Seq { private static Logger log = Logger.getLogger("GoSeq"); static { // Look for the shim class auto-generated by gomobile bind. // Its only purpose is to call System.loadLibrary. try { Class loadJNI = Class.forName("go.LoadJNI"); setContext(loadJNI.getDeclaredField("ctx").get(null)); } catch (ClassNotFoundException e) { // Ignore, assume the user will load JNI for it. log.warning("LoadJNI class not found"); } catch (NoSuchFieldException e) { log.severe("LoadJNI class missing field: " + e); } catch (IllegalAccessException e) { log.severe("LoadJNI class bad field: " + e); } initSeq(); new Thread("GoSeq") { public void run() { Seq.receive(); } }.start(); } @SuppressWarnings("UnusedDeclaration") private long memptr; // holds C-allocated pointer public Seq() { ensure(64); } // 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) { tracker.inc(ref); writeInt32(ref.refnum); } public Ref readRef() { int refnum = readInt32(); 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); // 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 { // refnum < 0: Go object tracked by Java // refnum > 0: Java object tracked by Go 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. private Ref(int refnum, Seq.Object o) { this.refnum = refnum; this.refcnt = 0; this.obj = o; } @Override protected void finalize() throws Throwable { if (refnum < 0) { // Go object: signal Go to decrement the reference count. Seq.destroyRef(refnum); } super.finalize(); } } static final RefTracker tracker = new RefTracker(); static final class RefTracker { private static final int REF_OFFSET = 42; // use single Ref for null Seq.Object private static final Ref nullRef = new Ref(REF_OFFSET - 1, null); // 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 = REF_OFFSET; // next Java object ref // 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 RefMap javaObjs = new RefMap(); // inc increments the reference count of a Java object when it // is sent to Go. synchronized void inc(Ref ref) { int refnum = ref.refnum; if (refnum <= 0) { // We don't keep track of the Go object. return; } if (refnum == nullRef.refnum) { return; } // Count how many times this ref's Java object is passed to Go. if (ref.refcnt == Integer.MAX_VALUE) { throw new RuntimeException("refnum " + refnum + " overflow"); } ref.refcnt++; Ref obj = javaObjs.get(refnum); if (obj == null) { javaObjs.put(refnum, ref); } } // dec decrements the reference count of a Java object when // Go signals a corresponding proxy object is finalized. // If the count reaches zero, the Java object is removed // from the javaObjs map. synchronized void dec(int refnum) { if (refnum <= 0) { // We don't keep track of the Go object. // This must not happen. log.severe("dec request for Go object "+ refnum); return; } if (refnum == nullRef.refnum) { return; } // Java objects are removed on request of Go. Ref obj = javaObjs.get(refnum); if (obj == null) { throw new RuntimeException("referenced Java object is not found: refnum="+refnum); } obj.refcnt--; if (obj.refcnt <= 0) { javaObjs.remove(refnum); } } synchronized Ref createRef(Seq.Object o) { if (o == null) { return nullRef; } 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. // // 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. synchronized Ref get(int refnum) { if (refnum > 0) { if (refnum == nullRef.refnum) { return nullRef; } Ref ref = javaObjs.get(refnum); if (ref == null) { throw new RuntimeException("unknown java Ref: "+refnum); } return ref; } else { // Go object. return new Ref(refnum, null); } } } // RefMap is a mapping of integers to Ref objects. // // The integers can be sparse. In Go this would be a map[int]*Ref. private static final class RefMap { private int next = 0; private int live = 0; private int[] keys = new int[16]; private Ref[] objs = new Ref[16]; RefMap() {} Ref get(int key) { int i = Arrays.binarySearch(keys, 0, next, key); if (i >= 0) { return objs[i]; } return null; } void remove(int key) { int i = Arrays.binarySearch(keys, 0, next, key); if (i >= 0) { if (objs[i] != null) { objs[i] = null; live--; } } } void put(int key, Ref obj) { if (obj == null) { throw new RuntimeException("put a null ref (with key "+key+")"); } int i = Arrays.binarySearch(keys, 0, next, key); if (i >= 0) { if (objs[i] == null) { objs[i] = obj; } if (objs[i] != obj) { throw new RuntimeException("replacing an existing ref (with key "+key+")"); } return; } if (next >= keys.length) { grow(); i = Arrays.binarySearch(keys, 0, next, key); } i = ~i; if (i < next) { // Insert, shift everything afterwards down. System.arraycopy(keys, i, keys, i+1, next-i); System.arraycopy(objs, i, objs, i+1, next-i); } keys[i] = key; objs[i] = obj; live++; next++; } private void grow() { // Compact and (if necessary) grow backing store. int[] newKeys; Ref[] newObjs; int len = 2*roundPow2(live); if (len > keys.length) { newKeys = new int[keys.length*2]; newObjs = new Ref[objs.length*2]; } else { newKeys = keys; newObjs = objs; } int j = 0; for (int i = 0; i < keys.length; i++) { if (objs[i] != null) { newKeys[j] = keys[i]; newObjs[j] = objs[i]; j++; } } for (int i = j; i < newKeys.length; i++) { newKeys[i] = 0; newObjs[i] = null; } keys = newKeys; objs = newObjs; next = j; if (live != next) { throw new RuntimeException("bad state: live="+live+", next="+next); } } private static int roundPow2(int x) { int p = 1; while (p < x) { p *= 2; } return p; } } }