Skip to content

Commit 42da35c

Browse files
committed
cmd/compile: SSA, don't let write barrier clobber return values
When we do *p = f(), we might need to copy the return value from f to p with a write barrier. The write barrier itself is a call, so we need to copy the return value of f to a temporary location before we call the write barrier function. Otherwise, the call itself (specifically, marshalling the args to typedmemmove) will clobber the value we're trying to write. Fixes #15854 Change-Id: I5703da87634d91a9884e3ec098d7b3af713462e7 Reviewed-on: https://go-review.googlesource.com/23522 Reviewed-by: David Chase <[email protected]> Run-TryBot: Keith Randall <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent 3a6a418 commit 42da35c

File tree

2 files changed

+130
-51
lines changed

2 files changed

+130
-51
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2016 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package gc
6+
7+
import "testing"
8+
9+
type T struct {
10+
x [2]int64 // field that will be clobbered. Also makes type not SSAable.
11+
p *byte // has a pointer
12+
}
13+
14+
//go:noinline
15+
func makeT() T {
16+
return T{}
17+
}
18+
19+
var g T
20+
21+
var sink []byte
22+
23+
func TestIssue15854(t *testing.T) {
24+
for i := 0; i < 10000; i++ {
25+
if g.x[0] != 0 {
26+
t.Fatalf("g.x[0] clobbered with %x\n", g.x[0])
27+
}
28+
// The bug was in the following assignment. The return
29+
// value of makeT() is not copied out of the args area of
30+
// stack frame in a timely fashion. So when write barriers
31+
// are enabled, the marshaling of the args for the write
32+
// barrier call clobbers the result of makeT() before it is
33+
// read by the write barrier code.
34+
g = makeT()
35+
sink = make([]byte, 1000) // force write barriers to eventually happen
36+
}
37+
}
38+
func TestIssue15854b(t *testing.T) {
39+
const N = 10000
40+
a := make([]T, N)
41+
for i := 0; i < N; i++ {
42+
a = append(a, makeT())
43+
sink = make([]byte, 1000) // force write barriers to eventually happen
44+
}
45+
for i, v := range a {
46+
if v.x[0] != 0 {
47+
t.Fatalf("a[%d].x[0] clobbered with %x\n", i, v.x[0])
48+
}
49+
}
50+
}

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

