go.tools/ssa: create thunks for method expressions T.f.

Until now, the same Function was used to represent a method
(T)func() and the "method expression" function func(T) formed
from it. So the SSA code for this:

    var buf bytes.Buffer
    f := Buffer.Bytes
    f(buf)
    buf.Bytes()

would involve an implicit cast (ChangeType) on line 2.
However, compilers based on go/ssa may want to use different
calling conventions for them, like gccgo does (see issue
7839).  This change decouples them by using an anonymous
function called a "thunk", rather like this:

    f := func(r *bytes.Buffer) []byte { return r.Bytes() }

Thunks are similar to method wrappers; both are created by
makeWrapper.

"Interface method wrappers" were a special case of thunks for
direct calls (no indirection/fields) of interface methods.
They are now subsumed by thunks and have been deleted.  Now
that only the needed thunks are built, we don't need to
populate the concrete method sets of interface types at all,
so (*Program).Method and LookupMethod return nil for them.
This results in a slight reduction in function count (>1%) and
instruction count (<<1%).

Details:

go/ssa:
- API: ChangeType no longer supports func/method conversions.
- API: (*Program).FuncValue now returns nil for abstract
  (interface) methods.
- API: (*Function).RelString simplified.
  "$bound" is now a suffix not a prefix, and the receiver
  type is rendered package-relative.
- API: Function.Object is now defined for all wrappers too.
- API: (*Program).Method and LookupMethod return nil for
  abstract methods.
- emitConv no longer permits (non-identical)
  Signature->Signature conversions.  Added assertion.
- add and use isInterface helper
- sanity: we check packages after Build, not Create, otherwise
  cross-package refs might fail.

go/pointer:
- update tests for new function strings.
- pointer_test: don't add non-pointerlike probes to analysis.
  (The error was checked, but too late, causing a panic.)
- fixed a minor bug: if a test probe print(x) was the sole
  reference to x, no nodes were generated for x.
- (reflect.Type).MethodByName: updated due to ssa API changes.
  Also, fixed incorrect testdata/funcreflect.go expectation
  for MethodByName on interfaces.

oracle:
- fix for new FuncValue semantics.
- a "pointsto" query on an I.f thunk now returns an error.

Fixes golang/go#7839

LGTM=gri
R=gri
CC=golang-codereviews, pcc
https://golang.org/cl/93780044
This commit is contained in:
Alan Donovan 2014-06-11 13:10:26 -04:00
parent 9fc9dd9a01
commit fec252214b
19 changed files with 316 additions and 258 deletions

View File

@ -548,6 +548,11 @@ func (a *analysis) genBuiltinCall(instr ssa.CallInstruction, cgn *cgnode) {
a.copy(a.valueNode(v), a.panicNode, 1) a.copy(a.valueNode(v), a.panicNode, 1)
} }
case "print":
// In the tests, the probe might be the sole reference
// to its arg, so make sure we create nodes for it.
a.valueNode(call.Args[0])
default: default:
// No-ops: close len cap real imag complex print println delete. // No-ops: close len cap real imag complex print println delete.
} }

View File

@ -296,7 +296,10 @@ func doOneInput(input, filename string) bool {
Log: &log, Log: &log,
} }
for probe := range probes { for probe := range probes {
config.AddQuery(probe.Args[0]) v := probe.Args[0]
if pointer.CanPoint(v.Type()) {
config.AddQuery(v)
}
} }
// Print the log is there was an error or a panic. // Print the log is there was an error or a panic.

View File

@ -1786,7 +1786,7 @@ func (c *rtypeMethodByNameConstraint) solve(a *analysis, _ *node, delta nodeset)
for tObj := range delta { for tObj := range delta {
T := a.nodes[tObj].obj.data.(types.Type) T := a.nodes[tObj].obj.data.(types.Type)
_, isInterface := T.Underlying().(*types.Interface) isIface := isInterface(T)
// We don't use Lookup(c.name) when c.name != "" to avoid // We don't use Lookup(c.name) when c.name != "" to avoid
// ambiguity: >1 unexported methods could match. // ambiguity: >1 unexported methods could match.
@ -1802,24 +1802,26 @@ func (c *rtypeMethodByNameConstraint) solve(a *analysis, _ *node, delta nodeset)
// 4 Func Value // 4 Func Value
// 5 Index int // 5 Index int
// } // }
fn := a.prog.Method(sel)
sig := fn.Signature var sig *types.Signature
if isInterface { var fn *ssa.Function
// discard receiver if isIface {
sig = types.NewSignature(nil, nil, sig.Params(), sig.Results(), sig.Variadic()) sig = sel.Type().(*types.Signature)
} else { } else {
fn = a.prog.Method(sel)
// move receiver to params[0] // move receiver to params[0]
sig = changeRecv(sig) sig = changeRecv(fn.Signature)
} }
// a.offsetOf(Type) is 3. // a.offsetOf(Type) is 3.
if id := c.result + 3; a.addLabel(id, a.makeRtype(sig)) { if id := c.result + 3; a.addLabel(id, a.makeRtype(sig)) {
a.addWork(id) a.addWork(id)
} }
// a.offsetOf(Func) is 4. if fn != nil {
if id := c.result + 4; a.addLabel(id, a.objectNode(nil, fn)) { // a.offsetOf(Func) is 4.
a.addWork(id) if id := c.result + 4; a.addLabel(id, a.objectNode(nil, fn)) {
a.addWork(id)
}
} }
} }
} }

View File

@ -52,7 +52,7 @@ func runtimeSetFinalizer3() {
runtime.SetFinalizer(x, (*T).finalize) runtime.SetFinalizer(x, (*T).finalize)
} }
// @calls main.runtimeSetFinalizer3 -> (*main.T).finalize // @calls main.runtimeSetFinalizer3 -> (*main.T).finalize$thunk
// I hope I never live to see this code in the wild. // I hope I never live to see this code in the wild.
var setFinalizer = runtime.SetFinalizer var setFinalizer = runtime.SetFinalizer

