From 761c80fdf4ef15924d56df8ca1b2b0764fd47627 Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Mon, 15 Dec 2014 12:58:00 -0500 Subject: [PATCH] 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 --- go/ssa/builder_test.go | 97 ++++++++++++++++++++++++++++++++++++++++++ go/ssa/create.go | 5 +++ go/ssa/ssa.go | 1 + go/ssa/wrappers.go | 17 +++++--- 4 files changed, 115 insertions(+), 5 deletions(-) diff --git a/go/ssa/builder_test.go b/go/ssa/builder_test.go index 858db4b786b..aa6f3199640 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) + 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 7f57101d3d9..9b3a91b4f9c 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 854399211bb..bc5e9da297b 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 10c8a64a9f3..3c7e7f048db 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