Lines changed: 80 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -580,8 +580,8 @@ func (s *state) stmt(n *Node) {
580580

581581
case OAS2DOTTYPE:
582582
res, resok := s.dottype(n.Rlist.First(), true)
583-
s.assign(n.List.First(), res, needwritebarrier(n.List.First(), n.Rlist.First()), false, n.Lineno, 0)
584-
s.assign(n.List.Second(), resok, false, false, n.Lineno, 0)
583+
s.assign(n.List.First(), res, needwritebarrier(n.List.First(), n.Rlist.First()), false, n.Lineno, 0, false)
584+
s.assign(n.List.Second(), resok, false, false, n.Lineno, 0, false)
585585
return
586586

587587
case ODCL:
@@ -700,13 +700,14 @@ func (s *state) stmt(n *Node) {
700700
}
701701
}
702702
var r *ssa.Value
703+
var isVolatile bool
703704
needwb := n.Op == OASWB && rhs != nil
704705
deref := !canSSAType(t)
705706
if deref {
706707
if rhs == nil {
707708
r = nil // Signal assign to use OpZero.
708709
} else {
709-
r = s.addr(rhs, false)
710+
r, isVolatile = s.addr(rhs, false)
710711
}
711712
} else {
712713
if rhs == nil {
@@ -755,7 +756,7 @@ func (s *state) stmt(n *Node) {
755756
}
756757
}
757758

758-
s.assign(n.Left, r, needwb, deref, n.Lineno, skip)
759+
s.assign(n.Left, r, needwb, deref, n.Lineno, skip, isVolatile)
759760

760761
case OIF:
761762
bThen := s.f.NewBlock(ssa.BlockPlain)
@@ -1438,10 +1439,10 @@ func (s *state) expr(n *Node) *ssa.Value {
14381439
if s.canSSA(n) {
14391440
return s.variable(n, n.Type)
14401441
}
1441-
addr := s.addr(n, false)
1442+
addr, _ := s.addr(n, false)
14421443
return s.newValue2(ssa.OpLoad, n.Type, addr, s.mem())
14431444
case OCLOSUREVAR:
1444-
addr := s.addr(n, false)
1445+
addr, _ := s.addr(n, false)
14451446
return s.newValue2(ssa.OpLoad, n.Type, addr, s.mem())
14461447
case OLITERAL:
14471448
switch u := n.Val().U.(type) {
@@ -1910,7 +1911,9 @@ func (s *state) expr(n *Node) *ssa.Value {
19101911
return s.expr(n.Left)
19111912

19121913
case OADDR:
1913-
return s.addr(n.Left, n.Bounded)
1914+
a, _ := s.addr(n.Left, n.Bounded)
1915+
// Note we know the volatile result is false because you can't write &f() in Go.
1916+
return a
19141917

19151918
case OINDREG:
19161919
if int(n.Reg) != Thearch.REGSP {
@@ -1930,7 +1933,7 @@ func (s *state) expr(n *Node) *ssa.Value {
19301933
v := s.expr(n.Left)
19311934
return s.newValue1I(ssa.OpStructSelect, n.Type, int64(fieldIdx(n)), v)
19321935
}
1933-
p := s.addr(n, false)
1936+
p, _ := s.addr(n, false)
19341937
return s.newValue2(ssa.OpLoad, n.Type, p, s.mem())
19351938

19361939
case ODOTPTR:
@@ -1957,11 +1960,11 @@ func (s *state) expr(n *Node) *ssa.Value {
19571960
}
19581961
return s.newValue2(ssa.OpLoad, Types[TUINT8], ptr, s.mem())
19591962
case n.Left.Type.IsSlice():
1960-
p := s.addr(n, false)
1963+
p, _ := s.addr(n, false)
19611964
return s.newValue2(ssa.OpLoad, n.Left.Type.Elem(), p, s.mem())
19621965
case n.Left.Type.IsArray():
19631966
// TODO: fix when we can SSA arrays of length 1.
1964-
p := s.addr(n, false)
1967+
p, _ := s.addr(n, false)
19651968
return s.newValue2(ssa.OpLoad, n.Left.Type.Elem(), p, s.mem())
19661969
default:
19671970
s.Fatalf("bad type for index %v", n.Left.Type)
@@ -2126,7 +2129,7 @@ func (s *state) append(n *Node, inplace bool) *ssa.Value {
21262129

21272130
var slice, addr *ssa.Value
21282131
if inplace {
2129-
addr = s.addr(sn, false)
2132+
addr, _ = s.addr(sn, false)
21302133
slice = s.newValue2(ssa.OpLoad, n.Type, addr, s.mem())
21312134
} else {
21322135
slice = s.expr(sn)
@@ -2197,15 +2200,21 @@ func (s *state) append(n *Node, inplace bool) *ssa.Value {
21972200
}
21982201

21992202
// Evaluate args
2200-
args := make([]*ssa.Value, 0, nargs)
2201-
store := make([]bool, 0, nargs)
2203+
type argRec struct {
2204+
// if store is true, we're appending the value v. If false, we're appending the
2205+
// value at *v. If store==false, isVolatile reports whether the source
2206+
// is in the outargs section of the stack frame.
2207+
v *ssa.Value
2208+
store bool
2209+
isVolatile bool
2210+
}
2211+
args := make([]argRec, 0, nargs)
22022212
for _, n := range n.List.Slice()[1:] {
22032213
if canSSAType(n.Type) {
2204-
args = append(args, s.expr(n))
2205-
store = append(store, true)
2214+
args = append(args, argRec{v: s.expr(n), store: true})
22062215
} else {
2207-
args = append(args, s.addr(n, false))
2208-
store = append(store, false)
2216+
v, isVolatile := s.addr(n, false)
2217+
args = append(args, argRec{v: v, isVolatile: isVolatile})
22092218
}
22102219
}
22112220

@@ -2219,17 +2228,17 @@ func (s *state) append(n *Node, inplace bool) *ssa.Value {
22192228
// TODO: maybe just one writeBarrier.enabled check?
22202229
for i, arg := range args {
22212230
addr := s.newValue2(ssa.OpPtrIndex, pt, p2, s.constInt(Types[TINT], int64(i)))
2222-
if store[i] {
2231+
if arg.store {
22232232
if haspointers(et) {
2224-
s.insertWBstore(et, addr, arg, n.Lineno, 0)
2233+
s.insertWBstore(et, addr, arg.v, n.Lineno, 0)
22252234
} else {
2226-
s.vars[&memVar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, et.Size(), addr, arg, s.mem())
2235+
s.vars[&memVar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, et.Size(), addr, arg.v, s.mem())
22272236
}
22282237
} else {
22292238
if haspointers(et) {
2230-
s.insertWBmove(et, addr, arg, n.Lineno)
2239+
s.insertWBmove(et, addr, arg.v, n.Lineno, arg.isVolatile)
22312240
} else {
2232-
s.vars[&memVar] = s.newValue3I(ssa.OpMove, ssa.TypeMem, et.Size(), addr, arg, s.mem())
2241+
s.vars[&memVar] = s.newValue3I(ssa.OpMove, ssa.TypeMem, et.Size(), addr, arg.v, s.mem())
22332242
}
22342243
}
22352244
}
@@ -2301,9 +2310,10 @@ const (
23012310
// Right has already been evaluated to ssa, left has not.
23022311
// If deref is true, then we do left = *right instead (and right has already been nil-checked).
23032312
// If deref is true and right == nil, just do left = 0.
2313+
// If deref is true, rightIsVolatile reports whether right points to volatile (clobbered by a call) storage.
23042314
// Include a write barrier if wb is true.
23052315
// skip indicates assignments (at the top level) that can be avoided.
2306-
func (s *state) assign(left *Node, right *ssa.Value, wb, deref bool, line int32, skip skipMask) {
2316+
func (s *state) assign(left *Node, right *ssa.Value, wb, deref bool, line int32, skip skipMask, rightIsVolatile bool) {
23072317
if left.Op == ONAME && isblank(left) {
23082318
return
23092319
}
@@ -2344,7 +2354,7 @@ func (s *state) assign(left *Node, right *ssa.Value, wb, deref bool, line int32,
23442354
}
23452355

23462356
// Recursively assign the new value we've made to the base of the dot op.
2347-
s.assign(left.Left, new, false, false, line, 0)
2357+
s.assign(left.Left, new, false, false, line, 0, rightIsVolatile)
23482358
// TODO: do we need to update named values here?
23492359
return
23502360
}
@@ -2354,7 +2364,7 @@ func (s *state) assign(left *Node, right *ssa.Value, wb, deref bool, line int32,
23542364
return
23552365
}
23562366
// Left is not ssa-able. Compute its address.
2357-
addr := s.addr(left, false)
2367+
addr, _ := s.addr(left, false)
23582368
if left.Op == ONAME && skip == 0 {
23592369
s.vars[&memVar] = s.newValue1A(ssa.OpVarDef, ssa.TypeMem, left, s.mem())
23602370
}
@@ -2365,7 +2375,7 @@ func (s *state) assign(left *Node, right *ssa.Value, wb, deref bool, line int32,
23652375
return
23662376
}
23672377
if wb {
2368-
s.insertWBmove(t, addr, right, line)
2378+
s.insertWBmove(t, addr, right, line, rightIsVolatile)
23692379
return
23702380
}
23712381
s.vars[&memVar] = s.newValue3I(ssa.OpMove, ssa.TypeMem, t.Size(), addr, right, s.mem())
@@ -2684,10 +2694,12 @@ func (s *state) lookupSymbol(n *Node, sym interface{}) interface{} {
26842694
}
26852695

26862696
// addr converts the address of the expression n to SSA, adds it to s and returns the SSA result.
2697+
// Also returns a bool reporting whether the returned value is "volatile", that is it
2698+
// points to the outargs section and thus the referent will be clobbered by any call.
26872699
// The value that the returned Value represents is guaranteed to be non-nil.
26882700
// If bounded is true then this address does not require a nil check for its operand
26892701
// even if that would otherwise be implied.
2690-
func (s *state) addr(n *Node, bounded bool) *ssa.Value {
2702+
func (s *state) addr(n *Node, bounded bool) (*ssa.Value, bool) {
26912703
t := Ptrto(n.Type)
26922704
switch n.Op {
26932705
case ONAME:
@@ -2700,41 +2712,41 @@ func (s *state) addr(n *Node, bounded bool) *ssa.Value {
27002712
if n.Xoffset != 0 {
27012713
v = s.entryNewValue1I(ssa.OpOffPtr, v.Type, n.Xoffset, v)
27022714
}
2703-
return v
2715+
return v, false
27042716
case PPARAM:
27052717
// parameter slot
27062718
v := s.decladdrs[n]
27072719
if v != nil {
2708-
return v
2720+
return v, false
27092721
}
27102722
if n.String() == ".fp" {
27112723
// Special arg that points to the frame pointer.
27122724
// (Used by the race detector, others?)
27132725
aux := s.lookupSymbol(n, &ssa.ArgSymbol{Typ: n.Type, Node: n})
2714-
return s.entryNewValue1A(ssa.OpAddr, t, aux, s.sp)
2726+
return s.entryNewValue1A(ssa.OpAddr, t, aux, s.sp), false
27152727
}
27162728
s.Fatalf("addr of undeclared ONAME %v. declared: %v", n, s.decladdrs)
2717-
return nil
2729+
return nil, false
27182730
case PAUTO:
27192731
aux := s.lookupSymbol(n, &ssa.AutoSymbol{Typ: n.Type, Node: n})
2720-
return s.newValue1A(ssa.OpAddr, t, aux, s.sp)
2732+
return s.newValue1A(ssa.OpAddr, t, aux, s.sp), false
27212733
case PPARAMOUT: // Same as PAUTO -- cannot generate LEA early.
27222734
// ensure that we reuse symbols for out parameters so
27232735
// that cse works on their addresses
27242736
aux := s.lookupSymbol(n, &ssa.ArgSymbol{Typ: n.Type, Node: n})
2725-
return s.newValue1A(ssa.OpAddr, t, aux, s.sp)
2737+
return s.newValue1A(ssa.OpAddr, t, aux, s.sp), false
27262738
default:
27272739
s.Unimplementedf("variable address class %v not implemented", classnames[n.Class])
2728-
return nil
2740+
return nil, false
27292741
}
27302742
case OINDREG:
27312743
// indirect off a register
27322744
// used for storing/loading arguments/returns to/from callees
27332745
if int(n.Reg) != Thearch.REGSP {
27342746
s.Unimplementedf("OINDREG of non-SP register %s in addr: %v", obj.Rconv(int(n.Reg)), n)
2735-
return nil
2747+
return nil, false
27362748
}
2737-
return s.entryNewValue1I(ssa.OpOffPtr, t, n.Xoffset, s.sp)
2749+
return s.entryNewValue1I(ssa.OpOffPtr, t, n.Xoffset, s.sp), true
27382750
case OINDEX:
27392751
if n.Left.Type.IsSlice() {
27402752
a := s.expr(n.Left)
@@ -2745,37 +2757,37 @@ func (s *state) addr(n *Node, bounded bool) *ssa.Value {
27452757
s.boundsCheck(i, len)
27462758
}
27472759
p := s.newValue1(ssa.OpSlicePtr, t, a)
2748-
return s.newValue2(ssa.OpPtrIndex, t, p, i)
2760+
return s.newValue2(ssa.OpPtrIndex, t, p, i), false
27492761
} else { // array
2750-
a := s.addr(n.Left, bounded)
2762+
a, isVolatile := s.addr(n.Left, bounded)
27512763
i := s.expr(n.Right)
27522764
i = s.extendIndex(i)
27532765
len := s.constInt(Types[TINT], n.Left.Type.NumElem())
27542766
if !n.Bounded {
27552767
s.boundsCheck(i, len)
27562768
}
2757-
return s.newValue2(ssa.OpPtrIndex, Ptrto(n.Left.Type.Elem()), a, i)
2769+
return s.newValue2(ssa.OpPtrIndex, Ptrto(n.Left.Type.Elem()), a, i), isVolatile
27582770
}
27592771
case OIND:
2760-
return s.exprPtr(n.Left, bounded, n.Lineno)
2772+
return s.exprPtr(n.Left, bounded, n.Lineno), false
27612773
case ODOT:
2762-
p := s.addr(n.Left, bounded)
2763-
return s.newValue1I(ssa.OpOffPtr, t, n.Xoffset, p)
2774+
p, isVolatile := s.addr(n.Left, bounded)
2775+
return s.newValue1I(ssa.OpOffPtr, t, n.Xoffset, p), isVolatile
27642776
case ODOTPTR:
27652777
p := s.exprPtr(n.Left, bounded, n.Lineno)
2766-
return s.newValue1I(ssa.OpOffPtr, t, n.Xoffset, p)
2778+
return s.newValue1I(ssa.OpOffPtr, t, n.Xoffset, p), false
27672779
case OCLOSUREVAR:
27682780
return s.newValue1I(ssa.OpOffPtr, t, n.Xoffset,
2769-
s.entryNewValue0(ssa.OpGetClosurePtr, Ptrto(Types[TUINT8])))
2781+
s.entryNewValue0(ssa.OpGetClosurePtr, Ptrto(Types[TUINT8]))), false
27702782
case OCONVNOP:
2771-
addr := s.addr(n.Left, bounded)
2772-
return s.newValue1(ssa.OpCopy, t, addr) // ensure that addr has the right type
2783+
addr, isVolatile := s.addr(n.Left, bounded)
2784+
return s.newValue1(ssa.OpCopy, t, addr), isVolatile // ensure that addr has the right type
27732785
case OCALLFUNC, OCALLINTER, OCALLMETH:
2774-
return s.call(n, callNormal)
2786+
return s.call(n, callNormal), true
27752787

27762788
default:
27772789
s.Unimplementedf("unhandled addr %v", n.Op)
2778-
return nil
2790+
return nil, false
27792791
}
27802792
}
27812793

@@ -3007,7 +3019,7 @@ func (s *state) rtcall(fn *Node, returns bool, results []*Type, args ...*ssa.Val
30073019

30083020
// insertWBmove inserts the assignment *left = *right including a write barrier.
30093021
// t is the type being assigned.
3010-
func (s *state) insertWBmove(t *Type, left, right *ssa.Value, line int32) {
3022+
func (s *state) insertWBmove(t *Type, left, right *ssa.Value, line int32, rightIsVolatile bool) {
30113023
// if writeBarrier.enabled {
30123024
// typedmemmove(&t, left, right)
30133025
// } else {
@@ -3038,8 +3050,25 @@ func (s *state) insertWBmove(t *Type, left, right *ssa.Value, line int32) {
30383050
b.AddEdgeTo(bElse)
30393051

30403052
s.startBlock(bThen)
3041-
taddr := s.newValue1A(ssa.OpAddr, Types[TUINTPTR], &ssa.ExternSymbol{Typ: Types[TUINTPTR], Sym: typenamesym(t)}, s.sb)
3042-
s.rtcall(typedmemmove, true, nil, taddr, left, right)
3053+
3054+
if !rightIsVolatile {
3055+
// Issue typedmemmove call.
3056+
taddr := s.newValue1A(ssa.OpAddr, Types[TUINTPTR], &ssa.ExternSymbol{Typ: Types[TUINTPTR], Sym: typenamesym(t)}, s.sb)
3057+
s.rtcall(typedmemmove, true, nil, taddr, left, right)
3058+
} else {
3059+
// Copy to temp location if the source is volatile (will be clobbered by
3060+
// a function call). Marshaling the args to typedmemmove might clobber the
3061+
// value we're trying to move.
3062+
tmp := temp(t)
3063+
s.vars[&memVar] = s.newValue1A(ssa.OpVarDef, ssa.TypeMem, tmp, s.mem())
3064+
tmpaddr, _ := s.addr(tmp, true)
3065+
s.vars[&memVar] = s.newValue3I(ssa.OpMove, ssa.TypeMem, t.Size(), tmpaddr, right, s.mem())
3066+
// Issue typedmemmove call.
3067+
taddr := s.newValue1A(ssa.OpAddr, Types[TUINTPTR], &ssa.ExternSymbol{Typ: Types[TUINTPTR], Sym: typenamesym(t)}, s.sb)
3068+
s.rtcall(typedmemmove, true, nil, taddr, left, tmpaddr)
3069+
// Mark temp as dead.
3070+
s.vars[&memVar] = s.newValue1A(ssa.OpVarKill, ssa.TypeMem, tmp, s.mem())
3071+
}
30433072
s.endBlock().AddEdgeTo(bEnd)
30443073

30453074
s.startBlock(bElse)

0 commit comments

Comments
 (0)