Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(237)

Side by Side Diff: lib/compiler/implementation/ssa/value_range_analyzer.dart

Issue 10968060: Add a value range analysis phase to remove bounds checks. (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 8 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 /**
6 * A [Value] represents both symbolic values like the value of a
7 * parameter, or the length of an array, and concrete values, like
8 * constants.
9 */
10 abstract class Value {
11 const Value();
12
13 Value operator +(Value other);
14 Value operator -(Value other);
15 Value operator &(Value other);
16
17 Value min(Value other) {
18 if (this == other) return this;
19 if (other == const MinValue()) return other;
20 if (other == const MaxValue()) return this;
21 Value value = this - other;
22 if (value.isPositive()) return other;
23 if (value.isNegative()) return this;
24 return const UnknownValue();
25 }
26
27 Value max(Value other) {
28 if (this == other) return this;
29 if (other == const MinValue()) return this;
30 if (other == const MaxValue()) return other;
31 Value value = this - other;
32 if (value.isPositive()) return this;
33 if (value.isNegative()) return other;
34 return const UnknownValue();
35 }
36
37 bool isNegative() => false;
38 bool isPositive() => false;
39 bool isZero() => false;
40 }
41
42 /**
43 * An [IntValue] contains a constant integer value.
44 */
45 class IntValue extends Value {
46 final int value;
47 const IntValue(this.value);
48
49 Value operator +(other) {
50 if (other is !IntValue) return other + this;
51 return new IntValue(value + other.value);
52 }
53
54 Value operator -(other) {
55 if (other is !IntValue) return other - this;
56 return new IntValue(value - other.value);
57 }
58
59 Value operator &(other) {
60 if (other is !IntValue) return this;
61 return new IntValue(value & other.value);
62 }
63
64 Value min(other) {
65 if (other is !IntValue) return other.min(this);
66 return this.value < other.value ? this : other;
67 }
68
69 Value max(other) {
70 if (other is !IntValue) return other.max(this);
71 return this.value < other.value ? other : this;
72 }
73
74 bool operator ==(other) {
75 if (other is !IntValue) return false;
76 return this.value == other.value;
77 }
78
79 String toString() => 'IntValue $value';
80 bool isNegative() => value < 0;
81 bool isPositive() => value >= 0;
82 bool isZero() => value == 0;
83 }
84
85 /**
86 * The [MaxValue] represents the maximum value an integer can have,
87 * which is currently +infinity.
88 */
89 class MaxValue extends Value {
Søren Gjesse 2012/09/26 09:08:24 Should this maybe be called MaxIntValue and extend
ngeoffray 2012/09/26 09:33:26 Renaming is fine, but if it extends IntValue what
Søren Gjesse 2012/09/26 14:00:15 Another alternative is to have AbstractIntValue wi
ngeoffray 2012/09/27 13:22:02 Let's rename MaxValue and MinValue to MaxIntValue
90 const MaxValue();
91 Value operator +(Value other) => this;
92 Value operator -(Value other) => this;
93 Value operator &(Value other) {
94 if (other.isPositive()) return other;
95 if (other.isNegative()) return const IntValue(0);
96 return this;
97 }
98 Value min(Value other) => other;
99 Value max(Value other) => this;
100 String toString() => 'Max';
101 bool isNegative() => false;
102 bool isPositive() => true;
103 }
104
105 /**
106 * The [MinValue] represents the minimum value an integer can have,
107 * which is currently -infinity.
108 */
109 class MinValue extends Value {
Søren Gjesse 2012/09/26 09:08:24 MinIntValue?
110 const MinValue();
111 Value operator +(Value other) => this;
112 Value operator -(Value other) => this;
113 Value operator &(Value other) {
114 if (other.isPositive()) return const IntValue(0);
115 if (other.isNegative()) return other;
116 return this;
117 }
118 Value min(Value other) => this;
119 Value max(Value other) => other;
120 String toString() => 'Min';
121 bool isNegative() => true;
122 bool isPositive() => false;
123 }
124
125 /**
126 * The [UnknownValue] is the sentinel in our analysis to mark an
127 * operation that could not be done because of too much complexity.
128 */
129 class UnknownValue extends Value {
130 const UnknownValue();
131 Value operator +(Value other) => const UnknownValue();
132 Value operator -(Value other) => const UnknownValue();
133 Value operator &(Value other) => const UnknownValue();
134 Value min(Value other) => const UnknownValue();
135 Value max(Value other) => const UnknownValue();
136 bool isNegative() => false;
137 bool isPositive() => false;
138 String toString() => 'Unknown';
139 }
140
141 /**
142 * A symbolic value representing an [HInstruction].
143 */
144 class InstructionValue extends Value {
145 final HInstruction instruction;
146 InstructionValue(this.instruction);
147
148 bool operator ==(other) {
149 if (other is !InstructionValue) return false;
150 return this.instruction == other.instruction;
151 }
152
153 Value operator +(Value other) {
154 if (other.isZero()) return this;
155 return new OperationValue(this, other, const AddOperation());
156 }
157
158 Value operator -(Value other) {
159 if (other.isZero()) return this;
160 if (this == other) return const IntValue(0);
161 return new OperationValue(this, other, const SubtractOperation());
162 }
163
164 Value operator &(Value other) {
165 if (other is IntValue) return other & this;
166 return this;
167 }
168
169 bool isNegative() => false;
170 bool isPositive() => false;
171
172 String toString() => 'Instruction: $instruction';
173 }
174
175 /**
176 * Special value for instructions that represent the length of an
177 * array. The difference with an [InstructionValue] is that we know
178 * the value is positive.
179 */
180 class LengthValue extends InstructionValue {
181 LengthValue(HInstruction instruction) : super(instruction);
182 bool isPositive() => true;
183 String toString() => 'Length: $instruction';
184 }
185
186 /**
187 * Represents a binary operation on two [Value], where the operation
188 * did not yield a canonical value.
189 */
190 class OperationValue extends Value {
191 final Value left;
192 final Value right;
193 final Operation operation;
194 OperationValue(this.left, this.right, this.operation);
195
196 bool operator ==(other) {
197 if (other is !OperationValue) return false;
198 return left == other.left
199 && right == other.right
200 && operation == other.operation;
201 }
202
203 Value operator +(Value other) => const UnknownValue();
204 Value operator &(Value other) => const UnknownValue();
205
206 Value operator -(Value other) {
Søren Gjesse 2012/09/26 09:08:24 Could you please explain how this works?
ngeoffray 2012/09/26 09:33:26 Done.
207 Value value = left - other;
208 if (value != const UnknownValue() && value is! OperationValue) {
209 return operation.apply(value, right);
210 }
211 value = right - other;
212 if (value != const UnknownValue() && value is! OperationValue) {
213 return operation.apply(left, value);
214 }
215 return const UnknownValue();
216 }
217
218 bool isNegative() => false;
219 bool isPositive() => false;
220 String toString() => '$left ${operation.name} $right';
221 }
222
223 /**
224 * A [Range] represents the possible integer values an instruction
225 * can have, from its [lower] bound to its [upper] bound, both
226 * included.
227 */
228 class Range {
229 final Value lower;
230 final Value upper;
231 const Range([this.lower = const MinValue(), this.upper = const MaxValue()]);
232
233 /**
234 * Checks if the range has UnknownValue as bounds, and returns a
235 * range that does not have any.
236 */
237 Range normalize() {
238 if (lower != const UnknownValue() && upper != const UnknownValue()) {
239 return this;
240 }
241 Value low = lower == const UnknownValue() ? const MinValue() : lower;
242 Value up = upper == const UnknownValue() ? const MaxValue() : upper;
243 return new Range(low, up);
244 }
245
246 Range union(Range other) {
247 Range range = new Range(lower.min(other.lower), upper.max(other.upper));
Søren Gjesse 2012/09/26 09:08:24 Maybe add Range.normalize constructor.
ngeoffray 2012/09/26 09:33:26 Good point. Done.
248 return range.normalize();
249 }
250
251 intersection(Range other) {
252 Range range = new Range(lower.max(other.lower), upper.min(other.upper));
253 return range.normalize();
254 }
255
256 Range operator +(Range other) {
257 Range range = new Range(lower + other.lower, upper + other.upper);
258 return range.normalize();
259 }
260
261 Range operator -(Range other) {
262 Range range = new Range(lower - other.lower, upper - other.upper);
263 return range.normalize();
264 }
265
266 Range operator &(Range other) {
267 Range range = new Range(lower & other.lower, upper & other.upper);
268 return range.normalize();
269 }
270
271 bool operator ==(other) {
272 if (other is! Range) return false;
273 return other.lower == lower && other.upper == upper;
274 }
275
276 bool isLessThan(Range other) {
277 return upper != other.lower && upper.min(other.lower) == upper;
278 }
279
280 bool isNegative() => upper.isNegative();
281 bool isPositive() => lower.isPositive();
282
283 String toString() => '[$lower, $upper]';
284 }
285
286 /**
287 * Visits the graph in dominator order, and computes value ranges for
288 * integer instructions. While visiting the graph, this phase also
289 * removes unnecessary bounds checks, and comparisons that are proven
290 * to be true or false.
291 */
292 class SsaValueRangeAnalyzer extends HBaseVisitor implements OptimizationPhase {
293 String get name => 'SSA value range builder';
294
295 /**
296 * List of [HRangeConversion] instructions created by the phase. We
297 * save them here in order to remove them once the phase is done.
298 */
299 final List<HRangeConversion> conversions = <HRangeConversion>[];
300
301 /**
302 * Value ranges for integer instructions. This map gets populated by
303 * the dominator tree visit.
304 */
305 final Map<HInstruction, Range> ranges = new Map<HInstruction, Range>();
306
307 final ConstantSystem constantSystem;
308 final HTypeMap types;
309 WorkItem work;
310 HGraph graph;
311
312 SsaValueRangeAnalyzer(this.constantSystem, this.types, WorkItem this.work);
313
314 void visitGraph(HGraph graph) {
315 this.graph = graph;
316 visitDominatorTree(graph);
317 // We remove the range conversions after visiting the graph so
318 // that the graph does not get polluted with these instructions
319 // only necessary for this phase.
320 removeRangeConversion();
321 }
322
323 void removeRangeConversion() {
324 conversions.forEach((HRangeConversion instruction) {
325 instruction.block.rewrite(instruction, instruction.inputs[0]);;
326 instruction.block.remove(instruction);
327 });
328 }
329
330 void visitBasicBlock(HBasicBlock block) {
331
332 void visit(HInstruction instruction) {
333 Range range = instruction.accept(this);
334 if (instruction.isInteger(types)) {
335 assert(range != null);
336 ranges[instruction] = range;
337 }
338 }
339
340 block.forEachPhi(visit);
341 block.forEachInstruction(visit);
342 }
343
344 Range visitInstruction(HInstruction instruction) {
345 return const Range(const MinValue(), const MaxValue());
Søren Gjesse 2012/09/26 09:08:24 What is the difference between returning null and
ngeoffray 2012/09/26 09:33:26 It's to make sure an instruction that has type int
Søren Gjesse 2012/09/26 14:00:15 I think returning the [min,max] range instead of n
346 }
347
348 Range visitParameterValue(HParameterValue parameter) {
349 if (!parameter.isInteger(types)) return null;
350 Value value = new InstructionValue(parameter);
351 return new Range(value, value);
352 }
353
354 Range visitPhi(HPhi phi) {
355 if (!phi.isInteger(types)) return null;
356 if (phi.block.isLoopHeader()) {
357 Range range = tryInferLoopPhiRange(phi);
358 if (range == null) return visitInstruction(phi);
359 return range;
360 }
361
362 Range range = ranges[phi.inputs[0]];
363 for (int i = 1; i < phi.inputs.length; i++) {
364 range = range.union(ranges[phi.inputs[i]]);
365 }
366 return range;
367 }
368
369 Range tryInferLoopPhiRange(HPhi phi) {
370 HInstruction update = phi.inputs[1];
371 return update.accept(new LoopUpdateRecognizer(phi, ranges, types));
372 }
373
374 Range visitConstant(HConstant constant) {
375 if (!constant.isInteger(types)) return null;
376 Value value = new IntValue(constant.constant.value);
377 return new Range(value, value);
378 }
379
380 Range visitInvokeInterceptor(HInvokeInterceptor interceptor) {
381 if (!interceptor.isInteger(types)) return null;
382 if (!interceptor.isLengthGetterOnStringOrArray(types)) {
383 return visitInstruction(interceptor);
384 }
385 LengthValue value = new LengthValue(interceptor);
386 return new Range(value, value);
387 }
388
389 bool handleBoundsCheck(HBoundsCheck check) {
390 Range indexRange = ranges[check.index];
391 Range lengthRange = ranges[check.length];
392 Value maxIndex = lengthRange.upper - const IntValue(1);
393 bool belowLength = maxIndex != const MaxValue()
394 && indexRange.upper.min(maxIndex) == indexRange.upper;
395 if (indexRange.isPositive() && belowLength) {
396 check.block.rewrite(check, check.index);
397 check.block.remove(check);
398 return true;
399 } else if (indexRange.isNegative() || lengthRange.isLessThan(indexRange)) {
400 check.staticChecks = HBoundsCheck.ALWAYS_FALSE;
401 } else if (indexRange.isPositive()) {
402 check.staticChecks = HBoundsCheck.ALWAYS_ABOVE_ZERO;
403 } else if (belowLength) {
404 check.staticChecks = HBoundsCheck.ALWAYS_BELOW_LENGTH;
405 }
406 return false;
407 }
408
409 Range visitBoundsCheck(HBoundsCheck check) {
410 HInstruction next = check.next;
411 Range indexRange = ranges[check.index];
412 Range lengthRange = ranges[check.length];
413 if (handleBoundsCheck(check)) return indexRange;
414 // Update the range of the index.
415 Range newIndexRange = indexRange.intersection(lengthRange);
416 if (indexRange == newIndexRange) return indexRange;
417 HInstruction instruction = createRangeConversion(check.next, check.index);
418 ranges[instruction] = newIndexRange;
419 return newIndexRange;
420 }
421
422 Range visitLess(HLess less) {
423 HInstruction right = less.right;
424 HInstruction left = less.left;
425 if (!left.isInteger(types)) return null;
426 if (!right.isInteger(types)) return null;
427 if (ranges[left].isLessThan(ranges[right])) {
428 less.block.rewrite(less, graph.addConstantBool(true, constantSystem));
429 less.block.remove(less);
430 return null;
431 }
432 if (ranges[right].isLessThan(ranges[left])) {
433 less.block.rewrite(less, graph.addConstantBool(false, constantSystem));
434 less.block.remove(less);
435 return null;
436 }
Søren Gjesse 2012/09/26 09:08:24 Missing explicit return.
ngeoffray 2012/09/26 09:33:26 Done.
437 }
438
439 Range handleBinaryOperation(HBinaryArithmetic instruction) {
440 if (!instruction.isInteger(types)) return null;
441 return instruction.operation(constantSystem).apply(
442 ranges[instruction.left], ranges[instruction.right]);
443 }
444
445 Range visitAdd(HAdd add) {
446 return handleBinaryOperation(add);
447 }
448
449 Range visitSubtract(HSubtract sub) {
450 return handleBinaryOperation(sub);
451 }
452
453 Range visitBitAnd(HBitAnd node) {
454 if (!node.isInteger(types)) return null;
455 HInstruction right = node.right;
456 HInstruction left = node.left;
457 if (left.isInteger(types) && right.isInteger(types)) {
458 return ranges[left] & ranges[right];
459 }
460
461 Range tryComputeRange(HInstruction instruction) {
462 Range range = ranges[instruction];
463 if (range.isPositive()) {
464 return new Range(const IntValue(0), range.upper);
465 } else if (range.isNegative()) {
466 return new Range(range.lower, const IntValue(0));
467 }
468 return visitInstruction(node);
469 }
470
471 if (left.isInteger(types)) {
472 return tryComputeRange(left);
473 } else if (right.isInteger(types)) {
474 return tryComputeRange(right);
475 }
476 return visitInstruction(node);
477 }
478
479 Range visitCheck(HCheck instruction) {
480 if (ranges[instruction.checkedInput] == null) {
481 return visitInstruction(instruction);
482 }
483 return ranges[instruction.checkedInput];
484 }
485
486 HInstruction createRangeConversion(HInstruction cursor,
487 HInstruction instruction) {
488 HRangeConversion newInstruction = new HRangeConversion(instruction);
489 conversions.add(newInstruction);
490 cursor.block.addBefore(cursor, newInstruction);
491 // Update the users of the instruction dominated by [cursor] to
492 // use the new instruction, that has an narrower range.
493 Set<HInstruction> dominatedUsers = instruction.dominatedUsers(cursor);
494 for (HInstruction user in dominatedUsers) {
495 user.changeUse(instruction, newInstruction);
496 }
497 return newInstruction;
498 }
499
500 Range visitConditionalBranch(HConditionalBranch branch) {
501 var condition = branch.condition;
502 // TODO(ngeoffray): Handle more condition kinds.
503 if (condition is !HLess) return null;
504 HInstruction right = condition.right;
505 HInstruction left = condition.left;
506 if (!left.isInteger(types)) return null;
507 if (!right.isInteger(types)) return null;
508
509 // Update the true branch to use a narrower range for [left].
510 // TODO(ngeoffray): Also do it for [right].
511 HInstruction instruction =
512 createRangeConversion(branch.trueBranch.first, left);
513 Range range = new Range(
514 const MinValue(), ranges[right].upper - const IntValue(1));
515 range = range.intersection(ranges[left]);
516 ranges[instruction] = range;
517
518 // Update the false branch to use a narrower range for [left].
519 // TODO(ngeoffray): Also do it for [right].
520 instruction = createRangeConversion(branch.falseBranch.first, left);
521 range = new Range(ranges[right].lower, const MaxValue());
522 range = range.intersection(ranges[left]);
523 ranges[instruction] = range;
524
525 return null;
526 }
527
528 Range visitRangeConversion(HRangeConversion conversion) {
529 return ranges[conversion];
530 }
531 }
532
533 /**
534 * Recognizes a number of patterns in a loop update instruction and
535 * tries to infer a range for the loop phi.
536 */
537 class LoopUpdateRecognizer extends HBaseVisitor {
538 final HPhi loopPhi;
539 final Map<HInstruction, Range> ranges;
540 final HTypeMap types;
541 LoopUpdateRecognizer(this.loopPhi, this.ranges, this.types);
542
543 Range visitAdd(HAdd operation) {
544 Range range = getRangeForRecognizableOperation(operation);
545 if (range == null) return null;
546 Range initial = ranges[loopPhi.inputs[0]];
547 if (range.isPositive()) {
548 return new Range(initial.lower, const MaxValue());
549 } else if (range.isNegative()) {
550 return new Range(const MinValue(), initial.upper);
551 }
Søren Gjesse 2012/09/26 09:08:24 Missing explicit "return null" here.
ngeoffray 2012/09/26 09:33:26 Done.
552 }
553
554 Range visitSubtract(HSubtract operation) {
555 Range range = getRangeForRecognizableOperation(operation);
556 if (range == null) return null;
557 Range initial = ranges[loopPhi.inputs[0]];
558 if (range.isPositive()) {
559 return new Range(const MinValue(), initial.upper);
560 } else if (range.isNegative()) {
561 return new Range(initial.lower, const MaxValue());
562 }
563 return null;
564 }
565
566 Range visitPhi(HPhi phi) {
567 // If one of the inputs is the loop phi, then we're only
568 // interested in the other input: a loop phi feeding itself means
569 // it is not being updated.
570 if (unwrap(phi.inputs[0]) == loopPhi) return phi.inputs[1].accept(this);
571 if (unwrap(phi.inputs[1]) == loopPhi) return phi.inputs[0].accept(this);
572 assert(phi.inputs.length == 2);
Søren Gjesse 2012/09/26 09:08:24 Ditto.
ngeoffray 2012/09/26 09:33:26 Done.
573 }
574
575 Range getRangeForRecognizableOperation(HBinaryArithmetic operation) {
576 if (!operation.left.isInteger(types)) return null;
577 if (!operation.right.isInteger(types)) return null;
578 HInstruction left = unwrap(operation.left);
579 HInstruction right = unwrap(operation.right);
580 // We only recognize operations that operate on the loop phi.
581 bool isLeftLoopPhi = (left == loopPhi);
582 bool isRightLoopPhi = (right == loopPhi);
583 if (!isLeftLoopPhi && !isRightLoopPhi) return null;
584
585 var other = isLeftLoopPhi ? right : left;
586 // If the analysis already computed range for the update, use it.
587 if (ranges[other] != null) return ranges[other];
588
589 // We currently only handle constants in updates if the
590 // update does not have a range.
591 if (other.isConstant()) {
592 Value value = new IntValue(other.constant.value);
593 return new Range(value, value);
594 }
595 return null;
596 }
597
598 /**
599 * [HCheck] instructions may check the loop phi. Since we only
600 * recognize updated on the loop phi, we must [unwrap] the [HCheck]
Søren Gjesse 2012/09/26 09:08:24 updated -> updates
ngeoffray 2012/09/26 09:33:26 Done.
601 * instruction to check if it references the loop phi.
602 */
603 HInstruction unwrap(instruction) {
604 if (instruction is HCheck) return unwrap(instruction.checkedInput);
605 // [HPhi] might have two different [HCheck] instructions as
606 // inputs, checking the same instruction.
607 if (instruction is HPhi && !instruction.block.isLoopHeader()) {
608 HInstruction result = unwrap(instruction.inputs[0]);
609 for (int i = 1; i < instruction.inputs.length; i++) {
610 if (result != unwrap(instruction.inputs[i])) return instruction;
611 }
612 return result;
613 }
614 return instruction;
615 }
616 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698