Skip to content

Commit 47c9e13

Browse files
committed
cmd/compile: extend prove pass to handle constant comparisons
Find comparisons to constants and propagate that information down the dominator tree. Use it to resolve other constant comparisons on the same variable. So if we know x >= 7, then a x > 4 condition must return true. This change allows us to use "_ = b[7]" hints to eliminate bounds checks. Fixes #14900 Change-Id: Idbf230bd5b7da43de3ecb48706e21cf01bf812f7 Reviewed-on: https://go-review.googlesource.com/21008 Reviewed-by: Alexandru Moșoi <[email protected]>
1 parent f5bd355 commit 47c9e13

File tree

4 files changed

+379
-16
lines changed

4 files changed

+379
-16
lines changed

src/cmd/compile/internal/ssa/prove.go

Lines changed: 189 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
package ssa
66

7+
import "math"
8+
79
type branch int
810

911
const (
@@ -66,30 +68,109 @@ type fact struct {
6668
r relation
6769
}
6870

71+
// a limit records known upper and lower bounds for a value.
72+
type limit struct {
73+
min, max int64 // min <= value <= max, signed
74+
umin, umax uint64 // umin <= value <= umax, unsigned
75+
}
76+
77+
var noLimit = limit{math.MinInt64, math.MaxInt64, 0, math.MaxUint64}
78+
79+
// a limitFact is a limit known for a particular value.
80+
type limitFact struct {
81+
vid ID
82+
limit limit
83+
}
84+
6985
// factsTable keeps track of relations between pairs of values.
7086
type factsTable struct {
7187
facts map[pair]relation // current known set of relation
7288
stack []fact // previous sets of relations
89+
90+
// known lower and upper bounds on individual values.
91+
limits map[ID]limit
92+
limitStack []limitFact // previous entries
7393
}
7494

7595
// checkpointFact is an invalid value used for checkpointing
7696
// and restoring factsTable.
7797
var checkpointFact = fact{}
98+
var checkpointBound = limitFact{}
7899

79100
func newFactsTable() *factsTable {
80101
ft := &factsTable{}
81102
ft.facts = make(map[pair]relation)
82103
ft.stack = make([]fact, 4)
104+
ft.limits = make(map[ID]limit)
105+
ft.limitStack = make([]limitFact, 4)
83106
return ft
84107
}
85108

86109
// get returns the known possible relations between v and w.
87110
// If v and w are not in the map it returns lt|eq|gt, i.e. any order.
88111
func (ft *factsTable) get(v, w *Value, d domain) relation {
112+
if v.isGenericIntConst() || w.isGenericIntConst() {
113+
reversed := false
114+
if v.isGenericIntConst() {
115+
v, w = w, v
116+
reversed = true
117+
}
118+
r := lt | eq | gt
119+
lim, ok := ft.limits[v.ID]
120+
if !ok {
121+
return r
122+
}
123+
c := w.AuxInt
124+
switch d {
125+
case signed:
126+
switch {
127+
case c < lim.min:
128+
r = gt
129+
case c > lim.max:
130+
r = lt
131+
case c == lim.min && c == lim.max:
132+
r = eq
133+
case c == lim.min:
134+
r = gt | eq
135+
case c == lim.max:
136+
r = lt | eq
137+
}
138+
case unsigned:
139+
// TODO: also use signed data if lim.min >= 0?
140+
var uc uint64
141+
switch w.Op {
142+
case OpConst64:
143+
uc = uint64(c)
144+
case OpConst32:
145+
uc = uint64(uint32(c))
146+
case OpConst16:
147+
uc = uint64(uint16(c))
148+
case OpConst8:
149+
uc = uint64(uint8(c))
150+
}
151+
switch {
152+
case uc < lim.umin:
153+
r = gt
154+
case uc > lim.umax:
155+
r = lt
156+
case uc == lim.umin && uc == lim.umax:
157+
r = eq
158+
case uc == lim.umin:
159+
r = gt | eq
160+
case uc == lim.umax:
161+
r = lt | eq
162+
}
163+
}
164+
if reversed {
165+
return reverseBits[r]
166+
}
167+
return r
168+
}
169+
89170
reversed := false
90171
if lessByID(w, v) {
91172
v, w = w, v
92-
reversed = true
173+
reversed = !reversed
93174
}
94175

95176
p := pair{v, w, d}
@@ -120,12 +201,106 @@ func (ft *factsTable) update(v, w *Value, d domain, r relation) {
120201
oldR := ft.get(v, w, d)
121202
ft.stack = append(ft.stack, fact{p, oldR})
122203
ft.facts[p] = oldR & r
204+
205+
// Extract bounds when comparing against constants
206+
if v.isGenericIntConst() {
207+
v, w = w, v
208+
r = reverseBits[r]
209+
}
210+
if v != nil && w.isGenericIntConst() {
211+
c := w.AuxInt
212+
// Note: all the +1/-1 below could overflow/underflow. Either will
213+
// still generate correct results, it will just lead to imprecision.
214+
// In fact if there is overflow/underflow, the corresponding
215+
// code is unreachable because the known range is outside the range
216+
// of the value's type.
217+
old, ok := ft.limits[v.ID]
218+
if !ok {
219+
old = noLimit
220+
}
221+
lim := old
222+
// Update lim with the new information we know.
223+
switch d {
224+
case signed:
225+
switch r {
226+
case lt:
227+
if c-1 < lim.max {
228+
lim.max = c - 1
229+
}
230+
case lt | eq:
231+
if c < lim.max {
232+
lim.max = c
233+
}
234+
case gt | eq:
235+
if c > lim.min {
236+
lim.min = c
237+
}
238+
case gt:
239+
if c+1 > lim.min {
240+
lim.min = c + 1
241+
}
242+
case lt | gt:
243+
if c == lim.min {
244+
lim.min++
245+
}
246+
if c == lim.max {
247+
lim.max--
248+
}
249+
case eq:
250+
lim.min = c
251+
lim.max = c
252+
}
253+
case unsigned:
254+
var uc uint64
255+
switch w.Op {
256+
case OpConst64:
257+
uc = uint64(c)
258+
case OpConst32:
259+
uc = uint64(uint32(c))
260+
case OpConst16:
261+
uc = uint64(uint16(c))
262+
case OpConst8:
263+
uc = uint64(uint8(c))
264+
}
265+
switch r {
266+
case lt:
267+
if uc-1 < lim.umax {
268+
lim.umax = uc - 1
269+
}
270+
case lt | eq:
271+
if uc < lim.umax {
272+
lim.umax = uc
273+
}
274+
case gt | eq:
275+
if uc > lim.umin {
276+
lim.umin = uc
277+
}
278+
case gt:
279+
if uc+1 > lim.umin {
280+
lim.umin = uc + 1
281+
}
282+
case lt | gt:
283+
if uc == lim.umin {
284+
lim.umin++
285+
}
286+
if uc == lim.umax {
287+
lim.umax--
288+
}
289+
case eq:
290+
lim.umin = uc
291+
lim.umax = uc
292+
}
293+
}
294+
ft.limitStack = append(ft.limitStack, limitFact{v.ID, old})
295+
ft.limits[v.ID] = lim
296+
}
123297
}
124298

125299
// checkpoint saves the current state of known relations.
126300
// Called when descending on a branch.
127301
func (ft *factsTable) checkpoint() {
128302
ft.stack = append(ft.stack, checkpointFact)
303+
ft.limitStack = append(ft.limitStack, checkpointBound)
129304
}
130305

131306
// restore restores known relation to the state just
@@ -144,6 +319,18 @@ func (ft *factsTable) restore() {
144319
ft.facts[old.p] = old.r
145320
}
146321
}
322+
for {
323+
old := ft.limitStack[len(ft.limitStack)-1]
324+
ft.limitStack = ft.limitStack[:len(ft.limitStack)-1]
325+
if old.vid == 0 { // checkpointBound
326+
break
327+
}
328+
if old.limit == noLimit {
329+
delete(ft.limits, old.vid)
330+
} else {
331+
ft.limits[old.vid] = old.limit
332+
}
333+
}
147334
}
148335

