1
0
mirror of https://github.com/golang/go synced 2024-09-30 08:18:40 -06:00

go.tools/ssa: add support for bound-method closures.

Extracted Builder.findMethod function to handle
methodset/receiver logic common to
function calls (Builder.setCall) and
bound method closure creation (Builder.selector).

Capture: added explicit Name, Type fields to Capture instead
of relying on Outer field, which is now un-exported since its
only purpose is to let Builder.expr(case *ast.FuncLit) know
which values to put in the closure; it is nilled immediately
after.

Simplified Function.lookup() logic: there's no need to walk
the Outer chain each time to set Alloc.Heap=true, as it's
already set during creation of the outermost
Capture{outer:*Alloc}.

Added interp/testdata/boundmeth.go test.

Cosmetic changes:
- add support for bound method thunks to Function.FullName().
- Simplified {Literal,Global,Builtin,Function}.String()
- doc: Captures are no longer necessarily addresses.
- added yet another missing pair of "()" (go/types accessors).
- print "Synthetic" not "Declared at -" for synthetic functions.
- use '$' not center-dot in synthetic identifiers (easier to type).

R=gri
CC=golang-dev
https://golang.org/cl/9654043
This commit is contained in:
Alan Donovan 2013-05-22 17:56:18 -04:00
parent bf87b9f0f5
commit 8cdf1f1cb1
10 changed files with 259 additions and 93 deletions

View File

