mirror of
https://github.com/golang/go
synced 2024-11-19 03:44:40 -07:00
35f4fd1cd1
Temporarily remove Field objects in favor of Vars for struct fields. In forthcoming CL, Fields will play the symmetric role to Methods, and serve as lookup results including index information. R=adonovan CC=golang-dev https://golang.org/cl/11594043
529 lines
15 KiB
Go
529 lines
15 KiB
Go
package ssa
|
|
|
|
// 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 wrappers.go when promotion logic has evaporated.
|
|
|
|
import (
|
|
"code.google.com/p/go.tools/go/types"
|
|
"fmt"
|
|
"go/token"
|
|
)
|
|
|
|
// anonFieldPath is a linked list of anonymous fields that
|
|
// breadth-first traversal has entered, rightmost (outermost) first.
|
|
// e.g. "e.f" denoting "e.A.B.C.f" would have a path [C, B, A].
|
|
// Common tails may be shared.
|
|
//
|
|
// It is used by various "promotion"-related algorithms.
|
|
//
|
|
type anonFieldPath struct {
|
|
tail *anonFieldPath
|
|
index int // index of field within enclosing types.Struct.Fields
|
|
field *types.Var
|
|
}
|
|
|
|
func (p *anonFieldPath) contains(f *types.Var) bool {
|
|
for ; p != nil; p = p.tail {
|
|
if p.field == f {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// reverse returns the linked list reversed, as a slice.
|
|
func (p *anonFieldPath) reverse() []*anonFieldPath {
|
|
n := 0
|
|
for q := p; q != nil; q = q.tail {
|
|
n++
|
|
}
|
|
s := make([]*anonFieldPath, n)
|
|
n = 0
|
|
for ; p != nil; p = p.tail {
|
|
s[len(s)-1-n] = p
|
|
n++
|
|
}
|
|
return s
|
|
}
|
|
|
|
// isIndirect returns true if the path indirects a pointer.
|
|
func (p *anonFieldPath) isIndirect() bool {
|
|
for ; p != nil; p = p.tail {
|
|
if isPointer(p.field.Type()) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Method Set construction ----------------------------------------
|
|
|
|
// A candidate is a method eligible for promotion: a method of an
|
|
// abstract (interface) or concrete (anonymous struct or named) type,
|
|
// along with the anonymous field path via which it is implicitly
|
|
// reached. If there is exactly one candidate for a given id, it will
|
|
// be promoted to membership of the original type's method-set.
|
|
//
|
|
// Candidates with path=nil are trivially members of the original
|
|
// type's method-set.
|
|
//
|
|
type candidate struct {
|
|
method *types.Func // method object of abstract or concrete type
|
|
path *anonFieldPath // desugared selector path
|
|
}
|
|
|
|
func (c candidate) String() string {
|
|
s := ""
|
|
// Inefficient!
|
|
for p := c.path; p != nil; p = p.tail {
|
|
s = "." + p.field.Name() + s
|
|
}
|
|
return s + "." + c.method.Name()
|
|
}
|
|
|
|
func (c candidate) isConcrete() bool {
|
|
return c.method.Type().(*types.Signature).Recv() != nil
|
|
}
|
|
|
|
// ptrRecv returns true if this candidate is a concrete method with a
|
|
// pointer receiver.
|
|
//
|
|
func (c candidate) ptrRecv() bool {
|
|
recv := c.method.Type().(*types.Signature).Recv()
|
|
return recv != nil && isPointer(recv.Type())
|
|
}
|
|
|
|
// 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.
|
|
//
|
|
func (p *Program) MethodSet(typ types.Type) MethodSet {
|
|
if !canHaveConcreteMethods(typ, true) {
|
|
return nil
|
|
}
|
|
|
|
p.methodsMu.Lock()
|
|
defer p.methodsMu.Unlock()
|
|
|
|
mset := p.methodSets.At(typ)
|
|
if mset == nil {
|
|
mset = buildMethodSet(p, typ)
|
|
p.methodSets.Set(typ, mset)
|
|
}
|
|
return mset.(MethodSet)
|
|
}
|
|
|
|
// buildMethodSet computes the concrete method set for type typ.
|
|
// It is the implementation of Program.MethodSet.
|
|
//
|
|
// TODO(adonovan): use go/types.MethodSet(typ) when it's ready.
|
|
//
|
|
// EXCLUSIVE_LOCKS_REQUIRED(meth.Prog.methodsMu)
|
|
//
|
|
func buildMethodSet(prog *Program, typ types.Type) MethodSet {
|
|
if prog.mode&LogSource != 0 {
|
|
defer logStack("buildMethodSet %s", typ)()
|
|
}
|
|
|
|
// cands maps ids (field and method names) encountered at any
|
|
// level of of the breadth-first traversal to a unique
|
|
// promotion candidate. A nil value indicates a "blocked" id
|
|
// (i.e. a field or ambiguous method).
|
|
//
|
|
// nextcands is the same but carries just the level in progress.
|
|
cands, nextcands := make(map[Id]*candidate), make(map[Id]*candidate)
|
|
|
|
var next, list []*anonFieldPath
|
|
list = append(list, nil) // hack: nil means "use typ"
|
|
|
|
// For each level of the type graph...
|
|
for len(list) > 0 {
|
|
// Invariant: next=[], nextcands={}.
|
|
|
|
// Collect selectors from one level into 'nextcands'.
|
|
// Record the next levels into 'next'.
|
|
for _, node := range list {
|
|
t := typ // first time only
|
|
if node != nil {
|
|
t = node.field.Type()
|
|
}
|
|
t = deref(t)
|
|
|
|
if nt, ok := t.(*types.Named); ok {
|
|
for i, n := 0, nt.NumMethods(); i < n; i++ {
|
|
addCandidate(nextcands, nt.Method(i), node)
|
|
}
|
|
t = nt.Underlying()
|
|
}
|
|
|
|
switch t := t.(type) {
|
|
case *types.Interface:
|
|
for i, n := 0, t.NumMethods(); i < n; i++ {
|
|
addCandidate(nextcands, t.Method(i), node)
|
|
}
|
|
|
|
case *types.Struct:
|
|
for i, n := 0, t.NumFields(); i < n; i++ {
|
|
f := t.Field(i)
|
|
nextcands[makeId(f.Name(), f.Pkg())] = nil // a field: block id
|
|
// Queue up anonymous fields for next iteration.
|
|
// Break cycles to ensure termination.
|
|
if f.Anonymous() && !node.contains(f) {
|
|
next = append(next, &anonFieldPath{node, i, f})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Examine collected selectors.
|
|
// Promote unique, non-blocked ones to cands.
|
|
for id, cand := range nextcands {
|
|
delete(nextcands, id)
|
|
if cand == nil {
|
|
// Update cands so we ignore it at all deeper levels.
|
|
// Don't clobber existing (shallower) binding!
|
|
if _, ok := cands[id]; !ok {
|
|
cands[id] = nil // block id
|
|
}
|
|
continue
|
|
}
|
|
if _, ok := cands[id]; ok {
|
|
// Ignore candidate: a shallower binding exists.
|
|
} else {
|
|
cands[id] = cand
|
|
}
|
|
}
|
|
list, next = next, list[:0] // reuse array
|
|
}
|
|
|
|
// Build method sets and wrapper methods.
|
|
mset := make(MethodSet)
|
|
for id, cand := range cands {
|
|
if cand == nil {
|
|
continue // blocked; ignore
|
|
}
|
|
if cand.ptrRecv() && !isPointer(typ) && !cand.path.isIndirect() {
|
|
// A candidate concrete method f with receiver
|
|
// *C is promoted into the method set of
|
|
// (non-pointer) E iff the implicit path selection
|
|
// is indirect, e.g. e.A->B.C.f
|
|
continue
|
|
}
|
|
var method *Function
|
|
if cand.path == nil {
|
|
// Trivial member of method-set; no promotion needed.
|
|
method = prog.concreteMethods[cand.method]
|
|
|
|
if !cand.ptrRecv() && isPointer(typ) {
|
|
// Call to method on T from receiver of type *T.
|
|
method = indirectionWrapper(method)
|
|
}
|
|
} else {
|
|
method = promotionWrapper(prog, typ, cand)
|
|
}
|
|
if method == nil {
|
|
panic("unexpected nil method in method set")
|
|
}
|
|
mset[id] = method
|
|
}
|
|
return mset
|
|
}
|
|
|
|
// addCandidate adds the promotion candidate (method, node) to m[(name, package)].
|
|
// If a map entry already exists (whether nil or not), its value is set to nil.
|
|
//
|
|
func addCandidate(m map[Id]*candidate, method *types.Func, node *anonFieldPath) {
|
|
id := makeId(method.Name(), method.Pkg())
|
|
prev, found := m[id]
|
|
switch {
|
|
case prev != nil:
|
|
// Two candidates for same selector: ambiguous; block it.
|
|
m[id] = nil
|
|
case found:
|
|
// Already blocked.
|
|
default:
|
|
// A viable candidate.
|
|
m[id] = &candidate{method, node}
|
|
}
|
|
}
|
|
|
|
// promotionWrapper returns a synthetic Function that delegates to a
|
|
// "promoted" method. For example, given these decls:
|
|
//
|
|
// type A struct {B}
|
|
// type B struct {*C}
|
|
// type C ...
|
|
// func (*C) f()
|
|
//
|
|
// 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 wrapper method. cand is the
|
|
// candidate method to be promoted; it may be concrete or an interface
|
|
// method.
|
|
//
|
|
// EXCLUSIVE_LOCKS_REQUIRED(meth.Prog.methodsMu)
|
|
//
|
|
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("promotionWrapper (%s)%s, type %s", typ, cand, sig)()
|
|
}
|
|
// TODO(adonovan): is there a *types.Func for this function?
|
|
fn := &Function{
|
|
name: cand.method.Name(),
|
|
Signature: sig,
|
|
Synthetic: fmt.Sprintf("promotion wrapper for (%s)%s", typ, cand),
|
|
Prog: prog,
|
|
pos: cand.method.Pos(),
|
|
}
|
|
fn.startBody()
|
|
fn.addSpilledParam(sig.Recv())
|
|
createParams(fn)
|
|
|
|
// 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
|
|
// preceded by Load).
|
|
var v Value = fn.Locals[0] // spilled receiver
|
|
if isPointer(typ) {
|
|
v = emitLoad(fn, v)
|
|
}
|
|
// Iterate over selections e.A.B.C.f in the natural order [A,B,C].
|
|
for _, p := range cand.path.reverse() {
|
|
// Loop invariant: v holds a pointer to a struct.
|
|
if _, ok := deref(v.Type()).Underlying().(*types.Struct); !ok {
|
|
panic(fmt.Sprint("not a *struct: ", v.Type(), p.field.Type))
|
|
}
|
|
sel := &FieldAddr{
|
|
X: v,
|
|
Field: p.index,
|
|
}
|
|
sel.setType(types.NewPointer(p.field.Type()))
|
|
v = fn.emit(sel)
|
|
if isPointer(p.field.Type()) {
|
|
v = emitLoad(fn, v)
|
|
}
|
|
}
|
|
if !cand.ptrRecv() {
|
|
v = emitLoad(fn, v)
|
|
}
|
|
|
|
var c Call
|
|
if cand.isConcrete() {
|
|
c.Call.Func = prog.concreteMethods[cand.method]
|
|
c.Call.Args = append(c.Call.Args, v)
|
|
} else {
|
|
iface := v.Type().Underlying().(*types.Interface)
|
|
id := makeId(cand.method.Name(), cand.method.Pkg())
|
|
c.Call.Method, _ = interfaceMethodIndex(iface, id)
|
|
c.Call.Recv = v
|
|
}
|
|
for _, arg := range fn.Params[1:] {
|
|
c.Call.Args = append(c.Call.Args, arg)
|
|
}
|
|
emitTailCall(fn, &c)
|
|
fn.finishBody()
|
|
return fn
|
|
}
|
|
|
|
// createParams creates parameters for wrapper method fn based on its
|
|
// Signature.Params, which do not include the receiver.
|
|
//
|
|
func createParams(fn *Function) {
|
|
var last *Parameter
|
|
tparams := fn.Signature.Params()
|
|
for i, n := 0, tparams.Len(); i < n; i++ {
|
|
last = fn.addParamObj(tparams.At(i))
|
|
}
|
|
if fn.Signature.IsVariadic() {
|
|
last.typ = types.NewSlice(last.typ)
|
|
}
|
|
}
|
|
|
|
// Wrappers for standalone interface methods ----------------------------------
|
|
|
|
// 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 // wrapper
|
|
// var i I
|
|
// m(i, 0)
|
|
//
|
|
// The wrapper is defined as if by:
|
|
//
|
|
// func I.f(i I, x int, ...) R {
|
|
// return i.f(x, ...)
|
|
// }
|
|
//
|
|
// TODO(adonovan): opt: currently the stub is created even when used
|
|
// in call position: I.f(i, 0). Clearly this is suboptimal.
|
|
//
|
|
// EXCLUSIVE_LOCKS_ACQUIRED(meth.Prog.methodsMu)
|
|
//
|
|
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(),
|
|
object: meth,
|
|
Signature: meth.Type().(*types.Signature),
|
|
Synthetic: fmt.Sprintf("interface method wrapper for %s.%s", typ, id),
|
|
pos: meth.Pos(),
|
|
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
|
|
}
|
|
return fn
|
|
}
|
|
|
|
// Wrappers for bound methods -------------------------------------------------
|
|
|
|
// 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.:
|
|
//
|
|
// type T int
|
|
// func (t T) meth()
|
|
// var t T
|
|
// f := t.meth
|
|
// f() // calls t.meth()
|
|
//
|
|
// f is a closure of a synthetic wrapper defined as if by:
|
|
//
|
|
// f := func() { return t.meth() }
|
|
//
|
|
// EXCLUSIVE_LOCKS_ACQUIRED(meth.Prog.methodsMu)
|
|
//
|
|
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.String(),
|
|
Signature: types.NewSignature(nil, s.Params(), s.Results(), s.IsVariadic()), // drop recv
|
|
Synthetic: "bound method wrapper for " + meth.String(),
|
|
Prog: prog,
|
|
pos: meth.Pos(),
|
|
}
|
|
|
|
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
|
|
}
|
|
return fn
|
|
}
|
|
|
|
// Receiver indirection wrapper ------------------------------------
|
|
|
|
// indirectionWrapper returns a synthetic method with *T receiver
|
|
// that delegates to meth, which has a T receiver.
|
|
//
|
|
// func (recv *T) f(...) ... {
|
|
// return (*recv).f(...)
|
|
// }
|
|
//
|
|
// EXCLUSIVE_LOCKS_REQUIRED(meth.Prog.methodsMu)
|
|
//
|
|
func indirectionWrapper(meth *Function) *Function {
|
|
prog := meth.Prog
|
|
fn, ok := prog.indirectionWrappers[meth]
|
|
if !ok {
|
|
if prog.mode&LogSource != 0 {
|
|
defer logStack("makeIndirectionWrapper %s", meth)()
|
|
}
|
|
|
|
s := meth.Signature
|
|
recv := types.NewVar(token.NoPos, meth.Pkg.Object, "recv",
|
|
types.NewPointer(s.Recv().Type()))
|
|
// TODO(adonovan): is there a *types.Func for this method?
|
|
fn = &Function{
|
|
name: meth.Name(),
|
|
Signature: types.NewSignature(recv, s.Params(), s.Results(), s.IsVariadic()),
|
|
Prog: prog,
|
|
Synthetic: "receiver indirection wrapper for " + meth.String(),
|
|
pos: meth.Pos(),
|
|
}
|
|
|
|
fn.startBody()
|
|
fn.addParamObj(recv)
|
|
createParams(fn)
|
|
// TODO(adonovan): consider emitting a nil-pointer check here
|
|
// with a nice error message, like gc does.
|
|
var c Call
|
|
c.Call.Func = meth
|
|
c.Call.Args = append(c.Call.Args, emitLoad(fn, fn.Params[0]))
|
|
for _, arg := range fn.Params[1:] {
|
|
c.Call.Args = append(c.Call.Args, arg)
|
|
}
|
|
emitTailCall(fn, &c)
|
|
fn.finishBody()
|
|
|
|
prog.indirectionWrappers[meth] = fn
|
|
}
|
|
return fn
|
|
}
|