mirror of
https://github.com/golang/go
synced 2024-11-18 19:14:40 -07: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:
parent
4b1d99f7f3
commit
761c80fdf4
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user