mirror of
https://github.com/golang/go
synced 2024-11-18 14:44:41 -07:00
80ec883f7b
- removed a number of obsolete TODO(gri) comments. - bring ssa.DefaultType back into sync with types.defaultType. - re-enable types.Package.Path()!="" assertion. - use Path() (not reflect pointer) in sort routine. - make interp.checkInterface use types.MissingMethod. - un-export ssa.MakeId function. - inline pointer() into all callers, and delete. - enable two more interp_tests: $GOROOT/test/{method3,cmp}.go - add links to bugs to other interp_tests. - add runtime.NumCPU to ssa/interp/externals.go R=gri CC=golang-dev https://golang.org/cl/11353043
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.Field
|
|
}
|
|
|
|
func (p *anonFieldPath) contains(f *types.Field) 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
|
|
}
|