mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
cmd/compile: extend prove pass to handle constant comparisons
Find comparisons to constants and propagate that information down the dominator tree. Use it to resolve other constant comparisons on the same variable. So if we know x >= 7, then a x > 4 condition must return true. This change allows us to use "_ = b[7]" hints to eliminate bounds checks. Fixes #14900 Change-Id: Idbf230bd5b7da43de3ecb48706e21cf01bf812f7 Reviewed-on: https://go-review.googlesource.com/21008 Reviewed-by: Alexandru Moșoi <alexandru@mosoi.ro>
This commit is contained in:
parent
f5bd3556f5
commit
47c9e139ae
4 changed files with 379 additions and 16 deletions
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
package ssa
|
||||
|
||||
import "math"
|
||||
|
||||
type branch int
|
||||
|
||||
const (
|
||||
|
|
@ -66,30 +68,109 @@ type fact struct {
|
|||
r relation
|
||||
}
|
||||
|
||||
// a limit records known upper and lower bounds for a value.
|
||||
type limit struct {
|
||||
min, max int64 // min <= value <= max, signed
|
||||
umin, umax uint64 // umin <= value <= umax, unsigned
|
||||
}
|
||||
|
||||
var noLimit = limit{math.MinInt64, math.MaxInt64, 0, math.MaxUint64}
|
||||
|
||||
// a limitFact is a limit known for a particular value.
|
||||
type limitFact struct {
|
||||
vid ID
|
||||
limit limit
|
||||
}
|
||||
|
||||
// factsTable keeps track of relations between pairs of values.
|
||||
type factsTable struct {
|
||||
facts map[pair]relation // current known set of relation
|
||||
stack []fact // previous sets of relations
|
||||
|
||||
// known lower and upper bounds on individual values.
|
||||
limits map[ID]limit
|
||||
limitStack []limitFact // previous entries
|
||||
}
|
||||
|
||||
// checkpointFact is an invalid value used for checkpointing
|
||||
// and restoring factsTable.
|
||||
var checkpointFact = fact{}
|
||||
var checkpointBound = limitFact{}
|
||||
|
||||
func newFactsTable() *factsTable {
|
||||
ft := &factsTable{}
|
||||
ft.facts = make(map[pair]relation)
|
||||
ft.stack = make([]fact, 4)
|
||||
ft.limits = make(map[ID]limit)
|
||||
ft.limitStack = make([]limitFact, 4)
|
||||
return ft
|
||||
}
|
||||
|
||||
// get returns the known possible relations between v and w.
|
||||
// If v and w are not in the map it returns lt|eq|gt, i.e. any order.
|
||||
func (ft *factsTable) get(v, w *Value, d domain) relation {
|
||||
if v.isGenericIntConst() || w.isGenericIntConst() {
|
||||
reversed := false
|
||||
if v.isGenericIntConst() {
|
||||
v, w = w, v
|
||||
reversed = true
|
||||
}
|
||||
r := lt | eq | gt
|
||||
lim, ok := ft.limits[v.ID]
|
||||
if !ok {
|
||||
return r
|
||||
}
|
||||
c := w.AuxInt
|
||||
switch d {
|
||||
case signed:
|
||||
switch {
|
||||
case c < lim.min:
|
||||
r = gt
|
||||
case c > lim.max:
|
||||
r = lt
|
||||
case c == lim.min && c == lim.max:
|
||||
r = eq
|
||||
case c == lim.min:
|
||||
r = gt | eq
|
||||
case c == lim.max:
|
||||
r = lt | eq
|
||||
}
|
||||
case unsigned:
|
||||
// TODO: also use signed data if lim.min >= 0?
|
||||
var uc uint64
|
||||
switch w.Op {
|
||||
case OpConst64:
|
||||
uc = uint64(c)
|
||||
case OpConst32:
|
||||
uc = uint64(uint32(c))
|
||||
case OpConst16:
|
||||
uc = uint64(uint16(c))
|
||||
case OpConst8:
|
||||
uc = uint64(uint8(c))
|
||||
}
|
||||
switch {
|
||||
case uc < lim.umin:
|
||||
r = gt
|
||||
case uc > lim.umax:
|
||||
r = lt
|
||||
case uc == lim.umin && uc == lim.umax:
|
||||
r = eq
|
||||
case uc == lim.umin:
|
||||
r = gt | eq
|
||||
case uc == lim.umax:
|
||||
r = lt | eq
|
||||
}
|
||||
}
|
||||
if reversed {
|
||||
return reverseBits[r]
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
reversed := false
|
||||
if lessByID(w, v) {
|
||||
v, w = w, v
|
||||
reversed = true
|
||||
reversed = !reversed
|
||||
}
|
||||
|
||||
p := pair{v, w, d}
|
||||
|
|
@ -120,12 +201,106 @@ func (ft *factsTable) update(v, w *Value, d domain, r relation) {
|
|||
oldR := ft.get(v, w, d)
|
||||
ft.stack = append(ft.stack, fact{p, oldR})
|
||||
ft.facts[p] = oldR & r
|
||||
|
||||
// Extract bounds when comparing against constants
|
||||
if v.isGenericIntConst() {
|
||||
v, w = w, v
|
||||
r = reverseBits[r]
|
||||
}
|
||||
if v != nil && w.isGenericIntConst() {
|
||||
c := w.AuxInt
|
||||
// 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, ok := ft.limits[v.ID]
|
||||
if !ok {
|
||||
old = noLimit
|
||||
}
|
||||
lim := old
|
||||
// Update lim with the new information we know.
|
||||
switch d {
|
||||
case signed:
|
||||
switch r {
|
||||
case lt:
|
||||
if c-1 < lim.max {
|
||||
lim.max = c - 1
|
||||
}
|
||||
case lt | eq:
|
||||
if c < lim.max {
|
||||
lim.max = c
|
||||
}
|
||||
case gt | eq:
|
||||
if c > lim.min {
|
||||
lim.min = c
|
||||
}
|
||||
case gt:
|
||||
if c+1 > lim.min {
|
||||
lim.min = c + 1
|
||||
}
|
||||
case lt | gt:
|
||||
if c == lim.min {
|
||||
lim.min++
|
||||
}
|
||||
if c == lim.max {
|
||||
lim.max--
|
||||
}
|
||||
case eq:
|
||||
lim.min = c
|
||||
lim.max = c
|
||||
}
|
||||
case unsigned:
|
||||
var uc uint64
|
||||
switch w.Op {
|
||||
case OpConst64:
|
||||
uc = uint64(c)
|
||||
case OpConst32:
|
||||
uc = uint64(uint32(c))
|
||||
case OpConst16:
|
||||
uc = uint64(uint16(c))
|
||||
case OpConst8:
|
||||
uc = uint64(uint8(c))
|
||||
}
|
||||
switch r {
|
||||
case lt:
|
||||
if uc-1 < lim.umax {
|
||||
lim.umax = uc - 1
|
||||
}
|
||||
case lt | eq:
|
||||
if uc < lim.umax {
|
||||
lim.umax = uc
|
||||
}
|
||||
case gt | eq:
|
||||
if uc > lim.umin {
|
||||
lim.umin = uc
|
||||
}
|
||||
case gt:
|
||||
if uc+1 > lim.umin {
|
||||
lim.umin = uc + 1
|
||||
}
|
||||
case lt | gt:
|
||||
if uc == lim.umin {
|
||||
lim.umin++
|
||||
}
|
||||
if uc == lim.umax {
|
||||
lim.umax--
|
||||
}
|
||||
case eq:
|
||||
lim.umin = uc
|
||||
lim.umax = uc
|
||||
}
|
||||
}
|
||||
ft.limitStack = append(ft.limitStack, limitFact{v.ID, old})
|
||||
ft.limits[v.ID] = lim
|
||||
}
|
||||
}
|
||||
|
||||
// checkpoint saves the current state of known relations.
|
||||
// Called when descending on a branch.
|
||||
func (ft *factsTable) checkpoint() {
|
||||
ft.stack = append(ft.stack, checkpointFact)
|
||||
ft.limitStack = append(ft.limitStack, checkpointBound)
|
||||
}
|
||||
|
||||
// restore restores known relation to the state just
|
||||
|
|
@ -144,6 +319,18 @@ func (ft *factsTable) restore() {
|
|||
ft.facts[old.p] = old.r
|
||||
}
|
||||
}
|
||||
for {
|
||||
old := ft.limitStack[len(ft.limitStack)-1]
|
||||
ft.limitStack = ft.limitStack[:len(ft.limitStack)-1]
|
||||
if old.vid == 0 { // checkpointBound
|
||||
break
|
||||
}
|
||||
if old.limit == noLimit {
|
||||
delete(ft.limits, old.vid)
|
||||
} else {
|
||||
ft.limits[old.vid] = old.limit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func lessByID(v, w *Value) bool {
|
||||
|
|
@ -421,6 +608,7 @@ func simplifyBlock(ft *factsTable, b *Block) branch {
|
|||
// to the upper bound than this is proven. Most useful in cases such as:
|
||||
// if len(a) <= 1 { return }
|
||||
// do something with a[1]
|
||||
// TODO: use constant bounds to do isNonNegative.
|
||||
if (c.Op == OpIsInBounds || c.Op == OpIsSliceInBounds) && isNonNegative(c.Args[0]) {
|
||||
m := ft.get(a0, a1, signed)
|
||||
if m != 0 && tr.r&m == m {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue