Commit 73f92f9b authored by Keith Randall's avatar Keith Randall

cmd/compile: use len(s)<=cap(s) to remove more bounds checks

When we discover a relation x <= len(s), also discover the relation
x <= cap(s).  That way, in situations like:

a := s[x:]  // tests 0 <= x <= len(s)
b := s[:x]  // tests 0 <= x <= cap(s)

the second check can be eliminated.

Fixes #16813

Change-Id: Ifc037920b6955e43bac1a1eaf6bac63a89cfbd44
Reviewed-on: https://go-review.googlesource.com/33633
Run-TryBot: Keith Randall <khr@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarAlexandru Moșoi <alexandru@mosoi.ro>
Reviewed-by: default avatarDavid Chase <drchase@google.com>
parent 6317f92f
...@@ -97,6 +97,12 @@ type factsTable struct { ...@@ -97,6 +97,12 @@ type factsTable struct {
// known lower and upper bounds on individual values. // known lower and upper bounds on individual values.
limits map[ID]limit limits map[ID]limit
limitStack []limitFact // previous entries 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
} }
// checkpointFact is an invalid value used for checkpointing // checkpointFact is an invalid value used for checkpointing
...@@ -432,7 +438,8 @@ var ( ...@@ -432,7 +438,8 @@ var (
} }
) )
// prove removes redundant BlockIf controls that can be inferred in a straight line. // 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. // By far, the most common redundant pair are generated by bounds checking.
// For example for the code: // For example for the code:
...@@ -455,6 +462,31 @@ var ( ...@@ -455,6 +462,31 @@ var (
// else branch of the first comparison is executed, we already know that i < len(a). // else branch of the first comparison is executed, we already know that i < len(a).
// The code for the second panic can be removed. // The code for the second panic can be removed.
func prove(f *Func) { func prove(f *Func) {
ft := newFactsTable()
// 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 OpSliceLen:
if ft.lens == nil {
ft.lens = map[ID]*Value{}
}
ft.lens[v.Args[0].ID] = v
case OpSliceCap:
if ft.caps == nil {
ft.caps = map[ID]*Value{}
}
ft.caps[v.Args[0].ID] = v
}
}
}
// current node state // current node state
type walkState int type walkState int
const ( const (
...@@ -472,7 +504,6 @@ func prove(f *Func) { ...@@ -472,7 +504,6 @@ func prove(f *Func) {
state: descend, state: descend,
}) })
ft := newFactsTable()
idom := f.Idom() idom := f.Idom()
sdom := f.sdom() sdom := f.sdom()
...@@ -559,8 +590,34 @@ func updateRestrictions(parent *Block, ft *factsTable, t domain, v, w *Value, r ...@@ -559,8 +590,34 @@ func updateRestrictions(parent *Block, ft *factsTable, t domain, v, w *Value, r
r = (lt | eq | gt) ^ r r = (lt | eq | gt) ^ r
} }
for i := domain(1); i <= t; i <<= 1 { for i := domain(1); i <= t; i <<= 1 {
if t&i != 0 { if t&i == 0 {
continue
}
ft.update(parent, v, w, i, r) ft.update(parent, v, w, i, r)
// Additional facts we know given the relationship between len and cap.
if i != signed && i != unsigned {
continue
}
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, i, 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], i, 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, i, 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], i, r|gt)
} }
} }
} }
......
...@@ -50,7 +50,7 @@ func f5(a []int) { ...@@ -50,7 +50,7 @@ func f5(a []int) {
if len(a) > 5 { if len(a) > 5 {
useInt(a[5]) useInt(a[5])
useSlice(a[6:]) useSlice(a[6:])
useSlice(a[:6]) // ERROR "Found IsSliceInBounds$" useSlice(a[:6])
} }
} }
......
...@@ -451,6 +451,18 @@ func f14(p, q *int, a []int) { ...@@ -451,6 +451,18 @@ func f14(p, q *int, a []int) {
useInt(a[i2+j]) // ERROR "Proved boolean IsInBounds$" useInt(a[i2+j]) // ERROR "Proved boolean IsInBounds$"
} }
func f15(s []int, x int) {
useSlice(s[x:])
useSlice(s[:x]) // ERROR "Proved IsSliceInBounds$"
}
func f16(s []int) []int {
if len(s) >= 10 {
return s[:10] // ERROR "Proved non-negative bounds IsSliceInBounds$"
}
return nil
}
//go:noinline //go:noinline
func useInt(a int) { func useInt(a int) {
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment