Skip to content

Commit bff39cf

Browse files
committed
cmd/compile: add automated rewrite cycle detection
A common bug during development is to introduce rewrite rule cycles. This is annoying because it takes a while to notice that make.bash is a bit too slow this time, and to remember why. And then you have to manually arrange to debug. Make this all easier by automating it. Detect cycles, and when we detect one, print the sequence of rewrite rules that occur within a single cycle before crashing. Change-Id: I8dadda13990ab925a81940d4833c9e5243368435 Reviewed-on: https://go-review.googlesource.com/c/go/+/347829 Trust: Josh Bleecher Snyder <[email protected]> Run-TryBot: Josh Bleecher Snyder <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Cherry Mui <[email protected]>
1 parent b61e1ed commit bff39cf

File tree

3 files changed

+52
-8
lines changed

3 files changed

+52
-8
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1221,7 +1221,7 @@ func (p htmlFuncPrinter) startBlock(b *Block, reachable bool) {
12211221
}
12221222
}
12231223

1224-
func (p htmlFuncPrinter) endBlock(b *Block) {
1224+
func (p htmlFuncPrinter) endBlock(b *Block, reachable bool) {
12251225
if len(b.Values) > 0 { // end list of values
12261226
io.WriteString(p.w, "</ul>")
12271227
io.WriteString(p.w, "</li>")

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

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,30 +17,39 @@ func printFunc(f *Func) {
1717

1818
func hashFunc(f *Func) []byte {
1919
h := sha256.New()
20-
p := stringFuncPrinter{w: h}
20+
p := stringFuncPrinter{w: h, printDead: true}
2121
fprintFunc(p, f)
2222
return h.Sum(nil)
2323
}
2424

2525
func (f *Func) String() string {
2626
var buf bytes.Buffer
27-
p := stringFuncPrinter{w: &buf}
27+
p := stringFuncPrinter{w: &buf, printDead: true}
2828
fprintFunc(p, f)
2929
return buf.String()
3030
}
3131

32+
// rewriteHash returns a hash of f suitable for detecting rewrite cycles.
33+
func (f *Func) rewriteHash() string {
34+
h := sha256.New()
35+
p := stringFuncPrinter{w: h, printDead: false}
36+
fprintFunc(p, f)
37+
return fmt.Sprintf("%x", h.Sum(nil))
38+
}
39+
3240
type funcPrinter interface {
3341
header(f *Func)
3442
startBlock(b *Block, reachable bool)
35-
endBlock(b *Block)
43+
endBlock(b *Block, reachable bool)
3644
value(v *Value, live bool)
3745
startDepCycle()
3846
endDepCycle()
3947
named(n LocalSlot, vals []*Value)
4048
}
4149

4250
type stringFuncPrinter struct {
43-
w io.Writer
51+
w io.Writer
52+
printDead bool
4453
}
4554

4655
func (p stringFuncPrinter) header(f *Func) {
@@ -50,6 +59,9 @@ func (p stringFuncPrinter) header(f *Func) {
5059
}
5160

5261
func (p stringFuncPrinter) startBlock(b *Block, reachable bool) {
62+
if !p.printDead && !reachable {
63+
return
64+
}
5365
fmt.Fprintf(p.w, " b%d:", b.ID)
5466
if len(b.Preds) > 0 {
5567
io.WriteString(p.w, " <-")
@@ -64,11 +76,17 @@ func (p stringFuncPrinter) startBlock(b *Block, reachable bool) {
6476
io.WriteString(p.w, "\n")
6577
}
6678

67-
func (p stringFuncPrinter) endBlock(b *Block) {
79+
func (p stringFuncPrinter) endBlock(b *Block, reachable bool) {
80+
if !p.printDead && !reachable {
81+
return
82+
}
6883
fmt.Fprintln(p.w, " "+b.LongString())
6984
}
7085

7186
func (p stringFuncPrinter) value(v *Value, live bool) {
87+
if !p.printDead && !live {
88+
return
89+
}
7290
fmt.Fprint(p.w, " ")
7391
//fmt.Fprint(p.w, v.Block.Func.fe.Pos(v.Pos))
7492
//fmt.Fprint(p.w, ": ")
@@ -103,7 +121,7 @@ func fprintFunc(p funcPrinter, f *Func) {
103121
p.value(v, live[v.ID])
104122
printed[v.ID] = true
105123
}
106-
p.endBlock(b)
124+
p.endBlock(b, reachable[b.ID])
107125
continue
108126
}
109127

@@ -151,7 +169,7 @@ func fprintFunc(p funcPrinter, f *Func) {
151169
}
152170
}
153171

154-
p.endBlock(b)
172+
p.endBlock(b, reachable[b.ID])
155173
}
156174
for _, name := range f.Names {
157175
p.named(*name, f.NamedValues[*name])

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ func applyRewrite(f *Func, rb blockRewriter, rv valueRewriter, deadcode deadValu
3636
if debug > 1 {
3737
fmt.Printf("%s: rewriting for %s\n", f.pass.name, f.Name)
3838
}
39+
var iters int
40+
var states map[string]bool
3941
for {
4042
change := false
4143
for _, b := range f.Blocks {
@@ -146,6 +148,30 @@ func applyRewrite(f *Func, rb blockRewriter, rv valueRewriter, deadcode deadValu
146148
if !change {
147149
break
148150
}
151+
iters++
152+
if iters > 1000 || debug >= 2 {
153+
// We've done a suspiciously large number of rewrites (or we're in debug mode).
154+
// As of Sep 2021, 90% of rewrites complete in 4 iterations or fewer
155+
// and the maximum value encountered during make.bash is 12.
156+
// Start checking for cycles. (This is too expensive to do routinely.)
157+
if states == nil {
158+
states = make(map[string]bool)
159+
}
160+
h := f.rewriteHash()
161+
if _, ok := states[h]; ok {
162+
// We've found a cycle.
163+
// To diagnose it, set debug to 2 and start again,
164+
// so that we'll print all rules applied until we complete another cycle.
165+
// If debug is already >= 2, we've already done that, so it's time to crash.
166+
if debug < 2 {
167+
debug = 2
168+
states = make(map[string]bool)
169+
} else {
170+
f.Fatalf("rewrite cycle detected")
171+
}
172+
}
173+
states[h] = true
174+
}
149175
}
150176
// remove clobbered values
151177
for _, b := range f.Blocks {

0 commit comments

Comments
 (0)