| // Copyright 2015 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 ( | |
| "cmd/compile/internal/types" | |
| "testing" | |
| ) | |
| func BenchmarkDominatorsLinear(b *testing.B) { benchmarkDominators(b, 10000, genLinear) } | |
| func BenchmarkDominatorsFwdBack(b *testing.B) { benchmarkDominators(b, 10000, genFwdBack) } | |
| func BenchmarkDominatorsManyPred(b *testing.B) { benchmarkDominators(b, 10000, genManyPred) } | |
| func BenchmarkDominatorsMaxPred(b *testing.B) { benchmarkDominators(b, 10000, genMaxPred) } | |
| func BenchmarkDominatorsMaxPredVal(b *testing.B) { benchmarkDominators(b, 10000, genMaxPredValue) } | |
| type blockGen func(size int) []bloc | |
| // genLinear creates an array of blocks that succeed one another | |
| // b_n -> [b_n+1]. | |
| func genLinear(size int) []bloc { | |
| var blocs []bloc | |
| blocs = append(blocs, | |
| Bloc("entry", | |
| Valu("mem", OpInitMem, types.TypeMem, 0, nil), | |
| Goto(blockn(0)), | |
| ), | |
| ) | |
| for i := 0; i < size; i++ { | |
| blocs = append(blocs, Bloc(blockn(i), | |
| Goto(blockn(i+1)))) | |
| } | |
| blocs = append(blocs, | |
| Bloc(blockn(size), Goto("exit")), | |
| Bloc("exit", Exit("mem")), | |
| ) | |
| return blocs | |
| } | |
| // genFwdBack creates an array of blocks that alternate between | |
| // b_n -> [b_n+1], b_n -> [b_n+1, b_n-1] , b_n -> [b_n+1, b_n+2] | |
| func genFwdBack(size int) []bloc { | |
| var blocs []bloc | |
| blocs = append(blocs, | |
| Bloc("entry", | |
| Valu("mem", OpInitMem, types.TypeMem, 0, nil), | |
| Valu("p", OpConstBool, types.Types[types.TBOOL], 1, nil), | |
| Goto(blockn(0)), | |
| ), | |
| ) | |
| for i := 0; i < size; i++ { | |
| switch i % 2 { | |
| case 0: | |
| blocs = append(blocs, Bloc(blockn(i), | |
| If("p", blockn(i+1), blockn(i+2)))) | |
| case 1: | |
| blocs = append(blocs, Bloc(blockn(i), | |
| If("p", blockn(i+1), blockn(i-1)))) | |
| } | |
| } | |
| blocs = append(blocs, | |
| Bloc(blockn(size), Goto("exit")), | |
| Bloc("exit", Exit("mem")), | |
| ) | |
| return blocs | |
| } | |
| // genManyPred creates an array of blocks where 1/3rd have a successor of the | |
| // first block, 1/3rd the last block, and the remaining third are plain. | |
| func genManyPred(size int) []bloc { | |
| var blocs []bloc | |
| blocs = append(blocs, | |
| Bloc("entry", | |
| Valu("mem", OpInitMem, types.TypeMem, 0, nil), | |
| Valu("p", OpConstBool, types.Types[types.TBOOL], 1, nil), | |
| Goto(blockn(0)), | |
| ), | |
| ) | |
| // We want predecessor lists to be long, so 2/3rds of the blocks have a | |
| // successor of the first or last block. | |
| for i := 0; i < size; i++ { | |
| switch i % 3 { | |
| case 0: | |
| blocs = append(blocs, Bloc(blockn(i), | |
| Valu("a", OpConstBool, types.Types[types.TBOOL], 1, nil), | |
| Goto(blockn(i+1)))) | |
| case 1: | |
| blocs = append(blocs, Bloc(blockn(i), | |
| Valu("a", OpConstBool, types.Types[types.TBOOL], 1, nil), | |
| If("p", blockn(i+1), blockn(0)))) | |
| case 2: | |
| blocs = append(blocs, Bloc(blockn(i), | |
| Valu("a", OpConstBool, types.Types[types.TBOOL], 1, nil), | |
| If("p", blockn(i+1), blockn(size)))) | |
| } | |
| } | |
| blocs = append(blocs, | |
| Bloc(blockn(size), Goto("exit")), | |
| Bloc("exit", Exit("mem")), | |
| ) | |
| return blocs | |
| } | |
| // genMaxPred maximizes the size of the 'exit' predecessor list. | |
| func genMaxPred(size int) []bloc { | |
| var blocs []bloc | |
| blocs = append(blocs, | |
| Bloc("entry", | |
| Valu("mem", OpInitMem, types.TypeMem, 0, nil), | |
| Valu("p", OpConstBool, types.Types[types.TBOOL], 1, nil), | |
| Goto(blockn(0)), | |
| ), | |
| ) | |
| for i := 0; i < size; i++ { | |
| blocs = append(blocs, Bloc(blockn(i), | |
| If("p", blockn(i+1), "exit"))) | |
| } | |
| blocs = append(blocs, | |
| Bloc(blockn(size), Goto("exit")), | |
| Bloc("exit", Exit("mem")), | |
| ) | |
| return blocs | |
| } | |
| // genMaxPredValue is identical to genMaxPred but contains an | |
| // additional value. | |
| func genMaxPredValue(size int) []bloc { | |
| var blocs []bloc | |
| blocs = append(blocs, | |
| Bloc("entry", | |
| Valu("mem", OpInitMem, types.TypeMem, 0, nil), | |
| Valu("p", OpConstBool, types.Types[types.TBOOL], 1, nil), | |
| Goto(blockn(0)), | |
| ), | |
| ) | |
| for i := 0; i < size; i++ { | |
| blocs = append(blocs, Bloc(blockn(i), | |
| Valu("a", OpConstBool, types.Types[types.TBOOL], 1, nil), | |
| If("p", blockn(i+1), "exit"))) | |
| } | |
| blocs = append(blocs, | |
| Bloc(blockn(size), Goto("exit")), | |
| Bloc("exit", Exit("mem")), | |
| ) | |
| return blocs | |
| } | |
| // sink for benchmark | |
| var domBenchRes []*Block | |
| func benchmarkDominators(b *testing.B, size int, bg blockGen) { | |
| c := testConfig(b) | |
| fun := c.Fun("entry", bg(size)...) | |
| CheckFunc(fun.f) | |
| b.SetBytes(int64(size)) | |
| b.ResetTimer() | |
| for i := 0; i < b.N; i++ { | |
| domBenchRes = dominators(fun.f) | |
| } | |
| } | |
| type domFunc func(f *Func) []*Block | |
| // verifyDominators verifies that the dominators of fut (function under test) | |
| // as determined by domFn, match the map node->dominator | |
| func verifyDominators(t *testing.T, fut fun, domFn domFunc, doms map[string]string) { | |
| blockNames := map[*Block]string{} | |
| for n, b := range fut.blocks { | |
| blockNames[b] = n | |
| } | |
| calcDom := domFn(fut.f) | |
| for n, d := range doms { | |
| nblk, ok := fut.blocks[n] | |
| if !ok { | |
| t.Errorf("invalid block name %s", n) | |
| } | |
| dblk, ok := fut.blocks[d] | |
| if !ok { | |
| t.Errorf("invalid block name %s", d) | |
| } | |
| domNode := calcDom[nblk.ID] | |
| switch { | |
| case calcDom[nblk.ID] == dblk: | |
| calcDom[nblk.ID] = nil | |
| continue | |
| case calcDom[nblk.ID] != dblk: | |
| t.Errorf("expected %s as dominator of %s, found %s", d, n, blockNames[domNode]) | |
| default: | |
| t.Fatal("unexpected dominator condition") | |
| } | |
| } | |
| for id, d := range calcDom { | |
| // If nil, we've already verified it | |
| if d == nil { | |
| continue | |
| } | |
| for _, b := range fut.blocks { | |
| if int(b.ID) == id { | |
| t.Errorf("unexpected dominator of %s for %s", blockNames[d], blockNames[b]) | |
| } | |
| } | |
| } | |
| } | |
| func TestDominatorsSingleBlock(t *testing.T) { | |
| c := testConfig(t) | |
| fun := c.Fun("entry", | |
| Bloc("entry", | |
| Valu("mem", OpInitMem, types.TypeMem, 0, nil), | |
| Exit("mem"))) | |
| doms := map[string]string{} | |
| CheckFunc(fun.f) | |
| verifyDominators(t, fun, dominators, doms) | |
| verifyDominators(t, fun, dominatorsSimple, doms) | |
| } | |
| func TestDominatorsSimple(t *testing.T) { | |
| c := testConfig(t) | |
| fun := c.Fun("entry", | |
| Bloc("entry", | |
| Valu("mem", OpInitMem, types.TypeMem, 0, nil), | |
| Goto("a")), | |
| Bloc("a", | |
| Goto("b")), | |
| Bloc("b", | |
| Goto("c")), | |
| Bloc("c", | |
| Goto("exit")), | |
| Bloc("exit", | |
| Exit("mem"))) | |
| doms := map[string]string{ | |
| "a": "entry", | |
| "b": "a", | |
| "c": "b", | |
| "exit": "c", | |
| } | |
| CheckFunc(fun.f) | |
| verifyDominators(t, fun, dominators, doms) | |
| verifyDominators(t, fun, dominatorsSimple, doms) | |
| } | |
| func TestDominatorsMultPredFwd(t *testing.T) { | |
| c := testConfig(t) | |
| fun := c.Fun("entry", | |
| Bloc("entry", | |
| Valu("mem", OpInitMem, types.TypeMem, 0, nil), | |
| Valu("p", OpConstBool, types.Types[types.TBOOL], 1, nil), | |
| If("p", "a", "c")), | |
| Bloc("a", | |
| If("p", "b", "c")), | |
| Bloc("b", | |
| Goto("c")), | |
| Bloc("c", | |
| Goto("exit")), | |
| Bloc("exit", | |
| Exit("mem"))) | |
| doms := map[string]string{ | |
| "a": "entry", | |
| "b": "a", | |
| "c": "entry", | |
| "exit": "c", | |
| } | |
| CheckFunc(fun.f) | |
| verifyDominators(t, fun, dominators, doms) | |
| verifyDominators(t, fun, dominatorsSimple, doms) | |
| } | |
| func TestDominatorsDeadCode(t *testing.T) { | |
| c := testConfig(t) | |
| fun := c.Fun("entry", | |
| Bloc("entry", | |
| Valu("mem", OpInitMem, types.TypeMem, 0, nil), | |
| Valu("p", OpConstBool, types.Types[types.TBOOL], 0, nil), | |
| If("p", "b3", "b5")), | |
| Bloc("b2", Exit("mem")), | |
| Bloc("b3", Goto("b2")), | |
| Bloc("b4", Goto("b2")), | |
| Bloc("b5", Goto("b2"))) | |
| doms := map[string]string{ | |
| "b2": "entry", | |
| "b3": "entry", | |
| "b5": "entry", | |
| } | |
| CheckFunc(fun.f) | |
| verifyDominators(t, fun, dominators, doms) | |
| verifyDominators(t, fun, dominatorsSimple, doms) | |
| } | |
| func TestDominatorsMultPredRev(t *testing.T) { | |
| c := testConfig(t) | |
| fun := c.Fun("entry", | |
| Bloc("entry", | |
| Goto("first")), | |
| Bloc("first", | |
| Valu("mem", OpInitMem, types.TypeMem, 0, nil), | |
| Valu("p", OpConstBool, types.Types[types.TBOOL], 1, nil), | |
| Goto("a")), | |
| Bloc("a", | |
| If("p", "b", "first")), | |
| Bloc("b", | |
| Goto("c")), | |
| Bloc("c", | |
| If("p", "exit", "b")), | |
| Bloc("exit", | |
| Exit("mem"))) | |
| doms := map[string]string{ | |
| "first": "entry", | |
| "a": "first", | |
| "b": "a", | |
| "c": "b", | |
| "exit": "c", | |
| } | |
| CheckFunc(fun.f) | |
| verifyDominators(t, fun, dominators, doms) | |
| verifyDominators(t, fun, dominatorsSimple, doms) | |
| } | |
| func TestDominatorsMultPred(t *testing.T) { | |
| c := testConfig(t) | |
| fun := c.Fun("entry", | |
| Bloc("entry", | |
| Valu("mem", OpInitMem, types.TypeMem, 0, nil), | |
| Valu("p", OpConstBool, types.Types[types.TBOOL], 1, nil), | |
| If("p", "a", "c")), | |
| Bloc("a", | |
| If("p", "b", "c")), | |
| Bloc("b", | |
| Goto("c")), | |
| Bloc("c", | |
| If("p", "b", "exit")), | |
| Bloc("exit", | |
| Exit("mem"))) | |
| doms := map[string]string{ | |
| "a": "entry", | |
| "b": "entry", | |
| "c": "entry", | |
| "exit": "c", | |
| } | |
| CheckFunc(fun.f) | |
| verifyDominators(t, fun, dominators, doms) | |
| verifyDominators(t, fun, dominatorsSimple, doms) | |
| } | |
| func TestInfiniteLoop(t *testing.T) { | |
| c := testConfig(t) | |
| // note lack of an exit block | |
| fun := c.Fun("entry", | |
| Bloc("entry", | |
| Valu("mem", OpInitMem, types.TypeMem, 0, nil), | |
| Valu("p", OpConstBool, types.Types[types.TBOOL], 1, nil), | |
| Goto("a")), | |
| Bloc("a", | |
| Goto("b")), | |
| Bloc("b", | |
| Goto("a"))) | |
| CheckFunc(fun.f) | |
| doms := map[string]string{"a": "entry", | |
| "b": "a"} | |
| verifyDominators(t, fun, dominators, doms) | |
| } | |
| func TestDomTricky(t *testing.T) { | |
| doms := map[string]string{ | |
| "4": "1", | |
| "2": "4", | |
| "5": "4", | |
| "11": "4", | |
| "15": "4", // the incorrect answer is "5" | |
| "10": "15", | |
| "19": "15", | |
| } | |
| if4 := [2]string{"2", "5"} | |
| if5 := [2]string{"15", "11"} | |
| if15 := [2]string{"19", "10"} | |
| for i := 0; i < 8; i++ { | |
| a := 1 & i | |
| b := 1 & i >> 1 | |
| c := 1 & i >> 2 | |
| cfg := testConfig(t) | |
| fun := cfg.Fun("1", | |
| Bloc("1", | |
| Valu("mem", OpInitMem, types.TypeMem, 0, nil), | |
| Valu("p", OpConstBool, types.Types[types.TBOOL], 1, nil), | |
| Goto("4")), | |
| Bloc("2", | |
| Goto("11")), | |
| Bloc("4", | |
| If("p", if4[a], if4[1-a])), // 2, 5 | |
| Bloc("5", | |
| If("p", if5[b], if5[1-b])), //15, 11 | |
| Bloc("10", | |
| Exit("mem")), | |
| Bloc("11", | |
| Goto("15")), | |
| Bloc("15", | |
| If("p", if15[c], if15[1-c])), //19, 10 | |
| Bloc("19", | |
| Goto("10"))) | |
| CheckFunc(fun.f) | |
| verifyDominators(t, fun, dominators, doms) | |
| verifyDominators(t, fun, dominatorsSimple, doms) | |
| } | |
| } | |
| // generateDominatorMap uses dominatorsSimple to obtain a | |
| // reference dominator tree for testing faster algorithms. | |
| func generateDominatorMap(fut fun) map[string]string { | |
| blockNames := map[*Block]string{} | |
| for n, b := range fut.blocks { | |
| blockNames[b] = n | |
| } | |
| referenceDom := dominatorsSimple(fut.f) | |
| doms := make(map[string]string) | |
| for _, b := range fut.f.Blocks { | |
| if d := referenceDom[b.ID]; d != nil { | |
| doms[blockNames[b]] = blockNames[d] | |
| } | |
| } | |
| return doms | |
| } | |
| func TestDominatorsPostTrickyA(t *testing.T) { | |
| testDominatorsPostTricky(t, "b8", "b11", "b10", "b8", "b14", "b15") | |
| } | |
| func TestDominatorsPostTrickyB(t *testing.T) { | |
| testDominatorsPostTricky(t, "b11", "b8", "b10", "b8", "b14", "b15") | |
| } | |
| func TestDominatorsPostTrickyC(t *testing.T) { | |
| testDominatorsPostTricky(t, "b8", "b11", "b8", "b10", "b14", "b15") | |
| } | |
| func TestDominatorsPostTrickyD(t *testing.T) { | |
| testDominatorsPostTricky(t, "b11", "b8", "b8", "b10", "b14", "b15") | |
| } | |
| func TestDominatorsPostTrickyE(t *testing.T) { | |
| testDominatorsPostTricky(t, "b8", "b11", "b10", "b8", "b15", "b14") | |
| } | |
| func TestDominatorsPostTrickyF(t *testing.T) { | |
| testDominatorsPostTricky(t, "b11", "b8", "b10", "b8", "b15", "b14") | |
| } | |
| func TestDominatorsPostTrickyG(t *testing.T) { | |
| testDominatorsPostTricky(t, "b8", "b11", "b8", "b10", "b15", "b14") | |
| } | |
| func TestDominatorsPostTrickyH(t *testing.T) { | |
| testDominatorsPostTricky(t, "b11", "b8", "b8", "b10", "b15", "b14") | |
| } | |
| func testDominatorsPostTricky(t *testing.T, b7then, b7else, b12then, b12else, b13then, b13else string) { | |
| c := testConfig(t) | |
| fun := c.Fun("b1", | |
| Bloc("b1", | |
| Valu("mem", OpInitMem, types.TypeMem, 0, nil), | |
| Valu("p", OpConstBool, types.Types[types.TBOOL], 1, nil), | |
| If("p", "b3", "b2")), | |
| Bloc("b3", | |
| If("p", "b5", "b6")), | |
| Bloc("b5", | |
| Goto("b7")), | |
| Bloc("b7", | |
| If("p", b7then, b7else)), | |
| Bloc("b8", | |
| Goto("b13")), | |
| Bloc("b13", | |
| If("p", b13then, b13else)), | |
| Bloc("b14", | |
| Goto("b10")), | |
| Bloc("b15", | |
| Goto("b16")), | |
| Bloc("b16", | |
| Goto("b9")), | |
| Bloc("b9", | |
| Goto("b7")), | |
| Bloc("b11", | |
| Goto("b12")), | |
| Bloc("b12", | |
| If("p", b12then, b12else)), | |
| Bloc("b10", | |
| Goto("b6")), | |
| Bloc("b6", | |
| Goto("b17")), | |
| Bloc("b17", | |
| Goto("b18")), | |
| Bloc("b18", | |
| If("p", "b22", "b19")), | |
| Bloc("b22", | |
| Goto("b23")), | |
| Bloc("b23", | |
| If("p", "b21", "b19")), | |
| Bloc("b19", | |
| If("p", "b24", "b25")), | |
| Bloc("b24", | |
| Goto("b26")), | |
| Bloc("b26", | |
| Goto("b25")), | |
| Bloc("b25", | |
| If("p", "b27", "b29")), | |
| Bloc("b27", | |
| Goto("b30")), | |
| Bloc("b30", | |
| Goto("b28")), | |
| Bloc("b29", | |
| Goto("b31")), | |
| Bloc("b31", | |
| Goto("b28")), | |
| Bloc("b28", | |
| If("p", "b32", "b33")), | |
| Bloc("b32", | |
| Goto("b21")), | |
| Bloc("b21", | |
| Goto("b47")), | |
| Bloc("b47", | |
| If("p", "b45", "b46")), | |
| Bloc("b45", | |
| Goto("b48")), | |
| Bloc("b48", | |
| Goto("b49")), | |
| Bloc("b49", | |
| If("p", "b50", "b51")), | |
| Bloc("b50", | |
| Goto("b52")), | |
| Bloc("b52", | |
| Goto("b53")), | |
| Bloc("b53", | |
| Goto("b51")), | |
| Bloc("b51", | |
| Goto("b54")), | |
| Bloc("b54", | |
| Goto("b46")), | |
| Bloc("b46", | |
| Exit("mem")), | |
| Bloc("b33", | |
| Goto("b34")), | |
| Bloc("b34", | |
| Goto("b37")), | |
| Bloc("b37", | |
| If("p", "b35", "b36")), | |
| Bloc("b35", | |
| Goto("b38")), | |
| Bloc("b38", | |
| Goto("b39")), | |
| Bloc("b39", | |
| If("p", "b40", "b41")), | |
| Bloc("b40", | |
| Goto("b42")), | |
| Bloc("b42", | |
| Goto("b43")), | |
| Bloc("b43", | |
| Goto("b41")), | |
| Bloc("b41", | |
| Goto("b44")), | |
| Bloc("b44", | |
| Goto("b36")), | |
| Bloc("b36", | |
| Goto("b20")), | |
| Bloc("b20", | |
| Goto("b18")), | |
| Bloc("b2", | |
| Goto("b4")), | |
| Bloc("b4", | |
| Exit("mem"))) | |
| CheckFunc(fun.f) | |
| doms := generateDominatorMap(fun) | |
| verifyDominators(t, fun, dominators, doms) | |
| } |