Skip to content

Commit 8acc2ea

Browse files
cherrymuibradfitz
authored andcommitted
[release-branch.go1.12] cmd/compile: copy volatile values before emitting write barrier call
It is possible that a "volatile" value (one that can be clobbered by preparing args of a call) to be used in multiple write barrier calls. We used to copy the volatile value right before each call. But this doesn't work if the value is used the second time, after the first call where it is already clobbered. Copy it before emitting any call. Updates #30977. Fixes #30996. Change-Id: Iedcc91ad848d5ded547bf37a8359c125d32e994c Reviewed-on: https://go-review.googlesource.com/c/go/+/168677 Run-TryBot: Cherry Zhang <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Keith Randall <[email protected]> (cherry picked from commit f23c601) Reviewed-on: https://go-review.googlesource.com/c/go/+/168817 Run-TryBot: Brad Fitzpatrick <[email protected]>
1 parent ec06e9b commit 8acc2ea

File tree

2 files changed

+106
-24
lines changed

2 files changed

+106
-24
lines changed

src/cmd/compile/internal/ssa/writebarrier.go

Lines changed: 54 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,43 @@ func writebarrier(f *Func) {
196196
// and simple store version to bElse
197197
memThen := mem
198198
memElse := mem
199+
200+
// If the source of a MoveWB is volatile (will be clobbered by a
201+
// function call), we need to copy it to a temporary location, as
202+
// marshaling the args of typedmemmove might clobber the value we're
203+
// trying to move.
204+
// Look for volatile source, copy it to temporary before we emit any
205+
// call.
206+
// It is unlikely to have more than one of them. Just do a linear
207+
// search instead of using a map.
208+
type volatileCopy struct {
209+
src *Value // address of original volatile value
210+
tmp *Value // address of temporary we've copied the volatile value into
211+
}
212+
var volatiles []volatileCopy
213+
copyLoop:
214+
for _, w := range stores {
215+
if w.Op == OpMoveWB {
216+
val := w.Args[1]
217+
if isVolatile(val) {
218+
for _, c := range volatiles {
219+
if val == c.src {
220+
continue copyLoop // already copied
221+
}
222+
}
223+
224+
t := val.Type.Elem()
225+
tmp := f.fe.Auto(w.Pos, t)
226+
memThen = bThen.NewValue1A(w.Pos, OpVarDef, types.TypeMem, tmp, memThen)
227+
tmpaddr := bThen.NewValue2A(w.Pos, OpLocalAddr, t.PtrTo(), tmp, sp, memThen)
228+
siz := t.Size()
229+
memThen = bThen.NewValue3I(w.Pos, OpMove, types.TypeMem, siz, tmpaddr, val, memThen)
230+
memThen.Aux = t
231+
volatiles = append(volatiles, volatileCopy{val, tmpaddr})
232+
}
233+
}
234+
}
235+
199236
for _, w := range stores {
200237
ptr := w.Args[0]
201238
pos := w.Pos
@@ -222,11 +259,19 @@ func writebarrier(f *Func) {
222259
// then block: emit write barrier call
223260
switch w.Op {
224261
case OpStoreWB, OpMoveWB, OpZeroWB:
225-
volatile := w.Op == OpMoveWB && isVolatile(val)
226262
if w.Op == OpStoreWB {
227263
memThen = bThen.NewValue3A(pos, OpWB, types.TypeMem, gcWriteBarrier, ptr, val, memThen)
228264
} else {
229-
memThen = wbcall(pos, bThen, fn, typ, ptr, val, memThen, sp, sb, volatile)
265+
srcval := val
266+
if w.Op == OpMoveWB && isVolatile(srcval) {
267+
for _, c := range volatiles {
268+
if srcval == c.src {
269+
srcval = c.tmp
270+
break
271+
}
272+
}
273+
}
274+
memThen = wbcall(pos, bThen, fn, typ, ptr, srcval, memThen, sp, sb)
230275
}
231276
// Note that we set up a writebarrier function call.
232277
f.fe.SetWBPos(pos)
@@ -249,6 +294,12 @@ func writebarrier(f *Func) {
249294
}
250295
}
251296

297+
// mark volatile temps dead
298+
for _, c := range volatiles {
299+
tmpNode := c.tmp.Aux
300+
memThen = bThen.NewValue1A(memThen.Pos, OpVarKill, types.TypeMem, tmpNode, memThen)
301+
}
302+
252303
// merge memory
253304
// Splice memory Phi into the last memory of the original sequence,
254305
// which may be used in subsequent blocks. Other memories in the
@@ -302,25 +353,9 @@ func writebarrier(f *Func) {
302353
}
303354

304355
// wbcall emits write barrier runtime call in b, returns memory.
305-
// if valIsVolatile, it moves val into temp space before making the call.
306-
func wbcall(pos src.XPos, b *Block, fn, typ *obj.LSym, ptr, val, mem, sp, sb *Value, valIsVolatile bool) *Value {
356+
func wbcall(pos src.XPos, b *Block, fn, typ *obj.LSym, ptr, val, mem, sp, sb *Value) *Value {
307357
config := b.Func.Config
308358

309-
var tmp GCNode
310-
if valIsVolatile {
311-
// Copy to temp location if the source is volatile (will be clobbered by
312-
// a function call). Marshaling the args to typedmemmove might clobber the
313-
// value we're trying to move.
314-
t := val.Type.Elem()
315-
tmp = b.Func.fe.Auto(val.Pos, t)
316-
mem = b.NewValue1A(pos, OpVarDef, types.TypeMem, tmp, mem)
317-
tmpaddr := b.NewValue2A(pos, OpLocalAddr, t.PtrTo(), tmp, sp, mem)
318-
siz := t.Size()
319-
mem = b.NewValue3I(pos, OpMove, types.TypeMem, siz, tmpaddr, val, mem)
320-
mem.Aux = t
321-
val = tmpaddr
322-
}
323-
324359
// put arguments on stack
325360
off := config.ctxt.FixedFrameSize()
326361

@@ -348,11 +383,6 @@ func wbcall(pos src.XPos, b *Block, fn, typ *obj.LSym, ptr, val, mem, sp, sb *Va
348383
// issue call
349384
mem = b.NewValue1A(pos, OpStaticCall, types.TypeMem, fn, mem)
350385
mem.AuxInt = off - config.ctxt.FixedFrameSize()
351-
352-
if valIsVolatile {
353-
mem = b.NewValue1A(pos, OpVarKill, types.TypeMem, tmp, mem) // mark temp dead
354-
}
355-
356386
return mem
357387
}
358388

test/fixedbugs/issue30977.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// run
2+
3+
// Copyright 2019 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
// Issue 30977: write barrier call clobbers volatile
8+
// value when there are multiple uses of the value.
9+
10+
package main
11+
12+
import "runtime"
13+
14+
type T struct {
15+
a, b, c, d, e string
16+
}
17+
18+
//go:noinline
19+
func g() T {
20+
return T{"a", "b", "c", "d", "e"}
21+
}
22+
23+
//go:noinline
24+
func f() {
25+
// The compiler optimizes this to direct copying
26+
// the call result to both globals, with write
27+
// barriers. The first write barrier call clobbers
28+
// the result of g on stack.
29+
X = g()
30+
Y = X
31+
}
32+
33+
var X, Y T
34+
35+
const N = 1000
36+
37+
func main() {
38+
// Keep GC running so the write barrier is on.
39+
go func() {
40+
for {
41+
runtime.GC()
42+
}
43+
}()
44+
45+
for i := 0; i < N; i++ {
46+
runtime.Gosched()
47+
f()
48+
if X != Y {
49+
panic("FAIL")
50+
}
51+
}
52+
}

0 commit comments

Comments
 (0)