mirror of
https://github.com/golang/go
synced 2024-11-18 19:44:46 -07:00
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:
parent
c98ff05fdd
commit
4da31df1c8
@ -23,6 +23,12 @@ type Method struct {
|
|||||||
selectorPath
|
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 returns the promoted signature type of m (i.e., the receiver
|
||||||
// type is Recv()).
|
// type is Recv()).
|
||||||
func (m *Method) Type() Type {
|
func (m *Method) Type() Type {
|
||||||
@ -33,6 +39,10 @@ func (m *Method) Type() Type {
|
|||||||
return &sig
|
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
|
// 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
|
// 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).
|
// field selections (e.g., x.f may be x.a.b.c.f).
|
||||||
|
@ -6,6 +6,7 @@ package types
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/token"
|
"go/token"
|
||||||
|
|
||||||
@ -46,6 +47,10 @@ func Id(pkg *Package, name string) string {
|
|||||||
}
|
}
|
||||||
// unexported names need the package path for differentiation
|
// unexported names need the package path for differentiation
|
||||||
path := ""
|
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 {
|
if pkg != nil {
|
||||||
path = pkg.path
|
path = pkg.path
|
||||||
if path == "" {
|
if path == "" {
|
||||||
@ -82,8 +87,10 @@ func (obj *object) toString(kind string, typ Type) string {
|
|||||||
buf.WriteByte('.')
|
buf.WriteByte('.')
|
||||||
}
|
}
|
||||||
buf.WriteString(obj.name)
|
buf.WriteString(obj.name)
|
||||||
buf.WriteByte(' ')
|
if typ != nil {
|
||||||
writeType(&buf, typ)
|
buf.WriteByte(' ')
|
||||||
|
writeType(&buf, typ)
|
||||||
|
}
|
||||||
|
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
@ -127,7 +134,7 @@ func NewPackage(pos token.Pos, path, name string, scope *Scope, imports map[stri
|
|||||||
return obj
|
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) Path() string { return obj.path }
|
||||||
func (obj *Package) Scope() *Scope { return obj.scope }
|
func (obj *Package) Scope() *Scope { return obj.scope }
|
||||||
func (obj *Package) Imports() map[string]*Package { return obj.imports }
|
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) Anonymous() bool { return obj.anonymous }
|
||||||
func (obj *Var) String() string { return obj.toString("var", obj.typ) }
|
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 {
|
type Func struct {
|
||||||
object
|
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}}
|
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.
|
// A Label represents a declared label.
|
||||||
type Label struct {
|
type Label struct {
|
||||||
@ -198,4 +233,4 @@ func NewLabel(pos token.Pos, name string) *Label {
|
|||||||
return &Label{object{nil, pos, nil, name, nil}}
|
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.
|
// At returns the i'th variable of tuple t.
|
||||||
func (t *Tuple) At(i int) *Var { return t.vars[i] }
|
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 {
|
type Signature struct {
|
||||||
scope *Scope // function scope, always present
|
scope *Scope // function scope, always present
|
||||||
labels *Scope // label scope, or nil (lazily allocated)
|
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}
|
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 }
|
func (s *Signature) Recv() *Var { return s.recv }
|
||||||
|
|
||||||
// Params returns the parameters of signature s, or nil.
|
// Params returns the parameters of signature s, or nil.
|
||||||
|
@ -122,6 +122,26 @@ func (info *PackageInfo) IsPackageRef(sel *ast.SelectorExpr) types.Object {
|
|||||||
return nil
|
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
|
// TypeCaseVar returns the implicit variable created by a single-type
|
||||||
// case clause in a type switch, or nil if not found.
|
// 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
|
// The builder's and Program's indices (maps) are populated and
|
||||||
// mutated during the CREATE phase, but during the BUILD phase they
|
// mutated during the CREATE phase, but during the BUILD phase they
|
||||||
// remain constant. The sole exception is Prog.methodSets, which is
|
// remain constant. The sole exception is Prog.methodSets and its
|
||||||
// protected by a dedicated mutex.
|
// related maps, which are protected by a dedicated mutex.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"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
|
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,
|
// addr lowers a single-result addressable expression e to SSA form,
|
||||||
// emitting code to fn and returning the location (an lvalue) defined
|
// emitting code to fn and returning the location (an lvalue) defined
|
||||||
// by the expression.
|
// 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())
|
panic("undefined package-qualified name: " + obj.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
// e.f where e is an expression.
|
// e.f where e is an expression and f is a field.
|
||||||
return &address{addr: b.selectField(fn, e, true, escaping)}
|
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:
|
case *ast.IndexExpr:
|
||||||
var x Value
|
var x Value
|
||||||
@ -676,37 +625,66 @@ func (b *builder) expr(fn *Function, e ast.Expr) Value {
|
|||||||
return v
|
return v
|
||||||
|
|
||||||
case *ast.SelectorExpr:
|
case *ast.SelectorExpr:
|
||||||
|
selKind := fn.Pkg.info.ClassifySelector(e)
|
||||||
|
|
||||||
// p.M where p is a package.
|
// 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)
|
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.
|
// (*T).f or T.f, the method f from the method-set of type T.
|
||||||
if fn.Pkg.info.IsType(e.X) {
|
// We return a standalone function that calls the method.
|
||||||
typ := fn.Pkg.typeOf(e.X)
|
if selKind == token.TYPE {
|
||||||
if m := fn.Prog.LookupMethod(typ, id); m != nil {
|
obj := obj.(*types.Func)
|
||||||
return emitConv(fn, m, fn.Pkg.typeOf(e))
|
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.
|
// TODO(gri): make LookupFieldOrMethod return one of these
|
||||||
return interfaceMethodWrapper(fn.Prog, typ, id)
|
// 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)
|
// selKind == token.VAR
|
||||||
if m, recv := b.findMethod(fn, e.X, id); m != nil {
|
|
||||||
|
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{
|
c := &MakeClosure{
|
||||||
Fn: boundMethodWrapper(m),
|
Fn: boundMethodWrapper(fn.Prog, obj),
|
||||||
Bindings: []Value{recv},
|
Bindings: []Value{v},
|
||||||
}
|
}
|
||||||
c.setPos(e.Sel.Pos())
|
c.setPos(e.Sel.Pos())
|
||||||
c.setType(fn.Pkg.typeOf(e))
|
c.setType(fn.Pkg.typeOf(e))
|
||||||
return fn.emit(c)
|
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.
|
panic("unexpected expression-relative selector")
|
||||||
return b.selectField(fn, e, false, false)
|
|
||||||
|
|
||||||
case *ast.IndexExpr:
|
case *ast.IndexExpr:
|
||||||
switch t := fn.Pkg.typeOf(e.X).Underlying().(type) {
|
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().
|
// receiver emits to fn code for expression e in the "receiver"
|
||||||
// It locates the method using the method-set for base's type,
|
// position of selection e.f (where f may be a field or a method) and
|
||||||
// and emits code for the receiver, handling the cases where
|
// returns the effective receiver after applying the implicit field
|
||||||
// the formal and actual parameter's pointerness are unequal.
|
// 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) {
|
// escaping is defined as per builder.addr().
|
||||||
typ := fn.Pkg.typeOf(base)
|
//
|
||||||
|
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.
|
last := len(indices) - 1
|
||||||
if m := fn.Prog.LookupMethod(typ, id); m != nil {
|
return emitImplicitSelections(fn, v, indices[:last])
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// setCallFunc populates the function parts of a CallCommon structure
|
// 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()?
|
// Is the call of the form x.f()?
|
||||||
sel, ok := unparen(e.Fun).(*ast.SelectorExpr)
|
sel, ok := unparen(e.Fun).(*ast.SelectorExpr)
|
||||||
|
|
||||||
// Case 0: e.Fun evaluates normally to a function.
|
// e.Fun is not a selector.
|
||||||
if !ok || fn.Pkg.info.IsPackageRef(sel) != nil {
|
// Evaluate it in the usual way.
|
||||||
|
if !ok {
|
||||||
c.Func = b.expr(fn, e.Fun)
|
c.Func = b.expr(fn, e.Fun)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Case 1: X.f() or (*X).f(): a statically dipatched call to
|
selKind := fn.Pkg.info.ClassifySelector(sel)
|
||||||
// the method f in the method-set of X or *X. X may be
|
|
||||||
// an interface. Treat like case 0.
|
// e.Fun refers to a package-level func or var.
|
||||||
// TODO(adonovan): opt: inline expr() here, to make the call static
|
// Evaluate it in the usual way.
|
||||||
// and to avoid generation of a stub for an interface method.
|
if selKind == token.PACKAGE {
|
||||||
if fn.Pkg.info.IsType(sel.X) {
|
|
||||||
c.Func = b.expr(fn, e.Fun)
|
c.Func = b.expr(fn, e.Fun)
|
||||||
return
|
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
|
// TODO(adonovan): opt: inline expr() here, to make
|
||||||
// from the method-set of X or perhaps *X (if x is addressable
|
// the call static and to avoid generation of
|
||||||
// but not a pointer).
|
// wrappers. It's somewhat tricky as it may consume
|
||||||
if m, recv := b.findMethod(fn, sel.X, id); m != nil {
|
// the first actual parameter if the call is "invoke"
|
||||||
c.Func = m
|
// mode.
|
||||||
c.Args = append(c.Args, recv)
|
//
|
||||||
|
// 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch t := fn.Pkg.typeOf(sel.X).Underlying().(type) {
|
// selKind == token.VAR
|
||||||
case *types.Struct, *types.Pointer:
|
|
||||||
// Case 3: x.f() where x.f is a function value in a
|
switch obj := obj.(type) {
|
||||||
// struct field f; not a method call. f is a 'var'
|
case *types.Func:
|
||||||
// (of function type) in the Fields of types.Struct X.
|
wantAddr := isPointer(recvType(obj))
|
||||||
// Treat like case 0.
|
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)
|
c.Func = b.expr(fn, e.Fun)
|
||||||
|
return
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// 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 {
|
} else {
|
||||||
// Replace a suffix of args with a slice containing it.
|
// Replace a suffix of args with a slice containing it.
|
||||||
at := types.NewArray(vt, int64(len(varargs)))
|
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)
|
a := emitNew(fn, at, token.NoPos)
|
||||||
for i, arg := range varargs {
|
for i, arg := range varargs {
|
||||||
iaddr := &IndexAddr{
|
iaddr := &IndexAddr{
|
||||||
@ -2235,7 +2238,7 @@ func (b *builder) buildDecl(pkg *Package, decl ast.Decl) {
|
|||||||
}
|
}
|
||||||
nt := pkg.objectOf(id).Type().(*types.Named)
|
nt := pkg.objectOf(id).Type().(*types.Named)
|
||||||
for i, n := 0, nt.NumMethods(); i < n; i++ {
|
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)
|
emitStore(init, initguard, vTrue)
|
||||||
|
|
||||||
// Call the init() function of each package we import.
|
// Call the init() function of each package we import.
|
||||||
for _, typkg := range p.info.Imports() {
|
for _, obj := range p.info.Imports() {
|
||||||
var v Call
|
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.Call.pos = init.pos
|
||||||
v.setType(types.NewTuple())
|
v.setType(types.NewTuple())
|
||||||
init.emit(&v)
|
init.emit(&v)
|
||||||
|
@ -27,7 +27,7 @@ import (
|
|||||||
func main() {
|
func main() {
|
||||||
var t testing.T
|
var t testing.T
|
||||||
t.Parallel() // static call to external declared method
|
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
|
testing.Short() // static call to external package-level function
|
||||||
|
|
||||||
var w io.Writer = new(bytes.Buffer)
|
var w io.Writer = new(bytes.Buffer)
|
||||||
@ -110,7 +110,7 @@ func main() {
|
|||||||
|
|
||||||
expectedCallee := []string{
|
expectedCallee := []string{
|
||||||
"(*testing.T).Parallel",
|
"(*testing.T).Parallel",
|
||||||
"(*testing.T).Fail",
|
"(*testing.common).Fail",
|
||||||
"testing.Short",
|
"testing.Short",
|
||||||
"N/A",
|
"N/A",
|
||||||
}
|
}
|
||||||
@ -123,7 +123,7 @@ func main() {
|
|||||||
if want := expectedCallee[callNum]; want != "N/A" {
|
if want := expectedCallee[callNum]; want != "N/A" {
|
||||||
got := call.StaticCallee().String()
|
got := call.StaticCallee().String()
|
||||||
if want != got {
|
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)
|
callNum, got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,8 +41,7 @@ func NewProgram(fset *token.FileSet, mode BuilderMode) *Program {
|
|||||||
packages: make(map[*types.Package]*Package),
|
packages: make(map[*types.Package]*Package),
|
||||||
builtins: make(map[types.Object]*Builtin),
|
builtins: make(map[types.Object]*Builtin),
|
||||||
concreteMethods: make(map[*types.Func]*Function),
|
concreteMethods: make(map[*types.Func]*Function),
|
||||||
indirectionWrappers: make(map[*Function]*Function),
|
boundMethodWrappers: make(map[*types.Func]*Function),
|
||||||
boundMethodWrappers: make(map[*Function]*Function),
|
|
||||||
ifaceMethodWrappers: make(map[*types.Func]*Function),
|
ifaceMethodWrappers: make(map[*types.Func]*Function),
|
||||||
mode: mode,
|
mode: mode,
|
||||||
}
|
}
|
||||||
@ -118,23 +117,13 @@ func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) {
|
|||||||
pkg.values[obj] = fn
|
pkg.values[obj] = fn
|
||||||
pkg.Members[name] = fn
|
pkg.Members[name] = fn
|
||||||
} else {
|
} else {
|
||||||
// TODO(adonovan): interface methods now have
|
// Concrete method.
|
||||||
// objects, but we probably don't want to call
|
_ = deref(recv.Type()).(*types.Named) // assertion
|
||||||
// memberFromObject for them.
|
pkg.Prog.concreteMethods[obj] = fn
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default: // (incl. *types.Package)
|
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()
|
scope := p.Object.Scope()
|
||||||
for _, name := range scope.Names() {
|
for _, name := range scope.Names() {
|
||||||
obj := scope.Lookup(name)
|
obj := scope.Lookup(name)
|
||||||
|
memberFromObject(p, obj, nil)
|
||||||
if obj, ok := obj.(*types.TypeName); ok {
|
if obj, ok := obj.(*types.TypeName); ok {
|
||||||
// TODO(adonovan): are the set of Func
|
named := obj.Type().(*types.Named)
|
||||||
// objects passed to memberFromObject
|
for i, n := 0, named.NumMethods(); i < n; i++ {
|
||||||
// duplicate-free? I doubt it. Check.
|
memberFromObject(p, named.Method(i), nil)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
if ti, ok := t.Underlying().(*types.Interface); ok {
|
||||||
// Even when ti==txi, we still need ChangeInterface
|
// Even when ti==txi, we still need ChangeInterface
|
||||||
// since it performs a nil-check.
|
// 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 := &ChangeInterface{X: x}
|
||||||
c.setPos(pos)
|
c.setPos(pos)
|
||||||
c.setType(t)
|
c.setType(t)
|
||||||
@ -347,6 +348,10 @@ func emitTailCall(f *Function, call *Call) {
|
|||||||
// implicit field selections specified by indices to base value v, and
|
// implicit field selections specified by indices to base value v, and
|
||||||
// returns the selected value.
|
// 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 {
|
func emitImplicitSelections(f *Function, v Value, indices []int) Value {
|
||||||
for _, index := range indices {
|
for _, index := range indices {
|
||||||
fld := deref(v.Type()).Underlying().(*types.Struct).Field(index)
|
fld := deref(v.Type()).Underlying().(*types.Struct).Field(index)
|
||||||
@ -373,3 +378,35 @@ func emitImplicitSelections(f *Function, v Value, indices []int) Value {
|
|||||||
}
|
}
|
||||||
return v
|
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.
|
// If from==f.Pkg, suppress package qualification.
|
||||||
func (f *Function) fullName(from *Package) string {
|
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?
|
// Anonymous?
|
||||||
if f.Enclosing != nil {
|
if f.Enclosing != nil {
|
||||||
|
@ -133,16 +133,16 @@ func (fr *frame) rundefers() {
|
|||||||
fr.defers = fr.defers[:0]
|
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.
|
// 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 {
|
switch typ {
|
||||||
case rtypeType:
|
case rtypeType:
|
||||||
return i.rtypeMethods
|
return i.rtypeMethods[meth.Id()]
|
||||||
case errorType:
|
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
|
// 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 {
|
if recv.t == nil {
|
||||||
panic("method invoked on nil interface")
|
panic("method invoked on nil interface")
|
||||||
}
|
}
|
||||||
id := call.MethodId()
|
fn = lookupMethod(fr.i, recv.t, call.Method)
|
||||||
fn = findMethodSet(fr.i, recv.t)[id]
|
|
||||||
if fn == nil {
|
if fn == nil {
|
||||||
// Unreachable in well-typed programs.
|
// 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))
|
args = append(args, copyVal(recv.v))
|
||||||
}
|
}
|
||||||
|
15
ssa/interp/testdata/boundmeth.go
vendored
15
ssa/interp/testdata/boundmeth.go
vendored
@ -89,6 +89,17 @@ func typeCheck() {
|
|||||||
_ = i.(func()) // type assertion: receiver type disappears
|
_ = 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() {
|
func main() {
|
||||||
valueReceiver()
|
valueReceiver()
|
||||||
pointerReceiver()
|
pointerReceiver()
|
||||||
@ -96,4 +107,8 @@ func main() {
|
|||||||
promotedReceiver()
|
promotedReceiver()
|
||||||
anonStruct()
|
anonStruct()
|
||||||
typeCheck()
|
typeCheck()
|
||||||
|
|
||||||
|
if e := regress1(errString("hi"))(); e != "hi" {
|
||||||
|
panic(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
23
ssa/interp/testdata/ifaceprom.go
vendored
23
ssa/interp/testdata/ifaceprom.go
vendored
@ -26,10 +26,33 @@ func (impl) two() string {
|
|||||||
func main() {
|
func main() {
|
||||||
var s S
|
var s S
|
||||||
s.I = impl{}
|
s.I = impl{}
|
||||||
|
if one := s.I.one(); one != 1 {
|
||||||
|
panic(one)
|
||||||
|
}
|
||||||
if one := s.one(); one != 1 {
|
if one := s.one(); one != 1 {
|
||||||
panic(one)
|
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" {
|
if two := s.two(); two != "two" {
|
||||||
panic(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() {
|
if !v.IsInvoke() {
|
||||||
b.WriteString(relName(v.Func, instr))
|
b.WriteString(relName(v.Func, instr))
|
||||||
} else {
|
} else {
|
||||||
name := v.Recv.Type().Underlying().(*types.Interface).Method(v.Method).Name()
|
fmt.Fprintf(&b, "invoke %s.%s", relName(v.Recv, instr), v.Method.Name())
|
||||||
fmt.Fprintf(&b, "invoke %s.%s [#%d]", relName(v.Recv, instr), name, v.Method)
|
|
||||||
}
|
}
|
||||||
b.WriteString("(")
|
b.WriteString("(")
|
||||||
for i, arg := range v.Args {
|
for i, arg := range v.Args {
|
||||||
|
284
ssa/promote.go
284
ssa/promote.go
@ -4,10 +4,9 @@ package ssa
|
|||||||
// synthesis of wrapper methods.
|
// synthesis of wrapper methods.
|
||||||
//
|
//
|
||||||
// Wrappers include:
|
// 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.
|
// - interface method wrappers for closures of I.f.
|
||||||
// - bound method wrappers, for uncalled obj.Method closures.
|
// - 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.
|
// TODO(adonovan): rename to wrappers.go.
|
||||||
|
|
||||||
@ -18,35 +17,54 @@ import (
|
|||||||
"code.google.com/p/go.tools/go/types"
|
"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
|
// MethodSet returns the method set for type typ, building wrapper
|
||||||
// methods as needed for embedded field promotion, and indirection for
|
// methods as needed for embedded field promotion, and indirection for
|
||||||
// *T receiver types, etc.
|
// *T receiver types, etc.
|
||||||
// A nil result indicates an empty set.
|
// 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.
|
// Thread-safe.
|
||||||
//
|
//
|
||||||
func (prog *Program) MethodSet(typ types.Type) MethodSet {
|
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
|
// populateMethodSet returns the method set for typ, ensuring that it
|
||||||
// contains at least key id. If id is empty, the entire method set is
|
// contains at least the value for obj, if that is a key.
|
||||||
// populated.
|
// If id is empty, the entire method set is populated.
|
||||||
//
|
//
|
||||||
func (prog *Program) populateMethodSet(typ types.Type, id string) MethodSet {
|
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
|
||||||
tmset := typ.MethodSet()
|
//
|
||||||
|
func (prog *Program) populateMethodSet(typ types.Type, meth *types.Method) MethodSet {
|
||||||
|
tmset := methodSet(typ)
|
||||||
n := tmset.Len()
|
n := tmset.Len()
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return nil
|
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 {
|
if prog.mode&LogSource != 0 {
|
||||||
defer logStack("MethodSet %s id=%s", typ, id)()
|
defer logStack("populateMethodSet %s meth=%v", typ, meth)()
|
||||||
}
|
}
|
||||||
|
|
||||||
prog.methodsMu.Lock()
|
prog.methodsMu.Lock()
|
||||||
@ -59,24 +77,18 @@ func (prog *Program) populateMethodSet(typ types.Type, id string) MethodSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(mset) < n {
|
if len(mset) < n {
|
||||||
if id != "" { // single method
|
if meth != nil { // single method
|
||||||
// tmset.Lookup() is no use to us with only an Id string.
|
id := meth.Id()
|
||||||
if mset[id] == nil {
|
if mset[id] == nil {
|
||||||
for i := 0; i < n; i++ {
|
mset[id] = findMethod(prog, meth)
|
||||||
obj := tmset.At(i)
|
|
||||||
if obj.Id() == id {
|
|
||||||
mset[id] = makeMethod(prog, typ, obj)
|
|
||||||
return mset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
// complete set
|
||||||
// complete set
|
for i := 0; i < n; i++ {
|
||||||
for i := 0; i < n; i++ {
|
meth := tmset.At(i)
|
||||||
obj := tmset.At(i)
|
if id := meth.Id(); mset[id] == nil {
|
||||||
if id := obj.Id(); mset[id] == nil {
|
mset[id] = findMethod(prog, meth)
|
||||||
mset[id] = makeMethod(prog, typ, obj)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -84,49 +96,70 @@ func (prog *Program) populateMethodSet(typ types.Type, id string) MethodSet {
|
|||||||
return mset
|
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
|
// LookupMethod returns the method id of type typ, building wrapper
|
||||||
// methods on demand. It returns nil if the typ has no such method.
|
// methods on demand. It returns nil if the typ has no such method.
|
||||||
//
|
//
|
||||||
// Thread-safe.
|
// Thread-safe.
|
||||||
//
|
//
|
||||||
func (prog *Program) LookupMethod(typ types.Type, id string) *Function {
|
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
|
||||||
return prog.populateMethodSet(typ, id)[id]
|
//
|
||||||
|
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,
|
// concreteMethod returns the concrete method denoted by obj.
|
||||||
// adapted if necessary so that its receiver type is typ.
|
// 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)
|
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
|
||||||
//
|
//
|
||||||
func makeMethod(prog *Program, typ types.Type, obj *types.Method) *Function {
|
func findMethod(prog *Program, meth *types.Method) *Function {
|
||||||
// Promoted method accessed via implicit field selections?
|
needsPromotion := len(meth.Index()) > 1
|
||||||
if len(obj.Index()) > 1 {
|
needsIndirection := !isPointer(recvType(meth.Func)) && isPointer(meth.Recv())
|
||||||
return promotionWrapper(prog, typ, obj)
|
|
||||||
|
if needsPromotion || needsIndirection {
|
||||||
|
return makeWrapper(prog, meth.Recv(), meth)
|
||||||
}
|
}
|
||||||
|
|
||||||
method := prog.concreteMethods[obj.Func]
|
if _, ok := meth.Recv().Underlying().(*types.Interface); ok {
|
||||||
if method == nil {
|
return interfaceMethodWrapper(prog, meth.Recv(), meth.Func)
|
||||||
panic("no concrete method for " + obj.Func.String())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call to method on T from receiver of type *T?
|
// Invariant: fn.Signature.Recv().Type() == recvType(meth.Func)
|
||||||
if !isPointer(method.Signature.Recv().Type()) && isPointer(typ) {
|
return prog.concreteMethod(meth.Func)
|
||||||
method = indirectionWrapper(method)
|
|
||||||
}
|
|
||||||
|
|
||||||
return method
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// promotionWrapper returns a synthetic wrapper Function that performs
|
// makeWrapper returns a synthetic wrapper Function that optionally
|
||||||
// a sequence of implicit field selections then tailcalls a "promoted"
|
// performs receiver indirection, implicit field selections and then a
|
||||||
// method. For example, given these decls:
|
// tailcall of a "promoted" method. For example, given these decls:
|
||||||
//
|
//
|
||||||
// type A struct {B}
|
// type A struct {B}
|
||||||
// type B struct {*C}
|
// type B struct {*C}
|
||||||
// type C ...
|
// type C ...
|
||||||
// func (*C) f()
|
// 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:
|
// synthesize this wrapper method:
|
||||||
//
|
//
|
||||||
// func (a A) f() { return a.B.C->f() }
|
// 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)
|
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
|
||||||
//
|
//
|
||||||
func promotionWrapper(prog *Program, typ types.Type, obj *types.Method) *Function {
|
func makeWrapper(prog *Program, typ types.Type, meth *types.Method) *Function {
|
||||||
old := obj.Func.Type().(*types.Signature)
|
old := meth.Func.Type().(*types.Signature)
|
||||||
sig := types.NewSignature(nil, types.NewVar(token.NoPos, nil, "recv", typ), old.Params(), old.Results(), old.IsVariadic())
|
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("wrapper for (%s).%s", old.Recv(), meth.Func.Name())
|
||||||
description := fmt.Sprintf("promotion wrapper for (%s).%s", old.Recv(), obj.Func.Name())
|
|
||||||
|
|
||||||
if prog.mode&LogSource != 0 {
|
if prog.mode&LogSource != 0 {
|
||||||
defer logStack("make %s to (%s)", description, typ)()
|
defer logStack("make %s to (%s)", description, typ)()
|
||||||
}
|
}
|
||||||
fn := &Function{
|
fn := &Function{
|
||||||
name: obj.Name(),
|
name: meth.Name(),
|
||||||
object: obj,
|
method: meth,
|
||||||
Signature: sig,
|
Signature: sig,
|
||||||
Synthetic: description,
|
Synthetic: description,
|
||||||
Prog: prog,
|
Prog: prog,
|
||||||
pos: obj.Pos(),
|
pos: meth.Pos(),
|
||||||
}
|
}
|
||||||
fn.startBody()
|
fn.startBody()
|
||||||
fn.addSpilledParam(sig.Recv())
|
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
|
var v Value = fn.Locals[0] // spilled receiver
|
||||||
if isPointer(typ) {
|
if isPointer(typ) {
|
||||||
|
// TODO(adonovan): consider emitting a nil-pointer check here
|
||||||
|
// with a nice error message, like gc does.
|
||||||
v = emitLoad(fn, v)
|
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
|
// Load) in preference to value extraction (Field possibly
|
||||||
// preceded by Load).
|
// preceded by Load).
|
||||||
|
|
||||||
indices := obj.Index()
|
indices := meth.Index()
|
||||||
v = emitImplicitSelections(fn, v, indices[:len(indices)-1])
|
v = emitImplicitSelections(fn, v, indices[:len(indices)-1])
|
||||||
|
|
||||||
// Invariant: v is a pointer, either
|
// 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()) {
|
if !isPointer(old.Recv().Type()) {
|
||||||
v = emitLoad(fn, v)
|
v = emitLoad(fn, v)
|
||||||
}
|
}
|
||||||
m := prog.concreteMethods[obj.Func]
|
c.Call.Func = prog.concreteMethod(meth.Func)
|
||||||
if m == nil {
|
|
||||||
panic("oops: " + fn.Synthetic)
|
|
||||||
}
|
|
||||||
c.Call.Func = m
|
|
||||||
c.Call.Args = append(c.Call.Args, v)
|
c.Call.Args = append(c.Call.Args, v)
|
||||||
} else {
|
} else {
|
||||||
c.Call.Method = indices[len(indices)-1]
|
c.Call.Method = meth.Func
|
||||||
c.Call.Recv = emitLoad(fn, v)
|
c.Call.Recv = emitLoad(fn, v)
|
||||||
}
|
}
|
||||||
for _, arg := range fn.Params[1:] {
|
for _, arg := range fn.Params[1:] {
|
||||||
@ -219,9 +248,9 @@ func createParams(fn *Function) {
|
|||||||
|
|
||||||
// Wrappers for standalone interface methods ----------------------------------
|
// Wrappers for standalone interface methods ----------------------------------
|
||||||
|
|
||||||
// interfaceMethodWrapper returns a synthetic wrapper function permitting a
|
// interfaceMethodWrapper returns a synthetic wrapper function
|
||||||
// method id of interface typ to be called like a standalone function,
|
// permitting an abstract method obj to be called like a standalone
|
||||||
// e.g.:
|
// function, e.g.:
|
||||||
//
|
//
|
||||||
// type I interface { f(x int) R }
|
// type I interface { f(x int) R }
|
||||||
// m := I.f // wrapper
|
// m := I.f // wrapper
|
||||||
@ -234,38 +263,41 @@ func createParams(fn *Function) {
|
|||||||
// return i.f(x, ...)
|
// 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
|
// TODO(adonovan): opt: currently the stub is created even when used
|
||||||
// in call position: I.f(i, 0). Clearly this is suboptimal.
|
// in call position: I.f(i, 0). Clearly this is suboptimal.
|
||||||
//
|
//
|
||||||
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
|
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
|
||||||
//
|
//
|
||||||
func interfaceMethodWrapper(prog *Program, typ types.Type, id string) *Function {
|
func interfaceMethodWrapper(prog *Program, typ types.Type, obj *types.Func) *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
|
// If one interface embeds another they'll share the same
|
||||||
// wrappers for common methods. This is safe, but it might
|
// wrappers for common methods. This is safe, but it might
|
||||||
// confuse some tools because of the implicit interface
|
// confuse some tools because of the implicit interface
|
||||||
// conversion applied to the first argument. If this becomes
|
// conversion applied to the first argument. If this becomes
|
||||||
// a problem, we should include 'typ' in the memoization key.
|
// a problem, we should include 'typ' in the memoization key.
|
||||||
fn, ok := prog.ifaceMethodWrappers[meth]
|
fn, ok := prog.ifaceMethodWrappers[obj]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
description := fmt.Sprintf("interface method wrapper for %s.%s", typ, obj)
|
||||||
if prog.mode&LogSource != 0 {
|
if prog.mode&LogSource != 0 {
|
||||||
defer logStack("interfaceMethodWrapper %s.%s", typ, id)()
|
defer logStack("%s", description)()
|
||||||
}
|
}
|
||||||
fn = &Function{
|
fn = &Function{
|
||||||
name: meth.Name(),
|
name: obj.Name(),
|
||||||
object: meth,
|
object: obj,
|
||||||
Signature: meth.Type().(*types.Signature),
|
Signature: obj.Type().(*types.Signature),
|
||||||
Synthetic: fmt.Sprintf("interface method wrapper for %s.%s", typ, id),
|
Synthetic: description,
|
||||||
pos: meth.Pos(),
|
pos: obj.Pos(),
|
||||||
Prog: prog,
|
Prog: prog,
|
||||||
}
|
}
|
||||||
fn.startBody()
|
fn.startBody()
|
||||||
fn.addParam("recv", typ, token.NoPos)
|
fn.addParam("recv", typ, token.NoPos)
|
||||||
createParams(fn)
|
createParams(fn)
|
||||||
var c Call
|
var c Call
|
||||||
c.Call.Method = index
|
|
||||||
|
c.Call.Method = obj
|
||||||
c.Call.Recv = fn.Params[0]
|
c.Call.Recv = fn.Params[0]
|
||||||
for _, arg := range fn.Params[1:] {
|
for _, arg := range fn.Params[1:] {
|
||||||
c.Call.Args = append(c.Call.Args, arg)
|
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)
|
emitTailCall(fn, &c)
|
||||||
fn.finishBody()
|
fn.finishBody()
|
||||||
|
|
||||||
prog.ifaceMethodWrappers[meth] = fn
|
prog.ifaceMethodWrappers[obj] = fn
|
||||||
}
|
}
|
||||||
return fn
|
return fn
|
||||||
}
|
}
|
||||||
@ -281,12 +313,12 @@ func interfaceMethodWrapper(prog *Program, typ types.Type, id string) *Function
|
|||||||
// Wrappers for bound methods -------------------------------------------------
|
// Wrappers for bound methods -------------------------------------------------
|
||||||
|
|
||||||
// boundMethodWrapper returns a synthetic wrapper function that
|
// boundMethodWrapper returns a synthetic wrapper function that
|
||||||
// delegates to a concrete method. The wrapper has one free variable,
|
// delegates to a concrete or interface method.
|
||||||
// the method's receiver. Use MakeClosure with such a wrapper to
|
// The wrapper has one free variable, the method's receiver.
|
||||||
// construct a bound-method closure.
|
// Use MakeClosure with such a wrapper to construct a bound-method
|
||||||
// e.g.:
|
// closure. e.g.:
|
||||||
//
|
//
|
||||||
// type T int
|
// type T int or: type T interface { meth() }
|
||||||
// func (t T) meth()
|
// func (t T) meth()
|
||||||
// var t T
|
// var t T
|
||||||
// f := t.meth
|
// f := t.meth
|
||||||
@ -298,22 +330,22 @@ func interfaceMethodWrapper(prog *Program, typ types.Type, id string) *Function
|
|||||||
//
|
//
|
||||||
// EXCLUSIVE_LOCKS_ACQUIRED(meth.Prog.methodsMu)
|
// EXCLUSIVE_LOCKS_ACQUIRED(meth.Prog.methodsMu)
|
||||||
//
|
//
|
||||||
func boundMethodWrapper(meth *Function) *Function {
|
func boundMethodWrapper(prog *Program, obj *types.Func) *Function {
|
||||||
prog := meth.Prog
|
|
||||||
prog.methodsMu.Lock()
|
prog.methodsMu.Lock()
|
||||||
defer prog.methodsMu.Unlock()
|
defer prog.methodsMu.Unlock()
|
||||||
fn, ok := prog.boundMethodWrappers[meth]
|
fn, ok := prog.boundMethodWrappers[obj]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
description := fmt.Sprintf("bound method wrapper for %s", obj)
|
||||||
if prog.mode&LogSource != 0 {
|
if prog.mode&LogSource != 0 {
|
||||||
defer logStack("boundMethodWrapper %s", meth)()
|
defer logStack("%s", description)()
|
||||||
}
|
}
|
||||||
s := meth.Signature
|
s := obj.Type().(*types.Signature)
|
||||||
fn = &Function{
|
fn = &Function{
|
||||||
name: "bound$" + meth.String(),
|
name: "bound$" + obj.String(),
|
||||||
Signature: types.NewSignature(nil, nil, s.Params(), s.Results(), s.IsVariadic()), // drop recv
|
Signature: types.NewSignature(nil, nil, s.Params(), s.Results(), s.IsVariadic()), // drop recv
|
||||||
Synthetic: "bound method wrapper for " + meth.String(),
|
Synthetic: description,
|
||||||
Prog: prog,
|
Prog: prog,
|
||||||
pos: meth.Pos(),
|
pos: obj.Pos(),
|
||||||
}
|
}
|
||||||
|
|
||||||
cap := &Capture{name: "recv", typ: s.Recv().Type(), parent: fn}
|
cap := &Capture{name: "recv", typ: s.Recv().Type(), parent: fn}
|
||||||
@ -321,65 +353,21 @@ func boundMethodWrapper(meth *Function) *Function {
|
|||||||
fn.startBody()
|
fn.startBody()
|
||||||
createParams(fn)
|
createParams(fn)
|
||||||
var c Call
|
var c Call
|
||||||
c.Call.Func = meth
|
|
||||||
c.Call.Args = []Value{cap}
|
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 {
|
for _, arg := range fn.Params {
|
||||||
c.Call.Args = append(c.Call.Args, arg)
|
c.Call.Args = append(c.Call.Args, arg)
|
||||||
}
|
}
|
||||||
emitTailCall(fn, &c)
|
emitTailCall(fn, &c)
|
||||||
fn.finishBody()
|
fn.finishBody()
|
||||||
|
|
||||||
prog.boundMethodWrappers[meth] = fn
|
prog.boundMethodWrappers[obj] = 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
|
|
||||||
}
|
}
|
||||||
return 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
|
// because its package was not built, the DebugInfo flag was not set
|
||||||
// during SSA construction, or the value was optimized away.
|
// 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
|
// ref must be the path to an ast.Ident (e.g. from
|
||||||
// PathEnclosingInterval), and that ident must resolve to obj.
|
// PathEnclosingInterval), and that ident must resolve to obj.
|
||||||
//
|
//
|
||||||
|
46
ssa/ssa.go
46
ssa/ssa.go
@ -22,13 +22,12 @@ type Program struct {
|
|||||||
PackagesByPath map[string]*Package // all loaded Packages, keyed by import path
|
PackagesByPath map[string]*Package // all loaded Packages, keyed by import path
|
||||||
packages map[*types.Package]*Package // all loaded Packages, keyed by object
|
packages map[*types.Package]*Package // all loaded Packages, keyed by object
|
||||||
builtins map[types.Object]*Builtin // all built-in functions, keyed by typechecker objects.
|
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
|
mode BuilderMode // set of mode bits for SSA construction
|
||||||
|
|
||||||
methodsMu sync.Mutex // guards the following maps:
|
methodsMu sync.Mutex // guards the following maps:
|
||||||
methodSets typemap.M // maps type to its concrete MethodSet
|
methodSets typemap.M // maps type to its concrete MethodSet
|
||||||
indirectionWrappers map[*Function]*Function // func(*T) wrappers for T-methods
|
boundMethodWrappers map[*types.Func]*Function // wrappers for curried x.Method closures
|
||||||
boundMethodWrappers map[*Function]*Function // wrappers for curried x.Method closures
|
|
||||||
ifaceMethodWrappers map[*types.Func]*Function // wrappers for curried I.Method functions
|
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()
|
// The keys of a method set are strings returned by the types.Id()
|
||||||
// function.
|
// 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
|
type MethodSet map[string]*Function
|
||||||
|
|
||||||
// A Type is a Member of a Package representing a package-level named type.
|
// A Type is a Member of a Package representing a package-level named type.
|
||||||
@ -250,7 +253,8 @@ type Instruction interface {
|
|||||||
//
|
//
|
||||||
type Function struct {
|
type Function struct {
|
||||||
name string
|
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
|
Signature *types.Signature
|
||||||
pos token.Pos
|
pos token.Pos
|
||||||
|
|
||||||
@ -1177,7 +1181,7 @@ type anInstruction struct {
|
|||||||
// Each CallCommon exists in one of two modes, function call and
|
// Each CallCommon exists in one of two modes, function call and
|
||||||
// interface method invocation, or "call" and "invoke" for short.
|
// 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.
|
// represents an ordinary function call of the value in Func.
|
||||||
//
|
//
|
||||||
// In the common case in which Func is a *Function, this indicates a
|
// In the common case in which Func is a *Function, this indicates a
|
||||||
@ -1197,10 +1201,10 @@ type anInstruction struct {
|
|||||||
// go t3()
|
// go t3()
|
||||||
// defer t5(...t6)
|
// 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.
|
// represents a dynamically dispatched call to an interface method.
|
||||||
// In this mode, Recv is the interface value and Method is the index
|
// In this mode, Recv is the interface value and Method is the
|
||||||
// of the method within the interface type of the receiver.
|
// interface's abstract method.
|
||||||
//
|
//
|
||||||
// Recv is implicitly supplied to the concrete method implementation
|
// Recv is implicitly supplied to the concrete method implementation
|
||||||
// as the receiver parameter; in other words, Args[0] holds not the
|
// as the receiver parameter; in other words, Args[0] holds not the
|
||||||
@ -1219,17 +1223,18 @@ type anInstruction struct {
|
|||||||
// readability of the printed form.)
|
// readability of the printed form.)
|
||||||
//
|
//
|
||||||
type CallCommon struct {
|
type CallCommon struct {
|
||||||
Recv Value // receiver, iff interface method invocation
|
// TODO(adonovan): combine Recv/Func fields since Method now discriminates.
|
||||||
Method int // index of interface method; call MethodId() for its Id
|
Recv Value // receiver (in "invoke" mode)
|
||||||
Func Value // target of call, iff function call
|
Method *types.Func // abstract method (in "invoke" mode)
|
||||||
Args []Value // actual parameters, including receiver in invoke mode
|
Func Value // target of call (in "call" mode)
|
||||||
HasEllipsis bool // true iff last Args is a slice of '...' args (needed?)
|
Args []Value // actual parameters, including receiver in invoke mode
|
||||||
pos token.Pos // position of CallExpr.Lparen, iff explicit in source
|
HasEllipsis bool // true iff last Args is a slice of '...' args (needed?)
|
||||||
|
pos token.Pos // position of CallExpr.Lparen, iff explicit in source
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsInvoke returns true if this call has "invoke" (not "call") mode.
|
// IsInvoke returns true if this call has "invoke" (not "call") mode.
|
||||||
func (c *CallCommon) IsInvoke() bool {
|
func (c *CallCommon) IsInvoke() bool {
|
||||||
return c.Recv != nil
|
return c.Method != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CallCommon) Pos() token.Pos { return c.pos }
|
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.
|
// Signature returns nil for a call to a built-in function.
|
||||||
//
|
//
|
||||||
func (c *CallCommon) Signature() *types.Signature {
|
func (c *CallCommon) Signature() *types.Signature {
|
||||||
if c.Recv != nil {
|
if c.Method != nil {
|
||||||
iface := c.Recv.Type().Underlying().(*types.Interface)
|
return c.Method.Type().(*types.Signature)
|
||||||
return iface.Method(c.Method).Type().(*types.Signature)
|
|
||||||
}
|
}
|
||||||
sig, _ := c.Func.Type().Underlying().(*types.Signature) // nil for *Builtin
|
sig, _ := c.Func.Type().Underlying().(*types.Signature) // nil for *Builtin
|
||||||
return sig
|
return sig
|
||||||
@ -1265,12 +1269,6 @@ func (c *CallCommon) StaticCallee() *Function {
|
|||||||
return nil
|
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
|
// Description returns a description of the mode of this call suitable
|
||||||
// for a user interface, e.g. "static method call".
|
// for a user interface, e.g. "static method call".
|
||||||
func (c *CallCommon) Description() string {
|
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
|
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;
|
// DefaultType returns the default "typed" type for an "untyped" type;
|
||||||
// it returns the incoming type for all other types. The default type
|
// it returns the incoming type for all other types. The default type
|
||||||
// for untyped nil is untyped nil.
|
// for untyped nil is untyped nil.
|
||||||
|
Loading…
Reference in New Issue
Block a user