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