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

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
« no previous file with comments | « lib/compiler/implementation/ssa/types.dart ('k') | tests/compiler/dart2js/mock_compiler.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 MinIntValue()) return other;
20 if (other == const MaxIntValue()) 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 MinIntValue()) return this;
30 if (other == const MaxIntValue()) 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 [MaxIntValue] represents the maximum value an integer can have,
87 * which is currently +infinity.
88 */
89 class MaxIntValue extends Value {
90 const MaxIntValue();
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 [MinIntValue] represents the minimum value an integer can have,
107 * which is currently -infinity.
108 */
109 class MinIntValue extends Value {
110 const MinIntValue();
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 return this;
116 }
117 Value min(Value other) => this;
118 Value max(Value other) => other;
119 String toString() => 'Min';
120 bool isNegative() => true;
121 bool isPositive() => false;
122 }
123
124 /**
125 * The [UnknownValue] is the sentinel in our analysis to mark an
126 * operation that could not be done because of too much complexity.
127 */
128 class UnknownValue extends Value {
129 const UnknownValue();
130 Value operator +(Value other) => const UnknownValue();
131 Value operator -(Value other) => const UnknownValue();
132 Value operator &(Value other) => const UnknownValue();
133 Value min(Value other) => const UnknownValue();
134 Value max(Value other) => const UnknownValue();
135 bool isNegative() => false;
136 bool isPositive() => false;
137 String toString() => 'Unknown';
138 }
139
140 /**
141 * A symbolic value representing an [HInstruction].
142 */
143 class InstructionValue extends Value {
144 final HInstruction instruction;
145 InstructionValue(this.instruction);
146
147 bool operator ==(other) {
148 if (other is !InstructionValue) return false;
149 return this.instruction == other.instruction;
150 }
151
152 Value operator +(Value other) {
153 if (other.isZero()) return this;
154 return new OperationValue(this, other, const AddOperation());
155 }
156
157 Value operator -(Value other) {
158 if (other.isZero()) return this;
159 if (this == other) return const IntValue(0);
160 return new OperationValue(this, other, const SubtractOperation());
161 }
162
163 Value operator &(Value other) {
164 if (other is IntValue) return other & this;
165 return this;
166 }
167
168 bool isNegative() => false;
169 bool isPositive() => false;
170
171 String toString() => 'Instruction: $instruction';
172 }
173
174 /**
175 * Special value for instructions that represent the length of an
176 * array. The difference with an [InstructionValue] is that we know
177 * the value is positive.
178 */
179 class LengthValue extends InstructionValue {
180 LengthValue(HInstruction instruction) : super(instruction);
181 bool isPositive() => true;
182 String toString() => 'Length: $instruction';
183 }
184
185 /**
186 * Represents a binary operation on two [Value], where the operation
187 * did not yield a canonical value.
188 */
189 class OperationValue extends Value {
190 final Value left;
191 final Value right;
192 final Operation operation;
193 OperationValue(this.left, this.right, this.operation);
194
195 bool operator ==(other) {
196 if (other is !OperationValue) return false;
197 return left == other.left
198 && right == other.right
199 && operation == other.operation;
200 }
201
202 Value operator +(Value other) => const UnknownValue();
203 Value operator &(Value other) => const UnknownValue();
204
205 Value operator -(Value other) {
206 // We try to create a simple [Value] out of this operation. So we
207 // first try to substract [other] to [left]. If the result is simple
208 // enough (not unknown and not an operation), we return the result
209 // of doing the operation of this [OperationValue] on the previous
210 // result and [right].
211 //
212 // For example:
213 // OperationValue(LengthValue(i1), IntValue(42), '-') - LengthValue(i1)
214 //
215 // Will return IntValue(-42)
216 //
217 // We're using the fact that (a - b) - c == a - (b - c) == (a - c) - b.
218 Value value = left - other;
219 if (value != const UnknownValue() && value is! OperationValue) {
220 return operation.apply(value, right);
221 }
222 // If the result is not simple enough, we try the same approach
223 // with [right].
224 value = right - other;
225 if (value != const UnknownValue() && value is! OperationValue) {
226 return operation.apply(left, value);
227 }
228 return const UnknownValue();
229 }
230
231 bool isNegative() => false;
232 bool isPositive() => false;
233 String toString() => '$left ${operation.name} $right';
234 }
235
236 /**
237 * A [Range] represents the possible integer values an instruction
238 * can have, from its [lower] bound to its [upper] bound, both
239 * included.
240 */
241 class Range {
242 final Value lower;
243 final Value upper;
244 const Range(this.lower, this.upper);
245 const Range.unbound()
246 : lower = const MinIntValue(),
247 upper = const MaxIntValue();
248 /**
249 * Checks if the given values are unknown, and creates a
250 * range that does not have any unknown values.
251 */
252 Range.normalize(Value low, Value up)
253 : lower = low == const UnknownValue() ? const MinIntValue() : low,
254 upper = up == const UnknownValue() ? const MaxIntValue() : up;
255
256 Range union(Range other) {
257 return new Range.normalize(lower.min(other.lower), upper.max(other.upper));
258 }
259
260 intersection(Range other) {
261 Value low = lower.max(other.lower);
262 Value up = upper.min(other.upper);
263 // If we could not compute max or min, pick a value in the two
264 // ranges, with priority to [IntValue]s because they are simpler.
265 if (low == const UnknownValue()) {
266 if (lower is IntValue) low = lower;
267 else if (other.lower is IntValue) low = other.lower;
268 else low = lower;
269 }
270 if (up == const UnknownValue()) {
271 if (upper is IntValue) up = upper;
272 else if (other.upper is IntValue) up = other.upper;
273 else up = upper;
274 }
275 return new Range(low, up);
276 }
277
278 Range operator +(Range other) {
279 return new Range.normalize(lower + other.lower, upper + other.upper);
280 }
281
282 Range operator -(Range other) {
283 return new Range.normalize(lower - other.lower, upper - other.upper);
284 }
285
286 Range operator &(Range other) {
287 return new Range.normalize(lower & other.lower, upper & other.upper);
288 }
289
290 bool operator ==(other) {
291 if (other is! Range) return false;
292 return other.lower == lower && other.upper == upper;
293 }
294
295 bool isLessThan(Range other) {
296 return upper != other.lower && upper.min(other.lower) == upper;
297 }
298
299 bool isNegative() => upper.isNegative();
300 bool isPositive() => lower.isPositive();
301
302 String toString() => '[$lower, $upper]';
303 }
304
305 /**
306 * Visits the graph in dominator order, and computes value ranges for
307 * integer instructions. While visiting the graph, this phase also
308 * removes unnecessary bounds checks, and comparisons that are proven
309 * to be true or false.
310 */
311 class SsaValueRangeAnalyzer extends HBaseVisitor implements OptimizationPhase {
312 String get name => 'SSA value range builder';
313
314 /**
315 * List of [HRangeConversion] instructions created by the phase. We
316 * save them here in order to remove them once the phase is done.
317 */
318 final List<HRangeConversion> conversions = <HRangeConversion>[];
319
320 /**
321 * Value ranges for integer instructions. This map gets populated by
322 * the dominator tree visit.
323 */
324 final Map<HInstruction, Range> ranges = new Map<HInstruction, Range>();
325
326 final ConstantSystem constantSystem;
327 final HTypeMap types;
328 WorkItem work;
329 HGraph graph;
330
331 SsaValueRangeAnalyzer(this.constantSystem, this.types, WorkItem this.work);
332
333 void visitGraph(HGraph graph) {
334 this.graph = graph;
335 visitDominatorTree(graph);
336 // We remove the range conversions after visiting the graph so
337 // that the graph does not get polluted with these instructions
338 // only necessary for this phase.
339 removeRangeConversion();
340 }
341
342 void removeRangeConversion() {
343 conversions.forEach((HRangeConversion instruction) {
344 instruction.block.rewrite(instruction, instruction.inputs[0]);;
345 instruction.block.remove(instruction);
346 });
347 }
348
349 void visitBasicBlock(HBasicBlock block) {
350
351 void visit(HInstruction instruction) {
352 Range range = instruction.accept(this);
353 if (instruction.isInteger(types)) {
354 assert(range != null);
355 ranges[instruction] = range;
356 }
357 }
358
359 block.forEachPhi(visit);
360 block.forEachInstruction(visit);
361 }
362
363 Range visitInstruction(HInstruction instruction) {
364 return const Range.unbound();
365 }
366
367 Range visitParameterValue(HParameterValue parameter) {
368 if (!parameter.isInteger(types)) return const Range.unbound();
369 Value value = new InstructionValue(parameter);
370 return new Range(value, value);
371 }
372
373 Range visitPhi(HPhi phi) {
374 if (!phi.isInteger(types)) return const Range.unbound();
375 if (phi.block.isLoopHeader()) {
376 Range range = tryInferLoopPhiRange(phi);
377 if (range == null) return const Range.unbound();
378 return range;
379 }
380
381 Range range = ranges[phi.inputs[0]];
382 for (int i = 1; i < phi.inputs.length; i++) {
383 range = range.union(ranges[phi.inputs[i]]);
384 }
385 return range;
386 }
387
388 Range tryInferLoopPhiRange(HPhi phi) {
389 HInstruction update = phi.inputs[1];
390 return update.accept(new LoopUpdateRecognizer(phi, ranges, types));
391 }
392
393 Range visitConstant(HConstant constant) {
394 if (!constant.isInteger(types)) return const Range.unbound();
395 Value value = new IntValue(constant.constant.value);
396 return new Range(value, value);
397 }
398
399 Range visitInvokeInterceptor(HInvokeInterceptor interceptor) {
400 if (!interceptor.isInteger(types)) return const Range.unbound();
401 if (!interceptor.isLengthGetterOnStringOrArray(types)) {
402 return visitInstruction(interceptor);
403 }
404 LengthValue value = new LengthValue(interceptor);
405 return new Range(value, value);
406 }
407
408 /**
409 * Returns true if the bounds check was eliminated.
410 */
411 bool handleBoundsCheck(HBoundsCheck check) {
412 Range indexRange = ranges[check.index];
413 Range lengthRange = ranges[check.length];
414 Value maxIndex = lengthRange.upper - const IntValue(1);
415 bool belowLength = maxIndex != const MaxIntValue()
416 && indexRange.upper.min(maxIndex) == indexRange.upper;
417 if (indexRange.isPositive() && belowLength) {
418 check.block.rewrite(check, check.index);
419 check.block.remove(check);
420 return true;
421 } else if (indexRange.isNegative() || lengthRange.isLessThan(indexRange)) {
422 check.staticChecks = HBoundsCheck.ALWAYS_FALSE;
423 } else if (indexRange.isPositive()) {
424 check.staticChecks = HBoundsCheck.ALWAYS_ABOVE_ZERO;
425 } else if (belowLength) {
426 check.staticChecks = HBoundsCheck.ALWAYS_BELOW_LENGTH;
427 }
428 return false;
429 }
430
431 Range visitBoundsCheck(HBoundsCheck check) {
432 HInstruction next = check.next;
433 Range indexRange = ranges[check.index];
434 Range lengthRange = ranges[check.length];
435 if (handleBoundsCheck(check)) return indexRange;
436 Range newIndexRange = indexRange.intersection(lengthRange);
437 // TODO(ngeoffray): Update the range of the index.
438 return newIndexRange;
439 }
440
441 Range visitLess(HLess less) {
442 HInstruction right = less.right;
443 HInstruction left = less.left;
444 if (!left.isInteger(types)) return const Range.unbound();
445 if (!right.isInteger(types)) return const Range.unbound();
446 if (ranges[left].isLessThan(ranges[right])) {
447 less.block.rewrite(less, graph.addConstantBool(true, constantSystem));
448 less.block.remove(less);
449 return const Range.unbound();
450 }
451 if (ranges[right].isLessThan(ranges[left])) {
452 less.block.rewrite(less, graph.addConstantBool(false, constantSystem));
453 less.block.remove(less);
454 return const Range.unbound();
455 }
456 return const Range.unbound();
457 }
458
459 Range handleBinaryOperation(HBinaryArithmetic instruction) {
460 if (!instruction.isInteger(types)) return const Range.unbound();
461 return instruction.operation(constantSystem).apply(
462 ranges[instruction.left], ranges[instruction.right]);
463 }
464
465 Range visitAdd(HAdd add) {
466 return handleBinaryOperation(add);
467 }
468
469 Range visitSubtract(HSubtract sub) {
470 return handleBinaryOperation(sub);
471 }
472
473 Range visitBitAnd(HBitAnd node) {
474 if (!node.isInteger(types)) return const Range.unbound();
475 HInstruction right = node.right;
476 HInstruction left = node.left;
477 if (left.isInteger(types) && right.isInteger(types)) {
478 return ranges[left] & ranges[right];
479 }
480
481 Range tryComputeRange(HInstruction instruction) {
482 Range range = ranges[instruction];
483 if (range.isPositive()) {
484 return new Range(const IntValue(0), range.upper);
485 } else if (range.isNegative()) {
486 return new Range(range.lower, const IntValue(0));
487 }
488 return const Range.unbound();
489 }
490
491 if (left.isInteger(types)) {
492 return tryComputeRange(left);
493 } else if (right.isInteger(types)) {
494 return tryComputeRange(right);
495 }
496 return const Range.unbound();
497 }
498
499 Range visitCheck(HCheck instruction) {
500 if (ranges[instruction.checkedInput] == null) {
501 return const Range.unbound();
502 }
503 return ranges[instruction.checkedInput];
504 }
505
506 HInstruction createRangeConversion(HInstruction cursor,
507 HInstruction instruction) {
508 HRangeConversion newInstruction = new HRangeConversion(instruction);
509 conversions.add(newInstruction);
510 cursor.block.addBefore(cursor, newInstruction);
511 // Update the users of the instruction dominated by [cursor] to
512 // use the new instruction, that has an narrower range.
513 Set<HInstruction> dominatedUsers = instruction.dominatedUsers(cursor);
514 for (HInstruction user in dominatedUsers) {
515 user.changeUse(instruction, newInstruction);
516 }
517 return newInstruction;
518 }
519
520 Range visitConditionalBranch(HConditionalBranch branch) {
521 var condition = branch.condition;
522 // TODO(ngeoffray): Handle more condition kinds.
523 if (condition is !HLess) return const Range.unbound();
524 HInstruction right = condition.right;
525 HInstruction left = condition.left;
526 if (!left.isInteger(types)) return const Range.unbound();
527 if (!right.isInteger(types)) return const Range.unbound();
528
529 // Update the true branch to use a narrower range for [left].
530 // TODO(ngeoffray): Also do it for [right].
531 HInstruction instruction =
532 createRangeConversion(branch.trueBranch.first, left);
533 Range range = new Range(
534 const MinIntValue(), ranges[right].upper - const IntValue(1));
535 range = range.intersection(ranges[left]);
536 ranges[instruction] = range;
537
538 // Update the false branch to use a narrower range for [left].
539 // TODO(ngeoffray): Also do it for [right].
540 instruction = createRangeConversion(branch.falseBranch.first, left);
541 range = new Range(ranges[right].lower, const MaxIntValue());
542 range = range.intersection(ranges[left]);
543 ranges[instruction] = range;
544
545 return const Range.unbound();
546 }
547
548 Range visitRangeConversion(HRangeConversion conversion) {
549 return ranges[conversion];
550 }
551 }
552
553 /**
554 * Recognizes a number of patterns in a loop update instruction and
555 * tries to infer a range for the loop phi.
556 */
557 class LoopUpdateRecognizer extends HBaseVisitor {
558 final HPhi loopPhi;
559 final Map<HInstruction, Range> ranges;
560 final HTypeMap types;
561 LoopUpdateRecognizer(this.loopPhi, this.ranges, this.types);
562
563 Range visitAdd(HAdd operation) {
564 Range range = getRangeForRecognizableOperation(operation);
565 if (range == null) return const Range.unbound();
566 Range initial = ranges[loopPhi.inputs[0]];
567 if (range.isPositive()) {
568 return new Range(initial.lower, const MaxIntValue());
569 } else if (range.isNegative()) {
570 return new Range(const MinIntValue(), initial.upper);
571 }
572 return const Range.unbound();
573 }
574
575 Range visitSubtract(HSubtract operation) {
576 Range range = getRangeForRecognizableOperation(operation);
577 if (range == null) return const Range.unbound();
578 Range initial = ranges[loopPhi.inputs[0]];
579 if (range.isPositive()) {
580 return new Range(const MinIntValue(), initial.upper);
581 } else if (range.isNegative()) {
582 return new Range(initial.lower, const MaxIntValue());
583 }
584 return const Range.unbound();
585 }
586
587 Range visitPhi(HPhi phi) {
588 Range phiRange;
589 for (HInstruction input in phi.inputs) {
590 HInstruction instruction = unwrap(input);
591 // If one of the inputs is the loop phi, then we're only
592 // interested in the other inputs: a loop phi feeding itself means
593 // it is not being updated.
594 if (instruction == loopPhi) continue;
595
596 // If another loop phi is involved, it's too complex to analyze.
597 if (instruction is HPhi && instruction.block.isLoopHeader()) return null;
598
599 Range inputRange = instruction.accept(this);
600 if (inputRange == null) return null;
601 if (phiRange == null) {
602 phiRange = inputRange;
603 } else {
604 phiRange = phiRange.union(inputRange);
605 }
606 }
607 return phiRange;
608 }
609
610 /**
611 * If [operation] is recognizable, returns the inferred range.
612 * Otherwise returns [null].
613 */
614 Range getRangeForRecognizableOperation(HBinaryArithmetic operation) {
615 if (!operation.left.isInteger(types)) return null;
616 if (!operation.right.isInteger(types)) return null;
617 HInstruction left = unwrap(operation.left);
618 HInstruction right = unwrap(operation.right);
619 // We only recognize operations that operate on the loop phi.
620 bool isLeftLoopPhi = (left == loopPhi);
621 bool isRightLoopPhi = (right == loopPhi);
622 if (!isLeftLoopPhi && !isRightLoopPhi) return null;
623
624 var other = isLeftLoopPhi ? right : left;
625 // If the analysis already computed range for the update, use it.
626 if (ranges[other] != null) return ranges[other];
627
628 // We currently only handle constants in updates if the
629 // update does not have a range.
630 if (other.isConstant()) {
631 Value value = new IntValue(other.constant.value);
632 return new Range(value, value);
633 }
634 return null;
635 }
636
637 /**
638 * [HCheck] instructions may check the loop phi. Since we only
639 * recognize updates on the loop phi, we must [unwrap] the [HCheck]
640 * instruction to check if it references the loop phi.
641 */
642 HInstruction unwrap(instruction) {
643 if (instruction is HCheck) return unwrap(instruction.checkedInput);
644 // [HPhi] might have two different [HCheck] instructions as
645 // inputs, checking the same instruction.
646 if (instruction is HPhi && !instruction.block.isLoopHeader()) {
647 HInstruction result = unwrap(instruction.inputs[0]);
648 for (int i = 1; i < instruction.inputs.length; i++) {
649 if (result != unwrap(instruction.inputs[i])) return instruction;
650 }
651 return result;
652 }
653 return instruction;
654 }
655 }
OLDNEW
« no previous file with comments | « lib/compiler/implementation/ssa/types.dart ('k') | tests/compiler/dart2js/mock_compiler.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698