4
4
5
5
package ssa
6
6
7
+ import "math"
8
+
7
9
type branch int
8
10
9
11
const (
@@ -66,30 +68,109 @@ type fact struct {
66
68
r relation
67
69
}
68
70
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
+
69
85
// factsTable keeps track of relations between pairs of values.
70
86
type factsTable struct {
71
87
facts map [pair ]relation // current known set of relation
72
88
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
73
93
}
74
94
75
95
// checkpointFact is an invalid value used for checkpointing
76
96
// and restoring factsTable.
77
97
var checkpointFact = fact {}
98
+ var checkpointBound = limitFact {}
78
99
79
100
func newFactsTable () * factsTable {
80
101
ft := & factsTable {}
81
102
ft .facts = make (map [pair ]relation )
82
103
ft .stack = make ([]fact , 4 )
104
+ ft .limits = make (map [ID ]limit )
105
+ ft .limitStack = make ([]limitFact , 4 )
83
106
return ft
84
107
}
85
108
86
109
// get returns the known possible relations between v and w.
87
110
// If v and w are not in the map it returns lt|eq|gt, i.e. any order.
88
111
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
+
89
170
reversed := false
90
171
if lessByID (w , v ) {
91
172
v , w = w , v
92
- reversed = true
173
+ reversed = ! reversed
93
174
}
94
175
95
176
p := pair {v , w , d }
@@ -120,12 +201,106 @@ func (ft *factsTable) update(v, w *Value, d domain, r relation) {
120
201
oldR := ft .get (v , w , d )
121
202
ft .stack = append (ft .stack , fact {p , oldR })
122
203
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
+ }
123
297
}
124
298
125
299
// checkpoint saves the current state of known relations.
126
300
// Called when descending on a branch.
127
301
func (ft * factsTable ) checkpoint () {
128
302
ft .stack = append (ft .stack , checkpointFact )
303
+ ft .limitStack = append (ft .limitStack , checkpointBound )
129
304
}
130
305
131
306
// restore restores known relation to the state just
@@ -144,6 +319,18 @@ func (ft *factsTable) restore() {
144
319
ft .facts [old .p ] = old .r
145
320
}
146
321
}
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
+ }
147
334
}
148
335
149
336
func lessByID (v , w * Value ) bool {
@@ -421,6 +608,7 @@ func simplifyBlock(ft *factsTable, b *Block) branch {
421
608
// to the upper bound than this is proven. Most useful in cases such as:
422
609
// if len(a) <= 1 { return }
423
610
// do something with a[1]
611
+ // TODO: use constant bounds to do isNonNegative.
424
612
if (c .Op == OpIsInBounds || c .Op == OpIsSliceInBounds ) && isNonNegative (c .Args [0 ]) {
425
613
m := ft .get (a0 , a1 , signed )
426
614
if m != 0 && tr .r & m == m {
0 commit comments