Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion bind/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,9 @@ func (g *Generator) cgoType(t types.Type) string {
if isBytesSlice(t) {
return "nbyteslice"
}
if _, ok := refSliceElem(t); ok {
return "nrefnumslice"
}
g.errorf("unsupported slice type: %s", t)
case *types.Pointer:
if _, ok := types.Unalias(t.Elem()).(*types.Named); ok {
Expand Down Expand Up @@ -497,7 +500,13 @@ func (g *Generator) isSupported(t types.Type) bool {
}
return false
case *types.Slice:
return isBytesSlice(t)
if isBytesSlice(t) {
return true
}
if n, ok := refSliceElem(t); ok {
return g.validPkg(n.Obj().Pkg())
}
return false
case *types.Pointer:
switch t := types.Unalias(t.Elem()).(type) {
case *types.Named:
Expand Down
52 changes: 52 additions & 0 deletions bind/gengo.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ func (g *goGen) genWrite(toVar, fromVar string, t types.Type, mode varMode) {
g.Printf("%s := fromSlice(%s, %v)\n", toVar, fromVar, mode == modeRetained)
return
}
if _, ok := refSliceElem(t); ok {
g.genToRefNumSlice(toVar, fromVar)
return
}
g.errorf("unsupported type: %s", t)
case *types.Pointer:
// TODO(crawshaw): test *int
Expand Down Expand Up @@ -147,6 +151,45 @@ func (g *goGen) genToRefNum(toVar, fromVar string) {
g.Printf("}\n")
}

// genToRefNumSlice generates Go code for converting a slice of pointers to
// bound types into a slice of refnums, passed on as C memory owned by the
// receiving foreign code.
func (g *goGen) genToRefNumSlice(toVar, fromVar string) {
g.Printf("var %s_refs []int32\n", toVar)
g.Printf("if %s != nil {\n", fromVar)
g.Printf(" %s_refs = make([]int32, len(%s))\n", toVar, fromVar)
g.Printf(" for i, e := range %s {\n", fromVar)
g.Printf(" %s_refs[i] = _seq.NullRefNum\n", toVar)
g.Printf(" if e != nil {\n")
g.Printf(" %s_refs[i] = _seq.ToRefNum(e)\n", toVar)
g.Printf(" }\n")
g.Printf(" }\n")
g.Printf("}\n")
g.Printf("%s := fromRefSlice(%s_refs)\n", toVar, toVar)
}

// genFromRefNumSlice generates Go code for converting a slice of refnums,
// held in C memory owned by this side, into a slice of pointers to the bound
// type n.
func (g *goGen) genFromRefNumSlice(toVar, fromVar string, n *types.Named) {
o := n.Obj()
oPkg := o.Pkg()
if !g.validPkg(oPkg) {
g.errorf("type %s is defined in %s, which is not bound", n, oPkg)
return
}
g.Printf("var %s []*%s%s\n", toVar, g.pkgName(oPkg), o.Name())
g.Printf("if %s_refs := toRefSlice(%s); %s_refs != nil {\n", toVar, fromVar, toVar)
g.Printf(" %s = make([]*%s%s, len(%s_refs))\n", toVar, g.pkgName(oPkg), o.Name(), toVar)
g.Printf(" for i, refnum := range %s_refs {\n", toVar)
g.Printf(" // Must be a Go object\n")
g.Printf(" if ref := _seq.FromRefNum(refnum); ref != nil {\n")
g.Printf(" %s[i] = ref.Get().(*%s%s)\n", toVar, g.pkgName(oPkg), o.Name())
g.Printf(" }\n")
g.Printf(" }\n")
g.Printf("}\n")
}

func (g *goGen) genFuncSignature(o *types.Func, objName string) {
g.Printf("//export proxy%s_%s_%s\n", g.pkgPrefix, objName, o.Name())
g.Printf("func proxy%s_%s_%s(", g.pkgPrefix, objName, o.Name())
Expand Down Expand Up @@ -393,6 +436,10 @@ func (g *goGen) genRead(toVar, fromVar string, typ types.Type, mode varMode) {
g.Printf("%s := toSlice(%s, %v)\n", toVar, fromVar, mode == modeRetained)
return
}
if n, ok := refSliceElem(t); ok {
g.genFromRefNumSlice(toVar, fromVar, n)
return
}
g.errorf("unsupported type: %s", t)
case *types.Pointer:
switch u := types.Unalias(t.Elem()).(type) {
Expand Down Expand Up @@ -484,6 +531,11 @@ func (g *goGen) typeString(typ types.Type) string {
default:
g.errorf("not yet supported, pointer type %s / %T", t, t)
}
case *types.Slice:
if _, ok := refSliceElem(t); ok {
return fmt.Sprintf("[]%s", g.typeString(t.Elem()))
}
return types.TypeString(typ, types.RelativeTo(pkg))
default:
return types.TypeString(typ, types.RelativeTo(pkg))
}
Expand Down
29 changes: 27 additions & 2 deletions bind/genjava.go
Original file line number Diff line number Diff line change
Expand Up @@ -637,8 +637,13 @@ func (g *JavaGen) jniType(T types.Type) string {
return "TODO"
}
case *types.Slice:
return "jbyteArray"

if isBytesSlice(T) {
return "jbyteArray"
}
if _, ok := refSliceElem(T); ok {
return "jobjectArray"
}
g.errorf("unsupported slice type: %s", T)
case *types.Pointer:
if _, ok := types.Unalias(T.Elem()).(*types.Named); ok {
return g.jniType(T.Elem())
Expand Down Expand Up @@ -907,6 +912,10 @@ func (g *JavaGen) genJavaToC(varName string, t types.Type, mode varMode) {
g.Printf("nbyteslice _%s = go_seq_from_java_bytearray(env, %s, %d);\n", varName, varName, toCFlag(mode == modeRetained))
return
}
if _, ok := refSliceElem(t); ok {
g.Printf("nrefnumslice _%s = go_seq_from_java_objectarray(env, %s);\n", varName, varName)
return
}
g.errorf("unsupported type: %s", t)
case *types.Named:
switch u := t.Underlying().(type) {
Expand Down Expand Up @@ -938,6 +947,10 @@ func (g *JavaGen) genCToJava(toName, fromName string, t types.Type, mode varMode
g.Printf("jbyteArray %s = go_seq_to_java_bytearray(env, %s, %d);\n", toName, fromName, toCFlag(mode == modeRetained))
return
}
if n, ok := refSliceElem(t); ok {
g.genFromRefnumArray(toName, fromName, t, n.Obj())
return
}
g.errorf("unsupported type: %s", t)
case *types.Pointer:
// TODO(crawshaw): test *int
Expand Down Expand Up @@ -977,6 +990,18 @@ func (g *JavaGen) genFromRefnum(toName, fromName string, t types.Type, o *types.
g.Printf(");\n")
}

// genFromRefnumArray generates the conversion of a slice of reference numbers
// to a Java array of proxies for the type named by o.
func (g *JavaGen) genFromRefnumArray(toName, fromName string, t types.Type, o *types.TypeName) {
oPkg := o.Pkg()
if !g.validPkg(oPkg) {
g.errorf("type %s is defined in package %s, which is not bound", t, oPkg)
return
}
p := pkgPrefix(oPkg)
g.Printf("jobjectArray %s = go_seq_to_java_objectarray(env, %s, proxy_class_%s_%s, proxy_class_%s_%s_cons);\n", toName, fromName, p, o.Name(), p, o.Name())
}

func (g *JavaGen) gobindOpts() string {
opts := []string{"-lang=java"}
if g.JavaPkg != "" {
Expand Down
20 changes: 19 additions & 1 deletion bind/genobjc.go
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,10 @@ func (g *ObjcGen) genWrite(varName string, t types.Type, mode varMode) {
g.Printf("nbyteslice _%s = go_seq_from_objc_bytearray(%s, %d);\n", varName, varName, toCFlag(mode == modeRetained))
return
}
if _, ok := refSliceElem(t); ok {
g.Printf("nrefnumslice _%s = go_seq_from_objc_objectarray(%s);\n", varName, varName)
return
}
g.errorf("unsupported type: %s", t)
case *types.Named:
switch u := t.Underlying().(type) {
Expand Down Expand Up @@ -739,6 +743,13 @@ func (g *ObjcGen) genRefRead(toName, fromName string, t types.Type) {
g.Printf("}\n")
}

// genRefReadArray generates the conversion of a slice of reference numbers to
// an NSArray of proxies for the type t.
func (g *ObjcGen) genRefReadArray(toName, fromName string, t types.Type) {
ptype := g.refTypeBase(t)
g.Printf("NSArray<%s*>* %s = go_seq_to_objc_objectarray(%s, [%s class]);\n", ptype, toName, fromName, ptype)
}

func (g *ObjcGen) genRead(toName, fromName string, t types.Type, mode varMode) {
switch t := types.Unalias(t).(type) {
case *types.Basic:
Expand All @@ -755,6 +766,10 @@ func (g *ObjcGen) genRead(toName, fromName string, t types.Type, mode varMode) {
g.Printf("NSData *%s = go_seq_to_objc_bytearray(%s, %d);\n", toName, fromName, toCFlag(mode == modeRetained))
return
}
if n, ok := refSliceElem(t); ok {
g.genRefReadArray(toName, fromName, types.NewPointer(n))
return
}
g.errorf("unsupported type: %s", t)
case *types.Pointer:
switch t := types.Unalias(t.Elem()).(type) {
Expand Down Expand Up @@ -1331,7 +1346,10 @@ func (g *ObjcGen) objcType(typ types.Type) string {
if elem == "byte" {
return "NSData* _Nullable"
}
// TODO(hyangah): support other slice types: NSArray or CFArrayRef.
if n, ok := refSliceElem(typ); ok {
return "NSArray<" + g.refTypeBase(types.NewPointer(n)) + "*>* _Nullable"
}
// TODO(hyangah): support other slice types: CFArrayRef.
// Investigate the performance implication.
g.errorf("unsupported type: %s", typ)
return "TODO"
Expand Down
48 changes: 48 additions & 0 deletions bind/java/SeqTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,54 @@ public NullTest null_() {
assertTrue(nullArger.callWithNull(null));
}

public void testStructSlice() {
Node a = Testpkg.newNode("A");
Node[] nodes = Testpkg.repeatNode(a, 3);
assertEquals("want three nodes", 3, nodes.length);
for (Node n : nodes) {
assertEquals("want the node we passed in", a, n);
}
assertEquals("want the names of the nodes", "A,A,A", Testpkg.nodeNames(nodes));
assertEquals("want no names for an empty slice", "", Testpkg.nodeNames(new Node[0]));
assertEquals("want no names for a nil slice", "", Testpkg.nodeNames(null));
assertEquals("want a nil element to be reported", "<nil>", Testpkg.nodeNames(new Node[]{null}));
assertEquals("want an empty slice back", 0, Testpkg.repeatNode(a, 0).length);

// Slices long enough to overflow the JNI local reference table if
// references were leaked.
Node[] many = Testpkg.repeatNode(a, 1000);
assertEquals("want a thousand nodes", 1000, many.length);
assertEquals("want a thousand names", 2*many.length - 1, Testpkg.nodeNames(many).length());
}

public void testStructSliceCallback() {
String names = Testpkg.callNodeSlicer(new NodeSlicer() {
@Override public Node[] slice(Node[] nodes) {
Node[] res = Arrays.copyOf(nodes, nodes.length + 1);
res[nodes.length] = Testpkg.newNode("B");
return res;
}
}, Testpkg.newNode("A"));
assertEquals("want the nodes to survive the round trip", "A,A,A,B", names);

Node a = Testpkg.newNode("A");
assertEquals("want a null element to be passed on", "<nil>,A", Testpkg.callNodeSlicer(new NodeSlicer() {
@Override public Node[] slice(Node[] nodes) {
return new Node[]{null, nodes[0]};
}
}, a));
assertEquals("want an empty array to be passed on", "", Testpkg.callNodeSlicer(new NodeSlicer() {
@Override public Node[] slice(Node[] nodes) {
return new Node[0];
}
}, a));
assertEquals("want a null array to be passed on", "", Testpkg.callNodeSlicer(new NodeSlicer() {
@Override public Node[] slice(Node[] nodes) {
return null;
}
}, a));
}

public void testPassByteArray() {
Testpkg.passByteArray(new B() {
@Override public void b(byte[] b) {
Expand Down
45 changes: 45 additions & 0 deletions bind/java/seq_android.c.support
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,27 @@ jbyteArray go_seq_to_java_bytearray(JNIEnv *env, nbyteslice s, int copy) {
return res;
}

// go_seq_to_java_objectarray converts a slice of refnums to an array of
// proxies of the class proxy_class. The refnums are freed.
jobjectArray go_seq_to_java_objectarray(JNIEnv *env, nrefnumslice arr, jclass proxy_class, jmethodID proxy_cons) {
if (arr.ptr == NULL) {
return NULL;
}
jobjectArray res = (*env)->NewObjectArray(env, arr.len, proxy_class, NULL);
if (res == NULL) {
LOG_FATAL("NewObjectArray failed");
}
for (jsize i = 0; i < arr.len; i++) {
jobject o = go_seq_from_refnum(env, arr.ptr[i], proxy_class, proxy_cons);
(*env)->SetObjectArrayElement(env, res, i, o);
// The array holds the only reference we need; drop the local one to
// avoid overflowing the local reference table for large arrays.
(*env)->DeleteLocalRef(env, o);
}
free(arr.ptr);
return res;
}

#define surr1 0xd800
#define surr2 0xdc00
#define surr3 0xe000
Expand Down Expand Up @@ -224,6 +245,30 @@ nbyteslice go_seq_from_java_bytearray(JNIEnv *env, jbyteArray arr, int copy) {
return res;
}

// go_seq_from_java_objectarray converts an array of proxies to a slice of
// refnums. The returned slice is freed by the Go side.
nrefnumslice go_seq_from_java_objectarray(JNIEnv *env, jobjectArray arr) {
struct nrefnumslice res = {NULL, 0};
if (arr == NULL) {
return res;
}
jsize len = (*env)->GetArrayLength(env, arr);
// Allocate at least one element so that an empty array is distinguishable
// from a null array.
int32_t *refnums = (int32_t *)malloc(len == 0 ? 1 : len * sizeof(int32_t));
if (refnums == NULL) {
LOG_FATAL("malloc failed");
}
for (jsize i = 0; i < len; i++) {
jobject o = (*env)->GetObjectArrayElement(env, arr, i);
refnums[i] = go_seq_to_refnum(env, o);
(*env)->DeleteLocalRef(env, o);
}
res.ptr = refnums;
res.len = len;
return res;
}

int32_t go_seq_to_refnum_go(JNIEnv *env, jobject o) {
if (o == NULL) {
return NULL_REFNUM;
Expand Down
33 changes: 33 additions & 0 deletions bind/java/seq_android.go.support
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,39 @@ func fromSlice(s []byte, cpy bool) C.nbyteslice {
return C.nbyteslice{ptr: unsafe.Pointer(ptr), len: n}
}

// fromRefSlice converts a slice of refnums to a nrefnumslice. The returned
// nrefnumslice points to C memory freed by go_seq_to_java_objectarray. A nil
// slice is converted to a nil pointer, an empty slice is not.
func fromRefSlice(refs []int32) C.nrefnumslice {
if refs == nil {
return C.nrefnumslice{}
}
// Allocate at least one element so that an empty slice is distinguishable
// from a nil slice.
sz := C.size_t(len(refs)) * C.size_t(unsafe.Sizeof(C.int32_t(0)))
if sz == 0 {
sz = 1
}
ptr := (*C.int32_t)(C.malloc(sz))
if ptr == nil {
panic("fromRefSlice: malloc failed")
}
copy(unsafe.Slice((*int32)(unsafe.Pointer(ptr)), len(refs)), refs)
return C.nrefnumslice{ptr: ptr, len: C.jsize(len(refs))}
}

// toRefSlice takes a nrefnumslice created by go_seq_from_java_objectarray
// and returns its refnums as a Go slice. The C memory is freed.
func toRefSlice(s C.nrefnumslice) []int32 {
if s.ptr == nil {
return nil
}
refs := make([]int32, s.len)
copy(refs, unsafe.Slice((*int32)(unsafe.Pointer(s.ptr)), s.len))
C.free(unsafe.Pointer(s.ptr))
return refs
}

// 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.
Expand Down
9 changes: 9 additions & 0 deletions bind/java/seq_android.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ typedef struct nbyteslice {
void *ptr;
jsize len;
} nbyteslice;
// nrefnumslice is a slice of reference numbers, used to pass slices of
// bound types across the language barrier. The memory pointed to by ptr is
// always owned by the receiver, which frees it after conversion.
typedef struct nrefnumslice {
int32_t *ptr;
jsize len;
} nrefnumslice;
typedef jlong nint;

extern void go_seq_dec_ref(int32_t ref);
Expand All @@ -47,6 +54,8 @@ extern jobject go_seq_get_exception(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 jobjectArray go_seq_to_java_objectarray(JNIEnv *env, nrefnumslice arr, jclass proxy_class, jmethodID proxy_cons);
extern nrefnumslice go_seq_from_java_objectarray(JNIEnv *env, jobjectArray arr);
extern void go_seq_release_byte_array(JNIEnv *env, jbyteArray arr, jbyte* ptr);

extern jstring go_seq_to_java_string(JNIEnv *env, nstring str);
Expand Down
Loading