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