diff --git a/ssa/builder.go b/ssa/builder.go index 9f304b4ba4a..37f71f0a6fa 100644 --- a/ssa/builder.go +++ b/ssa/builder.go @@ -449,6 +449,20 @@ func (b *Builder) builtin(fn *Function, name string, args []ast.Expr, typ types. // func (b *Builder) selector(fn *Function, e *ast.SelectorExpr, wantAddr, escaping bool) Value { id := MakeId(e.Sel.Name, fn.Pkg.Types) + + // Bound method closure? (e.m where m is a method) + if !wantAddr { + if m, recv := b.findMethod(fn, e.X, id); m != nil { + c := &MakeClosure{ + Fn: makeBoundMethodThunk(b.Prog, m, recv), + Bindings: []Value{recv}, + } + c.setPos(e.Sel.Pos()) + c.setType(fn.Pkg.TypeOf(e)) + return fn.emit(c) + } + } + st := fn.Pkg.TypeOf(e.X).Deref().Underlying().(*types.Struct) index := -1 for i, n := 0, st.NumFields(); i < n; i++ { @@ -704,7 +718,8 @@ func (b *Builder) expr(fn *Function, e ast.Expr) Value { v := &MakeClosure{Fn: fn2} v.setType(fn.Pkg.TypeOf(e)) for _, fv := range fn2.FreeVars { - v.Bindings = append(v.Bindings, fv.Outer) + v.Bindings = append(v.Bindings, fv.outer) + fv.outer = nil } return fn.emit(v) @@ -836,7 +851,7 @@ func (b *Builder) expr(fn *Function, e ast.Expr) Value { return makeImethodThunk(b.Prog, typ, id) } - // e.f where e is an expression. + // e.f where e is an expression. f may be a method. return b.selector(fn, e, false, false) case *ast.IndexExpr: @@ -894,6 +909,41 @@ func (b *Builder) stmtList(fn *Function, list []ast.Stmt) { } } +// findMethod returns the method and receiver for a call base.id(). +// It locates the method using the method-set for base's type, +// and emits code for the receiver, handling the cases where +// the formal and actual parameter's pointerness are unequal. +// +// findMethod returns (nil, nil) if no such method was found. +// +func (b *Builder) findMethod(fn *Function, base ast.Expr, id Id) (*Function, Value) { + typ := fn.Pkg.TypeOf(base) + + // Consult method-set of X. + if m := b.Prog.MethodSet(typ)[id]; m != nil { + aptr := isPointer(typ) + fptr := isPointer(m.Signature.Recv().Type()) + if aptr == fptr { + // Actual's and formal's "pointerness" match. + return m, b.expr(fn, base) + } + // Actual is a pointer, formal is not. + // Load a copy. + return m, emitLoad(fn, b.expr(fn, base)) + } + if !isPointer(typ) { + // Consult method-set of *X. + if m := b.Prog.MethodSet(pointer(typ))[id]; m != nil { + // A method found only in MS(*X) must have a + // pointer formal receiver; but the actual + // value is not a pointer. + // Implicit & -- possibly escaping. + return m, b.addr(fn, base, true).(address).addr + } + } + return nil, nil +} + // setCallFunc populates the function parts of a CallCommon structure // (Func, Method, Recv, Args[0]) based on the kind of invocation // occurring in e. @@ -934,45 +984,20 @@ func (b *Builder) setCallFunc(fn *Function, e *ast.CallExpr, c *CallCommon) { return } + id := MakeId(sel.Sel.Name, fn.Pkg.Types) + // Let X be the type of x. - typ := fn.Pkg.TypeOf(sel.X) // Case 2: x.f(): a statically dispatched call to a method // from the method-set of X or perhaps *X (if x is addressable // but not a pointer). - id := MakeId(sel.Sel.Name, fn.Pkg.Types) - // Consult method-set of X. - if m := b.Prog.MethodSet(typ)[id]; m != nil { - var recv Value - aptr := isPointer(typ) - fptr := isPointer(m.Signature.Recv().Type()) - if aptr == fptr { - // Actual's and formal's "pointerness" match. - recv = b.expr(fn, sel.X) - } else { - // Actual is a pointer, formal is not. - // Load a copy. - recv = emitLoad(fn, b.expr(fn, sel.X)) - } + if m, recv := b.findMethod(fn, sel.X, id); m != nil { c.Func = m c.Args = append(c.Args, recv) return } - if !isPointer(typ) { - // Consult method-set of *X. - if m := b.Prog.MethodSet(pointer(typ))[id]; m != nil { - // A method found only in MS(*X) must have a - // pointer formal receiver; but the actual - // value is not a pointer. - // Implicit & -- possibly escaping. - recv := b.addr(fn, sel.X, true).(address).addr - c.Func = m - c.Args = append(c.Args, recv) - return - } - } - switch t := typ.Underlying().(type) { + switch t := fn.Pkg.TypeOf(sel.X).Underlying().(type) { case *types.Struct, *types.Pointer: // Case 3: x.f() where x.f is a function value in a // struct field f; not a method call. f is a 'var' @@ -2562,7 +2587,7 @@ func (b *Builder) createPackageImpl(typkg *types.Package, importPath string, fil // Add initializer guard variable. initguard := &Global{ Pkg: p, - Name_: "init·guard", + Name_: "init$guard", Type_: pointer(tBool), } p.Members[initguard.Name()] = initguard @@ -2671,13 +2696,13 @@ func (b *Builder) BuildPackage(p *Package) { return // nothing to do } if b.Context.Mode&LogSource != 0 { - defer logStack("build package %s", p.Types.Path)() + defer logStack("build package %s", p.Types.Path())() } init := p.Init init.startBody() // Make init() skip if package is already initialized. - initguard := p.Var("init·guard") + initguard := p.Var("init$guard") doinit := init.newBasicBlock("init.start") done := init.newBasicBlock("init.done") emitIf(init, emitLoad(init, initguard), done, doinit) diff --git a/ssa/emit.go b/ssa/emit.go index 786ff67a481..dc639fc4904 100644 --- a/ssa/emit.go +++ b/ssa/emit.go @@ -280,16 +280,13 @@ func emitTypeTest(f *Function, x Value, t types.Type) Value { return f.emit(a) } -// emitTailCall emits to f a function call in tail position, -// passing on all but the first formal parameter to f as actual -// values in the call. Intended for delegating bridge methods. +// emitTailCall emits to f a function call in tail position. The +// caller is responsible for all fields of 'call' except its type. +// Intended for delegating bridge methods. // Precondition: f does/will not use deferred procedure calls. // Postcondition: f.currentBlock is nil. // func emitTailCall(f *Function, call *Call) { - for _, arg := range f.Params[1:] { - call.Call.Args = append(call.Call.Args, arg) - } tresults := f.Signature.Results() nr := tresults.Len() if nr == 1 { diff --git a/ssa/example_test.go b/ssa/example_test.go index 4fa9ba70fd1..fe19b4e9468 100644 --- a/ssa/example_test.go +++ b/ssa/example_test.go @@ -71,18 +71,18 @@ func main() { // Output: // // Package main: - // var init·guard *bool - // func main func() - // const message message = "Hello, World!":untyped string + // var init$guard *bool + // func main func() + // const message message = "Hello, World!":untyped string // // # Name: main.init - // # Declared at - + // # Synthetic // func init(): // .0.entry: P:0 S:2 - // t0 = *init·guard bool + // t0 = *init$guard bool // if t0 goto 2.init.done else 1.init.start // .1.init.start: P:1 S:1 - // *init·guard = true:bool + // *init$guard = true:bool // t1 = fmt.init() () // jump 2.init.done // .2.init.done: P:2 S:0 diff --git a/ssa/func.go b/ssa/func.go index 0b2c8213886..34340ca9392 100644 --- a/ssa/func.go +++ b/ssa/func.go @@ -8,6 +8,7 @@ import ( "go/token" "io" "os" + "strings" "code.google.com/p/go.tools/go/types" ) @@ -395,19 +396,8 @@ func (f *Function) addLocal(typ types.Type, pos token.Pos) *Alloc { // func (f *Function) lookup(obj types.Object, escaping bool) Value { if v, ok := f.objects[obj]; ok { - if escaping { - // Walk up the chain of Captures. - x := v - for { - if c, ok := x.(*Capture); ok { - x = c.Outer - } else { - break - } - } - // By construction, all captures are ultimately Allocs in the - // naive SSA form. Parameters are pre-spilled to the stack. - x.(*Alloc).Heap = true + if alloc, ok := v.(*Alloc); ok && escaping { + alloc.Heap = true } return v // function-local var (address) } @@ -417,7 +407,12 @@ func (f *Function) lookup(obj types.Object, escaping bool) Value { if f.Enclosing == nil { panic("no Value for type.Object " + obj.Name()) } - v := &Capture{Outer: f.Enclosing.lookup(obj, true)} // escaping + outer := f.Enclosing.lookup(obj, true) // escaping + v := &Capture{ + Name_: outer.Name(), + Type_: outer.Type(), + outer: outer, + } f.objects[obj] = v f.FreeVars = append(f.FreeVars, v) return v @@ -442,6 +437,7 @@ func (f *Function) emit(instr Instruction) Value { // "(*exp/ssa.Ret).Block" // a bridge method // "(ssa.Instruction).Block" // an interface method thunk // "func@5.32" // an anonymous function +// "bound$(*T).f" // a bound method thunk // func (f *Function) FullName() string { return f.fullName(nil) @@ -449,6 +445,8 @@ func (f *Function) FullName() string { // Like FullName, but if from==f.Pkg, suppress package qualification. func (f *Function) fullName(from *Package) string { + // TODO(adonovan): expose less fragile case discrimination. + // Anonymous? if f.Enclosing != nil { return f.Name_ @@ -461,6 +459,8 @@ func (f *Function) fullName(from *Package) string { var recvType types.Type if recv != nil { recvType = recv.Type() // bridge method + } else if strings.HasPrefix(f.Name_, "bound$") { + return f.Name_ // bound method thunk } else { recvType = f.Params[0].Type() // interface method thunk } @@ -527,7 +527,11 @@ func writeSignature(w io.Writer, name string, sig *types.Signature, params []*Pa // func (f *Function) DumpTo(w io.Writer) { fmt.Fprintf(w, "# Name: %s\n", f.FullName()) - fmt.Fprintf(w, "# Declared at %s\n", f.Prog.Files.Position(f.Pos())) + if pos := f.Pos(); pos.IsValid() { + fmt.Fprintf(w, "# Declared at %s\n", f.Prog.Files.Position(pos)) + } else { + fmt.Fprintln(w, "# Synthetic") + } if f.Enclosing != nil { fmt.Fprintf(w, "# Parent: %s\n", f.Enclosing.Name()) diff --git a/ssa/interp/interp_test.go b/ssa/interp/interp_test.go index 3b9c3f89f1c..75de8b8d9da 100644 --- a/ssa/interp/interp_test.go +++ b/ssa/interp/interp_test.go @@ -130,6 +130,7 @@ var gorootTests = []string{ var testdataTests = []string{ "coverage.go", "mrvchain.go", + "boundmeth.go", } func run(t *testing.T, dir, input string) bool { diff --git a/ssa/interp/testdata/boundmeth.go b/ssa/interp/testdata/boundmeth.go new file mode 100644 index 00000000000..947737f1db3 --- /dev/null +++ b/ssa/interp/testdata/boundmeth.go @@ -0,0 +1,88 @@ +// Tests of bound method closures. + +package main + +func assert(b bool) { + if !b { + panic("oops") + } +} + +type I int + +func (i I) add(x int) int { + return int(i) + x +} + +func valueReceiver() { + var three I = 3 + assert(three.add(5) == 8) + var add3 func(int) int = three.add + assert(add3(5) == 8) +} + +type S struct{ x int } + +func (s *S) incr() { + s.x++ +} + +func (s *S) get() int { + return s.x +} + +func pointerReceiver() { + ps := new(S) + incr := ps.incr + get := ps.get + assert(get() == 0) + incr() + incr() + incr() + assert(get() == 3) +} + +func addressibleValuePointerReceiver() { + var s S + incr := s.incr + get := s.get + assert(get() == 0) + incr() + incr() + incr() + assert(get() == 3) +} + +type S2 struct { + S +} + +func promotedReceiver() { + var s2 S2 + incr := s2.incr + get := s2.get + assert(get() == 0) + incr() + incr() + incr() + assert(get() == 3) +} + +func anonStruct() { + var s struct{ S } + incr := s.incr + get := s.get + assert(get() == 0) + incr() + incr() + incr() + assert(get() == 3) +} + +func main() { + valueReceiver() + pointerReceiver() + addressibleValuePointerReceiver() + promotedReceiver() + anonStruct() +} diff --git a/ssa/print.go b/ssa/print.go index 2a61e413d4f..92d5f860270 100644 --- a/ssa/print.go +++ b/ssa/print.go @@ -48,7 +48,7 @@ func relName(v Value, i Instruction) string { // It never appears in disassembly, which uses Value.Name(). func (v *Literal) String() string { - return fmt.Sprintf("literal %s", v.Name()) + return v.Name() } func (v *Parameter) String() string { @@ -60,15 +60,15 @@ func (v *Capture) String() string { } func (v *Global) String() string { - return fmt.Sprintf("global %s : %s", v.Name(), v.Type()) + return v.FullName() } func (v *Builtin) String() string { - return fmt.Sprintf("builtin %s : %s", v.Name(), v.Type()) + return fmt.Sprintf("builtin %s", v.Name()) } func (v *Function) String() string { - return fmt.Sprintf("function %s : %s", v.Name(), v.Type()) + return v.FullName() } // FullName returns g's package-qualified name. diff --git a/ssa/promote.go b/ssa/promote.go index 1d6f4fa4e52..1b8a9837f92 100644 --- a/ssa/promote.go +++ b/ssa/promote.go @@ -105,9 +105,7 @@ func (p *Program) MethodSet(typ types.Type) MethodSet { // TODO(adonovan): Using Types as map keys doesn't properly // de-dup. e.g. *NamedType are canonical but *Struct and - // others are not. Need to de-dup based on using a two-level - // hash-table with hash function types.Type.String and - // equivalence relation types.IsIdentical. + // others are not. Need to de-dup using typemap.T. mset := p.methodSets[typ] if mset == nil { mset = buildMethodSet(p, typ) @@ -308,13 +306,14 @@ func makeBridgeMethod(prog *Program, typ types.Type, cand *candidate) *Function var c Call if cand.concrete != nil { c.Call.Func = cand.concrete - fn.pos = c.Call.Func.(*Function).pos // TODO(adonovan): fix: wrong. - c.Call.pos = fn.pos // TODO(adonovan): fix: wrong. c.Call.Args = append(c.Call.Args, v) } else { c.Call.Recv = v c.Call.Method = 0 } + for _, arg := range fn.Params[1:] { + c.Call.Args = append(c.Call.Args, arg) + } emitTailCall(fn, &c) fn.finishBody() return fn @@ -339,7 +338,7 @@ func createParams(fn *Function) { // Thunks for standalone interface methods ---------------------------------------- -// makeImethodThunk returns a synthetic thunk function permitting an +// makeImethodThunk returns a synthetic thunk function permitting a // method id of interface typ to be called like a standalone function, // e.g.: // @@ -354,11 +353,6 @@ func createParams(fn *Function) { // return i.f(x, ...) // } // -// The generated thunks do not belong to any package. (Arguably they -// belong in the package that defines the interface, but we have no -// way to determine that on demand; we'd have to create all possible -// thunks a priori.) -// // TODO(adonovan): opt: currently the stub is created even when used // in call position: I.f(i, 0). Clearly this is suboptimal. // @@ -376,13 +370,61 @@ func makeImethodThunk(prog *Program, typ types.Type, id Id) *Function { Signature: &sig, Prog: prog, } - // TODO(adonovan): set fn.Pos to location of interface method ast.Field. fn.startBody() fn.addParam("recv", typ) createParams(fn) var c Call c.Call.Method = index c.Call.Recv = fn.Params[0] + for _, arg := range fn.Params[1:] { + c.Call.Args = append(c.Call.Args, arg) + } + emitTailCall(fn, &c) + fn.finishBody() + return fn +} + +// Thunks for bound methods ---------------------------------------- + +// makeBoundMethodThunk returns a synthetic thunk function that +// delegates to a concrete method. The thunk has one free variable, +// the method's receiver. Use MakeClosure with such a thunk to +// construct a bound-method closure. +// e.g.: +// +// type T int +// func (t T) meth() +// var t T +// f := t.meth +// f() // calls t.meth() +// +// f is a closure of a synthetic thunk defined as if by: +// +// f := func() { return t.meth() } +// +// TODO(adonovan): memoize creation of these functions in the Program. +// +func makeBoundMethodThunk(prog *Program, meth *Function, recv Value) *Function { + if prog.mode&LogSource != 0 { + defer logStack("makeBoundMethodThunk %s", meth)() + } + s := meth.Signature + fn := &Function{ + Name_: "bound$" + meth.FullName(), + Signature: types.NewSignature(nil, s.Params(), s.Results(), s.IsVariadic()), // drop recv + Prog: prog, + } + + cap := &Capture{Name_: "recv", Type_: recv.Type()} + fn.FreeVars = []*Capture{cap} + fn.startBody() + createParams(fn) + var c Call + c.Call.Func = meth + c.Call.Args = []Value{cap} + for _, arg := range fn.Params { + c.Call.Args = append(c.Call.Args, arg) + } emitTailCall(fn, &c) fn.finishBody() return fn diff --git a/ssa/ssa.go b/ssa/ssa.go index 3a24b0ade5f..c595fc82adc 100644 --- a/ssa/ssa.go +++ b/ssa/ssa.go @@ -242,7 +242,7 @@ type Instruction interface { // local variables ("free variables") has Capture parameters. Such // functions cannot be called directly but require a value created by // MakeClosure which, via its Bindings, supplies values for these -// parameters. Captures are always addresses. +// parameters. // // If the function is a method (Signature.Recv() != nil) then the first // element of Params is the receiver parameter. @@ -306,15 +306,26 @@ type BasicBlock struct { // Pure values ---------------------------------------- -// A Capture is a pointer to a lexically enclosing local variable. +// A Capture represents a free variable of the function to which it +// belongs. // -// The referent of a capture is an Alloc or another Capture and is -// always considered potentially escaping, so Captures are always -// addresses in the heap, and have pointer types. +// Captures are used to implement anonymous functions, whose free +// variables are lexically captured in a closure formed by +// MakeClosure. The referent of such a capture is an Alloc or another +// Capture and is considered a potentially escaping heap address, with +// pointer type. +// +// Captures are also used to implement bound method closures. Such a +// capture represents the receiver value and may be of any type that +// has concrete methods. // type Capture struct { - Outer Value // the Value captured from the enclosing context. + Name_ string + Type_ types.Type referrers []Instruction + + // Transiently needed during building. + outer Value // the Value captured from the enclosing context. } // A Parameter represents an input parameter of a function. @@ -587,20 +598,17 @@ type MakeInterface struct { Methods MethodSet // method set of (non-interface) X } -// The MakeClosure instruction yields an anonymous function value -// whose code is Fn and whose lexical capture slots are populated by -// Bindings. -// -// By construction, all captured variables are addresses of variables -// allocated with 'new', i.e. Alloc(Heap=true). +// The MakeClosure instruction yields a closure value whose code is +// Fn and whose free variables' values are supplied by Bindings. // // Type() returns a (possibly named) *types.Signature. // -// Pos() returns the ast.FuncLit.Type.Func of the function literal -// that created this closure. +// Pos() returns the ast.FuncLit.Type.Func for a function literal +// closure or the ast.SelectorExpr.Sel for a bound method closure. // // Example printed form: // t0 = make closure anon@1.2 [x y z] +// t1 = make closure bound$(main.I).add [i] // type MakeClosure struct { Register @@ -1217,8 +1225,8 @@ func (v *Builtin) Type() types.Type { return v.Object.Type() } func (v *Builtin) Name() string { return v.Object.Name() } func (*Builtin) Referrers() *[]Instruction { return nil } -func (v *Capture) Type() types.Type { return v.Outer.Type() } -func (v *Capture) Name() string { return v.Outer.Name() } +func (v *Capture) Type() types.Type { return v.Type_ } +func (v *Capture) Name() string { return v.Name_ } func (v *Capture) Referrers() *[]Instruction { return &v.referrers } func (v *Global) Type() types.Type { return v.Type_ } diff --git a/ssa/util.go b/ssa/util.go index acb4d33757e..391dd14e9a7 100644 --- a/ssa/util.go +++ b/ssa/util.go @@ -53,6 +53,7 @@ func isPointer(typ types.Type) bool { } // pointer(typ) returns the type that is a pointer to typ. +// TODO(adonovan): inline and eliminate. func pointer(typ types.Type) *types.Pointer { return types.NewPointer(typ) }