2
0
mirror of synced 2025-02-23 23:08:14 +00:00
mobile/bind/gen.go
Elias Naur ba0a725146 mobile/bind: avoid intermediate []rune copy converting Java string to Go
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>
2016-03-05 10:02:05 +00:00

252 lines
6.3 KiB
Go

// Copyright 2015 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 (
"bytes"
"fmt"
"go/token"
"go/types"
"io"
"regexp"
)
type (
ErrorList []error
// varMode describes the lifetime of an argument or
// return value. Modes are used to guide the conversion
// of string and byte slice values accross the language
// barrier. The same conversion mode must be used for
// both the conversion before a foreign call and the
// corresponding conversion after the call.
// See the mode* constants for a description of
// each mode.
varMode int
)
const (
// modeTransient are for function arguments that
// are not used after the function returns.
// Transient byte slices don't need copying
// when passed accross the language barrier.
modeTransient varMode = iota
// modeRetained are for returned values and for function
// arguments that are used after the function returns.
// Retained byte slices need an intermediate copy.
modeRetained
)
func (list ErrorList) Error() string {
buf := new(bytes.Buffer)
for i, err := range list {
if i > 0 {
buf.WriteRune('\n')
}
io.WriteString(buf, err.Error())
}
return buf.String()
}
type generator struct {
*printer
fset *token.FileSet
pkg *types.Package
err ErrorList
// fields set by init.
pkgName string
// pkgPrefix is a prefix for disambiguating
// function names for binding multiple packages
pkgPrefix string
funcs []*types.Func
constants []*types.Const
vars []*types.Var
interfaces []interfaceInfo
structs []structInfo
otherNames []*types.TypeName
}
func (g *generator) init() {
g.pkgName = g.pkg.Name()
// TODO(elias.naur): Avoid (and test) name clashes from multiple packages
// with the same name. Perhaps use the index from the order the package is
// generated.
g.pkgPrefix = g.pkgName
scope := g.pkg.Scope()
hasExported := false
for _, name := range scope.Names() {
obj := scope.Lookup(name)
if !obj.Exported() {
continue
}
hasExported = true
switch obj := obj.(type) {
case *types.Func:
if isCallable(obj) {
g.funcs = append(g.funcs, obj)
}
case *types.TypeName:
named := obj.Type().(*types.Named)
switch t := named.Underlying().(type) {
case *types.Struct:
g.structs = append(g.structs, structInfo{obj, t})
case *types.Interface:
g.interfaces = append(g.interfaces, interfaceInfo{obj, t, makeIfaceSummary(t)})
default:
g.otherNames = append(g.otherNames, obj)
}
case *types.Const:
if _, ok := obj.Type().(*types.Basic); !ok {
g.errorf("unsupported exported const for %s: %T", obj.Name(), obj)
continue
}
g.constants = append(g.constants, obj)
case *types.Var:
g.vars = append(g.vars, obj)
default:
g.errorf("unsupported exported type for %s: %T", obj.Name(), obj)
}
}
if !hasExported {
g.errorf("no exported names in the package %q", g.pkg.Path())
}
}
func (_ *generator) toCFlag(v bool) int {
if v {
return 1
}
return 0
}
func (g *generator) errorf(format string, args ...interface{}) {
g.err = append(g.err, fmt.Errorf(format, args...))
}
// cgoType returns the name of a Cgo type suitable for converting a value of
// the given type.
func (g *generator) cgoType(t types.Type) string {
if isErrorType(t) {
return g.cgoType(types.Typ[types.String])
}
switch t := t.(type) {
case *types.Basic:
switch t.Kind() {
case types.Bool, types.UntypedBool:
return "char"
case types.Int:
return "nint"
case types.Int8:
return "int8_t"
case types.Int16:
return "int16_t"
case types.Int32, types.UntypedRune: // types.Rune
return "int32_t"
case types.Int64, types.UntypedInt:
return "int64_t"
case types.Uint8: // types.Byte
return "uint8_t"
// TODO(crawshaw): case types.Uint, types.Uint16, types.Uint32, types.Uint64:
case types.Float32:
return "float"
case types.Float64, types.UntypedFloat:
return "double"
case types.String:
return "nstring"
default:
panic(fmt.Sprintf("unsupported basic type: %s", t))
}
case *types.Slice:
switch e := t.Elem().(type) {
case *types.Basic:
switch e.Kind() {
case types.Uint8: // Byte.
return "nbyteslice"
default:
panic(fmt.Sprintf("unsupported slice type: %s", t))
}
default:
panic(fmt.Sprintf("unsupported slice type: %s", t))
}
case *types.Pointer:
if _, ok := t.Elem().(*types.Named); ok {
return g.cgoType(t.Elem())
}
panic(fmt.Sprintf("unsupported pointer to type: %s", t))
case *types.Named:
return "int32_t"
default:
panic(fmt.Sprintf("unsupported type: %s", t))
}
}
func (g *generator) genInterfaceMethodSignature(m *types.Func, iName string, header bool) {
sig := m.Type().(*types.Signature)
params := sig.Params()
res := sig.Results()
if res.Len() == 0 {
g.Printf("void ")
} else {
if res.Len() == 1 {
g.Printf("%s ", g.cgoType(res.At(0).Type()))
} else {
if header {
g.Printf("typedef struct cproxy%s_%s_%s_return {\n", g.pkgPrefix, iName, m.Name())
g.Indent()
for i := 0; i < res.Len(); i++ {
t := res.At(i).Type()
g.Printf("%s r%d;\n", g.cgoType(t), i)
}
g.Outdent()
g.Printf("} cproxy%s_%s_%s_return;\n", g.pkgPrefix, iName, m.Name())
}
g.Printf("struct cproxy%s_%s_%s_return ", g.pkgPrefix, iName, m.Name())
}
}
g.Printf("cproxy%s_%s_%s(int32_t refnum", g.pkgPrefix, iName, m.Name())
for i := 0; i < params.Len(); i++ {
t := params.At(i).Type()
g.Printf(", %s %s", g.cgoType(t), paramName(params, i))
}
g.Printf(")")
if header {
g.Printf(";\n")
} else {
g.Printf(" {\n")
}
}
var paramRE = regexp.MustCompile(`^p[0-9]*$`)
// paramName replaces incompatible name with a p0-pN name.
// Missing names, or existing names of the form p[0-9] are incompatible.
// TODO(crawshaw): Replace invalid unicode names.
func paramName(params *types.Tuple, pos int) string {
name := params.At(pos).Name()
if name == "" || name[0] == '_' || paramRE.MatchString(name) {
name = fmt.Sprintf("p%d", pos)
}
return name
}
func constExactString(o *types.Const) string {
// TODO(hyangah): this is a temporary fix for golang.org/issues/14615.
// Clean this up when we can require at least go 1.6 or above.
type exactStringer interface {
ExactString() string
}
v := o.Val()
if v, ok := v.(exactStringer); ok {
return v.ExactString()
}
// TODO: warning?
return v.String()
}