| // 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 |
| } |