go/ssa: wait for shared functions to finish building

Package.Build and Program.MethodValue can create and build
Functions that may be depended on by other calls to
Package.Build and Program.MethodValue. Both of these
APIs now wait for any Functions that are shared and depended
on to finish building before returning.

This fixes some data races observed when accessing the Blocks
field of shared functions.

Functions that are members of Packages (fn.Pkg != nil)
are still always built by fn.Pkg.Build().

These dependencies can be create cycles between public API
calls that both depend on Functions the other is building.
For example, both the "net/http" and "internal/trace/testtrace"
packages both depend on slices.Contains[[]string,string] and
slices.Index[[]string, string]. Under some schedules, building
both packages may need to wait for the other package to finish
building before finishing.

This introduces a new internal type task. tasks
allows for waiting on the transitive closure of builders iterating
to be marked as done before proceedind.

Also:
- Add a test that reliably reproduces the data race.
- Refactors creator.add calls to happen after the function is created.
- Merges creator into builder.
- Updates the builder to mark when it is done building and wait
  for dependencies.
- Define type unit = struct{} and clean up.

Fixes golang/go#67079

Change-Id: I34d2b8730b19609f5fbf6b55b3db33f59818a17b
Reviewed-on: https://go-review.googlesource.com/c/tools/+/591775
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
diff --git a/go/ssa/builder.go b/go/ssa/builder.go
index 7486681..55943e4 100644
--- a/go/ssa/builder.go
+++ b/go/ssa/builder.go
@@ -127,10 +127,48 @@
 
 // builder holds state associated with the package currently being built.
 // Its methods contain all the logic for AST-to-SSA conversion.
+//
+// All Functions belong to the same Program.
+//
+// builders are not thread-safe.
 type builder struct {
-	// Invariant: 0 <= rtypes <= finished <= created.Len()
-	created  *creator // functions created during building
-	finished int      // Invariant: create[i].built holds for i in [0,finished)
+	fns []*Function // Functions that have finished their CREATE phases.
+
+	finished int // finished is the length of the prefix of fns containing built functions.
+
+	// The task of building shared functions within the builder.
+	// Shared functions are ones the the builder may either create or lookup.
+	// These may be built by other builders in parallel.
+	// The task is done when the builder has finished iterating, and it
+	// waits for all shared functions to finish building.
+	// nil implies there are no hared functions to wait on.
+	buildshared *task
+}
+
+// shared is done when the builder has built all of the
+// enqueued functions to a fixed-point.
+func (b *builder) shared() *task {
+	if b.buildshared == nil { // lazily-initialize
+		b.buildshared = &task{done: make(chan unit)}
+	}
+	return b.buildshared
+}
+
+// enqueue fn to be built by the builder.
+func (b *builder) enqueue(fn *Function) {
+	b.fns = append(b.fns, fn)
+}
+
+// waitForSharedFunction indicates that the builder should wait until
+// the potentially shared function fn has finished building.
+//
+// This should include any functions that may be built by other
+// builders.
+func (b *builder) waitForSharedFunction(fn *Function) {
+	if fn.buildshared != nil { // maybe need to wait?
+		s := b.shared()
+		s.addEdge(fn.buildshared)
+	}
 }
 
 // cond emits to fn code to evaluate boolean condition e and jump
@@ -779,7 +817,7 @@
 			callee := v.(*Function) // (func)
 			if callee.typeparams.Len() > 0 {
 				targs := fn.subst.types(instanceArgs(fn.info, e))
-				callee = callee.instance(targs, b.created)
+				callee = callee.instance(targs, b)
 			}
 			return callee
 		}
@@ -800,7 +838,8 @@
 		case types.MethodExpr:
 			// (*T).f or T.f, the method f from the method-set of type T.
 			// The result is a "thunk".
-			thunk := createThunk(fn.Prog, sel, b.created)
+			thunk := createThunk(fn.Prog, sel)
+			b.enqueue(thunk)
 			return emitConv(fn, thunk, fn.typ(tv.Type))
 
 		case types.MethodVal:
