Skip to content

Commit f9e61a9

Browse files
committed
cmd/compile: duplicate nil check to two branches of write barrier
Currently, for a write of a pointer (e.g. *p = x where x is a pointer), we generate an explicit nil check of p, despite that the store instruction may have the same faulting behavior as the nil check. This is because the write needs a write barrier, which creates a control flow, which prevents the nil check being removed as it is in a differnt block as the actual store. This CL duplicates the nil check to two branches, so it is likely that they will be followed by the actual store and the write barrier load, which may have the same faulting behavior, so they can be removed. Change-Id: Ied9480de5dd6a8fcbd5affc5f6e029944943cc07 Reviewed-on: https://go-review.googlesource.com/c/go/+/705156 Reviewed-by: Keith Randall <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Keith Randall <[email protected]>
1 parent 3cf1aaf commit f9e61a9

File tree

4 files changed

+51
-9
lines changed

4 files changed

+51
-9
lines changed

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

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,15 @@ func writebarrier(f *Func) {
303303
mem := stores[0].MemoryArg()
304304
pos := stores[0].Pos
305305

306+
// If there is a nil check before the WB store, duplicate it to
307+
// the two branches, where the store and the WB load occur. So
308+
// they are more likely be removed by late nilcheck removal (which
309+
// is block-local).
310+
var nilcheck, nilcheckThen, nilcheckEnd *Value
311+
if a := stores[0].Args[0]; a.Op == OpNilCheck && a.Args[1] == mem {
312+
nilcheck = a
313+
}
314+
306315
// If the source of a MoveWB is volatile (will be clobbered by a
307316
// function call), we need to copy it to a temporary location, as
308317
// marshaling the args of wbMove might clobber the value we're
@@ -377,6 +386,10 @@ func writebarrier(f *Func) {
377386
// For each write barrier store, append write barrier code to bThen.
378387
memThen := mem
379388

389+
if nilcheck != nil {
390+
nilcheckThen = bThen.NewValue2(nilcheck.Pos, OpNilCheck, nilcheck.Type, nilcheck.Args[0], memThen)
391+
}
392+
380393
// Note: we can issue the write barrier code in any order. In particular,
381394
// it doesn't matter if they are in a different order *even if* they end
382395
// up referring to overlapping memory regions. For instance if an OpStore
@@ -447,6 +460,9 @@ func writebarrier(f *Func) {
447460
// take care of the vast majority of these. We could
448461
// patch this up in the signal handler, or use XCHG to
449462
// combine the read and the write.
463+
if ptr == nilcheck {
464+
ptr = nilcheckThen
465+
}
450466
oldVal := bThen.NewValue2(pos, OpLoad, types.Types[types.TUINTPTR], ptr, memThen)
451467
// Save old value to write buffer.
452468
addEntry(pos, oldVal)
@@ -459,17 +475,19 @@ func writebarrier(f *Func) {
459475
// Now do the rare cases, Zeros and Moves.
460476
for _, w := range stores {
461477
pos := w.Pos
478+
dst := w.Args[0]
479+
if dst == nilcheck {
480+
dst = nilcheckThen
481+
}
462482
switch w.Op {
463483
case OpZeroWB:
464-
dst := w.Args[0]
465484
typ := reflectdata.TypeLinksym(w.Aux.(*types.Type))
466485
// zeroWB(&typ, dst)
467486
taddr := b.NewValue1A(pos, OpAddr, b.Func.Config.Types.Uintptr, typ, sb)
468487
memThen = wbcall(pos, bThen, wbZero, sp, memThen, taddr, dst)
469488
f.fe.Func().SetWBPos(pos)
470489
nWBops--
471490
case OpMoveWB:
472-
dst := w.Args[0]
473491
src := w.Args[1]
474492
if isVolatile(src) {
475493
for _, c := range volatiles {
@@ -491,24 +509,29 @@ func writebarrier(f *Func) {
491509
// merge memory
492510
mem = bEnd.NewValue2(pos, OpPhi, types.TypeMem, mem, memThen)
493511

512+
if nilcheck != nil {
513+
nilcheckEnd = bEnd.NewValue2(nilcheck.Pos, OpNilCheck, nilcheck.Type, nilcheck.Args[0], mem)
514+
}
515+
494516
// Do raw stores after merge point.
495517
for _, w := range stores {
496518
pos := w.Pos
519+
dst := w.Args[0]
520+
if dst == nilcheck {
521+
dst = nilcheckEnd
522+
}
497523
switch w.Op {
498524
case OpStoreWB:
499-
ptr := w.Args[0]
500525
val := w.Args[1]
501526
if buildcfg.Experiment.CgoCheck2 {
502527
// Issue cgo checking code.
503-
mem = wbcall(pos, bEnd, cgoCheckPtrWrite, sp, mem, ptr, val)
528+
mem = wbcall(pos, bEnd, cgoCheckPtrWrite, sp, mem, dst, val)
504529
}
505-
mem = bEnd.NewValue3A(pos, OpStore, types.TypeMem, w.Aux, ptr, val, mem)
530+
mem = bEnd.NewValue3A(pos, OpStore, types.TypeMem, w.Aux, dst, val, mem)
506531
case OpZeroWB:
507-
dst := w.Args[0]
508532
mem = bEnd.NewValue2I(pos, OpZero, types.TypeMem, w.AuxInt, dst, mem)
509533
mem.Aux = w.Aux
510534
case OpMoveWB:
511-
dst := w.Args[0]
512535
src := w.Args[1]
513536
if isVolatile(src) {
514537
for _, c := range volatiles {
@@ -529,9 +552,8 @@ func writebarrier(f *Func) {
529552
case OpVarDef, OpVarLive:
530553
mem = bEnd.NewValue1A(pos, w.Op, types.TypeMem, w.Aux, mem)
531554
case OpStore:
532-
ptr := w.Args[0]
533555
val := w.Args[1]
534-
mem = bEnd.NewValue3A(pos, OpStore, types.TypeMem, w.Aux, ptr, val, mem)
556+
mem = bEnd.NewValue3A(pos, OpStore, types.TypeMem, w.Aux, dst, val, mem)
535557
}
536558
}
537559

@@ -557,6 +579,9 @@ func writebarrier(f *Func) {
557579
f.freeValue(w)
558580
}
559581
}
582+
if nilcheck != nil && nilcheck.Uses == 0 {
583+
nilcheck.reset(OpInvalid)
584+
}
560585

561586
// put values after the store sequence into the end block
562587
bEnd.Values = append(bEnd.Values, after...)

test/nilptr5.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,9 @@ func f6(p, q *T) {
3030
func f8(t *struct{ b [8]int }) struct{ b [8]int } {
3131
return *t // ERROR "removed nil check"
3232
}
33+
34+
// nil check is removed for pointer write (which involves a
35+
// write barrier).
36+
func f9(x **int, y *int) {
37+
*x = y // ERROR "removed nil check"
38+
}

test/nilptr5_aix.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,9 @@ func f6(p, q *T) {
3030
func f8(t *[8]int) [8]int {
3131
return *t // ERROR "generated nil check"
3232
}
33+
34+
// On AIX, a write nil check is removed, but a read nil check
35+
// remains (for the write barrier).
36+
func f9(x **int, y *int) {
37+
*x = y // ERROR "generated nil check" "removed nil check"
38+
}

test/nilptr5_wasm.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,8 @@ func f6(p, q *T) {
3030
func f8(t *[8]int) [8]int {
3131
return *t // ERROR "generated nil check"
3232
}
33+
34+
// nil check is not removed on Wasm.
35+
func f9(x **int, y *int) {
36+
*x = y // ERROR "generated nil check"
37+
}

0 commit comments

Comments
 (0)