aboutsummaryrefslogtreecommitdiff
path: root/go
diff options
context:
space:
mode:
authorAlan Donovan <adonovan@google.com>2014-12-15 12:58:00 -0500
committerAlan Donovan <adonovan@google.com>2014-12-29 17:50:45 +0000
commit761c80fdf4ef15924d56df8ca1b2b0764fd47627 (patch)
tree9ae6ea78a836a37798b8c5ac4eff39d55d8eb502 /go
parent4b1d99f7f3ee4b328430e99cb7b364ccba3a2eef (diff)
downloadtools-761c80fdf4ef15924d56df8ca1b2b0764fd47627.tar.gz
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>
Diffstat (limited to 'go')
-rw-r--r--go/ssa/builder_test.go97
-rw-r--r--go/ssa/create.go5
-rw-r--r--go/ssa/ssa.go1
-rw-r--r--go/ssa/wrappers.go17
4 files changed, 115 insertions, 5 deletions
diff --git a/go/ssa/builder_test.go b/go/ssa/builder_test.go
index 858db4b..aa6f319 100644
--- a/go/ssa/builder_test.go
+++ b/go/ssa/builder_test.go
@@ -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)
+ }
+}
diff --git a/go/ssa/create.go b/go/ssa/create.go
index 7f57101..9b3a91b 100644
--- a/go/ssa/create.go
+++ b/go/ssa/create.go
@@ -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.
diff --git a/go/ssa/ssa.go b/go/ssa/ssa.go
index 8543992..bc5e9da 100644
--- a/go/ssa/ssa.go
+++ b/go/ssa/ssa.go
@@ -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
}
diff --git a/go/ssa/wrappers.go b/go/ssa/wrappers.go
index 10c8a64..3c7e7f0 100644
--- a/go/ssa/wrappers.go
+++ b/go/ssa/wrappers.go
@@ -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