1 | // Licensed to the .NET Foundation under one or more agreements. |
2 | // The .NET Foundation licenses this file to you under the MIT license. |
3 | // See the LICENSE file in the project root for more information. |
4 | |
5 | // |
6 | // |
7 | // We take the following approach to range check analysis: |
8 | // |
9 | // Consider the following loop: |
10 | // for (int i = 0; i < a.len; ++i) { |
11 | // a[i] = 0; |
12 | // } |
13 | // |
14 | // This would be represented as: |
15 | // i_0 = 0; BB0 |
16 | // / ______ a[i_1] = 0; BB2 |
17 | // / / i_2 = i_1 + 1; |
18 | // / / ^ |
19 | // i_1 = phi(i_0, i_2); BB1 | |
20 | // i_1 < a.len -------------------+ |
21 | // |
22 | // BB0 -> BB1 |
23 | // BB1 -> (i_1 < a.len) ? BB2 : BB3 |
24 | // BB2 -> BB1 |
25 | // BB3 -> return |
26 | // |
27 | // **Step 1. Walk the statements in the method checking if there is a bounds check. |
28 | // If there is a bounds check, ask the range of the index variable. |
29 | // In the above example i_1's range. |
30 | // |
31 | // **Step 2. Follow the defs and the dependency chain: |
32 | // i_1 is a local, so go to its definition which is i_1 = phi(i_0, i_2). |
33 | // |
34 | // Since rhs is a phi, we ask the range for i_0 and i_2 in the hopes of merging |
35 | // the resulting ranges for i_1. |
36 | // |
37 | // The range of i_0 follows immediately when going to its definition. |
38 | // Ask for the range of i_2, which leads to i_1 + 1. |
39 | // Ask for the range of i_1 and figure we are looping. Call the range of i_1 as |
40 | // "dependent" and quit looping further. The range of "1" is just <1, 1>. |
41 | // |
42 | // Now we have exhausted all the variables for which the range can be determined. |
43 | // The others are either "unknown" or "dependent." |
44 | // |
45 | // We also merge assertions from its pred block's edges for a phi argument otherwise |
46 | // from the block's assertionIn. This gives us an upper bound for i_1 as a.len. |
47 | // |
48 | // **Step 3. Check if an overflow occurs in the dependency chain (loop.) |
49 | // In the above case, we want to make sure there is no overflow in the definitions |
50 | // involving i_1 and i_2. Merge assertions from the block's edges whenever possible. |
51 | // |
52 | // **Step 4. Check if the dependency chain is monotonic. |
53 | // |
54 | // **Step 5. If monotonic is true, then perform a widening step, where we assume, the |
55 | // SSA variables that are "dependent" get their values from the definitions in the |
56 | // dependency loop and their initial values must be the definitions that are not in |
57 | // the dependency loop, in this case i_0's value which is 0. |
58 | // |
59 | |
60 | #pragma once |
61 | #include "compiler.h" |
62 | |
63 | static bool IntAddOverflows(int max1, int max2) |
64 | { |
65 | if (max1 > 0 && max2 > 0 && INT_MAX - max1 < max2) |
66 | { |
67 | return true; |
68 | } |
69 | if (max1 < 0 && max2 < 0 && max1 < INT_MIN - max2) |
70 | { |
71 | return true; |
72 | } |
73 | return false; |
74 | } |
75 | |
76 | struct Limit |
77 | { |
78 | enum LimitType |
79 | { |
80 | keUndef, // The limit is yet to be computed. |
81 | keBinOpArray, |
82 | keConstant, |
83 | keDependent, // The limit is dependent on some other value. |
84 | keUnknown, // The limit could not be determined. |
85 | }; |
86 | |
87 | Limit() : type(keUndef) |
88 | { |
89 | } |
90 | |
91 | Limit(LimitType type) : type(type) |
92 | { |
93 | } |
94 | |
95 | Limit(LimitType type, int cns) : cns(cns), vn(ValueNumStore::NoVN), type(type) |
96 | { |
97 | assert(type == keConstant); |
98 | } |
99 | |
100 | Limit(LimitType type, ValueNum vn, int cns) : cns(cns), vn(vn), type(type) |
101 | { |
102 | assert(type == keBinOpArray); |
103 | } |
104 | |
105 | bool IsUndef() |
106 | { |
107 | return type == keUndef; |
108 | } |
109 | bool IsDependent() |
110 | { |
111 | return type == keDependent; |
112 | } |
113 | bool IsUnknown() |
114 | { |
115 | return type == keUnknown; |
116 | } |
117 | bool IsConstant() |
118 | { |
119 | return type == keConstant; |
120 | } |
121 | int GetConstant() |
122 | { |
123 | return cns; |
124 | } |
125 | bool IsBinOpArray() |
126 | { |
127 | return type == keBinOpArray; |
128 | } |
129 | bool AddConstant(int i) |
130 | { |
131 | switch (type) |
132 | { |
133 | case keDependent: |
134 | return true; |
135 | case keBinOpArray: |
136 | if (IntAddOverflows(cns, i)) |
137 | { |
138 | return false; |
139 | } |
140 | cns += i; |
141 | return true; |
142 | |
143 | case keConstant: |
144 | if (IntAddOverflows(cns, i)) |
145 | { |
146 | return false; |
147 | } |
148 | cns += i; |
149 | return true; |
150 | |
151 | case keUndef: |
152 | case keUnknown: |
153 | // For these values of 'type', conservatively return false |
154 | break; |
155 | } |
156 | |
157 | return false; |
158 | } |
159 | |
160 | bool Equals(Limit& l) |
161 | { |
162 | switch (type) |
163 | { |
164 | case keUndef: |
165 | case keUnknown: |
166 | case keDependent: |
167 | return l.type == type; |
168 | |
169 | case keBinOpArray: |
170 | return l.type == type && l.vn == vn && l.cns == cns; |
171 | |
172 | case keConstant: |
173 | return l.type == type && l.cns == cns; |
174 | } |
175 | return false; |
176 | } |
177 | #ifdef DEBUG |
178 | const char* ToString(CompAllocator alloc) |
179 | { |
180 | unsigned size = 64; |
181 | char* buf = alloc.allocate<char>(size); |
182 | switch (type) |
183 | { |
184 | case keUndef: |
185 | return "Undef" ; |
186 | |
187 | case keUnknown: |
188 | return "Unknown" ; |
189 | |
190 | case keDependent: |
191 | return "Dependent" ; |
192 | |
193 | case keBinOpArray: |
194 | sprintf_s(buf, size, FMT_VN " + %d" , vn, cns); |
195 | return buf; |
196 | |
197 | case keConstant: |
198 | sprintf_s(buf, size, "%d" , cns); |
199 | return buf; |
200 | } |
201 | unreached(); |
202 | } |
203 | #endif |
204 | int cns; |
205 | ValueNum vn; |
206 | LimitType type; |
207 | }; |
208 | |
209 | // Range struct contains upper and lower limit. |
210 | struct Range |
211 | { |
212 | Limit uLimit; |
213 | Limit lLimit; |
214 | |
215 | Range(const Limit& limit) : uLimit(limit), lLimit(limit) |
216 | { |
217 | } |
218 | |
219 | Range(const Limit& lLimit, const Limit& uLimit) : uLimit(uLimit), lLimit(lLimit) |
220 | { |
221 | } |
222 | |
223 | Limit& UpperLimit() |
224 | { |
225 | return uLimit; |
226 | } |
227 | |
228 | Limit& LowerLimit() |
229 | { |
230 | return lLimit; |
231 | } |
232 | |
233 | #ifdef DEBUG |
234 | char* ToString(CompAllocator alloc) |
235 | { |
236 | size_t size = 64; |
237 | char* buf = alloc.allocate<char>(size); |
238 | sprintf_s(buf, size, "<%s, %s>" , lLimit.ToString(alloc), uLimit.ToString(alloc)); |
239 | return buf; |
240 | } |
241 | #endif |
242 | }; |
243 | |
244 | // Helpers for operations performed on ranges |
245 | struct RangeOps |
246 | { |
247 | // Given a constant limit in "l1", add it to l2 and mutate "l2". |
248 | static Limit AddConstantLimit(Limit& l1, Limit& l2) |
249 | { |
250 | assert(l1.IsConstant()); |
251 | Limit l = l2; |
252 | if (l.AddConstant(l1.GetConstant())) |
253 | { |
254 | return l; |
255 | } |
256 | else |
257 | { |
258 | return Limit(Limit::keUnknown); |
259 | } |
260 | } |
261 | |
262 | // Given two ranges "r1" and "r2", perform an add operation on the |
263 | // ranges. |
264 | static Range Add(Range& r1, Range& r2) |
265 | { |
266 | Limit& r1lo = r1.LowerLimit(); |
267 | Limit& r1hi = r1.UpperLimit(); |
268 | Limit& r2lo = r2.LowerLimit(); |
269 | Limit& r2hi = r2.UpperLimit(); |
270 | |
271 | Range result = Limit(Limit::keUnknown); |
272 | |
273 | // Check lo ranges if they are dependent and not unknown. |
274 | if ((r1lo.IsDependent() && !r1lo.IsUnknown()) || (r2lo.IsDependent() && !r2lo.IsUnknown())) |
275 | { |
276 | result.lLimit = Limit(Limit::keDependent); |
277 | } |
278 | // Check hi ranges if they are dependent and not unknown. |
279 | if ((r1hi.IsDependent() && !r1hi.IsUnknown()) || (r2hi.IsDependent() && !r2hi.IsUnknown())) |
280 | { |
281 | result.uLimit = Limit(Limit::keDependent); |
282 | } |
283 | |
284 | if (r1lo.IsConstant()) |
285 | { |
286 | result.lLimit = AddConstantLimit(r1lo, r2lo); |
287 | } |
288 | if (r2lo.IsConstant()) |
289 | { |
290 | result.lLimit = AddConstantLimit(r2lo, r1lo); |
291 | } |
292 | if (r1hi.IsConstant()) |
293 | { |
294 | result.uLimit = AddConstantLimit(r1hi, r2hi); |
295 | } |
296 | if (r2hi.IsConstant()) |
297 | { |
298 | result.uLimit = AddConstantLimit(r2hi, r1hi); |
299 | } |
300 | return result; |
301 | } |
302 | |
303 | // Given two ranges "r1" and "r2", do a Phi merge. If "monotonic" is true, |
304 | // then ignore the dependent variables. |
305 | static Range Merge(Range& r1, Range& r2, bool monotonic) |
306 | { |
307 | Limit& r1lo = r1.LowerLimit(); |
308 | Limit& r1hi = r1.UpperLimit(); |
309 | Limit& r2lo = r2.LowerLimit(); |
310 | Limit& r2hi = r2.UpperLimit(); |
311 | |
312 | // Take care of lo part. |
313 | Range result = Limit(Limit::keUnknown); |
314 | if (r1lo.IsUnknown() || r2lo.IsUnknown()) |
315 | { |
316 | result.lLimit = Limit(Limit::keUnknown); |
317 | } |
318 | // Uninitialized, just copy. |
319 | else if (r1lo.IsUndef()) |
320 | { |
321 | result.lLimit = r2lo; |
322 | } |
323 | else if (r1lo.IsDependent() || r2lo.IsDependent()) |
324 | { |
325 | if (monotonic) |
326 | { |
327 | result.lLimit = r1lo.IsDependent() ? r2lo : r1lo; |
328 | } |
329 | else |
330 | { |
331 | result.lLimit = Limit(Limit::keDependent); |
332 | } |
333 | } |
334 | |
335 | // Take care of hi part. |
336 | if (r1hi.IsUnknown() || r2hi.IsUnknown()) |
337 | { |
338 | result.uLimit = Limit(Limit::keUnknown); |
339 | } |
340 | else if (r1hi.IsUndef()) |
341 | { |
342 | result.uLimit = r2hi; |
343 | } |
344 | else if (r1hi.IsDependent() || r2hi.IsDependent()) |
345 | { |
346 | if (monotonic) |
347 | { |
348 | result.uLimit = r1hi.IsDependent() ? r2hi : r1hi; |
349 | } |
350 | else |
351 | { |
352 | result.uLimit = Limit(Limit::keDependent); |
353 | } |
354 | } |
355 | |
356 | if (r1lo.IsConstant() && r2lo.IsConstant()) |
357 | { |
358 | result.lLimit = Limit(Limit::keConstant, min(r1lo.GetConstant(), r2lo.GetConstant())); |
359 | } |
360 | if (r1hi.IsConstant() && r2hi.IsConstant()) |
361 | { |
362 | result.uLimit = Limit(Limit::keConstant, max(r1hi.GetConstant(), r2hi.GetConstant())); |
363 | } |
364 | if (r2hi.Equals(r1hi)) |
365 | { |
366 | result.uLimit = r2hi; |
367 | } |
368 | if (r2lo.Equals(r1lo)) |
369 | { |
370 | result.lLimit = r1lo; |
371 | } |
372 | // Widen Upper Limit => Max(k, (a.len + n)) yields (a.len + n), |
373 | // This is correct if k >= 0 and n >= k, since a.len always >= 0 |
374 | // (a.len + n) could overflow, but the result (a.len + n) also |
375 | // preserves the overflow. |
376 | if (r1hi.IsConstant() && r1hi.GetConstant() >= 0 && r2hi.IsBinOpArray() && |
377 | r2hi.GetConstant() >= r1hi.GetConstant()) |
378 | { |
379 | result.uLimit = r2hi; |
380 | } |
381 | if (r2hi.IsConstant() && r2hi.GetConstant() >= 0 && r1hi.IsBinOpArray() && |
382 | r1hi.GetConstant() >= r2hi.GetConstant()) |
383 | { |
384 | result.uLimit = r1hi; |
385 | } |
386 | if (r1hi.IsBinOpArray() && r2hi.IsBinOpArray() && r1hi.vn == r2hi.vn) |
387 | { |
388 | result.uLimit = r1hi; |
389 | // Widen the upper bound if the other constant is greater. |
390 | if (r2hi.GetConstant() > r1hi.GetConstant()) |
391 | { |
392 | result.uLimit = r2hi; |
393 | } |
394 | } |
395 | return result; |
396 | } |
397 | }; |
398 | |
399 | class RangeCheck |
400 | { |
401 | public: |
402 | // Constructor |
403 | RangeCheck(Compiler* pCompiler); |
404 | |
405 | typedef JitHashTable<GenTree*, JitPtrKeyFuncs<GenTree>, bool> OverflowMap; |
406 | typedef JitHashTable<GenTree*, JitPtrKeyFuncs<GenTree>, Range*> RangeMap; |
407 | typedef JitHashTable<GenTree*, JitPtrKeyFuncs<GenTree>, BasicBlock*> SearchPath; |
408 | |
409 | #ifdef DEBUG |
410 | // TODO-Cleanup: This code has been kept around just to ensure that the SSA data is still |
411 | // valid when RangeCheck runs. It should be removed at some point (and perhaps replaced |
412 | // by a proper SSA validity checker). |
413 | |
414 | // Location information is used to map where the defs occur in the method. |
415 | struct Location |
416 | { |
417 | BasicBlock* block; |
418 | GenTree* stmt; |
419 | GenTreeLclVarCommon* tree; |
420 | GenTree* parent; |
421 | Location(BasicBlock* block, GenTree* stmt, GenTreeLclVarCommon* tree, GenTree* parent) |
422 | : block(block), stmt(stmt), tree(tree), parent(parent) |
423 | { |
424 | } |
425 | |
426 | private: |
427 | Location(); |
428 | }; |
429 | |
430 | typedef JitHashTable<INT64, JitLargePrimitiveKeyFuncs<INT64>, Location*> VarToLocMap; |
431 | |
432 | // Generate a hashcode unique for this ssa var. |
433 | UINT64 HashCode(unsigned lclNum, unsigned ssaNum); |
434 | |
435 | // Add a location of the definition of ssa var to the location map. |
436 | // Requires "hash" to be computed using HashCode. |
437 | // Requires "location" to be the local definition. |
438 | void SetDef(UINT64 hash, Location* loc); |
439 | |
440 | // Given a tree node that is a local, return the Location defining the local. |
441 | Location* GetDef(GenTreeLclVarCommon* lcl); |
442 | Location* GetDef(unsigned lclNum, unsigned ssaNum); |
443 | |
444 | // Given a statement, check if it is a def and add its locations in a map. |
445 | void MapStmtDefs(const Location& loc); |
446 | |
447 | // Given the CFG, check if it has defs and add their locations in a map. |
448 | void MapMethodDefs(); |
449 | #endif |
450 | |
451 | int GetArrLength(ValueNum vn); |
452 | |
453 | // Check whether the computed range is within lower and upper bounds. This function |
454 | // assumes that the lower range is resolved and upper range is symbolic as in an |
455 | // increasing loop. |
456 | // TODO-CQ: This is not general enough. |
457 | bool BetweenBounds(Range& range, int lower, GenTree* upper); |
458 | |
459 | // Entry point to optimize range checks in the block. Assumes value numbering |
460 | // and assertion prop phases are completed. |
461 | void OptimizeRangeChecks(); |
462 | |
463 | // Given a "tree" node, check if it contains array bounds check node and |
464 | // optimize to remove it, if possible. Requires "stmt" and "block" that |
465 | // contain the tree. |
466 | void OptimizeRangeCheck(BasicBlock* block, GenTree* stmt, GenTree* tree); |
467 | |
468 | // Given the index expression try to find its range. |
469 | // The range of a variable depends on its rhs which in turn depends on its constituent variables. |
470 | // The "path" is the path taken in the search for the rhs' range and its constituents' range. |
471 | // If "monotonic" is true, the calculations are made more liberally assuming initial values |
472 | // at phi definitions. |
473 | Range GetRange(BasicBlock* block, GenTree* expr, bool monotonic DEBUGARG(int indent)); |
474 | |
475 | // Given the local variable, first find the definition of the local and find the range of the rhs. |
476 | // Helper for GetRange. |
477 | Range ComputeRangeForLocalDef(BasicBlock* block, GenTreeLclVarCommon* lcl, bool monotonic DEBUGARG(int indent)); |
478 | |
479 | // Compute the range, rather than retrieve a cached value. Helper for GetRange. |
480 | Range ComputeRange(BasicBlock* block, GenTree* expr, bool monotonic DEBUGARG(int indent)); |
481 | |
482 | // Compute the range for the op1 and op2 for the given binary operator. |
483 | Range ComputeRangeForBinOp(BasicBlock* block, GenTreeOp* binop, bool monotonic DEBUGARG(int indent)); |
484 | |
485 | // Merge assertions from AssertionProp's flags, for the corresponding "phiArg." |
486 | // Requires "pRange" to contain range that is computed partially. |
487 | void MergeAssertion(BasicBlock* block, GenTree* phiArg, Range* pRange DEBUGARG(int indent)); |
488 | |
489 | // Inspect the "assertions" and extract assertions about the given "phiArg" and |
490 | // refine the "pRange" value. |
491 | void MergeEdgeAssertions(GenTreeLclVarCommon* lcl, ASSERT_VALARG_TP assertions, Range* pRange); |
492 | |
493 | // The maximum possible value of the given "limit." If such a value could not be determined |
494 | // return "false." For example: ARRLEN_MAX for array length. |
495 | bool GetLimitMax(Limit& limit, int* pMax); |
496 | |
497 | // Does the addition of the two limits overflow? |
498 | bool AddOverflows(Limit& limit1, Limit& limit2); |
499 | |
500 | // Does the binary operation between the operands overflow? Check recursively. |
501 | bool DoesBinOpOverflow(BasicBlock* block, GenTreeOp* binop); |
502 | |
503 | // Does the phi operands involve an assignment that could overflow? |
504 | bool DoesPhiOverflow(BasicBlock* block, GenTree* expr); |
505 | |
506 | // Find the def of the "expr" local and recurse on the arguments if any of them involve a |
507 | // calculation that overflows. |
508 | bool DoesVarDefOverflow(GenTreeLclVarCommon* lcl); |
509 | |
510 | bool ComputeDoesOverflow(BasicBlock* block, GenTree* expr); |
511 | |
512 | // Does the current "expr" which is a use involve a definition, that overflows. |
513 | bool DoesOverflow(BasicBlock* block, GenTree* tree); |
514 | |
515 | // Widen the range by first checking if the induction variable is monotonic. Requires "pRange" |
516 | // to be partially computed. |
517 | void Widen(BasicBlock* block, GenTree* tree, Range* pRange); |
518 | |
519 | // Is the binary operation increasing the value. |
520 | bool IsBinOpMonotonicallyIncreasing(GenTreeOp* binop); |
521 | |
522 | // Given an "expr" trace its rhs and their definitions to check if all the assignments |
523 | // are monotonically increasing. |
524 | // |
525 | bool IsMonotonicallyIncreasing(GenTree* tree, bool rejectNegativeConst); |
526 | |
527 | // We allocate a budget to avoid walking long UD chains. When traversing each link in the UD |
528 | // chain, we decrement the budget. When the budget hits 0, then no more range check optimization |
529 | // will be applied for the currently compiled method. |
530 | bool IsOverBudget(); |
531 | |
532 | private: |
533 | // Given a lclvar use, try to find the lclvar's defining assignment and its containing block. |
534 | GenTreeOp* GetSsaDefAsg(GenTreeLclVarCommon* lclUse, BasicBlock** asgBlock); |
535 | |
536 | GenTreeBoundsChk* m_pCurBndsChk; |
537 | |
538 | // Get the cached overflow values. |
539 | OverflowMap* GetOverflowMap(); |
540 | OverflowMap* m_pOverflowMap; |
541 | |
542 | // Get the cached range values. |
543 | RangeMap* GetRangeMap(); |
544 | RangeMap* m_pRangeMap; |
545 | |
546 | SearchPath* m_pSearchPath; |
547 | |
548 | #ifdef DEBUG |
549 | bool m_fMappedDefs; |
550 | VarToLocMap* m_pDefTable; |
551 | #endif |
552 | |
553 | Compiler* m_pCompiler; |
554 | CompAllocator m_alloc; |
555 | |
556 | // The number of nodes for which range is computed throughout the current method. |
557 | // When this limit is zero, we have exhausted all the budget to walk the ud-chain. |
558 | int m_nVisitBudget; |
559 | }; |
560 | |