go.tools/ssa: memoize synthesis of all wrapper methods.

methodIndex() utility was split and specialized to its two
cases, *Interface vs *Named, which are logically quite
different.

We can't memoize promotion wrappers yet; we need typemap.

Terminology:
- "thunks" are now "wrappers"
- "bridge methods" are now "promotion wrappers"

Where the diff is messy it's just because of indentation.

R=gri
CC=golang-dev
https://golang.org/cl/10282043
This commit is contained in:
Alan Donovan 2013-06-14 15:50:37 -04:00
parent 0f26bbae8f
commit f1d4d01fed
9 changed files with 150 additions and 111 deletions

View File

@ -365,7 +365,7 @@ func NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named {
// TypeName returns the type name for the named type t. // TypeName returns the type name for the named type t.
func (t *Named) Obj() *TypeName { return t.obj } 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) } func (t *Named) NumMethods() int { return len(t.methods) }
// Method returns the i'th method of named type t for 0 <= i < t.NumMethods(). // Method returns the i'th method of named type t for 0 <= i < t.NumMethods().

View File

@ -5,7 +5,7 @@ package ssa
// SSA construction has two phases, CREATE and BUILD. In the CREATE phase // SSA construction has two phases, CREATE and BUILD. In the CREATE phase
// (create.go), all packages are constructed and type-checked and // (create.go), all packages are constructed and type-checked and
// definitions of all package members are created, method-sets are // 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, // proceeds in topological order over the import dependency graph,
// initiated by client calls to CreatePackages. // initiated by client calls to CreatePackages.
// //
@ -345,7 +345,7 @@ func (b *builder) selector(fn *Function, e *ast.SelectorExpr, wantAddr, escaping
if !wantAddr { if !wantAddr {
if m, recv := b.findMethod(fn, e.X, id); m != nil { if m, recv := b.findMethod(fn, e.X, id); m != nil {
c := &MakeClosure{ c := &MakeClosure{
Fn: makeBoundMethodThunk(fn.Prog, m, recv.Type()), Fn: boundMethodWrapper(m),
Bindings: []Value{recv}, Bindings: []Value{recv},
} }
c.setPos(e.Sel.Pos()) c.setPos(e.Sel.Pos())
@ -743,8 +743,8 @@ func (b *builder) expr(fn *Function, e ast.Expr) Value {
return m return m
} }
// T must be an interface; return method thunk. // T must be an interface; return wrapper.
return makeImethodThunk(fn.Prog, typ, id) return interfaceMethodWrapper(fn.Prog, typ, id)
} }
// e.f where e is an expression. f may be a method. // 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 // Case 4: x.f() where a dynamically dispatched call
// to an interface method f. f is a 'func' object in // to an interface method f. f is a 'func' object in
// the Methods of types.Interface X // the Methods of types.Interface X
c.Method, _ = methodIndex(t, id) c.Method, _ = interfaceMethodIndex(t, id)
c.Recv = b.expr(fn, sel.X) c.Recv = b.expr(fn, sel.X)
default: default:

View File

@ -42,6 +42,8 @@ func NewProgram(fset *token.FileSet, mode BuilderMode) *Program {
methodSets: make(map[types.Type]MethodSet), methodSets: make(map[types.Type]MethodSet),
concreteMethods: make(map[*types.Func]*Function), concreteMethods: make(map[*types.Func]*Function),
indirectionWrappers: make(map[*Function]*Function), indirectionWrappers: make(map[*Function]*Function),
boundMethodWrappers: make(map[*Function]*Function),
ifaceMethodWrappers: make(map[*types.Func]*Function),
mode: mode, mode: mode,
} }
@ -130,8 +132,9 @@ func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) {
pkg.Members[name] = fn pkg.Members[name] = fn
} else { } else {
// Method declaration. // Method declaration.
nt := recv.Type().Deref().(*types.Named) _, method := namedTypeMethodIndex(
_, method := methodIndex(nt, MakeId(name, pkg.Types)) recv.Type().Deref().(*types.Named),
MakeId(name, pkg.Types))
pkg.Prog.concreteMethods[method] = fn pkg.Prog.concreteMethods[method] = fn
} }

View File

