diff --git a/go/types/types.go b/go/types/types.go index 52502dc5..0b0b372d 100644 --- a/go/types/types.go +++ b/go/types/types.go @@ -365,7 +365,7 @@ func NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named { // TypeName returns the type name for the named type t. func (t *Named) Obj() *TypeName { return t.obj } -// NumMethods returns the number of methods directly associated with named type t. +// NumMethods returns the number of explicit methods whose receiver is named type t. func (t *Named) NumMethods() int { return len(t.methods) } // Method returns the i'th method of named type t for 0 <= i < t.NumMethods(). diff --git a/ssa/builder.go b/ssa/builder.go index 576c7360..9db4d796 100644 --- a/ssa/builder.go +++ b/ssa/builder.go @@ -5,7 +5,7 @@ package ssa // SSA construction has two phases, CREATE and BUILD. In the CREATE phase // (create.go), all packages are constructed and type-checked and // definitions of all package members are created, method-sets are -// computed, and bridge methods are synthesized. The create phase +// computed, and wrapper methods are synthesized. The create phase // proceeds in topological order over the import dependency graph, // initiated by client calls to CreatePackages. // @@ -345,7 +345,7 @@ func (b *builder) selector(fn *Function, e *ast.SelectorExpr, wantAddr, escaping if !wantAddr { if m, recv := b.findMethod(fn, e.X, id); m != nil { c := &MakeClosure{ - Fn: makeBoundMethodThunk(fn.Prog, m, recv.Type()), + Fn: boundMethodWrapper(m), Bindings: []Value{recv}, } c.setPos(e.Sel.Pos()) @@ -743,8 +743,8 @@ func (b *builder) expr(fn *Function, e ast.Expr) Value { return m } - // T must be an interface; return method thunk. - return makeImethodThunk(fn.Prog, typ, id) + // T must be an interface; return wrapper. + return interfaceMethodWrapper(fn.Prog, typ, id) } // e.f where e is an expression. f may be a method. @@ -905,7 +905,7 @@ func (b *builder) setCallFunc(fn *Function, e *ast.CallExpr, c *CallCommon) { // Case 4: x.f() where a dynamically dispatched call // to an interface method f. f is a 'func' object in // the Methods of types.Interface X - c.Method, _ = methodIndex(t, id) + c.Method, _ = interfaceMethodIndex(t, id) c.Recv = b.expr(fn, sel.X) default: diff --git a/ssa/create.go b/ssa/create.go index 435621c0..df2dbd80 100644 --- a/ssa/create.go +++ b/ssa/create.go @@ -42,6 +42,8 @@ func NewProgram(fset *token.FileSet, mode BuilderMode) *Program { methodSets: make(map[types.Type]MethodSet), concreteMethods: make(map[*types.Func]*Function), indirectionWrappers: make(map[*Function]*Function), + boundMethodWrappers: make(map[*Function]*Function), + ifaceMethodWrappers: make(map[*types.Func]*Function), mode: mode, } @@ -130,8 +132,9 @@ func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) { pkg.Members[name] = fn } else { // Method declaration. - nt := recv.Type().Deref().(*types.Named) - _, method := methodIndex(nt, MakeId(name, pkg.Types)) + _, method := namedTypeMethodIndex( + recv.Type().Deref().(*types.Named), + MakeId(name, pkg.Types)) pkg.Prog.concreteMethods[method] = fn } diff --git a/ssa/emit.go b/ssa/emit.go index 943b27f7..e7ea5f12 100644 --- a/ssa/emit.go +++ b/ssa/emit.go @@ -286,7 +286,7 @@ func emitTypeTest(f *Function, x Value, t types.Type, pos token.Pos) Value { // 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. +// Intended for wrapper methods. // Precondition: f does/will not use deferred procedure calls. // Postcondition: f.currentBlock is nil. // diff --git a/ssa/func.go b/ssa/func.go index c6d0d712..8b843b1d 100644 --- a/ssa/func.go +++ b/ssa/func.go @@ -443,10 +443,10 @@ func (f *Function) emit(instr Instruction) Value { // "math.IsNaN" // a package-level function // "IsNaN" // intra-package reference to same // "(*sync.WaitGroup).Add" // a declared method -// "(*exp/ssa.Ret).Block" // a bridge method -// "(ssa.Instruction).Block" // an interface method thunk +// "(*exp/ssa.Ret).Block" // a promotion wrapper method +// "(ssa.Instruction).Block" // an interface method wrapper // "func@5.32" // an anonymous function -// "bound$(*T).f" // a bound method thunk +// "bound$(*T).f" // a bound method wrapper // func (f *Function) FullName() string { return f.fullName(nil) @@ -467,11 +467,11 @@ func (f *Function) fullName(from *Package) string { if f.Pkg == nil { var recvType types.Type if recv != nil { - recvType = recv.Type() // bridge method + recvType = recv.Type() // promotion wrapper } else if strings.HasPrefix(f.name, "bound$") { - return f.name // bound method thunk + return f.name // bound method wrapper } else { - recvType = f.Params[0].Type() // interface method thunk + recvType = f.Params[0].Type() // interface method wrapper } return fmt.Sprintf("(%s).%s", recvType, f.name) } diff --git a/ssa/print.go b/ssa/print.go index 49bec845..46189c9d 100644 --- a/ssa/print.go +++ b/ssa/print.go @@ -385,7 +385,7 @@ func (p *Package) DumpTo(w io.Writer) { // We display only mset(*T) since its keys // are a superset of mset(T)'s keys, though the // methods themselves may differ, - // e.g. different bridge methods. + // e.g. promotion wrappers. // NB: if mem.Type() is a pointer, mset is empty. mset := p.Prog.MethodSet(pointer(mem.Type())) var keys ids diff --git a/ssa/promote.go b/ssa/promote.go index 6109d603..d726ac97 100644 --- a/ssa/promote.go +++ b/ssa/promote.go @@ -1,9 +1,15 @@ package ssa -// This file defines algorithms related to "promotion" of field and -// method selector expressions e.x, such as desugaring implicit field -// and method selections, method-set computation, and construction of -// synthetic "bridge" methods. +// This file defines utilities for method-set computation, synthesis +// of wrapper methods, and desugaring of implicit field selections. +// +// Wrappers include: +// - promotion wrappers for methods of embedded fields. +// - interface method wrappers for closures of I.f. +// - bound method wrappers, for uncalled obj.Method closures. +// - indirection wrappers, for calls to T-methods on a *T receiver. + +// TODO(adonovan): rename to methods.go. import ( "code.google.com/p/go.tools/go/types" @@ -90,9 +96,9 @@ func (c candidate) ptrRecv() bool { return c.concrete != nil && isPointer(c.concrete.Signature.Recv().Type()) } -// MethodSet returns the method set for type typ, -// building bridge methods as needed for promoted methods -// and indirection wrappers for *T receiver types. +// MethodSet returns the method set for type typ, building wrapper +// methods as needed for embedded field promotion, and indirection for +// *T receiver types, etc. // A nil result indicates an empty set. // // Thread-safe. @@ -101,8 +107,8 @@ func (p *Program) MethodSet(typ types.Type) MethodSet { return nil } - p.methodSetsMu.Lock() - defer p.methodSetsMu.Unlock() + p.methodsMu.Lock() + defer p.methodsMu.Unlock() // TODO(adonovan): Using Types as map keys doesn't properly // de-dup. e.g. *Named are canonical but *Struct and @@ -200,7 +206,7 @@ func buildMethodSet(prog *Program, typ types.Type) MethodSet { list, next = next, list[:0] // reuse array } - // Build method sets and bridge methods. + // Build method sets and wrapper methods. mset := make(MethodSet) for id, cand := range cands { if cand == nil { @@ -223,7 +229,7 @@ func buildMethodSet(prog *Program, typ types.Type) MethodSet { method = indirectionWrapper(method) } } else { - method = makeBridgeMethod(prog, typ, cand) + method = promotionWrapper(prog, typ, cand) } if method == nil { panic("unexpected nil method in method set") @@ -251,7 +257,7 @@ func addCandidate(m map[Id]*candidate, id Id, method *types.Func, concrete *Func } } -// makeBridgeMethod creates a synthetic Function that delegates to a +// promotionWrapper returns a synthetic Function that delegates to a // "promoted" method. For example, given these decls: // // type A struct {B} @@ -259,24 +265,25 @@ func addCandidate(m map[Id]*candidate, id Id, method *types.Func, concrete *Func // type C ... // func (*C) f() // -// then makeBridgeMethod(typ=A, cand={method:(*C).f, path:[B,*C]}) will -// synthesize this bridge method: +// then promotionWrapper(typ=A, cand={method:(*C).f, path:[B,*C]}) will +// synthesize this wrapper method: // // func (a A) f() { return a.B.C->f() } // // prog is the program to which the synthesized method will belong. -// typ is the receiver type of the bridge method. cand is the +// typ is the receiver type of the wrapper method. cand is the // candidate method to be promoted; it may be concrete or an interface // method. // -func makeBridgeMethod(prog *Program, typ types.Type, cand *candidate) *Function { +func promotionWrapper(prog *Program, typ types.Type, cand *candidate) *Function { old := cand.method.Type().(*types.Signature) sig := types.NewSignature(types.NewVar(token.NoPos, nil, "recv", typ), old.Params(), old.Results(), old.IsVariadic()) + // TODO(adonovan): consult memoization cache keyed by (typ, cand). + // Needs typemap. Also needs hash/eq functions for 'candidate'. if prog.mode&LogSource != 0 { - defer logStack("makeBridgeMethod %s, %s, type %s", typ, cand, sig)() + defer logStack("promotionWrapper %s, %s, type %s", typ, cand, sig)() } - fn := &Function{ name: cand.method.Name(), Signature: sig, @@ -286,7 +293,7 @@ func makeBridgeMethod(prog *Program, typ types.Type, cand *candidate) *Function fn.addSpilledParam(sig.Recv()) createParams(fn) - // Each bridge method performs a sequence of selections, + // Each promotion wrapper performs a sequence of selections, // then tailcalls the promoted method. // We use pointer arithmetic (FieldAddr possibly followed by // Load) in preference to value extraction (Field possibly @@ -320,14 +327,9 @@ func makeBridgeMethod(prog *Program, typ types.Type, cand *candidate) *Function c.Call.Func = cand.concrete c.Call.Args = append(c.Call.Args, v) } else { - c.Call.Method = -1 iface := v.Type().Underlying().(*types.Interface) - for i, n := 0, iface.NumMethods(); i < n; i++ { - if iface.Method(i) == cand.method { - c.Call.Method = i - break - } - } + id := MakeId(cand.method.Name(), cand.method.Pkg()) + c.Call.Method, _ = interfaceMethodIndex(iface, id) c.Call.Recv = v } for _, arg := range fn.Params[1:] { @@ -338,7 +340,7 @@ func makeBridgeMethod(prog *Program, typ types.Type, cand *candidate) *Function return fn } -// createParams creates parameters for bridge method fn based on its +// createParams creates parameters for wrapper method fn based on its // Signature.Params, which do not include the receiver. // func createParams(fn *Function) { @@ -352,18 +354,18 @@ func createParams(fn *Function) { } } -// Thunks for standalone interface methods ---------------------------------------- +// Wrappers for standalone interface methods ---------------------------------- -// makeImethodThunk returns a synthetic thunk function permitting a +// interfaceMethodWrapper returns a synthetic wrapper function permitting a // method id of interface typ to be called like a standalone function, // e.g.: // // type I interface { f(x int) R } -// m := I.f // thunk +// m := I.f // wrapper // var i I // m(i, 0) // -// The thunk is defined as if by: +// The wrapper is defined as if by: // // func I.f(i I, x int, ...) R { // return i.f(x, ...) @@ -372,39 +374,49 @@ func createParams(fn *Function) { // TODO(adonovan): opt: currently the stub is created even when used // in call position: I.f(i, 0). Clearly this is suboptimal. // -// TODO(adonovan): memoize creation of these functions in the Program. +// EXCLUSIVE_LOCKS_ACQUIRED(meth.Prog.methodsMu) // -func makeImethodThunk(prog *Program, typ types.Type, id Id) *Function { - if prog.mode&LogSource != 0 { - defer logStack("makeImethodThunk %s.%s", typ, id)() +func interfaceMethodWrapper(prog *Program, typ types.Type, id Id) *Function { + index, meth := interfaceMethodIndex(typ.Underlying().(*types.Interface), id) + prog.methodsMu.Lock() + defer prog.methodsMu.Unlock() + // If one interface embeds another they'll share the same + // wrappers for common methods. This is safe, but it might + // confuse some tools because of the implicit interface + // conversion applied to the first argument. If this becomes + // a problem, we should include 'typ' in the memoization key. + fn, ok := prog.ifaceMethodWrappers[meth] + if !ok { + if prog.mode&LogSource != 0 { + defer logStack("interfaceMethodWrapper %s.%s", typ, id)() + } + fn = &Function{ + name: meth.Name(), + Signature: meth.Type().(*types.Signature), + Prog: prog, + } + fn.startBody() + fn.addParam("recv", typ, token.NoPos) + 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() + + prog.ifaceMethodWrappers[meth] = fn } - itf := typ.Underlying().(*types.Interface) - index, meth := methodIndex(itf, id) - sig := *meth.Type().(*types.Signature) // copy; shared Values - fn := &Function{ - name: meth.Name(), - Signature: &sig, - Prog: prog, - } - fn.startBody() - fn.addParam("recv", typ, token.NoPos) - 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 ---------------------------------------- +// Wrappers 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 +// boundMethodWrapper returns a synthetic wrapper function that +// delegates to a concrete method. The wrapper has one free variable, +// the method's receiver. Use MakeClosure with such a wrapper to // construct a bound-method closure. // e.g.: // @@ -414,35 +426,43 @@ func makeImethodThunk(prog *Program, typ types.Type, id Id) *Function { // f := t.meth // f() // calls t.meth() // -// f is a closure of a synthetic thunk defined as if by: +// f is a closure of a synthetic wrapper defined as if by: // // f := func() { return t.meth() } // -// TODO(adonovan): memoize creation of these functions in the Program. +// EXCLUSIVE_LOCKS_ACQUIRED(meth.Prog.methodsMu) // -func makeBoundMethodThunk(prog *Program, meth *Function, recvType types.Type) *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, - } +func boundMethodWrapper(meth *Function) *Function { + prog := meth.Prog + prog.methodsMu.Lock() + defer prog.methodsMu.Unlock() + fn, ok := prog.boundMethodWrappers[meth] + if !ok { + if prog.mode&LogSource != 0 { + defer logStack("boundMethodWrapper %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", typ: recvType, parent: fn} - 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) + cap := &Capture{name: "recv", typ: s.Recv().Type(), parent: fn} + 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() + + prog.boundMethodWrappers[meth] = fn } - emitTailCall(fn, &c) - fn.finishBody() return fn } @@ -455,7 +475,7 @@ func makeBoundMethodThunk(prog *Program, meth *Function, recvType types.Type) *F // return (*recv).f(...) // } // -// EXCLUSIVE_LOCKS_REQUIRED(meth.Prog.methodSetsMu) +// EXCLUSIVE_LOCKS_REQUIRED(meth.Prog.methodsMu) // func indirectionWrapper(meth *Function) *Function { prog := meth.Prog diff --git a/ssa/ssa.go b/ssa/ssa.go index db2ecb3f..7ef5646f 100644 --- a/ssa/ssa.go +++ b/ssa/ssa.go @@ -24,9 +24,11 @@ type Program struct { concreteMethods map[*types.Func]*Function // maps named concrete methods to their code mode BuilderMode // set of mode bits for SSA construction - methodSetsMu sync.Mutex // guards methodSets, indirectionWrappers - methodSets map[types.Type]MethodSet // concrete method sets for all needed types [TODO(adonovan): de-dup] - indirectionWrappers map[*Function]*Function // func(*T) wrappers for T-methods + methodsMu sync.Mutex // guards the following maps: + methodSets map[types.Type]MethodSet // concrete method set each type [TODO(adonovan): de-dup] + indirectionWrappers map[*Function]*Function // func(*T) wrappers for T-methods + boundMethodWrappers map[*Function]*Function // wrappers for curried x.Method closures + ifaceMethodWrappers map[*types.Func]*Function // wrappers for curried I.Method functions } // A Package is a single analyzed Go package containing Members for diff --git a/ssa/util.go b/ssa/util.go index 8bd1db87..2554938b 100644 --- a/ssa/util.go +++ b/ssa/util.go @@ -55,17 +55,31 @@ func pointer(typ types.Type) *types.Pointer { return types.NewPointer(typ) } -// methodIndex returns the method (and its index) named id within the -// method table of named or interface type typ. If not found, -// panic ensues. +// namedTypeMethodIndex returns the method (and its index) named id +// within the set of explicitly declared concrete methods of named +// type typ. If not found, panic ensues. // -func methodIndex(typ types.Type, id Id) (int, *types.Func) { - t := typ.(interface { - NumMethods() int - Method(i int) *types.Func - }) - for i, n := 0, t.NumMethods(); i < n; i++ { - m := t.Method(i) +// TODO(gri): move this functionality into the go/types API? +// +func namedTypeMethodIndex(typ *types.Named, id Id) (int, *types.Func) { + for i, n := 0, typ.NumMethods(); i < n; i++ { + m := typ.Method(i) + if MakeId(m.Name(), m.Pkg()) == id { + return i, m + } + } + panic(fmt.Sprint("method not found: ", id, " in named type ", typ)) +} + +// interfaceMethodIndex returns the method (and its index) named id +// within the method-set of interface type typ. If not found, panic +// ensues. +// +// TODO(gri): move this functionality into the go/types API. +// +func interfaceMethodIndex(typ *types.Interface, id Id) (int, *types.Func) { + for i, n := 0, typ.NumMethods(); i < n; i++ { + m := typ.Method(i) if MakeId(m.Name(), m.Pkg()) == id { return i, m }