@@ -842,8 +881,11 @@
 				// obj is generic.
 				obj = fn.Prog.canon.instantiateMethod(obj, fn.subst.types(targs), fn.Prog.ctxt)
 			}
+			bound := createBound(fn.Prog, obj)
+			b.enqueue(bound)
+
 			c := &MakeClosure{
-				Fn:       createBound(fn.Prog, obj, b.created),
+				Fn:       bound,
 				Bindings: []Value{v},
 			}
 			c.setPos(e.Sel.Pos())
@@ -976,7 +1018,7 @@
 				c.Method = obj
 			} else {
 				// "Call"-mode call.
-				c.Value = fn.Prog.objectMethod(obj, b.created)
+				c.Value = fn.Prog.objectMethod(obj, b)
 				c.Args = append(c.Args, v)
 			}
 			return
@@ -2838,11 +2880,16 @@
 // iterate causes all created but unbuilt functions to be built. As
 // this may create new methods, the process is iterated until it
 // converges.
+//
+// Waits for any dependencies to finish building.
 func (b *builder) iterate() {
-	for ; b.finished < b.created.Len(); b.finished++ {
-		fn := b.created.At(b.finished)
+	for ; b.finished < len(b.fns); b.finished++ {
+		fn := b.fns[b.finished]
 		b.buildFunction(fn)
 	}
+
+	b.buildshared.markDone()
+	b.buildshared.wait()
 }
 
 // buildFunction builds SSA code for the body of function fn.  Idempotent.
@@ -3084,7 +3131,7 @@
 			p.Build()
 		} else {
 			wg.Add(1)
-			cpuLimit <- struct{}{} // acquire a token
+			cpuLimit <- unit{} // acquire a token
 			go func(p *Package) {
 				p.Build()
 				wg.Done()
@@ -3096,7 +3143,7 @@
 }
 
 // cpuLimit is a counting semaphore to limit CPU parallelism.
-var cpuLimit = make(chan struct{}, runtime.GOMAXPROCS(0))
+var cpuLimit = make(chan unit, runtime.GOMAXPROCS(0))
 
 // Build builds SSA code for all functions and vars in package p.
 //
@@ -3117,7 +3164,7 @@
 		defer logStack("build %s", p)()
 	}
 
-	b := builder{created: &p.created}
+	b := builder{fns: p.created}
 	b.iterate()
 
 	// We no longer need transient information: ASTs or go/types deductions.
diff --git a/go/ssa/builder_test.go b/go/ssa/builder_test.go
index fdd98f4..ed1d84f 100644
--- a/go/ssa/builder_test.go
+++ b/go/ssa/builder_test.go
@@ -20,6 +20,7 @@
 	"strings"
 	"testing"
 
+	"golang.org/x/sync/errgroup"
 	"golang.org/x/tools/go/analysis/analysistest"
 	"golang.org/x/tools/go/buildutil"
 	"golang.org/x/tools/go/loader"
@@ -1211,3 +1212,51 @@
 		})
 	}
 }
