blob: 439551cb4493d01cea762c735581c9e046f202c2 [file]
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
import (
"fmt"
"iter"
"slices"
"testing"
"cmd/compile/internal/ssa/block"
)
// genCrossLadder builds a k-column, 2-rail cross ladder CFG:
//
// entry → L1 R1
// │╲ ╱│ every block branches to BOTH blocks of
// │ ╳ │ the next column
// │╱ ╲│
// L2 R2
// ...
// Lk Rk
// ↘ ↙
// exit
//
// The cross edges help to do quadratic DF+ walks (assume each of N blocks has another def
// and needs to iterate with f.IterDomFrontierPlus O(N) blocks).
// Returned are the function and its column blocks, column by column.
func genCrossLadder(k int) (*Func, []*Block) {
f := (&Config{}).NewFunc(nil, &Cache{})
entry := f.NewBlock(block.BlockIf)
f.Entry = entry
exit := f.NewBlock(block.BlockExit)
col := make([]*Block, 0, 2*k)
prevL, prevR := entry, entry
for i := 0; i < k; i++ {
kind := block.BlockIf
if i == k-1 {
kind = block.BlockPlain // goto exit
}
l := f.NewBlock(kind)
r := f.NewBlock(kind)
col = append(col, l, r)
prevL.AddEdgeTo(l)
prevL.AddEdgeTo(r)
prevR.AddEdgeTo(l)
prevR.AddEdgeTo(r)
prevL, prevR = l, r
}
col[len(col)-2].AddEdgeTo(exit)
col[len(col)-1].AddEdgeTo(exit)
return f, col
}
// BenchmarkIterDomFrontierPlus walks the iterated dominance frontier of every
// column block of a cross ladder, the merge-set stress shape.
// Ideally, ns/block (ns/op ÷ blocks/op) stays flat as k grows.
func BenchmarkIterDomFrontierPlus(b *testing.B) {
for _, k := range []int{8, 16, 32} {
b.Run(fmt.Sprintf("k=%d", k), func(b *testing.B) {
f, col := genCrossLadder(k)
b.ReportAllocs()
b.ResetTimer()
var n int
for i := 0; i < b.N; i++ {
for j := range col {
for range f.IterDomFrontierPlus(slices.Values(col[j : j+1])) {
n++
}
}
}
b.StopTimer()
b.ReportMetric(float64(n)/float64(b.N), "blocks/op")
if n != 2*k*k*b.N {
b.Fatalf("walked %d blocks per round, want %d", n/b.N, 2*k*k)
}
})
}
}
// TestIterDomFrontierPlusSeedAtMerge tests DF+ of a small CFG with a loop
// where its header is both a seed and a merge point.
// A def in an unreachable block (u → b3) must not join the merge set,
// and the header (b2) must be in it.
func TestIterDomFrontierPlusSeedAtMerge(t *testing.T) {
//
// x := 0
// loop:
// x = x + 1 // b2: def in the loop head
// if c { continue } // b4 → b2, back edge 1
// if d { break } // b5 → b3
// goto loop // b5 → b2, back edge 2
// return x // b3
// unreachable: x = 9; // u → b3
//
f := (&Config{}).NewFunc(nil, &Cache{})
b1 := f.NewBlock(block.BlockPlain) // entry: x:=0
f.Entry = b1
b2 := f.NewBlock(block.BlockIf) // loop head: x = x + 1; if c
b4 := f.NewBlock(block.BlockPlain) // { continue }
b5 := f.NewBlock(block.BlockIf) // if d { break }; goto loop
b3 := f.NewBlock(block.BlockExit)
u := f.NewBlock(block.BlockPlain) // unreachable; goto b3
b1.AddEdgeTo(b2)
b2.AddEdgeTo(b4)
b2.AddEdgeTo(b5)
b4.AddEdgeTo(b2) // back edge 1
b5.AddEdgeTo(b3) // break
b5.AddEdgeTo(b2) // back edge 2
u.AddEdgeTo(b3) // goto b3
got := collectBlockIDs(f.IterDomFrontierPlus(slices.Values([]*Block{b1, b2, u})))
if want := []ID{b2.ID}; !slices.Equal(got, want) {
t.Errorf("got DF+ = %v, want %v", got, want)
}
}
func TestIterDomFrontierPlusOrigin(t *testing.T) {
f := (&Config{}).NewFunc(nil, &Cache{})
entry := f.NewBlock(block.BlockIf)
f.Entry = entry
left := f.NewBlock(block.BlockPlain)
right := f.NewBlock(block.BlockPlain)
merge := f.NewBlock(block.BlockExit)
entry.AddEdgeTo(left)
entry.AddEdgeTo(right)
left.AddEdgeTo(merge)
right.AddEdgeTo(merge)
var got [][2]*Block
for b, origin := range f.IterDomFrontierPlus(slices.Values([]*Block{left})) {
got = append(got, [2]*Block{b, origin})
}
if want := [][2]*Block{{merge, left}}; !slices.Equal(got, want) {
t.Fatalf("got frontier and origin %v, want %v", got, want)
}
}
func collectBlockIDs(seq iter.Seq2[*Block, *Block]) []ID {
var ids []ID
for b := range seq {
ids = append(ids, b.ID)
}
return ids
}