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