package ssa

// phiopt eliminates boolean Phis based on the previous if.
//
// Main use case is to transform:
//   x := false
//   if b {
//     x = true
//   }
// into x = b.
//
// In SSA code this appears as
//
// b0
//   If b -> b1 b2
// b1
//   Plain -> b2
// b2
//   x = (OpPhi (ConstBool [true]) (ConstBool [false]))
//
// In this case we can replace x with a copy of b.
func phiopt(f *Func) {
	for _, b := range f.Blocks {
		if len(b.Preds) != 2 || len(b.Values) == 0 {
			continue
		}

		pb0, b0 := b, b.Preds[0]
		for len(b0.Succs) == 1 && len(b0.Preds) == 1 {
			pb0, b0 = b0, b0.Preds[0]
		}
		if b0.Kind != BlockIf {
			continue
		}
		pb1, b1 := b, b.Preds[1]
		for len(b1.Succs) == 1 && len(b1.Preds) == 1 {
			pb1, b1 = b1, b1.Preds[0]
		}
		if b1 != b0 {
			continue
		}
		// b0 is the if block giving the boolean value.

		var reverse bool
		if b0.Succs[0] == pb0 && b0.Succs[1] == pb1 {
			reverse = false
		} else if b0.Succs[0] == pb1 && b0.Succs[1] == pb0 {
			reverse = true
		} else {
			b.Fatalf("invalid predecessors\n")
		}

		for _, v := range b.Values {
			if v.Op != OpPhi || !v.Type.IsBoolean() || v.Args[0].Op != OpConstBool || v.Args[1].Op != OpConstBool {
				continue
			}

			ok, isCopy := false, false
			if v.Args[0].AuxInt == 1 && v.Args[1].AuxInt == 0 {
				ok, isCopy = true, !reverse
			} else if v.Args[0].AuxInt == 0 && v.Args[1].AuxInt == 1 {
				ok, isCopy = true, reverse
			}

			// (Phi (ConstBool [x]) (ConstBool [x])) is already handled by opt / phielim.

			if ok && isCopy {
				if f.pass.debug > 0 {
					f.Config.Warnl(b.Line, "converted OpPhi to OpCopy")
				}
				v.reset(OpCopy)
				v.AddArg(b0.Control)
				continue
			}
			if ok && !isCopy {
				if f.pass.debug > 0 {
					f.Config.Warnl(b.Line, "converted OpPhi to OpNot")
				}
				v.reset(OpNot)
				v.AddArg(b0.Control)
				continue
			}
		}
	}

}
