mirror of
https://github.com/golang/go
synced 2024-09-04 23:44:16 +00:00
cmd/compile/internal/ssa: generalize prove to all booleans
* Refacts a bit saving and restoring parents restrictions * Shaves ~100k from pkg/tools/linux_amd64, but most of the savings come from the rewrite rules. * Improves on the following artificial test case: func f1(a4 bool, a6 bool) bool { return a6 || (a6 || (a6 || a4)) || (a6 || (a4 || a6 || (false || a6))) } Change-Id: I714000f75a37a3a6617c6e6834c75bd23674215f Reviewed-on: https://go-review.googlesource.com/20306 Reviewed-by: Keith Randall <khr@golang.org> Run-TryBot: Alexandru Moșoi <alexandru@mosoi.ro> TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
parent
6dfcc336c5
commit
cd798dcb88
|
@ -77,14 +77,19 @@
|
|||
(Rsh8x64 (Const8 [0]) _) -> (Const8 [0])
|
||||
(Rsh8Ux64 (Const8 [0]) _) -> (Const8 [0])
|
||||
|
||||
(IsInBounds x x) -> (ConstBool [0])
|
||||
(IsInBounds (And32 (Const32 [c]) _) (Const32 [d])) && inBounds32(c, d) -> (ConstBool [1])
|
||||
(IsInBounds (And64 (Const64 [c]) _) (Const64 [d])) && inBounds64(c, d) -> (ConstBool [1])
|
||||
(IsInBounds (Const32 [c]) (Const32 [d])) -> (ConstBool [b2i(inBounds32(c,d))])
|
||||
(IsInBounds (Const64 [c]) (Const64 [d])) -> (ConstBool [b2i(inBounds64(c,d))])
|
||||
(IsSliceInBounds x x) -> (ConstBool [1])
|
||||
(IsSliceInBounds (And32 (Const32 [c]) _) (Const32 [d])) && sliceInBounds32(c, d) -> (ConstBool [1])
|
||||
(IsSliceInBounds (And64 (Const64 [c]) _) (Const64 [d])) && sliceInBounds64(c, d) -> (ConstBool [1])
|
||||
(IsSliceInBounds (Const32 [0]) _) -> (ConstBool [1])
|
||||
(IsSliceInBounds (Const64 [0]) _) -> (ConstBool [1])
|
||||
(IsSliceInBounds (Const32 [c]) (Const32 [d])) -> (ConstBool [b2i(sliceInBounds32(c,d))])
|
||||
(IsSliceInBounds (Const64 [c]) (Const64 [d])) -> (ConstBool [b2i(sliceInBounds64(c,d))])
|
||||
(IsSliceInBounds (SliceLen x) (SliceCap x)) -> (ConstBool [1])
|
||||
|
||||
(Eq64 x x) -> (ConstBool [1])
|
||||
(Eq32 x x) -> (ConstBool [1])
|
||||
|
@ -547,6 +552,10 @@
|
|||
(SlicePtr (SliceMake (Const64 <t> [c]) _ _)) -> (Const64 <t> [c])
|
||||
(SliceLen (SliceMake _ (Const64 <t> [c]) _)) -> (Const64 <t> [c])
|
||||
(SliceCap (SliceMake _ _ (Const64 <t> [c]))) -> (Const64 <t> [c])
|
||||
(SlicePtr (SliceMake (SlicePtr x) _ _)) -> (SlicePtr x)
|
||||
(SliceLen (SliceMake _ (SliceLen x) _)) -> (SliceLen x)
|
||||
(SliceCap (SliceMake _ _ (SliceCap x))) -> (SliceCap x)
|
||||
|
||||
(ConstSlice) && config.PtrSize == 4 ->
|
||||
(SliceMake
|
||||
(ConstNil <config.fe.TypeBytePtr()>)
|
||||
|
|
|
@ -4,40 +4,161 @@
|
|||
|
||||
package ssa
|
||||
|
||||
// rangeMask represents the possible relations between a pair of variables.
|
||||
type rangeMask uint
|
||||
type branch int
|
||||
|
||||
const (
|
||||
lt rangeMask = 1 << iota
|
||||
unknown = 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
|
||||
)
|
||||
|
||||
// typeMask represents the universe of a variable pair in which
|
||||
// a set of relations is known.
|
||||
// For example, information learned for unsigned pairs cannot
|
||||
// be transfered to signed pairs because the same bit representation
|
||||
// can mean something else.
|
||||
type typeMask uint
|
||||
// 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 transfered to signed pairs because the same bit
|
||||
// representation can mean something else.
|
||||
type domain uint
|
||||
|
||||
const (
|
||||
signed typeMask = 1 << iota
|
||||
signed domain = 1 << iota
|
||||
unsigned
|
||||
pointer
|
||||
boolean
|
||||
)
|
||||
|
||||
type typeRange struct {
|
||||
t typeMask
|
||||
r rangeMask
|
||||
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
|
||||
}
|
||||
|
||||
type control struct {
|
||||
tm typeMask
|
||||
a0, a1 ID
|
||||
// fact is a pair plus a relation for that pair.
|
||||
type fact struct {
|
||||
p pair
|
||||
r relation
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// checkpointFact is an invalid value used for checkpointing
|
||||
// and restoring factsTable.
|
||||
var checkpointFact = fact{}
|
||||
|
||||
func newFactsTable() *factsTable {
|
||||
ft := &factsTable{}
|
||||
ft.facts = make(map[pair]relation)
|
||||
ft.stack = make([]fact, 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 {
|
||||
reversed := false
|
||||
if lessByID(w, v) {
|
||||
v, w = w, v
|
||||
reversed = true
|
||||
}
|
||||
|
||||
p := pair{v, w, d}
|
||||
r, ok := ft.facts[p]
|
||||
if !ok {
|
||||
if p.v == p.w {
|
||||
r = eq
|
||||
} else {
|
||||
r = lt | eq | gt
|
||||
}
|
||||
}
|
||||
|
||||
if reversed {
|
||||
return reverseBits[r]
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// update updates the set of relations between v and w in domain d
|
||||
// restricting it to r.
|
||||
func (ft *factsTable) update(v, w *Value, d domain, r relation) {
|
||||
if lessByID(w, v) {
|
||||
v, w = w, v
|
||||
r = reverseBits[r]
|
||||
}
|
||||
|
||||
p := pair{v, w, d}
|
||||
oldR := ft.get(v, w, d)
|
||||
ft.stack = append(ft.stack, fact{p, oldR})
|
||||
ft.facts[p] = oldR & r
|
||||
}
|
||||
|
||||
// checkpoint saves the current state of known relations.
|
||||
// Called when descending on a branch.
|
||||
func (ft *factsTable) checkpoint() {
|
||||
ft.stack = append(ft.stack, checkpointFact)
|
||||
}
|
||||
|
||||
// restore restores known relation to the state just
|
||||
// before the previous checkpoint.
|
||||
// Called when backing up on a branch.
|
||||
func (ft *factsTable) restore() {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 = [...]rangeMask{0, 4, 2, 6, 1, 5, 3, 7}
|
||||
reverseBits = [...]relation{0, 4, 2, 6, 1, 5, 3, 7}
|
||||
|
||||
// maps what we learn when the positive branch is taken.
|
||||
// For example:
|
||||
|
@ -45,7 +166,10 @@ var (
|
|||
// v1 = (OpLess8 v2 v3).
|
||||
// If v1 branch is taken than we learn that the rangeMaks
|
||||
// can be at most lt.
|
||||
typeRangeTable = map[Op]typeRange{
|
||||
domainRelationTable = map[Op]struct {
|
||||
d domain
|
||||
r relation
|
||||
}{
|
||||
OpEq8: {signed | unsigned, eq},
|
||||
OpEq16: {signed | unsigned, eq},
|
||||
OpEq32: {signed | unsigned, eq},
|
||||
|
@ -104,7 +228,7 @@ var (
|
|||
|
||||
// prove removes redundant BlockIf controls that can be inferred in a straight line.
|
||||
//
|
||||
// By far, the most common redundant control are generated by bounds checking.
|
||||
// By far, the most common redundant pair are generated by bounds checking.
|
||||
// For example for the code:
|
||||
//
|
||||
// a[i] = 4
|
||||
|
@ -136,9 +260,8 @@ func prove(f *Func) {
|
|||
)
|
||||
// work maintains the DFS stack.
|
||||
type bp struct {
|
||||
block *Block // current handled block
|
||||
state walkState // what's to do
|
||||
saved []typeRange // save previous map entries modified by node
|
||||
block *Block // current handled block
|
||||
state walkState // what's to do
|
||||
}
|
||||
work := make([]bp, 0, 256)
|
||||
work = append(work, bp{
|
||||
|
@ -146,31 +269,32 @@ func prove(f *Func) {
|
|||
state: descend,
|
||||
})
|
||||
|
||||
// mask keep tracks of restrictions for each pair of values in
|
||||
// the dominators for the current node.
|
||||
// Invariant: a0.ID <= a1.ID
|
||||
// For example {unsigned, a0, a1} -> eq|gt means that from
|
||||
// predecessors we know that a0 must be greater or equal to
|
||||
// a1.
|
||||
mask := make(map[control]rangeMask)
|
||||
ft := newFactsTable()
|
||||
|
||||
// DFS on the dominator tree.
|
||||
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:
|
||||
parent := idom[node.block.ID]
|
||||
tr := getRestrict(sdom, parent, node.block)
|
||||
saved := updateRestrictions(mask, parent, tr)
|
||||
if branch != unknown {
|
||||
ft.checkpoint()
|
||||
c := parent.Control
|
||||
updateRestrictions(ft, boolean, nil, c, lt|gt, branch)
|
||||
if tr, has := domainRelationTable[parent.Control.Op]; has {
|
||||
// When we branched from parent we learned a new set of
|
||||
// restrictions. Update the factsTable accordingly.
|
||||
updateRestrictions(ft, tr.d, c.Args[0], c.Args[1], tr.r, branch)
|
||||
}
|
||||
}
|
||||
|
||||
work = append(work, bp{
|
||||
block: node.block,
|
||||
state: simplify,
|
||||
saved: saved,
|
||||
})
|
||||
|
||||
for s := sdom.Child(node.block); s != nil; s = sdom.Sibling(s) {
|
||||
work = append(work, bp{
|
||||
block: s,
|
||||
|
@ -179,21 +303,28 @@ func prove(f *Func) {
|
|||
}
|
||||
|
||||
case simplify:
|
||||
simplifyBlock(mask, node.block)
|
||||
restoreRestrictions(mask, idom[node.block.ID], node.saved)
|
||||
succ := simplifyBlock(ft, node.block)
|
||||
if succ != unknown {
|
||||
b := node.block
|
||||
b.Kind = BlockFirst
|
||||
b.Control = nil
|
||||
if succ == negative {
|
||||
b.Succs[0], b.Succs[1] = b.Succs[1], b.Succs[0]
|
||||
}
|
||||
}
|
||||
|
||||
if branch != unknown {
|
||||
ft.restore()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getRestrict returns the range restrictions added by p
|
||||
// when reaching b. p is the immediate dominator or b.
|
||||
func getRestrict(sdom sparseTree, p *Block, b *Block) typeRange {
|
||||
// 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 typeRange{}
|
||||
}
|
||||
tr, has := typeRangeTable[p.Control.Op]
|
||||
if !has {
|
||||
return typeRange{}
|
||||
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
|
||||
|
@ -202,150 +333,119 @@ func getRestrict(sdom sparseTree, p *Block, b *Block) typeRange {
|
|||
// 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) && len(p.Succs[0].Preds) == 1 {
|
||||
return tr
|
||||
} else if sdom.isAncestorEq(p.Succs[1], b) && len(p.Succs[1].Preds) == 1 {
|
||||
tr.r = (lt | eq | gt) ^ tr.r
|
||||
return tr
|
||||
return positive
|
||||
}
|
||||
return typeRange{}
|
||||
if sdom.isAncestorEq(p.Succs[1], b) && len(p.Succs[1].Preds) == 1 {
|
||||
return negative
|
||||
}
|
||||
return unknown
|
||||
}
|
||||
|
||||
// updateRestrictions updates restrictions from the previous block (p) based on tr.
|
||||
// normally tr was calculated with getRestrict.
|
||||
func updateRestrictions(mask map[control]rangeMask, p *Block, tr typeRange) []typeRange {
|
||||
if tr.t == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// p modifies the restrictions for (a0, a1).
|
||||
// save and return the previous state.
|
||||
a0 := p.Control.Args[0]
|
||||
a1 := p.Control.Args[1]
|
||||
if a0.ID > a1.ID {
|
||||
tr.r = reverseBits[tr.r]
|
||||
a0, a1 = a1, a0
|
||||
}
|
||||
|
||||
saved := make([]typeRange, 0, 2)
|
||||
for t := typeMask(1); t <= tr.t; t <<= 1 {
|
||||
if t&tr.t == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
i := control{t, a0.ID, a1.ID}
|
||||
oldRange, ok := mask[i]
|
||||
if !ok {
|
||||
if a1 != a0 {
|
||||
oldRange = lt | eq | gt
|
||||
} else { // sometimes happens after cse
|
||||
oldRange = eq
|
||||
}
|
||||
}
|
||||
// if i was not already in the map we save the full range
|
||||
// so that when we restore it we properly keep track of it.
|
||||
saved = append(saved, typeRange{t, oldRange})
|
||||
// mask[i] contains the possible relations between a0 and a1.
|
||||
// When we branched from parent we learned that the possible
|
||||
// relations cannot be more than tr.r. We compute the new set of
|
||||
// relations as the intersection betwee the old and the new set.
|
||||
mask[i] = oldRange & tr.r
|
||||
}
|
||||
return saved
|
||||
}
|
||||
|
||||
func restoreRestrictions(mask map[control]rangeMask, p *Block, saved []typeRange) {
|
||||
if p == nil || p.Kind != BlockIf || len(saved) == 0 {
|
||||
// updateRestrictions updates restrictions from the immediate
|
||||
// dominating block (p) using r. r is adjusted according to the branch taken.
|
||||
func updateRestrictions(ft *factsTable, t domain, v, w *Value, r relation, branch branch) {
|
||||
if t == 0 || branch == unknown {
|
||||
// Trivial case: nothing to do, or branch unknown.
|
||||
// Shoult not happen, but just in case.
|
||||
return
|
||||
}
|
||||
|
||||
a0 := p.Control.Args[0].ID
|
||||
a1 := p.Control.Args[1].ID
|
||||
if a0 > a1 {
|
||||
a0, a1 = a1, a0
|
||||
if branch == negative {
|
||||
// Negative branch taken, complement the relations.
|
||||
r = (lt | eq | gt) ^ r
|
||||
}
|
||||
|
||||
for _, tr := range saved {
|
||||
i := control{tr.t, a0, a1}
|
||||
if tr.r != lt|eq|gt {
|
||||
mask[i] = tr.r
|
||||
} else {
|
||||
delete(mask, i)
|
||||
for i := domain(1); i <= t; i <<= 1 {
|
||||
if t&i != 0 {
|
||||
ft.update(v, w, i, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// simplifyBlock simplifies block known the restrictions in mask.
|
||||
func simplifyBlock(mask map[control]rangeMask, b *Block) {
|
||||
// simplifyBlock simplifies block known the restrictions in ft.
|
||||
// Returns which branch must always be taken.
|
||||
func simplifyBlock(ft *factsTable, b *Block) branch {
|
||||
if b.Kind != BlockIf {
|
||||
return
|
||||
return unknown
|
||||
}
|
||||
|
||||
tr, has := typeRangeTable[b.Control.Op]
|
||||
// First, checks if the condition itself is redundant.
|
||||
m := ft.get(nil, b.Control, boolean)
|
||||
if m == lt|gt {
|
||||
if b.Func.pass.debug > 0 {
|
||||
b.Func.Config.Warnl(int(b.Line), "Proved boolean %s", b.Control.Op)
|
||||
}
|
||||
return positive
|
||||
}
|
||||
if m == eq {
|
||||
if b.Func.pass.debug > 0 {
|
||||
b.Func.Config.Warnl(int(b.Line), "Disproved boolean %s", b.Control.Op)
|
||||
}
|
||||
return negative
|
||||
}
|
||||
|
||||
// Next look check equalities.
|
||||
c := b.Control
|
||||
tr, has := domainRelationTable[c.Op]
|
||||
if !has {
|
||||
return
|
||||
return unknown
|
||||
}
|
||||
|
||||
succ := -1
|
||||
a0 := b.Control.Args[0].ID
|
||||
a1 := b.Control.Args[1].ID
|
||||
if a0 > a1 {
|
||||
tr.r = reverseBits[tr.r]
|
||||
a0, a1 = a1, a0
|
||||
}
|
||||
|
||||
for t := typeMask(1); t <= tr.t; t <<= 1 {
|
||||
if t&tr.t == 0 {
|
||||
a0, a1 := c.Args[0], c.Args[1]
|
||||
for d := domain(1); d <= tr.d; d <<= 1 {
|
||||
if d&tr.d == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// tr.r represents in which case the positive branch is taken.
|
||||
// m.r represents which cases are possible because of previous relations.
|
||||
// If the set of possible relations m.r is included in the set of relations
|
||||
// m represents which cases are possible because of previous relations.
|
||||
// If the set of possible relations m is included in the set of relations
|
||||
// need to take the positive branch (or negative) then that branch will
|
||||
// always be taken.
|
||||
// For shortcut, if m.r == 0 then this block is dead code.
|
||||
i := control{t, a0, a1}
|
||||
m := mask[i]
|
||||
// For shortcut, if m == 0 then this block is dead code.
|
||||
m := ft.get(a0, a1, d)
|
||||
if m != 0 && tr.r&m == m {
|
||||
if b.Func.pass.debug > 0 {
|
||||
b.Func.Config.Warnl(int(b.Line), "Proved %s", b.Control.Op)
|
||||
b.Func.Config.Warnl(int(b.Line), "Proved %s", c.Op)
|
||||
}
|
||||
b.Logf("proved positive branch of %s, block %s in %s\n", b.Control, b, b.Func.Name)
|
||||
succ = 0
|
||||
break
|
||||
return positive
|
||||
}
|
||||
if m != 0 && ((lt|eq|gt)^tr.r)&m == m {
|
||||
if b.Func.pass.debug > 0 {
|
||||
b.Func.Config.Warnl(int(b.Line), "Disproved %s", b.Control.Op)
|
||||
b.Func.Config.Warnl(int(b.Line), "Disproved %s", c.Op)
|
||||
}
|
||||
b.Logf("proved negative branch of %s, block %s in %s\n", b.Control, b, b.Func.Name)
|
||||
succ = 1
|
||||
break
|
||||
return negative
|
||||
}
|
||||
}
|
||||
|
||||
if succ == -1 {
|
||||
// HACK: If the first argument of IsInBounds or IsSliceInBounds
|
||||
// is a constant and we already know that constant is smaller (or equal)
|
||||
// to the upper bound than this is proven. Most useful in cases such as:
|
||||
// if len(a) <= 1 { return }
|
||||
// do something with a[1]
|
||||
c := b.Control
|
||||
if (c.Op == OpIsInBounds || c.Op == OpIsSliceInBounds) &&
|
||||
c.Args[0].Op == OpConst64 && c.Args[0].AuxInt >= 0 {
|
||||
m := mask[control{signed, a0, a1}]
|
||||
if m != 0 && tr.r&m == m {
|
||||
if b.Func.pass.debug > 0 {
|
||||
b.Func.Config.Warnl(int(b.Line), "Proved constant %s", c.Op)
|
||||
}
|
||||
succ = 0
|
||||
// HACK: If the first argument of IsInBounds or IsSliceInBounds
|
||||
// is a constant and we already know that constant is smaller (or equal)
|
||||
// to the upper bound than this is proven. Most useful in cases such as:
|
||||
// if len(a) <= 1 { return }
|
||||
// do something with a[1]
|
||||
if (c.Op == OpIsInBounds || c.Op == OpIsSliceInBounds) && isNonNegative(c.Args[0]) {
|
||||
m := ft.get(a0, a1, signed)
|
||||
if m != 0 && tr.r&m == m {
|
||||
if b.Func.pass.debug > 0 {
|
||||
b.Func.Config.Warnl(int(b.Line), "Proved non-negative bounds %s", c.Op)
|
||||
}
|
||||
return positive
|
||||
}
|
||||
}
|
||||
|
||||
if succ != -1 {
|
||||
b.Kind = BlockFirst
|
||||
b.Control = nil
|
||||
b.Succs[0], b.Succs[1] = b.Succs[succ], b.Succs[1-succ]
|
||||
}
|
||||
return unknown
|
||||
}
|
||||
|
||||
// isNonNegative returns true is v is known to be greater or equal to zero.
|
||||
func isNonNegative(v *Value) bool {
|
||||
switch v.Op {
|
||||
case OpConst64:
|
||||
return v.AuxInt >= 0
|
||||
|
||||
case OpStringLen, OpSliceLen, OpSliceCap,
|
||||
OpZeroExt8to64, OpZeroExt16to64, OpZeroExt32to64:
|
||||
return true
|
||||
|
||||
case OpRsh64x64:
|
||||
return isNonNegative(v.Args[0])
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -2487,6 +2487,18 @@ func rewriteValuegeneric_OpITab(v *Value, config *Config) bool {
|
|||
func rewriteValuegeneric_OpIsInBounds(v *Value, config *Config) bool {
|
||||
b := v.Block
|
||||
_ = b
|
||||
// match: (IsInBounds x x)
|
||||
// cond:
|
||||
// result: (ConstBool [0])
|
||||
for {
|
||||
x := v.Args[0]
|
||||
if v.Args[1] != x {
|
||||
break
|
||||
}
|
||||
v.reset(OpConstBool)
|
||||
v.AuxInt = 0
|
||||
return true
|
||||
}
|
||||
// match: (IsInBounds (And32 (Const32 [c]) _) (Const32 [d]))
|
||||
// cond: inBounds32(c, d)
|
||||
// result: (ConstBool [1])
|
||||
|
@ -2568,6 +2580,18 @@ func rewriteValuegeneric_OpIsInBounds(v *Value, config *Config) bool {
|
|||
func rewriteValuegeneric_OpIsSliceInBounds(v *Value, config *Config) bool {
|
||||
b := v.Block
|
||||
_ = b
|
||||
// match: (IsSliceInBounds x x)
|
||||
// cond:
|
||||
// result: (ConstBool [1])
|
||||
for {
|
||||
x := v.Args[0]
|
||||
if v.Args[1] != x {
|
||||
break
|
||||
}
|
||||
v.reset(OpConstBool)
|
||||
v.AuxInt = 1
|
||||
return true
|
||||
}
|
||||
// match: (IsSliceInBounds (And32 (Const32 [c]) _) (Const32 [d]))
|
||||
// cond: sliceInBounds32(c, d)
|
||||
// result: (ConstBool [1])
|
||||
|
@ -2612,6 +2636,34 @@ func rewriteValuegeneric_OpIsSliceInBounds(v *Value, config *Config) bool {
|
|||
v.AuxInt = 1
|
||||
return true
|
||||
}
|
||||
// match: (IsSliceInBounds (Const32 [0]) _)
|
||||
// cond:
|
||||
// result: (ConstBool [1])
|
||||
for {
|
||||
if v.Args[0].Op != OpConst32 {
|
||||
break
|
||||
}
|
||||
if v.Args[0].AuxInt != 0 {
|
||||
break
|
||||
}
|
||||
v.reset(OpConstBool)
|
||||
v.AuxInt = 1
|
||||
return true
|
||||
}
|
||||
// match: (IsSliceInBounds (Const64 [0]) _)
|
||||
// cond:
|
||||
// result: (ConstBool [1])
|
||||
for {
|
||||
if v.Args[0].Op != OpConst64 {
|
||||
break
|
||||
}
|
||||
if v.Args[0].AuxInt != 0 {
|
||||
break
|
||||
}
|
||||
v.reset(OpConstBool)
|
||||
v.AuxInt = 1
|
||||
return true
|
||||
}
|
||||
// match: (IsSliceInBounds (Const32 [c]) (Const32 [d]))
|
||||
// cond:
|
||||
// result: (ConstBool [b2i(sliceInBounds32(c,d))])
|
||||
|
@ -2644,6 +2696,24 @@ func rewriteValuegeneric_OpIsSliceInBounds(v *Value, config *Config) bool {
|
|||
v.AuxInt = b2i(sliceInBounds64(c, d))
|
||||
return true
|
||||
}
|
||||
// match: (IsSliceInBounds (SliceLen x) (SliceCap x))
|
||||
// cond:
|
||||
// result: (ConstBool [1])
|
||||
for {
|
||||
if v.Args[0].Op != OpSliceLen {
|
||||
break
|
||||
}
|
||||
x := v.Args[0].Args[0]
|
||||
if v.Args[1].Op != OpSliceCap {
|
||||
break
|
||||
}
|
||||
if v.Args[1].Args[0] != x {
|
||||
break
|
||||
}
|
||||
v.reset(OpConstBool)
|
||||
v.AuxInt = 1
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
func rewriteValuegeneric_OpLeq16(v *Value, config *Config) bool {
|
||||
|
@ -6709,6 +6779,21 @@ func rewriteValuegeneric_OpSliceCap(v *Value, config *Config) bool {
|
|||
v.AuxInt = c
|
||||
return true
|
||||
}
|
||||
// match: (SliceCap (SliceMake _ _ (SliceCap x)))
|
||||
// cond:
|
||||
// result: (SliceCap x)
|
||||
for {
|
||||
if v.Args[0].Op != OpSliceMake {
|
||||
break
|
||||
}
|
||||
if v.Args[0].Args[2].Op != OpSliceCap {
|
||||
break
|
||||
}
|
||||
x := v.Args[0].Args[2].Args[0]
|
||||
v.reset(OpSliceCap)
|
||||
v.AddArg(x)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
func rewriteValuegeneric_OpSliceLen(v *Value, config *Config) bool {
|
||||
|
@ -6731,6 +6816,21 @@ func rewriteValuegeneric_OpSliceLen(v *Value, config *Config) bool {
|
|||
v.AuxInt = c
|
||||
return true
|
||||
}
|
||||
// match: (SliceLen (SliceMake _ (SliceLen x) _))
|
||||
// cond:
|
||||
// result: (SliceLen x)
|
||||
for {
|
||||
if v.Args[0].Op != OpSliceMake {
|
||||
break
|
||||
}
|
||||
if v.Args[0].Args[1].Op != OpSliceLen {
|
||||
break
|
||||
}
|
||||
x := v.Args[0].Args[1].Args[0]
|
||||
v.reset(OpSliceLen)
|
||||
v.AddArg(x)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
func rewriteValuegeneric_OpSlicePtr(v *Value, config *Config) bool {
|
||||
|
@ -6753,6 +6853,21 @@ func rewriteValuegeneric_OpSlicePtr(v *Value, config *Config) bool {
|
|||
v.AuxInt = c
|
||||
return true
|
||||
}
|
||||
// match: (SlicePtr (SliceMake (SlicePtr x) _ _))
|
||||
// cond:
|
||||
// result: (SlicePtr x)
|
||||
for {
|
||||
if v.Args[0].Op != OpSliceMake {
|
||||
break
|
||||
}
|
||||
if v.Args[0].Args[0].Op != OpSlicePtr {
|
||||
break
|
||||
}
|
||||
x := v.Args[0].Args[0].Args[0]
|
||||
v.reset(OpSlicePtr)
|
||||
v.AddArg(x)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
func rewriteValuegeneric_OpStore(v *Value, config *Config) bool {
|
||||
|
|
|
@ -5,11 +5,11 @@ package main
|
|||
|
||||
func f0(a []int) int {
|
||||
a[0] = 1
|
||||
a[0] = 1 // ERROR "Proved IsInBounds$"
|
||||
a[0] = 1 // ERROR "Proved boolean IsInBounds$"
|
||||
a[6] = 1
|
||||
a[6] = 1 // ERROR "Proved IsInBounds$"
|
||||
a[6] = 1 // ERROR "Proved boolean IsInBounds$"
|
||||
a[5] = 1
|
||||
a[5] = 1 // ERROR "Proved IsInBounds$"
|
||||
a[5] = 1 // ERROR "Proved boolean IsInBounds$"
|
||||
return 13
|
||||
}
|
||||
|
||||
|
@ -18,18 +18,18 @@ func f1(a []int) int {
|
|||
return 18
|
||||
}
|
||||
a[0] = 1
|
||||
a[0] = 1 // ERROR "Proved IsInBounds$"
|
||||
a[0] = 1 // ERROR "Proved boolean IsInBounds$"
|
||||
a[6] = 1
|
||||
a[6] = 1 // ERROR "Proved IsInBounds$"
|
||||
a[5] = 1 // ERROR "Proved constant IsInBounds$"
|
||||
a[5] = 1 // ERROR "Proved IsInBounds$"
|
||||
a[6] = 1 // ERROR "Proved boolean IsInBounds$"
|
||||
a[5] = 1 // ERROR "Proved non-negative bounds IsInBounds$"
|
||||
a[5] = 1 // ERROR "Proved boolean IsInBounds$"
|
||||
return 26
|
||||
}
|
||||
|
||||
func f2(a []int) int {
|
||||
for i := range a {
|
||||
a[i] = i
|
||||
a[i] = i // ERROR "Proved IsInBounds$"
|
||||
a[i] = i // ERROR "Proved boolean IsInBounds$"
|
||||
}
|
||||
return 34
|
||||
}
|
||||
|
@ -49,13 +49,13 @@ func f4a(a, b, c int) int {
|
|||
if a > b { // ERROR "Disproved Greater64$"
|
||||
return 50
|
||||
}
|
||||
if a < b { // ERROR "Proved Less64$"
|
||||
if a < b { // ERROR "Proved boolean Less64$"
|
||||
return 53
|
||||
}
|
||||
if a == b { // ERROR "Disproved Eq64$"
|
||||
if a == b { // ERROR "Disproved boolean Eq64$"
|
||||
return 56
|
||||
}
|
||||
if a > b {
|
||||
if a > b { // ERROR "Disproved boolean Greater64$"
|
||||
return 59
|
||||
}
|
||||
return 61
|
||||
|
@ -92,8 +92,8 @@ func f4c(a, b, c int) int {
|
|||
func f4d(a, b, c int) int {
|
||||
if a < b {
|
||||
if a < c {
|
||||
if a < b { // ERROR "Proved Less64$"
|
||||
if a < c { // ERROR "Proved Less64$"
|
||||
if a < b { // ERROR "Proved boolean Less64$"
|
||||
if a < c { // ERROR "Proved boolean Less64$"
|
||||
return 87
|
||||
}
|
||||
return 89
|
||||
|
@ -183,8 +183,8 @@ func f6e(a uint8) int {
|
|||
func f7(a []int, b int) int {
|
||||
if b < len(a) {
|
||||
a[b] = 3
|
||||
if b < len(a) { // ERROR "Proved Less64$"
|
||||
a[b] = 5 // ERROR "Proved IsInBounds$"
|
||||
if b < len(a) { // ERROR "Proved boolean Less64$"
|
||||
a[b] = 5 // ERROR "Proved boolean IsInBounds$"
|
||||
}
|
||||
}
|
||||
return 161
|
||||
|
@ -203,5 +203,55 @@ func f8(a, b uint) int {
|
|||
return 174
|
||||
}
|
||||
|
||||
func f9(a, b bool) int {
|
||||
if a {
|
||||
return 1
|
||||
}
|
||||
if a || b { // ERROR "Disproved boolean Arg$"
|
||||
return 2
|
||||
}
|
||||
return 3
|
||||
}
|
||||
|
||||
func f10(a string) int {
|
||||
n := len(a)
|
||||
if a[:n>>1] == "aaa" {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func f11a(a []int, i int) {
|
||||
useInt(a[i])
|
||||
useInt(a[i]) // ERROR "Proved boolean IsInBounds$"
|
||||
}
|
||||
|
||||
func f11b(a []int, i int) {
|
||||
useSlice(a[i:])
|
||||
useSlice(a[i:]) // ERROR "Proved boolean IsSliceInBounds$"
|
||||
}
|
||||
|
||||
func f11c(a []int, i int) {
|
||||
useSlice(a[:i])
|
||||
useSlice(a[:i]) // ERROR "Proved boolean IsSliceInBounds$"
|
||||
}
|
||||
|
||||
func f11d(a []int, i int) {
|
||||
useInt(a[2*i+7])
|
||||
useInt(a[2*i+7])
|
||||
}
|
||||
|
||||
func f12(a []int, b int) {
|
||||
useSlice(a[:b])
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func useInt(a int) {
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func useSlice(a []int) {
|
||||
}
|
||||
|
||||
func main() {
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue