1
0
mirror of https://github.com/golang/go synced 2024-10-01 01:18:32 -06:00

go/ssa: canonicalize receiver types to avoid creating duplicate thunk functions

+ test

Change-Id: Ie37835577ffcdd764cf6a0b611e02f04386755cf
Reviewed-on: https://go-review.googlesource.com/1580
Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
Alan Donovan 2014-12-15 12:58:00 -05:00
parent 4b1d99f7f3
commit 761c80fdf4
4 changed files with 115 additions and 5 deletions

View File

@ -13,6 +13,7 @@ import (
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
"golang.org/x/tools/go/types"
)
@ -317,3 +318,99 @@ func init():
}
}
}
// TestSyntheticFuncs checks that the expected synthetic functions are
// created, reachable, and not duplicated.
func TestSyntheticFuncs(t *testing.T) {
const input = `package P
type T int
func (T) f() int
func (*T) g() int
var (
// thunks
a = T.f
b = T.f
c = (struct{T}).f
d = (struct{T}).f
e = (*T).g
f = (*T).g
g = (struct{*T}).g
h = (struct{*T}).g
// bounds
i = T(0).f
j = T(0).f
k = new(T).g
l = new(T).g
// wrappers
m interface{} = struct{T}{}
n interface{} = struct{T}{}
o interface{} = struct{*T}{}
p interface{} = struct{*T}{}
q interface{} = new(struct{T})
r interface{} = new(struct{T})
s interface{} = new(struct{*T})
t interface{} = new(struct{*T})
)
`
// Parse
var conf loader.Config
f, err := conf.ParseFile("<input>", input)
if err != nil {
t.Fatalf("parse: %v", err)
}
conf.CreateFromFiles(f.Name.Name, f)
// Load
iprog, err := conf.Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
// Create and build SSA
prog := ssa.Create(iprog, 0)
prog.BuildAll()
// Enumerate reachable synthetic functions
want := map[string]string{
"(*P.T).g$bound": "bound method wrapper for func (*P.T).g() int",
"(P.T).f$bound": "bound method wrapper for func (P.T).f() int",
"(*P.T).g$thunk": "thunk for func (*P.T).g() int",
"(P.T).f$thunk": "thunk for func (P.T).f() int",
"(struct{*P.T}).g$thunk": "thunk for func (*P.T).g() int",
"(struct{P.T}).f$thunk": "thunk for func (P.T).f() int",
"(*P.T).f": "wrapper for func (P.T).f() int",
"(*struct{*P.T}).f": "wrapper for func (P.T).f() int",
"(*struct{*P.T}).g": "wrapper for func (*P.T).g() int",
"(*struct{P.T}).f": "wrapper for func (P.T).f() int",
"(*struct{P.T}).g": "wrapper for func (*P.T).g() int",
"(struct{*P.T}).f": "wrapper for func (P.T).f() int",
"(struct{*P.T}).g": "wrapper for func (*P.T).g() int",
"(struct{P.T}).f": "wrapper for func (P.T).f() int",
"P.init": "package initializer",
}
for fn := range ssautil.AllFunctions(prog) {
if fn.Synthetic == "" {
continue
}
name := fn.String()
wantDescr, ok := want[name]
if !ok {
t.Errorf("got unexpected/duplicate func: %q: %q", name, fn.Synthetic)
continue
}
delete(want, name)
if wantDescr != fn.Synthetic {
t.Errorf("(%s).Synthetic = %q, want %q",
name, fn.Synthetic, wantDescr)
}
}
for fn, descr := range want {
t.Errorf("want func: %q: %q", fn, descr)
}
}

View File

@ -15,6 +15,7 @@ import (
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/types"
"golang.org/x/tools/go/types/typeutil"
)
// BuilderMode is a bitmask of options for diagnostics and checking.
@ -49,6 +50,10 @@ func Create(iprog *loader.Program, mode BuilderMode) *Program {
mode: mode,
}
h := typeutil.MakeHasher() // protected by methodsMu, in effect
prog.methodSets.SetHasher(h)
prog.canon.SetHasher(h)
for _, info := range iprog.AllPackages {
// TODO(adonovan): relax this constraint if the
// program contains only "soft" errors.

View File

@ -30,6 +30,7 @@ type Program struct {
methodsMu sync.Mutex // guards the following maps:
methodSets typeutil.Map // maps type to its concrete methodSet
canon typeutil.Map // type canonicalization map
bounds map[*types.Func]*Function // bounds for curried x.Method closures
thunks map[selectionKey]*Function // thunks for T.Method expressions
}

View File

@ -5,13 +5,13 @@
package ssa
// This file defines synthesis of Functions that delegate to declared
// methods, which come in three kinds:
// methods; they come in three kinds:
//
// (1) wrappers: methods that wrap declared methods, performing
// implicit pointer indirections and embedded field selections.
//
// (2) thunks: funcs that wrap declared methods. Like wrappers,
// thunks perform indirections and field selections. The thunks's
// thunks perform indirections and field selections. The thunk's
// first parameter is used as the receiver for the method call.
//
// (3) bounds: funcs that wrap declared methods. The bound's sole
@ -250,8 +250,6 @@ func makeThunk(prog *Program, sel *types.Selection) *Function {
panic(sel)
}
// TODO(adonovan): opt: canonicalize the recv Type to avoid
// construct unnecessary duplicate thunks.
key := selectionKey{
kind: sel.Kind(),
recv: sel.Recv(),
@ -262,6 +260,15 @@ func makeThunk(prog *Program, sel *types.Selection) *Function {
prog.methodsMu.Lock()
defer prog.methodsMu.Unlock()
// Canonicalize key.recv to avoid constructing duplicate thunks.
canonRecv, ok := prog.canon.At(key.recv).(types.Type)
if !ok {
canonRecv = key.recv
prog.canon.Set(key.recv, canonRecv)
}
key.recv = canonRecv
fn, ok := prog.thunks[key]
if !ok {
fn = makeWrapper(prog, sel)
@ -280,7 +287,7 @@ func changeRecv(s *types.Signature, recv *types.Var) *types.Signature {
// selectionKey is like types.Selection but a usable map key.
type selectionKey struct {
kind types.SelectionKind
recv types.Type
recv types.Type // canonicalized via Program.canon
obj types.Object
index string
indirect bool