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:
parent
4b1d99f7f3
commit
761c80fdf4
|
@ -13,6 +13,7 @@ import (
|
||||||
|
|
||||||
"golang.org/x/tools/go/loader"
|
"golang.org/x/tools/go/loader"
|
||||||
"golang.org/x/tools/go/ssa"
|
"golang.org/x/tools/go/ssa"
|
||||||
|
"golang.org/x/tools/go/ssa/ssautil"
|
||||||
"golang.org/x/tools/go/types"
|
"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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
|
|
||||||
"golang.org/x/tools/go/loader"
|
"golang.org/x/tools/go/loader"
|
||||||
"golang.org/x/tools/go/types"
|
"golang.org/x/tools/go/types"
|
||||||
|
"golang.org/x/tools/go/types/typeutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BuilderMode is a bitmask of options for diagnostics and checking.
|
// BuilderMode is a bitmask of options for diagnostics and checking.
|
||||||
|
@ -49,6 +50,10 @@ func Create(iprog *loader.Program, mode BuilderMode) *Program {
|
||||||
mode: mode,
|
mode: mode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h := typeutil.MakeHasher() // protected by methodsMu, in effect
|
||||||
|
prog.methodSets.SetHasher(h)
|
||||||
|
prog.canon.SetHasher(h)
|
||||||
|
|
||||||
for _, info := range iprog.AllPackages {
|
for _, info := range iprog.AllPackages {
|
||||||
// TODO(adonovan): relax this constraint if the
|
// TODO(adonovan): relax this constraint if the
|
||||||
// program contains only "soft" errors.
|
// program contains only "soft" errors.
|
||||||
|
|
|
@ -30,6 +30,7 @@ type Program struct {
|
||||||
|
|
||||||
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
|
||||||
|
canon typeutil.Map // type canonicalization map
|
||||||
bounds map[*types.Func]*Function // bounds for curried x.Method closures
|
bounds map[*types.Func]*Function // bounds for curried x.Method closures
|
||||||
thunks map[selectionKey]*Function // thunks for T.Method expressions
|
thunks map[selectionKey]*Function // thunks for T.Method expressions
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,13 +5,13 @@
|
||||||
package ssa
|
package ssa
|
||||||
|
|
||||||
// This file defines synthesis of Functions that delegate to declared
|
// 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
|
// (1) wrappers: methods that wrap declared methods, performing
|
||||||
// implicit pointer indirections and embedded field selections.
|
// implicit pointer indirections and embedded field selections.
|
||||||
//
|
//
|
||||||
// (2) thunks: funcs that wrap declared methods. Like wrappers,
|
// (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.
|
// first parameter is used as the receiver for the method call.
|
||||||
//
|
//
|
||||||
// (3) bounds: funcs that wrap declared methods. The bound's sole
|
// (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)
|
panic(sel)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(adonovan): opt: canonicalize the recv Type to avoid
|
|
||||||
// construct unnecessary duplicate thunks.
|
|
||||||
key := selectionKey{
|
key := selectionKey{
|
||||||
kind: sel.Kind(),
|
kind: sel.Kind(),
|
||||||
recv: sel.Recv(),
|
recv: sel.Recv(),
|
||||||
|
@ -262,6 +260,15 @@ func makeThunk(prog *Program, sel *types.Selection) *Function {
|
||||||
|
|
||||||
prog.methodsMu.Lock()
|
prog.methodsMu.Lock()
|
||||||
defer prog.methodsMu.Unlock()
|
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]
|
fn, ok := prog.thunks[key]
|
||||||
if !ok {
|
if !ok {
|
||||||
fn = makeWrapper(prog, sel)
|
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.
|
// selectionKey is like types.Selection but a usable map key.
|
||||||
type selectionKey struct {
|
type selectionKey struct {
|
||||||
kind types.SelectionKind
|
kind types.SelectionKind
|
||||||
recv types.Type
|
recv types.Type // canonicalized via Program.canon
|
||||||
obj types.Object
|
obj types.Object
|
||||||
index string
|
index string
|
||||||
indirect bool
|
indirect bool
|
||||||
|
|
Loading…
Reference in New Issue