View File

@ -113,8 +113,8 @@ func func5() {
} }
// @calls main.func5 -> (*main.T).f // @calls main.func5 -> (*main.T).f
// @calls main.func5 -> (*main.T).g // @calls main.func5 -> (*main.T).g$thunk
// @calls main.func5 -> (*main.T).h // @calls main.func5 -> (*main.T).h$thunk
func func6() { func func6() {
A := &a A := &a
@ -138,18 +138,18 @@ func func7() {
var i I = D{} var i I = D{}
imethodClosure := i.f imethodClosure := i.f
imethodClosure() imethodClosure()
// @calls main.func7 -> bound$(main.I).f // @calls main.func7 -> (main.I).f$bound
// @calls bound$(main.I).f -> (main.D).f // @calls (main.I).f$bound -> (main.D).f
var d D var d D
cmethodClosure := d.f cmethodClosure := d.f
cmethodClosure() cmethodClosure()
// @calls main.func7 -> bound$(main.D).f // @calls main.func7 -> (main.D).f$bound
// @calls bound$(main.D).f ->(main.D).f // @calls (main.D).f$bound ->(main.D).f
methodExpr := D.f methodExpr := D.f
methodExpr(d) methodExpr(d)
// @calls main.func7 -> (main.D).f // @calls main.func7 -> (main.D).f$thunk
} }
func main() { func main() {

View File

@ -51,8 +51,8 @@ func reflectValueCallIndirect() {
print(res0.(*T)) // @pointsto new@newT2:19 print(res0.(*T)) // @pointsto new@newT2:19
} }
// @calls main.reflectValueCallIndirect -> bound$(reflect.Value).Call // @calls main.reflectValueCallIndirect -> (reflect.Value).Call$bound
// @calls bound$(reflect.Value).Call -> main.g // @calls (reflect.Value).Call$bound -> main.g
func reflectTypeInOut() { func reflectTypeInOut() {
var f func(float64, bool) (string, int) var f func(float64, bool) (string, int)
@ -111,7 +111,7 @@ func reflectTypeMethodByName() {
print(reflect.Zero(rThasF)) // @types hasF print(reflect.Zero(rThasF)) // @types hasF
F2, _ := rThasF.MethodByName("F") F2, _ := rThasF.MethodByName("F")
print(reflect.Zero(F2.Type)) // @types func() print(reflect.Zero(F2.Type)) // @types func()
print(F2.Func) // @pointsto (main.hasF).F print(F2.Func) // @pointsto
} }

View File

@ -135,12 +135,12 @@ func interface5() {
func interface6() { func interface6() {
f := I.f f := I.f
print(f) // @pointsto (main.I).f print(f) // @pointsto (main.I).f$thunk
f(new(struct{ D })) f(new(struct{ D }))
} }
// @calls main.interface6 -> (main.I).f // @calls main.interface6 -> (main.I).f$thunk
// @calls (main.I).f -> (*struct{main.D}).f // @calls (main.I).f$thunk -> (*struct{main.D}).f
func main() { func main() {
interface1() interface1()

View File

@ -431,7 +431,7 @@ func (b *builder) exprInPlace(fn *Function, loc lvalue, e ast.Expr) {
} }
if _, ok := loc.(*address); ok { if _, ok := loc.(*address); ok {
if _, ok := loc.typ().Underlying().(*types.Interface); ok { if isInterface(loc.typ()) {
// e.g. var x interface{} = T{...} // e.g. var x interface{} = T{...}
// Can't in-place initialize an interface value. // Can't in-place initialize an interface value.
// Fall back to copying. // Fall back to copying.
@ -624,25 +624,25 @@ func (b *builder) expr0(fn *Function, e ast.Expr) Value {
case types.MethodExpr: case types.MethodExpr:
// (*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.
// For declared methods, a simple conversion will suffice. // The result is a "thunk".
return emitConv(fn, fn.Prog.Method(sel), fn.Pkg.typeOf(e)) return emitConv(fn, makeThunk(fn.Prog, sel), fn.Pkg.typeOf(e))
case types.MethodVal: case types.MethodVal:
// e.f where e is an expression and f is a method. // e.f where e is an expression and f is a method.
// The result is a bound method closure. // The result is a "bound".
obj := sel.Obj().(*types.Func) obj := sel.Obj().(*types.Func)
rt := recvType(obj) rt := recvType(obj)
wantAddr := isPointer(rt) wantAddr := isPointer(rt)
escaping := true escaping := true
v := b.receiver(fn, e.X, wantAddr, escaping, sel) v := b.receiver(fn, e.X, wantAddr, escaping, sel)
if _, ok := rt.Underlying().(*types.Interface); ok { if isInterface(rt) {
// If v has interface type I, // If v has interface type I,
// we must emit a check that v is non-nil. // we must emit a check that v is non-nil.
// We use: typeassert v.(I). // We use: typeassert v.(I).
emitTypeAssert(fn, v, rt, token.NoPos) emitTypeAssert(fn, v, rt, token.NoPos)
} }
c := &MakeClosure{ c := &MakeClosure{
Fn: boundMethodWrapper(fn.Prog, obj), Fn: makeBound(fn.Prog, obj),
Bindings: []Value{v}, Bindings: []Value{v},
} }
c.setPos(e.Sel.Pos()) c.setPos(e.Sel.Pos())
@ -798,7 +798,7 @@ func (b *builder) setCallFunc(fn *Function, e *ast.CallExpr, c *CallCommon) {
wantAddr := isPointer(recvType(obj)) wantAddr := isPointer(recvType(obj))
escaping := true escaping := true
v := b.receiver(fn, selector.X, wantAddr, escaping, sel) v := b.receiver(fn, selector.X, wantAddr, escaping, sel)
if _, ok := deref(v.Type()).Underlying().(*types.Interface); ok { if isInterface(deref(v.Type())) {
// Invoke-mode call. // Invoke-mode call.
c.Value = v c.Value = v
c.Method = obj c.Method = obj
@ -2265,6 +2265,10 @@ func (p *Package) Build() {
init.finishBody() init.finishBody()
p.info = nil // We no longer need ASTs or go/types deductions. p.info = nil // We no longer need ASTs or go/types deductions.
if p.Prog.mode&SanityCheckFunctions != 0 {
sanityCheckPackage(p)
}
} }
// Only valid during p's create and build phases. // Only valid during p's create and build phases.

View File

@ -39,12 +39,12 @@ const (
// //
func Create(iprog *loader.Program, mode BuilderMode) *Program { func Create(iprog *loader.Program, mode BuilderMode) *Program {
prog := &Program{ prog := &Program{
Fset: iprog.Fset, Fset: iprog.Fset,
imported: make(map[string]*Package), imported: make(map[string]*Package),
packages: make(map[*types.Package]*Package), packages: make(map[*types.Package]*Package),
boundMethodWrappers: make(map[*types.Func]*Function), thunks: make(map[selectionKey]*Function),
ifaceMethodWrappers: make(map[*types.Func]*Function), bounds: make(map[*types.Func]*Function),
mode: mode, mode: mode,
} }
for _, info := range iprog.AllPackages { for _, info := range iprog.AllPackages {
@ -243,10 +243,6 @@ func (prog *Program) CreatePackage(info *loader.PackageInfo) *Package {
} }
prog.packages[p.Object] = p prog.packages[p.Object] = p
if prog.mode&SanityCheckFunctions != 0 {
sanityCheckPackage(p)
}
return p return p
} }

View File

@ -7,6 +7,7 @@ package ssa
// Helpers for emitting SSA instructions. // Helpers for emitting SSA instructions.
import ( import (
"fmt"
"go/ast" "go/ast"
"go/token" "go/token"
@ -161,11 +162,6 @@ func isValuePreserving(ut_src, ut_dst types.Type) bool {
// Conversion between pointers with identical base types? // Conversion between pointers with identical base types?
_, ok := ut_src.(*types.Pointer) _, ok := ut_src.(*types.Pointer)
return ok return ok
case *types.Signature:
// Conversion from (T) func f() method to f(T) function?
_, ok := ut_src.(*types.Signature)
return ok
} }
return false return false
} }
@ -233,10 +229,18 @@ func emitConv(f *Function, val Value, typ types.Type) Value {
// e.g. string -> []byte/[]rune. // e.g. string -> []byte/[]rune.
} }
// A representation-changing conversion. // A representation-changing conversion?
c := &Convert{X: val} // At least one of {ut_src,ut_dst} must be *Basic.
c.setType(typ) // (The other may be []byte or []rune.)
return f.emit(c) _, ok1 := ut_src.(*types.Basic)
_, ok2 := ut_dst.(*types.Basic)
if ok1 || ok2 {
c := &Convert{X: val}
c.setType(typ)
return f.emit(c)
}
panic(fmt.Sprintf("in %s: cannot convert %s (%s) to %s", f, val, val.Type(), typ))
} }
// emitStore emits to f an instruction to store value val at location // emitStore emits to f an instruction to store value val at location

View File

@ -454,52 +454,51 @@ func (f *Function) emit(instr Instruction) Value {
// Examples: // Examples:
// "math.IsNaN" // a package-level function // "math.IsNaN" // a package-level function
// "IsNaN" // intra-package reference to same // "IsNaN" // intra-package reference to same
// "(*sync.WaitGroup).Add" // a declared method // "(*bytes.Buffer).Bytes" // a declared method or a wrapper
// "(*Return).Block" // a promotion wrapper method (intra-package ref) // "(*Buffer).Bytes" // intra-package reference to same
// "(Instruction).Block" // an interface method wrapper (intra-package ref) // "(*Buffer).Bytes$thunk" // thunk (func wrapping method; receiver is param 0)
// "(*Buffer).Bytes$bound" // bound (func wrapping method; receiver supplied by closure)
// "main$1" // an anonymous function // "main$1" // an anonymous function
// "init$1" // a declared init function // "init$1" // a declared init function
// "init" // the synthesized package initializer // "init" // the synthesized package initializer
// "bound$(*T).f" // a bound method wrapper
// //
// If from==f.Pkg, suppress package qualification. // If from==f.Pkg, suppress package qualification.
//
func (f *Function) RelString(from *types.Package) string { func (f *Function) RelString(from *types.Package) string {
// TODO(adonovan): expose less fragile case discrimination
// using f.method.
// Anonymous? // Anonymous?
if f.Enclosing != nil { if f.Enclosing != nil {
return f.name return f.name
} }
// Declared method, or promotion/indirection wrapper? // Method (declared or wrapper)?
if recv := f.Signature.Recv(); recv != nil { if recv := f.Signature.Recv(); recv != nil {
return fmt.Sprintf("(%s).%s", relType(recv.Type(), from), f.name) return f.relMethod(from, recv.Type())
} }
// Other synthetic wrapper? // Thunk?
if f.Synthetic != "" { if f.method != nil {
// Bound method wrapper? return f.relMethod(from, f.method.Recv())
if strings.HasPrefix(f.name, "bound$") {
return f.name
}
// Interface method wrapper?
if strings.HasPrefix(f.Synthetic, "interface ") {
return fmt.Sprintf("(%s).%s", relType(f.Params[0].Type(), from), f.name)
}
// "package initializer" or "loaded from GC object file": fall through.
} }
// Package-level function. // Bound?
if len(f.FreeVars) == 1 && strings.HasSuffix(f.name, "$bound") {
return f.relMethod(from, f.FreeVars[0].Type())
}
// Package-level function?
// Prefix with package name for cross-package references only. // Prefix with package name for cross-package references only.
if p := f.pkgobj(); p != from { if p := f.pkgobj(); p != nil && p != from {
return fmt.Sprintf("%s.%s", p.Path(), f.name) return fmt.Sprintf("%s.%s", p.Path(), f.name)
} }
// Unknown.
return f.name return f.name
} }
func (f *Function) relMethod(from *types.Package, recv types.Type) string {
return fmt.Sprintf("(%s).%s", relType(recv, from), f.name)
}
// writeSignature writes to buf the signature sig in declaration syntax. // writeSignature writes to buf the signature sig in declaration syntax.
func writeSignature(buf *bytes.Buffer, pkg *types.Package, name string, sig *types.Signature, params []*Parameter) { func writeSignature(buf *bytes.Buffer, pkg *types.Package, name string, sig *types.Signature, params []*Parameter) {
buf.WriteString("func ") buf.WriteString("func ")

View File

@ -5,34 +5,51 @@
package ssa package ssa
// This file defines utilities for population of method sets and // This file defines utilities for population of method sets and
// synthesis of wrapper methods. // synthesis of Functions that delegate to declared methods, which
// come in three kinds:
// //
// Wrappers include: // (1) wrappers: methods that wrap declared methods, performing
// - indirection/promotion wrappers for methods of embedded fields. // implicit pointer indirections and embedded field selections.
// - interface method wrappers for expressions I.f. //
// - bound method wrappers, for uncalled obj.Method closures. // (2) thunks: funcs that wrap declared methods. Like wrappers,
// thunks perform indirections and field selections. The thunks's
// TODO(adonovan): split and rename to {methodset,wrappers}.go. // first parameter is used as the receiver for the method call.
//
// (3) bounds: funcs that wrap declared methods. The bound's sole
// free variable, supplied by a closure, is used as the receiver
// for the method call. No indirections or field selections are
// performed since they can be done before the call.
//
// TODO(adonovan): split and rename to {methodset,delegate}.go.
// TODO(adonovan): use 'sel' not 'meth' for *types.Selection; reserve 'meth' for *Function.
import ( import (
"fmt" "fmt"
"go/token"
"code.google.com/p/go.tools/go/types" "code.google.com/p/go.tools/go/types"
) )
// Method returns the Function implementing method meth, building // Method returns the Function implementing method meth, building
// wrapper methods on demand. // wrapper methods on demand. It returns nil if meth denotes an
// abstract (interface) method.
//
// Precondition: meth Kind() == MethodVal.
//
// TODO(adonovan): rename this to MethodValue because of the
// precondition, and for consistency with functions in source.go.
// //
// Thread-safe. // Thread-safe.
// //
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu) // EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
// //
func (prog *Program) Method(meth *types.Selection) *Function { func (prog *Program) Method(meth *types.Selection) *Function {
if meth == nil { if meth.Kind() != types.MethodVal {
panic("Method(nil)") panic(fmt.Sprintf("Method(%s) kind != MethodVal", meth))
} }
T := meth.Recv() T := meth.Recv()
if isInterface(T) {
return nil // abstract method
}
if prog.mode&LogSource != 0 { if prog.mode&LogSource != 0 {
defer logStack("Method %s %v", T, meth)() defer logStack("Method %s %v", T, meth)()
} }
@ -44,7 +61,8 @@ func (prog *Program) Method(meth *types.Selection) *Function {
} }
// LookupMethod returns the implementation of the method of type T // LookupMethod returns the implementation of the method of type T
// identified by (pkg, name). It panics if there is no such method. // identified by (pkg, name). It returns nil if the method exists but
// is abstract, and panics if T has no such method.
// //
func (prog *Program) LookupMethod(T types.Type, pkg *types.Package, name string) *Function { func (prog *Program) LookupMethod(T types.Type, pkg *types.Package, name string) *Function {
sel := prog.MethodSets.MethodSet(T).Lookup(pkg, name) sel := prog.MethodSets.MethodSet(T).Lookup(pkg, name)
@ -54,17 +72,20 @@ func (prog *Program) LookupMethod(T types.Type, pkg *types.Package, name string)
return prog.Method(sel) return prog.Method(sel)
} }
// makeMethods ensures that all wrappers in the complete method set of // makeMethods ensures that all concrete methods of type T are
// T are generated. It is equivalent to calling prog.Method() on all // generated. It is equivalent to calling prog.Method() on all
// members of T.methodSet(), but acquires fewer locks. // members of T.methodSet(), but acquires fewer locks.
// //
// It reports whether the type's method set is non-empty. // It reports whether the type's (concrete) method set is non-empty.
// //
// Thread-safe. // Thread-safe.
// //
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu) // EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
// //
func (prog *Program) makeMethods(T types.Type) bool { func (prog *Program) makeMethods(T types.Type) bool {
if isInterface(T) {
return false // abstract method
}
tmset := prog.MethodSets.MethodSet(T) tmset := prog.MethodSets.MethodSet(T)
n := tmset.Len() n := tmset.Len()
if n == 0 { if n == 0 {
@ -89,11 +110,13 @@ func (prog *Program) makeMethods(T types.Type) bool {
return true return true
} }
// methodSet contains the (concrete) methods of a non-interface type.
type methodSet struct { type methodSet struct {
mapping map[string]*Function // populated lazily mapping map[string]*Function // populated lazily
complete bool // mapping contains all methods complete bool // mapping contains all methods
} }
// Precondition: !isInterface(T).
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu) // EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
func (prog *Program) createMethodSet(T types.Type) *methodSet { func (prog *Program) createMethodSet(T types.Type) *methodSet {
mset, ok := prog.methodSets.At(T).(*methodSet) mset, ok := prog.methodSets.At(T).(*methodSet)
@ -105,19 +128,33 @@ func (prog *Program) createMethodSet(T types.Type) *methodSet {
} }
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu) // EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
func (prog *Program) addMethod(mset *methodSet, meth *types.Selection) *Function { func (prog *Program) addMethod(mset *methodSet, sel *types.Selection) *Function {
id := meth.Obj().Id() if sel.Kind() == types.MethodExpr {
panic(sel)
}
id := sel.Obj().Id()
fn := mset.mapping[id] fn := mset.mapping[id]
if fn == nil { if fn == nil {
fn = findMethod(prog, meth) obj := sel.Obj().(*types.Func)
needsPromotion := len(sel.Index()) > 1
needsIndirection := !isPointer(recvType(obj)) && isPointer(sel.Recv())
if needsPromotion || needsIndirection {
fn = makeWrapper(prog, sel)
} else {
fn = prog.declaredFunc(obj)
}
if fn.Signature.Recv() == nil {
panic(fn) // missing receiver
}
mset.mapping[id] = fn mset.mapping[id] = fn
} }
return fn return fn
} }
// TypesWithMethodSets returns a new unordered slice containing all // TypesWithMethodSets returns a new unordered slice containing all
// types in the program for which a complete (non-empty) method set is // concrete types in the program for which a complete (non-empty)
// required at run-time. // method set is required at run-time.
// //
// It is the union of pkg.TypesWithMethodSets() for all pkg in // It is the union of pkg.TypesWithMethodSets() for all pkg in
// prog.AllPackages(). // prog.AllPackages().
@ -139,10 +176,10 @@ func (prog *Program) TypesWithMethodSets() []types.Type {
return res return res
} }
// TypesWithMethodSets returns an unordered slice containing the // TypesWithMethodSets returns an unordered slice containing the set
// set of all types referenced within package pkg and not belonging to // of all concrete types referenced within package pkg and not
// some other package, for which a complete (non-empty) method set is // belonging to some other package, for which a complete (non-empty)
// required at run-time. // method set is required at run-time.
// //
// A type belongs to a package if it is a named type or a pointer to a // A type belongs to a package if it is a named type or a pointer to a
// named type, and the name was defined in that package. All other // named type, and the name was defined in that package. All other
@ -165,8 +202,6 @@ func (pkg *Package) TypesWithMethodSets() []types.Type {
return pkg.methodSets return pkg.methodSets
} }
// ------------------------------------------------------------------------
// declaredFunc returns the concrete function/method denoted by obj. // declaredFunc returns the concrete function/method denoted by obj.
// Panic ensues if there is none. // Panic ensues if there is none.
// //
@ -177,78 +212,63 @@ func (prog *Program) declaredFunc(obj *types.Func) *Function {
panic("no concrete method: " + obj.String()) panic("no concrete method: " + obj.String())
} }
// recvType returns the receiver type of method obj. // -- wrappers ---------------------------------------------------------
func recvType(obj *types.Func) types.Type {
return obj.Type().(*types.Signature).Recv().Type()
}
// findMethod returns the concrete Function for the method meth, // makeWrapper returns a synthetic method that delegates to the
// synthesizing wrappers as needed. // declared method denoted by meth.Obj(), first performing any
// necessary pointer indirections or field selections implied by meth.
//
// The resulting method's receiver type is meth.Recv().
//
// This function is versatile but quite subtle! Consider the
// following axes of variation when making changes:
// - optional receiver indirection
// - optional implicit field selections
// - meth.Obj() may denote a concrete or an interface method
// - the result may be a thunk or a wrapper.
// //
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu) // EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
// //
func findMethod(prog *Program, meth *types.Selection) *Function { func makeWrapper(prog *Program, meth *types.Selection) *Function {
needsPromotion := len(meth.Index()) > 1 obj := meth.Obj().(*types.Func) // the declared function
obj := meth.Obj().(*types.Func) sig := meth.Type().(*types.Signature) // type of this wrapper
needsIndirection := !isPointer(recvType(obj)) && isPointer(meth.Recv())
if needsPromotion || needsIndirection { var recv *types.Var // wrapper's receiver or thunk's params[0]
return makeWrapper(prog, meth.Recv(), meth) name := obj.Name()
var description string
var start int // first regular param
if meth.Kind() == types.MethodExpr {
name += "$thunk"
description = "thunk"
recv = sig.Params().At(0)
start = 1
} else {
description = "wrapper"
recv = sig.Recv()
} }
if _, ok := meth.Recv().Underlying().(*types.Interface); ok { description = fmt.Sprintf("%s for %s", description, meth.Obj())
return interfaceMethodWrapper(prog, meth.Recv(), obj)
}
return prog.declaredFunc(obj)
}
// 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 makeWrapper(typ=A, obj={Func:(*C).f, Indices=[B,C,f]})
// synthesize this wrapper method:
//
// func (a A) f() { return a.B.C->f() }
//
// prog is the program to which the synthesized method will belong.
// typ is the receiver type of the wrapper method. obj is the
// type-checker's object for the promoted method; its Func may be a
// concrete or an interface method.
//
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
//
func makeWrapper(prog *Program, typ types.Type, meth *types.Selection) *Function {
obj := meth.Obj().(*types.Func)
oldsig := obj.Type().(*types.Signature)
recv := newVar("recv", typ)
description := fmt.Sprintf("wrapper for %s", obj)
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, recv.Type())()
} }
fn := &Function{ fn := &Function{
name: obj.Name(), name: name,
method: meth, method: meth,
Signature: changeRecv(oldsig, recv), object: obj,
Signature: sig,
Synthetic: description, Synthetic: description,
Prog: prog, Prog: prog,
pos: obj.Pos(), pos: obj.Pos(),
} }
fn.startBody() fn.startBody()
fn.addSpilledParam(recv) fn.addSpilledParam(recv)
createParams(fn) createParams(fn, start)
var v Value = fn.Locals[0] // spilled receiver var v Value = fn.Locals[0] // spilled receiver
if isPointer(typ) { if isPointer(meth.Recv()) {
// TODO(adonovan): consider emitting a nil-pointer check here // TODO(adonovan): consider emitting a nil-pointer
// with a nice error message, like gc does. // check here with a nice error message, like gc does.
// We could define a new builtin for the purpose.
v = emitLoad(fn, v) v = emitLoad(fn, v)
} }
@ -268,8 +288,8 @@ func makeWrapper(prog *Program, typ types.Type, meth *types.Selection) *Function
// address of implicit C field. // address of implicit C field.
var c Call var c Call
if _, ok := oldsig.Recv().Type().Underlying().(*types.Interface); !ok { // concrete method if r := recvType(obj); !isInterface(r) { // concrete method
if !isPointer(oldsig.Recv().Type()) { if !isPointer(r) {
v = emitLoad(fn, v) v = emitLoad(fn, v)
} }
c.Call.Value = prog.declaredFunc(obj) c.Call.Value = prog.declaredFunc(obj)
@ -288,11 +308,12 @@ func makeWrapper(prog *Program, typ types.Type, meth *types.Selection) *Function
// createParams creates parameters for wrapper method fn based on its // createParams creates parameters for wrapper method fn based on its
// Signature.Params, which do not include the receiver. // Signature.Params, which do not include the receiver.
// start is the index of the first regular parameter to use.
// //
func createParams(fn *Function) { func createParams(fn *Function, start int) {
var last *Parameter var last *Parameter
tparams := fn.Signature.Params() tparams := fn.Signature.Params()
for i, n := 0, tparams.Len(); i < n; i++ { for i, n := start, tparams.Len(); i < n; i++ {
last = fn.addParamObj(tparams.At(i)) last = fn.addParamObj(tparams.At(i))
} }
if fn.Signature.Variadic() { if fn.Signature.Variadic() {
@ -300,76 +321,15 @@ func createParams(fn *Function) {
} }
} }
// Wrappers for standalone interface methods ---------------------------------- // -- bounds -----------------------------------------------------------
// interfaceMethodWrapper returns a synthetic wrapper function // makeBound returns a bound method wrapper (or "bound"), a synthetic
// permitting an abstract method obj to be called like a standalone // function that delegates to a concrete or interface method denoted
// function, e.g.: // by obj. The resulting function has no receiver, but has one free
// variable which will be used as the method's receiver in the
// tail-call.
// //
// type I interface { f(x int) R } // Use MakeClosure with such a wrapper to construct a bound method
// m := I.f // wrapper
// var i I
// m(i, 0)
//
// The wrapper is defined as if by:
//
// func (i I) f(x int, ...) R {
// 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_REQUIRED(prog.methodsMu)
//
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[obj]
if !ok {
description := "interface method wrapper"
if prog.mode&LogSource != 0 {
defer logStack("(%s).%s, %s", typ, obj.Name(), description)()
}
fn = &Function{
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 = obj
c.Call.Value = fn.Params[0]
for _, arg := range fn.Params[1:] {
c.Call.Args = append(c.Call.Args, arg)
}
emitTailCall(fn, &c)
fn.finishBody()
prog.ifaceMethodWrappers[obj] = fn
}
return fn
}
// Wrappers for bound methods -------------------------------------------------
// boundMethodWrapper returns a synthetic wrapper function that
// 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.: // closure. e.g.:
// //
// type T int or: type T interface { meth() } // type T int or: type T interface { meth() }
@ -382,19 +342,24 @@ func interfaceMethodWrapper(prog *Program, typ types.Type, obj *types.Func) *Fun
// //
// f := func() { return t.meth() } // f := func() { return t.meth() }
// //
// Unlike makeWrapper, makeBound need perform no indirection or field
// selections because that can be done before the closure is
// constructed.
//
// EXCLUSIVE_LOCKS_ACQUIRED(meth.Prog.methodsMu) // EXCLUSIVE_LOCKS_ACQUIRED(meth.Prog.methodsMu)
// //
func boundMethodWrapper(prog *Program, obj *types.Func) *Function { func makeBound(prog *Program, obj *types.Func) *Function {
prog.methodsMu.Lock() prog.methodsMu.Lock()
defer prog.methodsMu.Unlock() defer prog.methodsMu.Unlock()
fn, ok := prog.boundMethodWrappers[obj] fn, ok := prog.bounds[obj]
if !ok { if !ok {
description := fmt.Sprintf("bound method wrapper for %s", obj) description := fmt.Sprintf("bound method wrapper for %s", obj)
if prog.mode&LogSource != 0 { if prog.mode&LogSource != 0 {
defer logStack("%s", description)() defer logStack("%s", description)()
} }
fn = &Function{ fn = &Function{
name: "bound$" + obj.FullName(), name: obj.Name() + "$bound",
object: obj,
Signature: changeRecv(obj.Type().(*types.Signature), nil), // drop receiver Signature: changeRecv(obj.Type().(*types.Signature), nil), // drop receiver
Synthetic: description, Synthetic: description,
Prog: prog, Prog: prog,
@ -404,10 +369,10 @@ func boundMethodWrapper(prog *Program, obj *types.Func) *Function {
cap := &Capture{name: "recv", typ: recvType(obj), parent: fn} cap := &Capture{name: "recv", typ: recvType(obj), parent: fn}
fn.FreeVars = []*Capture{cap} fn.FreeVars = []*Capture{cap}
fn.startBody() fn.startBody()
createParams(fn) createParams(fn, 0)
var c Call var c Call
if _, ok := recvType(obj).Underlying().(*types.Interface); !ok { // concrete if !isInterface(recvType(obj)) { // concrete
c.Call.Value = prog.declaredFunc(obj) c.Call.Value = prog.declaredFunc(obj)
c.Call.Args = []Value{cap} c.Call.Args = []Value{cap}
} else { } else {
@ -420,7 +385,60 @@ func boundMethodWrapper(prog *Program, obj *types.Func) *Function {
emitTailCall(fn, &c) emitTailCall(fn, &c)
fn.finishBody() fn.finishBody()
prog.boundMethodWrappers[obj] = fn prog.bounds[obj] = fn
}
return fn
}
// -- thunks -----------------------------------------------------------
// makeThunk returns a thunk, a synthetic function that delegates to a
// concrete or interface method denoted by sel.Obj(). The resulting
// function has no receiver, but has an additional (first) regular
// parameter.
//
// Precondition: sel.Kind() == types.MethodExpr.
//
// type T int or: type T interface { meth() }
// func (t T) meth()
// f := T.meth
// var t T
// f(t) // calls t.meth()
//
// f is a synthetic wrapper defined as if by:
//
// f := func(t T) { return t.meth() }
//
// TODO(adonovan): opt: currently the stub is created even when used
// directly in a function call: C.f(i, 0). This is less efficient
// than inlining the stub.
//
// EXCLUSIVE_LOCKS_ACQUIRED(meth.Prog.methodsMu)
//
func makeThunk(prog *Program, sel *types.Selection) *Function {
if sel.Kind() != types.MethodExpr {
panic(sel)
}
// TODO(adonovan): opt: canonicalize the recv Type to avoid
// construct unnecessary duplicate thunks.
key := selectionKey{
kind: sel.Kind(),
recv: sel.Recv(),
obj: sel.Obj(),
index: fmt.Sprint(sel.Index()),
indirect: sel.Indirect(),
}
prog.methodsMu.Lock()
defer prog.methodsMu.Unlock()
fn, ok := prog.thunks[key]
if !ok {
fn = makeWrapper(prog, sel)
if fn.Signature.Recv() != nil {
panic(fn) // unexpected receiver
}
prog.thunks[key] = fn
} }
return fn return fn
} }
@ -428,3 +446,22 @@ func boundMethodWrapper(prog *Program, obj *types.Func) *Function {
func changeRecv(s *types.Signature, recv *types.Var) *types.Signature { func changeRecv(s *types.Signature, recv *types.Var) *types.Signature {
return types.NewSignature(nil, recv, s.Params(), s.Results(), s.Variadic()) return types.NewSignature(nil, recv, s.Params(), s.Results(), s.Variadic())
} }
// recvType returns the receiver type of method obj.
func recvType(obj *types.Func) types.Type {
return obj.Type().(*types.Signature).Recv().Type()
}
func isInterface(T types.Type) bool {
_, ok := T.Underlying().(*types.Interface)
return ok
}
// selectionKey is like types.Selection but a usable map key.
type selectionKey struct {
kind types.SelectionKind
recv types.Type
obj types.Object
index string
indirect bool
}

View File

@ -389,11 +389,13 @@ func (s *sanity) checkFunction(fn *Function) bool {
fn.String() // must not crash fn.String() // must not crash
fn.RelString(fn.pkgobj()) // must not crash fn.RelString(fn.pkgobj()) // must not crash
// All functions have a package, except wrappers (which are // All functions have a package, except delegates (which are
// shared across packages, or duplicated as weak symbols in a // shared across packages, or duplicated as weak symbols in a
// separate-compilation model), and error.Error. // separate-compilation model), and error.Error.
if fn.Pkg == nil { if fn.Pkg == nil {
if strings.Contains(fn.Synthetic, "wrapper") || if strings.HasPrefix(fn.Synthetic, "wrapper ") ||
strings.HasPrefix(fn.Synthetic, "bound ") ||
strings.HasPrefix(fn.Synthetic, "thunk ") ||
strings.HasSuffix(fn.name, "Error") { strings.HasSuffix(fn.name, "Error") {
// ok // ok
} else { } else {

View File

@ -193,20 +193,19 @@ func (prog *Program) packageLevelValue(obj types.Object) Value {
return nil return nil
} }
// FuncValue returns the Function denoted by the source-level named // FuncValue returns the concrete Function denoted by the source-level
// function obj. // named function obj, or nil if obj denotes an interface method.
//
// TODO(adonovan): check the invariant that obj.Type() matches the
// result's Signature, both in the params/results and in the receiver.
// //
func (prog *Program) FuncValue(obj *types.Func) *Function { func (prog *Program) FuncValue(obj *types.Func) *Function {
// Package-level function or declared method? fn, _ := prog.packageLevelValue(obj).(*Function)
if v := prog.packageLevelValue(obj); v != nil { return fn
return v.(*Function)
}
// Interface method wrapper?
return prog.LookupMethod(recvType(obj), obj.Pkg(), obj.Name())
} }
// ConstValue returns the SSA Value denoted by the source-level named // ConstValue returns the SSA Value denoted by the source-level named
// constant obj. The result may be a *Const, or nil if not found. // constant obj.
// //
func (prog *Program) ConstValue(obj *types.Const) *Const { func (prog *Program) ConstValue(obj *types.Const) *Const {
// TODO(adonovan): opt: share (don't reallocate) // TODO(adonovan): opt: share (don't reallocate)

View File

@ -110,7 +110,9 @@ func checkFuncValue(t *testing.T, prog *ssa.Program, obj *types.Func) {
fn := prog.FuncValue(obj) fn := prog.FuncValue(obj)
// fmt.Printf("FuncValue(%s) = %s\n", obj, fn) // debugging // fmt.Printf("FuncValue(%s) = %s\n", obj, fn) // debugging
if fn == nil { if fn == nil {
t.Errorf("FuncValue(%s) == nil", obj) if obj.Name() != "interfaceMethod" {
t.Errorf("FuncValue(%s) == nil", obj)
}
return return
} }
if fnobj := fn.Object(); fnobj != obj { if fnobj := fn.Object(); fnobj != obj {

View File

@ -28,10 +28,10 @@ type Program struct {
mode BuilderMode // set of mode bits for SSA construction mode BuilderMode // set of mode bits for SSA construction
MethodSets types.MethodSetCache // cache of type-checker's method-sets MethodSets types.MethodSetCache // cache of type-checker's method-sets
methodsMu sync.Mutex // guards the following maps: methodsMu sync.Mutex // guards the following maps:
methodSets typeutil.Map // maps type to its concrete methodSet methodSets typeutil.Map // maps type to its concrete methodSet
boundMethodWrappers map[*types.Func]*Function // wrappers for curried x.Method closures bounds map[*types.Func]*Function // bounds for curried x.Method closures
ifaceMethodWrappers map[*types.Func]*Function // wrappers for curried I.Method functions thunks map[selectionKey]*Function // thunks for T.Method expressions
} }
// A Package is a single analyzed Go package containing Members for // A Package is a single analyzed Go package containing Members for
@ -268,8 +268,8 @@ type Instruction interface {
// //
type Function struct { type Function struct {
name string name string
object types.Object // a declared *types.Func; nil for init, wrappers, etc. object types.Object // a declared *types.Func or one of its wrappers
method *types.Selection // info about provenance of synthetic methods [currently unused] method *types.Selection // info about provenance of synthetic methods
Signature *types.Signature Signature *types.Signature
pos token.Pos pos token.Pos
@ -548,7 +548,6 @@ type UnOp struct {
// - between a named type and its underlying type. // - between a named type and its underlying type.
// - between two named types of the same underlying type. // - between two named types of the same underlying type.
// - between (possibly named) pointers to identical base types. // - between (possibly named) pointers to identical base types.
// - between f(T) functions and (T) func f() methods.
// - from a bidirectional channel to a read- or write-channel, // - from a bidirectional channel to a read- or write-channel,
// optionally adding/removing a name. // optionally adding/removing a name.
// //

View File

@ -82,6 +82,7 @@ func (s *Selection) Type() Type {
// The type of x.f is a function (without receiver) // The type of x.f is a function (without receiver)
// and an additional first argument with the same type as x. // and an additional first argument with the same type as x.
// TODO(gri) Similar code is already in call.go - factor! // TODO(gri) Similar code is already in call.go - factor!
// TODO(gri) Compute this eagerly to avoid allocations.
sig := *s.obj.(*Func).typ.(*Signature) sig := *s.obj.(*Func).typ.(*Signature)
arg0 := *sig.recv arg0 := *sig.recv
sig.recv = nil sig.recv = nil

View File

@ -48,7 +48,8 @@ func pointsto(o *Oracle, qpos *QueryPos) (queryResult, error) {
return nil, fmt.Errorf("unexpected AST for expr: %T", n) return nil, fmt.Errorf("unexpected AST for expr: %T", n)
} }
// Reject non-pointerlike types (includes all constants). // Reject non-pointerlike types (includes all constants---except nil).
// TODO(adonovan): reject nil too.
typ := qpos.info.TypeOf(expr) typ := qpos.info.TypeOf(expr)
if !pointer.CanPoint(typ) { if !pointer.CanPoint(typ) {
return nil, fmt.Errorf("pointer analysis wants an expression of reference type; got %s", typ) return nil, fmt.Errorf("pointer analysis wants an expression of reference type; got %s", typ)
@ -96,7 +97,13 @@ func ssaValueForIdent(prog *ssa.Program, qinfo *loader.PackageInfo, obj types.Ob
return nil, false, fmt.Errorf("can't locate SSA Value for var %s", obj.Name()) return nil, false, fmt.Errorf("can't locate SSA Value for var %s", obj.Name())
case *types.Func: case *types.Func:
return prog.FuncValue(obj), false, nil fn := prog.FuncValue(obj)
if fn == nil {
return nil, false, fmt.Errorf("%s is an interface method", obj)
}
// TODO(adonovan): there's no point running PTA on a *Func ident.
// Eliminate this feature.
return fn, false, nil
} }
panic(obj) panic(obj)
} }

View File

@ -14,17 +14,15 @@ this func() may point to these objects:
(pointsto.D).f (pointsto.D).f
-------- @pointsto func-ref-I.f -------- -------- @pointsto func-ref-I.f --------
this func() may point to these objects:
(pointsto.I).f
Error: func (pointsto.I).f() is an interface method
-------- @pointsto func-ref-d.f -------- -------- @pointsto func-ref-d.f --------
this func() may point to these objects: this func() may point to these objects:
(pointsto.D).f (pointsto.D).f
-------- @pointsto func-ref-i.f -------- -------- @pointsto func-ref-i.f --------
this func() may point to these objects:
(pointsto.I).f
Error: func (pointsto.I).f() is an interface method
-------- @pointsto ref-lexical-d.f -------- -------- @pointsto ref-lexical-d.f --------
this func() may point to these objects: this func() may point to these objects:
(pointsto.D).f (pointsto.D).f