OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2015, 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 library dart2js.cps_ir.bounds_checker; | |
6 | |
7 import 'cps_ir_nodes.dart'; | |
8 import 'optimizers.dart' show Pass; | |
9 import 'octagon.dart'; | |
10 import '../constants/values.dart'; | |
11 import 'cps_fragment.dart'; | |
12 import 'type_mask_system.dart'; | |
13 import '../world.dart'; | |
14 import '../elements/elements.dart'; | |
15 | |
16 /// Eliminates bounds checks when they can be proven safe. | |
17 /// | |
18 /// In general, this pass will try to eliminate any branch with arithmetic | |
19 /// in the condition, i.e. `x < y`, `x <= y`, `x == y` etc. | |
20 /// | |
21 /// The analysis uses an [Octagon] abstract domain. Unlike traditional octagon | |
22 /// analyzers, we do not use a closed matrix representation, but just maintain | |
23 /// a bucket of constraints. Constraints can therefore be added and removed | |
24 /// on-the-fly without significant overhead. | |
25 /// | |
26 /// We never copy the constraint system. While traversing the IR, the | |
27 /// constraint system is mutated to take into account the knowledge that is | |
28 /// valid for the current location. Constraints are added when entering a | |
29 /// branch, for instance, and removed again after the branch has been processed. | |
30 /// | |
31 /// Loops are analyzed in two passes. The first pass establishes monotonicity | |
32 /// of loop variables, which the second pass uses to compute upper/lower bounds. | |
33 /// The first pass also records whether any side effects occurred in the loop. | |
34 /// | |
35 /// The two-pass scheme is suboptimal compared to a least fixed-point | |
36 /// computation, but does not require repeated iteration. Repeated iteration | |
37 /// would be expensive, since we cannot perform a sparse analysis with our | |
38 /// mutable octagon representation. | |
39 class BoundsChecker extends RecursiveVisitor implements Pass { | |
40 String get passName => 'Bounds checker'; | |
41 | |
42 static const int MAX_UINT32 = (1 << 32) - 1; | |
43 | |
44 /// All integers of this magnitude or less are representable as JS numbers. | |
45 static const int MAX_SAFE_INT = (1 << 53) - 1; | |
46 | |
47 /// Marker to indicate that a continuation should get a unique effect number. | |
48 static const int NEW_EFFECT = -1; | |
49 | |
50 final TypeMaskSystem types; | |
51 final World world; | |
52 | |
53 /// Fields for the constraint system and its variables. | |
54 final Octagon octagon = new Octagon(); | |
55 final Map<Primitive, SignedVariable> valueOf = {}; | |
56 final Map<Primitive, Map<int, SignedVariable>> lengthOf = {}; | |
57 | |
58 /// Fields for the two-pass handling of loops. | |
59 final Set<Continuation> loopsWithSideEffects = new Set<Continuation>(); | |
60 final Map<Parameter, Monotonicity> monotonicity = <Parameter, Monotonicity>{}; | |
61 bool isStrongLoopPass; | |
62 bool foundLoop = false; | |
63 | |
64 /// Fields for tracking side effects. | |
65 /// | |
66 /// The IR is divided into regions wherein the lengths of indexable objects | |
67 /// are known not to change. Regions are identified by their "effect number". | |
68 final Map<Continuation, int> effectNumberAt = <Continuation, int>{}; | |
69 int currentEffectNumber = 0; | |
70 int effectNumberCounter = 0; | |
71 | |
72 BoundsChecker(this.types, this.world); | |
73 | |
74 void rewrite(FunctionDefinition node) { | |
75 isStrongLoopPass = false; | |
76 visit(node); | |
77 if (foundLoop) { | |
78 isStrongLoopPass = true; | |
79 effectNumberAt.clear(); | |
80 visit(node); | |
81 } | |
82 } | |
83 | |
84 /// ------------- VARIABLES ----------------- | |
85 | |
86 int makeNewEffect() => ++effectNumberCounter; | |
87 | |
88 bool isInt(Primitive prim) { | |
89 return types.isDefinitelyInt(prim.type); | |
90 } | |
91 | |
92 bool isUInt32(Primitive prim) { | |
93 return types.isDefinitelyUInt32(prim.type); | |
94 } | |
95 | |
96 /// Get a constraint variable representing the numeric value of [number]. | |
97 SignedVariable getValue(Primitive number) { | |
98 assert(isInt(number)); | |
99 number = number.effectiveDefinition; | |
100 int min, max; | |
101 if (isUInt32(number)) { | |
sra1
2015/09/30 21:54:19
Is there a benefit to matching JSPositiveInt too?
asgerf
2015/10/01 09:49:34
Might as well match against it.
| |
102 min = 0; | |
103 max = MAX_UINT32; | |
104 } | |
105 return valueOf.putIfAbsent(number, () => octagon.makeVariable(min, max)); | |
106 } | |
107 | |
108 /// Get a constraint variable representing the length of [indexableObject] at | |
109 /// program locations with the given [effectCounter]. | |
110 SignedVariable getLength(Primitive indexableObject, int effectCounter) { | |
111 indexableObject = indexableObject.effectiveDefinition; | |
112 if (indexableObject.type != null && | |
113 types.isDefinitelyFixedLengthIndexable(indexableObject.type)) { | |
114 // Always use the same effect counter if the length is immutable. | |
115 effectCounter = 0; | |
116 } | |
117 return lengthOf | |
118 .putIfAbsent(indexableObject, () => <int, SignedVariable>{}) | |
119 .putIfAbsent(effectCounter, () => octagon.makeVariable(0, MAX_UINT32)); | |
120 } | |
121 | |
122 /// ------------- CONSTRAINT HELPERS ----------------- | |
sra1
2015/09/30 21:54:19
Use '//'.
This is a doc comment that applies to ap
asgerf
2015/10/01 09:49:33
Done.
| |
123 | |
124 // Puts the given constraint "in scope" by adding it to the octagon, and | |
125 // pushing a stack action that will remove it again. | |
126 void applyConstraint(SignedVariable v1, SignedVariable v2, int k) { | |
127 Constraint constraint = new Constraint(v1, v2, k); | |
128 octagon.pushConstraint(constraint); | |
129 pushAction(() => octagon.popConstraint(constraint)); | |
130 } | |
131 | |
132 /// Return true if we can prove that `v1 + v2 <= k`. | |
133 bool testConstraint(SignedVariable v1, SignedVariable v2, int k) { | |
134 // Add the negated constraint and check for solvability. | |
135 // !(v1 + v2 <= k) <==> -v1 - v2 <= -k-1 | |
136 Constraint constraint = new Constraint(v1.negated, v2.negated, -k - 1); | |
137 octagon.pushConstraint(constraint); | |
138 bool answer = octagon.isUnsolvable; | |
139 octagon.popConstraint(constraint); | |
140 return answer; | |
141 } | |
142 | |
143 void makeLessThanOrEqual(SignedVariable v1, SignedVariable v2) { | |
144 // v1 <= v2 <==> v1 - v2 <= 0 | |
145 applyConstraint(v1, v2.negated, 0); | |
146 } | |
147 | |
148 void makeLessThan(SignedVariable v1, SignedVariable v2) { | |
149 // v1 < v2 <==> v1 - v2 <= -1 | |
150 applyConstraint(v1, v2.negated, -1); | |
151 } | |
152 | |
153 void makeGreaterThanOrEqual(SignedVariable v1, SignedVariable v2) { | |
154 // v1 >= v2 <==> v2 - v1 <= 0 | |
155 applyConstraint(v2, v1.negated, 0); | |
156 } | |
157 | |
158 void makeGreaterThan(SignedVariable v1, SignedVariable v2) { | |
159 // v1 > v2 <==> v2 - v1 <= -1 | |
160 applyConstraint(v2, v1.negated, -1); | |
161 } | |
162 | |
163 void makeConstant(SignedVariable v1, int k) { | |
164 // We model this using the constraints: | |
165 // v1 + v1 <= 2k | |
166 // -v1 - v1 <= -2k | |
167 applyConstraint(v1, v1, 2 * k); | |
168 applyConstraint(v1.negated, v1.negated, -2 * k); | |
169 } | |
170 | |
171 /// Make `v1 = v2 + k`. | |
172 void makeExactSum(SignedVariable v1, SignedVariable v2, int k) { | |
173 applyConstraint(v1, v2.negated, k); | |
174 applyConstraint(v1.negated, v2, -k); | |
175 } | |
176 | |
177 /// Make `v1 = v2 [+] k` where [+] represents floating-point addition. | |
178 void makeFloatingPointSum(SignedVariable v1, SignedVariable v2, int k) { | |
179 if (isDefinitelyLessThanOrEqualToConstant(v2, MAX_SAFE_INT - k) && | |
180 isDefinitelyGreaterThanOrEqualToConstant(v2, -MAX_SAFE_INT + k)) { | |
181 // The result is known to be in the 53-bit range, so no rounding occurs. | |
182 makeExactSum(v1, v2, k); | |
183 } else { | |
184 // A rounding error may occur, so the result may not be exactly v2 + k. | |
185 // We can still add monotonicity constraints: | |
186 // adding a positive number cannot return a lesser number | |
187 // adding a negative number cannot return a greater number | |
188 if (k >= 0) { | |
189 // v1 >= v2 <==> v2 - v1 <= 0 <==> -v1 + v2 <= 0 | |
190 applyConstraint(v1.negated, v2, 0); | |
191 } else { | |
192 // v1 <= v2 <==> v1 - v2 <= 0 | |
193 applyConstraint(v1, v2.negated, 0); | |
194 } | |
195 } | |
196 } | |
197 | |
198 void makeEqual(SignedVariable v1, SignedVariable v2) { | |
199 // We model this using the constraints: | |
200 // v1 <= v2 <==> v1 - v2 <= 0 | |
201 // v1 >= v2 <==> v2 - v1 <= 0 | |
202 applyConstraint(v1, v2.negated, 0); | |
203 applyConstraint(v2, v1.negated, 0); | |
204 } | |
205 | |
206 void makeNotEqual(SignedVariable v1, SignedVariable v2) { | |
207 // The octagon cannot represent non-equality, but we can sharpen a weak | |
208 // inequality to a sharp one. If v1 and v2 are already known to be equal, | |
209 // this will create a contradiction and eliminate a dead branch. | |
210 // This is necessary for eliminating concurrent modification checks. | |
211 if (isDefinitelyLessThanOrEqualTo(v1, v2)) { | |
212 makeLessThan(v1, v2); | |
213 } else if (isDefinitelyGreaterThanOrEqualTo(v1, v2)) { | |
214 makeGreaterThan(v1, v2); | |
215 } | |
216 } | |
217 | |
218 /// Return true if we can prove that `v1 <= v2`. | |
219 bool isDefinitelyLessThanOrEqualTo(SignedVariable v1, SignedVariable v2) { | |
220 return testConstraint(v1, v2.negated, 0); | |
221 } | |
222 | |
223 /// Return true if we can prove that `v1 >= v2`. | |
224 bool isDefinitelyGreaterThanOrEqualTo(SignedVariable v1, SignedVariable v2) { | |
225 return testConstraint(v2, v1.negated, 0); | |
226 } | |
227 | |
228 bool isDefinitelyLessThanOrEqualToConstant(SignedVariable v1, int value) { | |
229 // v1 <= value <==> v1 + v1 <= 2 * value | |
230 return testConstraint(v1, v1, 2 * value); | |
231 } | |
232 | |
233 bool isDefinitelyGreaterThanOrEqualToConstant(SignedVariable v1, int value) { | |
234 // v1 >= value <==> -v1 - v1 <= -2 * value | |
235 return testConstraint(v1.negated, v1.negated, -2 * value); | |
236 } | |
237 | |
238 /// ------------- TAIL EXPRESSIONS ----------------- | |
239 | |
240 @override | |
241 void visitBranch(Branch node) { | |
242 Primitive condition = node.condition.definition; | |
243 Continuation trueCont = node.trueContinuation.definition; | |
244 Continuation falseCont = node.falseContinuation.definition; | |
245 effectNumberAt[trueCont] = currentEffectNumber; | |
246 effectNumberAt[falseCont] = currentEffectNumber; | |
247 pushAction(() { | |
248 // If the branching condition is known statically, either or both of the | |
249 // branch continuations will be replaced by Unreachable. Clean up the | |
250 // branch afterwards. | |
251 if (trueCont.body is Unreachable && falseCont.body is Unreachable) { | |
252 replaceExpression(node, new Unreachable()); | |
253 } else if (trueCont.body is Unreachable) { | |
254 replaceExpression( | |
255 node, new InvokeContinuation(falseCont, <Parameter>[])); | |
256 } else if (falseCont.body is Unreachable) { | |
257 replaceExpression( | |
258 node, new InvokeContinuation(trueCont, <Parameter>[])); | |
259 } | |
260 }); | |
261 void pushTrue(makeConstraint()) { | |
262 pushAction(() { | |
263 makeConstraint(); | |
264 push(trueCont); | |
265 }); | |
266 } | |
267 void pushFalse(makeConstraint()) { | |
268 pushAction(() { | |
269 makeConstraint(); | |
270 push(falseCont); | |
271 }); | |
272 } | |
273 if (condition is ApplyBuiltinOperator && | |
274 condition.arguments.length == 2 && | |
275 isInt(condition.arguments[0].definition) && | |
276 isInt(condition.arguments[1].definition)) { | |
277 SignedVariable v1 = getValue(condition.arguments[0].definition); | |
278 SignedVariable v2 = getValue(condition.arguments[1].definition); | |
279 switch (condition.operator) { | |
280 case BuiltinOperator.NumLe: | |
281 pushTrue(() => makeLessThanOrEqual(v1, v2)); | |
282 pushFalse(() => makeGreaterThan(v1, v2)); | |
283 return; | |
284 case BuiltinOperator.NumLt: | |
285 pushTrue(() => makeLessThan(v1, v2)); | |
286 pushFalse(() => makeGreaterThanOrEqual(v1, v2)); | |
287 return; | |
288 case BuiltinOperator.NumGe: | |
289 pushTrue(() => makeGreaterThanOrEqual(v1, v2)); | |
290 pushFalse(() => makeLessThan(v1, v2)); | |
291 return; | |
292 case BuiltinOperator.NumGt: | |
293 pushTrue(() => makeGreaterThan(v1, v2)); | |
294 pushFalse(() => makeLessThanOrEqual(v1, v2)); | |
295 return; | |
296 case BuiltinOperator.StrictEq: | |
297 pushTrue(() => makeEqual(v1, v2)); | |
298 pushFalse(() => makeNotEqual(v1, v2)); | |
299 return; | |
300 case BuiltinOperator.StrictNeq: | |
301 pushTrue(() => makeNotEqual(v1, v2)); | |
302 pushFalse(() => makeEqual(v1, v2)); | |
303 return; | |
304 default: | |
305 } | |
306 } | |
307 | |
308 push(trueCont); | |
309 push(falseCont); | |
310 } | |
311 | |
312 @override | |
313 void visitConstant(Constant node) { | |
314 // TODO(asgerf): It might be faster to inline the constant in the | |
315 // constraints that reference it. | |
316 if (node.value.isInt) { | |
317 IntConstantValue constant = node.value; | |
318 makeConstant(getValue(node), constant.primitiveValue); | |
319 } | |
320 } | |
321 | |
322 @override | |
323 void visitApplyBuiltinOperator(ApplyBuiltinOperator node) { | |
324 if (node.operator != BuiltinOperator.NumAdd && | |
325 node.operator != BuiltinOperator.NumSubtract) { | |
326 return; | |
327 } | |
328 if (!isInt(node.arguments[0].definition) || | |
329 !isInt(node.arguments[1].definition)) { | |
330 return; | |
331 } | |
332 // We have `v1 = v2 +/- v3`, but the octagon cannot represent constraints | |
333 // involving more than two variables. Check if one operand is a constant. | |
334 int getConstantArgument(int n) { | |
335 Primitive prim = node.arguments[n].definition; | |
336 if (prim is Constant && prim.value.isInt) { | |
337 IntConstantValue constant = prim.value; | |
338 return constant.primitiveValue; | |
339 } | |
340 return null; | |
341 } | |
342 int constant = getConstantArgument(0); | |
343 int operandIndex = 1; | |
344 if (constant == null) { | |
345 constant = getConstantArgument(1); | |
346 operandIndex = 0; | |
347 } | |
348 if (constant == null) { | |
349 // Neither argument was a constant. | |
350 // Classical octagon-based analyzers would compute upper and lower bounds | |
351 // for the two operands and add constraints for the result based on | |
352 // those. For performance reasons we omit that. | |
353 // TODO(asgerf): It seems expensive, but we should evaluate it. | |
354 return; | |
355 } | |
356 SignedVariable v1 = getValue(node); | |
357 SignedVariable v2 = getValue(node.arguments[operandIndex].definition); | |
358 | |
359 if (node.operator == BuiltinOperator.NumAdd) { | |
360 // v1 = v2 + const | |
361 makeFloatingPointSum(v1, v2, constant); | |
362 } else if (operandIndex == 0) { | |
363 // v1 = v2 - const | |
364 makeFloatingPointSum(v1, v2, -constant); | |
365 } else { | |
366 // v1 = const - v2 <==> v1 = (-v2) + const | |
367 makeFloatingPointSum(v1, v2.negated, constant); | |
368 } | |
369 } | |
370 | |
371 @override | |
372 void visitGetLength(GetLength node) { | |
373 valueOf[node] = getLength(node.object.definition, currentEffectNumber); | |
374 } | |
375 | |
376 void analyzeLoopEntry(InvokeContinuation node) { | |
377 foundLoop = true; | |
378 Continuation cont = node.continuation.definition; | |
379 if (isStrongLoopPass) { | |
380 for (int i = 0; i < node.arguments.length; ++i) { | |
381 Parameter param = cont.parameters[i]; | |
382 if (!isInt(param)) continue; | |
383 Primitive initialValue = node.arguments[i].definition; | |
384 SignedVariable initialVariable = getValue(initialValue); | |
385 Monotonicity mono = monotonicity[param]; | |
386 if (mono == null) { | |
387 // Value never changes. This is extremely uncommon. | |
388 initialValue.substituteFor(param); | |
389 } else if (mono == Monotonicity.Increasing) { | |
390 makeGreaterThanOrEqual(getValue(param), initialVariable); | |
391 } else if (mono == Monotonicity.Decreasing) { | |
392 makeLessThanOrEqual(getValue(param), initialVariable); | |
393 } | |
394 } | |
395 if (loopsWithSideEffects.contains(cont)) { | |
396 currentEffectNumber = makeNewEffect(); | |
397 } | |
398 } else { | |
399 // During the weak pass, conservatively make a new effect number in the | |
400 // loop body. This may be strengthened during the strong pass. | |
401 currentEffectNumber = effectNumberAt[cont] = makeNewEffect(); | |
402 } | |
403 push(cont); | |
404 } | |
405 | |
406 void analyzeLoopContinue(InvokeContinuation node) { | |
407 Continuation cont = node.continuation.definition; | |
408 | |
409 // During the strong loop phase, there is no need to compute monotonicity, | |
410 // and we already put bounds on the loop variables when we went into the | |
411 // loop. | |
412 if (isStrongLoopPass) return; | |
413 | |
414 // For each loop parameter, try to prove that the new value is definitely | |
415 // less/greater than its old value. When we fail to prove this, update the | |
416 // monotonicity flag accordingly. | |
417 for (int i = 0; i < node.arguments.length; ++i) { | |
418 Parameter param = cont.parameters[i]; | |
419 if (!isInt(param)) continue; | |
420 SignedVariable arg = getValue(node.arguments[i].definition); | |
421 SignedVariable paramVar = getValue(param); | |
422 if (!isDefinitelyLessThanOrEqualTo(arg, paramVar)) { | |
423 // We couldn't prove that the value does not increase, so assume | |
424 // henceforth that it might be increasing. | |
425 markMonotonicity(cont.parameters[i], Monotonicity.Increasing); | |
426 } | |
427 if (!isDefinitelyGreaterThanOrEqualTo(arg, paramVar)) { | |
428 // We couldn't prove that the value does not decrease, so assume | |
429 // henceforth that it might be decrease. | |
430 markMonotonicity(cont.parameters[i], Monotonicity.Decreasing); | |
431 } | |
432 } | |
433 | |
434 // If a side effect has occurred between the entry and continue, mark | |
435 // the loop as having side effects. | |
436 if (currentEffectNumber != effectNumberAt[cont]) { | |
437 loopsWithSideEffects.add(cont); | |
438 } | |
439 } | |
440 | |
441 void markMonotonicity(Parameter param, Monotonicity mono) { | |
442 Monotonicity current = monotonicity[param]; | |
443 if (current == null) { | |
444 monotonicity[param] = mono; | |
445 } else if (current != mono) { | |
446 monotonicity[param] = Monotonicity.NotMonotone; | |
447 } | |
448 } | |
449 | |
450 @override | |
451 void visitInvokeContinuation(InvokeContinuation node) { | |
452 Continuation cont = node.continuation.definition; | |
453 if (node.isRecursive) { | |
454 analyzeLoopContinue(node); | |
455 } else if (cont.isRecursive) { | |
456 analyzeLoopEntry(node); | |
457 } else { | |
458 int effect = effectNumberAt[cont]; | |
459 if (effect == null) { | |
460 effectNumberAt[cont] = currentEffectNumber; | |
461 } else if (effect != currentEffectNumber && effect != NEW_EFFECT) { | |
462 effectNumberAt[cont] = NEW_EFFECT; | |
463 } | |
464 // TODO(asgerf): Compute join for parameters to increase precision? | |
465 } | |
466 } | |
467 | |
468 /// ---------------- CALL EXPRESSIONS -------------------- | |
469 | |
470 @override | |
471 void visitInvokeMethod(InvokeMethod node) { | |
472 // TODO(asgerf): What we really need is a "changes length" side effect flag. | |
sra1
2015/09/30 21:54:20
More than that - with aliases so a.add(b[i]) does
asgerf
2015/10/01 09:49:33
Absolutely. I just wanted to clarify why we are us
| |
473 if (world | |
474 .getSideEffectsOfSelector(node.selector, node.mask) | |
475 .changesIndex()) { | |
476 currentEffectNumber = makeNewEffect(); | |
477 } | |
478 push(node.continuation.definition); | |
479 } | |
480 | |
481 @override | |
482 void visitInvokeStatic(InvokeStatic node) { | |
483 if (world.getSideEffectsOfElement(node.target).changesIndex()) { | |
484 currentEffectNumber = makeNewEffect(); | |
485 } | |
486 push(node.continuation.definition); | |
487 } | |
488 | |
489 @override | |
490 void visitInvokeMethodDirectly(InvokeMethodDirectly node) { | |
491 FunctionElement target = node.target; | |
492 if (target is ConstructorBodyElement) { | |
493 ConstructorBodyElement body = target; | |
494 target = body.constructor; | |
495 } | |
496 if (world.getSideEffectsOfElement(target).changesIndex()) { | |
497 currentEffectNumber = makeNewEffect(); | |
498 } | |
499 push(node.continuation.definition); | |
500 } | |
501 | |
502 @override | |
503 void visitInvokeConstructor(InvokeConstructor node) { | |
504 if (world.getSideEffectsOfElement(node.target).changesIndex()) { | |
505 currentEffectNumber = makeNewEffect(); | |
506 } | |
507 push(node.continuation.definition); | |
508 } | |
509 | |
510 @override | |
511 void visitTypeCast(TypeCast node) { | |
512 push(node.continuation.definition); | |
513 } | |
514 | |
515 @override | |
516 void visitGetLazyStatic(GetLazyStatic node) { | |
517 // TODO(asgerf): How do we get the side effects of a lazy field initializer? | |
518 currentEffectNumber = makeNewEffect(); | |
519 push(node.continuation.definition); | |
520 } | |
521 | |
522 @override | |
523 void visitForeignCode(ForeignCode node) { | |
524 if (node.nativeBehavior.sideEffects.changesIndex()) { | |
525 currentEffectNumber = makeNewEffect(); | |
526 } | |
527 push(node.continuation.definition); | |
528 } | |
529 | |
530 @override | |
531 void visitAwait(Await node) { | |
532 currentEffectNumber = makeNewEffect(); | |
533 push(node.continuation.definition); | |
534 } | |
535 | |
536 /// ---------------- PRIMITIVES -------------------- | |
537 | |
538 @override | |
539 void visitApplyBuiltinMethod(ApplyBuiltinMethod node) { | |
540 Primitive receiver = node.receiver.definition; | |
541 int effectBefore = currentEffectNumber; | |
542 currentEffectNumber = makeNewEffect(); | |
543 int effectAfter = currentEffectNumber; | |
544 SignedVariable lengthBefore = getLength(receiver, effectBefore); | |
545 SignedVariable lengthAfter = getLength(receiver, effectAfter); | |
546 switch (node.method) { | |
547 case BuiltinMethod.Push: | |
548 // after = before + count | |
549 int count = node.arguments.length; | |
550 makeExactSum(lengthAfter, lengthBefore, count); | |
551 break; | |
552 | |
553 case BuiltinMethod.Pop: | |
554 // after = before - 1 | |
555 makeExactSum(lengthAfter, lengthBefore, -1); | |
556 break; | |
557 } | |
558 } | |
559 | |
560 @override | |
561 void visitLiteralList(LiteralList node) { | |
562 makeConstant(getLength(node, currentEffectNumber), node.values.length); | |
563 } | |
564 | |
565 /// ---------------- INTERIOR EXPRESSIONS -------------------- | |
566 | |
567 @override | |
568 Expression traverseContinuation(Continuation cont) { | |
569 if (octagon.isUnsolvable) { | |
570 RemovalVisitor.remove(cont.body); | |
571 cont.body = new Unreachable(); | |
572 } else { | |
573 int effect = effectNumberAt[cont]; | |
574 if (effect != null) { | |
575 currentEffectNumber = effect == NEW_EFFECT ? makeNewEffect() : effect; | |
576 } | |
577 } | |
578 return cont.body; | |
579 } | |
580 | |
581 @override | |
582 Expression traverseLetCont(LetCont node) { | |
583 // Join continuations should be pushed at declaration-site, so all their | |
584 // call sites are seen before they are analyzed. | |
585 // Other continuations are pushed at the use site. | |
586 for (Continuation cont in node.continuations) { | |
587 if (cont.hasAtLeastOneUse && | |
588 !cont.isRecursive && | |
589 cont.firstRef.parent is InvokeContinuation) { | |
590 push(cont); | |
591 } | |
592 } | |
593 return node.body; | |
594 } | |
595 } | |
596 | |
597 /// Lattice representing the known monotonicity of a loop variable. | |
598 /// | |
599 /// The lattice bottom is represented by `null` and represents the case where | |
600 /// the loop variable never changes value during the loop. | |
sra1
2015/09/30 21:54:19
These are weak monotonicity, not strict, right?
asgerf
2015/10/01 09:49:33
Yes. Clarified in the comment.
| |
601 enum Monotonicity { NotMonotone, Increasing, Decreasing, } | |
OLD | NEW |