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
Родитель 0f26bbae8f
Коммит f1d4d01fed
9 изменённых файлов: 150 добавлений и 111 удалений

Просмотреть файл

@ -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().

Просмотреть файл

@ -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:

Просмотреть файл

@ -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
}

Просмотреть файл

@ -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.
//

Просмотреть файл

@ -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)
}

Просмотреть файл

@ -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

Просмотреть файл

@ -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

Просмотреть файл

@ -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

Просмотреть файл

@ -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
}