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

// fuseBranchRedirect checks for a CFG in which the outbound branch
// of an If block can be derived from its predecessor If block, in
// some such cases, we can redirect the predecessor If block to the
// corresponding successor block directly. For example:
// p:
//   v11 = Less64 <bool> v10 v8
//   If v11 goto b else u
// b: <- p ...
//   v17 = Leq64 <bool> v10 v8
//   If v17 goto s else o
// We can redirect p to s directly.
//
// The implementation here borrows the framework of the prove pass.
// 1, Traverse all blocks of function f to find If blocks.
// 2,   For any If block b, traverse all its predecessors to find If blocks.
// 3,     For any If block predecessor p, update relationship p->b.
// 4,     Traverse all successors of b.
// 5,       For any successor s of b, try to update relationship b->s, if a
//          contradiction is found then redirect p to another successor of b.
func fuseBranchRedirect(f *Func) bool {
	ft := newFactsTable(f)
	ft.checkpoint()

	changed := false
	for i := len(f.Blocks) - 1; i >= 0; i-- {
		b := f.Blocks[i]
		if b.Kind != BlockIf {
			continue
		}
		// b is either empty or only contains the control value.
		// TODO: if b contains only OpCopy or OpNot related to b.Controls,
		// such as Copy(Not(Copy(Less64(v1, v2)))), perhaps it can be optimized.
		bCtl := b.Controls[0]
		if bCtl.Block != b && len(b.Values) != 0 || (len(b.Values) != 1 || bCtl.Uses != 1) && bCtl.Block == b {
			continue
		}

		for k := 0; k < len(b.Preds); k++ {
			pk := b.Preds[k]
			p := pk.b
			if p.Kind != BlockIf || p == b {
				continue
			}
			pbranch := positive
			if pk.i == 1 {
				pbranch = negative
			}
			ft.checkpoint()
			// Assume branch p->b is taken.
			addBranchRestrictions(ft, p, pbranch)
			// Check if any outgoing branch is unreachable based on the above condition.
			parent := b
			for j, bbranch := range [...]branch{positive, negative} {
				ft.checkpoint()
				// Try to update relationship b->child, and check if the contradiction occurs.
				addBranchRestrictions(ft, parent, bbranch)
				unsat := ft.unsat
				ft.restore()
				if !unsat {
					continue
				}
				// This branch is impossible,so redirect p directly to another branch.
				out := 1 ^ j
				child := parent.Succs[out].b
				if child == b {
					continue
				}
				b.removePred(k)
				p.Succs[pk.i] = Edge{child, len(child.Preds)}
				// Fix up Phi value in b to have one less argument.
				for _, v := range b.Values {
					if v.Op != OpPhi {
						continue
					}
					b.removePhiArg(v, k)
					phielimValue(v)
				}
				// Fix up child to have one more predecessor.
				child.Preds = append(child.Preds, Edge{p, pk.i})
				ai := b.Succs[out].i
				for _, v := range child.Values {
					if v.Op != OpPhi {
						continue
					}
					v.AddArg(v.Args[ai])
				}
				if b.Func.pass.debug > 0 {
					b.Func.Warnl(b.Controls[0].Pos, "Redirect %s based on %s", b.Controls[0].Op, p.Controls[0].Op)
				}
				changed = true
				k--
				break
			}
			ft.restore()
		}
		if len(b.Preds) == 0 && b != f.Entry {
			// Block is now dead.
			b.Kind = BlockInvalid
		}
	}
	ft.restore()
	ft.cleanup(f)
	return changed
}
