Skip to content

Commit 5045fdd

Browse files
randall77gopherbot
authored andcommitted
cmd/compile: fix containsUnavoidableCall computation
The previous algorithm was incorrect, as it reused the dominatedByCall slice without resetting it. It also used the depth fields even though they were not yet calculated. Also, clean up a lot of the loop detector code that we never use. Always compute depths. It is cheap. Update #71868 Not really sure how to test this. As it is just an advisory bit, nothing goes really wrong when the result is incorrect. Change-Id: Ic0ae87a4d3576554831252d88b05b058ca68af41 Reviewed-on: https://go-review.googlesource.com/c/go/+/680775 LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Keith Randall <[email protected]> Reviewed-by: Keith Randall <[email protected]> Reviewed-by: David Chase <[email protected]>
1 parent d28b27c commit 5045fdd

File tree

4 files changed

+103
-178
lines changed

4 files changed

+103
-178
lines changed

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

Lines changed: 33 additions & 175 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,15 @@ type loop struct {
1212
header *Block // The header node of this (reducible) loop
1313
outer *loop // loop containing this loop
1414

15-
// By default, children, exits, and depth are not initialized.
16-
children []*loop // loops nested directly within this loop. Initialized by assembleChildren().
17-
exits []*Block // exits records blocks reached by exits from this loop. Initialized by findExits().
18-
1915
// Next three fields used by regalloc and/or
2016
// aid in computation of inner-ness and list of blocks.
2117
nBlocks int32 // Number of blocks in this loop but not within inner loops
2218
depth int16 // Nesting depth of the loop; 1 is outermost. Initialized by calculateDepths().
2319
isInner bool // True if never discovered to contain a loop
2420

25-
// register allocation uses this.
26-
containsUnavoidableCall bool // True if all paths through the loop have a call
21+
// True if all paths through the loop have a call.
22+
// Computed and used by regalloc; stored here for convenience.
23+
containsUnavoidableCall bool
2724
}
2825

2926
// outerinner records that outer contains inner
@@ -49,28 +46,13 @@ func (sdom SparseTree) outerinner(outer, inner *loop) {
4946
outer.isInner = false
5047
}
5148

52-
func checkContainsCall(bb *Block) bool {
53-
if bb.Kind == BlockDefer {
54-
return true
55-
}
56-
for _, v := range bb.Values {
57-
if opcodeTable[v.Op].call {
58-
return true
59-
}
60-
}
61-
return false
62-
}
63-
6449
type loopnest struct {
6550
f *Func
6651
b2l []*loop
6752
po []*Block
6853
sdom SparseTree
6954
loops []*loop
7055
hasIrreducible bool // TODO current treatment of irreducible loops is very flaky, if accurate loops are needed, must punt at function level.
71-
72-
// Record which of the lazily initialized fields have actually been initialized.
73-
initializedChildren, initializedDepth, initializedExits bool
7456
}
7557

