Skip to content

Commit db513b0

Browse files
committed
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 <[email protected]> Reviewed-by: Alan Donovan <[email protected]>
1 parent 8a45e6c commit db513b0

12 files changed

+271
-66
lines changed

go/ssa/builder.go

+59-12
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,48 @@ var (
127127

128128
// builder holds state associated with the package currently being built.
129129
// Its methods contain all the logic for AST-to-SSA conversion.
130+
//
131+
// All Functions belong to the same Program.
132+
//
133+
// builders are not thread-safe.
130134
type builder struct {
131-
// Invariant: 0 <= rtypes <= finished <= created.Len()
132-
created *creator // functions created during building
133-
finished int // Invariant: create[i].built holds for i in [0,finished)
135+
fns []*Function // Functions that have finished their CREATE phases.
136+
137+
finished int // finished is the length of the prefix of fns containing built functions.
138+
139+
// The task of building shared functions within the builder.
140+
// Shared functions are ones the the builder may either create or lookup.
141+
// These may be built by other builders in parallel.
142+
// The task is done when the builder has finished iterating, and it
143+
// waits for all shared functions to finish building.
144+
// nil implies there are no hared functions to wait on.
145+
buildshared *task
146+
}
147+
148+
// shared is done when the builder has built all of the
149+
// enqueued functions to a fixed-point.
150+
func (b *builder) shared() *task {
151+
if b.buildshared == nil { // lazily-initialize
152+
b.buildshared = &task{done: make(chan unit)}
153+
}
154+
return b.buildshared
155+
}
156+
157+
// enqueue fn to be built by the builder.
158+
func (b *builder) enqueue(fn *Function) {
159+
b.fns = append(b.fns, fn)
160+
}
161+
162+
// waitForSharedFunction indicates that the builder should wait until
163+
// the potentially shared function fn has finished building.
164+
//
165+
// This should include any functions that may be built by other
166+
// builders.
167+
func (b *builder) waitForSharedFunction(fn *Function) {
168+
if fn.buildshared != nil { // maybe need to wait?
169+
s := b.shared()
170+
s.addEdge(fn.buildshared)
171+
}
134172
}
135173

136174
// cond emits to fn code to evaluate boolean condition e and jump
@@ -779,7 +817,7 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value {
779817
callee := v.(*Function) // (func)
780818
if callee.typeparams.Len() > 0 {
781819
targs := fn.subst.types(instanceArgs(fn.info, e))
782-
callee = callee.instance(targs, b.created)
820+
callee = callee.instance(targs, b)
783821
}
784822
return callee
785823
}
@@ -800,7 +838,8 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value {
800838
case types.MethodExpr:
801839
// (*T).f or T.f, the method f from the method-set of type T.
802840
// The result is a "thunk".
803-
thunk := createThunk(fn.Prog, sel, b.created)
841+
thunk := createThunk(fn.Prog, sel)
842+
b.enqueue(thunk)
804843
return emitConv(fn, thunk, fn.typ(tv.Type))
805844

806845
case types.MethodVal:
@@ -842,8 +881,11 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value {
842881
// obj is generic.
843882
obj = fn.Prog.canon.instantiateMethod(obj, fn.subst.types(targs), fn.Prog.ctxt)
844883
}
884+
bound := createBound(fn.Prog, obj)
885+
b.enqueue(bound)
886+
845887
c := &MakeClosure{
846-
Fn: createBound(fn.Prog, obj, b.created),
888+
Fn: bound,
847889
Bindings: []Value{v},
848890
}
849891
c.setPos(e.Sel.Pos())
@@ -976,7 +1018,7 @@ func (b *builder) setCallFunc(fn *Function, e *ast.CallExpr, c *CallCommon) {
9761018
c.Method = obj
9771019
} else {
9781020
// "Call"-mode call.
979-
c.Value = fn.Prog.objectMethod(obj, b.created)
1021+
c.Value = fn.Prog.objectMethod(obj, b)
9801022
c.Args = append(c.Args, v)
9811023
}
9821024
return
@@ -2838,11 +2880,16 @@ type buildFunc = func(*builder, *Function)
28382880
// iterate causes all created but unbuilt functions to be built. As
28392881
// this may create new methods, the process is iterated until it
28402882
// converges.
2883+
//
2884+
// Waits for any dependencies to finish building.
28412885
func (b *builder) iterate() {
2842-
for ; b.finished < b.created.Len(); b.finished++ {
2843-
fn := b.created.At(b.finished)
2886+
for ; b.finished < len(b.fns); b.finished++ {
2887+
fn := b.fns[b.finished]
28442888
b.buildFunction(fn)
28452889
}
2890+
2891+
b.buildshared.markDone()
2892+
b.buildshared.wait()
28462893
}
28472894

28482895
// buildFunction builds SSA code for the body of function fn. Idempotent.
@@ -3084,7 +3131,7 @@ func (prog *Program) Build() {
30843131
p.Build()
30853132
} else {
30863133
wg.Add(1)
3087-
cpuLimit <- struct{}{} // acquire a token
3134+
cpuLimit <- unit{} // acquire a token
30883135
go func(p *Package) {
30893136
p.Build()
30903137
wg.Done()
@@ -3096,7 +3143,7 @@ func (prog *Program) Build() {
30963143
}
30973144

30983145
// cpuLimit is a counting semaphore to limit CPU parallelism.
3099-
var cpuLimit = make(chan struct{}, runtime.GOMAXPROCS(0))
3146+
var cpuLimit = make(chan unit, runtime.GOMAXPROCS(0))
31003147

31013148
// Build builds SSA code for all functions and vars in package p.
31023149
//
@@ -3117,7 +3164,7 @@ func (p *Package) build() {
31173164
defer logStack("build %s", p)()
31183165
}
31193166

3120-
b := builder{created: &p.created}
3167+
b := builder{fns: p.created}
31213168
b.iterate()
31223169

31233170
// We no longer need transient information: ASTs or go/types deductions.

go/ssa/builder_test.go

+49
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"strings"
2121
"testing"
2222

23+
"golang.org/x/sync/errgroup"
2324
"golang.org/x/tools/go/analysis/analysistest"
2425
"golang.org/x/tools/go/buildutil"
2526
"golang.org/x/tools/go/loader"
@@ -1211,3 +1212,51 @@ func TestFixedBugs(t *testing.T) {
12111212
})
12121213
}
12131214
}
1215+
1216+
func TestIssue67079(t *testing.T) {
1217+
// This test reproduced a race in the SSA builder nearly 100% of the time.
1218+
1219+
// Load the package.
1220+
const src = `package p; type T int; func (T) f() {}; var _ = (*T).f`
1221+
conf := loader.Config{Fset: token.NewFileSet()}
1222+
f, err := parser.ParseFile(conf.Fset, "p.go", src, 0)
1223+
if err != nil {
1224+
t.Fatal(err)
1225+
}
1226+
conf.CreateFromFiles("p", f)
1227+
iprog, err := conf.Load()
1228+
if err != nil {
1229+
t.Fatal(err)
1230+
}
1231+
pkg := iprog.Created[0].Pkg
1232+
1233+
// Create and build SSA program.
1234+
prog := ssautil.CreateProgram(iprog, ssa.BuilderMode(0))
1235+
prog.Build()
1236+
1237+
var g errgroup.Group
1238+
1239+
// Access bodies of all functions.
1240+
g.Go(func() error {
1241+
for fn := range ssautil.AllFunctions(prog) {
1242+
for _, b := range fn.Blocks {
1243+
for _, instr := range b.Instrs {
1244+
if call, ok := instr.(*ssa.Call); ok {
1245+
call.Common().StaticCallee() // access call.Value
1246+
}
1247+
}
1248+
}
1249+
}
1250+
return nil
1251+
})
1252+
1253+
// Force building of wrappers.
1254+
g.Go(func() error {
1255+
ptrT := types.NewPointer(pkg.Scope().Lookup("T").Type())
1256+
ptrTf := types.NewMethodSet(ptrT).At(0) // (*T).f symbol
1257+
prog.MethodValue(ptrTf)
1258+
return nil
1259+
})
1260+
1261+
g.Wait() // ignore error
1262+
}

go/ssa/create.go

+4-17
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,9 @@ func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node, goversion
9696
pkg.ninit++
9797
name = fmt.Sprintf("init#%d", pkg.ninit)
9898
}
99-
fn := createFunction(pkg.Prog, obj, name, syntax, pkg.info, goversion, &pkg.created)
99+
fn := createFunction(pkg.Prog, obj, name, syntax, pkg.info, goversion)
100100
fn.Pkg = pkg
101+
pkg.created = append(pkg.created, fn)
101102
pkg.objects[obj] = fn
102103
if name != "_" && sig.Recv() == nil {
103104
pkg.Members[name] = fn // package-level function
@@ -111,7 +112,7 @@ func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node, goversion
111112
// createFunction creates a function or method. It supports both
112113
// CreatePackage (with or without syntax) and the on-demand creation
113114
// of methods in non-created packages based on their types.Func.
114-
func createFunction(prog *Program, obj *types.Func, name string, syntax ast.Node, info *types.Info, goversion string, cr *creator) *Function {
115+
func createFunction(prog *Program, obj *types.Func, name string, syntax ast.Node, info *types.Info, goversion string) *Function {
115116
sig := obj.Type().(*types.Signature)
116117

117118
// Collect type parameters.
@@ -143,7 +144,6 @@ func createFunction(prog *Program, obj *types.Func, name string, syntax ast.Node
143144
if tparams.Len() > 0 {
144145
fn.generic = new(generic)
145146
}
146-
cr.Add(fn)
147147
return fn
148148
}
149149

@@ -184,19 +184,6 @@ func membersFromDecl(pkg *Package, decl ast.Decl, goversion string) {
184184
}
185185
}
186186

187-
// creator tracks functions that have finished their CREATE phases.
188-
//
189-
// All Functions belong to the same Program. May have differing packages.
190-
//
191-
// creators are not thread-safe.
192-
type creator []*Function
193-
194-
func (c *creator) Add(fn *Function) {
195-
*c = append(*c, fn)
196-
}
197-
func (c *creator) At(i int) *Function { return (*c)[i] }
198-
func (c *creator) Len() int { return len(*c) }
199-
200187
// CreatePackage creates and returns an SSA Package from the
201188
// specified type-checked, error-free file ASTs, and populates its
202189
// Members mapping.
@@ -238,7 +225,7 @@ func (prog *Program) CreatePackage(pkg *types.Package, files []*ast.File, info *
238225
goversion: "", // See Package.build for details.
239226
}
240227
p.Members[p.init.name] = p.init
241-
p.created.Add(p.init)
228+
p.created = append(p.created, p.init)
242229

243230
// Allocate all package members: vars, funcs, consts and types.
244231
if len(files) > 0 {

go/ssa/instantiate.go

+9-7
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ type generic struct {
2323
// Any created instance is added to cr.
2424
//
2525
// Acquires fn.generic.instancesMu.
26-
func (fn *Function) instance(targs []types.Type, cr *creator) *Function {
26+
func (fn *Function) instance(targs []types.Type, b *builder) *Function {
2727
key := fn.Prog.canon.List(targs)
2828

2929
gen := fn.generic
@@ -32,20 +32,24 @@ func (fn *Function) instance(targs []types.Type, cr *creator) *Function {
3232
defer gen.instancesMu.Unlock()
3333
inst, ok := gen.instances[key]
3434
if !ok {
35-
inst = createInstance(fn, targs, cr)
35+
inst = createInstance(fn, targs)
36+
inst.buildshared = b.shared()
37+
b.enqueue(inst)
38+
3639
if gen.instances == nil {
3740
gen.instances = make(map[*typeList]*Function)
3841
}
3942
gen.instances[key] = inst
43+
} else {
44+
b.waitForSharedFunction(inst)
4045
}
4146
return inst
4247
}
4348

4449
// createInstance returns the instantiation of generic function fn using targs.
45-
// If the instantiation is created, this is added to cr.
4650
//
4751
// Requires fn.generic.instancesMu.
48-
func createInstance(fn *Function, targs []types.Type, cr *creator) *Function {
52+
func createInstance(fn *Function, targs []types.Type) *Function {
4953
prog := fn.Prog
5054

5155
// Compute signature.
@@ -89,7 +93,7 @@ func createInstance(fn *Function, targs []types.Type, cr *creator) *Function {
8993
}
9094

9195
/* generic instance or instantiation wrapper */
92-
instance := &Function{
96+
return &Function{
9397
name: fmt.Sprintf("%s%s", fn.Name(), targs), // may not be unique
9498
object: obj,
9599
Signature: sig,
@@ -106,8 +110,6 @@ func createInstance(fn *Function, targs []types.Type, cr *creator) *Function {
106110
typeargs: targs,
107111
subst: subst,
108112
}
109-
cr.Add(instance)
110-
return instance
111113
}
112114

113115
// isParameterized reports whether any of the specified types contains

go/ssa/instantiate_test.go

+7-8
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,11 @@ func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
9696

9797
meth := prog.FuncValue(obj)
9898

99-
var cr creator
99+
b := &builder{}
100100
intSliceTyp := types.NewSlice(types.Typ[types.Int])
101-
instance := meth.instance([]types.Type{intSliceTyp}, &cr)
102-
if len(cr) != 1 {
103-
t.Errorf("Expected first instance to create a function. got %d created functions", len(cr))
101+
instance := meth.instance([]types.Type{intSliceTyp}, b)
102+
if len(b.fns) != 1 {
103+
t.Errorf("Expected first instance to create a function. got %d created functions", len(b.fns))
104104
}
105105
if instance.Origin() != meth {
106106
t.Errorf("Expected Origin of %s to be %s. got %s", instance, meth, instance.Origin())
@@ -114,13 +114,13 @@ func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
114114
}
115115

116116
// A second request with an identical type returns the same Function.
117-
second := meth.instance([]types.Type{types.NewSlice(types.Typ[types.Int])}, &cr)
118-
if second != instance || len(cr) != 1 {
117+
second := meth.instance([]types.Type{types.NewSlice(types.Typ[types.Int])}, b)
118+
if second != instance || len(b.fns) != 1 {
119119
t.Error("Expected second identical instantiation to not create a function")
120120
}
121121

122122
// Add a second instance.
123-
inst2 := meth.instance([]types.Type{types.NewSlice(types.Typ[types.Uint])}, &cr)
123+
inst2 := meth.instance([]types.Type{types.NewSlice(types.Typ[types.Uint])}, b)
124124
instances = allInstances(meth)
125125

126126
// Note: instance.Name() < inst2.Name()
@@ -134,7 +134,6 @@ func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
134134
// TODO(adonovan): tests should not rely on unexported functions.
135135

136136
// build and sanity check manually created instance.
137-
var b builder
138137
b.buildFunction(instance)
139138
var buf bytes.Buffer
140139
if !sanityCheck(instance, &buf) {

0 commit comments

Comments
 (0)