+
+func TestIssue67079(t *testing.T) {
+	// This test reproduced a race in the SSA builder nearly 100% of the time.
+
+	// Load the package.
+	const src = `package p; type T int; func (T) f() {}; var _ = (*T).f`
+	conf := loader.Config{Fset: token.NewFileSet()}
+	f, err := parser.ParseFile(conf.Fset, "p.go", src, 0)
+	if err != nil {
+		t.Fatal(err)
+	}
+	conf.CreateFromFiles("p", f)
+	iprog, err := conf.Load()
+	if err != nil {
+		t.Fatal(err)
+	}
+	pkg := iprog.Created[0].Pkg
+
+	// Create and build SSA program.
+	prog := ssautil.CreateProgram(iprog, ssa.BuilderMode(0))
+	prog.Build()
+
+	var g errgroup.Group
+
+	// Access bodies of all functions.
+	g.Go(func() error {
+		for fn := range ssautil.AllFunctions(prog) {
+			for _, b := range fn.Blocks {
+				for _, instr := range b.Instrs {
+					if call, ok := instr.(*ssa.Call); ok {
+						call.Common().StaticCallee() // access call.Value
+					}
+				}
+			}
+		}
+		return nil
+	})
+
+	// Force building of wrappers.
+	g.Go(func() error {
+		ptrT := types.NewPointer(pkg.Scope().Lookup("T").Type())
+		ptrTf := types.NewMethodSet(ptrT).At(0) // (*T).f symbol
+		prog.MethodValue(ptrTf)
+		return nil
+	})
+
+	g.Wait() // ignore error
+}
diff --git a/go/ssa/create.go b/go/ssa/create.go
index f4dab2d..423bce8 100644
--- a/go/ssa/create.go
+++ b/go/ssa/create.go
@@ -96,8 +96,9 @@
 			pkg.ninit++
 			name = fmt.Sprintf("init#%d", pkg.ninit)
 		}
-		fn := createFunction(pkg.Prog, obj, name, syntax, pkg.info, goversion, &pkg.created)
+		fn := createFunction(pkg.Prog, obj, name, syntax, pkg.info, goversion)
 		fn.Pkg = pkg
+		pkg.created = append(pkg.created, fn)
 		pkg.objects[obj] = fn
 		if name != "_" && sig.Recv() == nil {
 			pkg.Members[name] = fn // package-level function
@@ -111,7 +112,7 @@
 // createFunction creates a function or method. It supports both
 // CreatePackage (with or without syntax) and the on-demand creation
 // of methods in non-created packages based on their types.Func.
-func createFunction(prog *Program, obj *types.Func, name string, syntax ast.Node, info *types.Info, goversion string, cr *creator) *Function {
+func createFunction(prog *Program, obj *types.Func, name string, syntax ast.Node, info *types.Info, goversion string) *Function {
 	sig := obj.Type().(*types.Signature)
 
 	// Collect type parameters.
@@ -143,7 +144,6 @@
 	if tparams.Len() > 0 {
 		fn.generic = new(generic)
 	}
-	cr.Add(fn)
 	return fn
 }
 
@@ -184,19 +184,6 @@
 	}
 }
 
-// creator tracks functions that have finished their CREATE phases.
-//
-// All Functions belong to the same Program. May have differing packages.
-//
-// creators are not thread-safe.
-type creator []*Function
-
-func (c *creator) Add(fn *Function) {
-	*c = append(*c, fn)
-}
-func (c *creator) At(i int) *Function { return (*c)[i] }
-func (c *creator) Len() int           { return len(*c) }
-
 // CreatePackage creates and returns an SSA Package from the
 // specified type-checked, error-free file ASTs, and populates its
 // Members mapping.
@@ -238,7 +225,7 @@
 		goversion: "", // See Package.build for details.
 	}
 	p.Members[p.init.name] = p.init
