зеркало из https://github.com/golang/tools.git
go.tools/ssa: (another) major refactoring of method-set logic.
We now use LookupFieldOrMethod for all SelectorExprs, and simplify the logic to discriminate the various cases. We inline static calls to promoted/indirected functions, dramatically reducing the number of functions created. More tests are needed, but I'd like to submit this as-is. In this CL, we: - rely less on Id strings. Internally we now use *types.Method (and its components) almost everywhere. - stop thinking of types.Methods as objects. They don't have stable identities. (Hopefully they will become plain-old structs soon.) - eliminate receiver indirection wrappers: indirection and promotion are handled together by makeWrapper. - Handle the interactions of promotion, indirection and abstract methods much more cleanly. - support receiver-bound interface method closures. - break up builder.selectField so we can re-use parts (emitFieldSelection). - add importer.PackageInfo.classifySelector utility. - delete interfaceMethodIndex() - delete namedTypeMethodIndex() - delete isSuperInterface() (replaced by types.IsAssignable) - call memberFromObject on each declared concrete method's *types.Func, not on every Method frem each method set, in the CREATE phase for packages loaded by gcimporter. go/types: - document Func, Signature.Recv() better. - use fmt in {Package,Label}.String - reimplement Func.String to be prettier and to include method receivers. API changes: - Function.method now holds the types.Method (soon to be not-an-object) for synthetic wrappers. - CallCommon.Method now contains an abstract (interface) method object; was an abstract method index. - CallCommon.MethodId() gone. - Program.LookupMethod now takes a *Method not an Id string. R=gri CC=golang-dev https://golang.org/cl/11674043
This commit is contained in:
Родитель
c98ff05fdd
Коммит
4da31df1c8
|
@ -23,6 +23,12 @@ type Method struct {
|
|||
selectorPath
|
||||
}
|
||||
|
||||
// TODO(gri): expose this or something like it (e.g. lookupResult) to
|
||||
// make life easier for callers of LookupFieldOrMethod.
|
||||
func NewMethod(f *Func, recv Type, index []int, indirect bool) *Method {
|
||||
return &Method{f, selectorPath{recv, index, indirect}}
|
||||
}
|
||||
|
||||
// Type returns the promoted signature type of m (i.e., the receiver
|
||||
// type is Recv()).
|
||||
func (m *Method) Type() Type {
|
||||
|
@ -33,6 +39,10 @@ func (m *Method) Type() Type {
|
|||
return &sig
|
||||
}
|
||||
|
||||
func (m *Method) String() string {
|
||||
return fmt.Sprintf("method (%s).%s %s", m.Recv(), m.Name(), m.Type())
|
||||
}
|
||||
|
||||
// A selectorPath describes the path from a value x to one of its fields
|
||||
// or methods f for a selector expression x.f. A path may include implicit
|
||||
// field selections (e.g., x.f may be x.a.b.c.f).
|
||||
|
|
|
@ -6,6 +6,7 @@ package types
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
|
@ -46,6 +47,10 @@ func Id(pkg *Package, name string) string {
|
|||
}
|
||||
// unexported names need the package path for differentiation
|
||||
path := ""
|
||||
// TODO(gri): shouldn't !ast.IsExported(name) => pkg != nil be an precondition?
|
||||
// if pkg == nil {
|
||||
// panic("nil package in lookup of unexported name")
|
||||
// }
|
||||
if pkg != nil {
|
||||
path = pkg.path
|
||||
if path == "" {
|
||||
|
@ -82,8 +87,10 @@ func (obj *object) toString(kind string, typ Type) string {
|
|||
buf.WriteByte('.')
|
||||
}
|
||||
buf.WriteString(obj.name)
|
||||
if typ != nil {
|
||||
buf.WriteByte(' ')
|
||||
writeType(&buf, typ)
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
@ -127,7 +134,7 @@ func NewPackage(pos token.Pos, path, name string, scope *Scope, imports map[stri
|
|||
return obj
|
||||
}
|
||||
|
||||
func (obj *Package) String() string { return obj.toString("package", nil) }
|
||||
func (obj *Package) String() string { return fmt.Sprintf("package %s", obj.Path()) }
|
||||
func (obj *Package) Path() string { return obj.path }
|
||||
func (obj *Package) Scope() *Scope { return obj.scope }
|
||||
func (obj *Package) Imports() map[string]*Package { return obj.imports }
|
||||
|
@ -178,7 +185,9 @@ func NewFieldVar(pos token.Pos, pkg *Package, name string, typ Type, anonymous b
|
|||
func (obj *Var) Anonymous() bool { return obj.anonymous }
|
||||
func (obj *Var) String() string { return obj.toString("var", obj.typ) }
|
||||
|
||||
// A Func represents a declared function.
|
||||
// A Func represents a declared function, concrete method, or abstract
|
||||
// (interface) method. Its Type() is a *Signature.
|
||||
// An abstract method may belong to many interfaces due to embedding.
|
||||
type Func struct {
|
||||
object
|
||||
}
|
||||
|
@ -187,7 +196,33 @@ func NewFunc(pos token.Pos, pkg *Package, name string, typ Type) *Func {
|
|||
return &Func{object{nil, pos, pkg, name, typ}}
|
||||
}
|
||||
|
||||
func (obj *Func) String() string { return obj.toString("func", obj.typ) }
|
||||
func (obj *Func) String() string {
|
||||
var buf bytes.Buffer
|
||||
|
||||
buf.WriteString("func ")
|
||||
sig := obj.typ.(*Signature)
|
||||
if recv := sig.Recv(); recv != nil {
|
||||
buf.WriteByte('(')
|
||||
if _, ok := recv.Type().(*Interface); ok {
|
||||
// gcimporter creates abstract methods of
|
||||
// named interfaces using the interface type
|
||||
// (not the named type) as the receiver.
|
||||
// Don't print it in full.
|
||||
buf.WriteString("interface")
|
||||
} else {
|
||||
writeType(&buf, recv.Type())
|
||||
}
|
||||
buf.WriteByte(')')
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
if obj.pkg != nil {
|
||||
buf.WriteString(obj.pkg.name)
|
||||
buf.WriteByte('.')
|
||||
}
|
||||
buf.WriteString(obj.name)
|
||||
writeSignature(&buf, sig)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// A Label represents a declared label.
|
||||
type Label struct {
|
||||
|
@ -198,4 +233,4 @@ func NewLabel(pos token.Pos, name string) *Label {
|
|||
return &Label{object{nil, pos, nil, name, nil}}
|
||||
}
|
||||
|
||||
func (obj *Label) String() string { return obj.toString("label", nil) }
|
||||
func (obj *Label) String() string { return fmt.Sprintf("label %s", obj.Name()) }
|
||||
|
|
|
@ -191,7 +191,7 @@ func (t *Tuple) Len() int {
|
|||
// At returns the i'th variable of tuple t.
|
||||
func (t *Tuple) At(i int) *Var { return t.vars[i] }
|
||||
|
||||
// A Signature represents a (non-builtin) function type.
|
||||
// A Signature represents a (non-builtin) function or method type.
|
||||
type Signature struct {
|
||||
scope *Scope // function scope, always present
|
||||
labels *Scope // label scope, or nil (lazily allocated)
|
||||
|
@ -220,7 +220,12 @@ func NewSignature(scope *Scope, recv *Var, params, results *Tuple, isVariadic bo
|
|||
return &Signature{scope, nil, recv, params, results, isVariadic}
|
||||
}
|
||||
|
||||
// Recv returns the receiver of signature s, or nil.
|
||||
// Recv returns the receiver of signature s (if a method), or nil if a
|
||||
// function.
|
||||
//
|
||||
// For an abstract method, Recv returns the enclosing interface either
|
||||
// as a *Named or an *Interface. Due to embedding, an interface may
|
||||
// contain methods whose receiver type is a different interface.
|
||||
func (s *Signature) Recv() *Var { return s.recv }
|
||||
|
||||
// Params returns the parameters of signature s, or nil.
|
||||
|
|
|
@ -122,6 +122,26 @@ func (info *PackageInfo) IsPackageRef(sel *ast.SelectorExpr) types.Object {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ClassifySelector returns one of token.{PACKAGE,VAR,TYPE} to
|
||||
// indicate whether the base operand of sel is a package, expression
|
||||
// or type, respectively.
|
||||
//
|
||||
// Examples:
|
||||
// PACKAGE: fmt.Println (func), math.Pi (const), types.Universe (var)
|
||||
// VAR: info.IsType (*Method), info.Objects (*Field)
|
||||
// TYPE: PackageInfo.IsType (func)
|
||||
//
|
||||
func (info *PackageInfo) ClassifySelector(sel *ast.SelectorExpr) token.Token {
|
||||
switch {
|
||||
case info.IsPackageRef(sel) != nil:
|
||||
return token.PACKAGE
|
||||
case info.IsType(sel.X):
|
||||
return token.TYPE
|
||||
default:
|
||||
return token.VAR
|
||||
}
|
||||
}
|
||||
|
||||
// TypeCaseVar returns the implicit variable created by a single-type
|
||||
// case clause in a type switch, or nil if not found.
|
||||
//
|
||||
|
|
287
ssa/builder.go
287
ssa/builder.go
|
@ -19,8 +19,8 @@ package ssa
|
|||
//
|
||||
// The builder's and Program's indices (maps) are populated and
|
||||
// mutated during the CREATE phase, but during the BUILD phase they
|
||||
// remain constant. The sole exception is Prog.methodSets, which is
|
||||
// protected by a dedicated mutex.
|
||||
// remain constant. The sole exception is Prog.methodSets and its
|
||||
// related maps, which are protected by a dedicated mutex.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -328,64 +328,6 @@ func (b *builder) builtin(fn *Function, name string, args []ast.Expr, typ types.
|
|||
return nil // treat all others as a regular function call
|
||||
}
|
||||
|
||||
// selectField evaluates the field selector expression e and returns its value,
|
||||
// or if wantAddr is true, its address, in which case escaping
|
||||
// indicates whether the caller intends to use the resulting pointer
|
||||
// in a potentially escaping way.
|
||||
//
|
||||
func (b *builder) selectField(fn *Function, e *ast.SelectorExpr, wantAddr, escaping bool) Value {
|
||||
tx := fn.Pkg.typeOf(e.X)
|
||||
obj, indices, isIndirect := types.LookupFieldOrMethod(tx, fn.Pkg.Object, e.Sel.Name)
|
||||
if obj == nil {
|
||||
panic("field not found: " + e.Sel.Name)
|
||||
}
|
||||
|
||||
// Be careful! This code has proven very tricky.
|
||||
|
||||
// NB: The type of the final field is irrelevant to the logic.
|
||||
|
||||
// Emit code for the base expression.
|
||||
var v Value
|
||||
if wantAddr && !isIndirect && !isPointer(tx) {
|
||||
// TODO(adonovan): opt: also use this codepath
|
||||
// for !wantAddr, when safe (i.e. e.X is addressible),
|
||||
// since (FieldAddr;Load) is cheaper than (Load;Field).
|
||||
// Requires go/types to expose addressibility.
|
||||
v = b.addr(fn, e.X, escaping).address(fn)
|
||||
} else {
|
||||
v = b.expr(fn, e.X)
|
||||
}
|
||||
|
||||
v = emitImplicitSelections(fn, v, indices[:len(indices)-1])
|
||||
|
||||
// Final explicit field selection.
|
||||
// Invariant: v.Type() is a possibly named struct or *struct.
|
||||
index := indices[len(indices)-1]
|
||||
fld := deref(v.Type()).Underlying().(*types.Struct).Field(index)
|
||||
if isPointer(v.Type()) {
|
||||
instr := &FieldAddr{
|
||||
X: v,
|
||||
Field: index,
|
||||
}
|
||||
instr.setPos(e.Sel.Pos())
|
||||
instr.setType(types.NewPointer(fld.Type()))
|
||||
v = fn.emit(instr)
|
||||
// Load the field's value iff we don't want its address.
|
||||
if !wantAddr {
|
||||
v = emitLoad(fn, v)
|
||||
}
|
||||
} else {
|
||||
instr := &Field{
|
||||
X: v,
|
||||
Field: index,
|
||||
}
|
||||
instr.setPos(e.Sel.Pos())
|
||||
instr.setType(fld.Type())
|
||||
v = fn.emit(instr)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// addr lowers a single-result addressable expression e to SSA form,
|
||||
// emitting code to fn and returning the location (an lvalue) defined
|
||||
// by the expression.
|
||||
|
@ -445,8 +387,15 @@ func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) lvalue {
|
|||
panic("undefined package-qualified name: " + obj.Name())
|
||||
}
|
||||
|
||||
// e.f where e is an expression.
|
||||
return &address{addr: b.selectField(fn, e, true, escaping)}
|
||||
// e.f where e is an expression and f is a field.
|
||||
typ := fn.Pkg.typeOf(e.X)
|
||||
obj, indices, isIndirect := types.LookupFieldOrMethod(typ, fn.Pkg.Object, e.Sel.Name)
|
||||
_ = obj.(*types.Var) // assertion
|
||||
|
||||
wantAddr := true
|
||||
v := b.receiver(fn, e.X, wantAddr, escaping, indices, isIndirect)
|
||||
last := len(indices) - 1
|
||||
return &address{addr: emitFieldSelection(fn, v, indices[last], true, e.Sel.Pos())}
|
||||
|
||||
case *ast.IndexExpr:
|
||||
var x Value
|
||||
|
@ -676,37 +625,66 @@ func (b *builder) expr(fn *Function, e ast.Expr) Value {
|
|||
return v
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
selKind := fn.Pkg.info.ClassifySelector(e)
|
||||
|
||||
// p.M where p is a package.
|
||||
if obj := fn.Pkg.info.IsPackageRef(e); obj != nil {
|
||||
if selKind == token.PACKAGE {
|
||||
return b.expr(fn, e.Sel)
|
||||
}
|
||||
|
||||
id := types.Id(fn.Pkg.Object, e.Sel.Name)
|
||||
typ := fn.Pkg.typeOf(e.X)
|
||||
obj, indices, isIndirect := types.LookupFieldOrMethod(typ, fn.Pkg.Object, e.Sel.Name)
|
||||
|
||||
// (*T).f or T.f, the method f from the method-set of type T.
|
||||
if fn.Pkg.info.IsType(e.X) {
|
||||
typ := fn.Pkg.typeOf(e.X)
|
||||
if m := fn.Prog.LookupMethod(typ, id); m != nil {
|
||||
return emitConv(fn, m, fn.Pkg.typeOf(e))
|
||||
// We return a standalone function that calls the method.
|
||||
if selKind == token.TYPE {
|
||||
obj := obj.(*types.Func)
|
||||
if _, ok := typ.Underlying().(*types.Interface); ok {
|
||||
// T is an interface; return wrapper.
|
||||
fn.Prog.methodsMu.Lock()
|
||||
defer fn.Prog.methodsMu.Unlock()
|
||||
return interfaceMethodWrapper(fn.Prog, typ, obj)
|
||||
}
|
||||
|
||||
// T must be an interface; return wrapper.
|
||||
return interfaceMethodWrapper(fn.Prog, typ, id)
|
||||
// TODO(gri): make LookupFieldOrMethod return one of these
|
||||
// so we don't have to construct it.
|
||||
meth := types.NewMethod(obj, typ, indices, isIndirect)
|
||||
// For declared methods, a simple conversion will suffice.
|
||||
return emitConv(fn, fn.Prog.LookupMethod(meth), fn.Pkg.typeOf(e))
|
||||
}
|
||||
|
||||
// Bound method closure? (e.m where m is a method)
|
||||
if m, recv := b.findMethod(fn, e.X, id); m != nil {
|
||||
// selKind == token.VAR
|
||||
|
||||
switch obj := obj.(type) {
|
||||
case *types.Func:
|
||||
// e.f where e is an expression and f is a method.
|
||||
// The result is a bound method closure.
|
||||
wantAddr := isPointer(recvType(obj))
|
||||
escaping := true
|
||||
v := b.receiver(fn, e.X, wantAddr, escaping, indices, isIndirect)
|
||||
// TODO(adonovan): test the case where
|
||||
// *struct{I} inherits a method from interface I.
|
||||
c := &MakeClosure{
|
||||
Fn: boundMethodWrapper(m),
|
||||
Bindings: []Value{recv},
|
||||
Fn: boundMethodWrapper(fn.Prog, obj),
|
||||
Bindings: []Value{v},
|
||||
}
|
||||
c.setPos(e.Sel.Pos())
|
||||
c.setType(fn.Pkg.typeOf(e))
|
||||
return fn.emit(c)
|
||||
|
||||
// TODO(adonovan): add more tests for
|
||||
// interaction of bound interface method
|
||||
// closures and promotion.
|
||||
|
||||
case *types.Var:
|
||||
// e.f where e is an expression and f is a field.
|
||||
last := len(indices) - 1
|
||||
v := b.expr(fn, e.X)
|
||||
v = emitImplicitSelections(fn, v, indices[:last])
|
||||
return emitFieldSelection(fn, v, indices[last], false, e.Sel.Pos())
|
||||
}
|
||||
|
||||
// e.f where e is an expression. f may be a method.
|
||||
return b.selectField(fn, e, false, false)
|
||||
panic("unexpected expression-relative selector")
|
||||
|
||||
case *ast.IndexExpr:
|
||||
switch t := fn.Pkg.typeOf(e.X).Underlying().(type) {
|
||||
|
@ -763,39 +741,27 @@ func (b *builder) stmtList(fn *Function, list []ast.Stmt) {
|
|||
}
|
||||
}
|
||||
|
||||
// findMethod returns the method and receiver for a call base.id().
|
||||
// It locates the method using the method-set for base's type,
|
||||
// and emits code for the receiver, handling the cases where
|
||||
// the formal and actual parameter's pointerness are unequal.
|
||||
// receiver emits to fn code for expression e in the "receiver"
|
||||
// position of selection e.f (where f may be a field or a method) and
|
||||
// returns the effective receiver after applying the implicit field
|
||||
// selections of indices (the last element of which is ignored).
|
||||
//
|
||||
// findMethod returns (nil, nil) if no such method was found.
|
||||
// wantAddr requests that the result is an an address. If
|
||||
// !isIndirect, this may require that e be build in addr() mode; it
|
||||
// must thus be addressable.
|
||||
//
|
||||
func (b *builder) findMethod(fn *Function, base ast.Expr, id string) (*Function, Value) {
|
||||
typ := fn.Pkg.typeOf(base)
|
||||
// escaping is defined as per builder.addr().
|
||||
//
|
||||
func (b *builder) receiver(fn *Function, e ast.Expr, wantAddr, escaping bool, indices []int, isIndirect bool) Value {
|
||||
var v Value
|
||||
if wantAddr && !isIndirect && !isPointer(fn.Pkg.typeOf(e)) {
|
||||
v = b.addr(fn, e, escaping).address(fn)
|
||||
} else {
|
||||
v = b.expr(fn, e)
|
||||
}
|
||||
|
||||
// Consult method-set of X.
|
||||
if m := fn.Prog.LookupMethod(typ, id); m != nil {
|
||||
aptr := isPointer(typ)
|
||||
fptr := isPointer(m.Signature.Recv().Type())
|
||||
if aptr == fptr {
|
||||
// Actual's and formal's "pointerness" match.
|
||||
return m, b.expr(fn, base)
|
||||
}
|
||||
// Actual is a pointer, formal is not.
|
||||
// Load a copy.
|
||||
return m, emitLoad(fn, b.expr(fn, base))
|
||||
}
|
||||
if !isPointer(typ) {
|
||||
// Consult method-set of *X.
|
||||
if m := fn.Prog.LookupMethod(types.NewPointer(typ), id); m != nil {
|
||||
// A method found only in MS(*X) must have a
|
||||
// pointer formal receiver; but the actual
|
||||
// value is not a pointer.
|
||||
// Implicit & -- possibly escaping.
|
||||
return m, b.addr(fn, base, true).address(fn)
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
last := len(indices) - 1
|
||||
return emitImplicitSelections(fn, v, indices[:last])
|
||||
}
|
||||
|
||||
// setCallFunc populates the function parts of a CallCommon structure
|
||||
|
@ -809,53 +775,90 @@ func (b *builder) setCallFunc(fn *Function, e *ast.CallExpr, c *CallCommon) {
|
|||
// Is the call of the form x.f()?
|
||||
sel, ok := unparen(e.Fun).(*ast.SelectorExpr)
|
||||
|
||||
// Case 0: e.Fun evaluates normally to a function.
|
||||
if !ok || fn.Pkg.info.IsPackageRef(sel) != nil {
|
||||
// e.Fun is not a selector.
|
||||
// Evaluate it in the usual way.
|
||||
if !ok {
|
||||
c.Func = b.expr(fn, e.Fun)
|
||||
return
|
||||
}
|
||||
|
||||
// Case 1: X.f() or (*X).f(): a statically dipatched call to
|
||||
// the method f in the method-set of X or *X. X may be
|
||||
// an interface. Treat like case 0.
|
||||
// TODO(adonovan): opt: inline expr() here, to make the call static
|
||||
// and to avoid generation of a stub for an interface method.
|
||||
if fn.Pkg.info.IsType(sel.X) {
|
||||
selKind := fn.Pkg.info.ClassifySelector(sel)
|
||||
|
||||
// e.Fun refers to a package-level func or var.
|
||||
// Evaluate it in the usual way.
|
||||
if selKind == token.PACKAGE {
|
||||
c.Func = b.expr(fn, e.Fun)
|
||||
return
|
||||
}
|
||||
|
||||
id := types.Id(fn.Pkg.Object, sel.Sel.Name)
|
||||
typ := fn.Pkg.typeOf(sel.X)
|
||||
obj, indices, isIndirect := types.LookupFieldOrMethod(typ, fn.Pkg.Object, sel.Sel.Name)
|
||||
|
||||
// Let X be the type of x.
|
||||
// T.f() or (*T).f(): a statically dispatched call to the
|
||||
// method f in the method-set of T or *T.
|
||||
// T may be an interface.
|
||||
if selKind == token.TYPE {
|
||||
// e.Fun would evaluate to a concrete method,
|
||||
// interface wrapper function, or promotion wrapper.
|
||||
//
|
||||
// For now, we evaluate it in the usual way.
|
||||
c.Func = b.expr(fn, e.Fun)
|
||||
|
||||
// Case 2: x.f(): a statically dispatched call to a method
|
||||
// from the method-set of X or perhaps *X (if x is addressable
|
||||
// but not a pointer).
|
||||
if m, recv := b.findMethod(fn, sel.X, id); m != nil {
|
||||
c.Func = m
|
||||
c.Args = append(c.Args, recv)
|
||||
// TODO(adonovan): opt: inline expr() here, to make
|
||||
// the call static and to avoid generation of
|
||||
// wrappers. It's somewhat tricky as it may consume
|
||||
// the first actual parameter if the call is "invoke"
|
||||
// mode.
|
||||
//
|
||||
// Examples:
|
||||
// type T struct{}; func (T) f() {} // "call" mode
|
||||
// type T interface { f() } // "invoke" mode
|
||||
//
|
||||
// type S struct{ T }
|
||||
//
|
||||
// var s S
|
||||
// S.f(s)
|
||||
// (*S).f(&s)
|
||||
//
|
||||
// Suggested approach:
|
||||
// - consume the first actual parameter expression
|
||||
// and build it with b.expr().
|
||||
// - apply implicit field selections.
|
||||
// - use same code as selKind == VAR case to populate fields of c.
|
||||
return
|
||||
}
|
||||
|
||||
switch t := fn.Pkg.typeOf(sel.X).Underlying().(type) {
|
||||
case *types.Struct, *types.Pointer:
|
||||
// Case 3: x.f() where x.f is a function value in a
|
||||
// struct field f; not a method call. f is a 'var'
|
||||
// (of function type) in the Fields of types.Struct X.
|
||||
// Treat like case 0.
|
||||
c.Func = b.expr(fn, e.Fun)
|
||||
// selKind == token.VAR
|
||||
|
||||
case *types.Interface:
|
||||
// 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, _ = interfaceMethodIndex(t, id)
|
||||
c.Recv = b.expr(fn, sel.X)
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("illegal (%s).%s() call; X:%T", t, sel.Sel.Name, sel.X))
|
||||
switch obj := obj.(type) {
|
||||
case *types.Func:
|
||||
wantAddr := isPointer(recvType(obj))
|
||||
escaping := true
|
||||
v := b.receiver(fn, sel.X, wantAddr, escaping, indices, isIndirect)
|
||||
if _, ok := deref(v.Type()).Underlying().(*types.Interface); ok {
|
||||
if isPointer(v.Type()) {
|
||||
// *struct{I} inherits methods from I.
|
||||
v = emitLoad(fn, v)
|
||||
}
|
||||
// Invoke-mode call.
|
||||
c.Recv = v
|
||||
c.Method = obj
|
||||
} else {
|
||||
// "Call"-mode call.
|
||||
c.Func = fn.Prog.concreteMethod(obj)
|
||||
c.Args = append(c.Args, v)
|
||||
}
|
||||
return
|
||||
|
||||
case *types.Var:
|
||||
// Field access: x.f() where x.f is a function value
|
||||
// in a struct field f; not a method call.
|
||||
// Evaluate it in the usual way.
|
||||
c.Func = b.expr(fn, e.Fun)
|
||||
return
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("illegal (%s).%s() call; X:%T", typ, sel.Sel.Name, sel.X))
|
||||
}
|
||||
|
||||
// emitCallArgs emits to f code for the actual parameters of call e to
|
||||
|
@ -910,7 +913,7 @@ func (b *builder) emitCallArgs(fn *Function, sig *types.Signature, e *ast.CallEx
|
|||
} else {
|
||||
// Replace a suffix of args with a slice containing it.
|
||||
at := types.NewArray(vt, int64(len(varargs)))
|
||||
// Don't set pos (e.g. to e.Lparen) for implicit Allocs.
|
||||
// Don't set pos for implicit Allocs.
|
||||
a := emitNew(fn, at, token.NoPos)
|
||||
for i, arg := range varargs {
|
||||
iaddr := &IndexAddr{
|
||||
|
@ -2235,7 +2238,7 @@ func (b *builder) buildDecl(pkg *Package, decl ast.Decl) {
|
|||
}
|
||||
nt := pkg.objectOf(id).Type().(*types.Named)
|
||||
for i, n := 0, nt.NumMethods(); i < n; i++ {
|
||||
b.buildFunction(pkg.Prog.concreteMethods[nt.Method(i)])
|
||||
b.buildFunction(pkg.Prog.concreteMethod(nt.Method(i)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2331,9 +2334,9 @@ func (p *Package) Build() {
|
|||
emitStore(init, initguard, vTrue)
|
||||
|
||||
// Call the init() function of each package we import.
|
||||
for _, typkg := range p.info.Imports() {
|
||||
for _, obj := range p.info.Imports() {
|
||||
var v Call
|
||||
v.Call.Func = p.Prog.packages[typkg].init
|
||||
v.Call.Func = p.Prog.packages[obj].init
|
||||
v.Call.pos = init.pos
|
||||
v.setType(types.NewTuple())
|
||||
init.emit(&v)
|
||||
|
|
|
@ -27,7 +27,7 @@ import (
|
|||
func main() {
|
||||
var t testing.T
|
||||
t.Parallel() // static call to external declared method
|
||||
t.Fail() // static call to synthetic promotion wrapper
|
||||
t.Fail() // static call to promoted external declared method
|
||||
testing.Short() // static call to external package-level function
|
||||
|
||||
var w io.Writer = new(bytes.Buffer)
|
||||
|
@ -110,7 +110,7 @@ func main() {
|
|||
|
||||
expectedCallee := []string{
|
||||
"(*testing.T).Parallel",
|
||||
"(*testing.T).Fail",
|
||||
"(*testing.common).Fail",
|
||||
"testing.Short",
|
||||
"N/A",
|
||||
}
|
||||
|
@ -123,7 +123,7 @@ func main() {
|
|||
if want := expectedCallee[callNum]; want != "N/A" {
|
||||
got := call.StaticCallee().String()
|
||||
if want != got {
|
||||
t.Errorf("%dth call from main.main: got callee %s, want %s",
|
||||
t.Errorf("call #%d from main.main: got callee %s, want %s",
|
||||
callNum, got, want)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,8 +41,7 @@ func NewProgram(fset *token.FileSet, mode BuilderMode) *Program {
|
|||
packages: make(map[*types.Package]*Package),
|
||||
builtins: make(map[types.Object]*Builtin),
|
||||
concreteMethods: make(map[*types.Func]*Function),
|
||||
indirectionWrappers: make(map[*Function]*Function),
|
||||
boundMethodWrappers: make(map[*Function]*Function),
|
||||
boundMethodWrappers: make(map[*types.Func]*Function),
|
||||
ifaceMethodWrappers: make(map[*types.Func]*Function),
|
||||
mode: mode,
|
||||
}
|
||||
|
@ -118,23 +117,13 @@ func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) {
|
|||
pkg.values[obj] = fn
|
||||
pkg.Members[name] = fn
|
||||
} else {
|
||||
// TODO(adonovan): interface methods now have
|
||||
// objects, but we probably don't want to call
|
||||
// memberFromObject for them.
|
||||
|
||||
// Method declaration.
|
||||
// TODO(adonovan) Move this test elsewhere.
|
||||
if _, ok := recv.Type().Underlying().(*types.Interface); ok {
|
||||
return // ignore interface methods
|
||||
}
|
||||
_, method := namedTypeMethodIndex(
|
||||
deref(recv.Type()).(*types.Named),
|
||||
types.Id(pkg.Object, name))
|
||||
pkg.Prog.concreteMethods[method] = fn
|
||||
// Concrete method.
|
||||
_ = deref(recv.Type()).(*types.Named) // assertion
|
||||
pkg.Prog.concreteMethods[obj] = fn
|
||||
}
|
||||
|
||||
default: // (incl. *types.Package)
|
||||
panic(fmt.Sprintf("unexpected Object type: %T", obj))
|
||||
panic("unexpected Object type: " + obj.String())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -239,20 +228,13 @@ func (prog *Program) CreatePackage(info *importer.PackageInfo) *Package {
|
|||
scope := p.Object.Scope()
|
||||
for _, name := range scope.Names() {
|
||||
obj := scope.Lookup(name)
|
||||
if obj, ok := obj.(*types.TypeName); ok {
|
||||
// TODO(adonovan): are the set of Func
|
||||
// objects passed to memberFromObject
|
||||
// duplicate-free? I doubt it. Check.
|
||||
mset := types.NewMethodSet(obj.Type())
|
||||
for i, n := 0, mset.Len(); i < n; i++ {
|
||||
memberFromObject(p, mset.At(i).Func, nil)
|
||||
}
|
||||
mset = types.NewMethodSet(types.NewPointer(obj.Type()))
|
||||
for i, n := 0, mset.Len(); i < n; i++ {
|
||||
memberFromObject(p, mset.At(i).Func, nil)
|
||||
}
|
||||
}
|
||||
memberFromObject(p, obj, nil)
|
||||
if obj, ok := obj.(*types.TypeName); ok {
|
||||
named := obj.Type().(*types.Named)
|
||||
for i, n := 0, named.NumMethods(); i < n; i++ {
|
||||
memberFromObject(p, named.Method(i), nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
39
ssa/emit.go
39
ssa/emit.go
|
@ -273,7 +273,8 @@ func emitTypeAssert(f *Function, x Value, t types.Type, pos token.Pos) Value {
|
|||
if ti, ok := t.Underlying().(*types.Interface); ok {
|
||||
// Even when ti==txi, we still need ChangeInterface
|
||||
// since it performs a nil-check.
|
||||
if isSuperinterface(ti, txi) {
|
||||
// TODO(adonovan): needs more tests.
|
||||
if types.IsAssignableTo(ti, txi) {
|
||||
c := &ChangeInterface{X: x}
|
||||
c.setPos(pos)
|
||||
c.setType(t)
|
||||
|
@ -347,6 +348,10 @@ func emitTailCall(f *Function, call *Call) {
|
|||
// implicit field selections specified by indices to base value v, and
|
||||
// returns the selected value.
|
||||
//
|
||||
// If v is the address of a struct, the result will be the address of
|
||||
// a field; if it is the value of a struct, the result will be the
|
||||
// value of a field.
|
||||
//
|
||||
func emitImplicitSelections(f *Function, v Value, indices []int) Value {
|
||||
for _, index := range indices {
|
||||
fld := deref(v.Type()).Underlying().(*types.Struct).Field(index)
|
||||
|
@ -373,3 +378,35 @@ func emitImplicitSelections(f *Function, v Value, indices []int) Value {
|
|||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// emitFieldSelection emits to f code to select the index'th field of v.
|
||||
//
|
||||
// If wantAddr, the input must be a pointer-to-struct and the result
|
||||
// will be the field's address; otherwise the result will be the
|
||||
// field's value.
|
||||
//
|
||||
func emitFieldSelection(f *Function, v Value, index int, wantAddr bool, pos token.Pos) Value {
|
||||
fld := deref(v.Type()).Underlying().(*types.Struct).Field(index)
|
||||
if isPointer(v.Type()) {
|
||||
instr := &FieldAddr{
|
||||
X: v,
|
||||
Field: index,
|
||||
}
|
||||
instr.setPos(pos)
|
||||
instr.setType(types.NewPointer(fld.Type()))
|
||||
v = f.emit(instr)
|
||||
// Load the field's value iff we don't want its address.
|
||||
if !wantAddr {
|
||||
v = emitLoad(f, v)
|
||||
}
|
||||
} else {
|
||||
instr := &Field{
|
||||
X: v,
|
||||
Field: index,
|
||||
}
|
||||
instr.setPos(pos)
|
||||
instr.setType(fld.Type())
|
||||
v = f.emit(instr)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
|
|
@ -461,7 +461,8 @@ func (f *Function) emit(instr Instruction) Value {
|
|||
//
|
||||
// If from==f.Pkg, suppress package qualification.
|
||||
func (f *Function) fullName(from *Package) string {
|
||||
// TODO(adonovan): expose less fragile case discrimination.
|
||||
// TODO(adonovan): expose less fragile case discrimination
|
||||
// using f.method.
|
||||
|
||||
// Anonymous?
|
||||
if f.Enclosing != nil {
|
||||
|
|
|
@ -133,16 +133,16 @@ func (fr *frame) rundefers() {
|
|||
fr.defers = fr.defers[:0]
|
||||
}
|
||||
|
||||
// findMethodSet returns the method set for type typ, which may be one
|
||||
// lookupMethod returns the method set for type typ, which may be one
|
||||
// of the interpreter's fake types.
|
||||
func findMethodSet(i *interpreter, typ types.Type) ssa.MethodSet {
|
||||
func lookupMethod(i *interpreter, typ types.Type, meth *types.Func) *ssa.Function {
|
||||
switch typ {
|
||||
case rtypeType:
|
||||
return i.rtypeMethods
|
||||
return i.rtypeMethods[meth.Id()]
|
||||
case errorType:
|
||||
return i.errorMethods
|
||||
return i.errorMethods[meth.Id()]
|
||||
}
|
||||
return i.prog.MethodSet(typ)
|
||||
return i.prog.LookupMethod(typ.MethodSet().Lookup(meth.Pkg(), meth.Name()))
|
||||
}
|
||||
|
||||
// visitInstr interprets a single ssa.Instruction within the activation
|
||||
|
@ -391,11 +391,10 @@ func prepareCall(fr *frame, call *ssa.CallCommon) (fn value, args []value) {
|
|||
if recv.t == nil {
|
||||
panic("method invoked on nil interface")
|
||||
}
|
||||
id := call.MethodId()
|
||||
fn = findMethodSet(fr.i, recv.t)[id]
|
||||
fn = lookupMethod(fr.i, recv.t, call.Method)
|
||||
if fn == nil {
|
||||
// Unreachable in well-typed programs.
|
||||
panic(fmt.Sprintf("method set for dynamic type %v does not contain %s", recv.t, id))
|
||||
panic(fmt.Sprintf("method set for dynamic type %v does not contain %s", recv.t, call.Method))
|
||||
}
|
||||
args = append(args, copyVal(recv.v))
|
||||
}
|
||||
|
|
|
@ -89,6 +89,17 @@ func typeCheck() {
|
|||
_ = i.(func()) // type assertion: receiver type disappears
|
||||
}
|
||||
|
||||
type errString string
|
||||
|
||||
func (err errString) Error() string {
|
||||
return string(err)
|
||||
}
|
||||
|
||||
// Regression test for a builder crash.
|
||||
func regress1(x error) func() string {
|
||||
return x.Error
|
||||
}
|
||||
|
||||
func main() {
|
||||
valueReceiver()
|
||||
pointerReceiver()
|
||||
|
@ -96,4 +107,8 @@ func main() {
|
|||
promotedReceiver()
|
||||
anonStruct()
|
||||
typeCheck()
|
||||
|
||||
if e := regress1(errString("hi"))(); e != "hi" {
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,10 +26,33 @@ func (impl) two() string {
|
|||
func main() {
|
||||
var s S
|
||||
s.I = impl{}
|
||||
if one := s.I.one(); one != 1 {
|
||||
panic(one)
|
||||
}
|
||||
if one := s.one(); one != 1 {
|
||||
panic(one)
|
||||
}
|
||||
closOne := s.I.one
|
||||
if one := closOne(); one != 1 {
|
||||
panic(one)
|
||||
}
|
||||
closOne = s.one
|
||||
if one := closOne(); one != 1 {
|
||||
panic(one)
|
||||
}
|
||||
|
||||
if two := s.I.two(); two != "two" {
|
||||
panic(two)
|
||||
}
|
||||
if two := s.two(); two != "two" {
|
||||
panic(two)
|
||||
}
|
||||
closTwo := s.I.two
|
||||
if two := closTwo(); two != "two" {
|
||||
panic(two)
|
||||
}
|
||||
closTwo = s.two
|
||||
if two := closTwo(); two != "two" {
|
||||
panic(two)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -113,8 +113,7 @@ func printCall(v *CallCommon, prefix string, instr Instruction) string {
|
|||
if !v.IsInvoke() {
|
||||
b.WriteString(relName(v.Func, instr))
|
||||
} else {
|
||||
name := v.Recv.Type().Underlying().(*types.Interface).Method(v.Method).Name()
|
||||
fmt.Fprintf(&b, "invoke %s.%s [#%d]", relName(v.Recv, instr), name, v.Method)
|
||||
fmt.Fprintf(&b, "invoke %s.%s", relName(v.Recv, instr), v.Method.Name())
|
||||
}
|
||||
b.WriteString("(")
|
||||
for i, arg := range v.Args {
|
||||
|
|
278
ssa/promote.go
278
ssa/promote.go
|
@ -4,10 +4,9 @@ package ssa
|
|||
// synthesis of wrapper methods.
|
||||
//
|
||||
// Wrappers include:
|
||||
// - promotion wrappers for methods of embedded fields.
|
||||
// - indirection/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.
|
||||
|
||||
|
@ -18,35 +17,54 @@ import (
|
|||
"code.google.com/p/go.tools/go/types"
|
||||
)
|
||||
|
||||
// recvType returns the receiver type of method obj.
|
||||
func recvType(obj *types.Func) types.Type {
|
||||
return obj.Type().(*types.Signature).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.
|
||||
//
|
||||
// This function should only be called when you need to construct the
|
||||
// entire method set, synthesizing all wrappers, for example during
|
||||
// the processing of a MakeInterface instruction or when visiting all
|
||||
// reachable functions.
|
||||
//
|
||||
// If you only need to look up a single method (obj), avoid this
|
||||
// function and use LookupMethod instead:
|
||||
//
|
||||
// meth := types.MethodSet(typ).Lookup(pkg, name)
|
||||
// m := prog.MethodSet(typ)[meth.Id()] // don't do this
|
||||
// m := prog.LookupMethod(meth) // use this instead
|
||||
//
|
||||
// If you only need to enumerate the keys, use types.MethodSet
|
||||
// instead.
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
|
||||
//
|
||||
// Thread-safe.
|
||||
//
|
||||
func (prog *Program) MethodSet(typ types.Type) MethodSet {
|
||||
return prog.populateMethodSet(typ, "")
|
||||
return prog.populateMethodSet(typ, nil)
|
||||
}
|
||||
|
||||
// populateMethodSet returns the method set for typ, ensuring that it
|
||||
// contains at least key id. If id is empty, the entire method set is
|
||||
// populated.
|
||||
// contains at least the value for obj, if that is a key.
|
||||
// If id is empty, the entire method set is populated.
|
||||
//
|
||||
func (prog *Program) populateMethodSet(typ types.Type, id string) MethodSet {
|
||||
tmset := typ.MethodSet()
|
||||
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
|
||||
//
|
||||
func (prog *Program) populateMethodSet(typ types.Type, meth *types.Method) MethodSet {
|
||||
tmset := methodSet(typ)
|
||||
n := tmset.Len()
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
if _, ok := deref(typ).Underlying().(*types.Interface); ok {
|
||||
// TODO(gri): fix: go/types bug: pointer-to-interface
|
||||
// has no methods---yet go/types says it has!
|
||||
return nil
|
||||
}
|
||||
|
||||
if prog.mode&LogSource != 0 {
|
||||
defer logStack("MethodSet %s id=%s", typ, id)()
|
||||
defer logStack("populateMethodSet %s meth=%v", typ, meth)()
|
||||
}
|
||||
|
||||
prog.methodsMu.Lock()
|
||||
|
@ -59,24 +77,18 @@ func (prog *Program) populateMethodSet(typ types.Type, id string) MethodSet {
|
|||
}
|
||||
|
||||
if len(mset) < n {
|
||||
if id != "" { // single method
|
||||
// tmset.Lookup() is no use to us with only an Id string.
|
||||
if meth != nil { // single method
|
||||
id := meth.Id()
|
||||
if mset[id] == nil {
|
||||
for i := 0; i < n; i++ {
|
||||
obj := tmset.At(i)
|
||||
if obj.Id() == id {
|
||||
mset[id] = makeMethod(prog, typ, obj)
|
||||
return mset
|
||||
mset[id] = findMethod(prog, meth)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// complete set
|
||||
for i := 0; i < n; i++ {
|
||||
obj := tmset.At(i)
|
||||
if id := obj.Id(); mset[id] == nil {
|
||||
mset[id] = makeMethod(prog, typ, obj)
|
||||
meth := tmset.At(i)
|
||||
if id := meth.Id(); mset[id] == nil {
|
||||
mset[id] = findMethod(prog, meth)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -84,49 +96,70 @@ func (prog *Program) populateMethodSet(typ types.Type, id string) MethodSet {
|
|||
return mset
|
||||
}
|
||||
|
||||
func methodSet(typ types.Type) *types.MethodSet {
|
||||
// TODO(adonovan): temporary workaround. Inline it away when fixed.
|
||||
if _, ok := deref(typ).Underlying().(*types.Interface); ok && isPointer(typ) {
|
||||
// TODO(gri): fix: go/types bug: pointer-to-interface
|
||||
// has no methods---yet go/types says it has!
|
||||
return new(types.MethodSet)
|
||||
}
|
||||
return typ.MethodSet()
|
||||
}
|
||||
|
||||
// LookupMethod returns the method id of type typ, building wrapper
|
||||
// methods on demand. It returns nil if the typ has no such method.
|
||||
//
|
||||
// Thread-safe.
|
||||
//
|
||||
func (prog *Program) LookupMethod(typ types.Type, id string) *Function {
|
||||
return prog.populateMethodSet(typ, id)[id]
|
||||
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
|
||||
//
|
||||
func (prog *Program) LookupMethod(meth *types.Method) *Function {
|
||||
return prog.populateMethodSet(meth.Recv(), meth)[meth.Id()]
|
||||
}
|
||||
|
||||
// makeMethod returns the concrete Function for the method obj,
|
||||
// adapted if necessary so that its receiver type is typ.
|
||||
// concreteMethod returns the concrete method denoted by obj.
|
||||
// Panic ensues if there is no such method (e.g. it's a standalone
|
||||
// function).
|
||||
//
|
||||
func (prog *Program) concreteMethod(obj *types.Func) *Function {
|
||||
fn := prog.concreteMethods[obj]
|
||||
if fn == nil {
|
||||
panic("no concrete method: " + obj.String())
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
||||
// findMethod returns the concrete Function for the method meth,
|
||||
// synthesizing wrappers as needed.
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
|
||||
//
|
||||
func makeMethod(prog *Program, typ types.Type, obj *types.Method) *Function {
|
||||
// Promoted method accessed via implicit field selections?
|
||||
if len(obj.Index()) > 1 {
|
||||
return promotionWrapper(prog, typ, obj)
|
||||
func findMethod(prog *Program, meth *types.Method) *Function {
|
||||
needsPromotion := len(meth.Index()) > 1
|
||||
needsIndirection := !isPointer(recvType(meth.Func)) && isPointer(meth.Recv())
|
||||
|
||||
if needsPromotion || needsIndirection {
|
||||
return makeWrapper(prog, meth.Recv(), meth)
|
||||
}
|
||||
|
||||
method := prog.concreteMethods[obj.Func]
|
||||
if method == nil {
|
||||
panic("no concrete method for " + obj.Func.String())
|
||||
if _, ok := meth.Recv().Underlying().(*types.Interface); ok {
|
||||
return interfaceMethodWrapper(prog, meth.Recv(), meth.Func)
|
||||
}
|
||||
|
||||
// Call to method on T from receiver of type *T?
|
||||
if !isPointer(method.Signature.Recv().Type()) && isPointer(typ) {
|
||||
method = indirectionWrapper(method)
|
||||
}
|
||||
|
||||
return method
|
||||
// Invariant: fn.Signature.Recv().Type() == recvType(meth.Func)
|
||||
return prog.concreteMethod(meth.Func)
|
||||
}
|
||||
|
||||
// promotionWrapper returns a synthetic wrapper Function that performs
|
||||
// a sequence of implicit field selections then tailcalls a "promoted"
|
||||
// method. For example, given these decls:
|
||||
// makeWrapper returns a synthetic wrapper Function that optionally
|
||||
// performs receiver indirection, implicit field selections and then a
|
||||
// tailcall of 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, obj={Func:(*C).f, Indices=[B,C,f]})
|
||||
// then makeWrapper(typ=A, obj={Func:(*C).f, Indices=[B,C,f]})
|
||||
// synthesize this wrapper method:
|
||||
//
|
||||
// func (a A) f() { return a.B.C->f() }
|
||||
|
@ -138,23 +171,21 @@ func makeMethod(prog *Program, typ types.Type, obj *types.Method) *Function {
|
|||
//
|
||||
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
|
||||
//
|
||||
func promotionWrapper(prog *Program, typ types.Type, obj *types.Method) *Function {
|
||||
old := obj.Func.Type().(*types.Signature)
|
||||
func makeWrapper(prog *Program, typ types.Type, meth *types.Method) *Function {
|
||||
old := meth.Func.Type().(*types.Signature)
|
||||
sig := types.NewSignature(nil, types.NewVar(token.NoPos, nil, "recv", typ), old.Params(), old.Results(), old.IsVariadic())
|
||||
|
||||
// TODO(adonovan): include implicit field path in description.
|
||||
description := fmt.Sprintf("promotion wrapper for (%s).%s", old.Recv(), obj.Func.Name())
|
||||
|
||||
description := fmt.Sprintf("wrapper for (%s).%s", old.Recv(), meth.Func.Name())
|
||||
if prog.mode&LogSource != 0 {
|
||||
defer logStack("make %s to (%s)", description, typ)()
|
||||
}
|
||||
fn := &Function{
|
||||
name: obj.Name(),
|
||||
object: obj,
|
||||
name: meth.Name(),
|
||||
method: meth,
|
||||
Signature: sig,
|
||||
Synthetic: description,
|
||||
Prog: prog,
|
||||
pos: obj.Pos(),
|
||||
pos: meth.Pos(),
|
||||
}
|
||||
fn.startBody()
|
||||
fn.addSpilledParam(sig.Recv())
|
||||
|
@ -162,6 +193,8 @@ func promotionWrapper(prog *Program, typ types.Type, obj *types.Method) *Functio
|
|||
|
||||
var v Value = fn.Locals[0] // spilled receiver
|
||||
if isPointer(typ) {
|
||||
// TODO(adonovan): consider emitting a nil-pointer check here
|
||||
// with a nice error message, like gc does.
|
||||
v = emitLoad(fn, v)
|
||||
}
|
||||
|
||||
|
@ -173,7 +206,7 @@ func promotionWrapper(prog *Program, typ types.Type, obj *types.Method) *Functio
|
|||
// Load) in preference to value extraction (Field possibly
|
||||
// preceded by Load).
|
||||
|
||||
indices := obj.Index()
|
||||
indices := meth.Index()
|
||||
v = emitImplicitSelections(fn, v, indices[:len(indices)-1])
|
||||
|
||||
// Invariant: v is a pointer, either
|
||||
|
@ -185,14 +218,10 @@ func promotionWrapper(prog *Program, typ types.Type, obj *types.Method) *Functio
|
|||
if !isPointer(old.Recv().Type()) {
|
||||
v = emitLoad(fn, v)
|
||||
}
|
||||
m := prog.concreteMethods[obj.Func]
|
||||
if m == nil {
|
||||
panic("oops: " + fn.Synthetic)
|
||||
}
|
||||
c.Call.Func = m
|
||||
c.Call.Func = prog.concreteMethod(meth.Func)
|
||||
c.Call.Args = append(c.Call.Args, v)
|
||||
} else {
|
||||
c.Call.Method = indices[len(indices)-1]
|
||||
c.Call.Method = meth.Func
|
||||
c.Call.Recv = emitLoad(fn, v)
|
||||
}
|
||||
for _, arg := range fn.Params[1:] {
|
||||
|
@ -219,9 +248,9 @@ func createParams(fn *Function) {
|
|||
|
||||
// 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.:
|
||||
// interfaceMethodWrapper returns a synthetic wrapper function
|
||||
// permitting an abstract method obj to be called like a standalone
|
||||
// function, e.g.:
|
||||
//
|
||||
// type I interface { f(x int) R }
|
||||
// m := I.f // wrapper
|
||||
|
@ -234,38 +263,41 @@ func createParams(fn *Function) {
|
|||
// return i.f(x, ...)
|
||||
// }
|
||||
//
|
||||
// typ is the type of the receiver (I here). It isn't necessarily
|
||||
// equal to the recvType(obj) because one interface may embed another.
|
||||
// TODO(adonovan): more tests.
|
||||
//
|
||||
// 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(prog.methodsMu)
|
||||
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
|
||||
//
|
||||
func interfaceMethodWrapper(prog *Program, typ types.Type, id string) *Function {
|
||||
index, meth := interfaceMethodIndex(typ.Underlying().(*types.Interface), id)
|
||||
prog.methodsMu.Lock()
|
||||
defer prog.methodsMu.Unlock()
|
||||
func interfaceMethodWrapper(prog *Program, typ types.Type, obj *types.Func) *Function {
|
||||
// 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]
|
||||
fn, ok := prog.ifaceMethodWrappers[obj]
|
||||
if !ok {
|
||||
description := fmt.Sprintf("interface method wrapper for %s.%s", typ, obj)
|
||||
if prog.mode&LogSource != 0 {
|
||||
defer logStack("interfaceMethodWrapper %s.%s", typ, id)()
|
||||
defer logStack("%s", description)()
|
||||
}
|
||||
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(),
|
||||
name: obj.Name(),
|
||||
object: obj,
|
||||
Signature: obj.Type().(*types.Signature),
|
||||
Synthetic: description,
|
||||
pos: obj.Pos(),
|
||||
Prog: prog,
|
||||
}
|
||||
fn.startBody()
|
||||
fn.addParam("recv", typ, token.NoPos)
|
||||
createParams(fn)
|
||||
var c Call
|
||||
c.Call.Method = index
|
||||
|
||||
c.Call.Method = obj
|
||||
c.Call.Recv = fn.Params[0]
|
||||
for _, arg := range fn.Params[1:] {
|
||||
c.Call.Args = append(c.Call.Args, arg)
|
||||
|
@ -273,7 +305,7 @@ func interfaceMethodWrapper(prog *Program, typ types.Type, id string) *Function
|
|||
emitTailCall(fn, &c)
|
||||
fn.finishBody()
|
||||
|
||||
prog.ifaceMethodWrappers[meth] = fn
|
||||
prog.ifaceMethodWrappers[obj] = fn
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
@ -281,12 +313,12 @@ func interfaceMethodWrapper(prog *Program, typ types.Type, id string) *Function
|
|||
// 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.:
|
||||
// delegates to a concrete or interface 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
|
||||
// type T int or: type T interface { meth() }
|
||||
// func (t T) meth()
|
||||
// var t T
|
||||
// f := t.meth
|
||||
|
@ -298,22 +330,22 @@ func interfaceMethodWrapper(prog *Program, typ types.Type, id string) *Function
|
|||
//
|
||||
// EXCLUSIVE_LOCKS_ACQUIRED(meth.Prog.methodsMu)
|
||||
//
|
||||
func boundMethodWrapper(meth *Function) *Function {
|
||||
prog := meth.Prog
|
||||
func boundMethodWrapper(prog *Program, obj *types.Func) *Function {
|
||||
prog.methodsMu.Lock()
|
||||
defer prog.methodsMu.Unlock()
|
||||
fn, ok := prog.boundMethodWrappers[meth]
|
||||
fn, ok := prog.boundMethodWrappers[obj]
|
||||
if !ok {
|
||||
description := fmt.Sprintf("bound method wrapper for %s", obj)
|
||||
if prog.mode&LogSource != 0 {
|
||||
defer logStack("boundMethodWrapper %s", meth)()
|
||||
defer logStack("%s", description)()
|
||||
}
|
||||
s := meth.Signature
|
||||
s := obj.Type().(*types.Signature)
|
||||
fn = &Function{
|
||||
name: "bound$" + meth.String(),
|
||||
name: "bound$" + obj.String(),
|
||||
Signature: types.NewSignature(nil, nil, s.Params(), s.Results(), s.IsVariadic()), // drop recv
|
||||
Synthetic: "bound method wrapper for " + meth.String(),
|
||||
Synthetic: description,
|
||||
Prog: prog,
|
||||
pos: meth.Pos(),
|
||||
pos: obj.Pos(),
|
||||
}
|
||||
|
||||
cap := &Capture{name: "recv", typ: s.Recv().Type(), parent: fn}
|
||||
|
@ -321,65 +353,21 @@ func boundMethodWrapper(meth *Function) *Function {
|
|||
fn.startBody()
|
||||
createParams(fn)
|
||||
var c Call
|
||||
c.Call.Func = meth
|
||||
|
||||
if _, ok := recvType(obj).Underlying().(*types.Interface); !ok { // concrete
|
||||
c.Call.Func = prog.concreteMethod(obj)
|
||||
c.Call.Args = []Value{cap}
|
||||
} else {
|
||||
c.Call.Recv = cap
|
||||
c.Call.Method = obj
|
||||
}
|
||||
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(nil, 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
|
||||
prog.boundMethodWrappers[obj] = fn
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
|
|
@ -257,6 +257,8 @@ func (prog *Program) ConstValue(obj *types.Const) *Const {
|
|||
// because its package was not built, the DebugInfo flag was not set
|
||||
// during SSA construction, or the value was optimized away.
|
||||
//
|
||||
// TODO(adonovan): test on x.f where x is a field.
|
||||
//
|
||||
// ref must be the path to an ast.Ident (e.g. from
|
||||
// PathEnclosingInterval), and that ident must resolve to obj.
|
||||
//
|
||||
|
|
40
ssa/ssa.go
40
ssa/ssa.go
|
@ -22,13 +22,12 @@ type Program struct {
|
|||
PackagesByPath map[string]*Package // all loaded Packages, keyed by import path
|
||||
packages map[*types.Package]*Package // all loaded Packages, keyed by object
|
||||
builtins map[types.Object]*Builtin // all built-in functions, keyed by typechecker objects.
|
||||
concreteMethods map[*types.Func]*Function // maps named concrete methods to their code
|
||||
concreteMethods map[*types.Func]*Function // maps declared concrete methods to their code
|
||||
mode BuilderMode // set of mode bits for SSA construction
|
||||
|
||||
methodsMu sync.Mutex // guards the following maps:
|
||||
methodSets typemap.M // maps type to its concrete MethodSet
|
||||
indirectionWrappers map[*Function]*Function // func(*T) wrappers for T-methods
|
||||
boundMethodWrappers map[*Function]*Function // wrappers for curried x.Method closures
|
||||
boundMethodWrappers map[*types.Func]*Function // wrappers for curried x.Method closures
|
||||
ifaceMethodWrappers map[*types.Func]*Function // wrappers for curried I.Method functions
|
||||
}
|
||||
|
||||
|
@ -73,6 +72,10 @@ type Member interface {
|
|||
// The keys of a method set are strings returned by the types.Id()
|
||||
// function.
|
||||
//
|
||||
// TODO(adonovan): encapsulate the representation behind both Id-based
|
||||
// and types.Method-based accessors and enable lazy population.
|
||||
// Perhaps hide it entirely within the Program API.
|
||||
//
|
||||
type MethodSet map[string]*Function
|
||||
|
||||
// A Type is a Member of a Package representing a package-level named type.
|
||||
|
@ -250,7 +253,8 @@ type Instruction interface {
|
|||
//
|
||||
type Function struct {
|
||||
name string
|
||||
object types.Object // a *types.Func; may be nil for init, wrappers, etc.
|
||||
object types.Object // a declared *types.Func; nil for init, wrappers, etc.
|
||||
method *types.Method // info about provenance of synthetic methods [currently unused]
|
||||
Signature *types.Signature
|
||||
pos token.Pos
|
||||
|
||||
|
@ -1177,7 +1181,7 @@ type anInstruction struct {
|
|||
// Each CallCommon exists in one of two modes, function call and
|
||||
// interface method invocation, or "call" and "invoke" for short.
|
||||
//
|
||||
// 1. "call" mode: when Recv is nil (!IsInvoke), a CallCommon
|
||||
// 1. "call" mode: when Method is nil (!IsInvoke), a CallCommon
|
||||
// represents an ordinary function call of the value in Func.
|
||||
//
|
||||
// In the common case in which Func is a *Function, this indicates a
|
||||
|
@ -1197,10 +1201,10 @@ type anInstruction struct {
|
|||
// go t3()
|
||||
// defer t5(...t6)
|
||||
//
|
||||
// 2. "invoke" mode: when Recv is non-nil (IsInvoke), a CallCommon
|
||||
// 2. "invoke" mode: when Method is non-nil (IsInvoke), a CallCommon
|
||||
// represents a dynamically dispatched call to an interface method.
|
||||
// In this mode, Recv is the interface value and Method is the index
|
||||
// of the method within the interface type of the receiver.
|
||||
// In this mode, Recv is the interface value and Method is the
|
||||
// interface's abstract method.
|
||||
//
|
||||
// Recv is implicitly supplied to the concrete method implementation
|
||||
// as the receiver parameter; in other words, Args[0] holds not the
|
||||
|
@ -1219,9 +1223,10 @@ type anInstruction struct {
|
|||
// readability of the printed form.)
|
||||
//
|
||||
type CallCommon struct {
|
||||
Recv Value // receiver, iff interface method invocation
|
||||
Method int // index of interface method; call MethodId() for its Id
|
||||
Func Value // target of call, iff function call
|
||||
// TODO(adonovan): combine Recv/Func fields since Method now discriminates.
|
||||
Recv Value // receiver (in "invoke" mode)
|
||||
Method *types.Func // abstract method (in "invoke" mode)
|
||||
Func Value // target of call (in "call" mode)
|
||||
Args []Value // actual parameters, including receiver in invoke mode
|
||||
HasEllipsis bool // true iff last Args is a slice of '...' args (needed?)
|
||||
pos token.Pos // position of CallExpr.Lparen, iff explicit in source
|
||||
|
@ -1229,7 +1234,7 @@ type CallCommon struct {
|
|||
|
||||
// IsInvoke returns true if this call has "invoke" (not "call") mode.
|
||||
func (c *CallCommon) IsInvoke() bool {
|
||||
return c.Recv != nil
|
||||
return c.Method != nil
|
||||
}
|
||||
|
||||
func (c *CallCommon) Pos() token.Pos { return c.pos }
|
||||
|
@ -1245,9 +1250,8 @@ func (c *CallCommon) Pos() token.Pos { return c.pos }
|
|||
// Signature returns nil for a call to a built-in function.
|
||||
//
|
||||
func (c *CallCommon) Signature() *types.Signature {
|
||||
if c.Recv != nil {
|
||||
iface := c.Recv.Type().Underlying().(*types.Interface)
|
||||
return iface.Method(c.Method).Type().(*types.Signature)
|
||||
if c.Method != nil {
|
||||
return c.Method.Type().(*types.Signature)
|
||||
}
|
||||
sig, _ := c.Func.Type().Underlying().(*types.Signature) // nil for *Builtin
|
||||
return sig
|
||||
|
@ -1265,12 +1269,6 @@ func (c *CallCommon) StaticCallee() *Function {
|
|||
return nil
|
||||
}
|
||||
|
||||
// MethodId returns the Id for the method called by c, which must
|
||||
// have "invoke" mode.
|
||||
func (c *CallCommon) MethodId() string {
|
||||
return c.Recv.Type().Underlying().(*types.Interface).Method(c.Method).Id()
|
||||
}
|
||||
|
||||
// Description returns a description of the mode of this call suitable
|
||||
// for a user interface, e.g. "static method call".
|
||||
func (c *CallCommon) Description() string {
|
||||
|
|
53
ssa/util.go
53
ssa/util.go
|
@ -53,59 +53,6 @@ func deref(typ types.Type) types.Type {
|
|||
return typ
|
||||
}
|
||||
|
||||
// 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 namedTypeMethodIndex(typ *types.Named, id string) (int, *types.Func) {
|
||||
for i, n := 0, typ.NumMethods(); i < n; i++ {
|
||||
m := typ.Method(i)
|
||||
if m.Id() == 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.
|
||||
//
|
||||
func interfaceMethodIndex(typ *types.Interface, id string) (int, *types.Func) {
|
||||
for i, n := 0, typ.NumMethods(); i < n; i++ {
|
||||
m := typ.Method(i)
|
||||
if m.Id() == id {
|
||||
return i, m
|
||||
}
|
||||
}
|
||||
panic(fmt.Sprint("method not found: ", id, " in interface ", typ))
|
||||
}
|
||||
|
||||
// isSuperinterface returns true if x is a superinterface of y,
|
||||
// i.e. x's methods are a subset of y's.
|
||||
//
|
||||
func isSuperinterface(x, y *types.Interface) bool {
|
||||
if y.NumMethods() < x.NumMethods() {
|
||||
return false
|
||||
}
|
||||
// TODO(adonovan): opt: this is quadratic.
|
||||
outer:
|
||||
for i, n := 0, x.NumMethods(); i < n; i++ {
|
||||
xm := x.Method(i)
|
||||
for j, m := 0, y.NumMethods(); j < m; j++ {
|
||||
ym := y.Method(j)
|
||||
if xm.Id() == ym.Id() {
|
||||
if !types.IsIdentical(xm.Type(), ym.Type()) {
|
||||
return false // common name but conflicting types
|
||||
}
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
return false // y doesn't have this method
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// DefaultType returns the default "typed" type for an "untyped" type;
|
||||
// it returns the incoming type for all other types. The default type
|
||||
// for untyped nil is untyped nil.
|
||||
|
|
Загрузка…
Ссылка в новой задаче