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 <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Keith Randall <khr@golang.org>
Reviewed-by: Keith Randall <khr@google.com>
Reviewed-by: David Chase <drchase@google.com>
This commit is contained in:
Keith Randall 2025-06-10 14:37:47 -07:00 committed by Gopher Robot
parent d28b27cd8e
commit 5045fdd8ff
4 changed files with 105 additions and 180 deletions

View file

@ -2735,7 +2735,7 @@ func (s *regAllocState) computeLive() {
// out to all of them.
po := f.postorder()
s.loopnest = f.loopnest()
s.loopnest.calculateDepths()
s.loopnest.computeUnavoidableCalls()
for {
changed := false
@ -3042,3 +3042,72 @@ func (d *desiredState) merge(x *desiredState) {
d.addList(e.ID, e.regs)
}
}
// computeUnavoidableCalls computes the containsUnavoidableCall fields in the loop nest.
func (loopnest *loopnest) computeUnavoidableCalls() {
f := loopnest.f
hasCall := f.Cache.allocBoolSlice(f.NumBlocks())
defer f.Cache.freeBoolSlice(hasCall)
for _, b := range f.Blocks {
if b.containsCall() {
hasCall[b.ID] = true
}
}
found := f.Cache.allocSparseSet(f.NumBlocks())
defer f.Cache.freeSparseSet(found)
// Run dfs to find path through the loop that avoids all calls.
// Such path either escapes the loop or returns back to the header.
// It isn't enough to have exit not dominated by any call, for example:
// ... some loop
// call1 call2
// \ /
// block
// ...
// block is not dominated by any single call, but we don't have call-free path to it.
loopLoop:
for _, l := range loopnest.loops {
found.clear()
tovisit := make([]*Block, 0, 8)
tovisit = append(tovisit, l.header)
for len(tovisit) > 0 {
cur := tovisit[len(tovisit)-1]
tovisit = tovisit[:len(tovisit)-1]
if hasCall[cur.ID] {
continue
}
for _, s := range cur.Succs {
nb := s.Block()
if nb == l.header {
// Found a call-free path around the loop.
continue loopLoop
}
if found.contains(nb.ID) {
// Already found via another path.
continue
}
nl := loopnest.b2l[nb.ID]
if nl == nil || (nl.depth <= l.depth && nl != l) {
// Left the loop.
continue
}
tovisit = append(tovisit, nb)
found.add(nb.ID)
}
}
// No call-free path was found.
l.containsUnavoidableCall = true
}
}
func (b *Block) containsCall() bool {
if b.Kind == BlockDefer {
return true
}
for _, v := range b.Values {
if opcodeTable[v.Op].call {
return true
}
}
return false
}