]> Cypherpunks repositories - gostls13.git/commitdiff
reflect: add MakeFunc (API CHANGE)
authorRuss Cox <rsc@golang.org>
Tue, 25 Sep 2012 00:06:32 +0000 (20:06 -0400)
committerRuss Cox <rsc@golang.org>
Tue, 25 Sep 2012 00:06:32 +0000 (20:06 -0400)
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
src/pkg/reflect/asm_386.s [new file with mode: 0644]
src/pkg/reflect/asm_amd64.s [new file with mode: 0644]
src/pkg/reflect/asm_arm.s [new file with mode: 0644]
src/pkg/reflect/example_test.go [new file with mode: 0644]
src/pkg/reflect/makefunc.go [new file with mode: 0644]
src/pkg/reflect/value.go

index 674285d9e0c6a0486357a198d2fea99524a8b8bf..5dad071b3cc1ad3766c2d32daf21a50b7a3db4e6 100644 (file)
@@ -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 (file)
index 0000000..30ff341
--- /dev/null
@@ -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 (file)
index 0000000..ce33e78
--- /dev/null
@@ -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 (file)
index 0000000..3f1814c
--- /dev/null
@@ -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 (file)
index 0000000..62455c0
--- /dev/null
@@ -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 (file)
index 0000000..98b6efd
--- /dev/null
@@ -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
+}
index 45af13dd08a87992d5e24c24a56841d12f131dbc..74addd19530c34a3f38c0ec983d24b6811f7ead9 100644 (file)
@@ -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 {