mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
This reverts commit 3680b5e9c4.
Reason for revert: causes long compile times on certain functions. See issue #57959
Change-Id: Ie9e881ca8abbc79a46de2bfeaed0b9d6c416ed42
Reviewed-on: https://go-review.googlesource.com/c/go/+/463295
Run-TryBot: Keith Randall <khr@golang.org>
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
1627 lines
44 KiB
Go
1627 lines
44 KiB
Go
// Copyright 2016 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
|
|
|
|
import (
|
|
"cmd/internal/src"
|
|
"fmt"
|
|
"math"
|
|
)
|
|
|
|
type branch int
|
|
|
|
const (
|
|
unknown branch = iota
|
|
positive
|
|
negative
|
|
// The outedges from a jump table are jumpTable0,
|
|
// jumpTable0+1, jumpTable0+2, etc. There could be an
|
|
// arbitrary number so we can't list them all here.
|
|
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
|
|
// greater than w. When the execution path branches on the condition
|
|
// `v op w` the set of relations is updated to exclude any
|
|
// relation not possible due to `v op w` being true (or false).
|
|
//
|
|
// E.g.
|
|
//
|
|
// r := relation(...)
|
|
//
|
|
// if v < w {
|
|
// newR := r & lt
|
|
// }
|
|
// if v >= w {
|
|
// newR := r & (eq|gt)
|
|
// }
|
|
// if v != w {
|
|
// newR := r & (lt|gt)
|
|
// }
|
|
type relation uint
|
|
|
|
const (
|
|
lt relation = 1 << iota
|
|
eq
|
|
gt
|
|
)
|
|
|
|
var relationStrings = [...]string{
|
|
0: "none", lt: "<", eq: "==", lt | eq: "<=",
|
|
gt: ">", gt | lt: "!=", gt | eq: ">=", gt | eq | lt: "any",
|
|
}
|
|
|
|
func (r relation) String() string {
|
|
if r < relation(len(relationStrings)) {
|
|
return relationStrings[r]
|
|
}
|
|
return fmt.Sprintf("relation(%d)", uint(r))
|
|
}
|
|
|
|
// domain represents the domain of a variable pair in which a set
|
|
// of relations is known. For example, relations learned for unsigned
|
|
// pairs cannot be transferred to signed pairs because the same bit
|
|
// representation can mean something else.
|
|
type domain uint
|
|
|
|
const (
|
|
signed domain = 1 << iota
|
|
unsigned
|
|
pointer
|
|
boolean
|
|
)
|
|
|
|
var domainStrings = [...]string{
|
|
"signed", "unsigned", "pointer", "boolean",
|
|
}
|
|
|
|
func (d domain) String() string {
|
|
s := ""
|
|
for i, ds := range domainStrings {
|
|
if d&(1<<uint(i)) != 0 {
|
|
if len(s) != 0 {
|
|
s += "|"
|
|
}
|
|
s += ds
|
|
d &^= 1 << uint(i)
|
|
}
|
|
}
|
|
if d != 0 {
|
|
if len(s) != 0 {
|
|
s += "|"
|
|
}
|
|
s += fmt.Sprintf("0x%x", uint(d))
|
|
}
|
|
return s
|
|
}
|
|
|
|
type pair struct {
|
|
v, w *Value // a pair of values, ordered by ID.
|
|
// v can be nil, to mean the zero value.
|
|
// for booleans the zero value (v == nil) is false.
|
|
d domain
|
|
}
|
|
|
|
// fact is a pair plus a relation for that pair.
|
|
type fact struct {
|
|
p pair
|
|
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
|
|
}
|
|
|
|
func (l limit) String() string {
|
|
return fmt.Sprintf("sm,SM,um,UM=%d,%d,%d,%d", l.min, l.max, l.umin, l.umax)
|
|
}
|
|
|
|
func (l limit) intersect(l2 limit) limit {
|
|
if l.min < l2.min {
|
|
l.min = l2.min
|
|
}
|
|
if l.umin < l2.umin {
|
|
l.umin = l2.umin
|
|
}
|
|
if l.max > l2.max {
|
|
l.max = l2.max
|
|
}
|
|
if l.umax > l2.umax {
|
|
l.umax = l2.umax
|
|
}
|
|
return l
|
|
}
|
|
|
|
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.
|
|
//
|
|
// The fact table logic is sound, but incomplete. Outside of a few
|
|
// special cases, it performs no deduction or arithmetic. While there
|
|
// are known decision procedures for this, the ad hoc approach taken
|
|
// by the facts table is effective for real code while remaining very
|
|
// efficient.
|
|
type factsTable struct {
|
|
// unsat is true if facts contains a contradiction.
|
|
//
|
|
// Note that the factsTable logic is incomplete, so if unsat
|
|
// is false, the assertions in factsTable could be satisfiable
|
|
// *or* unsatisfiable.
|
|
unsat bool // true if facts contains a contradiction
|
|
unsatDepth int // number of unsat checkpoints
|
|
|
|
facts map[pair]relation // current known set of relation
|
|
stack []fact // previous sets of relations
|
|
|
|
// order is a couple of partial order sets that record information
|
|
// about relations between SSA values in the signed and unsigned
|
|
// domain.
|
|
orderS *poset
|
|
orderU *poset
|
|
|
|
// known lower and upper bounds on individual values.
|
|
limits map[ID]limit
|
|
limitStack []limitFact // previous entries
|
|
|
|
// 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
|
|
// and restoring factsTable.
|
|
var checkpointFact = fact{}
|
|
var checkpointBound = limitFact{}
|
|
|
|
func newFactsTable(f *Func) *factsTable {
|
|
ft := &factsTable{}
|
|
ft.orderS = f.newPoset()
|
|
ft.orderU = f.newPoset()
|
|
ft.orderS.SetUnsigned(false)
|
|
ft.orderU.SetUnsigned(true)
|
|
ft.facts = make(map[pair]relation)
|
|
ft.stack = make([]fact, 4)
|
|
ft.limits = make(map[ID]limit)
|
|
ft.limitStack = make([]limitFact, 4)
|
|
ft.zero = f.ConstInt64(f.Config.Types.Int64, 0)
|
|
return ft
|
|
}
|
|
|
|
// 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) {
|
|
if parent.Func.pass.debug > 2 {
|
|
parent.Func.Warnl(parent.Pos, "parent=%s, update %s %s %s", parent, v, w, r)
|
|
}
|
|
// No need to do anything else if we already found unsat.
|
|
if ft.unsat {
|
|
return
|
|
}
|
|
|
|
// Self-fact. It's wasteful to register it into the facts
|
|
// table, so just note whether it's satisfiable
|
|
if v == w {
|
|
if r&eq == 0 {
|
|
ft.unsat = true
|
|
}
|
|
return
|
|
}
|
|
|
|
if d == signed || d == unsigned {
|
|
var ok bool
|
|
order := ft.orderS
|
|
if d == unsigned {
|
|
order = ft.orderU
|
|
}
|
|
switch r {
|
|
case lt:
|
|
ok = order.SetOrder(v, w)
|
|
case gt:
|
|
ok = order.SetOrder(w, v)
|
|
case lt | eq:
|
|
ok = order.SetOrderOrEqual(v, w)
|
|
case gt | eq:
|
|
ok = order.SetOrderOrEqual(w, v)
|
|
case eq:
|
|
ok = order.SetEqual(v, w)
|
|
case lt | gt:
|
|
ok = order.SetNonEqual(v, w)
|
|
default:
|
|
panic("unknown relation")
|
|
}
|
|
if !ok {
|
|
if parent.Func.pass.debug > 2 {
|
|
parent.Func.Warnl(parent.Pos, "unsat %s %s %s", v, w, r)
|
|
}
|
|
ft.unsat = true
|
|
return
|
|
}
|
|
} else {
|
|
if lessByID(w, v) {
|
|
v, w = w, v
|
|
r = reverseBits[r]
|
|
}
|
|
|
|
p := pair{v, w, d}
|
|
oldR, ok := ft.facts[p]
|
|
if !ok {
|
|
if v == w {
|
|
oldR = eq
|
|
} else {
|
|
oldR = lt | eq | gt
|
|
}
|
|
}
|
|
// No changes compared to information already in facts table.
|
|
if oldR == r {
|
|
return
|
|
}
|
|
ft.stack = append(ft.stack, fact{p, oldR})
|
|
ft.facts[p] = oldR & r
|
|
// If this relation is not satisfiable, mark it and exit right away
|
|
if oldR&r == 0 {
|
|
if parent.Func.pass.debug > 2 {
|
|
parent.Func.Warnl(parent.Pos, "unsat %s %s %s", v, w, r)
|
|
}
|
|
ft.unsat = true
|
|
return
|
|
}
|
|
}
|
|
|
|
// Extract bounds when comparing against constants
|
|
if v.isGenericIntConst() {
|
|
v, w = w, v
|
|
r = reverseBits[r]
|
|
}
|
|
if v != nil && w.isGenericIntConst() {
|
|
// 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
|
|
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++
|
|
}
|
|
if c == lim.max {
|
|
lim.max--
|
|
}
|
|
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++
|
|
}
|
|
if uc == lim.umax {
|
|
lim.umax--
|
|
}
|
|
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
|
|
}
|
|
}
|
|
|
|
// Derived facts below here are only about numbers.
|
|
if d != signed && d != unsigned {
|
|
return
|
|
}
|
|
|
|
// Additional facts we know given the relationship between len and cap.
|
|
//
|
|
// TODO: Since prove now derives transitive relations, it
|
|
// should be sufficient to learn that len(w) <= cap(w) at the
|
|
// beginning of prove where we look for all len/cap ops.
|
|
if v.Op == OpSliceLen && r< == 0 && ft.caps[v.Args[0].ID] != nil {
|
|
// len(s) > w implies cap(s) > w
|
|
// len(s) >= w implies cap(s) >= w
|
|
// len(s) == w implies cap(s) >= w
|
|
ft.update(parent, ft.caps[v.Args[0].ID], w, d, r|gt)
|
|
}
|
|
if w.Op == OpSliceLen && r> == 0 && ft.caps[w.Args[0].ID] != nil {
|
|
// same, length on the RHS.
|
|
ft.update(parent, v, ft.caps[w.Args[0].ID], d, r|lt)
|
|
}
|
|
if v.Op == OpSliceCap && r> == 0 && ft.lens[v.Args[0].ID] != nil {
|
|
// cap(s) < w implies len(s) < w
|
|
// cap(s) <= w implies len(s) <= w
|
|
// cap(s) == w implies len(s) <= w
|
|
ft.update(parent, ft.lens[v.Args[0].ID], w, d, r|lt)
|
|
}
|
|
if w.Op == OpSliceCap && r< == 0 && ft.lens[w.Args[0].ID] != nil {
|
|
// same, capacity on the RHS.
|
|
ft.update(parent, v, ft.lens[w.Args[0].ID], d, r|gt)
|
|
}
|
|
|
|
// Process fence-post implications.
|
|
//
|
|
// First, make the condition > or >=.
|
|
if r == lt || r == lt|eq {
|
|
v, w = w, v
|
|
r = reverseBits[r]
|
|
}
|
|
switch r {
|
|
case gt:
|
|
if x, delta := isConstDelta(v); x != nil && delta == 1 {
|
|
// x+1 > w ⇒ x >= w
|
|
//
|
|
// This is useful for eliminating the
|
|
// growslice branch of append.
|
|
ft.update(parent, x, w, d, gt|eq)
|
|
} else if x, delta := isConstDelta(w); x != nil && delta == -1 {
|
|
// v > x-1 ⇒ v >= x
|
|
ft.update(parent, v, x, d, gt|eq)
|
|
}
|
|
case gt | eq:
|
|
if x, delta := isConstDelta(v); x != nil && delta == -1 {
|
|
// x-1 >= w && x > min ⇒ x > w
|
|
//
|
|
// Useful for i > 0; s[i-1].
|
|
lim, ok := ft.limits[x.ID]
|
|
if ok && ((d == signed && lim.min > opMin[v.Op]) || (d == unsigned && lim.umin > 0)) {
|
|
ft.update(parent, x, w, d, gt)
|
|
}
|
|
} else if x, delta := isConstDelta(w); x != nil && delta == 1 {
|
|
// v >= x+1 && x < max ⇒ v > x
|
|
lim, ok := ft.limits[x.ID]
|
|
if ok && ((d == signed && lim.max < opMax[w.Op]) || (d == unsigned && lim.umax < opUMax[w.Op])) {
|
|
ft.update(parent, v, x, d, gt)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process: x+delta > w (with delta constant)
|
|
// Only signed domain for now (useful for accesses to slices in loops).
|
|
if r == gt || r == gt|eq {
|
|
if x, delta := isConstDelta(v); x != nil && d == signed {
|
|
if parent.Func.pass.debug > 1 {
|
|
parent.Func.Warnl(parent.Pos, "x+d %s w; x:%v %v delta:%v w:%v d:%v", r, x, parent.String(), delta, w.AuxInt, d)
|
|
}
|
|
underflow := true
|
|
if l, has := ft.limits[x.ID]; has && delta < 0 {
|
|
if (x.Type.Size() == 8 && l.min >= math.MinInt64-delta) ||
|
|
(x.Type.Size() == 4 && l.min >= math.MinInt32-delta) {
|
|
underflow = false
|
|
}
|
|
}
|
|
if delta < 0 && !underflow {
|
|
// If delta < 0 and x+delta cannot underflow then x > x+delta (that is, x > v)
|
|
ft.update(parent, x, v, signed, gt)
|
|
}
|
|
if !w.isGenericIntConst() {
|
|
// If we know that x+delta > w but w is not constant, we can derive:
|
|
// if delta < 0 and x+delta cannot underflow, then x > w
|
|
// This is useful for loops with bounds "len(slice)-K" (delta = -K)
|
|
if delta < 0 && !underflow {
|
|
ft.update(parent, x, w, signed, r)
|
|
}
|
|
} else {
|
|
// With w,delta constants, we want to derive: x+delta > w ⇒ x > w-delta
|
|
//
|
|
// We compute (using integers of the correct size):
|
|
// min = w - delta
|
|
// max = MaxInt - delta
|
|
//
|
|
// And we prove that:
|
|
// if min<max: min < x AND x <= max
|
|
// if min>max: min < x OR x <= max
|
|
//
|
|
// This is always correct, even in case of overflow.
|
|
//
|
|
// If the initial fact is x+delta >= w instead, the derived conditions are:
|
|
// if min<max: min <= x AND x <= max
|
|
// if min>max: min <= x OR x <= max
|
|
//
|
|
// 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)
|
|
} 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 l, has := ft.limits[x.ID]; has {
|
|
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)
|
|
}
|
|
} else if l.min > max {
|
|
// x<=max is impossible, so it must be x>min
|
|
ft.update(parent, x, vmin, d, r)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Look through value-preserving extensions.
|
|
// If the domain is appropriate for the pre-extension Type,
|
|
// repeat the update with the pre-extension Value.
|
|
if isCleanExt(v) {
|
|
switch {
|
|
case d == signed && v.Args[0].Type.IsSigned():
|
|
fallthrough
|
|
case d == unsigned && !v.Args[0].Type.IsSigned():
|
|
ft.update(parent, v.Args[0], w, d, r)
|
|
}
|
|
}
|
|
if isCleanExt(w) {
|
|
switch {
|
|
case d == signed && w.Args[0].Type.IsSigned():
|
|
fallthrough
|
|
case d == unsigned && !w.Args[0].Type.IsSigned():
|
|
ft.update(parent, v, w.Args[0], d, r)
|
|
}
|
|
}
|
|
}
|
|
|
|
var opMin = map[Op]int64{
|
|
OpAdd64: math.MinInt64, OpSub64: math.MinInt64,
|
|
OpAdd32: math.MinInt32, OpSub32: math.MinInt32,
|
|
}
|
|
|
|
var opMax = map[Op]int64{
|
|
OpAdd64: math.MaxInt64, OpSub64: math.MaxInt64,
|
|
OpAdd32: math.MaxInt32, OpSub32: math.MaxInt32,
|
|
}
|
|
|
|
var opUMax = map[Op]uint64{
|
|
OpAdd64: math.MaxUint64, OpSub64: math.MaxUint64,
|
|
OpAdd32: math.MaxUint32, OpSub32: math.MaxUint32,
|
|
}
|
|
|
|
// 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, has := ft.limits[v.ID]; has && (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, has := ft.limits[x.ID]; has {
|
|
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)
|
|
}
|
|
|
|
// checkpoint saves the current state of known relations.
|
|
// Called when descending on a branch.
|
|
func (ft *factsTable) checkpoint() {
|
|
if ft.unsat {
|
|
ft.unsatDepth++
|
|
}
|
|
ft.stack = append(ft.stack, checkpointFact)
|
|
ft.limitStack = append(ft.limitStack, checkpointBound)
|
|
ft.orderS.Checkpoint()
|
|
ft.orderU.Checkpoint()
|
|
}
|
|
|
|
// restore restores known relation to the state just
|
|
// before the previous checkpoint.
|
|
// Called when backing up on a branch.
|
|
func (ft *factsTable) restore() {
|
|
if ft.unsatDepth > 0 {
|
|
ft.unsatDepth--
|
|
} else {
|
|
ft.unsat = false
|
|
}
|
|
for {
|
|
old := ft.stack[len(ft.stack)-1]
|
|
ft.stack = ft.stack[:len(ft.stack)-1]
|
|
if old == checkpointFact {
|
|
break
|
|
}
|
|
if old.r == lt|eq|gt {
|
|
delete(ft.facts, old.p)
|
|
} else {
|
|
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
|
|
}
|
|
}
|
|
ft.orderS.Undo()
|
|
ft.orderU.Undo()
|
|
}
|
|
|
|
func lessByID(v, w *Value) bool {
|
|
if v == nil && w == nil {
|
|
// Should not happen, but just in case.
|
|
return false
|
|
}
|
|
if v == nil {
|
|
return true
|
|
}
|
|
return w != nil && v.ID < w.ID
|
|
}
|
|
|
|
var (
|
|
reverseBits = [...]relation{0, 4, 2, 6, 1, 5, 3, 7}
|
|
|
|
// maps what we learn when the positive branch is taken.
|
|
// For example:
|
|
// OpLess8: {signed, lt},
|
|
// v1 = (OpLess8 v2 v3).
|
|
// If v1 branch is taken then we learn that the rangeMask
|
|
// can be at most lt.
|
|
domainRelationTable = map[Op]struct {
|
|
d domain
|
|
r relation
|
|
}{
|
|
OpEq8: {signed | unsigned, eq},
|
|
OpEq16: {signed | unsigned, eq},
|
|
OpEq32: {signed | unsigned, eq},
|
|
OpEq64: {signed | unsigned, eq},
|
|
OpEqPtr: {pointer, eq},
|
|
|
|
OpNeq8: {signed | unsigned, lt | gt},
|
|
OpNeq16: {signed | unsigned, lt | gt},
|
|
OpNeq32: {signed | unsigned, lt | gt},
|
|
OpNeq64: {signed | unsigned, lt | gt},
|
|
OpNeqPtr: {pointer, lt | gt},
|
|
|
|
OpLess8: {signed, lt},
|
|
OpLess8U: {unsigned, lt},
|
|
OpLess16: {signed, lt},
|
|
OpLess16U: {unsigned, lt},
|
|
OpLess32: {signed, lt},
|
|
OpLess32U: {unsigned, lt},
|
|
OpLess64: {signed, lt},
|
|
OpLess64U: {unsigned, lt},
|
|
|
|
OpLeq8: {signed, lt | eq},
|
|
OpLeq8U: {unsigned, lt | eq},
|
|
OpLeq16: {signed, lt | eq},
|
|
OpLeq16U: {unsigned, lt | eq},
|
|
OpLeq32: {signed, lt | eq},
|
|
OpLeq32U: {unsigned, lt | eq},
|
|
OpLeq64: {signed, lt | eq},
|
|
OpLeq64U: {unsigned, lt | eq},
|
|
|
|
// For these ops, the negative branch is different: we can only
|
|
// prove signed/GE (signed/GT) if we can prove that arg0 is non-negative.
|
|
// See the special case in addBranchRestrictions.
|
|
OpIsInBounds: {signed | unsigned, lt}, // 0 <= arg0 < arg1
|
|
OpIsSliceInBounds: {signed | unsigned, lt | eq}, // 0 <= arg0 <= arg1
|
|
}
|
|
)
|
|
|
|
// cleanup returns the posets to the free list
|
|
func (ft *factsTable) cleanup(f *Func) {
|
|
for _, po := range []*poset{ft.orderS, ft.orderU} {
|
|
// Make sure it's empty as it should be. A non-empty poset
|
|
// might cause errors and miscompilations if reused.
|
|
if checkEnabled {
|
|
if err := po.CheckEmpty(); err != nil {
|
|
f.Fatalf("poset not empty after function %s: %v", f.Name, err)
|
|
}
|
|
}
|
|
f.retPoset(po)
|
|
}
|
|
}
|
|
|
|
// prove removes redundant BlockIf branches that can be inferred
|
|
// from previous dominating comparisons.
|
|
//
|
|
// By far, the most common redundant pair are generated by bounds checking.
|
|
// For example for the code:
|
|
//
|
|
// a[i] = 4
|
|
// foo(a[i])
|
|
//
|
|
// The compiler will generate the following code:
|
|
//
|
|
// if i >= len(a) {
|
|
// panic("not in bounds")
|
|
// }
|
|
// a[i] = 4
|
|
// if i >= len(a) {
|
|
// panic("not in bounds")
|
|
// }
|
|
// foo(a[i])
|
|
//
|
|
// The second comparison i >= len(a) is clearly redundant because if the
|
|
// else branch of the first comparison is executed, we already know that i < len(a).
|
|
// The code for the second panic can be removed.
|
|
//
|
|
// prove works by finding contradictions and trimming branches whose
|
|
// conditions are unsatisfiable given the branches leading up to them.
|
|
// It tracks a "fact table" of branch conditions. For each branching
|
|
// block, it asserts the branch conditions that uniquely dominate that
|
|
// block, and then separately asserts the block's branch condition and
|
|
// its negation. If either leads to a contradiction, it can trim that
|
|
// successor.
|
|
func prove(f *Func) {
|
|
ft := newFactsTable(f)
|
|
ft.checkpoint()
|
|
|
|
var lensVars map[*Block][]*Value
|
|
|
|
// Find length and capacity ops.
|
|
for _, b := range f.Blocks {
|
|
for _, v := range b.Values {
|
|
if v.Uses == 0 {
|
|
// We don't care about dead values.
|
|
// (There can be some that are CSEd but not removed yet.)
|
|
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{}
|
|
}
|
|
// Set all len Values for the same slice as equal in the poset.
|
|
// The poset handles transitive relations, so Values related to
|
|
// any OpSliceLen for this slice will be correctly related to others.
|
|
if l, ok := ft.lens[v.Args[0].ID]; ok {
|
|
ft.update(b, v, l, signed, eq)
|
|
} 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{}
|
|
}
|
|
// Same as case OpSliceLen above, but for slice cap.
|
|
if c, ok := ft.caps[v.Args[0].ID]; ok {
|
|
ft.update(b, v, c, signed, eq)
|
|
} 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)
|
|
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 neglible gain.
|
|
// In Go itself, very few functions get improved (< 5) at a cost of 5-7% total increase
|
|
// of compile time.
|
|
}
|
|
}
|
|
}
|
|
// Find induction variables. Currently, findIndVars
|
|
// is limited to one induction variable per block.
|
|
var indVars map[*Block]indVar
|
|
for _, v := range findIndVar(f) {
|
|
if indVars == nil {
|
|
indVars = make(map[*Block]indVar)
|
|
}
|
|
indVars[v.entry] = v
|
|
}
|
|
|
|
// current node state
|
|
type walkState int
|
|
const (
|
|
descend walkState = iota
|
|
simplify
|
|
)
|
|
// work maintains the DFS stack.
|
|
type bp struct {
|
|
block *Block // current handled block
|
|
state walkState // what's to do
|
|
}
|
|
work := make([]bp, 0, 256)
|
|
work = append(work, bp{
|
|
block: f.Entry,
|
|
state: descend,
|
|
})
|
|
|
|
idom := f.Idom()
|
|
sdom := f.Sdom()
|
|
|
|
// DFS on the dominator tree.
|
|
//
|
|
// For efficiency, we consider only the dominator tree rather
|
|
// than the entire flow graph. On the way down, we consider
|
|
// incoming branches and accumulate conditions that uniquely
|
|
// dominate the current block. If we discover a contradiction,
|
|
// we can eliminate the entire block and all of its children.
|
|
// On the way back up, we consider outgoing branches that
|
|
// haven't already been considered. This way we consider each
|
|
// branch condition only once.
|
|
for len(work) > 0 {
|
|
node := work[len(work)-1]
|
|
work = work[:len(work)-1]
|
|
parent := idom[node.block.ID]
|
|
branch := getBranch(sdom, parent, node.block)
|
|
|
|
switch node.state {
|
|
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.
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
if branch != unknown {
|
|
addBranchRestrictions(ft, parent, branch)
|
|
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)
|
|
|
|
work = append(work, bp{
|
|
block: node.block,
|
|
state: simplify,
|
|
})
|
|
for s := sdom.Child(node.block); s != nil; s = sdom.Sibling(s) {
|
|
work = append(work, bp{
|
|
block: s,
|
|
state: descend,
|
|
})
|
|
}
|
|
|
|
case simplify:
|
|
simplifyBlock(sdom, ft, node.block)
|
|
ft.restore()
|
|
}
|
|
}
|
|
|
|
ft.restore()
|
|
|
|
ft.cleanup(f)
|
|
}
|
|
|
|
// 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 {
|
|
if p == nil {
|
|
return unknown
|
|
}
|
|
switch p.Kind {
|
|
case BlockIf:
|
|
// If p and p.Succs[0] are dominators it means that every path
|
|
// from entry to b passes through p and p.Succs[0]. We care that
|
|
// no path from entry to b passes through p.Succs[1]. If p.Succs[0]
|
|
// has one predecessor then (apart from the degenerate case),
|
|
// there is no path from entry that can reach b through p.Succs[1].
|
|
// TODO: how about p->yes->b->yes, i.e. a loop in yes.
|
|
if sdom.IsAncestorEq(p.Succs[0].b, b) && len(p.Succs[0].b.Preds) == 1 {
|
|
return positive
|
|
}
|
|
if sdom.IsAncestorEq(p.Succs[1].b, b) && len(p.Succs[1].b.Preds) == 1 {
|
|
return negative
|
|
}
|
|
case BlockJumpTable:
|
|
// TODO: this loop can lead to quadratic behavior, as
|
|
// getBranch can be called len(p.Succs) times.
|
|
for i, e := range p.Succs {
|
|
if sdom.IsAncestorEq(e.b, b) && len(e.b.Preds) == 1 {
|
|
return jumpTable0 + branch(i)
|
|
}
|
|
}
|
|
}
|
|
return unknown
|
|
}
|
|
|
|
// addIndVarRestrictions updates the factsTables ft with the facts
|
|
// learned from the induction variable indVar which drives the loop
|
|
// starting in Block b.
|
|
func addIndVarRestrictions(ft *factsTable, b *Block, iv indVar) {
|
|
d := signed
|
|
if ft.isNonNegative(iv.min) && ft.isNonNegative(iv.max) {
|
|
d |= unsigned
|
|
}
|
|
|
|
if iv.flags&indVarMinExc == 0 {
|
|
addRestrictions(b, ft, d, iv.min, iv.ind, lt|eq)
|
|
} else {
|
|
addRestrictions(b, ft, d, iv.min, iv.ind, lt)
|
|
}
|
|
|
|
if iv.flags&indVarMaxInc == 0 {
|
|
addRestrictions(b, ft, d, iv.ind, iv.max, lt)
|
|
} else {
|
|
addRestrictions(b, ft, d, iv.ind, iv.max, lt|eq)
|
|
}
|
|
}
|
|
|
|
// addBranchRestrictions updates the factsTables ft with the facts learned when
|
|
// branching from Block b in direction br.
|
|
func addBranchRestrictions(ft *factsTable, b *Block, br branch) {
|
|
c := b.Controls[0]
|
|
switch {
|
|
case br == negative:
|
|
addRestrictions(b, ft, boolean, nil, c, eq)
|
|
case br == positive:
|
|
addRestrictions(b, ft, boolean, nil, c, lt|gt)
|
|
case br >= jumpTable0:
|
|
idx := br - jumpTable0
|
|
val := int64(idx)
|
|
if v, off := isConstDelta(c); v != nil {
|
|
// Establish the bound on the underlying value we're switching on,
|
|
// not on the offset-ed value used as the jump table index.
|
|
c = v
|
|
val -= off
|
|
}
|
|
old, ok := ft.limits[c.ID]
|
|
if !ok {
|
|
old = noLimit
|
|
}
|
|
ft.limitStack = append(ft.limitStack, limitFact{c.ID, old})
|
|
if val < old.min || val > old.max || uint64(val) < old.umin || uint64(val) > old.umax {
|
|
ft.unsat = true
|
|
if b.Func.pass.debug > 2 {
|
|
b.Func.Warnl(b.Pos, "block=%s outedge=%d %s=%d unsat", b, idx, c, val)
|
|
}
|
|
} else {
|
|
ft.limits[c.ID] = limit{val, val, uint64(val), uint64(val)}
|
|
if b.Func.pass.debug > 2 {
|
|
b.Func.Warnl(b.Pos, "block=%s outedge=%d %s=%d", b, idx, c, val)
|
|
}
|
|
}
|
|
default:
|
|
panic("unknown branch")
|
|
}
|
|
if tr, has := domainRelationTable[c.Op]; has {
|
|
// When we branched from parent we learned a new set of
|
|
// restrictions. Update the factsTable accordingly.
|
|
d := tr.d
|
|
if d == signed && ft.isNonNegative(c.Args[0]) && ft.isNonNegative(c.Args[1]) {
|
|
d |= unsigned
|
|
}
|
|
switch c.Op {
|
|
case OpIsInBounds, OpIsSliceInBounds:
|
|
// 0 <= a0 < a1 (or 0 <= a0 <= a1)
|
|
//
|
|
// On the positive branch, we learn:
|
|
// signed: 0 <= a0 < a1 (or 0 <= a0 <= a1)
|
|
// unsigned: a0 < a1 (or a0 <= a1)
|
|
//
|
|
// On the negative branch, we learn (0 > a0 ||
|
|
// a0 >= a1). In the unsigned domain, this is
|
|
// simply a0 >= a1 (which is the reverse of the
|
|
// positive branch, so nothing surprising).
|
|
// But in the signed domain, we can't express the ||
|
|
// condition, so check if a0 is non-negative instead,
|
|
// to be able to learn something.
|
|
switch br {
|
|
case negative:
|
|
d = unsigned
|
|
if ft.isNonNegative(c.Args[0]) {
|
|
d |= signed
|
|
}
|
|
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)
|
|
addRestrictions(b, ft, d, c.Args[0], c.Args[1], tr.r)
|
|
}
|
|
default:
|
|
switch br {
|
|
case negative:
|
|
addRestrictions(b, ft, d, c.Args[0], c.Args[1], tr.r^(lt|gt|eq))
|
|
case positive:
|
|
addRestrictions(b, ft, d, c.Args[0], c.Args[1], tr.r)
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// addRestrictions updates restrictions from the immediate
|
|
// dominating block (p) using r.
|
|
func addRestrictions(parent *Block, ft *factsTable, t domain, v, w *Value, r relation) {
|
|
if t == 0 {
|
|
// Trivial case: nothing to do.
|
|
// Shoult not happen, but just in case.
|
|
return
|
|
}
|
|
for i := domain(1); i <= t; i <<= 1 {
|
|
if t&i == 0 {
|
|
continue
|
|
}
|
|
ft.update(parent, v, w, i, r)
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
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
|
|
}
|
|
r := tr.r
|
|
if br == negative {
|
|
// Negative branch taken to reach b.
|
|
// Complement the relations.
|
|
r = (lt | eq | gt) ^ r
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var ctzNonZeroOp = map[Op]Op{OpCtz8: OpCtz8NonZero, OpCtz16: OpCtz16NonZero, OpCtz32: OpCtz32NonZero, OpCtz64: OpCtz64NonZero}
|
|
var mostNegativeDividend = map[Op]int64{
|
|
OpDiv16: -1 << 15,
|
|
OpMod16: -1 << 15,
|
|
OpDiv32: -1 << 31,
|
|
OpMod32: -1 << 31,
|
|
OpDiv64: -1 << 63,
|
|
OpMod64: -1 << 63}
|
|
|
|
// simplifyBlock simplifies some constant values in b and evaluates
|
|
// branches to non-uniquely dominated successors of b.
|
|
func simplifyBlock(sdom SparseTree, ft *factsTable, b *Block) {
|
|
for _, v := range b.Values {
|
|
switch v.Op {
|
|
case OpSlicemask:
|
|
// Replace OpSlicemask operations in b with constants where possible.
|
|
x, delta := isConstDelta(v.Args[0])
|
|
if x == nil {
|
|
break
|
|
}
|
|
// slicemask(x + y)
|
|
// if x is larger than -y (y is negative), then slicemask is -1.
|
|
lim, ok := ft.limits[x.ID]
|
|
if !ok {
|
|
break
|
|
}
|
|
if lim.umin > uint64(-delta) {
|
|
if v.Args[0].Op == OpAdd64 {
|
|
v.reset(OpConst64)
|
|
} else {
|
|
v.reset(OpConst32)
|
|
}
|
|
if b.Func.pass.debug > 0 {
|
|
b.Func.Warnl(v.Pos, "Proved slicemask not needed")
|
|
}
|
|
v.AuxInt = -1
|
|
}
|
|
case OpCtz8, OpCtz16, OpCtz32, OpCtz64:
|
|
// On some architectures, notably amd64, we can generate much better
|
|
// code for CtzNN if we know that the argument is non-zero.
|
|
// Capture that information here for use in arch-specific optimizations.
|
|
x := v.Args[0]
|
|
lim, ok := ft.limits[x.ID]
|
|
if !ok {
|
|
break
|
|
}
|
|
if lim.umin > 0 || lim.min > 0 || lim.max < 0 {
|
|
if b.Func.pass.debug > 0 {
|
|
b.Func.Warnl(v.Pos, "Proved %v non-zero", v.Op)
|
|
}
|
|
v.Op = ctzNonZeroOp[v.Op]
|
|
}
|
|
case OpRsh8x8, OpRsh8x16, OpRsh8x32, OpRsh8x64,
|
|
OpRsh16x8, OpRsh16x16, OpRsh16x32, OpRsh16x64,
|
|
OpRsh32x8, OpRsh32x16, OpRsh32x32, OpRsh32x64,
|
|
OpRsh64x8, OpRsh64x16, OpRsh64x32, OpRsh64x64:
|
|
// Check whether, for a >> b, we know that a is non-negative
|
|
// and b is all of a's bits except the MSB. If so, a is shifted to zero.
|
|
bits := 8 * v.Type.Size()
|
|
if v.Args[1].isGenericIntConst() && v.Args[1].AuxInt >= bits-1 && ft.isNonNegative(v.Args[0]) {
|
|
if b.Func.pass.debug > 0 {
|
|
b.Func.Warnl(v.Pos, "Proved %v shifts to zero", v.Op)
|
|
}
|
|
switch bits {
|
|
case 64:
|
|
v.reset(OpConst64)
|
|
case 32:
|
|
v.reset(OpConst32)
|
|
case 16:
|
|
v.reset(OpConst16)
|
|
case 8:
|
|
v.reset(OpConst8)
|
|
default:
|
|
panic("unexpected integer size")
|
|
}
|
|
v.AuxInt = 0
|
|
break // Be sure not to fallthrough - this is no longer OpRsh.
|
|
}
|
|
// If the Rsh hasn't been replaced with 0, still check if it is bounded.
|
|
fallthrough
|
|
case OpLsh8x8, OpLsh8x16, OpLsh8x32, OpLsh8x64,
|
|
OpLsh16x8, OpLsh16x16, OpLsh16x32, OpLsh16x64,
|
|
OpLsh32x8, OpLsh32x16, OpLsh32x32, OpLsh32x64,
|
|
OpLsh64x8, OpLsh64x16, OpLsh64x32, OpLsh64x64,
|
|
OpRsh8Ux8, OpRsh8Ux16, OpRsh8Ux32, OpRsh8Ux64,
|
|
OpRsh16Ux8, OpRsh16Ux16, OpRsh16Ux32, OpRsh16Ux64,
|
|
OpRsh32Ux8, OpRsh32Ux16, OpRsh32Ux32, OpRsh32Ux64,
|
|
OpRsh64Ux8, OpRsh64Ux16, OpRsh64Ux32, OpRsh64Ux64:
|
|
// Check whether, for a << b, we know that b
|
|
// is strictly less than the number of bits in a.
|
|
by := v.Args[1]
|
|
lim, ok := ft.limits[by.ID]
|
|
if !ok {
|
|
break
|
|
}
|
|
bits := 8 * v.Args[0].Type.Size()
|
|
if lim.umax < uint64(bits) || (lim.max < bits && ft.isNonNegative(by)) {
|
|
v.AuxInt = 1 // see shiftIsBounded
|
|
if b.Func.pass.debug > 0 {
|
|
b.Func.Warnl(v.Pos, "Proved %v bounded", v.Op)
|
|
}
|
|
}
|
|
case OpDiv16, OpDiv32, OpDiv64, OpMod16, OpMod32, OpMod64:
|
|
// On amd64 and 386 fix-up code can be avoided if we know
|
|
// the divisor is not -1 or the dividend > MinIntNN.
|
|
// Don't modify AuxInt on other architectures,
|
|
// as that can interfere with CSE.
|
|
// TODO: add other architectures?
|
|
if b.Func.Config.arch != "386" && b.Func.Config.arch != "amd64" {
|
|
break
|
|
}
|
|
divr := v.Args[1]
|
|
divrLim, divrLimok := ft.limits[divr.ID]
|
|
divd := v.Args[0]
|
|
divdLim, divdLimok := ft.limits[divd.ID]
|
|
if (divrLimok && (divrLim.max < -1 || divrLim.min > -1)) ||
|
|
(divdLimok && divdLim.min > mostNegativeDividend[v.Op]) {
|
|
// See DivisionNeedsFixUp in rewrite.go.
|
|
// v.AuxInt = 1 means we have proved both that the divisor is not -1
|
|
// and that the dividend is not the most negative integer,
|
|
// so we do not need to add fix-up code.
|
|
v.AuxInt = 1
|
|
if b.Func.pass.debug > 0 {
|
|
b.Func.Warnl(v.Pos, "Proved %v does not need fix-up", v.Op)
|
|
}
|
|
}
|
|
}
|
|
// Fold provable constant results.
|
|
// Helps in cases where we reuse a value after branching on its equality.
|
|
for i, arg := range v.Args {
|
|
switch arg.Op {
|
|
case OpConst64, OpConst32, OpConst16, OpConst8:
|
|
continue
|
|
}
|
|
lim, ok := ft.limits[arg.ID]
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
var constValue int64
|
|
typ := arg.Type
|
|
bits := 8 * typ.Size()
|
|
switch {
|
|
case lim.min == lim.max:
|
|
constValue = lim.min
|
|
case lim.umin == lim.umax:
|
|
// truncate then sign extand
|
|
switch bits {
|
|
case 64:
|
|
constValue = int64(lim.umin)
|
|
case 32:
|
|
constValue = int64(int32(lim.umin))
|
|
case 16:
|
|
constValue = int64(int16(lim.umin))
|
|
case 8:
|
|
constValue = int64(int8(lim.umin))
|
|
default:
|
|
panic("unexpected integer size")
|
|
}
|
|
default:
|
|
continue
|
|
}
|
|
var c *Value
|
|
f := b.Func
|
|
switch bits {
|
|
case 64:
|
|
c = f.ConstInt64(typ, constValue)
|
|
case 32:
|
|
c = f.ConstInt32(typ, int32(constValue))
|
|
case 16:
|
|
c = f.ConstInt16(typ, int16(constValue))
|
|
case 8:
|
|
c = f.ConstInt8(typ, int8(constValue))
|
|
default:
|
|
panic("unexpected integer size")
|
|
}
|
|
v.SetArg(i, c)
|
|
if b.Func.pass.debug > 1 {
|
|
b.Func.Warnl(v.Pos, "Proved %v's arg %d (%v) is constant %d", v, i, arg, constValue)
|
|
}
|
|
}
|
|
}
|
|
|
|
if b.Kind != BlockIf {
|
|
return
|
|
}
|
|
|
|
// Consider outgoing edges from this block.
|
|
parent := b
|
|
for i, branch := range [...]branch{positive, negative} {
|
|
child := parent.Succs[i].b
|
|
if getBranch(sdom, parent, child) != unknown {
|
|
// For edges to uniquely dominated blocks, we
|
|
// already did this when we visited the child.
|
|
continue
|
|
}
|
|
// For edges to other blocks, this can trim a branch
|
|
// even if we couldn't get rid of the child itself.
|
|
ft.checkpoint()
|
|
addBranchRestrictions(ft, parent, branch)
|
|
unsat := ft.unsat
|
|
ft.restore()
|
|
if unsat {
|
|
// This branch is impossible, so remove it
|
|
// from the block.
|
|
removeBranch(parent, branch)
|
|
// No point in considering the other branch.
|
|
// (It *is* possible for both to be
|
|
// unsatisfiable since the fact table is
|
|
// incomplete. We could turn this into a
|
|
// BlockExit, but it doesn't seem worth it.)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func removeBranch(b *Block, branch branch) {
|
|
c := b.Controls[0]
|
|
if b.Func.pass.debug > 0 {
|
|
verb := "Proved"
|
|
if branch == positive {
|
|
verb = "Disproved"
|
|
}
|
|
if b.Func.pass.debug > 1 {
|
|
b.Func.Warnl(b.Pos, "%s %s (%s)", verb, c.Op, c)
|
|
} else {
|
|
b.Func.Warnl(b.Pos, "%s %s", verb, c.Op)
|
|
}
|
|
}
|
|
if c != nil && c.Pos.IsStmt() == src.PosIsStmt && c.Pos.SameFileAndLine(b.Pos) {
|
|
// attempt to preserve statement marker.
|
|
b.Pos = b.Pos.WithIsStmt()
|
|
}
|
|
if branch == positive || branch == negative {
|
|
b.Kind = BlockFirst
|
|
b.ResetControls()
|
|
if branch == positive {
|
|
b.swapSuccessors()
|
|
}
|
|
} else {
|
|
// TODO: figure out how to remove an entry from a jump table
|
|
}
|
|
}
|
|
|
|
// isNonNegative reports whether v is known to be greater or equal to zero.
|
|
func isNonNegative(v *Value) bool {
|
|
if !v.Type.IsInteger() {
|
|
v.Fatalf("isNonNegative bad type: %v", v.Type)
|
|
}
|
|
// TODO: return true if !v.Type.IsSigned()
|
|
// SSA isn't type-safe enough to do that now (issue 37753).
|
|
// The checks below depend only on the pattern of bits.
|
|
|
|
switch v.Op {
|
|
case OpConst64:
|
|
return v.AuxInt >= 0
|
|
|
|
case OpConst32:
|
|
return int32(v.AuxInt) >= 0
|
|
|
|
case OpConst16:
|
|
return int16(v.AuxInt) >= 0
|
|
|
|
case OpConst8:
|
|
return int8(v.AuxInt) >= 0
|
|
|
|
case OpStringLen, OpSliceLen, OpSliceCap,
|
|
OpZeroExt8to64, OpZeroExt16to64, OpZeroExt32to64,
|
|
OpZeroExt8to32, OpZeroExt16to32, OpZeroExt8to16,
|
|
OpCtz64, OpCtz32, OpCtz16, OpCtz8,
|
|
OpCtz64NonZero, OpCtz32NonZero, OpCtz16NonZero, OpCtz8NonZero,
|
|
OpBitLen64, OpBitLen32, OpBitLen16, OpBitLen8:
|
|
return true
|
|
|
|
case OpRsh64Ux64, OpRsh32Ux64:
|
|
by := v.Args[1]
|
|
return by.Op == OpConst64 && by.AuxInt > 0
|
|
|
|
case OpRsh64x64, OpRsh32x64, OpRsh8x64, OpRsh16x64, OpRsh32x32, OpRsh64x32,
|
|
OpSignExt32to64, OpSignExt16to64, OpSignExt8to64, OpSignExt16to32, OpSignExt8to32:
|
|
return isNonNegative(v.Args[0])
|
|
|
|
case OpAnd64, OpAnd32, OpAnd16, OpAnd8:
|
|
return isNonNegative(v.Args[0]) || isNonNegative(v.Args[1])
|
|
|
|
case OpMod64, OpMod32, OpMod16, OpMod8,
|
|
OpDiv64, OpDiv32, OpDiv16, OpDiv8,
|
|
OpOr64, OpOr32, OpOr16, OpOr8,
|
|
OpXor64, OpXor32, OpXor16, OpXor8:
|
|
return isNonNegative(v.Args[0]) && isNonNegative(v.Args[1])
|
|
|
|
// We could handle OpPhi here, but the improvements from doing
|
|
// so are very minor, and it is neither simple nor cheap.
|
|
}
|
|
return false
|
|
}
|
|
|
|
// isConstDelta returns non-nil if v is equivalent to w+delta (signed).
|
|
func isConstDelta(v *Value) (w *Value, delta int64) {
|
|
cop := OpConst64
|
|
switch v.Op {
|
|
case OpAdd32, OpSub32:
|
|
cop = OpConst32
|
|
case OpAdd16, OpSub16:
|
|
cop = OpConst16
|
|
case OpAdd8, OpSub8:
|
|
cop = OpConst8
|
|
}
|
|
switch v.Op {
|
|
case OpAdd64, OpAdd32, OpAdd16, OpAdd8:
|
|
if v.Args[0].Op == cop {
|
|
return v.Args[1], v.Args[0].AuxInt
|
|
}
|
|
if v.Args[1].Op == cop {
|
|
return v.Args[0], v.Args[1].AuxInt
|
|
}
|
|
case OpSub64, OpSub32, OpSub16, OpSub8:
|
|
if v.Args[1].Op == cop {
|
|
aux := v.Args[1].AuxInt
|
|
if aux != -aux { // Overflow; too bad
|
|
return v.Args[0], -aux
|
|
}
|
|
}
|
|
}
|
|
return nil, 0
|
|
}
|
|
|
|
// isCleanExt reports whether v is the result of a value-preserving
|
|
// sign or zero extension.
|
|
func isCleanExt(v *Value) bool {
|
|
switch v.Op {
|
|
case OpSignExt8to16, OpSignExt8to32, OpSignExt8to64,
|
|
OpSignExt16to32, OpSignExt16to64, OpSignExt32to64:
|
|
// signed -> signed is the only value-preserving sign extension
|
|
return v.Args[0].Type.IsSigned() && v.Type.IsSigned()
|
|
|
|
case OpZeroExt8to16, OpZeroExt8to32, OpZeroExt8to64,
|
|
OpZeroExt16to32, OpZeroExt16to64, OpZeroExt32to64:
|
|
// unsigned -> signed/unsigned are value-preserving zero extensions
|
|
return !v.Args[0].Type.IsSigned()
|
|
}
|
|
return false
|
|
}
|