@ -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 // emitTailCall emits to f a function call in tail position. The
// caller is responsible for all fields of 'call' except its type. // 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. // Precondition: f does/will not use deferred procedure calls.
// Postcondition: f.currentBlock is nil. // Postcondition: f.currentBlock is nil.
// //

View File

@ -443,10 +443,10 @@ func (f *Function) emit(instr Instruction) Value {
// "math.IsNaN" // a package-level function // "math.IsNaN" // a package-level function
// "IsNaN" // intra-package reference to same // "IsNaN" // intra-package reference to same
// "(*sync.WaitGroup).Add" // a declared method // "(*sync.WaitGroup).Add" // a declared method
// "(*exp/ssa.Ret).Block" // a bridge method // "(*exp/ssa.Ret).Block" // a promotion wrapper method
// "(ssa.Instruction).Block" // an interface method thunk // "(ssa.Instruction).Block" // an interface method wrapper
// "func@5.32" // an anonymous function // "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 { func (f *Function) FullName() string {
return f.fullName(nil) return f.fullName(nil)
@ -467,11 +467,11 @@ func (f *Function) fullName(from *Package) string {
if f.Pkg == nil { if f.Pkg == nil {
var recvType types.Type var recvType types.Type
if recv != nil { if recv != nil {
recvType = recv.Type() // bridge method recvType = recv.Type() // promotion wrapper
} else if strings.HasPrefix(f.name, "bound$") { } else if strings.HasPrefix(f.name, "bound$") {
return f.name // bound method thunk return f.name // bound method wrapper
} else { } 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) return fmt.Sprintf("(%s).%s", recvType, f.name)
} }

View File

@ -385,7 +385,7 @@ func (p *Package) DumpTo(w io.Writer) {
// We display only mset(*T) since its keys // We display only mset(*T) since its keys
// are a superset of mset(T)'s keys, though the // are a superset of mset(T)'s keys, though the
// methods themselves may differ, // methods themselves may differ,
// e.g. different bridge methods. // e.g. promotion wrappers.
// NB: if mem.Type() is a pointer, mset is empty. // NB: if mem.Type() is a pointer, mset is empty.
mset := p.Prog.MethodSet(pointer(mem.Type())) mset := p.Prog.MethodSet(pointer(mem.Type()))
var keys ids var keys ids

View File

@ -1,9 +1,15 @@
package ssa package ssa
// This file defines algorithms related to "promotion" of field and // This file defines utilities for method-set computation, synthesis
// method selector expressions e.x, such as desugaring implicit field // of wrapper methods, and desugaring of implicit field selections.
// and method selections, method-set computation, and construction of //
// synthetic "bridge" methods. // 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 ( import (
"code.google.com/p/go.tools/go/types" "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()) return c.concrete != nil && isPointer(c.concrete.Signature.Recv().Type())
} }
// MethodSet returns the method set for type typ, // MethodSet returns the method set for type typ, building wrapper
// building bridge methods as needed for promoted methods // methods as needed for embedded field promotion, and indirection for
// and indirection wrappers for *T receiver types. // *T receiver types, etc.
// A nil result indicates an empty set. // A nil result indicates an empty set.
// //
// Thread-safe. // Thread-safe.
@ -101,8 +107,8 @@ func (p *Program) MethodSet(typ types.Type) MethodSet {
return nil return nil
} }
p.methodSetsMu.Lock() p.methodsMu.Lock()
defer p.methodSetsMu.Unlock() defer p.methodsMu.Unlock()
// TODO(adonovan): Using Types as map keys doesn't properly // TODO(adonovan): Using Types as map keys doesn't properly
// de-dup. e.g. *Named are canonical but *Struct and // 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 list, next = next, list[:0] // reuse array
} }
// Build method sets and bridge methods. // Build method sets and wrapper methods.
mset := make(MethodSet) mset := make(MethodSet)
for id, cand := range cands { for id, cand := range cands {
if cand == nil { if cand == nil {
@ -223,7 +229,7 @@ func buildMethodSet(prog *Program, typ types.Type) MethodSet {
method = indirectionWrapper(method) method = indirectionWrapper(method)
} }
} else { } else {
method = makeBridgeMethod(prog, typ, cand) method = promotionWrapper(prog, typ, cand)
} }
if method == nil { if method == nil {
panic("unexpected nil method in method set") 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: // "promoted" method. For example, given these decls:
// //
// type A struct {B} // type A struct {B}
@ -259,24 +265,25 @@ func addCandidate(m map[Id]*candidate, id Id, method *types.Func, concrete *Func
// type C ... // type C ...
// func (*C) f() // func (*C) f()
// //
// then makeBridgeMethod(typ=A, cand={method:(*C).f, path:[B,*C]}) will // then promotionWrapper(typ=A, cand={method:(*C).f, path:[B,*C]}) will
// synthesize this bridge method: // synthesize this wrapper method:
// //
// func (a A) f() { return a.B.C->f() } // func (a A) f() { return a.B.C->f() }
// //
// prog is the program to which the synthesized method will belong. // 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 // candidate method to be promoted; it may be concrete or an interface
// method. // 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) old := cand.method.Type().(*types.Signature)
sig := types.NewSignature(types.NewVar(token.NoPos, nil, "recv", typ), old.Params(), old.Results(), old.IsVariadic()) 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 { 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{ fn := &Function{
name: cand.method.Name(), name: cand.method.Name(),
Signature: sig, Signature: sig,
@ -286,7 +293,7 @@ func makeBridgeMethod(prog *Program, typ types.Type, cand *candidate) *Function
fn.addSpilledParam(sig.Recv()) fn.addSpilledParam(sig.Recv())
createParams(fn) createParams(fn)
// Each bridge method performs a sequence of selections, // Each promotion wrapper performs a sequence of selections,
// then tailcalls the promoted method. // then tailcalls the promoted method.
// We use pointer arithmetic (FieldAddr possibly followed by // We use pointer arithmetic (FieldAddr possibly followed by
// Load) in preference to value extraction (Field possibly // 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.Func = cand.concrete
c.Call.Args = append(c.Call.Args, v) c.Call.Args = append(c.Call.Args, v)
} else { } else {
c.Call.Method = -1
iface := v.Type().Underlying().(*types.Interface) iface := v.Type().Underlying().(*types.Interface)
for i, n := 0, iface.NumMethods(); i < n; i++ { id := MakeId(cand.method.Name(), cand.method.Pkg())
if iface.Method(i) == cand.method { c.Call.Method, _ = interfaceMethodIndex(iface, id)
c.Call.Method = i
break
}
}
c.Call.Recv = v c.Call.Recv = v
} }
for _, arg := range fn.Params[1:] { for _, arg := range fn.Params[1:] {
@ -338,7 +340,7 @@ func makeBridgeMethod(prog *Program, typ types.Type, cand *candidate) *Function
return fn 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. // Signature.Params, which do not include the receiver.
// //
func createParams(fn *Function) { 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, // method id of interface typ to be called like a standalone function,
// e.g.: // e.g.:
// //
// type I interface { f(x int) R } // type I interface { f(x int) R }
// m := I.f // thunk // m := I.f // wrapper
// var i I // var i I
// m(i, 0) // 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 { // func I.f(i I, x int, ...) R {
// return i.f(x, ...) // return i.f(x, ...)
@ -372,18 +374,25 @@ func createParams(fn *Function) {
// TODO(adonovan): opt: currently the stub is created even when used // TODO(adonovan): opt: currently the stub is created even when used
// in call position: I.f(i, 0). Clearly this is suboptimal. // 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 { 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 { if prog.mode&LogSource != 0 {
defer logStack("makeImethodThunk %s.%s", typ, id)() defer logStack("interfaceMethodWrapper %s.%s", typ, id)()
} }
itf := typ.Underlying().(*types.Interface) fn = &Function{
index, meth := methodIndex(itf, id)
sig := *meth.Type().(*types.Signature) // copy; shared Values
fn := &Function{
name: meth.Name(), name: meth.Name(),
Signature: &sig, Signature: meth.Type().(*types.Signature),
Prog: prog, Prog: prog,
} }
fn.startBody() fn.startBody()
@ -397,14 +406,17 @@ func makeImethodThunk(prog *Program, typ types.Type, id Id) *Function {
} }
emitTailCall(fn, &c) emitTailCall(fn, &c)
fn.finishBody() fn.finishBody()
prog.ifaceMethodWrappers[meth] = fn
}
return fn return fn
} }
// Thunks for bound methods ---------------------------------------- // Wrappers for bound methods -------------------------------------------------
// makeBoundMethodThunk returns a synthetic thunk function that // boundMethodWrapper returns a synthetic wrapper function that
// delegates to a concrete method. The thunk has one free variable, // delegates to a concrete method. The wrapper has one free variable,
// the method's receiver. Use MakeClosure with such a thunk to // the method's receiver. Use MakeClosure with such a wrapper to
// construct a bound-method closure. // construct a bound-method closure.
// e.g.: // e.g.:
// //
@ -414,24 +426,29 @@ func makeImethodThunk(prog *Program, typ types.Type, id Id) *Function {
// f := t.meth // f := t.meth
// f() // calls 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() } // 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 { 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 { if prog.mode&LogSource != 0 {
defer logStack("makeBoundMethodThunk %s", meth)() defer logStack("boundMethodWrapper %s", meth)()
} }
s := meth.Signature s := meth.Signature
fn := &Function{ fn = &Function{
name: "bound$" + meth.FullName(), name: "bound$" + meth.FullName(),
Signature: types.NewSignature(nil, s.Params(), s.Results(), s.IsVariadic()), // drop recv Signature: types.NewSignature(nil, s.Params(), s.Results(), s.IsVariadic()), // drop recv
Prog: prog, Prog: prog,
} }
cap := &Capture{name: "recv", typ: recvType, parent: fn} cap := &Capture{name: "recv", typ: s.Recv().Type(), parent: fn}
fn.FreeVars = []*Capture{cap} fn.FreeVars = []*Capture{cap}
fn.startBody() fn.startBody()
createParams(fn) createParams(fn)
@ -443,6 +460,9 @@ func makeBoundMethodThunk(prog *Program, meth *Function, recvType types.Type) *F
} }
emitTailCall(fn, &c) emitTailCall(fn, &c)
fn.finishBody() fn.finishBody()
prog.boundMethodWrappers[meth] = fn
}
return fn return fn
} }
@ -455,7 +475,7 @@ func makeBoundMethodThunk(prog *Program, meth *Function, recvType types.Type) *F
// return (*recv).f(...) // return (*recv).f(...)
// } // }
// //
// EXCLUSIVE_LOCKS_REQUIRED(meth.Prog.methodSetsMu) // EXCLUSIVE_LOCKS_REQUIRED(meth.Prog.methodsMu)
// //
func indirectionWrapper(meth *Function) *Function { func indirectionWrapper(meth *Function) *Function {
prog := meth.Prog prog := meth.Prog

View File

@ -24,9 +24,11 @@ type Program struct {
concreteMethods map[*types.Func]*Function // maps named concrete methods to their code concreteMethods map[*types.Func]*Function // maps named concrete methods to their code
mode BuilderMode // set of mode bits for SSA construction mode BuilderMode // set of mode bits for SSA construction
methodSetsMu sync.Mutex // guards methodSets, indirectionWrappers methodsMu sync.Mutex // guards the following maps:
methodSets map[types.Type]MethodSet // concrete method sets for all needed types [TODO(adonovan): de-dup] methodSets map[types.Type]MethodSet // concrete method set each type [TODO(adonovan): de-dup]
indirectionWrappers map[*Function]*Function // func(*T) wrappers for T-methods 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 // A Package is a single analyzed Go package containing Members for

View File

@ -55,17 +55,31 @@ func pointer(typ types.Type) *types.Pointer {
return types.NewPointer(typ) return types.NewPointer(typ)
} }
// methodIndex returns the method (and its index) named id within the // namedTypeMethodIndex returns the method (and its index) named id
// method table of named or interface type typ. If not found, // within the set of explicitly declared concrete methods of named
// panic ensues. // type typ. If not found, panic ensues.
// //
func methodIndex(typ types.Type, id Id) (int, *types.Func) { // TODO(gri): move this functionality into the go/types API?
t := typ.(interface { //
NumMethods() int func namedTypeMethodIndex(typ *types.Named, id Id) (int, *types.Func) {
Method(i int) *types.Func for i, n := 0, typ.NumMethods(); i < n; i++ {
}) m := typ.Method(i)
for i, n := 0, t.NumMethods(); i < n; i++ { if MakeId(m.Name(), m.Pkg()) == id {
m := t.Method(i) 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 { if MakeId(m.Name(), m.Pkg()) == id {
return i, m return i, m
} }