149336
func lessByID(v, w *Value) bool {
@@ -421,6 +608,7 @@ func simplifyBlock(ft *factsTable, b *Block) branch {
421608
// to the upper bound than this is proven. Most useful in cases such as:
422609
// if len(a) <= 1 { return }
423610
// do something with a[1]
611+
// TODO: use constant bounds to do isNonNegative.
424612
if (c.Op == OpIsInBounds || c.Op == OpIsSliceInBounds) && isNonNegative(c.Args[0]) {
425613
m := ft.get(a0, a1, signed)
426614
if m != 0 && tr.r&m == m {

src/cmd/compile/internal/ssa/value.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,11 @@ func (v *Value) Unimplementedf(msg string, args ...interface{}) {
218218
v.Block.Func.Config.Unimplementedf(v.Line, msg, args...)
219219
}
220220

221+
// isGenericIntConst returns whether v is a generic integer constant.
222+
func (v *Value) isGenericIntConst() bool {
223+
return v != nil && (v.Op == OpConst64 || v.Op == OpConst32 || v.Op == OpConst16 || v.Op == OpConst8)
224+
}
225+
221226
// ExternSymbol is an aux value that encodes a variable's
222227
// constant offset from the static base pointer.
223228
type ExternSymbol struct {

src/encoding/binary/binary.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,37 +49,37 @@ var BigEndian bigEndian
4949
type littleEndian struct{}
5050

5151
func (littleEndian) Uint16(b []byte) uint16 {
52-
b = b[:2:len(b)] // bounds check hint to compiler; see golang.org/issue/14808
52+
_ = b[1] // bounds check hint to compiler; see golang.org/issue/14808
5353
return uint16(b[0]) | uint16(b[1])<<8
5454
}
5555

5656
func (littleEndian) PutUint16(b []byte, v uint16) {
57-
b = b[:2:len(b)] // early bounds check to guarantee safety of writes below
57+
_ = b[1] // early bounds check to guarantee safety of writes below
5858
b[0] = byte(v)
5959
b[1] = byte(v >> 8)
6060
}
6161

6262
func (littleEndian) Uint32(b []byte) uint32 {
63-
b = b[:4:len(b)] // bounds check hint to compiler; see golang.org/issue/14808
63+
_ = b[3] // bounds check hint to compiler; see golang.org/issue/14808
6464
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
6565
}
6666

6767
func (littleEndian) PutUint32(b []byte, v uint32) {
68-
b = b[:4:len(b)] // early bounds check to guarantee safety of writes below
68+
_ = b[3] // early bounds check to guarantee safety of writes below
6969
b[0] = byte(v)
7070
b[1] = byte(v >> 8)
7171
b[2] = byte(v >> 16)
7272
b[3] = byte(v >> 24)
7373
}
7474

7575
func (littleEndian) Uint64(b []byte) uint64 {
76-
b = b[:8:len(b)] // bounds check hint to compiler; see golang.org/issue/14808
76+
_ = b[7] // bounds check hint to compiler; see golang.org/issue/14808
7777
return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 |
7878
uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56
7979
}
8080

8181
func (littleEndian) PutUint64(b []byte, v uint64) {
82-
b = b[:8:len(b)] // early bounds check to guarantee safety of writes below
82+
_ = b[7] // early bounds check to guarantee safety of writes below
8383
b[0] = byte(v)
8484
b[1] = byte(v >> 8)
8585
b[2] = byte(v >> 16)
@@ -97,37 +97,37 @@ func (littleEndian) GoString() string { return "binary.LittleEndian" }
9797
type bigEndian struct{}
9898

9999
func (bigEndian) Uint16(b []byte) uint16 {
100-
b = b[:2:len(b)] // bounds check hint to compiler; see golang.org/issue/14808
100+
_ = b[1] // bounds check hint to compiler; see golang.org/issue/14808
101101
return uint16(b[1]) | uint16(b[0])<<8
102102
}
103103

104104
func (bigEndian) PutUint16(b []byte, v uint16) {
105-
b = b[:2:len(b)] // early bounds check to guarantee safety of writes below
105+
_ = b[1] // early bounds check to guarantee safety of writes below
106106
b[0] = byte(v >> 8)
107107
b[1] = byte(v)
108108
}
109109

110110
func (bigEndian) Uint32(b []byte) uint32 {
111-
b = b[:4:len(b)] // bounds check hint to compiler; see golang.org/issue/14808
111+
_ = b[3] // bounds check hint to compiler; see golang.org/issue/14808
112112
return uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24
113113
}
114114

115115
func (bigEndian) PutUint32(b []byte, v uint32) {
116-
b = b[:4:len(b)] // early bounds check to guarantee safety of writes below
116+
_ = b[3] // early bounds check to guarantee safety of writes below
117117
b[0] = byte(v >> 24)
118118
b[1] = byte(v >> 16)
119119
b[2] = byte(v >> 8)
120120
b[3] = byte(v)
121121
}
122122

123123
func (bigEndian) Uint64(b []byte) uint64 {
124-
b = b[:8:len(b)] // bounds check hint to compiler; see golang.org/issue/14808
124+
_ = b[7] // bounds check hint to compiler; see golang.org/issue/14808
125125
return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 |
126126
uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56
127127
}
128128

129129
func (bigEndian) PutUint64(b []byte, v uint64) {
130-
b = b[:8:len(b)] // early bounds check to guarantee safety of writes below
130+
_ = b[7] // early bounds check to guarantee safety of writes below
131131
b[0] = byte(v >> 56)
132132
b[1] = byte(v >> 48)
133133
b[2] = byte(v >> 40)

0 commit comments

Comments
 (0)