package ssa
type indVar struct {
ind *Value // induction variable
inc *Value // increment, a constant
nxt *Value // ind+inc variable
min *Value // minimum value. inclusive,
max *Value // maximum value. exclusive.
entry *Block // entry block in the loop.
// Invariants: for all blocks dominated by entry:
// min <= ind < max
// min <= nxt <= max
}
// findIndVar finds induction variables in a function.
//
// Look for variables and blocks that satisfy the following
//
// loop:
// ind = (Phi min nxt),
// if ind < max
// then goto enter_loop
// else goto exit_loop
//
// enter_loop:
// do something
// nxt = inc + ind
// goto loop
//
// exit_loop:
//
//
// TODO: handle 32 bit operations
func findIndVar(f *Func) []indVar {
var iv []indVar
sdom := f.sdom()
nextb:
for _, b := range f.Blocks {
if b.Kind != BlockIf || len(b.Preds) != 2 {
continue
}
var ind, max *Value // induction, and maximum
entry := -1 // which successor of b enters the loop
// Check thet the control if it either ind < max or max > ind.
// TODO: Handle Leq64, Geq64.
switch b.Control.Op {
case OpLess64:
entry = 0
ind, max = b.Control.Args[0], b.Control.Args[1]
case OpGreater64:
entry = 0
ind, max = b.Control.Args[1], b.Control.Args[0]
default:
continue nextb
}
// Check that the induction variable is a phi that depends on itself.
if ind.Op != OpPhi {
continue
}
// Extract min and nxt knowing that nxt is an addition (e.g. Add64).
var min, nxt *Value // minimum, and next value
if n := ind.Args[0]; n.Op == OpAdd64 && (n.Args[0] == ind || n.Args[1] == ind) {
min, nxt = ind.Args[1], n
} else if n := ind.Args[1]; n.Op == OpAdd64 && (n.Args[0] == ind || n.Args[1] == ind) {
min, nxt = ind.Args[0], n
} else {
// Not a recognized induction variable.
continue
}
var inc *Value
if nxt.Args[0] == ind { // nxt = ind + inc
inc = nxt.Args[1]
} else if nxt.Args[1] == ind { // nxt = inc + ind
inc = nxt.Args[0]
} else {
panic("unreachable") // one of the cases must be true from the above.
}
// Expect the increment to be a positive constant.
// TODO: handle negative increment.
if inc.Op != OpConst64 || inc.AuxInt <= 0 {
continue
}
// Up to now we extracted the induction variable (ind),
// the increment delta (inc), the temporary sum (nxt),
// the mininum value (min) and the maximum value (max).
//
// We also know that ind has the form (Phi min nxt) where
// nxt is (Add inc nxt) which means: 1) inc dominates nxt
// and 2) there is a loop starting at inc and containing nxt.
//
// We need to prove that the induction variable is incremented
// only when it's smaller than the maximum value.
// Two conditions must happen listed below to accept ind
// as an induction variable.
// First condition: loop entry has a single predecessor, which
// is the header block. This implies that b.Succs[entry] is
// reached iff ind < max.
if len(b.Succs[entry].b.Preds) != 1 {
// b.Succs[1-entry] must exit the loop.
continue
}
// Second condition: b.Succs[entry] dominates nxt so that
// nxt is computed when inc < max, meaning nxt <= max.
if !sdom.isAncestorEq(b.Succs[entry].b, nxt.Block) {
// inc+ind can only be reached through the branch that enters the loop.
continue
}
// If max is c + SliceLen with c <= 0 then we drop c.
// Makes sure c + SliceLen doesn't overflow when SliceLen == 0.
// TODO: save c as an offset from max.
if w, c := dropAdd64(max); (w.Op == OpStringLen || w.Op == OpSliceLen) && 0 >= c && -c >= 0 {
max = w
}
// We can only guarantee that the loops runs within limits of induction variable
// if the increment is 1 or when the limits are constants.
if inc.AuxInt != 1 {
ok := false
if min.Op == OpConst64 && max.Op == OpConst64 {
if max.AuxInt > min.AuxInt && max.AuxInt%inc.AuxInt == min.AuxInt%inc.AuxInt { // handle overflow
ok = true
}
}
if !ok {
continue
}
}
if f.pass.debug > 1 {
if min.Op == OpConst64 {
b.Func.Warnl(b.Pos, "Induction variable with minimum %d and increment %d", min.AuxInt, inc.AuxInt)
} else {
b.Func.Warnl(b.Pos, "Induction variable with non-const minimum and increment %d", inc.AuxInt)
}
}
iv = append(iv, indVar{
ind: ind,
inc: inc,
nxt: nxt,
min: min,
max: max,
entry: b.Succs[entry].b,
})
b.Logf("found induction variable %v (inc = %v, min = %v, max = %v)\n", ind, inc, min, max)
}
return iv
}
// loopbce performs loop based bounds check elimination.
func loopbce(f *Func) {
ivList := findIndVar(f)
m := make(map[*Value]indVar)
for _, iv := range ivList {
m[iv.ind] = iv
}
removeBoundsChecks(f, m)
}
// removesBoundsChecks remove IsInBounds and IsSliceInBounds based on the induction variables.
func removeBoundsChecks(f *Func, m map[*Value]indVar) {
sdom := f.sdom()
for _, b := range f.Blocks {
if b.Kind != BlockIf {
continue
}
v := b.Control
// Simplify:
// (IsInBounds ind max) where 0 <= const == min <= ind < max.
// (IsSliceInBounds ind max) where 0 <= const == min <= ind < max.
// Found in:
// for i := range a {
// use a[i]
// use a[i:]
// use a[:i]
// }
if v.Op == OpIsInBounds || v.Op == OpIsSliceInBounds {
ind, add := dropAdd64(v.Args[0])
if ind.Op != OpPhi {
goto skip1
}
if v.Op == OpIsInBounds && add != 0 {
goto skip1
}
if v.Op == OpIsSliceInBounds && (0 > add || add > 1) {
goto skip1
}
if iv, has := m[ind]; has && sdom.isAncestorEq(iv.entry, b) && isNonNegative(iv.min) {
if v.Args[1] == iv.max {
if f.pass.debug > 0 {
f.Warnl(b.Pos, "Found redundant %s", v.Op)
}
goto simplify
}
}
}
skip1:
// Simplify:
// (IsSliceInBounds ind (SliceCap a)) where 0 <= min <= ind < max == (SliceLen a)
// Found in:
// for i := range a {
// use a[:i]
// use a[:i+1]
// }
if v.Op == OpIsSliceInBounds {
ind, add := dropAdd64(v.Args[0])
if ind.Op != OpPhi {
goto skip2
}
if 0 > add || add > 1 {
goto skip2
}
if iv, has := m[ind]; has && sdom.isAncestorEq(iv.entry, b) && isNonNegative(iv.min) {
if v.Args[1].Op == OpSliceCap && iv.max.Op == OpSliceLen && v.Args[1].Args[0] == iv.max.Args[0] {
if f.pass.debug > 0 {
f.Warnl(b.Pos, "Found redundant %s (len promoted to cap)", v.Op)
}
goto simplify
}
}
}
skip2:
// Simplify
// (IsInBounds (Add64 ind) (Const64 [c])) where 0 <= min <= ind < max <= (Const64 [c])
// (IsSliceInBounds ind (Const64 [c])) where 0 <= min <= ind < max <= (Const64 [c])
if v.Op == OpIsInBounds || v.Op == OpIsSliceInBounds {
ind, add := dropAdd64(v.Args[0])
if ind.Op != OpPhi {
goto skip3
}
// ind + add >= 0 <-> min + add >= 0 <-> min >= -add
if iv, has := m[ind]; has && sdom.isAncestorEq(iv.entry, b) && isGreaterOrEqualThan(iv.min, -add) {
if !v.Args[1].isGenericIntConst() || !iv.max.isGenericIntConst() {
goto skip3
}
limit := v.Args[1].AuxInt
if v.Op == OpIsSliceInBounds {
// If limit++ overflows signed integer then 0 <= max && max <= limit will be false.
limit++
}
if max := iv.max.AuxInt + add; 0 <= max && max <= limit { // handle overflow
if f.pass.debug > 0 {
f.Warnl(b.Pos, "Found redundant (%s ind %d), ind < %d", v.Op, v.Args[1].AuxInt, iv.max.AuxInt+add)
}
goto simplify
}
}
}
skip3:
continue
simplify:
f.Logf("removing bounds check %v at %v in %s\n", b.Control, b, f.Name)
b.Kind = BlockFirst
b.SetControl(nil)
}
}
func dropAdd64(v *Value) (*Value, int64) {
if v.Op == OpAdd64 && v.Args[0].Op == OpConst64 {
return v.Args[1], v.Args[0].AuxInt
}
if v.Op == OpAdd64 && v.Args[1].Op == OpConst64 {
return v.Args[0], v.Args[1].AuxInt
}
return v, 0
}
func isGreaterOrEqualThan(v *Value, c int64) bool {
if c == 0 {
return isNonNegative(v)
}
if v.isGenericIntConst() && v.AuxInt >= c {
return true
}
return false
}