]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile: rewrite the constant parts of the prove pass
authorkhr@golang.org <khr@golang.org>
Sat, 15 Jun 2024 05:29:09 +0000 (22:29 -0700)
committerKeith Randall <khr@golang.org>
Wed, 7 Aug 2024 16:07:33 +0000 (16:07 +0000)
Handles a lot more cases where constant ranges can eliminate
various (mostly bounds failure) paths.

Fixes #66826
Fixes #66692
Fixes #48213
Update #57959

TODO: remove constant logic from poset code, no longer needed.

Change-Id: Id196436fcd8a0c84c7d59c04f93bd92e26a0fd7e
Reviewed-on: https://go-review.googlesource.com/c/go/+/599096
Reviewed-by: David Chase <drchase@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
src/cmd/compile/internal/ssa/func.go
src/cmd/compile/internal/ssa/prove.go
src/cmd/compile/internal/ssa/rewrite.go
test/checkbce.go
test/codegen/comparisons.go
test/loopbce.go
test/prove.go
test/prove_constant_folding.go

index 62472cc94eb8a557ab9a56f36e97c3ce95744b53..cd8900d19a83fab6c314f7f1ad64203f051684fb 100644 (file)
@@ -796,7 +796,7 @@ func (f *Func) invalidateCFG() {
 //     base.DebugHashMatch(this function's package.name)
 //
 // for use in bug isolation.  The return value is true unless
-// environment variable GOSSAHASH is set, in which case "it depends".
+// environment variable GOCOMPILEDEBUG=gossahash=X is set, in which case "it depends on X".
 // See [base.DebugHashMatch] for more information.
 func (f *Func) DebugHashMatch() bool {
        if !base.HasDebugHash() {
index af69086981261b80d85b2fd4eaa158d2a1014252..df3566985fa25256a41228bc068ef4588067860b 100644 (file)
@@ -22,6 +22,19 @@ const (
        jumpTable0
 )
 
+func (b branch) String() string {
+       switch b {
+       case unknown:
+               return "unk"
+       case positive:
+               return "pos"
+       case negative:
+               return "neg"
+       default:
+               return fmt.Sprintf("jmp%d", b-jumpTable0)
+       }
+}
+
 // relation represents the set of possible relations between
 // pairs of variables (v, w). Without a priori knowledge the
 // mask is lt | eq | gt meaning v can be less than, equal to or
@@ -139,6 +152,165 @@ func (l limit) intersect(l2 limit) limit {
        return l
 }
 
+func (l limit) signedMin(m int64) limit {
+       if l.min < m {
+               l.min = m
+       }
+       return l
+}
+func (l limit) signedMax(m int64) limit {
+       if l.max > m {
+               l.max = m
+       }
+       return l
+}
+func (l limit) signedMinMax(min, max int64) limit {
+       if l.min < min {
+               l.min = min
+       }
+       if l.max > max {
+               l.max = max
+       }
+       return l
+}
+
+func (l limit) unsignedMin(m uint64) limit {
+       if l.umin < m {
+               l.umin = m
+       }
+       return l
+}
+func (l limit) unsignedMax(m uint64) limit {
+       if l.umax > m {
+               l.umax = m
+       }
+       return l
+}
+func (l limit) unsignedMinMax(min, max uint64) limit {
+       if l.umin < min {
+               l.umin = min
+       }
+       if l.umax > max {
+               l.umax = max
+       }
+       return l
+}
+
+func (l limit) nonzero() bool {
+       return l.min > 0 || l.umin > 0 || l.max < 0
+}
+func (l limit) nonnegative() bool {
+       return l.min >= 0
+}
+func (l limit) unsat() bool {
+       return l.min > l.max || l.umin > l.umax
+}
+
+// If x and y can add without overflow or underflow
+// (using b bits), safeAdd returns x+y, true.
+// Otherwise, returns 0, false.
+func safeAdd(x, y int64, b uint) (int64, bool) {
+       s := x + y
+       if x >= 0 && y >= 0 && s < 0 {
+               return 0, false // 64-bit overflow
+       }
+       if x < 0 && y < 0 && s >= 0 {
+               return 0, false // 64-bit underflow
+       }
+       if !fitsInBits(s, b) {
+               return 0, false
+       }
+       return s, true
+}
+
+// same as safeAdd for unsigned arithmetic.
+func safeAddU(x, y uint64, b uint) (uint64, bool) {
+       s := x + y
+       if s < x || s < y {
+               return 0, false // 64-bit overflow
+       }
+       if !fitsInBitsU(s, b) {
+               return 0, false
+       }
+       return s, true
+}
+
+// same as safeAdd but for subtraction.
+func safeSub(x, y int64, b uint) (int64, bool) {
+       if y == math.MinInt64 {
+               if x == math.MaxInt64 {
+                       return 0, false // 64-bit overflow
+               }
+               x++
+               y++
+       }
+       return safeAdd(x, -y, b)
+}
+
+// same as safeAddU but for subtraction.
+func safeSubU(x, y uint64, b uint) (uint64, bool) {
+       if x < y {
+               return 0, false // 64-bit underflow
+       }
+       s := x - y
+       if !fitsInBitsU(s, b) {
+               return 0, false
+       }
+       return s, true
+}
+
+// fitsInBits reports whether x fits in b bits (signed).
+func fitsInBits(x int64, b uint) bool {
+       if b == 64 {
+               return true
+       }
+       m := int64(-1) << (b - 1)
+       M := -m - 1
+       return x >= m && x <= M
+}
+
+// fitsInBitsU reports whether x fits in b bits (unsigned).
+func fitsInBitsU(x uint64, b uint) bool {
+       return x>>b == 0
+}
+
+// add returns the limit obtained by adding a value with limit l
+// to a value with limit l2. The result must fit in b bits.
+func (l limit) add(l2 limit, b uint) limit {
+       r := noLimit
+       min, minOk := safeAdd(l.min, l2.min, b)
+       max, maxOk := safeAdd(l.max, l2.max, b)
+       if minOk && maxOk {
+               r.min = min
+               r.max = max
+       }
+       umin, uminOk := safeAddU(l.umin, l2.umin, b)
+       umax, umaxOk := safeAddU(l.umax, l2.umax, b)
+       if uminOk && umaxOk {
+               r.umin = umin
+               r.umax = umax
+       }
+       return r
+}
+
+// same as add but for subtraction.
+func (l limit) sub(l2 limit, b uint) limit {
+       r := noLimit
+       min, minOk := safeSub(l.min, l2.max, b)
+       max, maxOk := safeSub(l.max, l2.min, b)
+       if minOk && maxOk {
+               r.min = min
+               r.max = max
+       }
+       umin, uminOk := safeSubU(l.umin, l2.umax, b)
+       umax, umaxOk := safeSubU(l.umax, l2.umin, b)
+       if uminOk && umaxOk {
+               r.umin = umin
+               r.umax = umax
+       }
+       return r
+}
+
 var noLimit = limit{math.MinInt64, math.MaxInt64, 0, math.MaxUint64}
 
 // a limitFact is a limit known for a particular value.
@@ -147,6 +319,15 @@ type limitFact struct {
        limit limit
 }
 
+// An ordering encodes facts like v < w.
+type ordering struct {
+       next *ordering // linked list of all known orderings for v.
+       // Note: v is implicit here, determined by which linked list it is in.
+       w *Value
+       d domain   // one of signed or unsigned
+       r relation // one of ==,!=,<,<=,>,>=
+}
+
 // factsTable keeps track of relations between pairs of values.
 //
 // The fact table logic is sound, but incomplete. Outside of a few
@@ -172,18 +353,27 @@ type factsTable struct {
        orderS *poset
        orderU *poset
 
-       // known lower and upper bounds on individual values.
-       limits     []limit     // indexed by value ID
-       limitStack []limitFact // previous entries
+       // orderings contains a list of known orderings between values.
+       // These lists are indexed by v.ID.
+       // We do not record transitive orderings. Only explicitly learned
+       // orderings are recorded. Transitive orderings can be obtained
+       // by walking along the individual orderings.
+       orderings map[ID]*ordering
+       // stack of IDs which have had an entry added in orderings.
+       // In addition, ID==0 are checkpoint markers.
+       orderingsStack []ID
+       orderingCache  *ordering // unused ordering records
+
+       // known lower and upper constant bounds on individual values.
+       limits       []limit     // indexed by value ID
+       limitStack   []limitFact // previous entries
+       recurseCheck []bool      // recursion detector for limit propagation
 
        // For each slice s, a map from s to a len(s)/cap(s) value (if any)
        // TODO: check if there are cases that matter where we have
        // more than one len(s) for a slice. We could keep a list if necessary.
        lens map[ID]*Value
        caps map[ID]*Value
-
-       // zero is a zero-valued constant
-       zero *Value
 }
 
 // checkpointFact is an invalid value used for checkpointing
@@ -197,17 +387,179 @@ func newFactsTable(f *Func) *factsTable {
        ft.orderU = f.newPoset()
        ft.orderS.SetUnsigned(false)
        ft.orderU.SetUnsigned(true)
+       ft.orderings = make(map[ID]*ordering)
        ft.facts = make(map[pair]relation)
        ft.stack = make([]fact, 4)
        ft.limits = f.Cache.allocLimitSlice(f.NumValues())
-       ft.limitStack = make([]limitFact, 4)
-       ft.zero = f.ConstInt64(f.Config.Types.Int64, 0)
-       for i := range ft.limits {
-               ft.limits[i] = noLimit
+       for _, b := range f.Blocks {
+               for _, v := range b.Values {
+                       ft.limits[v.ID] = initLimit(v)
+               }
        }
+       ft.limitStack = make([]limitFact, 4)
+       ft.recurseCheck = f.Cache.allocBoolSlice(f.NumValues())
        return ft
 }
 
+// signedMin records the fact that we know v is at least
+// min in the signed domain.
+func (ft *factsTable) signedMin(v *Value, min int64) bool {
+       return ft.newLimit(v, limit{min: min, max: math.MaxInt64, umin: 0, umax: math.MaxUint64})
+}
+
+// signedMax records the fact that we know v is at most
+// max in the signed domain.
+func (ft *factsTable) signedMax(v *Value, max int64) bool {
+       return ft.newLimit(v, limit{min: math.MinInt64, max: max, umin: 0, umax: math.MaxUint64})
+}
+func (ft *factsTable) signedMinMax(v *Value, min, max int64) bool {
+       return ft.newLimit(v, limit{min: min, max: max, umin: 0, umax: math.MaxUint64})
+}
+
+// setNonNegative records the fact that v is known to be non-negative.
+func (ft *factsTable) setNonNegative(v *Value) bool {
+       return ft.signedMin(v, 0)
+}
+
+// unsignedMin records the fact that we know v is at least
+// min in the unsigned domain.
+func (ft *factsTable) unsignedMin(v *Value, min uint64) bool {
+       return ft.newLimit(v, limit{min: math.MinInt64, max: math.MaxInt64, umin: min, umax: math.MaxUint64})
+}
+
+// unsignedMax records the fact that we know v is at most
+// max in the unsigned domain.
+func (ft *factsTable) unsignedMax(v *Value, max uint64) bool {
+       return ft.newLimit(v, limit{min: math.MinInt64, max: math.MaxInt64, umin: 0, umax: max})
+}
+func (ft *factsTable) unsignedMinMax(v *Value, min, max uint64) bool {
+       return ft.newLimit(v, limit{min: math.MinInt64, max: math.MaxInt64, umin: min, umax: max})
+}
+
+// newLimit adds new limiting information for v.
+// Returns true if the new limit added any new information.
+func (ft *factsTable) newLimit(v *Value, newLim limit) bool {
+       oldLim := ft.limits[v.ID]
+
+       // Merge old and new information.
+       lim := oldLim.intersect(newLim)
+
+       // signed <-> unsigned propagation
+       if lim.min >= 0 {
+               lim = lim.unsignedMinMax(uint64(lim.min), uint64(lim.max))
+       }
+       if fitsInBitsU(lim.umax, uint(8*v.Type.Size()-1)) {
+               lim = lim.signedMinMax(int64(lim.umin), int64(lim.umax))
+       }
+
+       if lim == oldLim {
+               return false // nothing new to record
+       }
+
+       if lim.unsat() {
+               ft.unsat = true
+               return true
+       }
+
+       // Check for recursion. This normally happens because in unsatisfiable
+       // cases we have a < b < a, and every update to a's limits returns
+       // here again with the limit increased by 2.
+       // Normally this is caught early by the orderS/orderU posets, but in
+       // cases where the comparisons jump between signed and unsigned domains,
+       // the posets will not notice.
+       if ft.recurseCheck[v.ID] {
+               // This should only happen for unsatisfiable cases. TODO: check
+               return false
+       }
+       ft.recurseCheck[v.ID] = true
+       defer func() {
+               ft.recurseCheck[v.ID] = false
+       }()
+
+       // Record undo information.
+       ft.limitStack = append(ft.limitStack, limitFact{v.ID, oldLim})
+       // Record new information.
+       ft.limits[v.ID] = lim
+       if v.Block.Func.pass.debug > 2 {
+               // TODO: pos is probably wrong. This is the position where v is defined,
+               // not the position where we learned the fact about it (which was
+               // probably some subsequent compare+branch).
+               v.Block.Func.Warnl(v.Pos, "new limit %s %s unsat=%v", v, lim.String(), ft.unsat)
+       }
+
+       // Propagate this new constant range to other values
+       // that we know are ordered with respect to this one.
+       // Note overflow/underflow in the arithmetic below is ok,
+       // it will just lead to imprecision (undetected unsatisfiability).
+       for o := ft.orderings[v.ID]; o != nil; o = o.next {
+               switch o.d {
+               case signed:
+                       switch o.r {
+                       case eq: // v == w
+                               ft.signedMinMax(o.w, lim.min, lim.max)
+                       case lt | eq: // v <= w
+                               ft.signedMin(o.w, lim.min)
+                       case lt: // v < w
+                               ft.signedMin(o.w, lim.min+1)
+                       case gt | eq: // v >= w
+                               ft.signedMax(o.w, lim.max)
+                       case gt: // v > w
+                               ft.signedMax(o.w, lim.max-1)
+                       case lt | gt: // v != w
+                               if lim.min == lim.max { // v is a constant
+                                       c := lim.min
+                                       if ft.limits[o.w.ID].min == c {
+                                               ft.signedMin(o.w, c+1)
+                                       }
+                                       if ft.limits[o.w.ID].max == c {
+                                               ft.signedMax(o.w, c-1)
+                                       }
+                               }
+                       }
+               case unsigned:
+                       switch o.r {
+                       case eq: // v == w
+                               ft.unsignedMinMax(o.w, lim.umin, lim.umax)
+                       case lt | eq: // v <= w
+                               ft.unsignedMin(o.w, lim.umin)
+                       case lt: // v < w
+                               ft.unsignedMin(o.w, lim.umin+1)
+                       case gt | eq: // v >= w
+                               ft.unsignedMax(o.w, lim.umax)
+                       case gt: // v > w
+                               ft.unsignedMax(o.w, lim.umax-1)
+                       case lt | gt: // v != w
+                               if lim.umin == lim.umax { // v is a constant
+                                       c := lim.umin
+                                       if ft.limits[o.w.ID].umin == c {
+                                               ft.unsignedMin(o.w, c+1)
+                                       }
+                                       if ft.limits[o.w.ID].umax == c {
+                                               ft.unsignedMax(o.w, c-1)
+                                       }
+                               }
+                       }
+               }
+       }
+
+       return true
+}
+
+func (ft *factsTable) addOrdering(v, w *Value, d domain, r relation) {
+       o := ft.orderingCache
+       if o == nil {
+               o = &ordering{}
+       } else {
+               ft.orderingCache = o.next
+       }
+       o.w = w
+       o.d = d
+       o.r = r
+       o.next = ft.orderings[v.ID]
+       ft.orderings[v.ID] = o
+       ft.orderingsStack = append(ft.orderingsStack, v.ID)
+}
+
 // update updates the set of relations between v and w in domain d
 // restricting it to r.
 func (ft *factsTable) update(parent *Block, v, w *Value, d domain, r relation) {
@@ -250,6 +602,9 @@ func (ft *factsTable) update(parent *Block, v, w *Value, d domain, r relation) {
                default:
                        panic("unknown relation")
                }
+               ft.addOrdering(v, w, d, r)
+               ft.addOrdering(w, v, d, reverseBits[r])
+
                if !ok {
                        if parent.Func.pass.debug > 2 {
                                parent.Func.Warnl(parent.Pos, "unsat %s %s %s", v, w, r)
@@ -288,106 +643,90 @@ func (ft *factsTable) update(parent *Block, v, w *Value, d domain, r relation) {
                }
        }
 
-       // Extract bounds when comparing against constants
-       if v.isGenericIntConst() {
-               v, w = w, v
-               r = reverseBits[r]
-       }
-       if v != nil && w.isGenericIntConst() {
+       // Extract new constant limits based on the comparison.
+       if d == signed || d == unsigned {
+               vLimit := ft.limits[v.ID]
+               wLimit := ft.limits[w.ID]
                // Note: all the +1/-1 below could overflow/underflow. Either will
                // still generate correct results, it will just lead to imprecision.
                // In fact if there is overflow/underflow, the corresponding
                // code is unreachable because the known range is outside the range
                // of the value's type.
-               old := ft.limits[v.ID]
-               if old == noLimit {
-                       if v.isGenericIntConst() {
-                               switch d {
-                               case signed:
-                                       old.min, old.max = v.AuxInt, v.AuxInt
-                                       if v.AuxInt >= 0 {
-                                               old.umin, old.umax = uint64(v.AuxInt), uint64(v.AuxInt)
-                                       }
-                               case unsigned:
-                                       old.umin = v.AuxUnsigned()
-                                       old.umax = old.umin
-                                       if int64(old.umin) >= 0 {
-                                               old.min, old.max = int64(old.umin), int64(old.umin)
-                                       }
-                               }
-                       }
-               }
-               lim := noLimit
                switch d {
                case signed:
-                       c := w.AuxInt
                        switch r {
-                       case lt:
-                               lim.max = c - 1
-                       case lt | eq:
-                               lim.max = c
-                       case gt | eq:
-                               lim.min = c
-                       case gt:
-                               lim.min = c + 1
-                       case lt | gt:
-                               lim = old
-                               if c == lim.min {
-                                       lim.min++
+                       case eq: // v == w
+                               ft.signedMinMax(v, wLimit.min, wLimit.max)
+                               ft.signedMinMax(w, vLimit.min, vLimit.max)
+                       case lt: // v < w
+                               ft.signedMax(v, wLimit.max-1)
+                               ft.signedMin(w, vLimit.min+1)
+                       case lt | eq: // v <= w
+                               ft.signedMax(v, wLimit.max)
+                               ft.signedMin(w, vLimit.min)
+                       case gt: // v > w
+                               ft.signedMin(v, wLimit.min+1)
+                               ft.signedMax(w, vLimit.max-1)
+                       case gt | eq: // v >= w
+                               ft.signedMin(v, wLimit.min)
+                               ft.signedMax(w, vLimit.max)
+                       case lt | gt: // v != w
+                               if vLimit.min == vLimit.max { // v is a constant
+                                       c := vLimit.min
+                                       if wLimit.min == c {
+                                               ft.signedMin(w, c+1)
+                                       }
+                                       if wLimit.max == c {
+                                               ft.signedMax(w, c-1)
+                                       }
                                }
-                               if c == lim.max {
-                                       lim.max--
+                               if wLimit.min == wLimit.max { // w is a constant
+                                       c := wLimit.min
+                                       if vLimit.min == c {
+                                               ft.signedMin(v, c+1)
+                                       }
+                                       if vLimit.max == c {
+                                               ft.signedMax(v, c-1)
+                                       }
                                }
-                       case eq:
-                               lim.min = c
-                               lim.max = c
-                       }
-                       if lim.min >= 0 {
-                               // int(x) >= 0 && int(x) >= N  â‡’  uint(x) >= N
-                               lim.umin = uint64(lim.min)
-                       }
-                       if lim.max != noLimit.max && old.min >= 0 && lim.max >= 0 {
-                               // 0 <= int(x) <= N  â‡’  0 <= uint(x) <= N
-                               // This is for a max update, so the lower bound
-                               // comes from what we already know (old).
-                               lim.umax = uint64(lim.max)
                        }
                case unsigned:
-                       uc := w.AuxUnsigned()
                        switch r {
-                       case lt:
-                               lim.umax = uc - 1
-                       case lt | eq:
-                               lim.umax = uc
-                       case gt | eq:
-                               lim.umin = uc
-                       case gt:
-                               lim.umin = uc + 1
-                       case lt | gt:
-                               lim = old
-                               if uc == lim.umin {
-                                       lim.umin++
+                       case eq: // v == w
+                               ft.unsignedMinMax(v, wLimit.umin, wLimit.umax)
+                               ft.unsignedMinMax(w, vLimit.umin, vLimit.umax)
+                       case lt: // v < w
+                               ft.unsignedMax(v, wLimit.umax-1)
+                               ft.unsignedMin(w, vLimit.umin+1)
+                       case lt | eq: // v <= w
+                               ft.unsignedMax(v, wLimit.umax)
+                               ft.unsignedMin(w, vLimit.umin)
+                       case gt: // v > w
+                               ft.unsignedMin(v, wLimit.umin+1)
+                               ft.unsignedMax(w, vLimit.umax-1)
+                       case gt | eq: // v >= w
+                               ft.unsignedMin(v, wLimit.umin)
+                               ft.unsignedMax(w, vLimit.umax)
+                       case lt | gt: // v != w
+                               if vLimit.umin == vLimit.umax { // v is a constant
+                                       c := vLimit.umin
+                                       if wLimit.umin == c {
+                                               ft.unsignedMin(w, c+1)
+                                       }
+                                       if wLimit.umax == c {
+                                               ft.unsignedMax(w, c-1)
+                                       }
                                }
-                               if uc == lim.umax {
-                                       lim.umax--
+                               if wLimit.umin == wLimit.umax { // w is a constant
+                                       c := wLimit.umin
+                                       if vLimit.umin == c {
+                                               ft.unsignedMin(v, c+1)
+                                       }
+                                       if vLimit.umax == c {
+                                               ft.unsignedMax(v, c-1)
+                                       }
                                }
-                       case eq:
-                               lim.umin = uc
-                               lim.umax = uc
                        }
-                       // We could use the contrapositives of the
-                       // signed implications to derive signed facts,
-                       // but it turns out not to matter.
-               }
-               ft.limitStack = append(ft.limitStack, limitFact{v.ID, old})
-               lim = old.intersect(lim)
-               ft.limits[v.ID] = lim
-               if v.Block.Func.pass.debug > 2 {
-                       v.Block.Func.Warnl(parent.Pos, "parent=%s, new limits %s %s %s %s", parent, v, w, r, lim.String())
-               }
-               if lim.min > lim.max || lim.umin > lim.umax {
-                       ft.unsat = true
-                       return
                }
        }
 
@@ -503,57 +842,45 @@ func (ft *factsTable) update(parent *Block, v, w *Value, d domain, r relation) {
                                //
                                // Notice the conditions for max are still <=, as they handle overflows.
                                var min, max int64
-                               var vmin, vmax *Value
                                switch x.Type.Size() {
                                case 8:
                                        min = w.AuxInt - delta
                                        max = int64(^uint64(0)>>1) - delta
-
-                                       vmin = parent.NewValue0I(parent.Pos, OpConst64, parent.Func.Config.Types.Int64, min)
-                                       vmax = parent.NewValue0I(parent.Pos, OpConst64, parent.Func.Config.Types.Int64, max)
-
                                case 4:
                                        min = int64(int32(w.AuxInt) - int32(delta))
                                        max = int64(int32(^uint32(0)>>1) - int32(delta))
-
-                                       vmin = parent.NewValue0I(parent.Pos, OpConst32, parent.Func.Config.Types.Int32, min)
-                                       vmax = parent.NewValue0I(parent.Pos, OpConst32, parent.Func.Config.Types.Int32, max)
-
                                case 2:
                                        min = int64(int16(w.AuxInt) - int16(delta))
                                        max = int64(int16(^uint16(0)>>1) - int16(delta))
-
-                                       vmin = parent.NewValue0I(parent.Pos, OpConst16, parent.Func.Config.Types.Int16, min)
-                                       vmax = parent.NewValue0I(parent.Pos, OpConst16, parent.Func.Config.Types.Int16, max)
-
                                case 1:
                                        min = int64(int8(w.AuxInt) - int8(delta))
                                        max = int64(int8(^uint8(0)>>1) - int8(delta))
-
-                                       vmin = parent.NewValue0I(parent.Pos, OpConst8, parent.Func.Config.Types.Int8, min)
-                                       vmax = parent.NewValue0I(parent.Pos, OpConst8, parent.Func.Config.Types.Int8, max)
-
                                default:
                                        panic("unimplemented")
                                }
 
                                if min < max {
                                        // Record that x > min and max >= x
-                                       ft.update(parent, x, vmin, d, r)
-                                       ft.update(parent, vmax, x, d, r|eq)
+                                       if r == gt {
+                                               min++
+                                       }
+                                       ft.signedMinMax(x, min, max)
                                } else {
                                        // We know that either x>min OR x<=max. factsTable cannot record OR conditions,
                                        // so let's see if we can already prove that one of them is false, in which case
                                        // the other must be true
+                                       if r == gt {
+                                               min++
+                                       }
                                        if l := ft.limits[x.ID]; l != noLimit {
                                                if l.max <= min {
                                                        if r&eq == 0 || l.max < min {
                                                                // x>min (x>=min) is impossible, so it must be x<=max
-                                                               ft.update(parent, vmax, x, d, r|eq)
+                                                               ft.signedMax(x, max)
                                                        }
                                                } else if l.min > max {
                                                        // x<=max is impossible, so it must be x>min
-                                                       ft.update(parent, x, vmin, d, r)
+                                                       ft.signedMin(x, min)
                                                }
                                        }
                                }
@@ -599,49 +926,7 @@ var opUMax = map[Op]uint64{
 
 // isNonNegative reports whether v is known to be non-negative.
 func (ft *factsTable) isNonNegative(v *Value) bool {
-       if isNonNegative(v) {
-               return true
-       }
-
-       var max int64
-       switch v.Type.Size() {
-       case 1:
-               max = math.MaxInt8
-       case 2:
-               max = math.MaxInt16
-       case 4:
-               max = math.MaxInt32
-       case 8:
-               max = math.MaxInt64
-       default:
-               panic("unexpected integer size")
-       }
-
-       // Check if the recorded limits can prove that the value is positive
-
-       if l := ft.limits[v.ID]; l != noLimit && (l.min >= 0 || l.umax <= uint64(max)) {
-               return true
-       }
-
-       // Check if v = x+delta, and we can use x's limits to prove that it's positive
-       if x, delta := isConstDelta(v); x != nil {
-               if l := ft.limits[x.ID]; l != noLimit {
-                       if delta > 0 && l.min >= -delta && l.max <= max-delta {
-                               return true
-                       }
-                       if delta < 0 && l.min >= -delta {
-                               return true
-                       }
-               }
-       }
-
-       // Check if v is a value-preserving extension of a non-negative value.
-       if isCleanExt(v) && ft.isNonNegative(v.Args[0]) {
-               return true
-       }
-
-       // Check if the signed poset can prove that the value is >= 0
-       return ft.orderS.OrderedOrEqual(ft.zero, v)
+       return ft.limits[v.ID].min >= 0
 }
 
 // checkpoint saves the current state of known relations.
@@ -654,6 +939,7 @@ func (ft *factsTable) checkpoint() {
        ft.limitStack = append(ft.limitStack, checkpointBound)
        ft.orderS.Checkpoint()
        ft.orderU.Checkpoint()
+       ft.orderingsStack = append(ft.orderingsStack, 0)
 }
 
 // restore restores known relation to the state just
@@ -687,6 +973,17 @@ func (ft *factsTable) restore() {
        }
        ft.orderS.Undo()
        ft.orderU.Undo()
+       for {
+               id := ft.orderingsStack[len(ft.orderingsStack)-1]
+               ft.orderingsStack = ft.orderingsStack[:len(ft.orderingsStack)-1]
+               if id == 0 { // checkpoint marker
+                       break
+               }
+               o := ft.orderings[id]
+               ft.orderings[id] = o.next
+               o.next = ft.orderingCache
+               ft.orderingCache = o
+       }
 }
 
 func lessByID(v, w *Value) bool {
@@ -764,6 +1061,7 @@ func (ft *factsTable) cleanup(f *Func) {
                f.retPoset(po)
        }
        f.Cache.freeLimitSlice(ft.limits)
+       f.Cache.freeBoolSlice(ft.recurseCheck)
 }
 
 // prove removes redundant BlockIf branches that can be inferred
@@ -944,9 +1242,6 @@ func prove(f *Func) {
        ft := newFactsTable(f)
        ft.checkpoint()
 
-       var lensVars map[*Block][]*Value
-       var logicVars map[*Block][]*Value
-
        // Find length and capacity ops.
        for _, b := range f.Blocks {
                for _, v := range b.Values {
@@ -956,8 +1251,6 @@ func prove(f *Func) {
                                continue
                        }
                        switch v.Op {
-                       case OpStringLen:
-                               ft.update(b, v, ft.zero, signed, gt|eq)
                        case OpSliceLen:
                                if ft.lens == nil {
                                        ft.lens = map[ID]*Value{}
@@ -970,13 +1263,6 @@ func prove(f *Func) {
                                } else {
                                        ft.lens[v.Args[0].ID] = v
                                }
-                               ft.update(b, v, ft.zero, signed, gt|eq)
-                               if v.Args[0].Op == OpSliceMake {
-                                       if lensVars == nil {
-                                               lensVars = make(map[*Block][]*Value)
-                                       }
-                                       lensVars[b] = append(lensVars[b], v)
-                               }
                        case OpSliceCap:
                                if ft.caps == nil {
                                        ft.caps = map[ID]*Value{}
@@ -987,92 +1273,6 @@ func prove(f *Func) {
                                } else {
                                        ft.caps[v.Args[0].ID] = v
                                }
-                               ft.update(b, v, ft.zero, signed, gt|eq)
-                               if v.Args[0].Op == OpSliceMake {
-                                       if lensVars == nil {
-                                               lensVars = make(map[*Block][]*Value)
-                                       }
-                                       lensVars[b] = append(lensVars[b], v)
-                               }
-                       case OpCtz64, OpCtz32, OpCtz16, OpCtz8, OpBitLen64, OpBitLen32, OpBitLen16, OpBitLen8:
-                               ft.update(b, v, ft.zero, signed, gt|eq)
-                               // TODO: we could also do <= 64/32/16/8, if that helped.
-                       case OpAnd64, OpAnd32, OpAnd16, OpAnd8:
-                               ft.update(b, v, v.Args[1], unsigned, lt|eq)
-                               ft.update(b, v, v.Args[0], unsigned, lt|eq)
-                               for i := 0; i < 2; i++ {
-                                       if isNonNegative(v.Args[i]) {
-                                               ft.update(b, v, v.Args[i], signed, lt|eq)
-                                               ft.update(b, v, ft.zero, signed, gt|eq)
-                                       }
-                               }
-                               if logicVars == nil {
-                                       logicVars = make(map[*Block][]*Value)
-                               }
-                               logicVars[b] = append(logicVars[b], v)
-                       case OpOr64, OpOr32, OpOr16, OpOr8:
-                               // TODO: investigate how to always add facts without much slowdown, see issue #57959.
-                               if v.Args[0].isGenericIntConst() {
-                                       ft.update(b, v, v.Args[0], unsigned, gt|eq)
-                               }
-                               if v.Args[1].isGenericIntConst() {
-                                       ft.update(b, v, v.Args[1], unsigned, gt|eq)
-                               }
-                       case OpDiv64u, OpDiv32u, OpDiv16u, OpDiv8u,
-                               OpRsh8Ux64, OpRsh8Ux32, OpRsh8Ux16, OpRsh8Ux8,
-                               OpRsh16Ux64, OpRsh16Ux32, OpRsh16Ux16, OpRsh16Ux8,
-                               OpRsh32Ux64, OpRsh32Ux32, OpRsh32Ux16, OpRsh32Ux8,
-                               OpRsh64Ux64, OpRsh64Ux32, OpRsh64Ux16, OpRsh64Ux8:
-                               ft.update(b, v, v.Args[0], unsigned, lt|eq)
-                       case OpMod64u, OpMod32u, OpMod16u, OpMod8u:
-                               ft.update(b, v, v.Args[0], unsigned, lt|eq)
-                               ft.update(b, v, v.Args[1], unsigned, lt)
-                       case OpPhi:
-                               // Determine the min and max value of OpPhi composed entirely of integer constants.
-                               //
-                               // For example, for an OpPhi:
-                               //
-                               // v1 = OpConst64 [13]
-                               // v2 = OpConst64 [7]
-                               // v3 = OpConst64 [42]
-                               //
-                               // v4 = OpPhi(v1, v2, v3)
-                               //
-                               // We can prove:
-                               //
-                               // v4 >= 7 && v4 <= 42
-                               //
-                               // TODO(jake-ciolek): Handle nested constant OpPhi's
-                               sameConstOp := true
-                               min := 0
-                               max := 0
-
-                               if !v.Args[min].isGenericIntConst() {
-                                       break
-                               }
-
-                               for k := range v.Args {
-                                       if v.Args[k].Op != v.Args[min].Op {
-                                               sameConstOp = false
-                                               break
-                                       }
-                                       if v.Args[k].AuxInt < v.Args[min].AuxInt {
-                                               min = k
-                                       }
-                                       if v.Args[k].AuxInt > v.Args[max].AuxInt {
-                                               max = k
-                                       }
-                               }
-
-                               if sameConstOp {
-                                       ft.update(b, v, v.Args[min], signed, gt|eq)
-                                       ft.update(b, v, v.Args[max], signed, lt|eq)
-                               }
-                               // One might be tempted to create a v >= ft.zero relation for
-                               // all OpPhi's composed of only provably-positive values
-                               // but that bloats up the facts table for a very negligible gain.
-                               // In Go itself, very few functions get improved (< 5) at a cost of 5-7% total increase
-                               // of compile time.
                        }
                }
        }
@@ -1117,54 +1317,32 @@ func prove(f *Func) {
                case descend:
                        ft.checkpoint()
 
-                       // Entering the block, add the block-depending facts that we collected
-                       // at the beginning: induction variables and lens/caps of slices.
+                       // Entering the block, add facts about the induction variable
+                       // that is bound to this block.
                        if iv, ok := indVars[node.block]; ok {
                                addIndVarRestrictions(ft, parent, iv)
                        }
-                       if lens, ok := lensVars[node.block]; ok {
-                               for _, v := range lens {
-                                       switch v.Op {
-                                       case OpSliceLen:
-                                               ft.update(node.block, v, v.Args[0].Args[1], signed, eq)
-                                       case OpSliceCap:
-                                               ft.update(node.block, v, v.Args[0].Args[2], signed, eq)
-                                       }
-                               }
-                       }
 
+                       // Add results of reaching this block via a branch from
+                       // its immediate dominator (if any).
                        if branch != unknown {
                                addBranchRestrictions(ft, parent, branch)
-                               // After we add the branch restriction, re-check the logic operations in the parent block,
-                               // it may give us more info to omit some branches
-                               if logic, ok := logicVars[parent]; ok {
-                                       for _, v := range logic {
-                                               // we only have OpAnd for now
-                                               ft.update(parent, v, v.Args[1], unsigned, lt|eq)
-                                               ft.update(parent, v, v.Args[0], unsigned, lt|eq)
-                                               for i := 0; i < 2; i++ {
-                                                       if isNonNegative(v.Args[i]) {
-                                                               ft.update(parent, v, v.Args[i], signed, lt|eq)
-                                                               ft.update(parent, v, ft.zero, signed, gt|eq)
-                                                       }
-                                               }
-                                       }
-                               }
-                               if ft.unsat {
-                                       // node.block is unreachable.
-                                       // Remove it and don't visit
-                                       // its children.
-                                       removeBranch(parent, branch)
-                                       ft.restore()
-                                       break
-                               }
-                               // Otherwise, we can now commit to
-                               // taking this branch. We'll restore
-                               // ft when we unwind.
                        }
 
-                       // Add inductive facts for phis in this block.
-                       addLocalInductiveFacts(ft, node.block)
+                       // Add facts about the values in the current block.
+                       addLocalFacts(ft, node.block)
+
+                       if ft.unsat {
+                               // node.block is unreachable.
+                               // Remove it and don't visit
+                               // its children.
+                               removeBranch(parent, branch)
+                               ft.restore()
+                               break
+                       }
+                       // Otherwise, we can now commit to
+                       // taking this branch. We'll restore
+                       // ft when we unwind.
 
                        work = append(work, bp{
                                block: node.block,
@@ -1188,6 +1366,212 @@ func prove(f *Func) {
        ft.cleanup(f)
 }
 
+// initLimit sets initial constant limit for v.  This limit is based
+// only on the operation itself, not any of its input arguments. This
+// method is only called once on prove pass startup (unlike
+// flowLimit, below, which computes additional constraints based on
+// ranges of opcode arguments).
+func initLimit(v *Value) limit {
+       if !v.Type.IsInteger() {
+               // TODO: boolean?
+               return noLimit
+       }
+
+       // Default limits based on type.
+       var lim limit
+       switch v.Type.Size() {
+       case 8:
+               lim = limit{min: math.MinInt64, max: math.MaxInt64, umin: 0, umax: math.MaxUint64}
+       case 4:
+               lim = limit{min: math.MinInt32, max: math.MaxInt32, umin: 0, umax: math.MaxUint32}
+       case 2:
+               lim = limit{min: math.MinInt16, max: math.MaxInt16, umin: 0, umax: math.MaxUint16}
+       case 1:
+               lim = limit{min: math.MinInt8, max: math.MaxInt8, umin: 0, umax: math.MaxUint8}
+       default:
+               panic("bad")
+       }
+
+       // Tighter limits on some opcodes.
+       switch v.Op {
+       // constants
+       case OpConst64:
+               lim = limit{min: v.AuxInt, max: v.AuxInt, umin: uint64(v.AuxInt), umax: uint64(v.AuxInt)}
+       case OpConst32:
+               lim = limit{min: v.AuxInt, max: v.AuxInt, umin: uint64(uint32(v.AuxInt)), umax: uint64(uint32(v.AuxInt))}
+       case OpConst16:
+               lim = limit{min: v.AuxInt, max: v.AuxInt, umin: uint64(uint16(v.AuxInt)), umax: uint64(uint16(v.AuxInt))}
+       case OpConst8:
+               lim = limit{min: v.AuxInt, max: v.AuxInt, umin: uint64(uint8(v.AuxInt)), umax: uint64(uint8(v.AuxInt))}
+
+       // extensions
+       case OpZeroExt8to64, OpZeroExt8to32, OpZeroExt8to16:
+               lim = lim.signedMinMax(0, 1<<8-1)
+               lim = lim.unsignedMax(1<<8 - 1)
+       case OpZeroExt16to64, OpZeroExt16to32:
+               lim = lim.signedMinMax(0, 1<<16-1)
+               lim = lim.unsignedMax(1<<16 - 1)
+       case OpZeroExt32to64:
+               lim = lim.signedMinMax(0, 1<<32-1)
+               lim = lim.unsignedMax(1<<32 - 1)
+       case OpSignExt8to64, OpSignExt8to32, OpSignExt8to16:
+               lim = lim.signedMinMax(math.MinInt8, math.MaxInt8)
+       case OpSignExt16to64, OpSignExt16to32:
+               lim = lim.signedMinMax(math.MinInt16, math.MaxInt16)
+       case OpSignExt32to64:
+               lim = lim.signedMinMax(math.MinInt32, math.MaxInt32)
+
+       // math/bits intrinsics
+       case OpCtz64, OpBitLen64:
+               lim = lim.unsignedMax(64)
+       case OpCtz32, OpBitLen32:
+               lim = lim.unsignedMax(32)
+       case OpCtz16, OpBitLen16:
+               lim = lim.unsignedMax(16)
+       case OpCtz8, OpBitLen8:
+               lim = lim.unsignedMax(8)
+
+       // length operations
+       case OpStringLen, OpSliceLen, OpSliceCap:
+               lim = lim.signedMin(0)
+       }
+
+       // signed <-> unsigned propagation
+       if lim.min >= 0 {
+               lim = lim.unsignedMinMax(uint64(lim.min), uint64(lim.max))
+       }
+       if fitsInBitsU(lim.umax, uint(8*v.Type.Size()-1)) {
+               lim = lim.signedMinMax(int64(lim.umin), int64(lim.umax))
+       }
+
+       return lim
+}
+
+// flowLimit updates the known limits of v in ft. Returns true if anything changed.
+// flowLimit can use the ranges of input arguments.
+//
+// Note: this calculation only happens at the point the value is defined. We do not reevaluate
+// it later. So for example:
+//
+//     v := x + y
+//     if 0 <= x && x < 5 && 0 <= y && y < 5 { ... use v ... }
+//
+// we don't discover that the range of v is bounded in the conditioned
+// block. We could recompute the range of v once we enter the block so
+// we know that it is 0 <= v <= 8, but we don't have a mechanism to do
+// that right now.
+func (ft *factsTable) flowLimit(v *Value) bool {
+       if !v.Type.IsInteger() {
+               // TODO: boolean?
+               return false
+       }
+
+       // Additional limits based on opcode and argument.
+       // No need to repeat things here already done in initLimit.
+       switch v.Op {
+
+       // extensions
+       case OpZeroExt8to64, OpZeroExt8to32, OpZeroExt8to16, OpZeroExt16to64, OpZeroExt16to32, OpZeroExt32to64:
+               a := ft.limits[v.Args[0].ID]
+               return ft.unsignedMinMax(v, a.umin, a.umax)
+       case OpSignExt8to64, OpSignExt8to32, OpSignExt8to16, OpSignExt16to64, OpSignExt16to32, OpSignExt32to64:
+               a := ft.limits[v.Args[0].ID]
+               return ft.signedMinMax(v, a.min, a.max)
+
+       // math/bits
+       case OpCtz64:
+               a := ft.limits[v.Args[0].ID]
+               if a.nonzero() {
+                       return ft.unsignedMax(v, 63)
+               }
+       case OpCtz32:
+               a := ft.limits[v.Args[0].ID]
+               if a.nonzero() {
+                       return ft.unsignedMax(v, 31)
+               }
+       case OpCtz16:
+               a := ft.limits[v.Args[0].ID]
+               if a.nonzero() {
+                       return ft.unsignedMax(v, 15)
+               }
+       case OpCtz8:
+               a := ft.limits[v.Args[0].ID]
+               if a.nonzero() {
+                       return ft.unsignedMax(v, 7)
+               }
+
+       // Masks.
+       case OpAnd64, OpAnd32, OpAnd16, OpAnd8:
+               // AND can only make the value smaller.
+               a := ft.limits[v.Args[0].ID]
+               b := ft.limits[v.Args[1].ID]
+               return ft.unsignedMax(v, minU(a.umax, b.umax))
+       case OpOr64, OpOr32, OpOr16, OpOr8:
+               // OR can only make the value bigger.
+               a := ft.limits[v.Args[0].ID]
+               b := ft.limits[v.Args[1].ID]
+               return ft.unsignedMin(v, maxU(a.umin, b.umin))
+       case OpXor64, OpXor32, OpXor16, OpXor8:
+               // TODO: use leading/trailing zeroes?
+               // Not sure if it is worth it.
+
+       // Arithmetic.
+       case OpAdd64:
+               a := ft.limits[v.Args[0].ID]
+               b := ft.limits[v.Args[1].ID]
+               return ft.newLimit(v, a.add(b, 64))
+       case OpAdd32:
+               a := ft.limits[v.Args[0].ID]
+               b := ft.limits[v.Args[1].ID]
+               return ft.newLimit(v, a.add(b, 32))
+       case OpAdd16:
+               a := ft.limits[v.Args[0].ID]
+               b := ft.limits[v.Args[1].ID]
+               return ft.newLimit(v, a.add(b, 16))
+       case OpAdd8:
+               a := ft.limits[v.Args[0].ID]
+               b := ft.limits[v.Args[1].ID]
+               return ft.newLimit(v, a.add(b, 8))
+       case OpSub64:
+               a := ft.limits[v.Args[0].ID]
+               b := ft.limits[v.Args[1].ID]
+               return ft.newLimit(v, a.sub(b, 64))
+       case OpSub32:
+               a := ft.limits[v.Args[0].ID]
+               b := ft.limits[v.Args[1].ID]
+               return ft.newLimit(v, a.sub(b, 32))
+       case OpSub16:
+               a := ft.limits[v.Args[0].ID]
+               b := ft.limits[v.Args[1].ID]
+               return ft.newLimit(v, a.sub(b, 16))
+       case OpSub8:
+               a := ft.limits[v.Args[0].ID]
+               b := ft.limits[v.Args[1].ID]
+               return ft.newLimit(v, a.sub(b, 8))
+       case OpPhi:
+               // Compute the union of all the input phis.
+               // Often this will convey no information, because the block
+               // is not dominated by its predecessors and hence the
+               // phi arguments might not have been processed yet. But if
+               // the values are declared earlier, it may help. e.g., for
+               //    v = phi(c3, c5)
+               // where c3 = OpConst [3] and c5 = OpConst [5] are
+               // defined in the entry block, we can derive [3,5]
+               // as the limit for v.
+               l := ft.limits[v.Args[0].ID]
+               for _, a := range v.Args[1:] {
+                       l2 := ft.limits[a.ID]
+                       l.min = min(l.min, l2.min)
+                       l.max = max(l.max, l2.max)
+                       l.umin = minU(l.umin, l2.umin)
+                       l.umax = maxU(l.umax, l2.umax)
+               }
+               return ft.newLimit(v, l)
+       }
+       // TODO: mul/lsh, rsh, div/mod, and/or/xor
+       return false
+}
+
 // getBranch returns the range restrictions added by p
 // when reaching b. p is the immediate dominator of b.
 func getBranch(sdom SparseTree, p *Block, b *Block) branch {
@@ -1306,7 +1690,7 @@ func addBranchRestrictions(ft *factsTable, b *Block, br branch) {
                                }
                                addRestrictions(b, ft, d, c.Args[0], c.Args[1], tr.r^(lt|gt|eq))
                        case positive:
-                               addRestrictions(b, ft, signed, ft.zero, c.Args[0], lt|eq)
+                               ft.setNonNegative(c.Args[0])
                                addRestrictions(b, ft, d, c.Args[0], c.Args[1], tr.r)
                        }
                default:
@@ -1337,111 +1721,56 @@ func addRestrictions(parent *Block, ft *factsTable, t domain, v, w *Value, r rel
        }
 }
 
-// addLocalInductiveFacts adds inductive facts when visiting b, where
-// b is a join point in a loop. In contrast with findIndVar, this
-// depends on facts established for b, which is why it happens when
-// visiting b.
-//
-// TODO: It would be nice to combine this with findIndVar.
-func addLocalInductiveFacts(ft *factsTable, b *Block) {
-       // This looks for a specific pattern of induction:
-       //
-       // 1. i1 = OpPhi(min, i2) in b
-       // 2. i2 = i1 + 1
-       // 3. i2 < max at exit from b.Preds[1]
-       // 4. min < max
-       //
-       // If all of these conditions are true, then i1 < max and i1 >= min.
-
-       // To ensure this is a loop header node.
-       if len(b.Preds) != 2 {
-               return
-       }
-
-       for _, i1 := range b.Values {
-               if i1.Op != OpPhi {
-                       continue
-               }
-
-               // Check for conditions 1 and 2. This is easy to do
-               // and will throw out most phis.
-               min, i2 := i1.Args[0], i1.Args[1]
-               if i1q, delta := isConstDelta(i2); i1q != i1 || delta != 1 {
-                       continue
+func addLocalFacts(ft *factsTable, b *Block) {
+       // Propagate constant ranges among values in this block.
+       // We do this before the second loop so that we have the
+       // most up-to-date constant bounds for isNonNegative calls.
+       for {
+               changed := false
+               for _, v := range b.Values {
+                       changed = ft.flowLimit(v) || changed
                }
-
-               // Try to prove condition 3. We can't just query the
-               // fact table for this because we don't know what the
-               // facts of b.Preds[1] are (in general, b.Preds[1] is
-               // a loop-back edge, so we haven't even been there
-               // yet). As a conservative approximation, we look for
-               // this condition in the predecessor chain until we
-               // hit a join point.
-               uniquePred := func(b *Block) *Block {
-                       if len(b.Preds) == 1 {
-                               return b.Preds[0].b
-                       }
-                       return nil
+               if !changed {
+                       break
                }
-               pred, child := b.Preds[1].b, b
-               for ; pred != nil; pred, child = uniquePred(pred), pred {
-                       if pred.Kind != BlockIf {
-                               continue
-                       }
-                       control := pred.Controls[0]
-
-                       br := unknown
-                       if pred.Succs[0].b == child {
-                               br = positive
-                       }
-                       if pred.Succs[1].b == child {
-                               if br != unknown {
-                                       continue
-                               }
-                               br = negative
-                       }
-                       if br == unknown {
-                               continue
-                       }
+       }
 
-                       tr, has := domainRelationTable[control.Op]
-                       if !has {
-                               continue
+       // Add facts about individual operations.
+       for _, v := range b.Values {
+               switch v.Op {
+               case OpAnd64, OpAnd32, OpAnd16, OpAnd8:
+                       ft.update(b, v, v.Args[0], unsigned, lt|eq)
+                       ft.update(b, v, v.Args[1], unsigned, lt|eq)
+                       if ft.isNonNegative(v.Args[0]) {
+                               ft.update(b, v, v.Args[0], signed, lt|eq)
                        }
-                       r := tr.r
-                       if br == negative {
-                               // Negative branch taken to reach b.
-                               // Complement the relations.
-                               r = (lt | eq | gt) ^ r
+                       if ft.isNonNegative(v.Args[1]) {
+                               ft.update(b, v, v.Args[1], signed, lt|eq)
                        }
-
-                       // Check for i2 < max or max > i2.
-                       var max *Value
-                       if r == lt && control.Args[0] == i2 {
-                               max = control.Args[1]
-                       } else if r == gt && control.Args[1] == i2 {
-                               max = control.Args[0]
-                       } else {
-                               continue
+               case OpOr64, OpOr32, OpOr16, OpOr8:
+                       // TODO: investigate how to always add facts without much slowdown, see issue #57959
+                       //ft.update(b, v, v.Args[0], unsigned, gt|eq)
+                       //ft.update(b, v, v.Args[1], unsigned, gt|eq)
+               case OpDiv64u, OpDiv32u, OpDiv16u, OpDiv8u,
+                       OpRsh8Ux64, OpRsh8Ux32, OpRsh8Ux16, OpRsh8Ux8,
+                       OpRsh16Ux64, OpRsh16Ux32, OpRsh16Ux16, OpRsh16Ux8,
+                       OpRsh32Ux64, OpRsh32Ux32, OpRsh32Ux16, OpRsh32Ux8,
+                       OpRsh64Ux64, OpRsh64Ux32, OpRsh64Ux16, OpRsh64Ux8:
+                       ft.update(b, v, v.Args[0], unsigned, lt|eq)
+               case OpMod64u, OpMod32u, OpMod16u, OpMod8u:
+                       ft.update(b, v, v.Args[0], unsigned, lt|eq)
+                       // Note: we have to be careful that this doesn't imply
+                       // that the modulus is >0, which isn't true until *after*
+                       // the mod instruction executes (and thus panics if the
+                       // modulus is 0). See issue 67625.
+                       ft.update(b, v, v.Args[1], unsigned, lt)
+               case OpSliceLen:
+                       if v.Args[0].Op == OpSliceMake {
+                               ft.update(b, v, v.Args[0].Args[1], signed, eq)
                        }
-
-                       // Check condition 4 now that we have a
-                       // candidate max. For this we can query the
-                       // fact table. We "prove" min < max by showing
-                       // that min >= max is unsat. (This may simply
-                       // compare two constants; that's fine.)
-                       ft.checkpoint()
-                       ft.update(b, min, max, tr.d, gt|eq)
-                       proved := ft.unsat
-                       ft.restore()
-
-                       if proved {
-                               // We know that min <= i1 < max.
-                               if b.Func.pass.debug > 0 {
-                                       printIndVar(b, i1, min, max, 1, 0)
-                               }
-                               ft.update(b, min, i1, tr.d, lt|eq)
-                               ft.update(b, i1, max, tr.d, lt)
+               case OpSliceCap:
+                       if v.Args[0].Op == OpSliceMake {
+                               ft.update(b, v, v.Args[0].Args[2], signed, eq)
                        }
                }
        }
@@ -1544,8 +1873,17 @@ func simplifyBlock(sdom SparseTree, ft *factsTable, b *Block) {
                        }
                        bits := 8 * v.Args[0].Type.Size()
                        if lim.umax < uint64(bits) || (lim.max < bits && ft.isNonNegative(by)) {
+                               if by.isGenericIntConst() {
+                                       // TODO: get rid of this block.
+                                       // Currently this causes lowering to happen
+                                       // in different orders, which causes some
+                                       // problems for codegen tests for arm64
+                                       // where rule application order results
+                                       // in different final instructions.
+                                       break
+                               }
                                v.AuxInt = 1 // see shiftIsBounded
-                               if b.Func.pass.debug > 0 {
+                               if b.Func.pass.debug > 0 && !by.isGenericIntConst() {
                                        b.Func.Warnl(v.Pos, "Proved %v bounded", v.Op)
                                }
                        }
@@ -1692,6 +2030,7 @@ func removeBranch(b *Block, branch branch) {
 }
 
 // isNonNegative reports whether v is known to be greater or equal to zero.
+// TODO: no longer used by the prove pass; move elsewhere.
 func isNonNegative(v *Value) bool {
        if !v.Type.IsInteger() {
                v.Fatalf("isNonNegative bad type: %v", v.Type)
index 5bc03083628117d388128f6a4488575c517f3cc3..07004dea96052c66310e0a64e4d728d47c6431ea 100644 (file)
@@ -1188,6 +1188,8 @@ func logRule(s string) {
 
 var ruleFile io.Writer
 
+// TODO: replace these with the built-in min/max once they are available
+// during bootstrap (when bootstrapping with 1.21 or later).
 func min(x, y int64) int64 {
        if x < y {
                return x
@@ -1200,6 +1202,18 @@ func max(x, y int64) int64 {
        }
        return y
 }
+func minU(x, y uint64) uint64 {
+       if x < y {
+               return x
+       }
+       return y
+}
+func maxU(x, y uint64) uint64 {
+       if x > y {
+               return x
+       }
+       return y
+}
 
 func isConstZero(v *Value) bool {
        switch v.Op {
index 71acfb71ac17d46ec08acde3f0d878f40c8cec65..49d047443e09a4a61e1df08aaa67ae2c5f611e77 100644 (file)
@@ -159,16 +159,14 @@ func decode1(data []byte) (x uint64) {
 }
 
 func decode2(data []byte) (x uint64) {
-       // TODO(rasky): this should behave like decode1 and compile to no
-       // boundchecks. We're currently not able to remove all of them.
        for len(data) >= 32 {
                x += binary.BigEndian.Uint64(data)
                data = data[8:]
-               x += binary.BigEndian.Uint64(data) // ERROR "Found IsInBounds$"
+               x += binary.BigEndian.Uint64(data)
                data = data[8:]
-               x += binary.BigEndian.Uint64(data) // ERROR "Found IsInBounds$"
+               x += binary.BigEndian.Uint64(data)
                data = data[8:]
-               x += binary.BigEndian.Uint64(data) // ERROR "Found IsInBounds$"
+               x += binary.BigEndian.Uint64(data)
                data = data[8:]
        }
        return x
index e585045aa4730e27a4f275b2a9c1ad7fe00be54d..909cf954b14d3833de331022bb62b2bbeae14509 100644 (file)
@@ -268,7 +268,7 @@ func CmpToZero(a, b, d int32, e, f int64, deOptC0, deOptC1 bool) int32 {
        }
 }
 
-func CmpLogicalToZero(a, b, c uint32, d, e uint64) uint64 {
+func CmpLogicalToZero(a, b, c uint32, d, e, f, g uint64) uint64 {
 
        // ppc64x:"ANDCC",-"CMPW"
        // wasm:"I64Eqz",-"I32Eqz",-"I64ExtendI32U",-"I32WrapI64"
@@ -289,7 +289,7 @@ func CmpLogicalToZero(a, b, c uint32, d, e uint64) uint64 {
        }
        // ppc64x:"ORCC",-"CMP"
        // wasm:"I64Eqz",-"I32Eqz",-"I64ExtendI32U",-"I32WrapI64"
-       if d|e == 0 {
+       if f|g == 0 {
                return 1
        }
 
index 04c186be0eb194d52db47beb6955c0b4e7f5003e..2d5c965ae700bb7faed4aac9ab8c308f3917501e 100644 (file)
@@ -27,7 +27,7 @@ func f0c(a []int) int {
        x := 0
        for i := range a { // ERROR "Induction variable: limits \[0,\?\), increment 1$"
                b := a[:i+1] // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
-               x += b[0]
+               x += b[0]    // ERROR "(\([0-9]+\) )?Proved IsInBounds$"
        }
        return x
 }
@@ -168,7 +168,7 @@ func g2() int {
 func g3a() {
        a := "this string has length 25"
        for i := 0; i < len(a); i += 5 { // ERROR "Induction variable: limits \[0,20\], increment 5$"
-               useString(a[i:]) // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
+               useString(a[i:])   // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
                useString(a[:i+3]) // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
                useString(a[:i+5]) // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
                useString(a[:i+6])
@@ -294,8 +294,10 @@ func k3neg2(a [100]int) [100]int {
 }
 
 func k4(a [100]int) [100]int {
-       min := (-1) << 63
-       for i := min; i < min+50; i++ { // ERROR "Induction variable: limits \[-9223372036854775808,-9223372036854775758\), increment 1$"
+       // Note: can't use (-1)<<63 here, because i-min doesn't get rewritten to i+(-min),
+       // and it isn't worth adding that special case to prove.
+       min := (-1)<<63 + 1
+       for i := min; i < min+50; i++ { // ERROR "Induction variable: limits \[-9223372036854775807,-9223372036854775757\), increment 1$"
                a[i-min] = i // ERROR "(\([0-9]+\) )?Proved IsInBounds$"
        }
        return a
@@ -314,7 +316,7 @@ func d1(a [100]int) [100]int {
        for i := 0; i < 100; i++ { // ERROR "Induction variable: limits \[0,100\), increment 1$"
                for j := 0; j < i; j++ { // ERROR "Induction variable: limits \[0,\?\), increment 1$"
                        a[j] = 0   // ERROR "Proved IsInBounds$"
-                       a[j+1] = 0 // FIXME: this boundcheck should be eliminated
+                       a[j+1] = 0 // ERROR "Proved IsInBounds$"
                        a[j+2] = 0
                }
        }
@@ -325,7 +327,7 @@ func d2(a [100]int) [100]int {
        for i := 0; i < 100; i++ { // ERROR "Induction variable: limits \[0,100\), increment 1$"
                for j := 0; i > j; j++ { // ERROR "Induction variable: limits \[0,\?\), increment 1$"
                        a[j] = 0   // ERROR "Proved IsInBounds$"
-                       a[j+1] = 0 // FIXME: this boundcheck should be eliminated
+                       a[j+1] = 0 // ERROR "Proved IsInBounds$"
                        a[j+2] = 0
                }
        }
@@ -419,12 +421,12 @@ func nobce2(a string) {
        for i := int64(0); i < int64(len(a))-31337; i++ { // ERROR "Induction variable: limits \[0,\?\), increment 1$"
                useString(a[i:]) // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
        }
-       for i := int64(0); i < int64(len(a))+int64(-1<<63); i++ { // ERROR "Induction variable: limits \[0,\?\), increment 1$"
-               useString(a[i:]) // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
+       for i := int64(0); i < int64(len(a))+int64(-1<<63); i++ { // ERROR "Induction variable: limits \[0,\?\), increment 1$" "Disproved Less64"
+               useString(a[i:])
        }
        j := int64(len(a)) - 123
-       for i := int64(0); i < j+123+int64(-1<<63); i++ { // ERROR "Induction variable: limits \[0,\?\), increment 1$"
-               useString(a[i:]) // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
+       for i := int64(0); i < j+123+int64(-1<<63); i++ { // ERROR "Induction variable: limits \[0,\?\), increment 1$" "Disproved Less64"
+               useString(a[i:])
        }
        for i := int64(0); i < j+122+int64(-1<<63); i++ { // ERROR "Induction variable: limits \[0,\?\), increment 1$"
                // len(a)-123+122+MinInt overflows when len(a) == 0, so a bound check is needed here
index 0d93db905ae871bdf8de8f3c447834de796506b5..70466ce2c5132c3bf6cac60bc6c3f6aca767762b 100644 (file)
@@ -400,8 +400,8 @@ func f13f(a, b int64) int64 {
        if b != math.MaxInt64 {
                return 42
        }
-       if a > b {
-               if a == 0 { // ERROR "Disproved Eq64$"
+       if a > b { // ERROR "Disproved Less64$"
+               if a == 0 {
                        return 1
                }
        }
@@ -684,20 +684,6 @@ func constsuffix(s string) bool {
        return suffix(s, "abc") // ERROR "Proved IsSliceInBounds$"
 }
 
-// oforuntil tests the pattern created by OFORUNTIL blocks. These are
-// handled by addLocalInductiveFacts rather than findIndVar.
-func oforuntil(b []int) {
-       i := 0
-       if len(b) > i {
-       top:
-               println(b[i]) // ERROR "Induction variable: limits \[0,\?\), increment 1$" "Proved IsInBounds$"
-               i++
-               if i < len(b) {
-                       goto top
-               }
-       }
-}
-
 func atexit(foobar []func()) {
        for i := len(foobar) - 1; i >= 0; i-- { // ERROR "Induction variable: limits \[0,\?\], increment 1"
                f := foobar[i]
@@ -877,11 +863,11 @@ func unrollDecMin(a []int, b int) int {
                return 42
        }
        var i, x int
-       for i = len(a); i >= b; i -= 2 {
+       for i = len(a); i >= b; i -= 2 { // ERROR "Proved Leq64"
                x += a[i-1]
                x += a[i-2]
        }
-       if i == 1 { // ERROR "Disproved Eq64$"
+       if i == 1 {
                x += a[i-1]
        }
        return x
@@ -893,11 +879,11 @@ func unrollIncMin(a []int, b int) int {
                return 42
        }
        var i, x int
-       for i = len(a); i >= b; i += 2 {
+       for i = len(a); i >= b; i += 2 { // ERROR "Proved Leq64"
                x += a[i-1]
                x += a[i-2]
        }
-       if i == 1 { // ERROR "Disproved Eq64$"
+       if i == 1 {
                x += a[i-1]
        }
        return x
@@ -1107,7 +1093,7 @@ func modu2(x, y uint) int {
 
 func issue57077(s []int) (left, right []int) {
        middle := len(s) / 2
-       left = s[:middle] // ERROR "Proved IsSliceInBounds$"
+       left = s[:middle]  // ERROR "Proved IsSliceInBounds$"
        right = s[middle:] // ERROR "Proved IsSliceInBounds$"
        return
 }
@@ -1124,6 +1110,43 @@ func issue45928(x int) {
        useInt(combinedFrac)
 }
 
+func constantBounds1(i, j uint) int {
+       var a [10]int
+       if j < 11 && i < j {
+               return a[i] // ERROR "Proved IsInBounds$"
+       }
+       return 0
+}
+
+func constantBounds2(i, j uint) int {
+       var a [10]int
+       if i < j && j < 11 {
+               return a[i] // ERROR "Proved IsInBounds"
+       }
+       return 0
+}
+
+func constantBounds3(i, j, k, l uint) int {
+       var a [8]int
+       if i < j && j < k && k < l && l < 11 {
+               return a[i] // ERROR "Proved IsInBounds"
+       }
+       return 0
+}
+
+func equalityPropagation(a [1]int, i, j uint) int {
+       if i == j && i == 5 {
+               return a[j-5] // ERROR "Proved IsInBounds"
+       }
+       return 0
+}
+func inequalityPropagation(a [1]int, i, j uint) int {
+       if i != j && j >= 5 && j <= 6 && i == 5 {
+               return a[j-6] // ERROR "Proved IsInBounds"
+       }
+       return 0
+}
+
 //go:noinline
 func useInt(a int) {
 }
index ed63e6851c264bbfc71b53d3cd8746f832e89ee5..bca96847ba644174259b706edff1803defa7fac8 100644 (file)
@@ -14,7 +14,7 @@ func f0i(x int) int {
   }
 
   if (x + 20) == 20 {
-    return x + 5 // ERROR "Proved.+is constant 0$"
+    return x + 5 // ERROR "Proved.+is constant 0$" "Proved.+is constant 5$"
   }
 
   return x / 2
@@ -26,7 +26,7 @@ func f0u(x uint) uint {
   }
 
   if (x + 20) == 20 {
-    return x + 5 // ERROR "Proved.+is constant 0$"
+    return x + 5 // ERROR "Proved.+is constant 0$" "Proved.+is constant 5$"
   }
 
   return x / 2