@ -449,6 +449,20 @@ func (b *Builder) builtin(fn *Function, name string, args []ast.Expr, typ types.
//
func (b *Builder) selector(fn *Function, e *ast.SelectorExpr, wantAddr, escaping bool) Value {
id := MakeId(e.Sel.Name, fn.Pkg.Types)
// Bound method closure? (e.m where m is a method)
if !wantAddr {
if m, recv := b.findMethod(fn, e.X, id); m != nil {
c := &MakeClosure{
Fn: makeBoundMethodThunk(b.Prog, m, recv),
Bindings: []Value{recv},
}
c.setPos(e.Sel.Pos())
c.setType(fn.Pkg.TypeOf(e))
return fn.emit(c)
}
}
st := fn.Pkg.TypeOf(e.X).Deref().Underlying().(*types.Struct)
index := -1
for i, n := 0, st.NumFields(); i < n; i++ {
@ -704,7 +718,8 @@ func (b *Builder) expr(fn *Function, e ast.Expr) Value {
v := &MakeClosure{Fn: fn2}
v.setType(fn.Pkg.TypeOf(e))
for _, fv := range fn2.FreeVars {
v.Bindings = append(v.Bindings, fv.Outer)
v.Bindings = append(v.Bindings, fv.outer)
fv.outer = nil
}
return fn.emit(v)
@ -836,7 +851,7 @@ func (b *Builder) expr(fn *Function, e ast.Expr) Value {
return makeImethodThunk(b.Prog, typ, id)
}
// e.f where e is an expression.
// e.f where e is an expression. f may be a method.
return b.selector(fn, e, false, false)
case *ast.IndexExpr:
@ -894,6 +909,41 @@ 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.
//
// findMethod returns (nil, nil) if no such method was found.
//
func (b *Builder) findMethod(fn *Function, base ast.Expr, id Id) (*Function, Value) {
typ := fn.Pkg.TypeOf(base)
// Consult method-set of X.
if m := b.Prog.MethodSet(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 := b.Prog.MethodSet(pointer(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).addr
}
}
return nil, nil
}
// setCallFunc populates the function parts of a CallCommon structure
// (Func, Method, Recv, Args[0]) based on the kind of invocation
// occurring in e.
@ -934,45 +984,20 @@ func (b *Builder) setCallFunc(fn *Function, e *ast.CallExpr, c *CallCommon) {
return
}
id := MakeId(sel.Sel.Name, fn.Pkg.Types)
// Let X be the type of x.
typ := fn.Pkg.TypeOf(sel.X)
// 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).
id := MakeId(sel.Sel.Name, fn.Pkg.Types)
// Consult method-set of X.
if m := b.Prog.MethodSet(typ)[id]; m != nil {
var recv Value
aptr := isPointer(typ)
fptr := isPointer(m.Signature.Recv().Type())
if aptr == fptr {
// Actual's and formal's "pointerness" match.
recv = b.expr(fn, sel.X)
} else {
// Actual is a pointer, formal is not.
// Load a copy.
recv = emitLoad(fn, b.expr(fn, sel.X))
}
if m, recv := b.findMethod(fn, sel.X, id); m != nil {
c.Func = m
c.Args = append(c.Args, recv)
return
}
if !isPointer(typ) {
// Consult method-set of *X.
if m := b.Prog.MethodSet(pointer(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.
recv := b.addr(fn, sel.X, true).(address).addr
c.Func = m
c.Args = append(c.Args, recv)
return
}
}
switch t := typ.Underlying().(type) {
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'
@ -2562,7 +2587,7 @@ func (b *Builder) createPackageImpl(typkg *types.Package, importPath string, fil
// Add initializer guard variable.
initguard := &Global{
Pkg: p,
Name_: "init·guard",
Name_: "init$guard",
Type_: pointer(tBool),
}
p.Members[initguard.Name()] = initguard
@ -2671,13 +2696,13 @@ func (b *Builder) BuildPackage(p *Package) {
return // nothing to do
}
if b.Context.Mode&LogSource != 0 {
defer logStack("build package %s", p.Types.Path)()
defer logStack("build package %s", p.Types.Path())()
}
init := p.Init
init.startBody()
// Make init() skip if package is already initialized.
initguard := p.Var("init·guard")
initguard := p.Var("init$guard")
doinit := init.newBasicBlock("init.start")
done := init.newBasicBlock("init.done")
emitIf(init, emitLoad(init, initguard), done, doinit)

View File

@ -280,16 +280,13 @@ func emitTypeTest(f *Function, x Value, t types.Type) Value {
return f.emit(a)
}
// emitTailCall emits to f a function call in tail position,
// passing on all but the first formal parameter to f as actual
// values in the call. Intended for delegating bridge methods.
// emitTailCall emits to f a function call in tail position. The
// caller is responsible for all fields of 'call' except its type.
// Intended for delegating bridge methods.
// Precondition: f does/will not use deferred procedure calls.
// Postcondition: f.currentBlock is nil.
//
func emitTailCall(f *Function, call *Call) {
for _, arg := range f.Params[1:] {
call.Call.Args = append(call.Call.Args, arg)
}
tresults := f.Signature.Results()
nr := tresults.Len()
if nr == 1 {

View File

@ -71,18 +71,18 @@ func main() {
// Output:
//
// Package main:
// var init·guard *bool
// func main func()
// const message message = "Hello, World!":untyped string
// var init$guard *bool
// func main func()
// const message message = "Hello, World!":untyped string
//
// # Name: main.init
// # Declared at -
// # Synthetic
// func init():
// .0.entry: P:0 S:2
// t0 = *init·guard bool
// t0 = *init$guard bool
// if t0 goto 2.init.done else 1.init.start
// .1.init.start: P:1 S:1
// *init·guard = true:bool
// *init$guard = true:bool
// t1 = fmt.init() ()
// jump 2.init.done
// .2.init.done: P:2 S:0

View File

@ -8,6 +8,7 @@ import (
"go/token"
"io"
"os"
"strings"
"code.google.com/p/go.tools/go/types"
)
@ -395,19 +396,8 @@ func (f *Function) addLocal(typ types.Type, pos token.Pos) *Alloc {
//
func (f *Function) lookup(obj types.Object, escaping bool) Value {
if v, ok := f.objects[obj]; ok {
if escaping {
// Walk up the chain of Captures.
x := v
for {
if c, ok := x.(*Capture); ok {
x = c.Outer
} else {
break
}
}
// By construction, all captures are ultimately Allocs in the
// naive SSA form. Parameters are pre-spilled to the stack.
x.(*Alloc).Heap = true
if alloc, ok := v.(*Alloc); ok && escaping {
alloc.Heap = true
}
return v // function-local var (address)
}
@ -417,7 +407,12 @@ func (f *Function) lookup(obj types.Object, escaping bool) Value {
if f.Enclosing == nil {
panic("no Value for type.Object " + obj.Name())
}
v := &Capture{Outer: f.Enclosing.lookup(obj, true)} // escaping
outer := f.Enclosing.lookup(obj, true) // escaping
v := &Capture{
Name_: outer.Name(),
Type_: outer.Type(),
outer: outer,
}
f.objects[obj] = v
f.FreeVars = append(f.FreeVars, v)
return v
@ -442,6 +437,7 @@ func (f *Function) emit(instr Instruction) Value {
// "(*exp/ssa.Ret).Block" // a bridge method
// "(ssa.Instruction).Block" // an interface method thunk
// "func@5.32" // an anonymous function
// "bound$(*T).f" // a bound method thunk
//
func (f *Function) FullName() string {
return f.fullName(nil)
@ -449,6 +445,8 @@ func (f *Function) FullName() string {
// Like FullName, but if from==f.Pkg, suppress package qualification.
func (f *Function) fullName(from *Package) string {
// TODO(adonovan): expose less fragile case discrimination.
// Anonymous?
if f.Enclosing != nil {
return f.Name_
@ -461,6 +459,8 @@ func (f *Function) fullName(from *Package) string {
var recvType types.Type
if recv != nil {
recvType = recv.Type() // bridge method
} else if strings.HasPrefix(f.Name_, "bound$") {
return f.Name_ // bound method thunk
} else {
recvType = f.Params[0].Type() // interface method thunk
}
@ -527,7 +527,11 @@ func writeSignature(w io.Writer, name string, sig *types.Signature, params []*Pa
//
func (f *Function) DumpTo(w io.Writer) {
fmt.Fprintf(w, "# Name: %s\n", f.FullName())
fmt.Fprintf(w, "# Declared at %s\n", f.Prog.Files.Position(f.Pos()))
if pos := f.Pos(); pos.IsValid() {
fmt.Fprintf(w, "# Declared at %s\n", f.Prog.Files.Position(pos))
} else {
fmt.Fprintln(w, "# Synthetic")
}
if f.Enclosing != nil {
fmt.Fprintf(w, "# Parent: %s\n", f.Enclosing.Name())

View File

@ -130,6 +130,7 @@ var gorootTests = []string{
var testdataTests = []string{
"coverage.go",
"mrvchain.go",
"boundmeth.go",
}
func run(t *testing.T, dir, input string) bool {

88
ssa/interp/testdata/boundmeth.go vendored Normal file
View File

@ -0,0 +1,88 @@
// Tests of bound method closures.
package main
func assert(b bool) {
if !b {
panic("oops")
}
}
type I int
func (i I) add(x int) int {
return int(i) + x
}
func valueReceiver() {
var three I = 3
assert(three.add(5) == 8)
var add3 func(int) int = three.add
assert(add3(5) == 8)
}
type S struct{ x int }
func (s *S) incr() {
s.x++
}
func (s *S) get() int {
return s.x
}
func pointerReceiver() {
ps := new(S)
incr := ps.incr
get := ps.get
assert(get() == 0)
incr()
incr()
incr()
assert(get() == 3)
}
func addressibleValuePointerReceiver() {
var s S
incr := s.incr
get := s.get
assert(get() == 0)
incr()
incr()
incr()
assert(get() == 3)
}
type S2 struct {
S
}
func promotedReceiver() {
var s2 S2
incr := s2.incr
get := s2.get
assert(get() == 0)
incr()
incr()
incr()
assert(get() == 3)
}
func anonStruct() {
var s struct{ S }
incr := s.incr
get := s.get
assert(get() == 0)
incr()
incr()
incr()
assert(get() == 3)
}
func main() {
valueReceiver()
pointerReceiver()
addressibleValuePointerReceiver()
promotedReceiver()
anonStruct()
}

View File

@ -48,7 +48,7 @@ func relName(v Value, i Instruction) string {
// It never appears in disassembly, which uses Value.Name().
func (v *Literal) String() string {
return fmt.Sprintf("literal %s", v.Name())
return v.Name()
}
func (v *Parameter) String() string {
@ -60,15 +60,15 @@ func (v *Capture) String() string {
}
func (v *Global) String() string {
return fmt.Sprintf("global %s : %s", v.Name(), v.Type())
return v.FullName()
}
func (v *Builtin) String() string {
return fmt.Sprintf("builtin %s : %s", v.Name(), v.Type())
return fmt.Sprintf("builtin %s", v.Name())
}
func (v *Function) String() string {
return fmt.Sprintf("function %s : %s", v.Name(), v.Type())
return v.FullName()
}
// FullName returns g's package-qualified name.

View File

@ -105,9 +105,7 @@ func (p *Program) MethodSet(typ types.Type) MethodSet {
// TODO(adonovan): Using Types as map keys doesn't properly
// de-dup. e.g. *NamedType are canonical but *Struct and
// others are not. Need to de-dup based on using a two-level
// hash-table with hash function types.Type.String and
// equivalence relation types.IsIdentical.
// others are not. Need to de-dup using typemap.T.
mset := p.methodSets[typ]
if mset == nil {
mset = buildMethodSet(p, typ)
@ -308,13 +306,14 @@ func makeBridgeMethod(prog *Program, typ types.Type, cand *candidate) *Function
var c Call
if cand.concrete != nil {
c.Call.Func = cand.concrete
fn.pos = c.Call.Func.(*Function).pos // TODO(adonovan): fix: wrong.
c.Call.pos = fn.pos // TODO(adonovan): fix: wrong.
c.Call.Args = append(c.Call.Args, v)
} else {
c.Call.Recv = v
c.Call.Method = 0
}
for _, arg := range fn.Params[1:] {
c.Call.Args = append(c.Call.Args, arg)
}
emitTailCall(fn, &c)
fn.finishBody()
return fn
@ -339,7 +338,7 @@ func createParams(fn *Function) {
// Thunks for standalone interface methods ----------------------------------------
// makeImethodThunk returns a synthetic thunk function permitting an
// makeImethodThunk returns a synthetic thunk function permitting a
// method id of interface typ to be called like a standalone function,
// e.g.:
//
@ -354,11 +353,6 @@ func createParams(fn *Function) {
// return i.f(x, ...)
// }
//
// The generated thunks do not belong to any package. (Arguably they
// belong in the package that defines the interface, but we have no
// way to determine that on demand; we'd have to create all possible
// thunks a priori.)
//
// TODO(adonovan): opt: currently the stub is created even when used
// in call position: I.f(i, 0). Clearly this is suboptimal.
//
@ -376,13 +370,61 @@ func makeImethodThunk(prog *Program, typ types.Type, id Id) *Function {
Signature: &sig,
Prog: prog,
}
// TODO(adonovan): set fn.Pos to location of interface method ast.Field.
fn.startBody()
fn.addParam("recv", typ)
createParams(fn)
var c Call
c.Call.Method = index
c.Call.Recv = fn.Params[0]
for _, arg := range fn.Params[1:] {
c.Call.Args = append(c.Call.Args, arg)
}
emitTailCall(fn, &c)
fn.finishBody()
return fn
}
// Thunks for bound methods ----------------------------------------
// makeBoundMethodThunk returns a synthetic thunk function that
// delegates to a concrete method. The thunk has one free variable,
// the method's receiver. Use MakeClosure with such a thunk to
// construct a bound-method closure.
// e.g.:
//
// type T int
// func (t T) meth()
// var t T
// f := t.meth
// f() // calls t.meth()
//
// f is a closure of a synthetic thunk defined as if by:
//
// f := func() { return t.meth() }
//
// TODO(adonovan): memoize creation of these functions in the Program.
//
func makeBoundMethodThunk(prog *Program, meth *Function, recv Value) *Function {
if prog.mode&LogSource != 0 {
defer logStack("makeBoundMethodThunk %s", meth)()
}
s := meth.Signature
fn := &Function{
Name_: "bound$" + meth.FullName(),
Signature: types.NewSignature(nil, s.Params(), s.Results(), s.IsVariadic()), // drop recv
Prog: prog,
}
cap := &Capture{Name_: "recv", Type_: recv.Type()}
fn.FreeVars = []*Capture{cap}
fn.startBody()
createParams(fn)
var c Call
c.Call.Func = meth
c.Call.Args = []Value{cap}
for _, arg := range fn.Params {
c.Call.Args = append(c.Call.Args, arg)
}
emitTailCall(fn, &c)
fn.finishBody()
return fn

View File

@ -242,7 +242,7 @@ type Instruction interface {
// local variables ("free variables") has Capture parameters. Such
// functions cannot be called directly but require a value created by
// MakeClosure which, via its Bindings, supplies values for these
// parameters. Captures are always addresses.
// parameters.
//
// If the function is a method (Signature.Recv() != nil) then the first
// element of Params is the receiver parameter.
@ -306,15 +306,26 @@ type BasicBlock struct {
// Pure values ----------------------------------------
// A Capture is a pointer to a lexically enclosing local variable.
// A Capture represents a free variable of the function to which it
// belongs.
//
// The referent of a capture is an Alloc or another Capture and is
// always considered potentially escaping, so Captures are always
// addresses in the heap, and have pointer types.
// Captures are used to implement anonymous functions, whose free
// variables are lexically captured in a closure formed by
// MakeClosure. The referent of such a capture is an Alloc or another
// Capture and is considered a potentially escaping heap address, with
// pointer type.
//
// Captures are also used to implement bound method closures. Such a
// capture represents the receiver value and may be of any type that
// has concrete methods.
//
type Capture struct {
Outer Value // the Value captured from the enclosing context.
Name_ string
Type_ types.Type
referrers []Instruction
// Transiently needed during building.
outer Value // the Value captured from the enclosing context.
}
// A Parameter represents an input parameter of a function.
@ -587,20 +598,17 @@ type MakeInterface struct {
Methods MethodSet // method set of (non-interface) X
}
// The MakeClosure instruction yields an anonymous function value
// whose code is Fn and whose lexical capture slots are populated by
// Bindings.
//
// By construction, all captured variables are addresses of variables
// allocated with 'new', i.e. Alloc(Heap=true).
// The MakeClosure instruction yields a closure value whose code is
// Fn and whose free variables' values are supplied by Bindings.
//
// Type() returns a (possibly named) *types.Signature.
//
// Pos() returns the ast.FuncLit.Type.Func of the function literal
// that created this closure.
// Pos() returns the ast.FuncLit.Type.Func for a function literal
// closure or the ast.SelectorExpr.Sel for a bound method closure.
//
// Example printed form:
// t0 = make closure anon@1.2 [x y z]
// t1 = make closure bound$(main.I).add [i]
//
type MakeClosure struct {
Register
@ -1217,8 +1225,8 @@ func (v *Builtin) Type() types.Type { return v.Object.Type() }
func (v *Builtin) Name() string { return v.Object.Name() }
func (*Builtin) Referrers() *[]Instruction { return nil }
func (v *Capture) Type() types.Type { return v.Outer.Type() }
func (v *Capture) Name() string { return v.Outer.Name() }
func (v *Capture) Type() types.Type { return v.Type_ }
func (v *Capture) Name() string { return v.Name_ }
func (v *Capture) Referrers() *[]Instruction { return &v.referrers }
func (v *Global) Type() types.Type { return v.Type_ }

View File

@ -53,6 +53,7 @@ func isPointer(typ types.Type) bool {
}
// pointer(typ) returns the type that is a pointer to typ.
// TODO(adonovan): inline and eliminate.
func pointer(typ types.Type) *types.Pointer {
return types.NewPointer(typ)
}