7658
const (
@@ -355,91 +337,59 @@ func loopnestfor(f *Func) *loopnest {
355337
visited[b.ID] = true
356338
}
357339

358-
ln := &loopnest{f: f, b2l: b2l, po: po, sdom: sdom, loops: loops, hasIrreducible: sawIrred}
359-
360-
// Calculate containsUnavoidableCall for regalloc
361-
dominatedByCall := f.Cache.allocBoolSlice(f.NumBlocks())
362-
defer f.Cache.freeBoolSlice(dominatedByCall)
363-
for _, b := range po {
364-
if checkContainsCall(b) {
365-
dominatedByCall[b.ID] = true
366-
}
367-
}
368-
// Run dfs to find path through the loop that avoids all calls.
369-
// Such path either escapes loop or return back to header.
370-
// It isn't enough to have exit not dominated by any call, for example:
371-
// ... some loop
372-
// call1 call2
373-
// \ /
374-
// exit
375-
// ...
376-
// exit is not dominated by any call, but we don't have call-free path to it.
340+
// Compute depths.
377341
for _, l := range loops {
378-
// Header contains call.
379-
if dominatedByCall[l.header.ID] {
380-
l.containsUnavoidableCall = true
342+
if l.depth != 0 {
343+
// Already computed because it is an ancestor of
344+
// a previous loop.
381345
continue
382346
}
383-
callfreepath := false
384-
tovisit := make([]*Block, 0, len(l.header.Succs))
385-
// Push all non-loop non-exit successors of header onto toVisit.
386-
for _, s := range l.header.Succs {
387-
nb := s.Block()
388-
// This corresponds to loop with zero iterations.
389-
if !l.iterationEnd(nb, b2l) {
390-
tovisit = append(tovisit, nb)
347+
// Find depth by walking up the loop tree.
348+
d := int16(0)
349+
for x := l; x != nil; x = x.outer {
350+
if x.depth != 0 {
351+
d += x.depth
352+
break
391353
}
354+
d++
392355
}
393-
for len(tovisit) > 0 {
394-
cur := tovisit[len(tovisit)-1]
395-
tovisit = tovisit[:len(tovisit)-1]
396-
if dominatedByCall[cur.ID] {
397-
continue
398-
}
399-
// Record visited in dominatedByCall.
400-
dominatedByCall[cur.ID] = true
401-
for _, s := range cur.Succs {
402-
nb := s.Block()
403-
if l.iterationEnd(nb, b2l) {
404-
callfreepath = true
405-
}
406-
if !dominatedByCall[nb.ID] {
407-
tovisit = append(tovisit, nb)
408-
}
409-
410-
}
411-
if callfreepath {
356+
// Set depth for every ancestor.
357+
for x := l; x != nil; x = x.outer {
358+
if x.depth != 0 {
412359
break
413360
}
361+
x.depth = d
362+
d--
363+
}
364+
}
365+
// Double-check depths.
366+
for _, l := range loops {
367+
want := int16(1)
368+
if l.outer != nil {
369+
want = l.outer.depth + 1
414370
}
415-
if !callfreepath {
416-
l.containsUnavoidableCall = true
371+
if l.depth != want {
372+
l.header.Fatalf("bad depth calculation for loop %s: got %d want %d", l.header, l.depth, want)
417373
}
418374
}
419375

376+
ln := &loopnest{f: f, b2l: b2l, po: po, sdom: sdom, loops: loops, hasIrreducible: sawIrred}
377+
420378
// Curious about the loopiness? "-d=ssa/likelyadjust/stats"
421379
if f.pass != nil && f.pass.stats > 0 && len(loops) > 0 {
422-
ln.assembleChildren()
423-
ln.calculateDepths()
424-
ln.findExits()
425380

426381
// Note stats for non-innermost loops are slightly flawed because
427382
// they don't account for inner loop exits that span multiple levels.
428383

429384
for _, l := range loops {
430-
x := len(l.exits)
431-
cf := 0
432-
if !l.containsUnavoidableCall {
433-
cf = 1
434-
}
435385
inner := 0
436386
if l.isInner {
437387
inner++
438388
}
439389

440-
f.LogStat("loopstats:",
441-
l.depth, "depth", x, "exits",
442-
inner, "is_inner", cf, "always_calls", l.nBlocks, "n_blocks")
390+
f.LogStat("loopstats in "+f.Name+":",
391+
l.depth, "depth",
392+
inner, "is_inner", l.nBlocks, "n_blocks")
443393
}
444394
}
445395

@@ -465,102 +415,10 @@ func loopnestfor(f *Func) *loopnest {
465415
return ln
466416
}
467417

468-
// assembleChildren initializes the children field of each
469-
// loop in the nest. Loop A is a child of loop B if A is
470-
// directly nested within B (based on the reducible-loops
471-
// detection above)
472-
func (ln *loopnest) assembleChildren() {
473-
if ln.initializedChildren {
474-
return
475-
}
476-
for _, l := range ln.loops {
477-
if l.outer != nil {
478-
l.outer.children = append(l.outer.children, l)
479-
}
480-
}
481-
ln.initializedChildren = true
482-
}
483-
484-
// calculateDepths uses the children field of loops
485-
// to determine the nesting depth (outer=1) of each
486-
// loop. This is helpful for finding exit edges.
487-
func (ln *loopnest) calculateDepths() {
488-
if ln.initializedDepth {
489-
return
490-
}
491-
ln.assembleChildren()
492-
for _, l := range ln.loops {
493-
if l.outer == nil {
494-
l.setDepth(1)
495-
}
496-
}
497-
ln.initializedDepth = true
498-
}
499-
500-
// findExits uses loop depth information to find the
501-
// exits from a loop.
502-
func (ln *loopnest) findExits() {
503-
if ln.initializedExits {
504-
return
505-
}
506-
ln.calculateDepths()
507-
b2l := ln.b2l
508-
for _, b := range ln.po {
509-
l := b2l[b.ID]
510-
if l != nil && len(b.Succs) == 2 {
511-
sl := b2l[b.Succs[0].b.ID]
512-
if recordIfExit(l, sl, b.Succs[0].b) {
513-
continue
514-
}
515-
sl = b2l[b.Succs[1].b.ID]
516-
if recordIfExit(l, sl, b.Succs[1].b) {
517-
continue
518-
}
519-
}
520-
}
521-
ln.initializedExits = true
522-
}
523-
524418
// depth returns the loop nesting level of block b.
525419
func (ln *loopnest) depth(b ID) int16 {
526420
if l := ln.b2l[b]; l != nil {
527421
return l.depth
528422
}
529423
return 0
530424
}
531-
532-
// recordIfExit checks sl (the loop containing b) to see if it
533-
// is outside of loop l, and if so, records b as an exit block
534-
// from l and returns true.
535-
func recordIfExit(l, sl *loop, b *Block) bool {
536-
if sl != l {
537-
if sl == nil || sl.depth <= l.depth {
538-
l.exits = append(l.exits, b)
539-
return true
540-
}
541-
// sl is not nil, and is deeper than l
542-
// it's possible for this to be a goto into an irreducible loop made from gotos.
543-
for sl.depth > l.depth {
544-
sl = sl.outer
545-
}
546-
if sl != l {
547-
l.exits = append(l.exits, b)
548-
return true
549-
}
550-
}
551-
return false
552-
}
553-
554-
func (l *loop) setDepth(d int16) {
555-
l.depth = d
556-
for _, c := range l.children {
557-
c.setDepth(d + 1)
558-
}
559-
}
560-
561-
// iterationEnd checks if block b ends iteration of loop l.
562-
// Ending iteration means either escaping to outer loop/code or
563-
// going back to header
564-
func (l *loop) iterationEnd(b *Block, b2l []*loop) bool {
565-
return b == l.header || b2l[b.ID] == nil || (b2l[b.ID] != l && b2l[b.ID].depth <= l.depth)
566-
}

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

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2735,7 +2735,7 @@ func (s *regAllocState) computeLive() {
27352735
// out to all of them.
27362736
po := f.postorder()
27372737
s.loopnest = f.loopnest()
2738-
s.loopnest.calculateDepths()
2738+
s.loopnest.computeUnavoidableCalls()
27392739
for {
27402740
changed := false
27412741

@@ -3042,3 +3042,72 @@ func (d *desiredState) merge(x *desiredState) {
30423042
d.addList(e.ID, e.regs)
30433043
}
30443044
}
3045+
3046+
// computeUnavoidableCalls computes the containsUnavoidableCall fields in the loop nest.
3047+
func (loopnest *loopnest) computeUnavoidableCalls() {
3048+
f := loopnest.f
3049+
3050+
hasCall := f.Cache.allocBoolSlice(f.NumBlocks())
3051+
defer f.Cache.freeBoolSlice(hasCall)
3052+
for _, b := range f.Blocks {
3053+
if b.containsCall() {
3054+
hasCall[b.ID] = true
3055+
}
3056+
}
3057+
found := f.Cache.allocSparseSet(f.NumBlocks())
3058+
defer f.Cache.freeSparseSet(found)
3059+
// Run dfs to find path through the loop that avoids all calls.
3060+
// Such path either escapes the loop or returns back to the header.
3061+
// It isn't enough to have exit not dominated by any call, for example:
3062+
// ... some loop
3063+
// call1 call2
3064+
// \ /
3065+
// block
3066+
// ...
3067+
// block is not dominated by any single call, but we don't have call-free path to it.
3068+
loopLoop:
3069+
for _, l := range loopnest.loops {
3070+
found.clear()
3071+
tovisit := make([]*Block, 0, 8)
3072+
tovisit = append(tovisit, l.header)
3073+
for len(tovisit) > 0 {
3074+
cur := tovisit[len(tovisit)-1]
3075+
tovisit = tovisit[:len(tovisit)-1]
3076+
if hasCall[cur.ID] {
3077+
continue
3078+
}
3079+
for _, s := range cur.Succs {
3080+
nb := s.Block()
3081+
if nb == l.header {
3082+
// Found a call-free path around the loop.
3083+
continue loopLoop
3084+
}
3085+
if found.contains(nb.ID) {
3086+
// Already found via another path.
3087+
continue
3088+
}
3089+
nl := loopnest.b2l[nb.ID]
3090+
if nl == nil || (nl.depth <= l.depth && nl != l) {
3091+
// Left the loop.
3092+
continue
3093+
}
3094+
tovisit = append(tovisit, nb)
3095+
found.add(nb.ID)
3096+
}
3097+
}
3098+
// No call-free path was found.
3099+
l.containsUnavoidableCall = true
3100+
}
3101+
}
3102+
3103+
func (b *Block) containsCall() bool {
3104+
if b.Kind == BlockDefer {
3105+
return true
3106+
}
3107+
for _, v := range b.Values {
3108+
if opcodeTable[v.Op].call {
3109+
return true
3110+
}
3111+
}
3112+
return false
3113+
}

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,6 @@ func canMergeLoadClobber(target, load, x *Value) bool {
303303
return false
304304
}
305305
loopnest := x.Block.Func.loopnest()
306-
loopnest.calculateDepths()
307306
if loopnest.depth(target.Block.ID) > loopnest.depth(x.Block.ID) {
308307
return false
309308
}

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ func tighten(f *Func) {
8282
// We use this to make sure we don't tighten a value into a (deeper) loop.
8383
idom := f.Idom()
8484
loops := f.loopnest()
85-
loops.calculateDepths()
8685

8786
changed := true
8887
for changed {

0 commit comments

Comments
 (0)