mirror of
https://github.com/golang/go
synced 2024-11-19 22:14:43 -07:00
reflect: add MakeFunc (API CHANGE)
Fixes #1765. R=iant, r, daniel.morsing, minux.ma, bradfitz, rogpeppe, remyoudompheng CC=golang-dev https://golang.org/cl/6554067
This commit is contained in:
parent
4b9e8415de
commit
ba4625c66f
@ -1394,23 +1394,56 @@ func fmtSelect(info []caseInfo) string {
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
type two [2]uintptr
|
||||
|
||||
// Difficult test for function call because of
|
||||
// implicit padding between arguments.
|
||||
func dummy(b byte, c int, d byte) (i byte, j int, k byte) {
|
||||
return b, c, d
|
||||
func dummy(b byte, c int, d byte, e two, f byte, g float32, h byte) (i byte, j int, k byte, l two, m byte, n float32, o byte) {
|
||||
return b, c, d, e, f, g, h
|
||||
}
|
||||
|
||||
func TestFunc(t *testing.T) {
|
||||
ret := ValueOf(dummy).Call([]Value{ValueOf(byte(10)), ValueOf(20), ValueOf(byte(30))})
|
||||
if len(ret) != 3 {
|
||||
t.Fatalf("Call returned %d values, want 3", len(ret))
|
||||
ret := ValueOf(dummy).Call([]Value{
|
||||
ValueOf(byte(10)),
|
||||
ValueOf(20),
|
||||
ValueOf(byte(30)),
|
||||
ValueOf(two{40, 50}),
|
||||
ValueOf(byte(60)),
|
||||
ValueOf(float32(70)),
|
||||
ValueOf(byte(80)),
|
||||
})
|
||||
if len(ret) != 7 {
|
||||
t.Fatalf("Call returned %d values, want 7", len(ret))
|
||||
}
|
||||
|
||||
i := byte(ret[0].Uint())
|
||||
j := int(ret[1].Int())
|
||||
k := byte(ret[2].Uint())
|
||||
if i != 10 || j != 20 || k != 30 {
|
||||
t.Errorf("Call returned %d, %d, %d; want 10, 20, 30", i, j, k)
|
||||
l := ret[3].Interface().(two)
|
||||
m := byte(ret[4].Uint())
|
||||
n := float32(ret[5].Float())
|
||||
o := byte(ret[6].Uint())
|
||||
|
||||
if i != 10 || j != 20 || k != 30 || l != (two{40, 50}) || m != 60 || n != 70 || o != 80 {
|
||||
t.Errorf("Call returned %d, %d, %d, %v, %d, %g, %d; want 10, 20, 30, [40, 50], 60, 70, 80", i, j, k, l, m, n, o)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeFunc(t *testing.T) {
|
||||
f := dummy
|
||||
fv := MakeFunc(TypeOf(f), func(in []Value) []Value { return in })
|
||||
ValueOf(&f).Elem().Set(fv)
|
||||
|
||||
// Call g with small arguments so that there is
|
||||
// something predictable (and different from the
|
||||
// correct results) in those positions on the stack.
|
||||
g := dummy
|
||||
g(1, 2, 3, two{4, 5}, 6, 7, 8)
|
||||
|
||||
// Call constructed function f.
|
||||
i, j, k, l, m, n, o := f(10, 20, 30, two{40, 50}, 60, 70, 80)
|
||||
if i != 10 || j != 20 || k != 30 || l != (two{40, 50}) || m != 60 || n != 70 || o != 80 {
|
||||
t.Errorf("Call returned %d, %d, %d, %v, %d, %g, %d; want 10, 20, 30, [40, 50], 60, 70, 80", i, j, k, l, m, n, o)
|
||||
}
|
||||
}
|
||||
|
||||
|
18
src/pkg/reflect/asm_386.s
Normal file
18
src/pkg/reflect/asm_386.s
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright 2012 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.
|
||||
|
||||
// makeFuncStub is jumped to by the code generated by MakeFunc.
|
||||
// The code sets AX = type, BX = fn, CX = frame before the jump.
|
||||
// See the comment on the declaration of makeFuncStub in value.go
|
||||
// for more details.
|
||||
TEXT ·makeFuncStub(SB),7,$12
|
||||
MOVL AX, 0(SP)
|
||||
MOVL BX, 4(SP)
|
||||
MOVL CX, 8(SP)
|
||||
CALL ·callReflect(SB)
|
||||
RET
|
||||
|
||||
// unused
|
||||
TEXT ·cacheflush(SB),7,$0
|
||||
RET
|
18
src/pkg/reflect/asm_amd64.s
Normal file
18
src/pkg/reflect/asm_amd64.s
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright 2012 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.
|
||||
|
||||
// makeFuncStub is jumped to by the code generated by MakeFunc.
|
||||
// The code sets AX = type, BX = fn, CX = frame before the jump.
|
||||
// See the comment on the declaration of makeFuncStub in value.go
|
||||
// for more details.
|
||||
TEXT ·makeFuncStub(SB),7,$24
|
||||
MOVQ AX, 0(SP)
|
||||
MOVQ BX, 8(SP)
|
||||
MOVQ CX, 16(SP)
|
||||
CALL ·callReflect(SB)
|
||||
RET
|
||||
|
||||
// unused
|
||||
TEXT ·cacheflush(SB),7,$0
|
||||
RET
|
17
src/pkg/reflect/asm_arm.s
Normal file
17
src/pkg/reflect/asm_arm.s
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2012 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.
|
||||
|
||||
// makeFuncStub is jumped to by the code generated by MakeFunc.
|
||||
// The code sets R0 = type, R1 = fn, R2 = frame before the jump.
|
||||
// See the comment on the declaration of makeFuncStub in value.go
|
||||
// for more details.
|
||||
TEXT ·makeFuncStub(SB),7,$12
|
||||
MOVW R0, 4(R13)
|
||||
MOVW R1, 8(R13)
|
||||
MOVW R2, 12(R13)
|
||||
BL ·callReflect(SB)
|
||||
RET
|
||||
|
||||
TEXT ·cacheflush(SB),7,$-4
|
||||
B runtime·cacheflush(SB)
|
52
src/pkg/reflect/example_test.go
Normal file
52
src/pkg/reflect/example_test.go
Normal file
@ -0,0 +1,52 @@
|
||||
// Copyright 2012 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 reflect_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func ExampleMakeFunc() {
|
||||
// swap is the implementation passed to MakeFunc.
|
||||
// It must work in terms of reflect.Values so that it is possible
|
||||
// to write code without knowing beforehand what the types
|
||||
// will be.
|
||||
swap := func(in []reflect.Value) []reflect.Value {
|
||||
return []reflect.Value{in[1], in[0]}
|
||||
}
|
||||
|
||||
// makeSwap expects fptr to be a pointer to a nil function.
|
||||
// It sets that pointer to a new function created with MakeFunc.
|
||||
// When the function is invoked, reflect turns the arguments
|
||||
// into Values, calls swap, and then turns swap's result slice
|
||||
// into the values returned by the new function.
|
||||
makeSwap := func(fptr interface{}) {
|
||||
// fptr is a pointer to a function.
|
||||
// Obtain the function value itself (likely nil) as a reflect.Value
|
||||
// so that we can query its type and then set the value.
|
||||
fn := reflect.ValueOf(fptr).Elem()
|
||||
|
||||
// Make a function of the right type.
|
||||
v := reflect.MakeFunc(fn.Type(), swap)
|
||||
|
||||
// Assign it to the value fn represents.
|
||||
fn.Set(v)
|
||||
}
|
||||
|
||||
// Make and call a swap function for ints.
|
||||
var intSwap func(int, int) (int, int)
|
||||
makeSwap(&intSwap)
|
||||
fmt.Println(intSwap(0, 1))
|
||||
|
||||
// Make and call a swap function for float64s.
|
||||
var floatSwap func(float64, float64) (float64, float64)
|
||||
makeSwap(&floatSwap)
|
||||
fmt.Println(floatSwap(2.72, 3.14))
|
||||
|
||||
// Output:
|
||||
// 1 0
|
||||
// 3.14 2.72
|
||||
}
|
156
src/pkg/reflect/makefunc.go
Normal file
156
src/pkg/reflect/makefunc.go
Normal file
@ -0,0 +1,156 @@
|
||||
// Copyright 2012 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.
|
||||
|
||||
// MakeFunc implementation.
|
||||
|
||||
package reflect
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// makeFuncImpl is the closure value implementing the function
|
||||
// returned by MakeFunc.
|
||||
type makeFuncImpl struct {
|
||||
// References visible to the garbage collector.
|
||||
// The code array below contains the same references
|
||||
// embedded in the machine code.
|
||||
typ *commonType
|
||||
fn func([]Value) []Value
|
||||
|
||||
// code is the actual machine code invoked for the closure.
|
||||
code [40]byte
|
||||
}
|
||||
|
||||
// MakeFunc returns a new function of the given Type
|
||||
// that wraps the function fn. When called, that new function
|
||||
// does the following:
|
||||
//
|
||||
// - converts its arguments to a list of Values args.
|
||||
// - runs results := fn(args).
|
||||
// - returns the results as a slice of Values, one per formal result.
|
||||
//
|
||||
// The implementation fn can assume that the argument Value slice
|
||||
// has the number and type of arguments given by typ.
|
||||
// If typ describes a variadic function, the final Value is itself
|
||||
// a slice representing the variadic arguments, as in the
|
||||
// body of a variadic function. The result Value slice returned by fn
|
||||
// must have the number and type of results given by typ.
|
||||
//
|
||||
// The Value.Call method allows the caller to invoke a typed function
|
||||
// in terms of Values; in contrast, MakeFunc allows the caller to implement
|
||||
// a typed function in terms of Values.
|
||||
//
|
||||
// The Examples section of the documentation includes an illustration
|
||||
// of how to use MakeFunc to build a swap function for different types.
|
||||
//
|
||||
func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value {
|
||||
if typ.Kind() != Func {
|
||||
panic("reflect: call of MakeFunc with non-Func type")
|
||||
}
|
||||
|
||||
// Gather type pointer and function pointers
|
||||
// for use in hand-assembled closure.
|
||||
t := typ.common()
|
||||
|
||||
// Create function impl.
|
||||
// We don't need to save a pointer to makeFuncStub, because it is in
|
||||
// the text segment and cannot be garbage collected.
|
||||
impl := &makeFuncImpl{
|
||||
typ: t,
|
||||
fn: fn,
|
||||
}
|
||||
|
||||
tptr := unsafe.Pointer(t)
|
||||
fptr := *(*unsafe.Pointer)(unsafe.Pointer(&fn))
|
||||
tmp := makeFuncStub
|
||||
stub := *(*unsafe.Pointer)(unsafe.Pointer(&tmp))
|
||||
|
||||
// Create code. Copy template and fill in pointer values.
|
||||
switch runtime.GOARCH {
|
||||
default:
|
||||
panic("reflect.MakeFunc: unexpected GOARCH: " + runtime.GOARCH)
|
||||
|
||||
case "amd64":
|
||||
copy(impl.code[:], amd64CallStub)
|
||||
*(*unsafe.Pointer)(unsafe.Pointer(&impl.code[2])) = tptr
|
||||
*(*unsafe.Pointer)(unsafe.Pointer(&impl.code[12])) = fptr
|
||||
*(*unsafe.Pointer)(unsafe.Pointer(&impl.code[22])) = stub
|
||||
|
||||
case "386":
|
||||
copy(impl.code[:], _386CallStub)
|
||||
*(*unsafe.Pointer)(unsafe.Pointer(&impl.code[1])) = tptr
|
||||
*(*unsafe.Pointer)(unsafe.Pointer(&impl.code[6])) = fptr
|
||||
*(*unsafe.Pointer)(unsafe.Pointer(&impl.code[11])) = stub
|
||||
|
||||
case "arm":
|
||||
code := (*[10]uintptr)(unsafe.Pointer(&impl.code[0]))
|
||||
copy(code[:], armCallStub)
|
||||
code[len(armCallStub)] = uintptr(tptr)
|
||||
code[len(armCallStub)+1] = uintptr(fptr)
|
||||
code[len(armCallStub)+2] = uintptr(stub)
|
||||
|
||||
cacheflush(&impl.code[0], &impl.code[len(impl.code)-1])
|
||||
}
|
||||
|
||||
return Value{t, unsafe.Pointer(&impl.code[0]), flag(Func) << flagKindShift}
|
||||
}
|
||||
|
||||
func cacheflush(start, end *byte)
|
||||
|
||||
// makeFuncStub is an assembly function used by the code generated
|
||||
// and returned from MakeFunc. The code returned from makeFunc
|
||||
// does, schematically,
|
||||
//
|
||||
// MOV $typ, R0
|
||||
// MOV $fn, R1
|
||||
// MOV $0(FP), R2
|
||||
// JMP makeFuncStub
|
||||
//
|
||||
// That is, it copies the type and function pointer passed to MakeFunc
|
||||
// into the first two machine registers and then copies the argument frame
|
||||
// pointer into the third. Then it jumps to makeFuncStub, which calls callReflect
|
||||
// with those arguments. Using a jmp to makeFuncStub instead of making the
|
||||
// call directly keeps the allocated code simpler but, perhaps more
|
||||
// importantly, also keeps the allocated PCs off the call stack.
|
||||
// Nothing ever returns to the allocated code.
|
||||
func makeFuncStub()
|
||||
|
||||
// amd64CallStub is the MakeFunc code template for amd64 machines.
|
||||
var amd64CallStub = []byte{
|
||||
// MOVQ $constant, AX
|
||||
0x48, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// MOVQ $constant, BX
|
||||
0x48, 0xbb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// MOVQ $constant, DX
|
||||
0x48, 0xba, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// LEAQ 8(SP), CX (argument frame)
|
||||
0x48, 0x8d, 0x4c, 0x24, 0x08,
|
||||
// JMP *DX
|
||||
0xff, 0xe2,
|
||||
}
|
||||
|
||||
// _386CallStub is the MakeFunc code template for 386 machines.
|
||||
var _386CallStub = []byte{
|
||||
// MOVL $constant, AX
|
||||
0xb8, 0x00, 0x00, 0x00, 0x00,
|
||||
// MOVL $constant, BX
|
||||
0xbb, 0x00, 0x00, 0x00, 0x00,
|
||||
// MOVL $constant, DX
|
||||
0xba, 0x00, 0x00, 0x00, 0x00,
|
||||
// LEAL 4(SP), CX (argument frame)
|
||||
0x8d, 0x4c, 0x24, 0x04,
|
||||
// JMP *DX
|
||||
0xff, 0xe2,
|
||||
}
|
||||
|
||||
// armCallStub is the MakeFunc code template for arm machines.
|
||||
var armCallStub = []uintptr{
|
||||
0xe59f000c, // MOVW 0x14(PC), R0
|
||||
0xe59f100c, // MOVW 0x14(PC), R1
|
||||
0xe28d2004, // MOVW $4(SP), R2
|
||||
0xe59ff008, // MOVW 0x10(PC), PC
|
||||
0xeafffffe, // B 0(PC), just in case
|
||||
}
|
@ -547,6 +547,82 @@ func (v Value) call(method string, in []Value) []Value {
|
||||
return ret
|
||||
}
|
||||
|
||||
// callReflect is the call implementation used by a function
|
||||
// returned by MakeFunc. In many ways it is the opposite of the
|
||||
// method Value.call above. The method above converts a call using Values
|
||||
// into a call of a function with a concrete argument frame, while
|
||||
// callReflect converts a call of a function with a concrete argument
|
||||
// frame into a call using Values.
|
||||
// It is in this file so that it can be next to the call method above.
|
||||
// The remainder of the MakeFunc implementation is in makefunc.go.
|
||||
func callReflect(ftyp *funcType, f func([]Value) []Value, frame unsafe.Pointer) {
|
||||
// Copy argument frame into Values.
|
||||
ptr := frame
|
||||
off := uintptr(0)
|
||||
in := make([]Value, 0, len(ftyp.in))
|
||||
for _, arg := range ftyp.in {
|
||||
typ := toCommonType(arg)
|
||||
off += -off & uintptr(typ.align-1)
|
||||
v := Value{typ, nil, flag(typ.Kind()) << flagKindShift}
|
||||
if typ.size <= ptrSize {
|
||||
// value fits in word.
|
||||
v.val = unsafe.Pointer(loadIword(unsafe.Pointer(uintptr(ptr)+off), typ.size))
|
||||
} else {
|
||||
// value does not fit in word.
|
||||
// Must make a copy, because f might keep a reference to it,
|
||||
// and we cannot let f keep a reference to the stack frame
|
||||
// after this function returns, not even a read-only reference.
|
||||
v.val = unsafe_New(typ)
|
||||
memmove(v.val, unsafe.Pointer(uintptr(ptr)+off), typ.size)
|
||||
v.flag |= flagIndir
|
||||
}
|
||||
in = append(in, v)
|
||||
off += typ.size
|
||||
}
|
||||
|
||||
// Call underlying function.
|
||||
out := f(in)
|
||||
if len(out) != len(ftyp.out) {
|
||||
panic("reflect: wrong return count from function created by MakeFunc")
|
||||
}
|
||||
|
||||
// Copy results back into argument frame.
|
||||
if len(ftyp.out) > 0 {
|
||||
off += -off & (ptrSize - 1)
|
||||
for i, arg := range ftyp.out {
|
||||
typ := toCommonType(arg)
|
||||
v := out[i]
|
||||
if v.typ != typ {
|
||||
panic("reflect: function created by MakeFunc using " + funcName(f) +
|
||||
" returned wrong type: have " +
|
||||
out[i].typ.String() + " for " + typ.String())
|
||||
}
|
||||
if v.flag&flagRO != 0 {
|
||||
panic("reflect: function created by MakeFunc using " + funcName(f) +
|
||||
" returned value obtained from unexported field")
|
||||
}
|
||||
off += -off & uintptr(typ.align-1)
|
||||
addr := unsafe.Pointer(uintptr(ptr) + off)
|
||||
if v.flag&flagIndir == 0 {
|
||||
storeIword(addr, iword(v.val), typ.size)
|
||||
} else {
|
||||
memmove(addr, v.val, typ.size)
|
||||
}
|
||||
off += typ.size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// funcName returns the name of f, for use in error messages.
|
||||
func funcName(f func([]Value) []Value) string {
|
||||
pc := *(*uintptr)(unsafe.Pointer(&f))
|
||||
rf := runtime.FuncForPC(pc)
|
||||
if rf != nil {
|
||||
return rf.Name()
|
||||
}
|
||||
return "closure"
|
||||
}
|
||||
|
||||
// Cap returns v's capacity.
|
||||
// It panics if v's Kind is not Array, Chan, or Slice.
|
||||
func (v Value) Cap() int {
|
||||
|
Loading…
Reference in New Issue
Block a user