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

// mark values
const (
	notFound    = 0 // block has not been discovered yet
	notExplored = 1 // discovered and in queue, outedges not processed yet
	explored    = 2 // discovered and in queue, outedges processed
	done        = 3 // all done, in output ordering
)

// This file contains code to compute the dominator tree
// of a control-flow graph.

// postorder computes a postorder traversal ordering for the
// basic blocks in f. Unreachable blocks will not appear.
func postorder(f *Func) []*Block {
	mark := make([]byte, f.NumBlocks())

	// result ordering
	var order []*Block

	// stack of blocks
	var s []*Block
	s = append(s, f.Entry)
	mark[f.Entry.ID] = notExplored
	for len(s) > 0 {
		b := s[len(s)-1]
		switch mark[b.ID] {
		case explored:
			// Children have all been visited. Pop & output block.
			s = s[:len(s)-1]
			mark[b.ID] = done
			order = append(order, b)
		case notExplored:
			// Children have not been visited yet. Mark as explored
			// and queue any children we haven't seen yet.
			mark[b.ID] = explored
			for _, c := range b.Succs {
				if mark[c.ID] == notFound {
					mark[c.ID] = notExplored
					s = append(s, c)
				}
			}
		default:
			b.Fatalf("bad stack state %v %d", b, mark[b.ID])
		}
	}
	return order
}

type linkedBlocks func(*Block) []*Block

const nscratchslices = 8

// experimentally, functions with 512 or fewer blocks account
// for 75% of memory (size) allocation for dominator computation
// in make.bash.
const minscratchblocks = 512

func (cfg *Config) scratchBlocksForDom(maxBlockID int) (a, b, c, d, e, f, g, h []ID) {
	tot := maxBlockID * nscratchslices
	scratch := cfg.domblockstore
	if len(scratch) < tot {
		// req = min(1.5*tot, nscratchslices*minscratchblocks)
		// 50% padding allows for graph growth in later phases.
		req := (tot * 3) >> 1
		if req < nscratchslices*minscratchblocks {
			req = nscratchslices * minscratchblocks
		}
		scratch = make([]ID, req)
		cfg.domblockstore = scratch
	} else {
		// Clear as much of scratch as we will (re)use
		scratch = scratch[0:tot]
		for i := range scratch {
			scratch[i] = 0
		}
	}

	a = scratch[0*maxBlockID : 1*maxBlockID]
	b = scratch[1*maxBlockID : 2*maxBlockID]
	c = scratch[2*maxBlockID : 3*maxBlockID]
	d = scratch[3*maxBlockID : 4*maxBlockID]
	e = scratch[4*maxBlockID : 5*maxBlockID]
	f = scratch[5*maxBlockID : 6*maxBlockID]
	g = scratch[6*maxBlockID : 7*maxBlockID]
	h = scratch[7*maxBlockID : 8*maxBlockID]

	return
}

// dfs performs a depth first search over the blocks starting at the set of
// blocks in the entries list (in arbitrary order). dfnum contains a mapping
// from block id to an int indicating the order the block was reached or
// notFound if the block was not reached.  order contains a mapping from dfnum
// to block.
func (f *Func) dfs(entries []*Block, succFn linkedBlocks, dfnum, order, parent []ID) (fromID []*Block) {
	maxBlockID := entries[0].Func.NumBlocks()

	fromID = make([]*Block, maxBlockID)

	for _, entry := range entries[0].Func.Blocks {
		eid := entry.ID
		if fromID[eid] != nil {
			panic("Colliding entry IDs")
		}
		fromID[eid] = entry
	}

	n := ID(0)
	s := make([]*Block, 0, 256)
	for _, entry := range entries {
		if dfnum[entry.ID] != notFound {
			continue // already found from a previous entry
		}
		s = append(s, entry)
		parent[entry.ID] = entry.ID
		for len(s) > 0 {
			node := s[len(s)-1]
			s = s[:len(s)-1]

			n++
			for _, w := range succFn(node) {
				// if it has a dfnum, we've already visited it
				if dfnum[w.ID] == notFound {
					s = append(s, w)
					parent[w.ID] = node.ID
					dfnum[w.ID] = notExplored
				}
			}
			dfnum[node.ID] = n
			order[n] = node.ID
		}
	}

	return
}

// dominators computes the dominator tree for f. It returns a slice
// which maps block ID to the immediate dominator of that block.
// Unreachable blocks map to nil. The entry block maps to nil.
func dominators(f *Func) []*Block {
	preds := func(b *Block) []*Block { return b.Preds }
	succs := func(b *Block) []*Block { return b.Succs }

	//TODO: benchmark and try to find criteria for swapping between
	// dominatorsSimple and dominatorsLT
	return f.dominatorsLT([]*Block{f.Entry}, preds, succs)
}

// postDominators computes the post-dominator tree for f.
func postDominators(f *Func) []*Block {
	preds := func(b *Block) []*Block { return b.Preds }
	succs := func(b *Block) []*Block { return b.Succs }

	if len(f.Blocks) == 0 {
		return nil
	}

	// find the exit blocks
	var exits []*Block
	for i := len(f.Blocks) - 1; i >= 0; i-- {
		switch f.Blocks[i].Kind {
		case BlockExit, BlockRet, BlockRetJmp, BlockCall, BlockCheck:
			exits = append(exits, f.Blocks[i])
			break
		}
	}

	// infinite loop with no exit
	if exits == nil {
		return make([]*Block, f.NumBlocks())
	}
	return f.dominatorsLT(exits, succs, preds)
}

