1
0
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:
Alan Donovan 2013-07-26 11:22:34 -04:00
parent c98ff05fdd
commit 4da31df1c8
17 changed files with 481 additions and 417 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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