diff --git a/api/next/49340.txt b/api/next/49340.txt new file mode 100644 index 00000000000000..054e2cbb601aaa --- /dev/null +++ b/api/next/49340.txt @@ -0,0 +1,5 @@ +pkg reflect, method (*Caller) Call([]Value, []Value) #49340 +pkg reflect, method (*Caller) CallSlice([]Value, []Value) #49340 +pkg reflect, method (*Caller) Type() Type #49340 +pkg reflect, method (Value) Caller() *Caller #49340 +pkg reflect, type Caller struct #49340 diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index 31f6416ed9ac0a..e4186dcfbb58d7 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -8410,3 +8410,32 @@ func TestClear(t *testing.T) { }) } } + +func TestCaller_CallReusesOutValues(t *testing.T) { + fn := func() int { + return 27 + } + + var i int + v := ValueOf(&i).Elem() + out := []Value{v} + + ValueOf(fn).Caller().Call([]Value{}, out) + + if i != 27 { + t.Errorf("unexpected result for output value; got %d, want 27", i) + } +} + +func TestCaller_CallAllocations(t *testing.T) { + fn := func() int { + return 27 + } + c := ValueOf(fn).Caller() + + out := make([]Value, c.Type().NumOut()) + + noAlloc(t, 100, func(int) { + c.Call([]Value{}, out) + }) +} diff --git a/src/reflect/benchmark_test.go b/src/reflect/benchmark_test.go index 51634ab4f7de03..b7b1ba83edbd9d 100644 --- a/src/reflect/benchmark_test.go +++ b/src/reflect/benchmark_test.go @@ -197,6 +197,19 @@ func BenchmarkCall(b *testing.B) { }) } +func BenchmarkCallerCall(b *testing.B) { + fv := ValueOf(func(a, b string) {}) + c := fv.Caller() + out := make([]Value, c.Type().NumOut()) + b.ReportAllocs() + b.RunParallel(func(pb *testing.PB) { + args := []Value{ValueOf("a"), ValueOf("b")} + for pb.Next() { + c.Call(args, out) + } + }) +} + type myint int64 func (i *myint) inc() { @@ -213,6 +226,17 @@ func BenchmarkCallMethod(b *testing.B) { } } +func BenchmarkCallerCallMethod(b *testing.B) { + b.ReportAllocs() + z := new(myint) + + v := ValueOf(z.inc) + c := v.Caller() + for i := 0; i < b.N; i++ { + c.Call(nil, nil) + } +} + func BenchmarkCallArgCopy(b *testing.B) { byteArray := func(n int) Value { return Zero(ArrayOf(n, TypeOf(byte(0)))) diff --git a/src/reflect/value.go b/src/reflect/value.go index fb29769e87efd6..ab959e3920f7ff 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -12,6 +12,7 @@ import ( "internal/unsafeheader" "math" "runtime" + "sync" "unsafe" ) @@ -356,6 +357,42 @@ func (v Value) CanSet() bool { return v.flag&(flagAddr|flagRO) == flagAddr } +// Caller creates a function caller for the function v. +// The returned Caller caches information about the function making +// repeated calls more efficient. +// Caller panics if v's Kind is not Func. +func (v Value) Caller() *Caller { + v.mustBe(Func) + v.mustBeExported() + + // Get function pointer, type. + t := (*funcType)(unsafe.Pointer(v.typ)) + var ( + fn unsafe.Pointer + rcvr Value + rcvrtype *rtype + ) + if v.flag&flagMethod != 0 { + rcvr = v + rcvrtype, t, fn = methodReceiver("Caller", v, int(v.flag)>>flagMethodShift) + } else if v.flag&flagIndir != 0 { + fn = *(*unsafe.Pointer)(v.ptr) + } else { + fn = v.ptr + } + + frametype, framePool, abid := funcLayout(t, rcvrtype) + + return &Caller{ + t: t, + fn: fn, + rcvr: rcvr, + frametype: frametype, + framePool: framePool, + abid: abid, + } +} + // Call calls the function v with the input arguments in. // For example, if len(in) == 3, v.Call(in) represents the Go call v(in[0], in[1], in[2]). // Call panics if v's Kind is not Func. @@ -367,7 +404,11 @@ func (v Value) CanSet() bool { func (v Value) Call(in []Value) []Value { v.mustBe(Func) v.mustBeExported() - return v.call("Call", in) + + c := v.Caller() + out := make([]Value, v.Type().NumOut()) + c.Call(in, out) + return out } // CallSlice calls the variadic function v with the input arguments in, @@ -380,37 +421,64 @@ func (v Value) Call(in []Value) []Value { func (v Value) CallSlice(in []Value) []Value { v.mustBe(Func) v.mustBeExported() - return v.call("CallSlice", in) + + c := v.Caller() + out := make([]Value, v.Type().NumOut()) + c.CallSlice(in, out) + return out } -var callGC bool // for testing; see TestCallMethodJump and TestCallArgLive +// Caller is a function wrapper that caches details about a function +// to make repeated calls more efficient. +type Caller struct { + t *funcType + fn unsafe.Pointer + rcvr Value + frametype *rtype + framePool *sync.Pool + abid abiDesc +} -const debugReflectCall = false +// Type returns c's type. +func (c *Caller) Type() Type { + return c.t +} -func (v Value) call(op string, in []Value) []Value { - // Get function pointer, type. - t := (*funcType)(unsafe.Pointer(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 +// Call calls the function c with the input arguments in and output result slice out. +// For example, if len(in) == 3, c.Call(in) represents the Go call c(in[0], in[1], in[2]). +// As in Go, each input argument must be assignable to the +// type of the function's corresponding input parameter. +// If c is a variadic function, Call creates the variadic slice parameter +// itself, copying in the corresponding values. +func (c *Caller) Call(in, out []Value) { + if c.fn == nil { + panic("Caller: call of nil function") } - if fn == nil { - panic("reflect.Value.Call: call of nil function") + c.call("Call", in, out) +} + +// CallSlice calls the variadic function c with the input arguments in and output result slice out, +// assigning the slice in[len(in)-1] to c's final variadic argument. +// For example, if len(in) == 3, c.CallSlice(in) represents the Go call c(in[0], in[1], in[2]...). +// As in Go, each input argument must be assignable to the +// type of the function's corresponding input parameter. +func (c *Caller) CallSlice(in, out []Value) { + if c.fn == nil { + panic("Caller: call of nil function") } + c.call("CallSlice", in, out) +} + +var callGC bool // for testing; see TestCallMethodJump and TestCallArgLive + +const debugReflectCall = false + +func (c *Caller) call(op string, in, out []Value) { isSlice := op == "CallSlice" - n := t.NumIn() - isVariadic := t.IsVariadic() + n := c.t.NumIn() + isVariadic := c.t.IsVariadic() if isSlice { if !isVariadic { panic("reflect: CallSlice of non-variadic function") @@ -438,15 +506,15 @@ func (v Value) call(op string, in []Value) []Value { } } for i := 0; i < n; i++ { - if xt, targ := in[i].Type(), t.In(i); !xt.AssignableTo(targ) { + if xt, targ := in[i].Type(), c.t.In(i); !xt.AssignableTo(targ) { panic("reflect: " + op + " using " + xt.String() + " as type " + targ.String()) } } if !isSlice && isVariadic { // prepare slice for remaining values m := len(in) - n - slice := MakeSlice(t.In(n), m, m) - elem := t.In(n).Elem() + slice := MakeSlice(c.t.In(n), m, m) + elem := c.t.In(n).Elem() for i := 0; i < m; i++ { x := in[n+i] if xt := x.Type(); !xt.AssignableTo(elem) { @@ -459,55 +527,59 @@ func (v Value) call(op string, in []Value) []Value { copy(in[:n], origIn) in[n] = slice } + n = c.t.NumOut() + if len(out) < n { + panic("reflect: " + op + " with too few output arguments") + } + if len(out) > n { + panic("reflect: " + op + " with too many output arguments") + } nin := len(in) - if nin != t.NumIn() { - panic("reflect.Value.Call: wrong argument count") + if nin != c.t.NumIn() { + panic("reflect.Caller.Call: wrong argument count") } - nout := t.NumOut() + nout := c.t.NumOut() // Register argument space. var regArgs abi.RegArgs - // Compute frame type. - frametype, framePool, abid := funcLayout(t, rcvrtype) - // Allocate a chunk of memory for frame if needed. var stackArgs unsafe.Pointer - if frametype.size != 0 { + if c.frametype.size != 0 { if nout == 0 { - stackArgs = framePool.Get().(unsafe.Pointer) + stackArgs = c.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. - stackArgs = unsafe_New(frametype) + stackArgs = unsafe_New(c.frametype) } } - frameSize := frametype.size + frameSize := c.frametype.size if debugReflectCall { - println("reflect.call", t.String()) - abid.dump() + println("reflect.call", c.t.String()) + c.abid.dump() } // Copy inputs into args. // Handle receiver. inStart := 0 - if rcvrtype != nil { + if c.rcvr.IsValid() { // Guaranteed to only be one word in size, // so it will only take up exactly 1 abiStep (either // in a register or on the stack). - switch st := abid.call.steps[0]; st.kind { + switch st := c.abid.call.steps[0]; st.kind { case abiStepStack: - storeRcvr(rcvr, stackArgs) + storeRcvr(c.rcvr, stackArgs) case abiStepPointer: - storeRcvr(rcvr, unsafe.Pointer(®Args.Ptrs[st.ireg])) + storeRcvr(c.rcvr, unsafe.Pointer(®Args.Ptrs[st.ireg])) fallthrough case abiStepIntReg: - storeRcvr(rcvr, unsafe.Pointer(®Args.Ints[st.ireg])) + storeRcvr(c.rcvr, unsafe.Pointer(®Args.Ints[st.ireg])) case abiStepFloatReg: - storeRcvr(rcvr, unsafe.Pointer(®Args.Floats[st.freg])) + storeRcvr(c.rcvr, unsafe.Pointer(®Args.Floats[st.freg])) default: panic("unknown ABI parameter kind") } @@ -517,13 +589,13 @@ func (v Value) call(op string, in []Value) []Value { // Handle arguments. for i, v := range in { v.mustBeExported() - targ := t.In(i).(*rtype) + targ := c.t.In(i).(*rtype) // TODO(mknyszek): Figure out if it's possible to get some // scratch space for this assignment check. Previously, it // was possible to use space in the argument frame. v = v.assignTo("reflect.Value.Call", targ, nil) stepsLoop: - for _, st := range abid.call.stepsForValue(i + inStart) { + for _, st := range c.abid.call.stepsForValue(i + inStart) { switch st.kind { case abiStepStack: // Copy values to the "stack." @@ -568,10 +640,10 @@ func (v Value) call(op string, in []Value) []Value { // TODO(mknyszek): Remove this when we no longer have // caller reserved spill space. frameSize = align(frameSize, goarch.PtrSize) - frameSize += abid.spill + frameSize += c.abid.spill // Mark pointers in registers for the return path. - regArgs.ReturnIsPtr = abid.outRegPtrs + regArgs.ReturnIsPtr = c.abid.outRegPtrs if debugReflectCall { regArgs.Dump() @@ -583,48 +655,51 @@ func (v Value) call(op string, in []Value) []Value { } // Call. - call(frametype, fn, stackArgs, uint32(frametype.size), uint32(abid.retOffset), uint32(frameSize), ®Args) + call(c.frametype, c.fn, stackArgs, uint32(c.frametype.size), uint32(c.abid.retOffset), uint32(frameSize), ®Args) // For testing; see TestCallMethodJump. if callGC { runtime.GC() } - var ret []Value if nout == 0 { if stackArgs != nil { - typedmemclr(frametype, stackArgs) - framePool.Put(stackArgs) + typedmemclr(c.frametype, stackArgs) + c.framePool.Put(stackArgs) } } else { if stackArgs != nil { // 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. - typedmemclrpartial(frametype, stackArgs, 0, abid.retOffset) + typedmemclrpartial(c.frametype, stackArgs, 0, c.abid.retOffset) } // Wrap Values around return values in args. - ret = make([]Value, nout) - for i := 0; i < nout; i++ { - tv := t.Out(i) + for i, v := range out { + tv := c.t.Out(i) if tv.Size() == 0 { // 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) + out[i] = Zero(tv) continue } - steps := abid.ret.stepsForValue(i) + + s := v.ptr + if s != nil && v.flag&flagIndir != 0 && v.typ != tv { + panic("reflect: cannot use " + v.typ.String() + " as type " + tv.String() + " in " + op) + } + + steps := c.abid.ret.stepsForValue(i) if st := steps[0]; st.kind == abiStepStack { // This value is on the stack. If part of a value is stack - // allocated, the entire value is according to the ABI. So - // just make an indirection into the allocated frame. - fl := flagIndir | flag(tv.Kind()) - ret[i] = Value{tv.common(), add(stackArgs, st.stkOff, "tv.Size() != 0"), fl} - // Note: this does introduce false sharing between results - - // if any result is live, they are all live. - // (And the space for the args is live as well, but as we've - // cleared that space it isn't as big a deal.) + // allocated, the entire value is according to the ABI. + addr := add(stackArgs, st.stkOff, "tv.Size() != 0") + if s != nil && v.flag&flagIndir != 0 { + typedmemmove(c.t.Out(i).(*rtype), s, addr) + continue + } + out[i] = Value{tv.common(), addr, flagIndir | flag(tv.Kind())} continue } @@ -636,20 +711,16 @@ func (v Value) call(op string, in []Value) []Value { print("kind=", steps[0].kind, ", type=", tv.String(), "\n") panic("mismatch between ABI description and types") } - ret[i] = Value{tv.common(), regArgs.Ptrs[steps[0].ireg], flag(tv.Kind())} + out[i] = Value{tv.common(), regArgs.Ptrs[steps[0].ireg], flag(tv.Kind())} continue } // All that's left is values passed in registers that we need to - // create space for and copy values back into. - // - // TODO(mknyszek): We make a new allocation for each register-allocated - // value, but previously we could always point into the heap-allocated - // stack frame. This is a regression that could be fixed by adding - // additional space to the allocated stack frame and storing the - // register-allocated return values into the allocated stack frame and - // referring there in the resulting Value. - s := unsafe_New(tv.common()) + // potentially create space for and copy values back into. + if s == nil || v.flag&flagIndir == 0 { + s = unsafe_New(tv.common()) + out[i] = Value{tv.common(), s, flagIndir | flag(tv.Kind())} + } for _, st := range steps { switch st.kind { case abiStepIntReg: @@ -667,11 +738,8 @@ func (v Value) call(op string, in []Value) []Value { panic("unknown ABI part kind") } } - ret[i] = Value{tv.common(), s, flagIndir | flag(tv.Kind())} } } - - return ret } // callReflect is the call implementation used by a function