// dominatorsLt runs Lengauer-Tarjan to compute a dominator tree starting at
// entry and using predFn/succFn to find predecessors/successors to allow
// computing both dominator and post-dominator trees.
func (f *Func) dominatorsLT(entries []*Block, predFn linkedBlocks, succFn linkedBlocks) []*Block {
	// Based on Lengauer-Tarjan from Modern Compiler Implementation in C -
	// Appel with optimizations from Finding Dominators in Practice -
	// Georgiadis

	maxBlockID := entries[0].Func.NumBlocks()

	dfnum, vertex, parent, semi, samedom, ancestor, best, bucket := f.Config.scratchBlocksForDom(maxBlockID)

	// dfnum := make([]ID, maxBlockID) // conceptually int32, but punning for allocation purposes.
	// vertex := make([]ID, maxBlockID)
	// parent := make([]ID, maxBlockID)

	// semi := make([]ID, maxBlockID)
	// samedom := make([]ID, maxBlockID)
	// ancestor := make([]ID, maxBlockID)
	// best := make([]ID, maxBlockID)
	// bucket := make([]ID, maxBlockID)

	// Step 1. Carry out a depth first search of the problem graph. Number
	// the vertices from 1 to n as they are reached during the search.
	fromID := f.dfs(entries, succFn, dfnum, vertex, parent)

	idom := make([]*Block, maxBlockID)

	// Step 2. Compute the semidominators of all vertices by applying
	// Theorem 4.  Carry out the computation vertex by vertex in decreasing
	// order by number.
	for i := maxBlockID - 1; i > 0; i-- {
		w := vertex[i]
		if w == 0 {
			continue
		}

		if dfnum[w] == notFound {
			// skip unreachable node
			continue
		}

		// Step 3. Implicitly define the immediate dominator of each
		// vertex by applying Corollary 1. (reordered)
		for v := bucket[w]; v != 0; v = bucket[v] {
			u := eval(v, ancestor, semi, dfnum, best)
			if semi[u] == semi[v] {
				idom[v] = fromID[w] // true dominator
			} else {
				samedom[v] = u // v has same dominator as u
			}
		}

		p := parent[w]
		s := p // semidominator

		var sp ID
		// calculate the semidominator of w
		for _, v := range predFn(fromID[w]) {
			if dfnum[v.ID] == notFound {
				// skip unreachable predecessor
				continue
			}

			if dfnum[v.ID] <= dfnum[w] {
				sp = v.ID
			} else {
				sp = semi[eval(v.ID, ancestor, semi, dfnum, best)]
			}

			if dfnum[sp] < dfnum[s] {
				s = sp
			}
		}

		// link
		ancestor[w] = p
		best[w] = w

		semi[w] = s
		if semi[s] != parent[s] {
			bucket[w] = bucket[s]
			bucket[s] = w
		}
	}

	// Final pass of step 3
	for v := bucket[0]; v != 0; v = bucket[v] {
		idom[v] = fromID[bucket[0]]
	}

	// Step 4. Explictly define the immediate dominator of each vertex,
	// carrying out the computation vertex by vertex in increasing order by
	// number.
	for i := 1; i < maxBlockID-1; i++ {
		w := vertex[i]
		if w == 0 {
			continue
		}
		// w has the same dominator as samedom[w]
		if samedom[w] != 0 {
			idom[w] = idom[samedom[w]]
		}
	}
	return idom
}

// eval function from LT paper with path compression
func eval(v ID, ancestor []ID, semi []ID, dfnum []ID, best []ID) ID {
	a := ancestor[v]
	if ancestor[a] != 0 {
		bid := eval(a, ancestor, semi, dfnum, best)
		ancestor[v] = ancestor[a]
		if dfnum[semi[bid]] < dfnum[semi[best[v]]] {
			best[v] = bid
		}
	}
	return best[v]
}

// dominators computes the dominator tree for f. It returns a slice
// which maps block ID to the immediate dominator of that block.
// Unreachable blocks map to nil. The entry block maps to nil.
func dominatorsSimple(f *Func) []*Block {
	// A simple algorithm for now
	// Cooper, Harvey, Kennedy
	idom := make([]*Block, f.NumBlocks())

	// Compute postorder walk
	post := postorder(f)

	// Make map from block id to order index (for intersect call)
	postnum := make([]int, f.NumBlocks())
	for i, b := range post {
		postnum[b.ID] = i
	}

	// Make the entry block a self-loop
	idom[f.Entry.ID] = f.Entry
	if postnum[f.Entry.ID] != len(post)-1 {
		f.Fatalf("entry block %v not last in postorder", f.Entry)
	}

	// Compute relaxation of idom entries
	for {
		changed := false

		for i := len(post) - 2; i >= 0; i-- {
			b := post[i]
			var d *Block
			for _, p := range b.Preds {
				if idom[p.ID] == nil {
					continue
				}
				if d == nil {
					d = p
					continue
				}
				d = intersect(d, p, postnum, idom)
			}
			if d != idom[b.ID] {
				idom[b.ID] = d
				changed = true
			}
		}
		if !changed {
			break
		}
	}
	// Set idom of entry block to nil instead of itself.
	idom[f.Entry.ID] = nil
	return idom
}

// intersect finds the closest dominator of both b and c.
// It requires a postorder numbering of all the blocks.
func intersect(b, c *Block, postnum []int, idom []*Block) *Block {
	// TODO: This loop is O(n^2). See BenchmarkNilCheckDeep*.
	for b != c {
		if postnum[b.ID] < postnum[c.ID] {
			b = idom[b.ID]
		} else {
			c = idom[c.ID]
		}
	}
	return b
}
