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 TrampolineRecursiveVisitor 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 bool isNonNegativeInt(Primitive prim) { |
| 97 return types.isDefinitelyNonNegativeInt(prim.type); |
| 98 } |
| 99 |
| 100 /// Get a constraint variable representing the numeric value of [number]. |
| 101 SignedVariable getValue(Primitive number) { |
| 102 number = number.effectiveDefinition; |
| 103 int min, max; |
| 104 if (isUInt32(number)) { |
| 105 min = 0; |
| 106 max = MAX_UINT32; |
| 107 } else if (isNonNegativeInt(number)) { |
| 108 min = 0; |
| 109 } |
| 110 return valueOf.putIfAbsent(number, () => octagon.makeVariable(min, max)); |
| 111 } |
| 112 |
| 113 /// Get a constraint variable representing the length of [indexableObject] at |
| 114 /// program locations with the given [effectCounter]. |
| 115 SignedVariable getLength(Primitive indexableObject, int effectCounter) { |
| 116 indexableObject = indexableObject.effectiveDefinition; |
| 117 if (indexableObject.type != null && |
| 118 types.isDefinitelyFixedLengthIndexable(indexableObject.type)) { |
| 119 // Always use the same effect counter if the length is immutable. |
| 120 effectCounter = 0; |
| 121 } |
| 122 return lengthOf |
| 123 .putIfAbsent(indexableObject, () => <int, SignedVariable>{}) |
| 124 .putIfAbsent(effectCounter, () => octagon.makeVariable(0, MAX_UINT32)); |
| 125 } |
| 126 |
| 127 // ------------- CONSTRAINT HELPERS ----------------- |
| 128 |
| 129 /// Puts the given constraint "in scope" by adding it to the octagon, and |
| 130 /// pushing a stack action that will remove it again. |
| 131 void applyConstraint(SignedVariable v1, SignedVariable v2, int k) { |
| 132 Constraint constraint = new Constraint(v1, v2, k); |
| 133 octagon.pushConstraint(constraint); |
| 134 pushAction(() => octagon.popConstraint(constraint)); |
| 135 } |
| 136 |
| 137 /// Return true if we can prove that `v1 + v2 <= k`. |
| 138 bool testConstraint(SignedVariable v1, SignedVariable v2, int k) { |
| 139 // Add the negated constraint and check for solvability. |
| 140 // !(v1 + v2 <= k) <==> -v1 - v2 <= -k-1 |
| 141 Constraint constraint = new Constraint(v1.negated, v2.negated, -k - 1); |
| 142 octagon.pushConstraint(constraint); |
| 143 bool answer = octagon.isUnsolvable; |
| 144 octagon.popConstraint(constraint); |
| 145 return answer; |
| 146 } |
| 147 |
| 148 void makeLessThanOrEqual(SignedVariable v1, SignedVariable v2) { |
| 149 // v1 <= v2 <==> v1 - v2 <= 0 |
| 150 applyConstraint(v1, v2.negated, 0); |
| 151 } |
| 152 |
| 153 void makeLessThan(SignedVariable v1, SignedVariable v2) { |
| 154 // v1 < v2 <==> v1 - v2 <= -1 |
| 155 applyConstraint(v1, v2.negated, -1); |
| 156 } |
| 157 |
| 158 void makeGreaterThanOrEqual(SignedVariable v1, SignedVariable v2) { |
| 159 // v1 >= v2 <==> v2 - v1 <= 0 |
| 160 applyConstraint(v2, v1.negated, 0); |
| 161 } |
| 162 |
| 163 void makeGreaterThan(SignedVariable v1, SignedVariable v2) { |
| 164 // v1 > v2 <==> v2 - v1 <= -1 |
| 165 applyConstraint(v2, v1.negated, -1); |
| 166 } |
| 167 |
| 168 void makeConstant(SignedVariable v1, int k) { |
| 169 // We model this using the constraints: |
| 170 // v1 + v1 <= 2k |
| 171 // -v1 - v1 <= -2k |
| 172 applyConstraint(v1, v1, 2 * k); |
| 173 applyConstraint(v1.negated, v1.negated, -2 * k); |
| 174 } |
| 175 |
| 176 /// Make `v1 = v2 + k`. |
| 177 void makeExactSum(SignedVariable v1, SignedVariable v2, int k) { |
| 178 applyConstraint(v1, v2.negated, k); |
| 179 applyConstraint(v1.negated, v2, -k); |
| 180 } |
| 181 |
| 182 /// Make `v1 = v2 [+] k` where [+] represents floating-point addition. |
| 183 void makeFloatingPointSum(SignedVariable v1, SignedVariable v2, int k) { |
| 184 if (isDefinitelyLessThanOrEqualToConstant(v2, MAX_SAFE_INT - k) && |
| 185 isDefinitelyGreaterThanOrEqualToConstant(v2, -MAX_SAFE_INT + k)) { |
| 186 // The result is known to be in the 53-bit range, so no rounding occurs. |
| 187 makeExactSum(v1, v2, k); |
| 188 } else { |
| 189 // A rounding error may occur, so the result may not be exactly v2 + k. |
| 190 // We can still add monotonicity constraints: |
| 191 // adding a positive number cannot return a lesser number |
| 192 // adding a negative number cannot return a greater number |
| 193 if (k >= 0) { |
| 194 // v1 >= v2 <==> v2 - v1 <= 0 <==> -v1 + v2 <= 0 |
| 195 applyConstraint(v1.negated, v2, 0); |
| 196 } else { |
| 197 // v1 <= v2 <==> v1 - v2 <= 0 |
| 198 applyConstraint(v1, v2.negated, 0); |
| 199 } |
| 200 } |
| 201 } |
| 202 |
| 203 void makeEqual(SignedVariable v1, SignedVariable v2) { |
| 204 // We model this using the constraints: |
| 205 // v1 <= v2 <==> v1 - v2 <= 0 |
| 206 // v1 >= v2 <==> v2 - v1 <= 0 |
| 207 applyConstraint(v1, v2.negated, 0); |
| 208 applyConstraint(v2, v1.negated, 0); |
| 209 } |
| 210 |
| 211 void makeNotEqual(SignedVariable v1, SignedVariable v2) { |
| 212 // The octagon cannot represent non-equality, but we can sharpen a weak |
| 213 // inequality to a sharp one. If v1 and v2 are already known to be equal, |
| 214 // this will create a contradiction and eliminate a dead branch. |
| 215 // This is necessary for eliminating concurrent modification checks. |
| 216 if (isDefinitelyLessThanOrEqualTo(v1, v2)) { |
| 217 makeLessThan(v1, v2); |
| 218 } else if (isDefinitelyGreaterThanOrEqualTo(v1, v2)) { |
| 219 makeGreaterThan(v1, v2); |
| 220 } |
| 221 } |
| 222 |
| 223 /// Return true if we can prove that `v1 <= v2`. |
| 224 bool isDefinitelyLessThanOrEqualTo(SignedVariable v1, SignedVariable v2) { |
| 225 return testConstraint(v1, v2.negated, 0); |
| 226 } |
| 227 |
| 228 /// Return true if we can prove that `v1 >= v2`. |
| 229 bool isDefinitelyGreaterThanOrEqualTo(SignedVariable v1, SignedVariable v2) { |
| 230 return testConstraint(v2, v1.negated, 0); |
| 231 } |
| 232 |
| 233 bool isDefinitelyLessThanOrEqualToConstant(SignedVariable v1, int value) { |
| 234 // v1 <= value <==> v1 + v1 <= 2 * value |
| 235 return testConstraint(v1, v1, 2 * value); |
| 236 } |
| 237 |
| 238 bool isDefinitelyGreaterThanOrEqualToConstant(SignedVariable v1, int value) { |
| 239 // v1 >= value <==> -v1 - v1 <= -2 * value |
| 240 return testConstraint(v1.negated, v1.negated, -2 * value); |
| 241 } |
| 242 |
| 243 // ------------- TAIL EXPRESSIONS ----------------- |
| 244 |
| 245 @override |
| 246 void visitBranch(Branch node) { |
| 247 Primitive condition = node.condition.definition; |
| 248 Continuation trueCont = node.trueContinuation.definition; |
| 249 Continuation falseCont = node.falseContinuation.definition; |
| 250 effectNumberAt[trueCont] = currentEffectNumber; |
| 251 effectNumberAt[falseCont] = currentEffectNumber; |
| 252 pushAction(() { |
| 253 // If the branching condition is known statically, either or both of the |
| 254 // branch continuations will be replaced by Unreachable. Clean up the |
| 255 // branch afterwards. |
| 256 if (trueCont.body is Unreachable && falseCont.body is Unreachable) { |
| 257 destroyAndReplace(node, new Unreachable()); |
| 258 } else if (trueCont.body is Unreachable) { |
| 259 destroyAndReplace( |
| 260 node, new InvokeContinuation(falseCont, <Parameter>[])); |
| 261 } else if (falseCont.body is Unreachable) { |
| 262 destroyAndReplace( |
| 263 node, new InvokeContinuation(trueCont, <Parameter>[])); |
| 264 } |
| 265 }); |
| 266 void pushTrue(makeConstraint()) { |
| 267 pushAction(() { |
| 268 makeConstraint(); |
| 269 push(trueCont); |
| 270 }); |
| 271 } |
| 272 void pushFalse(makeConstraint()) { |
| 273 pushAction(() { |
| 274 makeConstraint(); |
| 275 push(falseCont); |
| 276 }); |
| 277 } |
| 278 if (condition is ApplyBuiltinOperator && |
| 279 condition.arguments.length == 2 && |
| 280 isInt(condition.arguments[0].definition) && |
| 281 isInt(condition.arguments[1].definition)) { |
| 282 SignedVariable v1 = getValue(condition.arguments[0].definition); |
| 283 SignedVariable v2 = getValue(condition.arguments[1].definition); |
| 284 switch (condition.operator) { |
| 285 case BuiltinOperator.NumLe: |
| 286 pushTrue(() => makeLessThanOrEqual(v1, v2)); |
| 287 pushFalse(() => makeGreaterThan(v1, v2)); |
| 288 return; |
| 289 case BuiltinOperator.NumLt: |
| 290 pushTrue(() => makeLessThan(v1, v2)); |
| 291 pushFalse(() => makeGreaterThanOrEqual(v1, v2)); |
| 292 return; |
| 293 case BuiltinOperator.NumGe: |
| 294 pushTrue(() => makeGreaterThanOrEqual(v1, v2)); |
| 295 pushFalse(() => makeLessThan(v1, v2)); |
| 296 return; |
| 297 case BuiltinOperator.NumGt: |
| 298 pushTrue(() => makeGreaterThan(v1, v2)); |
| 299 pushFalse(() => makeLessThanOrEqual(v1, v2)); |
| 300 return; |
| 301 case BuiltinOperator.StrictEq: |
| 302 pushTrue(() => makeEqual(v1, v2)); |
| 303 pushFalse(() => makeNotEqual(v1, v2)); |
| 304 return; |
| 305 case BuiltinOperator.StrictNeq: |
| 306 pushTrue(() => makeNotEqual(v1, v2)); |
| 307 pushFalse(() => makeEqual(v1, v2)); |
| 308 return; |
| 309 default: |
| 310 } |
| 311 } |
| 312 |
| 313 push(trueCont); |
| 314 push(falseCont); |
| 315 } |
| 316 |
| 317 @override |
| 318 void visitConstant(Constant node) { |
| 319 // TODO(asgerf): It might be faster to inline the constant in the |
| 320 // constraints that reference it. |
| 321 if (node.value.isInt) { |
| 322 IntConstantValue constant = node.value; |
| 323 makeConstant(getValue(node), constant.primitiveValue); |
| 324 } |
| 325 } |
| 326 |
| 327 @override |
| 328 void visitApplyBuiltinOperator(ApplyBuiltinOperator node) { |
| 329 if (node.operator != BuiltinOperator.NumAdd && |
| 330 node.operator != BuiltinOperator.NumSubtract) { |
| 331 return; |
| 332 } |
| 333 if (!isInt(node.arguments[0].definition) || |
| 334 !isInt(node.arguments[1].definition)) { |
| 335 return; |
| 336 } |
| 337 if (!isInt(node)) { |
| 338 // TODO(asgerf): The result of this operation should always be an integer, |
| 339 // but currently type propagation does not always prove this. |
| 340 return; |
| 341 } |
| 342 // We have `v1 = v2 +/- v3`, but the octagon cannot represent constraints |
| 343 // involving more than two variables. Check if one operand is a constant. |
| 344 int getConstantArgument(int n) { |
| 345 Primitive prim = node.arguments[n].definition; |
| 346 if (prim is Constant && prim.value.isInt) { |
| 347 IntConstantValue constant = prim.value; |
| 348 return constant.primitiveValue; |
| 349 } |
| 350 return null; |
| 351 } |
| 352 int constant = getConstantArgument(0); |
| 353 int operandIndex = 1; |
| 354 if (constant == null) { |
| 355 constant = getConstantArgument(1); |
| 356 operandIndex = 0; |
| 357 } |
| 358 if (constant == null) { |
| 359 // Neither argument was a constant. |
| 360 // Classical octagon-based analyzers would compute upper and lower bounds |
| 361 // for the two operands and add constraints for the result based on |
| 362 // those. For performance reasons we omit that. |
| 363 // TODO(asgerf): It seems expensive, but we should evaluate it. |
| 364 return; |
| 365 } |
| 366 SignedVariable v1 = getValue(node); |
| 367 SignedVariable v2 = getValue(node.arguments[operandIndex].definition); |
| 368 |
| 369 if (node.operator == BuiltinOperator.NumAdd) { |
| 370 // v1 = v2 + const |
| 371 makeFloatingPointSum(v1, v2, constant); |
| 372 } else if (operandIndex == 0) { |
| 373 // v1 = v2 - const |
| 374 makeFloatingPointSum(v1, v2, -constant); |
| 375 } else { |
| 376 // v1 = const - v2 <==> v1 = (-v2) + const |
| 377 makeFloatingPointSum(v1, v2.negated, constant); |
| 378 } |
| 379 } |
| 380 |
| 381 @override |
| 382 void visitGetLength(GetLength node) { |
| 383 valueOf[node] = getLength(node.object.definition, currentEffectNumber); |
| 384 } |
| 385 |
| 386 void analyzeLoopEntry(InvokeContinuation node) { |
| 387 foundLoop = true; |
| 388 Continuation cont = node.continuation.definition; |
| 389 if (isStrongLoopPass) { |
| 390 for (int i = 0; i < node.arguments.length; ++i) { |
| 391 Parameter param = cont.parameters[i]; |
| 392 if (!isInt(param)) continue; |
| 393 Primitive initialValue = node.arguments[i].definition; |
| 394 SignedVariable initialVariable = getValue(initialValue); |
| 395 Monotonicity mono = monotonicity[param]; |
| 396 if (mono == null) { |
| 397 // Value never changes. This is extremely uncommon. |
| 398 initialValue.substituteFor(param); |
| 399 } else if (mono == Monotonicity.Increasing) { |
| 400 makeGreaterThanOrEqual(getValue(param), initialVariable); |
| 401 } else if (mono == Monotonicity.Decreasing) { |
| 402 makeLessThanOrEqual(getValue(param), initialVariable); |
| 403 } |
| 404 } |
| 405 if (loopsWithSideEffects.contains(cont)) { |
| 406 currentEffectNumber = makeNewEffect(); |
| 407 } |
| 408 } else { |
| 409 // During the weak pass, conservatively make a new effect number in the |
| 410 // loop body. This may be strengthened during the strong pass. |
| 411 currentEffectNumber = effectNumberAt[cont] = makeNewEffect(); |
| 412 } |
| 413 push(cont); |
| 414 } |
| 415 |
| 416 void analyzeLoopContinue(InvokeContinuation node) { |
| 417 Continuation cont = node.continuation.definition; |
| 418 |
| 419 // During the strong loop phase, there is no need to compute monotonicity, |
| 420 // and we already put bounds on the loop variables when we went into the |
| 421 // loop. |
| 422 if (isStrongLoopPass) return; |
| 423 |
| 424 // For each loop parameter, try to prove that the new value is definitely |
| 425 // less/greater than its old value. When we fail to prove this, update the |
| 426 // monotonicity flag accordingly. |
| 427 for (int i = 0; i < node.arguments.length; ++i) { |
| 428 Parameter param = cont.parameters[i]; |
| 429 if (!isInt(param)) continue; |
| 430 SignedVariable arg = getValue(node.arguments[i].definition); |
| 431 SignedVariable paramVar = getValue(param); |
| 432 if (!isDefinitelyLessThanOrEqualTo(arg, paramVar)) { |
| 433 // We couldn't prove that the value does not increase, so assume |
| 434 // henceforth that it might be increasing. |
| 435 markMonotonicity(cont.parameters[i], Monotonicity.Increasing); |
| 436 } |
| 437 if (!isDefinitelyGreaterThanOrEqualTo(arg, paramVar)) { |
| 438 // We couldn't prove that the value does not decrease, so assume |
| 439 // henceforth that it might be decreasing. |
| 440 markMonotonicity(cont.parameters[i], Monotonicity.Decreasing); |
| 441 } |
| 442 } |
| 443 |
| 444 // If a side effect has occurred between the entry and continue, mark |
| 445 // the loop as having side effects. |
| 446 if (currentEffectNumber != effectNumberAt[cont]) { |
| 447 loopsWithSideEffects.add(cont); |
| 448 } |
| 449 } |
| 450 |
| 451 void markMonotonicity(Parameter param, Monotonicity mono) { |
| 452 Monotonicity current = monotonicity[param]; |
| 453 if (current == null) { |
| 454 monotonicity[param] = mono; |
| 455 } else if (current != mono) { |
| 456 monotonicity[param] = Monotonicity.NotMonotone; |
| 457 } |
| 458 } |
| 459 |
| 460 @override |
| 461 void visitInvokeContinuation(InvokeContinuation node) { |
| 462 Continuation cont = node.continuation.definition; |
| 463 if (node.isRecursive) { |
| 464 analyzeLoopContinue(node); |
| 465 } else if (cont.isRecursive) { |
| 466 analyzeLoopEntry(node); |
| 467 } else { |
| 468 int effect = effectNumberAt[cont]; |
| 469 if (effect == null) { |
| 470 effectNumberAt[cont] = currentEffectNumber; |
| 471 } else if (effect != currentEffectNumber && effect != NEW_EFFECT) { |
| 472 effectNumberAt[cont] = NEW_EFFECT; |
| 473 } |
| 474 // TODO(asgerf): Compute join for parameters to increase precision? |
| 475 } |
| 476 } |
| 477 |
| 478 // ---------------- CALL EXPRESSIONS -------------------- |
| 479 |
| 480 @override |
| 481 void visitInvokeMethod(InvokeMethod node) { |
| 482 // TODO(asgerf): What we really need is a "changes length" side effect flag. |
| 483 if (world |
| 484 .getSideEffectsOfSelector(node.selector, node.mask) |
| 485 .changesIndex()) { |
| 486 currentEffectNumber = makeNewEffect(); |
| 487 } |
| 488 push(node.continuation.definition); |
| 489 } |
| 490 |
| 491 @override |
| 492 void visitInvokeStatic(InvokeStatic node) { |
| 493 if (world.getSideEffectsOfElement(node.target).changesIndex()) { |
| 494 currentEffectNumber = makeNewEffect(); |
| 495 } |
| 496 push(node.continuation.definition); |
| 497 } |
| 498 |
| 499 @override |
| 500 void visitInvokeMethodDirectly(InvokeMethodDirectly node) { |
| 501 FunctionElement target = node.target; |
| 502 if (target is ConstructorBodyElement) { |
| 503 ConstructorBodyElement body = target; |
| 504 target = body.constructor; |
| 505 } |
| 506 if (world.getSideEffectsOfElement(target).changesIndex()) { |
| 507 currentEffectNumber = makeNewEffect(); |
| 508 } |
| 509 push(node.continuation.definition); |
| 510 } |
| 511 |
| 512 @override |
| 513 void visitInvokeConstructor(InvokeConstructor node) { |
| 514 if (world.getSideEffectsOfElement(node.target).changesIndex()) { |
| 515 currentEffectNumber = makeNewEffect(); |
| 516 } |
| 517 push(node.continuation.definition); |
| 518 } |
| 519 |
| 520 @override |
| 521 void visitTypeCast(TypeCast node) { |
| 522 push(node.continuation.definition); |
| 523 } |
| 524 |
| 525 @override |
| 526 void visitGetLazyStatic(GetLazyStatic node) { |
| 527 // TODO(asgerf): How do we get the side effects of a lazy field initializer? |
| 528 currentEffectNumber = makeNewEffect(); |
| 529 push(node.continuation.definition); |
| 530 } |
| 531 |
| 532 @override |
| 533 void visitForeignCode(ForeignCode node) { |
| 534 if (node.nativeBehavior.sideEffects.changesIndex()) { |
| 535 currentEffectNumber = makeNewEffect(); |
| 536 } |
| 537 push(node.continuation.definition); |
| 538 } |
| 539 |
| 540 @override |
| 541 void visitAwait(Await node) { |
| 542 currentEffectNumber = makeNewEffect(); |
| 543 push(node.continuation.definition); |
| 544 } |
| 545 |
| 546 @override |
| 547 void visitYield(Yield node) { |
| 548 currentEffectNumber = makeNewEffect(); |
| 549 push(node.continuation.definition); |
| 550 } |
| 551 |
| 552 // ---------------- PRIMITIVES -------------------- |
| 553 |
| 554 @override |
| 555 void visitApplyBuiltinMethod(ApplyBuiltinMethod node) { |
| 556 Primitive receiver = node.receiver.definition; |
| 557 int effectBefore = currentEffectNumber; |
| 558 currentEffectNumber = makeNewEffect(); |
| 559 int effectAfter = currentEffectNumber; |
| 560 SignedVariable lengthBefore = getLength(receiver, effectBefore); |
| 561 SignedVariable lengthAfter = getLength(receiver, effectAfter); |
| 562 switch (node.method) { |
| 563 case BuiltinMethod.Push: |
| 564 // after = before + count |
| 565 int count = node.arguments.length; |
| 566 makeExactSum(lengthAfter, lengthBefore, count); |
| 567 break; |
| 568 |
| 569 case BuiltinMethod.Pop: |
| 570 // after = before - 1 |
| 571 makeExactSum(lengthAfter, lengthBefore, -1); |
| 572 break; |
| 573 } |
| 574 } |
| 575 |
| 576 @override |
| 577 void visitLiteralList(LiteralList node) { |
| 578 makeConstant(getLength(node, currentEffectNumber), node.values.length); |
| 579 } |
| 580 |
| 581 // ---------------- INTERIOR EXPRESSIONS -------------------- |
| 582 |
| 583 @override |
| 584 Expression traverseContinuation(Continuation cont) { |
| 585 if (octagon.isUnsolvable) { |
| 586 destroyAndReplace(cont.body, new Unreachable()); |
| 587 } else { |
| 588 int effect = effectNumberAt[cont]; |
| 589 if (effect != null) { |
| 590 currentEffectNumber = effect == NEW_EFFECT ? makeNewEffect() : effect; |
| 591 } |
| 592 } |
| 593 return cont.body; |
| 594 } |
| 595 |
| 596 @override |
| 597 Expression traverseLetCont(LetCont node) { |
| 598 // Join continuations should be pushed at declaration-site, so all their |
| 599 // call sites are seen before they are analyzed. |
| 600 // Other continuations are pushed at the use site. |
| 601 for (Continuation cont in node.continuations) { |
| 602 if (cont.hasAtLeastOneUse && |
| 603 !cont.isRecursive && |
| 604 cont.firstRef.parent is InvokeContinuation) { |
| 605 push(cont); |
| 606 } |
| 607 } |
| 608 return node.body; |
| 609 } |
| 610 } |
| 611 |
| 612 /// Lattice representing the known (weak) monotonicity of a loop variable. |
| 613 /// |
| 614 /// The lattice bottom is represented by `null` and represents the case where |
| 615 /// the loop variable never changes value during the loop. |
| 616 enum Monotonicity { NotMonotone, Increasing, Decreasing, } |
OLD | NEW |