summaryrefslogtreecommitdiffstats
path: root/src/cmd/compile/internal/ssa/prove.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/compile/internal/ssa/prove.go')
-rw-r--r--src/cmd/compile/internal/ssa/prove.go1426
1 files changed, 1426 insertions, 0 deletions
diff --git a/src/cmd/compile/internal/ssa/prove.go b/src/cmd/compile/internal/ssa/prove.go
new file mode 100644
index 0000000..8a2e7c0
--- /dev/null
+++ b/src/cmd/compile/internal/ssa/prove.go
@@ -0,0 +1,1426 @@
+// 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
+)
+
+// 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&lt == 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&gt == 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&gt == 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&lt == 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)
+ }
+ if !w.isGenericIntConst() {
+ // If we know that x+delta > w but w is not constant, we can derive:
+ // if delta < 0 and x > MinInt - delta, then x > w (because x+delta cannot underflow)
+ // This is useful for loops with bounds "len(slice)-K" (delta = -K)
+ 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) {
+ 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)
+
+ 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
+ }
+)
+
+// 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{}
+ }
+ 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{}
+ }
+ 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)
+ }
+ }
+ }
+ }
+
+ // 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()
+
+ // Return the posets to the free list
+ 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("prove poset not empty after function %s: %v", f.Name, err)
+ }
+ }
+ f.retPoset(po)
+ }
+}
+
+// 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 || p.Kind != BlockIf {
+ return unknown
+ }
+ // 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
+ }
+ 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 br {
+ case negative:
+ addRestrictions(b, ft, boolean, nil, c, eq)
+ case positive:
+ addRestrictions(b, ft, boolean, nil, c, lt|gt)
+ 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. addLocalInductiveFacts specifically targets the pattern
+// created by OFORUNTIL, which isn't detected by findIndVar.
+//
+// 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 {
+ continue
+ }
+ // slicemask(x + y)
+ // if x is larger than -y (y is negative), then slicemask is -1.
+ lim, ok := ft.limits[x.ID]
+ if !ok {
+ continue
+ }
+ 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 {
+ continue
+ }
+ 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
+ continue // 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 {
+ continue
+ }
+ 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)
+ }
+ }
+ }
+ }
+
+ 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()
+ }
+ b.Kind = BlockFirst
+ b.ResetControls()
+ if branch == positive {
+ b.swapSuccessors()
+ }
+}
+
+// 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:
+ 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
+ }
+ switch v.Op {
+ case OpAdd64, OpAdd32:
+ 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:
+ 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
+}