blob: b421a9302903fbbd8cb6b25d6bb0d791d9495ef5 [file] [log] [blame]
// 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.IdentityHashMap;
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);
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();
}
public static Ref getRef(int refnum) {
return tracker.get(refnum);
}
// Increment the Go reference count before sending over a refnum.
public static native void incGoRef(int refnum);
// Informs the Go ref tracker that Java is done with this ref.
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 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,
// 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
public final int refnum;
private int refcnt; // for Java obj: track how many times sent to Go.
public final Object obj; // for Java obj: pointers to the Java obj.
Ref(int refnum, 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();
}
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<Object, Integer> 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 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 == NULL_REFNUM) {
return nullRef;
} else if (refnum > 0) {
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.
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;
}
}
}