Skip to content

Commit 6adaf17

Browse files
committed
cmd/compile: preserve statements in late nilcheckelim optimization
When a subsequent load/store of a ptr makes the nil check of that pointer unnecessary, if their lines differ, change the line of the load/store to that of the nilcheck, and attempt to rehome the load/store position instead. This fix makes profiling less accurate in order to make panics more informative. Fixes #33724 Change-Id: Ib9afaac12fe0d0320aea1bf493617facc34034b3 Reviewed-on: https://go-review.googlesource.com/c/go/+/200197 Run-TryBot: David Chase <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Keith Randall <[email protected]>
1 parent c2c2ba2 commit 6adaf17

File tree

4 files changed

+65
-8
lines changed

4 files changed

+65
-8
lines changed

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

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,8 @@ var faultOnLoad = objabi.GOOS != "aix"
199199
// nilcheckelim2 eliminates unnecessary nil checks.
200200
// Runs after lowering and scheduling.
201201
func nilcheckelim2(f *Func) {
202-
unnecessary := f.newSparseSet(f.NumValues())
203-
defer f.retSparseSet(unnecessary)
202+
unnecessary := f.newSparseMap(f.NumValues()) // map from pointer that will be dereferenced to index of dereferencing value in b.Values[]
203+
defer f.retSparseMap(unnecessary)
204204

205205
pendingLines := f.cachedLineStarts // Holds statement boundaries that need to be moved to a new value/block
206206

@@ -218,9 +218,21 @@ func nilcheckelim2(f *Func) {
218218
if f.fe.Debug_checknil() && v.Pos.Line() > 1 {
219219
f.Warnl(v.Pos, "removed nil check")
220220
}
221-
if v.Pos.IsStmt() == src.PosIsStmt {
221+
// For bug 33724, policy is that we might choose to bump an existing position
222+
// off the faulting load/store in favor of the one from the nil check.
223+
224+
// Iteration order means that first nilcheck in the chain wins, others
225+
// are bumped into the ordinary statement preservation algorithm.
226+
u := b.Values[unnecessary.get(v.Args[0].ID)]
227+
if !u.Pos.SameFileAndLine(v.Pos) {
228+
if u.Pos.IsStmt() == src.PosIsStmt {
229+
pendingLines.add(u.Pos)
230+
}
231+
u.Pos = v.Pos
232+
} else if v.Pos.IsStmt() == src.PosIsStmt {
222233
pendingLines.add(v.Pos)
223234
}
235+
224236
v.reset(OpUnknown)
225237
firstToRemove = i
226238
continue
@@ -294,15 +306,15 @@ func nilcheckelim2(f *Func) {
294306
}
295307
// This instruction is guaranteed to fault if ptr is nil.
296308
// Any previous nil check op is unnecessary.
297-
unnecessary.add(ptr.ID)
309+
unnecessary.set(ptr.ID, int32(i), src.NoXPos)
298310
}
299311
}
300312
// Remove values we've clobbered with OpUnknown.
301313
i := firstToRemove
302314
for j := i; j < len(b.Values); j++ {
303315
v := b.Values[j]
304316
if v.Op != OpUnknown {
305-
if v.Pos.IsStmt() != src.PosNotStmt && pendingLines.contains(v.Pos) {
317+
if !notStmtBoundary(v.Op) && pendingLines.contains(v.Pos) { // Late in compilation, so any remaining NotStmt values are probably okay now.
306318
v.Pos = v.Pos.WithIsStmt()
307319
pendingLines.remove(v.Pos)
308320
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func nextGoodStatementIndex(v *Value, i int, b *Block) int {
7474
// rewrite.
7575
func notStmtBoundary(op Op) bool {
7676
switch op {
77-
case OpCopy, OpPhi, OpVarKill, OpVarDef, OpUnknown, OpFwdRef, OpArg:
77+
case OpCopy, OpPhi, OpVarKill, OpVarDef, OpVarLive, OpUnknown, OpFwdRef, OpArg:
7878
return true
7979
}
8080
return false

test/codegen/memcombine.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,8 +321,8 @@ func fcall_uint32(a, b uint32) (uint32, uint32) {
321321
// We want to merge load+op in the first function, but not in the
322322
// second. See Issue 19595.
323323
func load_op_merge(p, q *int) {
324-
x := *p
325-
*q += x // amd64:`ADDQ\t\(`
324+
x := *p // amd64:`ADDQ\t\(`
325+
*q += x // The combined nilcheck and load would normally have this line number, but we want that combined operation to have the line number of the nil check instead (see #33724).
326326
}
327327
func load_op_no_merge(p, q *int) {
328328
x := *p

test/fixedbugs/issue33724.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// run
2+
package main
3+
4+
import (
5+
"fmt"
6+
"runtime/debug"
7+
"strings"
8+
)
9+
10+
type Inner struct {
11+
Err int
12+
}
13+
14+
func (i *Inner) NotExpectedInStackTrace() int {
15+
if i == nil {
16+
return 86
17+
}
18+
return 17 + i.Err
19+
}
20+
21+
type Outer struct {
22+
Inner
23+
}
24+
25+
func ExpectedInStackTrace() {
26+
var o *Outer
27+
println(o.NotExpectedInStackTrace())
28+
}
29+
30+
func main() {
31+
defer func() {
32+
if r := recover(); r != nil {
33+
stacktrace := string(debug.Stack())
34+
if strings.Contains(stacktrace, "NotExpectedInStackTrace") {
35+
fmt.Println("FAIL, stacktrace contains NotExpectedInStackTrace")
36+
}
37+
if !strings.Contains(stacktrace, "ExpectedInStackTrace") {
38+
fmt.Println("FAIL, stacktrace does not contain ExpectedInStackTrace")
39+
}
40+
} else {
41+
fmt.Println("FAIL, should have panicked but did not")
42+
}
43+
}()
44+
ExpectedInStackTrace()
45+
}

0 commit comments

Comments
 (0)