From ba4625c66f5d27e1093758b182c1cd5674c4e67b Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Mon, 24 Sep 2012 20:06:32 -0400 Subject: [PATCH] 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 --- src/pkg/reflect/all_test.go | 47 ++++++++-- src/pkg/reflect/asm_386.s | 18 ++++ src/pkg/reflect/asm_amd64.s | 18 ++++ src/pkg/reflect/asm_arm.s | 17 ++++ src/pkg/reflect/example_test.go | 52 +++++++++++ src/pkg/reflect/makefunc.go | 156 ++++++++++++++++++++++++++++++++ src/pkg/reflect/value.go | 76 ++++++++++++++++ 7 files changed, 377 insertions(+), 7 deletions(-) create mode 100644 src/pkg/reflect/asm_386.s create mode 100644 src/pkg/reflect/asm_amd64.s create mode 100644 src/pkg/reflect/asm_arm.s create mode 100644 src/pkg/reflect/example_test.go create mode 100644 src/pkg/reflect/makefunc.go diff --git a/src/pkg/reflect/all_test.go b/src/pkg/reflect/all_test.go index 674285d9e0..5dad071b3c 100644 --- a/src/pkg/reflect/all_test.go +++ b/src/pkg/reflect/all_test.go @@ -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) } } diff --git a/src/pkg/reflect/asm_386.s b/src/pkg/reflect/asm_386.s new file mode 100644 index 0000000000..30ff3413ac --- /dev/null +++ b/src/pkg/reflect/asm_386.s @@ -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 diff --git a/src/pkg/reflect/asm_amd64.s b/src/pkg/reflect/asm_amd64.s new file mode 100644 index 0000000000..ce33e78c24 --- /dev/null +++ b/src/pkg/reflect/asm_amd64.s @@ -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 diff --git a/src/pkg/reflect/asm_arm.s b/src/pkg/reflect/asm_arm.s new file mode 100644 index 0000000000..3f1814cf06 --- /dev/null +++ b/src/pkg/reflect/asm_arm.s @@ -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) diff --git a/src/pkg/reflect/example_test.go b/src/pkg/reflect/example_test.go new file mode 100644 index 0000000000..62455c00ad --- /dev/null +++ b/src/pkg/reflect/example_test.go @@ -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 +} diff --git a/src/pkg/reflect/makefunc.go b/src/pkg/reflect/makefunc.go new file mode 100644 index 0000000000..98b6efd5c7 --- /dev/null +++ b/src/pkg/reflect/makefunc.go @@ -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 +} diff --git a/src/pkg/reflect/value.go b/src/pkg/reflect/value.go index 45af13dd08..74addd1953 100644 --- a/src/pkg/reflect/value.go +++ b/src/pkg/reflect/value.go @@ -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 {