Converting a Go string to a string suitable use a specialized function, UTF16Encode, that can encode the string directly to a malloc'ed buffer. That way, only two copies are made when strings are passed from Go to Java; once for UTF-8 to UTF-16 encoding and once for the creation of the Java String. This CL implements the same optimization in the other direction, with a UTF-16 to UTF-8 decoder implemented in C. Unfortunately, while calling into a Go decoder also saves the extra copy, the Cgo overhead makes the calls much slower for short strings. To alleviate the risk of introducing decoding bugs, I've added the tests from the encoding/utf16 package to SeqTest. As a sideeffect, both Java and ObjC now always copy strings, regardless of the argument mode. The cpy argument can therefore be removed from the string conversion functions. Furthermore, the modeRetained and modeReturned modes can be collapsed into just one. While we're here, delete a leftover function from seq/strings.go that wasn't removed when the old seq buffers went away. Benchmarks, as compared with benchstat over 5 runs: name old time/op new time/op delta JavaStringShort 11.4µs ±13% 11.6µs ± 4% ~ (p=0.859 n=10+5) JavaStringShortDirect 19.5µs ± 9% 20.3µs ± 2% +3.68% (p=0.019 n=9+5) JavaStringLong 103µs ± 8% 24µs ± 4% -77.13% (p=0.001 n=9+5) JavaStringLongDirect 113µs ± 9% 32µs ± 7% -71.63% (p=0.001 n=9+5) JavaStringShortUnicode 11.1µs ±16% 10.7µs ± 5% ~ (p=0.190 n=9+5) JavaStringShortUnicodeDirect 19.6µs ± 7% 20.2µs ± 1% +2.78% (p=0.029 n=9+5) JavaStringLongUnicode 97.1µs ± 9% 28.0µs ± 5% -71.17% (p=0.001 n=9+5) JavaStringLongUnicodeDirect 105µs ±10% 34µs ± 5% -67.23% (p=0.002 n=8+5) JavaStringRetShort 14.2µs ± 2% 13.9µs ± 1% -2.15% (p=0.006 n=8+5) JavaStringRetShortDirect 20.8µs ± 2% 20.4µs ± 2% ~ (p=0.065 n=8+5) JavaStringRetLong 42.2µs ± 9% 42.4µs ± 3% ~ (p=0.190 n=9+5) JavaStringRetLongDirect 51.2µs ±21% 50.8µs ± 8% ~ (p=0.518 n=9+5) GoStringShort 23.4µs ± 7% 22.5µs ± 3% -3.55% (p=0.019 n=9+5) GoStringLong 51.9µs ± 9% 53.1µs ± 3% ~ (p=0.240 n=9+5) GoStringShortUnicode 24.2µs ± 6% 22.8µs ± 1% -5.54% (p=0.002 n=9+5) GoStringLongUnicode 58.6µs ± 8% 57.6µs ± 3% ~ (p=0.518 n=9+5) GoStringRetShort 27.6µs ± 1% 23.2µs ± 2% -15.87% (p=0.003 n=7+5) GoStringRetLong 129µs ±12% 33µs ± 2% -74.03% (p=0.001 n=10+5) Change-Id: Icb9481981493ffca8defed9fb80a9433d6048937 Reviewed-on: https://go-review.googlesource.com/20250 Reviewed-by: David Crawshaw <crawshaw@golang.org>
479 lines
12 KiB
Go
479 lines
12 KiB
Go
// 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 bind
|
|
|
|
import (
|
|
"fmt"
|
|
"go/types"
|
|
"strings"
|
|
)
|
|
|
|
type goGen struct {
|
|
*generator
|
|
}
|
|
|
|
const (
|
|
goPreamble = `// Package gomobile_bind is an autogenerated binder stub for package %[1]s.
|
|
// gobind -lang=go %[2]s
|
|
//
|
|
// File is generated by gobind. Do not edit.
|
|
package gomobile_bind
|
|
|
|
/*
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include "seq.h"
|
|
#include "%[1]s.h"
|
|
|
|
*/
|
|
import "C"
|
|
|
|
import (
|
|
_seq "golang.org/x/mobile/bind/seq"
|
|
%[2]q
|
|
)
|
|
|
|
// suppress the error if seq ends up unused
|
|
var _ = _seq.FromRefNum
|
|
|
|
`
|
|
)
|
|
|
|
func (g *goGen) genFuncBody(o *types.Func, selectorLHS string) {
|
|
sig := o.Type().(*types.Signature)
|
|
params := sig.Params()
|
|
for i := 0; i < params.Len(); i++ {
|
|
p := params.At(i)
|
|
pn := "param_" + paramName(params, i)
|
|
g.genRead("_"+pn, pn, p.Type(), modeTransient)
|
|
}
|
|
|
|
res := sig.Results()
|
|
if res.Len() > 2 || res.Len() == 2 && !isErrorType(res.At(1).Type()) {
|
|
g.errorf("functions and methods must return either zero or one values, and optionally an error")
|
|
return
|
|
}
|
|
if res.Len() > 0 {
|
|
for i := 0; i < res.Len(); i++ {
|
|
if i > 0 {
|
|
g.Printf(", ")
|
|
}
|
|
g.Printf("res_%d", i)
|
|
}
|
|
g.Printf(" := ")
|
|
}
|
|
|
|
g.Printf("%s.%s(", selectorLHS, o.Name())
|
|
for i := 0; i < params.Len(); i++ {
|
|
if i > 0 {
|
|
g.Printf(", ")
|
|
}
|
|
g.Printf("_param_%s", paramName(params, i))
|
|
}
|
|
g.Printf(")\n")
|
|
|
|
for i := 0; i < res.Len(); i++ {
|
|
pn := fmt.Sprintf("res_%d", i)
|
|
g.genWrite("_"+pn, pn, res.At(i).Type(), modeRetained)
|
|
}
|
|
if res.Len() > 0 {
|
|
g.Printf("return ")
|
|
for i := 0; i < res.Len(); i++ {
|
|
if i > 0 {
|
|
g.Printf(", ")
|
|
}
|
|
g.Printf("_res_%d", i)
|
|
}
|
|
g.Printf("\n")
|
|
}
|
|
}
|
|
|
|
func (g *goGen) genWrite(toVar, fromVar string, t types.Type, mode varMode) {
|
|
if isErrorType(t) {
|
|
g.Printf("var %s_str string\n", toVar)
|
|
g.Printf("if %s == nil {\n", fromVar)
|
|
g.Printf(" %s_str = \"\"\n", toVar)
|
|
g.Printf("} else {\n")
|
|
g.Printf(" %s_str = %s.Error()\n", toVar, fromVar)
|
|
g.Printf("}\n")
|
|
g.genWrite(toVar, toVar+"_str", types.Typ[types.String], mode)
|
|
return
|
|
}
|
|
switch t := t.(type) {
|
|
case *types.Basic:
|
|
switch t.Kind() {
|
|
case types.String:
|
|
g.Printf("%s := encodeString(%s)\n", toVar, fromVar)
|
|
case types.Bool:
|
|
g.Printf("var %s C.%s = 0\n", toVar, g.cgoType(t))
|
|
g.Printf("if %s { %s = 1 }\n", fromVar, toVar)
|
|
default:
|
|
g.Printf("%s := C.%s(%s)\n", toVar, g.cgoType(t), fromVar)
|
|
}
|
|
case *types.Slice:
|
|
switch e := t.Elem().(type) {
|
|
case *types.Basic:
|
|
switch e.Kind() {
|
|
case types.Uint8: // Byte.
|
|
g.Printf("%s := fromSlice(%s, %v)\n", toVar, fromVar, mode == modeRetained)
|
|
default:
|
|
g.errorf("unsupported type: %s", t)
|
|
}
|
|
default:
|
|
g.errorf("unsupported type: %s", t)
|
|
}
|
|
case *types.Pointer:
|
|
// TODO(crawshaw): test *int
|
|
// TODO(crawshaw): test **Generator
|
|
switch t := t.Elem().(type) {
|
|
case *types.Named:
|
|
obj := t.Obj()
|
|
if obj.Pkg() != g.pkg {
|
|
g.errorf("type %s not defined in %s", t, g.pkg)
|
|
return
|
|
}
|
|
g.genToRefNum(toVar, fromVar)
|
|
default:
|
|
g.errorf("unsupported type %s", t)
|
|
}
|
|
case *types.Named:
|
|
switch u := t.Underlying().(type) {
|
|
case *types.Interface, *types.Pointer:
|
|
g.genToRefNum(toVar, fromVar)
|
|
default:
|
|
g.errorf("unsupported, direct named type %s: %s", t, u)
|
|
}
|
|
default:
|
|
g.errorf("unsupported type %s", t)
|
|
}
|
|
}
|
|
|
|
// genToRefNum generates Go code for converting a variable to its refnum.
|
|
// Note that the nil-check cannot be lifted into seq.ToRefNum, because a nil
|
|
// struct pointer does not convert to a nil interface.
|
|
func (g *goGen) genToRefNum(toVar, fromVar string) {
|
|
g.Printf("var %s C.int32_t = _seq.NullRefNum\n", toVar)
|
|
g.Printf("if %s != nil {\n", fromVar)
|
|
g.Printf(" %s = C.int32_t(_seq.ToRefNum(%s))\n", toVar, fromVar)
|
|
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())
|
|
if objName != "" {
|
|
g.Printf("refnum C.int32_t")
|
|
}
|
|
sig := o.Type().(*types.Signature)
|
|
params := sig.Params()
|
|
for i := 0; i < params.Len(); i++ {
|
|
if objName != "" || i > 0 {
|
|
g.Printf(", ")
|
|
}
|
|
p := params.At(i)
|
|
g.Printf("param_%s C.%s", paramName(params, i), g.cgoType(p.Type()))
|
|
}
|
|
g.Printf(") ")
|
|
res := sig.Results()
|
|
if res.Len() > 0 {
|
|
g.Printf("(")
|
|
for i := 0; i < res.Len(); i++ {
|
|
if i > 0 {
|
|
g.Printf(", ")
|
|
}
|
|
g.Printf("C.%s", g.cgoType(res.At(i).Type()))
|
|
}
|
|
g.Printf(") ")
|
|
}
|
|
g.Printf("{\n")
|
|
}
|
|
|
|
func (g *goGen) genFunc(o *types.Func) {
|
|
g.genFuncSignature(o, "")
|
|
g.Indent()
|
|
g.genFuncBody(o, g.pkg.Name())
|
|
g.Outdent()
|
|
g.Printf("}\n\n")
|
|
}
|
|
|
|
func (g *goGen) genStruct(obj *types.TypeName, T *types.Struct) {
|
|
fields := exportedFields(T)
|
|
methods := exportedMethodSet(types.NewPointer(obj.Type()))
|
|
|
|
g.Printf("type proxy%s _seq.Ref\n\n", obj.Name())
|
|
|
|
for _, f := range fields {
|
|
g.Printf("//export proxy%s_%s_%s_Set\n", g.pkgPrefix, obj.Name(), f.Name())
|
|
g.Printf("func proxy%s_%s_%s_Set(refnum C.int32_t, v C.%s) {\n", g.pkgPrefix, obj.Name(), f.Name(), g.cgoType(f.Type()))
|
|
g.Indent()
|
|
g.Printf("ref := _seq.FromRefNum(int32(refnum))\n")
|
|
g.genRead("_v", "v", f.Type(), modeRetained)
|
|
g.Printf("ref.Get().(*%s.%s).%s = _v\n", g.pkg.Name(), obj.Name(), f.Name())
|
|
g.Outdent()
|
|
g.Printf("}\n\n")
|
|
|
|
g.Printf("//export proxy%s_%s_%s_Get\n", g.pkgPrefix, obj.Name(), f.Name())
|
|
g.Printf("func proxy%s_%s_%s_Get(refnum C.int32_t) C.%s {\n", g.pkgPrefix, obj.Name(), f.Name(), g.cgoType(f.Type()))
|
|
g.Indent()
|
|
g.Printf("ref := _seq.FromRefNum(int32(refnum))\n")
|
|
g.Printf("v := ref.Get().(*%s.%s).%s\n", g.pkg.Name(), obj.Name(), f.Name())
|
|
g.genWrite("_v", "v", f.Type(), modeRetained)
|
|
g.Printf("return _v\n")
|
|
g.Outdent()
|
|
g.Printf("}\n\n")
|
|
}
|
|
|
|
for _, m := range methods {
|
|
g.genFuncSignature(m, obj.Name())
|
|
g.Indent()
|
|
g.Printf("ref := _seq.FromRefNum(int32(refnum))\n")
|
|
g.Printf("v := ref.Get().(*%s.%s)\n", g.pkg.Name(), obj.Name())
|
|
g.genFuncBody(m, "v")
|
|
g.Outdent()
|
|
g.Printf("}\n\n")
|
|
}
|
|
}
|
|
|
|
func (g *goGen) genVar(o *types.Var) {
|
|
// TODO(hyangah): non-struct pointer types (*int), struct type.
|
|
|
|
v := fmt.Sprintf("%s.%s", g.pkg.Name(), o.Name())
|
|
|
|
// var I int
|
|
//
|
|
// func var_setI(v int)
|
|
g.Printf("//export var_set%s_%s\n", g.pkgPrefix, o.Name())
|
|
g.Printf("func var_set%s_%s(v C.%s) {\n", g.pkgPrefix, o.Name(), g.cgoType(o.Type()))
|
|
g.Indent()
|
|
g.genRead("_v", "v", o.Type(), modeRetained)
|
|
g.Printf("%s = _v\n", v)
|
|
g.Outdent()
|
|
g.Printf("}\n")
|
|
|
|
// func var_getI() int
|
|
g.Printf("//export var_get%s_%s\n", g.pkgPrefix, o.Name())
|
|
g.Printf("func var_get%s_%s() C.%s {\n", g.pkgPrefix, o.Name(), g.cgoType(o.Type()))
|
|
g.Indent()
|
|
g.Printf("v := %s\n", v)
|
|
g.genWrite("_v", "v", o.Type(), modeRetained)
|
|
g.Printf("return _v\n")
|
|
g.Outdent()
|
|
g.Printf("}\n")
|
|
}
|
|
|
|
func (g *goGen) genInterface(obj *types.TypeName) {
|
|
iface := obj.Type().(*types.Named).Underlying().(*types.Interface)
|
|
|
|
summary := makeIfaceSummary(iface)
|
|
|
|
// Define the entry points.
|
|
for _, m := range summary.callable {
|
|
g.genFuncSignature(m, obj.Name())
|
|
g.Indent()
|
|
g.Printf("ref := _seq.FromRefNum(int32(refnum))\n")
|
|
g.Printf("v := ref.Get().(%s.%s)\n", g.pkg.Name(), obj.Name())
|
|
g.genFuncBody(m, "v")
|
|
g.Outdent()
|
|
g.Printf("}\n\n")
|
|
}
|
|
|
|
// Define a proxy interface.
|
|
if !summary.implementable {
|
|
// The interface defines an unexported method or a method that
|
|
// uses an unexported type. We cannot generate a proxy object
|
|
// for such a type.
|
|
return
|
|
}
|
|
g.Printf("type proxy%s_%s _seq.Ref\n\n", g.pkgPrefix, obj.Name())
|
|
|
|
for _, m := range summary.callable {
|
|
sig := m.Type().(*types.Signature)
|
|
params := sig.Params()
|
|
res := sig.Results()
|
|
|
|
if res.Len() > 2 ||
|
|
(res.Len() == 2 && !isErrorType(res.At(1).Type())) {
|
|
g.errorf("functions and methods must return either zero or one value, and optionally an error: %s.%s", obj.Name(), m.Name())
|
|
continue
|
|
}
|
|
|
|
g.Printf("func (p *proxy%s_%s) %s(", g.pkgPrefix, obj.Name(), m.Name())
|
|
for i := 0; i < params.Len(); i++ {
|
|
if i > 0 {
|
|
g.Printf(", ")
|
|
}
|
|
g.Printf("param_%s %s", paramName(params, i), g.typeString(params.At(i).Type()))
|
|
}
|
|
g.Printf(") ")
|
|
|
|
if res.Len() == 1 {
|
|
g.Printf(g.typeString(res.At(0).Type()))
|
|
} else if res.Len() == 2 {
|
|
g.Printf("(%s, error)", g.typeString(res.At(0).Type()))
|
|
}
|
|
g.Printf(" {\n")
|
|
g.Indent()
|
|
|
|
for i := 0; i < params.Len(); i++ {
|
|
pn := "param_" + paramName(params, i)
|
|
g.genWrite("_"+pn, pn, params.At(i).Type(), modeTransient)
|
|
}
|
|
|
|
if res.Len() > 0 {
|
|
g.Printf("res := ")
|
|
}
|
|
g.Printf("C.cproxy%s_%s_%s(C.int32_t(p.Num)", g.pkgPrefix, obj.Name(), m.Name())
|
|
for i := 0; i < params.Len(); i++ {
|
|
g.Printf(", _param_%s", paramName(params, i))
|
|
}
|
|
g.Printf(")\n")
|
|
var retName string
|
|
if res.Len() > 0 {
|
|
if res.Len() == 1 {
|
|
T := res.At(0).Type()
|
|
g.genRead("_res", "res", T, modeRetained)
|
|
retName = "_res"
|
|
} else {
|
|
var rvs []string
|
|
for i := 0; i < res.Len(); i++ {
|
|
rv := fmt.Sprintf("res_%d", i)
|
|
g.genRead(rv, fmt.Sprintf("res.r%d", i), res.At(i).Type(), modeRetained)
|
|
rvs = append(rvs, rv)
|
|
}
|
|
retName = strings.Join(rvs, ", ")
|
|
}
|
|
g.Printf("return %s\n", retName)
|
|
}
|
|
g.Outdent()
|
|
g.Printf("}\n\n")
|
|
}
|
|
}
|
|
|
|
func (g *goGen) genRead(toVar, fromVar string, typ types.Type, mode varMode) {
|
|
if isErrorType(typ) {
|
|
g.genRead(toVar+"_str", fromVar, types.Typ[types.String], mode)
|
|
g.Printf("%s := toError(%s_str)\n", toVar, toVar)
|
|
return
|
|
}
|
|
switch t := typ.(type) {
|
|
case *types.Basic:
|
|
switch t.Kind() {
|
|
case types.String:
|
|
g.Printf("%s := decodeString(%s)\n", toVar, fromVar)
|
|
case types.Bool:
|
|
g.Printf("%s := %s != 0\n", toVar, fromVar)
|
|
default:
|
|
g.Printf("%s := %s(%s)\n", toVar, t.Underlying().String(), fromVar)
|
|
}
|
|
case *types.Slice:
|
|
switch e := t.Elem().(type) {
|
|
case *types.Basic:
|
|
switch e.Kind() {
|
|
case types.Uint8: // Byte.
|
|
g.Printf("%s := toSlice(%s, %v)\n", toVar, fromVar, mode == modeRetained)
|
|
default:
|
|
g.errorf("unsupported type: %s", t)
|
|
}
|
|
default:
|
|
g.errorf("unsupported type: %s", t)
|
|
}
|
|
case *types.Pointer:
|
|
switch u := t.Elem().(type) {
|
|
case *types.Named:
|
|
o := u.Obj()
|
|
if o.Pkg() != g.pkg {
|
|
g.errorf("type %s not defined in %s", u, g.pkg)
|
|
return
|
|
}
|
|
g.Printf("// Must be a Go object\n")
|
|
g.Printf("%s_ref := _seq.FromRefNum(int32(%s))\n", toVar, fromVar)
|
|
g.Printf("%s := %s_ref.Get().(*%s.%s)\n", toVar, toVar, g.pkg.Name(), o.Name())
|
|
default:
|
|
g.errorf("unsupported pointer type %s", t)
|
|
}
|
|
case *types.Named:
|
|
switch t.Underlying().(type) {
|
|
case *types.Interface, *types.Pointer:
|
|
hasProxy := true
|
|
if iface, ok := t.Underlying().(*types.Interface); ok {
|
|
hasProxy = makeIfaceSummary(iface).implementable
|
|
}
|
|
o := t.Obj()
|
|
if o.Pkg() != g.pkg {
|
|
g.errorf("type %s not defined in %s", t, g.pkg)
|
|
return
|
|
}
|
|
g.Printf("var %s %s\n", toVar, g.typeString(t))
|
|
g.Printf("%s_ref := _seq.FromRefNum(int32(%s))\n", toVar, fromVar)
|
|
g.Printf("if %s_ref != nil {\n", toVar)
|
|
g.Printf(" if %s_ref.Num < 0 { // go object \n", toVar)
|
|
g.Printf(" %s = %s_ref.Get().(%s.%s)\n", toVar, toVar, g.pkg.Name(), o.Name())
|
|
if hasProxy {
|
|
g.Printf(" } else { // foreign object \n")
|
|
g.Printf(" %s = (*proxy%s_%s)(%s_ref)\n", toVar, g.pkgPrefix, o.Name(), toVar)
|
|
}
|
|
g.Printf(" }\n")
|
|
g.Printf("}\n")
|
|
default:
|
|
g.errorf("unsupported named type %s", t)
|
|
}
|
|
default:
|
|
g.errorf("unsupported type: %s", typ)
|
|
}
|
|
}
|
|
|
|
func (g *goGen) typeString(typ types.Type) string {
|
|
pkg := g.pkg
|
|
|
|
switch t := typ.(type) {
|
|
case *types.Named:
|
|
obj := t.Obj()
|
|
if obj.Pkg() == nil { // e.g. error type is *types.Named.
|
|
return types.TypeString(typ, types.RelativeTo(pkg))
|
|
}
|
|
if obj.Pkg() != g.pkg {
|
|
g.errorf("type %s not defined in %s", t, g.pkg)
|
|
}
|
|
|
|
switch t.Underlying().(type) {
|
|
case *types.Interface, *types.Struct:
|
|
return fmt.Sprintf("%s.%s", pkg.Name(), types.TypeString(typ, types.RelativeTo(pkg)))
|
|
default:
|
|
g.errorf("unsupported named type %s / %T", t, t)
|
|
}
|
|
case *types.Pointer:
|
|
switch t := t.Elem().(type) {
|
|
case *types.Named:
|
|
return fmt.Sprintf("*%s", g.typeString(t))
|
|
default:
|
|
g.errorf("not yet supported, pointer type %s / %T", t, t)
|
|
}
|
|
default:
|
|
return types.TypeString(typ, types.RelativeTo(pkg))
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (g *goGen) gen() error {
|
|
g.Printf(goPreamble, g.pkg.Name(), g.pkg.Path())
|
|
|
|
for _, s := range g.structs {
|
|
g.genStruct(s.obj, s.t)
|
|
}
|
|
for _, intf := range g.interfaces {
|
|
g.genInterface(intf.obj)
|
|
}
|
|
for _, v := range g.vars {
|
|
g.genVar(v)
|
|
}
|
|
for _, f := range g.funcs {
|
|
g.genFunc(f)
|
|
}
|
|
if len(g.err) > 0 {
|
|
return g.err
|
|
}
|
|
return nil
|
|
}
|