| Index: pkg/compiler/lib/src/cps_ir/bounds_checker.dart
|
| diff --git a/pkg/compiler/lib/src/cps_ir/bounds_checker.dart b/pkg/compiler/lib/src/cps_ir/bounds_checker.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..14677456e216d84d8ebf5e451c481f534b8f8197
|
| --- /dev/null
|
| +++ b/pkg/compiler/lib/src/cps_ir/bounds_checker.dart
|
| @@ -0,0 +1,616 @@
|
| +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
|
| +// for details. All rights reserved. Use of this source code is governed by a
|
| +// BSD-style license that can be found in the LICENSE file.
|
| +
|
| +library dart2js.cps_ir.bounds_checker;
|
| +
|
| +import 'cps_ir_nodes.dart';
|
| +import 'optimizers.dart' show Pass;
|
| +import 'octagon.dart';
|
| +import '../constants/values.dart';
|
| +import 'cps_fragment.dart';
|
| +import 'type_mask_system.dart';
|
| +import '../world.dart';
|
| +import '../elements/elements.dart';
|
| +
|
| +/// Eliminates bounds checks when they can be proven safe.
|
| +///
|
| +/// In general, this pass will try to eliminate any branch with arithmetic
|
| +/// in the condition, i.e. `x < y`, `x <= y`, `x == y` etc.
|
| +///
|
| +/// The analysis uses an [Octagon] abstract domain. Unlike traditional octagon
|
| +/// analyzers, we do not use a closed matrix representation, but just maintain
|
| +/// a bucket of constraints. Constraints can therefore be added and removed
|
| +/// on-the-fly without significant overhead.
|
| +///
|
| +/// We never copy the constraint system. While traversing the IR, the
|
| +/// constraint system is mutated to take into account the knowledge that is
|
| +/// valid for the current location. Constraints are added when entering a
|
| +/// branch, for instance, and removed again after the branch has been processed.
|
| +///
|
| +/// Loops are analyzed in two passes. The first pass establishes monotonicity
|
| +/// of loop variables, which the second pass uses to compute upper/lower bounds.
|
| +/// The first pass also records whether any side effects occurred in the loop.
|
| +///
|
| +/// The two-pass scheme is suboptimal compared to a least fixed-point
|
| +/// computation, but does not require repeated iteration. Repeated iteration
|
| +/// would be expensive, since we cannot perform a sparse analysis with our
|
| +/// mutable octagon representation.
|
| +class BoundsChecker extends TrampolineRecursiveVisitor implements Pass {
|
| + String get passName => 'Bounds checker';
|
| +
|
| + static const int MAX_UINT32 = (1 << 32) - 1;
|
| +
|
| + /// All integers of this magnitude or less are representable as JS numbers.
|
| + static const int MAX_SAFE_INT = (1 << 53) - 1;
|
| +
|
| + /// Marker to indicate that a continuation should get a unique effect number.
|
| + static const int NEW_EFFECT = -1;
|
| +
|
| + final TypeMaskSystem types;
|
| + final World world;
|
| +
|
| + /// Fields for the constraint system and its variables.
|
| + final Octagon octagon = new Octagon();
|
| + final Map<Primitive, SignedVariable> valueOf = {};
|
| + final Map<Primitive, Map<int, SignedVariable>> lengthOf = {};
|
| +
|
| + /// Fields for the two-pass handling of loops.
|
| + final Set<Continuation> loopsWithSideEffects = new Set<Continuation>();
|
| + final Map<Parameter, Monotonicity> monotonicity = <Parameter, Monotonicity>{};
|
| + bool isStrongLoopPass;
|
| + bool foundLoop = false;
|
| +
|
| + /// Fields for tracking side effects.
|
| + ///
|
| + /// The IR is divided into regions wherein the lengths of indexable objects
|
| + /// are known not to change. Regions are identified by their "effect number".
|
| + final Map<Continuation, int> effectNumberAt = <Continuation, int>{};
|
| + int currentEffectNumber = 0;
|
| + int effectNumberCounter = 0;
|
| +
|
| + BoundsChecker(this.types, this.world);
|
| +
|
| + void rewrite(FunctionDefinition node) {
|
| + isStrongLoopPass = false;
|
| + visit(node);
|
| + if (foundLoop) {
|
| + isStrongLoopPass = true;
|
| + effectNumberAt.clear();
|
| + visit(node);
|
| + }
|
| + }
|
| +
|
| + // ------------- VARIABLES -----------------
|
| +
|
| + int makeNewEffect() => ++effectNumberCounter;
|
| +
|
| + bool isInt(Primitive prim) {
|
| + return types.isDefinitelyInt(prim.type);
|
| + }
|
| +
|
| + bool isUInt32(Primitive prim) {
|
| + return types.isDefinitelyUint32(prim.type);
|
| + }
|
| +
|
| + bool isNonNegativeInt(Primitive prim) {
|
| + return types.isDefinitelyNonNegativeInt(prim.type);
|
| + }
|
| +
|
| + /// Get a constraint variable representing the numeric value of [number].
|
| + SignedVariable getValue(Primitive number) {
|
| + number = number.effectiveDefinition;
|
| + int min, max;
|
| + if (isUInt32(number)) {
|
| + min = 0;
|
| + max = MAX_UINT32;
|
| + } else if (isNonNegativeInt(number)) {
|
| + min = 0;
|
| + }
|
| + return valueOf.putIfAbsent(number, () => octagon.makeVariable(min, max));
|
| + }
|
| +
|
| + /// Get a constraint variable representing the length of [indexableObject] at
|
| + /// program locations with the given [effectCounter].
|
| + SignedVariable getLength(Primitive indexableObject, int effectCounter) {
|
| + indexableObject = indexableObject.effectiveDefinition;
|
| + if (indexableObject.type != null &&
|
| + types.isDefinitelyFixedLengthIndexable(indexableObject.type)) {
|
| + // Always use the same effect counter if the length is immutable.
|
| + effectCounter = 0;
|
| + }
|
| + return lengthOf
|
| + .putIfAbsent(indexableObject, () => <int, SignedVariable>{})
|
| + .putIfAbsent(effectCounter, () => octagon.makeVariable(0, MAX_UINT32));
|
| + }
|
| +
|
| + // ------------- CONSTRAINT HELPERS -----------------
|
| +
|
| + /// Puts the given constraint "in scope" by adding it to the octagon, and
|
| + /// pushing a stack action that will remove it again.
|
| + void applyConstraint(SignedVariable v1, SignedVariable v2, int k) {
|
| + Constraint constraint = new Constraint(v1, v2, k);
|
| + octagon.pushConstraint(constraint);
|
| + pushAction(() => octagon.popConstraint(constraint));
|
| + }
|
| +
|
| + /// Return true if we can prove that `v1 + v2 <= k`.
|
| + bool testConstraint(SignedVariable v1, SignedVariable v2, int k) {
|
| + // Add the negated constraint and check for solvability.
|
| + // !(v1 + v2 <= k) <==> -v1 - v2 <= -k-1
|
| + Constraint constraint = new Constraint(v1.negated, v2.negated, -k - 1);
|
| + octagon.pushConstraint(constraint);
|
| + bool answer = octagon.isUnsolvable;
|
| + octagon.popConstraint(constraint);
|
| + return answer;
|
| + }
|
| +
|
| + void makeLessThanOrEqual(SignedVariable v1, SignedVariable v2) {
|
| + // v1 <= v2 <==> v1 - v2 <= 0
|
| + applyConstraint(v1, v2.negated, 0);
|
| + }
|
| +
|
| + void makeLessThan(SignedVariable v1, SignedVariable v2) {
|
| + // v1 < v2 <==> v1 - v2 <= -1
|
| + applyConstraint(v1, v2.negated, -1);
|
| + }
|
| +
|
| + void makeGreaterThanOrEqual(SignedVariable v1, SignedVariable v2) {
|
| + // v1 >= v2 <==> v2 - v1 <= 0
|
| + applyConstraint(v2, v1.negated, 0);
|
| + }
|
| +
|
| + void makeGreaterThan(SignedVariable v1, SignedVariable v2) {
|
| + // v1 > v2 <==> v2 - v1 <= -1
|
| + applyConstraint(v2, v1.negated, -1);
|
| + }
|
| +
|
| + void makeConstant(SignedVariable v1, int k) {
|
| + // We model this using the constraints:
|
| + // v1 + v1 <= 2k
|
| + // -v1 - v1 <= -2k
|
| + applyConstraint(v1, v1, 2 * k);
|
| + applyConstraint(v1.negated, v1.negated, -2 * k);
|
| + }
|
| +
|
| + /// Make `v1 = v2 + k`.
|
| + void makeExactSum(SignedVariable v1, SignedVariable v2, int k) {
|
| + applyConstraint(v1, v2.negated, k);
|
| + applyConstraint(v1.negated, v2, -k);
|
| + }
|
| +
|
| + /// Make `v1 = v2 [+] k` where [+] represents floating-point addition.
|
| + void makeFloatingPointSum(SignedVariable v1, SignedVariable v2, int k) {
|
| + if (isDefinitelyLessThanOrEqualToConstant(v2, MAX_SAFE_INT - k) &&
|
| + isDefinitelyGreaterThanOrEqualToConstant(v2, -MAX_SAFE_INT + k)) {
|
| + // The result is known to be in the 53-bit range, so no rounding occurs.
|
| + makeExactSum(v1, v2, k);
|
| + } else {
|
| + // A rounding error may occur, so the result may not be exactly v2 + k.
|
| + // We can still add monotonicity constraints:
|
| + // adding a positive number cannot return a lesser number
|
| + // adding a negative number cannot return a greater number
|
| + if (k >= 0) {
|
| + // v1 >= v2 <==> v2 - v1 <= 0 <==> -v1 + v2 <= 0
|
| + applyConstraint(v1.negated, v2, 0);
|
| + } else {
|
| + // v1 <= v2 <==> v1 - v2 <= 0
|
| + applyConstraint(v1, v2.negated, 0);
|
| + }
|
| + }
|
| + }
|
| +
|
| + void makeEqual(SignedVariable v1, SignedVariable v2) {
|
| + // We model this using the constraints:
|
| + // v1 <= v2 <==> v1 - v2 <= 0
|
| + // v1 >= v2 <==> v2 - v1 <= 0
|
| + applyConstraint(v1, v2.negated, 0);
|
| + applyConstraint(v2, v1.negated, 0);
|
| + }
|
| +
|
| + void makeNotEqual(SignedVariable v1, SignedVariable v2) {
|
| + // The octagon cannot represent non-equality, but we can sharpen a weak
|
| + // inequality to a sharp one. If v1 and v2 are already known to be equal,
|
| + // this will create a contradiction and eliminate a dead branch.
|
| + // This is necessary for eliminating concurrent modification checks.
|
| + if (isDefinitelyLessThanOrEqualTo(v1, v2)) {
|
| + makeLessThan(v1, v2);
|
| + } else if (isDefinitelyGreaterThanOrEqualTo(v1, v2)) {
|
| + makeGreaterThan(v1, v2);
|
| + }
|
| + }
|
| +
|
| + /// Return true if we can prove that `v1 <= v2`.
|
| + bool isDefinitelyLessThanOrEqualTo(SignedVariable v1, SignedVariable v2) {
|
| + return testConstraint(v1, v2.negated, 0);
|
| + }
|
| +
|
| + /// Return true if we can prove that `v1 >= v2`.
|
| + bool isDefinitelyGreaterThanOrEqualTo(SignedVariable v1, SignedVariable v2) {
|
| + return testConstraint(v2, v1.negated, 0);
|
| + }
|
| +
|
| + bool isDefinitelyLessThanOrEqualToConstant(SignedVariable v1, int value) {
|
| + // v1 <= value <==> v1 + v1 <= 2 * value
|
| + return testConstraint(v1, v1, 2 * value);
|
| + }
|
| +
|
| + bool isDefinitelyGreaterThanOrEqualToConstant(SignedVariable v1, int value) {
|
| + // v1 >= value <==> -v1 - v1 <= -2 * value
|
| + return testConstraint(v1.negated, v1.negated, -2 * value);
|
| + }
|
| +
|
| + // ------------- TAIL EXPRESSIONS -----------------
|
| +
|
| + @override
|
| + void visitBranch(Branch node) {
|
| + Primitive condition = node.condition.definition;
|
| + Continuation trueCont = node.trueContinuation.definition;
|
| + Continuation falseCont = node.falseContinuation.definition;
|
| + effectNumberAt[trueCont] = currentEffectNumber;
|
| + effectNumberAt[falseCont] = currentEffectNumber;
|
| + pushAction(() {
|
| + // If the branching condition is known statically, either or both of the
|
| + // branch continuations will be replaced by Unreachable. Clean up the
|
| + // branch afterwards.
|
| + if (trueCont.body is Unreachable && falseCont.body is Unreachable) {
|
| + destroyAndReplace(node, new Unreachable());
|
| + } else if (trueCont.body is Unreachable) {
|
| + destroyAndReplace(
|
| + node, new InvokeContinuation(falseCont, <Parameter>[]));
|
| + } else if (falseCont.body is Unreachable) {
|
| + destroyAndReplace(
|
| + node, new InvokeContinuation(trueCont, <Parameter>[]));
|
| + }
|
| + });
|
| + void pushTrue(makeConstraint()) {
|
| + pushAction(() {
|
| + makeConstraint();
|
| + push(trueCont);
|
| + });
|
| + }
|
| + void pushFalse(makeConstraint()) {
|
| + pushAction(() {
|
| + makeConstraint();
|
| + push(falseCont);
|
| + });
|
| + }
|
| + if (condition is ApplyBuiltinOperator &&
|
| + condition.arguments.length == 2 &&
|
| + isInt(condition.arguments[0].definition) &&
|
| + isInt(condition.arguments[1].definition)) {
|
| + SignedVariable v1 = getValue(condition.arguments[0].definition);
|
| + SignedVariable v2 = getValue(condition.arguments[1].definition);
|
| + switch (condition.operator) {
|
| + case BuiltinOperator.NumLe:
|
| + pushTrue(() => makeLessThanOrEqual(v1, v2));
|
| + pushFalse(() => makeGreaterThan(v1, v2));
|
| + return;
|
| + case BuiltinOperator.NumLt:
|
| + pushTrue(() => makeLessThan(v1, v2));
|
| + pushFalse(() => makeGreaterThanOrEqual(v1, v2));
|
| + return;
|
| + case BuiltinOperator.NumGe:
|
| + pushTrue(() => makeGreaterThanOrEqual(v1, v2));
|
| + pushFalse(() => makeLessThan(v1, v2));
|
| + return;
|
| + case BuiltinOperator.NumGt:
|
| + pushTrue(() => makeGreaterThan(v1, v2));
|
| + pushFalse(() => makeLessThanOrEqual(v1, v2));
|
| + return;
|
| + case BuiltinOperator.StrictEq:
|
| + pushTrue(() => makeEqual(v1, v2));
|
| + pushFalse(() => makeNotEqual(v1, v2));
|
| + return;
|
| + case BuiltinOperator.StrictNeq:
|
| + pushTrue(() => makeNotEqual(v1, v2));
|
| + pushFalse(() => makeEqual(v1, v2));
|
| + return;
|
| + default:
|
| + }
|
| + }
|
| +
|
| + push(trueCont);
|
| + push(falseCont);
|
| + }
|
| +
|
| + @override
|
| + void visitConstant(Constant node) {
|
| + // TODO(asgerf): It might be faster to inline the constant in the
|
| + // constraints that reference it.
|
| + if (node.value.isInt) {
|
| + IntConstantValue constant = node.value;
|
| + makeConstant(getValue(node), constant.primitiveValue);
|
| + }
|
| + }
|
| +
|
| + @override
|
| + void visitApplyBuiltinOperator(ApplyBuiltinOperator node) {
|
| + if (node.operator != BuiltinOperator.NumAdd &&
|
| + node.operator != BuiltinOperator.NumSubtract) {
|
| + return;
|
| + }
|
| + if (!isInt(node.arguments[0].definition) ||
|
| + !isInt(node.arguments[1].definition)) {
|
| + return;
|
| + }
|
| + if (!isInt(node)) {
|
| + // TODO(asgerf): The result of this operation should always be an integer,
|
| + // but currently type propagation does not always prove this.
|
| + return;
|
| + }
|
| + // We have `v1 = v2 +/- v3`, but the octagon cannot represent constraints
|
| + // involving more than two variables. Check if one operand is a constant.
|
| + int getConstantArgument(int n) {
|
| + Primitive prim = node.arguments[n].definition;
|
| + if (prim is Constant && prim.value.isInt) {
|
| + IntConstantValue constant = prim.value;
|
| + return constant.primitiveValue;
|
| + }
|
| + return null;
|
| + }
|
| + int constant = getConstantArgument(0);
|
| + int operandIndex = 1;
|
| + if (constant == null) {
|
| + constant = getConstantArgument(1);
|
| + operandIndex = 0;
|
| + }
|
| + if (constant == null) {
|
| + // Neither argument was a constant.
|
| + // Classical octagon-based analyzers would compute upper and lower bounds
|
| + // for the two operands and add constraints for the result based on
|
| + // those. For performance reasons we omit that.
|
| + // TODO(asgerf): It seems expensive, but we should evaluate it.
|
| + return;
|
| + }
|
| + SignedVariable v1 = getValue(node);
|
| + SignedVariable v2 = getValue(node.arguments[operandIndex].definition);
|
| +
|
| + if (node.operator == BuiltinOperator.NumAdd) {
|
| + // v1 = v2 + const
|
| + makeFloatingPointSum(v1, v2, constant);
|
| + } else if (operandIndex == 0) {
|
| + // v1 = v2 - const
|
| + makeFloatingPointSum(v1, v2, -constant);
|
| + } else {
|
| + // v1 = const - v2 <==> v1 = (-v2) + const
|
| + makeFloatingPointSum(v1, v2.negated, constant);
|
| + }
|
| + }
|
| +
|
| + @override
|
| + void visitGetLength(GetLength node) {
|
| + valueOf[node] = getLength(node.object.definition, currentEffectNumber);
|
| + }
|
| +
|
| + void analyzeLoopEntry(InvokeContinuation node) {
|
| + foundLoop = true;
|
| + Continuation cont = node.continuation.definition;
|
| + if (isStrongLoopPass) {
|
| + for (int i = 0; i < node.arguments.length; ++i) {
|
| + Parameter param = cont.parameters[i];
|
| + if (!isInt(param)) continue;
|
| + Primitive initialValue = node.arguments[i].definition;
|
| + SignedVariable initialVariable = getValue(initialValue);
|
| + Monotonicity mono = monotonicity[param];
|
| + if (mono == null) {
|
| + // Value never changes. This is extremely uncommon.
|
| + initialValue.substituteFor(param);
|
| + } else if (mono == Monotonicity.Increasing) {
|
| + makeGreaterThanOrEqual(getValue(param), initialVariable);
|
| + } else if (mono == Monotonicity.Decreasing) {
|
| + makeLessThanOrEqual(getValue(param), initialVariable);
|
| + }
|
| + }
|
| + if (loopsWithSideEffects.contains(cont)) {
|
| + currentEffectNumber = makeNewEffect();
|
| + }
|
| + } else {
|
| + // During the weak pass, conservatively make a new effect number in the
|
| + // loop body. This may be strengthened during the strong pass.
|
| + currentEffectNumber = effectNumberAt[cont] = makeNewEffect();
|
| + }
|
| + push(cont);
|
| + }
|
| +
|
| + void analyzeLoopContinue(InvokeContinuation node) {
|
| + Continuation cont = node.continuation.definition;
|
| +
|
| + // During the strong loop phase, there is no need to compute monotonicity,
|
| + // and we already put bounds on the loop variables when we went into the
|
| + // loop.
|
| + if (isStrongLoopPass) return;
|
| +
|
| + // For each loop parameter, try to prove that the new value is definitely
|
| + // less/greater than its old value. When we fail to prove this, update the
|
| + // monotonicity flag accordingly.
|
| + for (int i = 0; i < node.arguments.length; ++i) {
|
| + Parameter param = cont.parameters[i];
|
| + if (!isInt(param)) continue;
|
| + SignedVariable arg = getValue(node.arguments[i].definition);
|
| + SignedVariable paramVar = getValue(param);
|
| + if (!isDefinitelyLessThanOrEqualTo(arg, paramVar)) {
|
| + // We couldn't prove that the value does not increase, so assume
|
| + // henceforth that it might be increasing.
|
| + markMonotonicity(cont.parameters[i], Monotonicity.Increasing);
|
| + }
|
| + if (!isDefinitelyGreaterThanOrEqualTo(arg, paramVar)) {
|
| + // We couldn't prove that the value does not decrease, so assume
|
| + // henceforth that it might be decreasing.
|
| + markMonotonicity(cont.parameters[i], Monotonicity.Decreasing);
|
| + }
|
| + }
|
| +
|
| + // If a side effect has occurred between the entry and continue, mark
|
| + // the loop as having side effects.
|
| + if (currentEffectNumber != effectNumberAt[cont]) {
|
| + loopsWithSideEffects.add(cont);
|
| + }
|
| + }
|
| +
|
| + void markMonotonicity(Parameter param, Monotonicity mono) {
|
| + Monotonicity current = monotonicity[param];
|
| + if (current == null) {
|
| + monotonicity[param] = mono;
|
| + } else if (current != mono) {
|
| + monotonicity[param] = Monotonicity.NotMonotone;
|
| + }
|
| + }
|
| +
|
| + @override
|
| + void visitInvokeContinuation(InvokeContinuation node) {
|
| + Continuation cont = node.continuation.definition;
|
| + if (node.isRecursive) {
|
| + analyzeLoopContinue(node);
|
| + } else if (cont.isRecursive) {
|
| + analyzeLoopEntry(node);
|
| + } else {
|
| + int effect = effectNumberAt[cont];
|
| + if (effect == null) {
|
| + effectNumberAt[cont] = currentEffectNumber;
|
| + } else if (effect != currentEffectNumber && effect != NEW_EFFECT) {
|
| + effectNumberAt[cont] = NEW_EFFECT;
|
| + }
|
| + // TODO(asgerf): Compute join for parameters to increase precision?
|
| + }
|
| + }
|
| +
|
| + // ---------------- CALL EXPRESSIONS --------------------
|
| +
|
| + @override
|
| + void visitInvokeMethod(InvokeMethod node) {
|
| + // TODO(asgerf): What we really need is a "changes length" side effect flag.
|
| + if (world
|
| + .getSideEffectsOfSelector(node.selector, node.mask)
|
| + .changesIndex()) {
|
| + currentEffectNumber = makeNewEffect();
|
| + }
|
| + push(node.continuation.definition);
|
| + }
|
| +
|
| + @override
|
| + void visitInvokeStatic(InvokeStatic node) {
|
| + if (world.getSideEffectsOfElement(node.target).changesIndex()) {
|
| + currentEffectNumber = makeNewEffect();
|
| + }
|
| + push(node.continuation.definition);
|
| + }
|
| +
|
| + @override
|
| + void visitInvokeMethodDirectly(InvokeMethodDirectly node) {
|
| + FunctionElement target = node.target;
|
| + if (target is ConstructorBodyElement) {
|
| + ConstructorBodyElement body = target;
|
| + target = body.constructor;
|
| + }
|
| + if (world.getSideEffectsOfElement(target).changesIndex()) {
|
| + currentEffectNumber = makeNewEffect();
|
| + }
|
| + push(node.continuation.definition);
|
| + }
|
| +
|
| + @override
|
| + void visitInvokeConstructor(InvokeConstructor node) {
|
| + if (world.getSideEffectsOfElement(node.target).changesIndex()) {
|
| + currentEffectNumber = makeNewEffect();
|
| + }
|
| + push(node.continuation.definition);
|
| + }
|
| +
|
| + @override
|
| + void visitTypeCast(TypeCast node) {
|
| + push(node.continuation.definition);
|
| + }
|
| +
|
| + @override
|
| + void visitGetLazyStatic(GetLazyStatic node) {
|
| + // TODO(asgerf): How do we get the side effects of a lazy field initializer?
|
| + currentEffectNumber = makeNewEffect();
|
| + push(node.continuation.definition);
|
| + }
|
| +
|
| + @override
|
| + void visitForeignCode(ForeignCode node) {
|
| + if (node.nativeBehavior.sideEffects.changesIndex()) {
|
| + currentEffectNumber = makeNewEffect();
|
| + }
|
| + push(node.continuation.definition);
|
| + }
|
| +
|
| + @override
|
| + void visitAwait(Await node) {
|
| + currentEffectNumber = makeNewEffect();
|
| + push(node.continuation.definition);
|
| + }
|
| +
|
| + @override
|
| + void visitYield(Yield node) {
|
| + currentEffectNumber = makeNewEffect();
|
| + push(node.continuation.definition);
|
| + }
|
| +
|
| + // ---------------- PRIMITIVES --------------------
|
| +
|
| + @override
|
| + void visitApplyBuiltinMethod(ApplyBuiltinMethod node) {
|
| + Primitive receiver = node.receiver.definition;
|
| + int effectBefore = currentEffectNumber;
|
| + currentEffectNumber = makeNewEffect();
|
| + int effectAfter = currentEffectNumber;
|
| + SignedVariable lengthBefore = getLength(receiver, effectBefore);
|
| + SignedVariable lengthAfter = getLength(receiver, effectAfter);
|
| + switch (node.method) {
|
| + case BuiltinMethod.Push:
|
| + // after = before + count
|
| + int count = node.arguments.length;
|
| + makeExactSum(lengthAfter, lengthBefore, count);
|
| + break;
|
| +
|
| + case BuiltinMethod.Pop:
|
| + // after = before - 1
|
| + makeExactSum(lengthAfter, lengthBefore, -1);
|
| + break;
|
| + }
|
| + }
|
| +
|
| + @override
|
| + void visitLiteralList(LiteralList node) {
|
| + makeConstant(getLength(node, currentEffectNumber), node.values.length);
|
| + }
|
| +
|
| + // ---------------- INTERIOR EXPRESSIONS --------------------
|
| +
|
| + @override
|
| + Expression traverseContinuation(Continuation cont) {
|
| + if (octagon.isUnsolvable) {
|
| + destroyAndReplace(cont.body, new Unreachable());
|
| + } else {
|
| + int effect = effectNumberAt[cont];
|
| + if (effect != null) {
|
| + currentEffectNumber = effect == NEW_EFFECT ? makeNewEffect() : effect;
|
| + }
|
| + }
|
| + return cont.body;
|
| + }
|
| +
|
| + @override
|
| + Expression traverseLetCont(LetCont node) {
|
| + // Join continuations should be pushed at declaration-site, so all their
|
| + // call sites are seen before they are analyzed.
|
| + // Other continuations are pushed at the use site.
|
| + for (Continuation cont in node.continuations) {
|
| + if (cont.hasAtLeastOneUse &&
|
| + !cont.isRecursive &&
|
| + cont.firstRef.parent is InvokeContinuation) {
|
| + push(cont);
|
| + }
|
| + }
|
| + return node.body;
|
| + }
|
| +}
|
| +
|
| +/// Lattice representing the known (weak) monotonicity of a loop variable.
|
| +///
|
| +/// The lattice bottom is represented by `null` and represents the case where
|
| +/// the loop variable never changes value during the loop.
|
| +enum Monotonicity { NotMonotone, Increasing, Decreasing, }
|
|
|