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

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 {
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 {
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;
Søren Gjesse 2012/09/26 14:00:15 Shouldn't this be MinValue instead of other? They
ngeoffray 2012/09/27 13:22:02 Good point. Done.
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) {
207 // We try to create a simple [Value] out of this operation. So we
208 // first try to substract [other] to [left]. If the result is simple
209 // enough (not unknown and not an operation), we return the result
210 // of doing the operation of this [OperationValue] on the previous
211 // result and [right].
212 //
213 // For example:
214 // OperationValue(LengthValue(i1), IntValue(42), '-') - LengthValue(i1)
215 //
216 // Will return IntValue(-42)
Søren Gjesse 2012/09/26 14:00:15 So using the fact that (a - b) - c == a - (b - c)
ngeoffray 2012/09/27 13:22:02 Added as a comment.
217 Value value = left - other;
218 if (value != const UnknownValue() && value is! OperationValue) {
219 return operation.apply(value, right);
220 }
221 // If the result is not simple enough, we try the same approach
222 // with [right].
223 value = right - other;
224 if (value != const UnknownValue() && value is! OperationValue) {
225 return operation.apply(left, value);
226 }
227 return const UnknownValue();
228 }
229
230 bool isNegative() => false;
231 bool isPositive() => false;
232 String toString() => '$left ${operation.name} $right';
233 }
234
235 /**
236 * A [Range] represents the possible integer values an instruction
237 * can have, from its [lower] bound to its [upper] bound, both
238 * included.
239 */
240 class Range {
241 final Value lower;
242 final Value upper;
243 const Range([this.lower = const MinValue(), this.upper = const MaxValue()]);
244 /**
245 * Checks if the given values are unknown, and creates a
246 * range that does not have any unknown values.
247 */
248 Range.normalize(Value low, Value up)
249 : lower = low == const UnknownValue() ? const MinValue() : low,
250 upper = up == const UnknownValue() ? const MaxValue() : up;
251
252 Range union(Range other) {
253 return new Range.normalize(lower.min(other.lower), upper.max(other.upper));
254 }
255
256 intersection(Range other) {
257 return new Range.normalize(lower.max(other.lower), upper.min(other.upper));
258 }
259
260 Range operator +(Range other) {
261 return new Range.normalize(lower + other.lower, upper + other.upper);
262 }
263
264 Range operator -(Range other) {
265 return new Range.normalize(lower - other.lower, upper - other.upper);
266 }
267
268 Range operator &(Range other) {
269 return new Range.normalize(lower & other.lower, upper & other.upper);
270 }
271
272 bool operator ==(other) {
273 if (other is! Range) return false;
274 return other.lower == lower && other.upper == upper;
275 }
276
277 bool isLessThan(Range other) {
278 return upper != other.lower && upper.min(other.lower) == upper;
279 }
280
281 bool isNegative() => upper.isNegative();
282 bool isPositive() => lower.isPositive();
283
284 String toString() => '[$lower, $upper]';
285 }
286
287 /**
288 * Visits the graph in dominator order, and computes value ranges for
289 * integer instructions. While visiting the graph, this phase also
290 * removes unnecessary bounds checks, and comparisons that are proven
291 * to be true or false.
292 */
293 class SsaValueRangeAnalyzer extends HBaseVisitor implements OptimizationPhase {
294 String get name => 'SSA value range builder';
295
296 /**
297 * List of [HRangeConversion] instructions created by the phase. We
298 * save them here in order to remove them once the phase is done.
299 */
300 final List<HRangeConversion> conversions = <HRangeConversion>[];
301
302 /**
303 * Value ranges for integer instructions. This map gets populated by
304 * the dominator tree visit.
305 */
306 final Map<HInstruction, Range> ranges = new Map<HInstruction, Range>();
307
308 final ConstantSystem constantSystem;
309 final HTypeMap types;
310 WorkItem work;
311 HGraph graph;
312
313 SsaValueRangeAnalyzer(this.constantSystem, this.types, WorkItem this.work);
314
315 void visitGraph(HGraph graph) {
316 this.graph = graph;
317 visitDominatorTree(graph);
318 // We remove the range conversions after visiting the graph so
319 // that the graph does not get polluted with these instructions
320 // only necessary for this phase.
321 removeRangeConversion();
322 }
323
324 void removeRangeConversion() {
325 conversions.forEach((HRangeConversion instruction) {
326 instruction.block.rewrite(instruction, instruction.inputs[0]);;
327 instruction.block.remove(instruction);
328 });
329 }
330
331 void visitBasicBlock(HBasicBlock block) {
332
333 void visit(HInstruction instruction) {
334 Range range = instruction.accept(this);
335 if (instruction.isInteger(types)) {
336 assert(range != null);
337 ranges[instruction] = range;
338 }
339 }
340
341 block.forEachPhi(visit);
342 block.forEachInstruction(visit);
343 }
344
345 Range visitInstruction(HInstruction instruction) {
346 return const Range(const MinValue(), const MaxValue());
347 }
348
349 Range visitParameterValue(HParameterValue parameter) {
350 if (!parameter.isInteger(types)) return null;
351 Value value = new InstructionValue(parameter);
352 return new Range(value, value);
353 }
354
355 Range visitPhi(HPhi phi) {
356 if (!phi.isInteger(types)) return null;
357 if (phi.block.isLoopHeader()) {
358 Range range = tryInferLoopPhiRange(phi);
359 if (range == null) return visitInstruction(phi);
360 return range;
361 }
362
363 Range range = ranges[phi.inputs[0]];
364 for (int i = 1; i < phi.inputs.length; i++) {
365 range = range.union(ranges[phi.inputs[i]]);
366 }
367 return range;
368 }
369
370 Range tryInferLoopPhiRange(HPhi phi) {
371 HInstruction update = phi.inputs[1];
372 return update.accept(new LoopUpdateRecognizer(phi, ranges, types));
373 }
374
375 Range visitConstant(HConstant constant) {
376 if (!constant.isInteger(types)) return null;
377 Value value = new IntValue(constant.constant.value);
378 return new Range(value, value);
379 }
380
381 Range visitInvokeInterceptor(HInvokeInterceptor interceptor) {
382 if (!interceptor.isInteger(types)) return null;
383 if (!interceptor.isLengthGetterOnStringOrArray(types)) {
384 return visitInstruction(interceptor);
385 }
386 LengthValue value = new LengthValue(interceptor);
387 return new Range(value, value);
388 }
389
390 bool handleBoundsCheck(HBoundsCheck check) {
Søren Gjesse 2012/09/26 14:00:15 Maybe comment that this returns true is the bounds
ngeoffray 2012/09/27 13:22:02 Done.
391 Range indexRange = ranges[check.index];
392 Range lengthRange = ranges[check.length];
393 Value maxIndex = lengthRange.upper - const IntValue(1);
394 bool belowLength = maxIndex != const MaxValue()
395 && indexRange.upper.min(maxIndex) == indexRange.upper;
396 if (indexRange.isPositive() && belowLength) {
397 check.block.rewrite(check, check.index);
398 check.block.remove(check);
399 return true;
400 } else if (indexRange.isNegative() || lengthRange.isLessThan(indexRange)) {
401 check.staticChecks = HBoundsCheck.ALWAYS_FALSE;
402 } else if (indexRange.isPositive()) {
403 check.staticChecks = HBoundsCheck.ALWAYS_ABOVE_ZERO;
404 } else if (belowLength) {
405 check.staticChecks = HBoundsCheck.ALWAYS_BELOW_LENGTH;
406 }
407 return false;
408 }
409
410 Range visitBoundsCheck(HBoundsCheck check) {
411 HInstruction next = check.next;
412 Range indexRange = ranges[check.index];
413 Range lengthRange = ranges[check.length];
414 if (handleBoundsCheck(check)) return indexRange;
415 // Update the range of the index.
416 Range newIndexRange = indexRange.intersection(lengthRange);
417 if (indexRange == newIndexRange) return indexRange;
418 HInstruction instruction = createRangeConversion(check.next, check.index);
419 ranges[instruction] = newIndexRange;
420 return newIndexRange;
421 }
422
423 Range visitLess(HLess less) {
424 HInstruction right = less.right;
425 HInstruction left = less.left;
426 if (!left.isInteger(types)) return null;
427 if (!right.isInteger(types)) return null;
428 if (ranges[left].isLessThan(ranges[right])) {
429 less.block.rewrite(less, graph.addConstantBool(true, constantSystem));
430 less.block.remove(less);
431 return null;
432 }
433 if (ranges[right].isLessThan(ranges[left])) {
434 less.block.rewrite(less, graph.addConstantBool(false, constantSystem));
435 less.block.remove(less);
436 return null;
437 }
438 return null;
439 }
440
441 Range handleBinaryOperation(HBinaryArithmetic instruction) {
442 if (!instruction.isInteger(types)) return null;
443 return instruction.operation(constantSystem).apply(
444 ranges[instruction.left], ranges[instruction.right]);
445 }
446
447 Range visitAdd(HAdd add) {
448 return handleBinaryOperation(add);
449 }
450
451 Range visitSubtract(HSubtract sub) {
452 return handleBinaryOperation(sub);
453 }
454
455 Range visitBitAnd(HBitAnd node) {
456 if (!node.isInteger(types)) return null;
457 HInstruction right = node.right;
458 HInstruction left = node.left;
459 if (left.isInteger(types) && right.isInteger(types)) {
460 return ranges[left] & ranges[right];
461 }
462
463 Range tryComputeRange(HInstruction instruction) {
464 Range range = ranges[instruction];
465 if (range.isPositive()) {
466 return new Range(const IntValue(0), range.upper);
467 } else if (range.isNegative()) {
468 return new Range(range.lower, const IntValue(0));
469 }
470 return visitInstruction(node);
471 }
472
473 if (left.isInteger(types)) {
474 return tryComputeRange(left);
475 } else if (right.isInteger(types)) {
476 return tryComputeRange(right);
477 }
478 return visitInstruction(node);
479 }
480
481 Range visitCheck(HCheck instruction) {
482 if (ranges[instruction.checkedInput] == null) {
483 return visitInstruction(instruction);
484 }
485 return ranges[instruction.checkedInput];
486 }
487
488 HInstruction createRangeConversion(HInstruction cursor,
489 HInstruction instruction) {
490 HRangeConversion newInstruction = new HRangeConversion(instruction);
491 conversions.add(newInstruction);
492 cursor.block.addBefore(cursor, newInstruction);
493 // Update the users of the instruction dominated by [cursor] to
494 // use the new instruction, that has an narrower range.
495 Set<HInstruction> dominatedUsers = instruction.dominatedUsers(cursor);
496 for (HInstruction user in dominatedUsers) {
497 user.changeUse(instruction, newInstruction);
498 }
499 return newInstruction;
500 }
501
502 Range visitConditionalBranch(HConditionalBranch branch) {
503 var condition = branch.condition;
504 // TODO(ngeoffray): Handle more condition kinds.
505 if (condition is !HLess) return null;
506 HInstruction right = condition.right;
507 HInstruction left = condition.left;
508 if (!left.isInteger(types)) return null;
509 if (!right.isInteger(types)) return null;
510
511 // Update the true branch to use a narrower range for [left].
512 // TODO(ngeoffray): Also do it for [right].
513 HInstruction instruction =
514 createRangeConversion(branch.trueBranch.first, left);
515 Range range = new Range(
516 const MinValue(), ranges[right].upper - const IntValue(1));
517 range = range.intersection(ranges[left]);
518 ranges[instruction] = range;
519
520 // Update the false branch to use a narrower range for [left].
521 // TODO(ngeoffray): Also do it for [right].
522 instruction = createRangeConversion(branch.falseBranch.first, left);
523 range = new Range(ranges[right].lower, const MaxValue());
524 range = range.intersection(ranges[left]);
525 ranges[instruction] = range;
526
527 return null;
528 }
529
530 Range visitRangeConversion(HRangeConversion conversion) {
531 return ranges[conversion];
532 }
533 }
534
535 /**
536 * Recognizes a number of patterns in a loop update instruction and
537 * tries to infer a range for the loop phi.
538 */
539 class LoopUpdateRecognizer extends HBaseVisitor {
540 final HPhi loopPhi;
541 final Map<HInstruction, Range> ranges;
542 final HTypeMap types;
543 LoopUpdateRecognizer(this.loopPhi, this.ranges, this.types);
544
545 Range visitAdd(HAdd operation) {
546 Range range = getRangeForRecognizableOperation(operation);
547 if (range == null) return null;
548 Range initial = ranges[loopPhi.inputs[0]];
549 if (range.isPositive()) {
550 return new Range(initial.lower, const MaxValue());
551 } else if (range.isNegative()) {
552 return new Range(const MinValue(), initial.upper);
553 }
554 return null;
555 }
556
557 Range visitSubtract(HSubtract operation) {
558 Range range = getRangeForRecognizableOperation(operation);
559 if (range == null) return null;
560 Range initial = ranges[loopPhi.inputs[0]];
561 if (range.isPositive()) {
562 return new Range(const MinValue(), initial.upper);
563 } else if (range.isNegative()) {
564 return new Range(initial.lower, const MaxValue());
565 }
566 return null;
567 }
568
569 Range visitPhi(HPhi phi) {
570 // If one of the inputs is the loop phi, then we're only
571 // interested in the other input: a loop phi feeding itself means
572 // it is not being updated.
573 if (unwrap(phi.inputs[0]) == loopPhi) return phi.inputs[1].accept(this);
574 if (unwrap(phi.inputs[1]) == loopPhi) return phi.inputs[0].accept(this);
575 assert(phi.inputs.length == 2);
576 return null;
577 }
578
579 Range getRangeForRecognizableOperation(HBinaryArithmetic operation) {
580 if (!operation.left.isInteger(types)) return null;
581 if (!operation.right.isInteger(types)) return null;
582 HInstruction left = unwrap(operation.left);
583 HInstruction right = unwrap(operation.right);
584 // We only recognize operations that operate on the loop phi.
585 bool isLeftLoopPhi = (left == loopPhi);
586 bool isRightLoopPhi = (right == loopPhi);
587 if (!isLeftLoopPhi && !isRightLoopPhi) return null;
588
589 var other = isLeftLoopPhi ? right : left;
590 // If the analysis already computed range for the update, use it.
591 if (ranges[other] != null) return ranges[other];
592
593 // We currently only handle constants in updates if the
594 // update does not have a range.
595 if (other.isConstant()) {
596 Value value = new IntValue(other.constant.value);
597 return new Range(value, value);
598 }
599 return null;
600 }
601
602 /**
603 * [HCheck] instructions may check the loop phi. Since we only
604 * recognize updates on the loop phi, we must [unwrap] the [HCheck]
605 * instruction to check if it references the loop phi.
606 */
607 HInstruction unwrap(instruction) {
608 if (instruction is HCheck) return unwrap(instruction.checkedInput);
609 // [HPhi] might have two different [HCheck] instructions as
610 // inputs, checking the same instruction.
611 if (instruction is HPhi && !instruction.block.isLoopHeader()) {
612 HInstruction result = unwrap(instruction.inputs[0]);
613 for (int i = 1; i < instruction.inputs.length; i++) {
614 if (result != unwrap(instruction.inputs[i])) return instruction;
615 }
616 return result;
617 }
618 return instruction;
619 }
620 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698