// 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.lang.ref.PhantomReference; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.IdentityHashMap; import java.util.HashSet; import java.util.Set; import java.util.logging.Logger; import go.Universe; // 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"); // 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 Object public static final Ref nullRef = new Ref(NULL_REFNUM, null); // The singleton GoRefQueue private static final GoRefQueue goRefQueue = new GoRefQueue(); 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); } init(); Universe.touch(); } private static native void init(); // Empty method to run class initializer public static void touch() {} private Seq() { } // ctx is an android.context.Context. static native void setContext(java.lang.Object ctx); public static void incRefnum(int refnum) { tracker.incRefnum(refnum); } // incRef increments the reference count of Java objects. // For proxies for Go objects, it calls into the Proxy method // incRefnum() to make sure the Go reference count is positive // even if the Proxy is garbage collected and its Ref is finalized. public static int incRef(Object o) { return tracker.inc(o); } public static int incGoObjectRef(GoObject o) { return o.incRefnum(); } // trackGoRef tracks a Go reference and decrements its refcount // when the given GoObject wrapper is garbage collected. // // 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. public static void trackGoRef(int refnum, GoObject obj) { if (refnum > 0) { throw new RuntimeException("trackGoRef called with Java refnum " + refnum); } goRefQueue.track(refnum, obj); } public static Ref getRef(int refnum) { return tracker.get(refnum); } // Increment the Go reference count before sending over a refnum. // The ref parameter is only used to make sure the referenced // object is not garbage collected before Go increments the // count. It's the equivalent of Go's runtime.KeepAlive. public static native void incGoRef(int refnum, GoObject ref); // Informs the Go ref tracker that Java is done with this refnum. static native void destroyRef(int refnum); // decRef is called from seq.FinalizeRef static void decRef(int refnum) { tracker.dec(refnum); } // A GoObject is a Java class implemented in Go. When a GoObject // is passed to Go, it is wrapped in a Go proxy, to make it behave // the same as passing a regular Java class. public interface GoObject { // Increment refcount and return the refnum of the proxy. // // The Go reference count need to be bumped while the // refnum is passed to Go, to avoid finalizing and // invalidating it before being translated on the Go side. int incRefnum(); } // A Proxy is a Java object that proxies a Go object. Proxies, unlike // GoObjects, are unwrapped to their Go counterpart when deserialized // in Go. public interface Proxy extends GoObject {} // A Ref represents an instance of a Java object passed back and forth // across the language boundary. public static final class Ref { public final int refnum; private int refcnt; // Track how many times sent to Go. public final Object obj; // The referenced Java obj. Ref(int refnum, Object o) { if (refnum < 0) { throw new RuntimeException("Ref instantiated with a Go refnum " + refnum); } this.refnum = refnum; this.refcnt = 0; this.obj = o; } void inc() { // Count how many times this ref's Java object is passed to Go. if (refcnt == Integer.MAX_VALUE) { throw new RuntimeException("refnum " + refnum + " overflow"); } refcnt++; } } static final RefTracker tracker = new RefTracker(); static final class RefTracker { private static final int REF_OFFSET = 42; // 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 final RefMap javaObjs = new RefMap(); // Java objects to refnum private final IdentityHashMap javaRefs = new IdentityHashMap<>(); // inc increments the reference count of a Java object when it // is sent to Go. inc returns the refnum for the object. synchronized int inc(Object o) { if (o == null) { return NULL_REFNUM; } if (o instanceof Proxy) { return ((Proxy)o).incRefnum(); } Integer refnumObj = javaRefs.get(o); if (refnumObj == null) { if (next == Integer.MAX_VALUE) { throw new RuntimeException("createRef overflow for " + o); } refnumObj = next++; javaRefs.put(o, refnumObj); } int refnum = refnumObj; Ref ref = javaObjs.get(refnum); if (ref == null) { ref = new Ref(refnum, o); javaObjs.put(refnum, ref); } ref.inc(); return refnum; } synchronized void incRefnum(int refnum) { Ref ref = javaObjs.get(refnum); if (ref == null) { throw new RuntimeException("referenced Java object is not found: refnum="+refnum); } ref.inc(); } // 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 == Seq.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); javaRefs.remove(obj.obj); } } // get returns an existing Ref to a Java object. synchronized Ref get(int refnum) { if (refnum < 0) { throw new RuntimeException("ref called with Go refnum " + refnum); } if (refnum == NULL_REFNUM) { return nullRef; } Ref ref = javaObjs.get(refnum); if (ref == null) { throw new RuntimeException("unknown java Ref: "+refnum); } return ref; } } // GoRefQueue is a queue of GoRefs that are no longer live. An internal thread // processes the queue and decrement the reference count on the Go side. static class GoRefQueue extends ReferenceQueue { // The set of tracked GoRefs. If we don't hold on to the GoRef instances, the Java GC // will not add them to the queue when their referents are reclaimed. private final Collection refs = Collections.synchronizedCollection(new HashSet()); void track(int refnum, GoObject obj) { refs.add(new GoRef(refnum, obj, this)); } GoRefQueue() { Thread daemon = new Thread(new Runnable() { @Override public void run() { while (true) { try { GoRef ref = (GoRef)remove(); refs.remove(ref); destroyRef(ref.refnum); ref.clear(); } catch (InterruptedException e) { // Ignore } } } }); daemon.setDaemon(true); daemon.setName("GoRefQueue Finalizer Thread"); daemon.start(); } } // A GoRef is a PhantomReference to a Java proxy for a Go object. // GoRefs are enqueued to the singleton GoRefQueue when no longer live, // so the corresponding reference count can be decremented. static class GoRef extends PhantomReference { final int refnum; GoRef(int refnum, GoObject obj, GoRefQueue q) { super(obj, q); if (refnum > 0) { throw new RuntimeException("GoRef instantiated with a Java refnum " + refnum); } this.refnum = refnum; } } // RefMap is a mapping of integers to Ref objects. // // The integers can be sparse. In Go this would be a map[int]*Ref. 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; live++; } 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; } } }