-	p.created.Add(p.init)
+	p.created = append(p.created, p.init)
 
 	// Allocate all package members: vars, funcs, consts and types.
 	if len(files) > 0 {
diff --git a/go/ssa/instantiate.go b/go/ssa/instantiate.go
index 2cd7405..2512f32 100644
--- a/go/ssa/instantiate.go
+++ b/go/ssa/instantiate.go
@@ -23,7 +23,7 @@
 // Any created instance is added to cr.
 //
 // Acquires fn.generic.instancesMu.
-func (fn *Function) instance(targs []types.Type, cr *creator) *Function {
+func (fn *Function) instance(targs []types.Type, b *builder) *Function {
 	key := fn.Prog.canon.List(targs)
 
 	gen := fn.generic
@@ -32,20 +32,24 @@
 	defer gen.instancesMu.Unlock()
 	inst, ok := gen.instances[key]
 	if !ok {
-		inst = createInstance(fn, targs, cr)
+		inst = createInstance(fn, targs)
+		inst.buildshared = b.shared()
+		b.enqueue(inst)
+
 		if gen.instances == nil {
 			gen.instances = make(map[*typeList]*Function)
 		}
 		gen.instances[key] = inst
+	} else {
+		b.waitForSharedFunction(inst)
 	}
 	return inst
 }
 
 // createInstance returns the instantiation of generic function fn using targs.
-// If the instantiation is created, this is added to cr.
 //
 // Requires fn.generic.instancesMu.
-func createInstance(fn *Function, targs []types.Type, cr *creator) *Function {
+func createInstance(fn *Function, targs []types.Type) *Function {
 	prog := fn.Prog
 
 	// Compute signature.
@@ -89,7 +93,7 @@
 	}
 
 	/* generic instance or instantiation wrapper */
-	instance := &Function{
+	return &Function{
 		name:           fmt.Sprintf("%s%s", fn.Name(), targs), // may not be unique
 		object:         obj,
 		Signature:      sig,
@@ -106,8 +110,6 @@
 		typeargs:       targs,
 		subst:          subst,
 	}
-	cr.Add(instance)
-	return instance
 }
 
 // isParameterized reports whether any of the specified types contains
diff --git a/go/ssa/instantiate_test.go b/go/ssa/instantiate_test.go
index 476848d..25f7849 100644
--- a/go/ssa/instantiate_test.go
+++ b/go/ssa/instantiate_test.go
@@ -96,11 +96,11 @@
 
 		meth := prog.FuncValue(obj)
 
-		var cr creator
+		b := &builder{}
 		intSliceTyp := types.NewSlice(types.Typ[types.Int])
-		instance := meth.instance([]types.Type{intSliceTyp}, &cr)
-		if len(cr) != 1 {
-			t.Errorf("Expected first instance to create a function. got %d created functions", len(cr))
+		instance := meth.instance([]types.Type{intSliceTyp}, b)
+		if len(b.fns) != 1 {
+			t.Errorf("Expected first instance to create a function. got %d created functions", len(b.fns))
 		}
 		if instance.Origin() != meth {
 			t.Errorf("Expected Origin of %s to be %s. got %s", instance, meth, instance.Origin())
@@ -114,13 +114,13 @@
 		}
 
 		// A second request with an identical type returns the same Function.
-		second := meth.instance([]types.Type{types.NewSlice(types.Typ[types.Int])}, &cr)
-		if second != instance || len(cr) != 1 {
+		second := meth.instance([]types.Type{types.NewSlice(types.Typ[types.Int])}, b)
+		if second != instance || len(b.fns) != 1 {
 			t.Error("Expected second identical instantiation to not create a function")
 		}
 
 		// Add a second instance.
-		inst2 := meth.instance([]types.Type{types.NewSlice(types.Typ[types.Uint])}, &cr)
+		inst2 := meth.instance([]types.Type{types.NewSlice(types.Typ[types.Uint])}, b)
 		instances = allInstances(meth)
 
 		// Note: instance.Name() < inst2.Name()
@@ -134,7 +134,6 @@
 		// TODO(adonovan): tests should not rely on unexported functions.
 
 		// build and sanity check manually created instance.
-		var b builder
 		b.buildFunction(instance)
 		var buf bytes.Buffer
 		if !sanityCheck(instance, &buf) {
diff --git a/go/ssa/methods.go b/go/ssa/methods.go
index 58bd45b..b956018 100644
--- a/go/ssa/methods.go
+++ b/go/ssa/methods.go
@@ -40,7 +40,7 @@
 		defer logStack("MethodValue %s %v", T, sel)()
 	}
 
-	var cr creator
+	var b builder
 
 	m := func() *Function {
 		prog.methodsMu.Lock()
@@ -61,20 +61,23 @@
 			needsPromotion := len(sel.Index()) > 1
 			needsIndirection := !isPointer(recvType(obj)) && isPointer(T)
 			if needsPromotion || needsIndirection {
-				fn = createWrapper(prog, toSelection(sel), &cr)
+				fn = createWrapper(prog, toSelection(sel))
+				fn.buildshared = b.shared()
+				b.enqueue(fn)
 			} else {
-				fn = prog.objectMethod(obj, &cr)
+				fn = prog.objectMethod(obj, &b)
 			}
 			if fn.Signature.Recv() == nil {
 				panic(fn)
 			}
 			mset.mapping[id] = fn
+		} else {
+			b.waitForSharedFunction(fn)
 		}
 
 		return fn
 	}()
 
-	b := builder{created: &cr}
 	b.iterate()
 
 	return m
@@ -88,7 +91,7 @@
 // objectMethod panics if the function is not a method.
 //
 // Acquires prog.objectMethodsMu.
-func (prog *Program) objectMethod(obj *types.Func, cr *creator) *Function {
+func (prog *Program) objectMethod(obj *types.Func, b *builder) *Function {
 	sig := obj.Type().(*types.Signature)
 	if sig.Recv() == nil {
 		panic("not a method: " + obj.String())
@@ -101,10 +104,10 @@
 
 	// Instantiation of generic?
 	if originObj := obj.Origin(); originObj != obj {
-		origin := prog.objectMethod(originObj, cr)
+		origin := prog.objectMethod(originObj, b)
 		assert(origin.typeparams.Len() > 0, "origin is not generic")
 		targs := receiverTypeArgs(obj)
-		return origin.instance(targs, cr)
+		return origin.instance(targs, b)
 	}
 
 	// Consult/update cache of methods created from types.Func.
@@ -112,13 +115,17 @@
 	defer prog.objectMethodsMu.Unlock()
 	fn, ok := prog.objectMethods[obj]
 	if !ok {
-		fn = createFunction(prog, obj, obj.Name(), nil, nil, "", cr)
+		fn = createFunction(prog, obj, obj.Name(), nil, nil, "")
 		fn.Synthetic = "from type information (on demand)"
+		fn.buildshared = b.shared()
+		b.enqueue(fn)
 
 		if prog.objectMethods == nil {
 			prog.objectMethods = make(map[*types.Func]*Function)
 		}
 		prog.objectMethods[obj] = fn
+	} else {
+		b.waitForSharedFunction(fn)
 	}
 	return fn
 }
diff --git a/go/ssa/sanity.go b/go/ssa/sanity.go
index d635c15..285cba0 100644
--- a/go/ssa/sanity.go
+++ b/go/ssa/sanity.go
@@ -21,7 +21,7 @@
 	reporter io.Writer
 	fn       *Function
 	block    *BasicBlock
-	instrs   map[Instruction]struct{}
+	instrs   map[Instruction]unit
 	insane   bool
 }
 
@@ -461,10 +461,10 @@
 		}
 	}
 	// Build the set of valid referrers.
-	s.instrs = make(map[Instruction]struct{})
+	s.instrs = make(map[Instruction]unit)
 	for _, b := range fn.Blocks {
 		for _, instr := range b.Instrs {
-			s.instrs[instr] = struct{}{}
+			s.instrs[instr] = unit{}
 		}
 	}
 	for i, p := range fn.Params {
diff --git a/go/ssa/ssa.go b/go/ssa/ssa.go
index 2c44fe4..1231afd 100644
--- a/go/ssa/ssa.go
+++ b/go/ssa/ssa.go
@@ -69,7 +69,7 @@
 	ninit       int32               // number of init functions
 	info        *types.Info         // package type information
 	files       []*ast.File         // package ASTs
-	created     creator             // members created as a result of building this package (includes declared functions, wrappers)
+	created     []*Function         // members created as a result of building this package (includes declared functions, wrappers)
 	initVersion map[ast.Expr]string // goversion to use for each global var init expr
 }
 
@@ -348,6 +348,8 @@
 	Pkg    *Package  // enclosing package; nil for shared funcs (wrappers and error.Error)
 	Prog   *Program  // enclosing program
 
+	buildshared *task // wait for a shared function to be done building (may be nil if <=1 builder ever needs to wait)
+
 	// These fields are populated only when the function body is built:
 
 	Params    []*Parameter  // function parameters; for methods, includes receiver
diff --git a/go/ssa/stdlib_test.go b/go/ssa/stdlib_test.go
index d294fe6..03c8851 100644
--- a/go/ssa/stdlib_test.go
+++ b/go/ssa/stdlib_test.go
@@ -50,6 +50,16 @@
 	testLoad(t, 120, "net/http")
 }
 
+// TestCycles loads two standard libraries that depend on the same
+// generic instantiations.
+// internal/trace/testtrace and net/http both depend on
+// slices.Contains[[]string string] and slices.Index[[]string string]
+// This can under some schedules create a cycle of dependencies
+// where both need to wait on the other to finish building.
+func TestCycles(t *testing.T) {
+	testLoad(t, 120, "net/http", "internal/trace/testtrace")
+}
+
 func testLoad(t *testing.T, minPkgs int, patterns ...string) {
 	// Note: most of the commentary below applies to TestStdlib.
 
diff --git a/go/ssa/task.go b/go/ssa/task.go
new file mode 100644
index 0000000..5024985
--- /dev/null
+++ b/go/ssa/task.go
@@ -0,0 +1,103 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssa
+
+import (
+	"sync/atomic"
+)
+
+// Each task has two states: it is initially "active",
+// and transitions to "done".
+//
+// tasks form a directed graph. An edge from x to y (with y in x.edges)
+// indicates that the task x waits on the task y to be done.
+// Cycles are permitted.
+//
+// Calling x.wait() blocks the calling goroutine until task x,
+// and all the tasks transitively reachable from x are done.
+//
+// The nil *task is always considered done.
+type task struct {
+	done       chan unit      // close when the task is done.
+	edges      map[*task]unit // set of predecessors of this task.
+	transitive atomic.Bool    // true once it is known all predecessors are done.
+}
+
+func (x *task) isTransitivelyDone() bool { return x == nil || x.transitive.Load() }
+
+// addEdge creates an edge from x to y, indicating that
+// x.wait() will not return before y is done.
+// All calls to x.addEdge(...) should happen before x.markDone().
+func (x *task) addEdge(y *task) {
+	if x == y || y.isTransitivelyDone() {
+		return // no work remaining
+	}
+
+	// heuristic done check
+	select {
+	case <-x.done:
+		panic("cannot add an edge to a done task")
+	default:
+	}
+
+	if x.edges == nil {
+		x.edges = make(map[*task]unit)
+	}
+	x.edges[y] = unit{}
+}
+
+// markDone changes the task's state to markDone.
+func (x *task) markDone() {
+	if x != nil {
+		close(x.done)
+	}
+}
+
+// wait blocks until x and all the tasks it can reach through edges are done.
+func (x *task) wait() {
+	if x.isTransitivelyDone() {
+		return // already known to be done. Skip allocations.
+	}
+
+	// Use BFS to wait on u.done to be closed, for all u transitively
+	// reachable from x via edges.
+	//
+	// This work can be repeated by multiple workers doing wait().
+	//
+	// Note: Tarjan's SCC algorithm is able to mark SCCs as transitively done
+	// as soon as the SCC has been visited. This is theoretically faster, but is
+	// a more complex algorithm. Until we have evidence, we need the more complex
+	// algorithm, the simpler algorithm BFS is implemented.
+	//
+	// In Go 1.23, ssa/TestStdlib reaches <=3 *tasks per wait() in most schedules
+	// On some schedules, there is a cycle building net/http and internal/trace/testtrace
+	// due to slices functions.
+	work := []*task{x}
+	enqueued := map[*task]unit{x: {}}
+	for i := 0; i < len(work); i++ {
+		u := work[i]
+		if u.isTransitivelyDone() { // already transitively done
+			work[i] = nil
+			continue
+		}
+		<-u.done // wait for u to be marked done.
+
+		for v := range u.edges {
+			if _, ok := enqueued[v]; !ok {
+				enqueued[v] = unit{}
+				work = append(work, v)
+			}
+		}
+	}
+
+	// work is transitively closed over dependencies.
+	// u in work is done (or transitively done and skipped).
+	// u is transitively done.
+	for _, u := range work {
+		if u != nil {
+			x.transitive.Store(true)
+		}
+	}
+}
diff --git a/go/ssa/util.go b/go/ssa/util.go
index bd0e62e..549c9c8 100644
--- a/go/ssa/util.go
+++ b/go/ssa/util.go
@@ -22,6 +22,8 @@
 	"golang.org/x/tools/internal/typesinternal"
 )
 
+type unit struct{}
+
 //// Sanity checking utilities
 
 // assert panics with the mesage msg if p is false.
diff --git a/go/ssa/wrappers.go b/go/ssa/wrappers.go
index b25c4c7..d09b4f2 100644
--- a/go/ssa/wrappers.go
+++ b/go/ssa/wrappers.go
@@ -42,7 +42,7 @@
 //   - optional implicit field selections
 //   - meth.Obj() may denote a concrete or an interface method
 //   - the result may be a thunk or a wrapper.
-func createWrapper(prog *Program, sel *selection, cr *creator) *Function {
+func createWrapper(prog *Program, sel *selection) *Function {
 	obj := sel.obj.(*types.Func)      // the declared function
 	sig := sel.typ.(*types.Signature) // type of this wrapper
 
@@ -63,7 +63,7 @@
 		defer logStack("create %s to (%s)", description, recv.Type())()
 	}
 	/* method wrapper */
-	fn := &Function{
+	return &Function{
 		name:      name,
 		method:    sel,
 		object:    obj,
@@ -77,8 +77,6 @@
 		info:      nil,
 		goversion: "",
 	}
-	cr.Add(fn)
-	return fn
 }
 
 // buildWrapper builds fn.Body for a method wrapper.
@@ -141,7 +139,7 @@
 		if !isPointer(r) {
 			v = emitLoad(fn, v)
 		}
-		c.Call.Value = fn.Prog.objectMethod(fn.object, b.created)
+		c.Call.Value = fn.Prog.objectMethod(fn.object, b)
 		c.Call.Args = append(c.Call.Args, v)
 	} else {
 		c.Call.Method = fn.object
@@ -188,7 +186,7 @@
 // Unlike createWrapper, createBound need perform no indirection or field
 // selections because that can be done before the closure is
 // constructed.
-func createBound(prog *Program, obj *types.Func, cr *creator) *Function {
+func createBound(prog *Program, obj *types.Func) *Function {
 	description := fmt.Sprintf("bound method wrapper for %s", obj)
 	if prog.mode&LogSource != 0 {
 		defer logStack("%s", description)()
@@ -208,7 +206,6 @@
 		goversion: "",
 	}
 	fn.FreeVars = []*FreeVar{{name: "recv", typ: recvType(obj), parent: fn}} // (cyclic)
-	cr.Add(fn)
 	return fn
 }
 
@@ -220,7 +217,7 @@
 
 	recv := fn.FreeVars[0]
 	if !types.IsInterface(recvType(fn.object)) { // concrete
-		c.Call.Value = fn.Prog.objectMethod(fn.object, b.created)
+		c.Call.Value = fn.Prog.objectMethod(fn.object, b)
 		c.Call.Args = []Value{recv}
 	} else {
 		c.Call.Method = fn.object
@@ -251,12 +248,12 @@
 // f is a synthetic wrapper defined as if by:
 //
 //	f := func(t T) { return t.meth() }
-func createThunk(prog *Program, sel *selection, cr *creator) *Function {
+func createThunk(prog *Program, sel *selection) *Function {
 	if sel.kind != types.MethodExpr {
 		panic(sel)
 	}
 
-	fn := createWrapper(prog, sel, cr)
+	fn := createWrapper(prog, sel)
 	if fn.Signature.Recv() != nil {
 		panic(fn) // unexpected receiver
 	}