Skip to content

Instantly share code, notes, and snippets.

@patricksuo
Last active July 26, 2018 15:14
Show Gist options
  • Save patricksuo/606e4874839456cc02335bd1c5045f27 to your computer and use it in GitHub Desktop.
Save patricksuo/606e4874839456cc02335bd1c5045f27 to your computer and use it in GitHub Desktop.
package reflectimport ( "runtime"
"sync"
"unsafe"
)
type Caller struct {
v Value
//readonly cache
numIn int
numOut int
isVariadic bool
frametype *rtype
retOffset uintptr
framePool *sync.Pool
}
func NewCaller(v Value) *Caller {
c := new(Caller)
c.v = v
c.numIn = v.typ.NumIn()
c.numOut = v.typ.NumOut()
c.isVariadic = v.typ.IsVariadic()
var (
t = v.typ
rcvrtype *rtype
)
if v.flag&flagMethod != 0 {
rcvrtype, t, _ = methodReceiver("call", v, int(v.flag)>>flagMethodShift)
}
c.frametype, _, c.retOffset, _, c.framePool = funcLayout(t, rcvrtype)
return c
}
func (c *Caller) Call(in []Value) []Value {
c.v.mustBe(Func)
c.v.mustBeExported()
return c.call("Call", in)
}
func (c *Caller) call(op string, in []Value) []Value {
// Get function pointer, type.
v := c.v
t := v.typ
var (
fn unsafe.Pointer
rcvr Value
rcvrtype *rtype
)
if v.flag&flagMethod != 0 {
rcvr = v
rcvrtype, t, fn = methodReceiver(op, v, int(v.flag)>>flagMethodShift)
} else if v.flag&flagIndir != 0 {
fn = *(*unsafe.Pointer)(v.ptr)
} else {
fn = v.ptr
}
if fn == nil {
panic("reflect.Value.Call: call of nil function")
}
isSlice := op == "CallSlice"
n := c.numIn
if isSlice {
if !c.isVariadic {
panic("reflect: CallSlice of non-variadic function")
}
if len(in) < n {
panic("reflect: CallSlice with too few input arguments")
}
if len(in) > n {
panic("reflect: CallSlice with too many input arguments")
}
} else {
if c.isVariadic {
n--
}
if len(in) < n {
panic("reflect: Call with too few input arguments")
}
if !c.isVariadic && len(in) > n {
panic("reflect: Call with too many input arguments")
}
}
for _, x := range in {
if x.Kind() == Invalid {
panic("reflect: " + op + " using zero Value argument")
}
}
for i := 0; i < n; i++ {
if xt, targ := in[i].Type(), t.In(i); !xt.AssignableTo(targ) {
panic("reflect: " + op + " using " + xt.String() + " as type " + targ.String())
}
}
if !isSlice && c.isVariadic {
// prepare slice for remaining values
m := len(in) - n
slice := MakeSlice(t.In(n), m, m)
elem := t.In(n).Elem()
for i := 0; i < m; i++ {
x := in[n+i]
if xt := x.Type(); !xt.AssignableTo(elem) {
panic("reflect: cannot use " + xt.String() + " as type " + elem.String() + " in " + op)
}
slice.Index(i).Set(x)
}
origIn := in
in = make([]Value, n+1)
copy(in[:n], origIn)
in[n] = slice
}
nin := len(in)
if nin != c.numIn {
panic("reflect.Value.Call: wrong argument count")
}
nout := c.numOut
// Compute frame type.
//frametype, _, retOffset, _, framePool := funcLayout(t, rcvrtype)
frametype, retOffset, framePool := c.frametype, c.retOffset, c.framePool
// Allocate a chunk of memory for frame.
var args unsafe.Pointer
if nout == 0 {
args = framePool.Get().(unsafe.Pointer)
} else {
// Can't use pool if the function has return values.
// We will leak pointer to args in ret, so its lifetime is not scoped.
args = unsafe_New(frametype)
}
off := uintptr(0)
// Copy inputs into args.
if rcvrtype != nil {
storeRcvr(rcvr, args)
off = ptrSize
}
for i, v := range in {
v.mustBeExported()
targ := t.In(i).(*rtype)
a := uintptr(targ.align)
off = (off + a - 1) &^ (a - 1)
n := targ.size
if n == 0 {
// Not safe to compute args+off pointing at 0 bytes,
// because that might point beyond the end of the frame,
// but we still need to call assignTo to check assignability.
v.assignTo("reflect.Value.Call", targ, nil)
continue
}
addr := add(args, off, "n > 0")
v = v.assignTo("reflect.Value.Call", targ, addr)
if v.flag&flagIndir != 0 {
typedmemmove(targ, addr, v.ptr)
} else {
*(*unsafe.Pointer)(addr) = v.ptr
}
off += n
}
// Call.
call(frametype, fn, args, uint32(frametype.size), uint32(retOffset))
// For testing; see TestCallMethodJump.
if callGC {
runtime.GC()
}
var ret []Value
if nout == 0 {
// This is untyped because the frame is really a
// stack, even though it's a heap object.
memclrNoHeapPointers(args, frametype.size)
framePool.Put(args)
} else {
// Zero the now unused input area of args,
// because the Values returned by this function contain pointers to the args object,
// and will thus keep the args object alive indefinitely.
memclrNoHeapPointers(args, retOffset)
// Wrap Values around return values in args.
ret = make([]Value, nout)
off = retOffset
for i := 0; i < nout; i++ {
tv := t.Out(i)
a := uintptr(tv.Align())
off = (off + a - 1) &^ (a - 1)
if tv.Size() != 0 {
fl := flagIndir | flag(tv.Kind())
ret[i] = Value{tv.common(), add(args, off, "tv.Size() != 0"), fl}
} else {
// For zero-sized return value, args+off may point to the next object.
// In this case, return the zero value instead.
ret[i] = Zero(tv)
}
off += tv.Size()
}
}
return ret
}
package reflectbench
import (
"reflect"
"testing"
"unsafe"
)
type myint int64
type Inccer interface {
inc()
}
func (i *myint) inc() {
*i = *i + 1
}
func BenchmarkReflectCaller(b *testing.B) {
i := new(myint)
incnReflectCaller(i.inc, b.N)
}
func BenchmarkReflectMethodCall(b *testing.B) {
i := new(myint)
incnReflectCall(i.inc, b.N)
}
func BenchmarkReflectOnceMethodCall(b *testing.B) {
i := new(myint)
incnReflectOnceCall(i.inc, b.N)
}
func BenchmarkStructMethodCall(b *testing.B) {
i := new(myint)
incnIntmethod(i, b.N)
}
func BenchmarkInterfaceMethodCall(b *testing.B) {
i := new(myint)
incnInterface(i, b.N)
}
func BenchmarkTypeSwitchMethodCall(b *testing.B) {
i := new(myint)
incnSwitch(i, b.N)
}
func BenchmarkTypeAssertionMethodCall(b *testing.B) {
i := new(myint)
incnAssertion(i, b.N)
}
func incnReflectCaller(v interface{}, n int) {
c := reflect.NewCaller(reflect.ValueOf(v))
for k := 0; k < n; k++ {
c.Call(nil)
}
}
func incnReflectCall(v interface{}, n int) {
for k := 0; k < n; k++ {
reflect.ValueOf(v).Call(nil)
}
}
func incnReflectOnceCall(v interface{}, n int) {
fn := reflect.ValueOf(v)
for k := 0; k < n; k++ {
fn.Call(nil)
}
}
func incnIntmethod(i *myint, n int) {
for k := 0; k < n; k++ {
i.inc()
}
}
func incnInterface(any Inccer, n int) {
for k := 0; k < n; k++ {
any.inc()
}
}
func incnSwitch(any Inccer, n int) {
for k := 0; k < n; k++ {
switch v := any.(type) {
case *myint:
v.inc()
}
}
}
func incnAssertion(any Inccer, n int) {
for k := 0; k < n; k++ {
if newint, ok := any.(*myint); ok {
newint.inc()
}
}
}
BenchmarkReflectCaller-8 20000000 64.6 ns/op
BenchmarkReflectMethodCall-8 10000000 135 ns/op
BenchmarkReflectOnceMethodCall-8 10000000 126 ns/op
BenchmarkStructMethodCall-8 2000000000 1.67 ns/op
BenchmarkInterfaceMethodCall-8 1000000000 2.41 ns/op
BenchmarkTypeSwitchMethodCall-8 2000000000 1.23 ns/op
BenchmarkTypeAssertionMethodCall-8 2000000000 1.42 ns/op
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment