| Index: pkg/compiler/lib/src/ssa/optimize.dart
|
| diff --git a/pkg/compiler/lib/src/ssa/optimize.dart b/pkg/compiler/lib/src/ssa/optimize.dart
|
| deleted file mode 100644
|
| index 28f0632fd1ad6a96d65176f166615ed9a5305a26..0000000000000000000000000000000000000000
|
| --- a/pkg/compiler/lib/src/ssa/optimize.dart
|
| +++ /dev/null
|
| @@ -1,2151 +0,0 @@
|
| -// Copyright (c) 2012, 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.
|
| -
|
| -part of ssa;
|
| -
|
| -abstract class OptimizationPhase {
|
| - String get name;
|
| - void visitGraph(HGraph graph);
|
| -}
|
| -
|
| -class SsaOptimizerTask extends CompilerTask {
|
| - final JavaScriptBackend backend;
|
| - SsaOptimizerTask(JavaScriptBackend backend)
|
| - : this.backend = backend,
|
| - super(backend.compiler);
|
| - String get name => 'SSA optimizer';
|
| - Compiler get compiler => backend.compiler;
|
| - Map<HInstruction, Range> ranges = <HInstruction, Range>{};
|
| -
|
| - void runPhases(HGraph graph, List<OptimizationPhase> phases) {
|
| - for (OptimizationPhase phase in phases) {
|
| - runPhase(graph, phase);
|
| - }
|
| - }
|
| -
|
| - void runPhase(HGraph graph, OptimizationPhase phase) {
|
| - phase.visitGraph(graph);
|
| - compiler.tracer.traceGraph(phase.name, graph);
|
| - assert(graph.isValid());
|
| - }
|
| -
|
| - void optimize(CodegenWorkItem work, HGraph graph) {
|
| - ConstantSystem constantSystem = compiler.backend.constantSystem;
|
| - JavaScriptItemCompilationContext context = work.compilationContext;
|
| - measure(() {
|
| - SsaDeadCodeEliminator dce;
|
| - List<OptimizationPhase> phases = <OptimizationPhase>[
|
| - // Run trivial instruction simplification first to optimize
|
| - // some patterns useful for type conversion.
|
| - new SsaInstructionSimplifier(constantSystem, backend, work),
|
| - new SsaTypeConversionInserter(compiler),
|
| - new SsaRedundantPhiEliminator(),
|
| - new SsaDeadPhiEliminator(),
|
| - new SsaTypePropagator(compiler),
|
| - // After type propagation, more instructions can be
|
| - // simplified.
|
| - new SsaInstructionSimplifier(constantSystem, backend, work),
|
| - new SsaCheckInserter(backend, work, context.boundsChecked),
|
| - new SsaInstructionSimplifier(constantSystem, backend, work),
|
| - new SsaCheckInserter(backend, work, context.boundsChecked),
|
| - new SsaTypePropagator(compiler),
|
| - // Run a dead code eliminator before LICM because dead
|
| - // interceptors are often in the way of LICM'able instructions.
|
| - new SsaDeadCodeEliminator(compiler),
|
| - new SsaGlobalValueNumberer(compiler),
|
| - // After GVN, some instructions might need their type to be
|
| - // updated because they now have different inputs.
|
| - new SsaTypePropagator(compiler),
|
| - new SsaCodeMotion(),
|
| - new SsaLoadElimination(compiler),
|
| - new SsaDeadPhiEliminator(),
|
| - new SsaTypePropagator(compiler),
|
| - new SsaValueRangeAnalyzer(compiler, constantSystem, work),
|
| - // Previous optimizations may have generated new
|
| - // opportunities for instruction simplification.
|
| - new SsaInstructionSimplifier(constantSystem, backend, work),
|
| - new SsaCheckInserter(backend, work, context.boundsChecked),
|
| - new SsaSimplifyInterceptors(compiler, constantSystem, work),
|
| - dce = new SsaDeadCodeEliminator(compiler),
|
| - new SsaTypePropagator(compiler)];
|
| - runPhases(graph, phases);
|
| - if (dce.eliminatedSideEffects) {
|
| - phases = <OptimizationPhase>[
|
| - new SsaGlobalValueNumberer(compiler),
|
| - new SsaCodeMotion(),
|
| - new SsaValueRangeAnalyzer(compiler, constantSystem, work),
|
| - new SsaInstructionSimplifier(constantSystem, backend, work),
|
| - new SsaCheckInserter(backend, work, context.boundsChecked),
|
| - new SsaSimplifyInterceptors(compiler, constantSystem, work),
|
| - new SsaDeadCodeEliminator(compiler)];
|
| - } else {
|
| - phases = <OptimizationPhase>[
|
| - // Run the simplifier to remove unneeded type checks inserted
|
| - // by type propagation.
|
| - new SsaInstructionSimplifier(constantSystem, backend, work)];
|
| - }
|
| - runPhases(graph, phases);
|
| - });
|
| - }
|
| -}
|
| -
|
| -bool isFixedLength(mask, Compiler compiler) {
|
| - ClassWorld classWorld = compiler.world;
|
| - JavaScriptBackend backend = compiler.backend;
|
| - if (mask.isContainer && mask.length != null) {
|
| - // A container on which we have inferred the length.
|
| - return true;
|
| - } else if (mask.containsOnly(backend.jsFixedArrayClass)
|
| - || mask.containsOnlyString(classWorld)
|
| - || backend.isTypedArray(mask)) {
|
| - return true;
|
| - }
|
| - return false;
|
| -}
|
| -
|
| -/**
|
| - * If both inputs to known operations are available execute the operation at
|
| - * compile-time.
|
| - */
|
| -class SsaInstructionSimplifier extends HBaseVisitor
|
| - implements OptimizationPhase {
|
| -
|
| - // We don't produce constant-folded strings longer than this unless they have
|
| - // a single use. This protects against exponentially large constant folded
|
| - // strings.
|
| - static const MAX_SHARED_CONSTANT_FOLDED_STRING_LENGTH = 512;
|
| -
|
| - final String name = "SsaInstructionSimplifier";
|
| - final JavaScriptBackend backend;
|
| - final CodegenWorkItem work;
|
| - final ConstantSystem constantSystem;
|
| - HGraph graph;
|
| - Compiler get compiler => backend.compiler;
|
| -
|
| - SsaInstructionSimplifier(this.constantSystem, this.backend, this.work);
|
| -
|
| - void visitGraph(HGraph visitee) {
|
| - graph = visitee;
|
| - visitDominatorTree(visitee);
|
| - }
|
| -
|
| - visitBasicBlock(HBasicBlock block) {
|
| - HInstruction instruction = block.first;
|
| - while (instruction != null) {
|
| - HInstruction next = instruction.next;
|
| - HInstruction replacement = instruction.accept(this);
|
| - if (replacement != instruction) {
|
| - block.rewrite(instruction, replacement);
|
| -
|
| - // The intersection of double and int return conflicting, and
|
| - // because of our number implementation for JavaScript, it
|
| - // might be that an operation thought to return double, can be
|
| - // simplified to an int. For example:
|
| - // `2.5 * 10`.
|
| - if (!(replacement.isNumberOrNull(compiler)
|
| - && instruction.isNumberOrNull(compiler))) {
|
| - // If we can replace [instruction] with [replacement], then
|
| - // [replacement]'s type can be narrowed.
|
| - TypeMask newType = replacement.instructionType.intersection(
|
| - instruction.instructionType, compiler.world);
|
| - replacement.instructionType = newType;
|
| - }
|
| -
|
| - // If the replacement instruction does not know its
|
| - // source element, use the source element of the
|
| - // instruction.
|
| - if (replacement.sourceElement == null) {
|
| - replacement.sourceElement = instruction.sourceElement;
|
| - }
|
| - if (replacement.sourcePosition == null) {
|
| - replacement.sourcePosition = instruction.sourcePosition;
|
| - }
|
| - if (!replacement.isInBasicBlock()) {
|
| - // The constant folding can return an instruction that is already
|
| - // part of the graph (like an input), so we only add the replacement
|
| - // if necessary.
|
| - block.addAfter(instruction, replacement);
|
| - // Visit the replacement as the next instruction in case it
|
| - // can also be constant folded away.
|
| - next = replacement;
|
| - }
|
| - block.remove(instruction);
|
| - }
|
| - instruction = next;
|
| - }
|
| - }
|
| -
|
| - HInstruction visitInstruction(HInstruction node) {
|
| - return node;
|
| - }
|
| -
|
| - HInstruction visitBoolify(HBoolify node) {
|
| - List<HInstruction> inputs = node.inputs;
|
| - assert(inputs.length == 1);
|
| - HInstruction input = inputs[0];
|
| - if (input.isBoolean(compiler)) return input;
|
| - // All values that cannot be 'true' are boolified to false.
|
| - TypeMask mask = input.instructionType;
|
| - if (!mask.contains(backend.jsBoolClass, compiler.world)) {
|
| - return graph.addConstantBool(false, compiler);
|
| - }
|
| - return node;
|
| - }
|
| -
|
| - HInstruction visitNot(HNot node) {
|
| - List<HInstruction> inputs = node.inputs;
|
| - assert(inputs.length == 1);
|
| - HInstruction input = inputs[0];
|
| - if (input is HConstant) {
|
| - HConstant constant = input;
|
| - bool isTrue = constant.constant.isTrue;
|
| - return graph.addConstantBool(!isTrue, compiler);
|
| - } else if (input is HNot) {
|
| - return input.inputs[0];
|
| - }
|
| - return node;
|
| - }
|
| -
|
| - HInstruction visitInvokeUnary(HInvokeUnary node) {
|
| - HInstruction folded =
|
| - foldUnary(node.operation(constantSystem), node.operand);
|
| - return folded != null ? folded : node;
|
| - }
|
| -
|
| - HInstruction foldUnary(UnaryOperation operation, HInstruction operand) {
|
| - if (operand is HConstant) {
|
| - HConstant receiver = operand;
|
| - ConstantValue folded = operation.fold(receiver.constant);
|
| - if (folded != null) return graph.addConstant(folded, compiler);
|
| - }
|
| - return null;
|
| - }
|
| -
|
| - HInstruction tryOptimizeLengthInterceptedGetter(HInvokeDynamic node) {
|
| - HInstruction actualReceiver = node.inputs[1];
|
| - if (actualReceiver.isIndexablePrimitive(compiler)) {
|
| - if (actualReceiver.isConstantString()) {
|
| - HConstant constantInput = actualReceiver;
|
| - StringConstantValue constant = constantInput.constant;
|
| - return graph.addConstantInt(constant.length, compiler);
|
| - } else if (actualReceiver.isConstantList()) {
|
| - HConstant constantInput = actualReceiver;
|
| - ListConstantValue constant = constantInput.constant;
|
| - return graph.addConstantInt(constant.length, compiler);
|
| - }
|
| - Element element = backend.jsIndexableLength;
|
| - bool isFixed = isFixedLength(actualReceiver.instructionType, compiler);
|
| - HFieldGet result = new HFieldGet(
|
| - element, actualReceiver, backend.positiveIntType,
|
| - isAssignable: !isFixed);
|
| - return result;
|
| - } else if (actualReceiver.isConstantMap()) {
|
| - HConstant constantInput = actualReceiver;
|
| - MapConstantValue constant = constantInput.constant;
|
| - return graph.addConstantInt(constant.length, compiler);
|
| - }
|
| - return null;
|
| - }
|
| -
|
| - HInstruction handleInterceptedCall(HInvokeDynamic node) {
|
| - // Try constant folding the instruction.
|
| - Operation operation = node.specializer.operation(constantSystem);
|
| - if (operation != null) {
|
| - HInstruction instruction = node.inputs.length == 2
|
| - ? foldUnary(operation, node.inputs[1])
|
| - : foldBinary(operation, node.inputs[1], node.inputs[2]);
|
| - if (instruction != null) return instruction;
|
| - }
|
| -
|
| - // Try converting the instruction to a builtin instruction.
|
| - HInstruction instruction =
|
| - node.specializer.tryConvertToBuiltin(node, compiler);
|
| - if (instruction != null) return instruction;
|
| -
|
| - Selector selector = node.selector;
|
| - HInstruction input = node.inputs[1];
|
| -
|
| - World world = compiler.world;
|
| - if (selector.isCall || selector.isOperator) {
|
| - Element target;
|
| - if (input.isExtendableArray(compiler)) {
|
| - if (selector.applies(backend.jsArrayRemoveLast, world)) {
|
| - target = backend.jsArrayRemoveLast;
|
| - } else if (selector.applies(backend.jsArrayAdd, world)) {
|
| - // The codegen special cases array calls, but does not
|
| - // inline argument type checks.
|
| - if (!compiler.enableTypeAssertions) {
|
| - target = backend.jsArrayAdd;
|
| - }
|
| - }
|
| - } else if (input.isStringOrNull(compiler)) {
|
| - if (selector.applies(backend.jsStringSplit, world)) {
|
| - HInstruction argument = node.inputs[2];
|
| - if (argument.isString(compiler)) {
|
| - target = backend.jsStringSplit;
|
| - }
|
| - } else if (selector.applies(backend.jsStringOperatorAdd, world)) {
|
| - // `operator+` is turned into a JavaScript '+' so we need to
|
| - // make sure the receiver and the argument are not null.
|
| - // TODO(sra): Do this via [node.specializer].
|
| - HInstruction argument = node.inputs[2];
|
| - if (argument.isString(compiler)
|
| - && !input.canBeNull()) {
|
| - return new HStringConcat(input, argument, null,
|
| - node.instructionType);
|
| - }
|
| - } else if (selector.applies(backend.jsStringToString, world)
|
| - && !input.canBeNull()) {
|
| - return input;
|
| - }
|
| - }
|
| - if (target != null) {
|
| - // TODO(ngeoffray): There is a strong dependency between codegen
|
| - // and this optimization that the dynamic invoke does not need an
|
| - // interceptor. We currently need to keep a
|
| - // HInvokeDynamicMethod and not create a HForeign because
|
| - // HForeign is too opaque for the SsaCheckInserter (that adds a
|
| - // bounds check on removeLast). Once we start inlining, the
|
| - // bounds check will become explicit, so we won't need this
|
| - // optimization.
|
| - HInvokeDynamicMethod result = new HInvokeDynamicMethod(
|
| - node.selector, node.inputs.sublist(1), node.instructionType);
|
| - result.element = target;
|
| - return result;
|
| - }
|
| - } else if (selector.isGetter) {
|
| - if (selector.asUntyped.applies(backend.jsIndexableLength, world)) {
|
| - HInstruction optimized = tryOptimizeLengthInterceptedGetter(node);
|
| - if (optimized != null) return optimized;
|
| - }
|
| - }
|
| -
|
| - return node;
|
| - }
|
| -
|
| - HInstruction visitInvokeDynamicMethod(HInvokeDynamicMethod node) {
|
| - if (node.isInterceptedCall) {
|
| - HInstruction folded = handleInterceptedCall(node);
|
| - if (folded != node) return folded;
|
| - }
|
| -
|
| - TypeMask receiverType = node.getDartReceiver(compiler).instructionType;
|
| - Selector selector =
|
| - new TypedSelector(receiverType, node.selector, compiler.world);
|
| - Element element = compiler.world.locateSingleElement(selector);
|
| - // TODO(ngeoffray): Also fold if it's a getter or variable.
|
| - if (element != null
|
| - && element.isFunction
|
| - // If we found out that the only target is a [:noSuchMethod:],
|
| - // we just ignore it.
|
| - && element.name == selector.name) {
|
| - FunctionElement method = element;
|
| -
|
| - if (method.isNative) {
|
| - HInstruction folded = tryInlineNativeMethod(node, method);
|
| - if (folded != null) return folded;
|
| - } else {
|
| - // TODO(ngeoffray): If the method has optional parameters,
|
| - // we should pass the default values.
|
| - FunctionSignature parameters = method.functionSignature;
|
| - if (parameters.optionalParameterCount == 0
|
| - || parameters.parameterCount == node.selector.argumentCount) {
|
| - node.element = element;
|
| - }
|
| - }
|
| - }
|
| - return node;
|
| - }
|
| -
|
| - HInstruction tryInlineNativeMethod(HInvokeDynamicMethod node,
|
| - FunctionElement method) {
|
| - // Enable direct calls to a native method only if we don't run in checked
|
| - // mode, where the Dart version may have type annotations on parameters and
|
| - // return type that it should check.
|
| - // Also check that the parameters are not functions: it's the callee that
|
| - // will translate them to JS functions.
|
| - //
|
| - // TODO(ngeoffray): There are some cases where we could still inline in
|
| - // checked mode if we know the arguments have the right type. And we could
|
| - // do the closure conversion as well as the return type annotation check.
|
| -
|
| - if (!node.isInterceptedCall) return null;
|
| -
|
| - // TODO(sra): Check for legacy methods with bodies in the native strings.
|
| - // foo() native 'return something';
|
| - // They should not be used.
|
| -
|
| - FunctionSignature signature = method.functionSignature;
|
| - if (signature.optionalParametersAreNamed) return null;
|
| -
|
| - // Return types on native methods don't need to be checked, since the
|
| - // declaration has to be truthful.
|
| -
|
| - // The call site might omit optional arguments. The inlined code must
|
| - // preserve the number of arguments, so check only the actual arguments.
|
| -
|
| - List<HInstruction> inputs = node.inputs.sublist(1);
|
| - int inputPosition = 1; // Skip receiver.
|
| - bool canInline = true;
|
| - signature.forEachParameter((ParameterElement element) {
|
| - if (inputPosition < inputs.length && canInline) {
|
| - HInstruction input = inputs[inputPosition++];
|
| - DartType type = element.type.unalias(compiler);
|
| - if (type is FunctionType) {
|
| - canInline = false;
|
| - }
|
| - if (compiler.enableTypeAssertions) {
|
| - // TODO(sra): Check if [input] is guaranteed to pass the parameter
|
| - // type check. Consider using a strengthened type check to avoid
|
| - // passing `null` to primitive types since the native methods usually
|
| - // have non-nullable primitive parameter types.
|
| - canInline = false;
|
| - }
|
| - }
|
| - });
|
| -
|
| - if (!canInline) return null;
|
| -
|
| - // Strengthen instruction type from annotations to help optimize
|
| - // dependent instructions.
|
| - native.NativeBehavior nativeBehavior =
|
| - native.NativeBehavior.ofMethod(method, compiler);
|
| - TypeMask returnType =
|
| - TypeMaskFactory.fromNativeBehavior(nativeBehavior, compiler);
|
| - HInvokeDynamicMethod result =
|
| - new HInvokeDynamicMethod(node.selector, inputs, returnType);
|
| - result.element = method;
|
| - return result;
|
| - }
|
| -
|
| - HInstruction visitBoundsCheck(HBoundsCheck node) {
|
| - HInstruction index = node.index;
|
| - if (index.isInteger(compiler)) return node;
|
| - if (index.isConstant()) {
|
| - HConstant constantInstruction = index;
|
| - assert(!constantInstruction.constant.isInt);
|
| - if (!constantSystem.isInt(constantInstruction.constant)) {
|
| - // -0.0 is a double but will pass the runtime integer check.
|
| - node.staticChecks = HBoundsCheck.ALWAYS_FALSE;
|
| - }
|
| - }
|
| - return node;
|
| - }
|
| -
|
| - HInstruction foldBinary(BinaryOperation operation,
|
| - HInstruction left,
|
| - HInstruction right) {
|
| - if (left is HConstant && right is HConstant) {
|
| - HConstant op1 = left;
|
| - HConstant op2 = right;
|
| - ConstantValue folded = operation.fold(op1.constant, op2.constant);
|
| - if (folded != null) return graph.addConstant(folded, compiler);
|
| - }
|
| - return null;
|
| - }
|
| -
|
| - HInstruction visitAdd(HAdd node) {
|
| - HInstruction left = node.left;
|
| - HInstruction right = node.right;
|
| - // We can only perform this rewriting on Integer, as it is not
|
| - // valid for -0.0.
|
| - if (left.isInteger(compiler) && right.isInteger(compiler)) {
|
| - if (left is HConstant && left.constant.isZero) return right;
|
| - if (right is HConstant && right.constant.isZero) return left;
|
| - }
|
| - return super.visitAdd(node);
|
| - }
|
| -
|
| - HInstruction visitMultiply(HMultiply node) {
|
| - HInstruction left = node.left;
|
| - HInstruction right = node.right;
|
| - if (left.isNumber(compiler) && right.isNumber(compiler)) {
|
| - if (left is HConstant && left.constant.isOne) return right;
|
| - if (right is HConstant && right.constant.isOne) return left;
|
| - }
|
| - return super.visitMultiply(node);
|
| - }
|
| -
|
| - HInstruction visitInvokeBinary(HInvokeBinary node) {
|
| - HInstruction left = node.left;
|
| - HInstruction right = node.right;
|
| - BinaryOperation operation = node.operation(constantSystem);
|
| - HConstant folded = foldBinary(operation, left, right);
|
| - if (folded != null) return folded;
|
| - return node;
|
| - }
|
| -
|
| - bool allUsersAreBoolifies(HInstruction instruction) {
|
| - List<HInstruction> users = instruction.usedBy;
|
| - int length = users.length;
|
| - for (int i = 0; i < length; i++) {
|
| - if (users[i] is! HBoolify) return false;
|
| - }
|
| - return true;
|
| - }
|
| -
|
| - HInstruction visitRelational(HRelational node) {
|
| - if (allUsersAreBoolifies(node)) {
|
| - // TODO(ngeoffray): Call a boolified selector.
|
| - // This node stays the same, but the Boolify node will go away.
|
| - }
|
| - // Note that we still have to call [super] to make sure that we end up
|
| - // in the remaining optimizations.
|
| - return super.visitRelational(node);
|
| - }
|
| -
|
| - HInstruction handleIdentityCheck(HRelational node) {
|
| - HInstruction left = node.left;
|
| - HInstruction right = node.right;
|
| - TypeMask leftType = left.instructionType;
|
| - TypeMask rightType = right.instructionType;
|
| -
|
| - // Intersection of int and double return conflicting, so
|
| - // we don't optimize on numbers to preserve the runtime semantics.
|
| - if (!(left.isNumberOrNull(compiler) && right.isNumberOrNull(compiler))) {
|
| - TypeMask intersection = leftType.intersection(rightType, compiler.world);
|
| - if (intersection.isEmpty && !intersection.isNullable) {
|
| - return graph.addConstantBool(false, compiler);
|
| - }
|
| - }
|
| -
|
| - if (left.isNull() && right.isNull()) {
|
| - return graph.addConstantBool(true, compiler);
|
| - }
|
| -
|
| - if (left.isConstantBoolean() && right.isBoolean(compiler)) {
|
| - HConstant constant = left;
|
| - if (constant.constant.isTrue) {
|
| - return right;
|
| - } else {
|
| - return new HNot(right, backend.boolType);
|
| - }
|
| - }
|
| -
|
| - if (right.isConstantBoolean() && left.isBoolean(compiler)) {
|
| - HConstant constant = right;
|
| - if (constant.constant.isTrue) {
|
| - return left;
|
| - } else {
|
| - return new HNot(left, backend.boolType);
|
| - }
|
| - }
|
| -
|
| - return null;
|
| - }
|
| -
|
| - HInstruction visitIdentity(HIdentity node) {
|
| - HInstruction newInstruction = handleIdentityCheck(node);
|
| - return newInstruction == null ? super.visitIdentity(node) : newInstruction;
|
| - }
|
| -
|
| - void simplifyCondition(HBasicBlock block,
|
| - HInstruction condition,
|
| - bool value) {
|
| - condition.dominatedUsers(block.first).forEach((user) {
|
| - HInstruction newCondition = graph.addConstantBool(value, compiler);
|
| - user.changeUse(condition, newCondition);
|
| - });
|
| - }
|
| -
|
| - HInstruction visitIf(HIf node) {
|
| - HInstruction condition = node.condition;
|
| - if (condition.isConstant()) return node;
|
| - bool isNegated = condition is HNot;
|
| -
|
| - if (isNegated) {
|
| - condition = condition.inputs[0];
|
| - } else {
|
| - // It is possible for LICM to move a negated version of the
|
| - // condition out of the loop where it used. We still want to
|
| - // simplify the nested use of the condition in that case, so
|
| - // we look for all dominating negated conditions and replace
|
| - // nested uses of them with true or false.
|
| - Iterable<HInstruction> dominating = condition.usedBy.where((user) =>
|
| - user is HNot && user.dominates(node));
|
| - dominating.forEach((hoisted) {
|
| - simplifyCondition(node.thenBlock, hoisted, false);
|
| - simplifyCondition(node.elseBlock, hoisted, true);
|
| - });
|
| - }
|
| - simplifyCondition(node.thenBlock, condition, !isNegated);
|
| - simplifyCondition(node.elseBlock, condition, isNegated);
|
| - return node;
|
| - }
|
| -
|
| - HInstruction visitIs(HIs node) {
|
| - DartType type = node.typeExpression;
|
| - Element element = type.element;
|
| -
|
| - if (!node.isRawCheck) {
|
| - return node;
|
| - } else if (type.isTypedef) {
|
| - return node;
|
| - } else if (element == compiler.functionClass) {
|
| - return node;
|
| - }
|
| -
|
| - if (element == compiler.objectClass || type.treatAsDynamic) {
|
| - return graph.addConstantBool(true, compiler);
|
| - }
|
| -
|
| - ClassWorld classWorld = compiler.world;
|
| - HInstruction expression = node.expression;
|
| - if (expression.isInteger(compiler)) {
|
| - if (identical(element, compiler.intClass)
|
| - || identical(element, compiler.numClass)
|
| - || Elements.isNumberOrStringSupertype(element, compiler)) {
|
| - return graph.addConstantBool(true, compiler);
|
| - } else if (identical(element, compiler.doubleClass)) {
|
| - // We let the JS semantics decide for that check. Currently
|
| - // the code we emit will always return true.
|
| - return node;
|
| - } else {
|
| - return graph.addConstantBool(false, compiler);
|
| - }
|
| - } else if (expression.isDouble(compiler)) {
|
| - if (identical(element, compiler.doubleClass)
|
| - || identical(element, compiler.numClass)
|
| - || Elements.isNumberOrStringSupertype(element, compiler)) {
|
| - return graph.addConstantBool(true, compiler);
|
| - } else if (identical(element, compiler.intClass)) {
|
| - // We let the JS semantics decide for that check. Currently
|
| - // the code we emit will return true for a double that can be
|
| - // represented as a 31-bit integer and for -0.0.
|
| - return node;
|
| - } else {
|
| - return graph.addConstantBool(false, compiler);
|
| - }
|
| - } else if (expression.isNumber(compiler)) {
|
| - if (identical(element, compiler.numClass)) {
|
| - return graph.addConstantBool(true, compiler);
|
| - } else {
|
| - // We cannot just return false, because the expression may be of
|
| - // type int or double.
|
| - }
|
| - } else if (expression.canBePrimitiveNumber(compiler)
|
| - && identical(element, compiler.intClass)) {
|
| - // We let the JS semantics decide for that check.
|
| - return node;
|
| - // We need the [:hasTypeArguments:] check because we don't have
|
| - // the notion of generics in the backend. For example, [:this:] in
|
| - // a class [:A<T>:], is currently always considered to have the
|
| - // raw type.
|
| - } else if (!RuntimeTypes.hasTypeArguments(type)) {
|
| - TypeMask expressionMask = expression.instructionType;
|
| - assert(TypeMask.assertIsNormalized(expressionMask, classWorld));
|
| - TypeMask typeMask = (element == compiler.nullClass)
|
| - ? new TypeMask.subtype(element, classWorld)
|
| - : new TypeMask.nonNullSubtype(element, classWorld);
|
| - if (expressionMask.union(typeMask, classWorld) == typeMask) {
|
| - return graph.addConstantBool(true, compiler);
|
| - } else if (expressionMask.intersection(typeMask,
|
| - compiler.world).isEmpty) {
|
| - return graph.addConstantBool(false, compiler);
|
| - }
|
| - }
|
| - return node;
|
| - }
|
| -
|
| - HInstruction visitTypeConversion(HTypeConversion node) {
|
| - HInstruction value = node.inputs[0];
|
| - DartType type = node.typeExpression;
|
| - if (type != null) {
|
| - if (type.isMalformed) {
|
| - // Malformed types are treated as dynamic statically, but should
|
| - // throw a type error at runtime.
|
| - return node;
|
| - }
|
| - if (!type.treatAsRaw || type.isTypeVariable) {
|
| - return node;
|
| - }
|
| - if (type.isFunctionType) {
|
| - // TODO(johnniwinther): Optimize function type conversions.
|
| - return node;
|
| - }
|
| - }
|
| - return removeIfCheckAlwaysSucceeds(node, node.checkedType);
|
| - }
|
| -
|
| - HInstruction visitTypeKnown(HTypeKnown node) {
|
| - return removeIfCheckAlwaysSucceeds(node, node.knownType);
|
| - }
|
| -
|
| - HInstruction removeIfCheckAlwaysSucceeds(HCheck node, TypeMask checkedType) {
|
| - ClassWorld classWorld = compiler.world;
|
| - if (checkedType.containsAll(classWorld)) return node;
|
| - HInstruction input = node.checkedInput;
|
| - TypeMask inputType = input.instructionType;
|
| - return inputType.isInMask(checkedType, classWorld) ? input : node;
|
| - }
|
| -
|
| - VariableElement findConcreteFieldForDynamicAccess(HInstruction receiver,
|
| - Selector selector) {
|
| - TypeMask receiverType = receiver.instructionType;
|
| - return compiler.world.locateSingleField(
|
| - new TypedSelector(receiverType, selector, compiler.world));
|
| - }
|
| -
|
| - HInstruction visitFieldGet(HFieldGet node) {
|
| - if (node.isNullCheck) return node;
|
| - var receiver = node.receiver;
|
| - if (node.element == backend.jsIndexableLength) {
|
| - JavaScriptItemCompilationContext context = work.compilationContext;
|
| - if (context.allocatedFixedLists.contains(receiver)) {
|
| - // TODO(ngeoffray): checking if the second input is an integer
|
| - // should not be necessary but it currently makes it easier for
|
| - // other optimizations to reason about a fixed length constructor
|
| - // that we know takes an int.
|
| - if (receiver.inputs[0].isInteger(compiler)) {
|
| - return receiver.inputs[0];
|
| - }
|
| - } else if (receiver.isConstantList() || receiver.isConstantString()) {
|
| - return graph.addConstantInt(receiver.constant.length, compiler);
|
| - } else {
|
| - var type = receiver.instructionType;
|
| - if (type.isContainer && type.length != null) {
|
| - HInstruction constant = graph.addConstantInt(type.length, compiler);
|
| - if (type.isNullable) {
|
| - // If the container can be null, we update all uses of the
|
| - // length access to use the constant instead, but keep the
|
| - // length access in the graph, to ensure we still have a
|
| - // null check.
|
| - node.block.rewrite(node, constant);
|
| - return node;
|
| - } else {
|
| - return constant;
|
| - }
|
| - }
|
| - }
|
| - }
|
| -
|
| - // HFieldGet of a constructed constant can be replaced with the constant's
|
| - // field.
|
| - if (receiver is HConstant) {
|
| - ConstantValue constant = receiver.constant;
|
| - if (constant.isConstructedObject) {
|
| - ConstructedConstantValue constructedConstant = constant;
|
| - Map<Element, ConstantValue> fields = constructedConstant.fieldElements;
|
| - ConstantValue value = fields[node.element];
|
| - if (value != null) {
|
| - return graph.addConstant(value, compiler);
|
| - }
|
| - }
|
| - }
|
| -
|
| - return node;
|
| - }
|
| -
|
| - HInstruction visitIndex(HIndex node) {
|
| - if (node.receiver.isConstantList() && node.index.isConstantInteger()) {
|
| - var instruction = node.receiver;
|
| - List<ConstantValue> entries = instruction.constant.entries;
|
| - instruction = node.index;
|
| - int index = instruction.constant.primitiveValue;
|
| - if (index >= 0 && index < entries.length) {
|
| - return graph.addConstant(entries[index], compiler);
|
| - }
|
| - }
|
| - return node;
|
| - }
|
| -
|
| - HInstruction visitInvokeDynamicGetter(HInvokeDynamicGetter node) {
|
| - if (node.isInterceptedCall) {
|
| - HInstruction folded = handleInterceptedCall(node);
|
| - if (folded != node) return folded;
|
| - }
|
| - HInstruction receiver = node.getDartReceiver(compiler);
|
| - Element field = findConcreteFieldForDynamicAccess(receiver, node.selector);
|
| - if (field == null) return node;
|
| - return directFieldGet(receiver, field);
|
| - }
|
| -
|
| - HInstruction directFieldGet(HInstruction receiver, Element field) {
|
| - bool isAssignable = !compiler.world.fieldNeverChanges(field);
|
| -
|
| - TypeMask type;
|
| - if (field.enclosingClass.isNative) {
|
| - type = TypeMaskFactory.fromNativeBehavior(
|
| - native.NativeBehavior.ofFieldLoad(field, compiler),
|
| - compiler);
|
| - } else {
|
| - type = TypeMaskFactory.inferredTypeForElement(field, compiler);
|
| - }
|
| -
|
| - return new HFieldGet(
|
| - field, receiver, type, isAssignable: isAssignable);
|
| - }
|
| -
|
| - HInstruction visitInvokeDynamicSetter(HInvokeDynamicSetter node) {
|
| - if (node.isInterceptedCall) {
|
| - HInstruction folded = handleInterceptedCall(node);
|
| - if (folded != node) return folded;
|
| - }
|
| -
|
| - HInstruction receiver = node.getDartReceiver(compiler);
|
| - VariableElement field =
|
| - findConcreteFieldForDynamicAccess(receiver, node.selector);
|
| - if (field == null || !field.isAssignable) return node;
|
| - // Use [:node.inputs.last:] in case the call follows the
|
| - // interceptor calling convention, but is not a call on an
|
| - // interceptor.
|
| - HInstruction value = node.inputs.last;
|
| - if (compiler.enableTypeAssertions) {
|
| - DartType type = field.type;
|
| - if (!type.treatAsRaw || type.isTypeVariable) {
|
| - // We cannot generate the correct type representation here, so don't
|
| - // inline this access.
|
| - return node;
|
| - }
|
| - HInstruction other = value.convertType(
|
| - compiler,
|
| - type,
|
| - HTypeConversion.CHECKED_MODE_CHECK);
|
| - if (other != value) {
|
| - node.block.addBefore(node, other);
|
| - value = other;
|
| - }
|
| - }
|
| - return new HFieldSet(field, receiver, value);
|
| - }
|
| -
|
| - HInstruction visitStringConcat(HStringConcat node) {
|
| - // Simplify string concat:
|
| - //
|
| - // "" + R -> R
|
| - // L + "" -> L
|
| - // "L" + "R" -> "LR"
|
| - // (prefix + "L") + "R" -> prefix + "LR"
|
| - //
|
| - StringConstantValue getString(HInstruction instruction) {
|
| - if (!instruction.isConstantString()) return null;
|
| - HConstant constant = instruction;
|
| - return constant.constant;
|
| - }
|
| -
|
| - StringConstantValue leftString = getString(node.left);
|
| - if (leftString != null && leftString.primitiveValue.length == 0) {
|
| - return node.right;
|
| - }
|
| -
|
| - StringConstantValue rightString = getString(node.right);
|
| - if (rightString == null) return node;
|
| - if (rightString.primitiveValue.length == 0) return node.left;
|
| -
|
| - HInstruction prefix = null;
|
| - if (leftString == null) {
|
| - if (node.left is! HStringConcat) return node;
|
| - HStringConcat leftConcat = node.left;
|
| - // Don't undo CSE.
|
| - if (leftConcat.usedBy.length != 1) return node;
|
| - prefix = leftConcat.left;
|
| - leftString = getString(leftConcat.right);
|
| - if (leftString == null) return node;
|
| - }
|
| -
|
| - if (leftString.primitiveValue.length + rightString.primitiveValue.length >
|
| - MAX_SHARED_CONSTANT_FOLDED_STRING_LENGTH) {
|
| - if (node.usedBy.length > 1) return node;
|
| - }
|
| -
|
| - HInstruction folded = graph.addConstant(
|
| - constantSystem.createString(new ast.DartString.concat(
|
| - leftString.primitiveValue, rightString.primitiveValue)),
|
| - compiler);
|
| - if (prefix == null) return folded;
|
| - return new HStringConcat(prefix, folded, node.node, backend.stringType);
|
| - }
|
| -
|
| - HInstruction visitStringify(HStringify node) {
|
| - HInstruction input = node.inputs[0];
|
| - if (input.isString(compiler)) return input;
|
| - if (input.isConstant()) {
|
| - HConstant constant = input;
|
| - if (!constant.constant.isPrimitive) return node;
|
| - if (constant.constant.isInt) {
|
| - // Only constant-fold int.toString() when Dart and JS results the same.
|
| - // TODO(18103): We should be able to remove this work-around when issue
|
| - // 18103 is resolved by providing the correct string.
|
| - IntConstantValue intConstant = constant.constant;
|
| - // Very conservative range.
|
| - if (!intConstant.isUInt32()) return node;
|
| - }
|
| - PrimitiveConstantValue primitive = constant.constant;
|
| - return graph.addConstant(constantSystem.createString(
|
| - primitive.toDartString()), compiler);
|
| - }
|
| - return node;
|
| - }
|
| -
|
| - HInstruction visitOneShotInterceptor(HOneShotInterceptor node) {
|
| - return handleInterceptedCall(node);
|
| - }
|
| -}
|
| -
|
| -class SsaCheckInserter extends HBaseVisitor implements OptimizationPhase {
|
| - final Set<HInstruction> boundsChecked;
|
| - final CodegenWorkItem work;
|
| - final JavaScriptBackend backend;
|
| - final String name = "SsaCheckInserter";
|
| - HGraph graph;
|
| -
|
| - SsaCheckInserter(this.backend,
|
| - this.work,
|
| - this.boundsChecked);
|
| -
|
| - void visitGraph(HGraph graph) {
|
| - this.graph = graph;
|
| - visitDominatorTree(graph);
|
| - }
|
| -
|
| - void visitBasicBlock(HBasicBlock block) {
|
| - HInstruction instruction = block.first;
|
| - while (instruction != null) {
|
| - HInstruction next = instruction.next;
|
| - instruction = instruction.accept(this);
|
| - instruction = next;
|
| - }
|
| - }
|
| -
|
| - HBoundsCheck insertBoundsCheck(HInstruction indexNode,
|
| - HInstruction array,
|
| - HInstruction indexArgument) {
|
| - Compiler compiler = backend.compiler;
|
| - HFieldGet length = new HFieldGet(
|
| - backend.jsIndexableLength, array, backend.positiveIntType,
|
| - isAssignable: !isFixedLength(array.instructionType, compiler));
|
| - indexNode.block.addBefore(indexNode, length);
|
| -
|
| - TypeMask type = indexArgument.isPositiveInteger(compiler)
|
| - ? indexArgument.instructionType
|
| - : backend.positiveIntType;
|
| - HBoundsCheck check = new HBoundsCheck(
|
| - indexArgument, length, array, type);
|
| - indexNode.block.addBefore(indexNode, check);
|
| - // If the index input to the bounds check was not known to be an integer
|
| - // then we replace its uses with the bounds check, which is known to be an
|
| - // integer. However, if the input was already an integer we don't do this
|
| - // because putting in a check instruction might obscure the real nature of
|
| - // the index eg. if it is a constant. The range information from the
|
| - // BoundsCheck instruction is attached to the input directly by
|
| - // visitBoundsCheck in the SsaValueRangeAnalyzer.
|
| - if (!indexArgument.isInteger(compiler)) {
|
| - indexArgument.replaceAllUsersDominatedBy(indexNode, check);
|
| - }
|
| - boundsChecked.add(indexNode);
|
| - return check;
|
| - }
|
| -
|
| - void visitIndex(HIndex node) {
|
| - if (boundsChecked.contains(node)) return;
|
| - HInstruction index = node.index;
|
| - index = insertBoundsCheck(node, node.receiver, index);
|
| - }
|
| -
|
| - void visitIndexAssign(HIndexAssign node) {
|
| - if (boundsChecked.contains(node)) return;
|
| - HInstruction index = node.index;
|
| - index = insertBoundsCheck(node, node.receiver, index);
|
| - }
|
| -
|
| - void visitInvokeDynamicMethod(HInvokeDynamicMethod node) {
|
| - Element element = node.element;
|
| - if (node.isInterceptedCall) return;
|
| - if (element != backend.jsArrayRemoveLast) return;
|
| - if (boundsChecked.contains(node)) return;
|
| - insertBoundsCheck(
|
| - node, node.receiver, graph.addConstantInt(0, backend.compiler));
|
| - }
|
| -}
|
| -
|
| -class SsaDeadCodeEliminator extends HGraphVisitor implements OptimizationPhase {
|
| - final String name = "SsaDeadCodeEliminator";
|
| -
|
| - final Compiler compiler;
|
| - SsaLiveBlockAnalyzer analyzer;
|
| - bool eliminatedSideEffects = false;
|
| - SsaDeadCodeEliminator(this.compiler);
|
| -
|
| - HInstruction zapInstructionCache;
|
| - HInstruction get zapInstruction {
|
| - if (zapInstructionCache == null) {
|
| - // A constant with no type does not pollute types at phi nodes.
|
| - ConstantValue constant =
|
| - new DummyConstantValue(const TypeMask.nonNullEmpty());
|
| - zapInstructionCache = analyzer.graph.addConstant(constant, compiler);
|
| - }
|
| - return zapInstructionCache;
|
| - }
|
| -
|
| - /// Returns whether the next throwing instruction that may have side
|
| - /// effects after [instruction], throws [NoSuchMethodError] on the
|
| - /// same receiver of [instruction].
|
| - bool hasFollowingThrowingNSM(HInstruction instruction) {
|
| - HInstruction receiver = instruction.getDartReceiver(compiler);
|
| - HInstruction current = instruction.next;
|
| - do {
|
| - if ((current.getDartReceiver(compiler) == receiver)
|
| - && current.canThrow()) {
|
| - return true;
|
| - }
|
| - if (current.canThrow() || current.sideEffects.hasSideEffects()) {
|
| - return false;
|
| - }
|
| - if (current.next == null && current is HGoto) {
|
| - // We do not merge blocks in our SSA graph, so if this block
|
| - // just jumps to a single predecessor, visit this predecessor.
|
| - assert(current.block.successors.length == 1);
|
| - current = current.block.successors[0].first;
|
| - } else {
|
| - current = current.next;
|
| - }
|
| - } while (current != null);
|
| - return false;
|
| - }
|
| -
|
| - bool isDeadCode(HInstruction instruction) {
|
| - if (!instruction.usedBy.isEmpty) return false;
|
| - if (instruction.sideEffects.hasSideEffects()) return false;
|
| - if (instruction.canThrow()
|
| - && instruction.onlyThrowsNSM()
|
| - && hasFollowingThrowingNSM(instruction)) {
|
| - return true;
|
| - }
|
| - return !instruction.canThrow()
|
| - && instruction is !HParameterValue
|
| - && instruction is !HLocalSet;
|
| - }
|
| -
|
| - void visitGraph(HGraph graph) {
|
| - analyzer = new SsaLiveBlockAnalyzer(graph, compiler);
|
| - analyzer.analyze();
|
| - visitPostDominatorTree(graph);
|
| - cleanPhis(graph);
|
| - }
|
| -
|
| - void visitBasicBlock(HBasicBlock block) {
|
| - bool isDeadBlock = analyzer.isDeadBlock(block);
|
| - block.isLive = !isDeadBlock;
|
| - // Start from the last non-control flow instruction in the block.
|
| - HInstruction instruction = block.last.previous;
|
| - while (instruction != null) {
|
| - var previous = instruction.previous;
|
| - if (isDeadBlock) {
|
| - eliminatedSideEffects = eliminatedSideEffects ||
|
| - instruction.sideEffects.hasSideEffects();
|
| - removeUsers(instruction);
|
| - block.remove(instruction);
|
| - } else if (isDeadCode(instruction)) {
|
| - block.remove(instruction);
|
| - }
|
| - instruction = previous;
|
| - }
|
| - }
|
| -
|
| - void cleanPhis(HGraph graph) {
|
| - L: for (HBasicBlock block in graph.blocks) {
|
| - List<HBasicBlock> predecessors = block.predecessors;
|
| - // Zap all inputs to phis that correspond to dead blocks.
|
| - block.forEachPhi((HPhi phi) {
|
| - for (int i = 0; i < phi.inputs.length; ++i) {
|
| - if (!predecessors[i].isLive && phi.inputs[i] != zapInstruction) {
|
| - replaceInput(i, phi, zapInstruction);
|
| - }
|
| - }
|
| - });
|
| - if (predecessors.length < 2) continue L;
|
| - // Find the index of the single live predecessor if it exists.
|
| - int indexOfLive = -1;
|
| - for (int i = 0; i < predecessors.length; i++) {
|
| - if (predecessors[i].isLive) {
|
| - if (indexOfLive >= 0) continue L;
|
| - indexOfLive = i;
|
| - }
|
| - }
|
| - // Run through the phis of the block and replace them with their input
|
| - // that comes from the only live predecessor if that dominates the phi.
|
| - block.forEachPhi((HPhi phi) {
|
| - HInstruction replacement = (indexOfLive >= 0)
|
| - ? phi.inputs[indexOfLive] : zapInstruction;
|
| - if (replacement.dominates(phi)) {
|
| - block.rewrite(phi, replacement);
|
| - block.removePhi(phi);
|
| - }
|
| - });
|
| - }
|
| - }
|
| -
|
| - void replaceInput(int i, HInstruction from, HInstruction by) {
|
| - from.inputs[i].usedBy.remove(from);
|
| - from.inputs[i] = by;
|
| - by.usedBy.add(from);
|
| - }
|
| -
|
| - void removeUsers(HInstruction instruction) {
|
| - instruction.usedBy.forEach((user) {
|
| - removeInput(user, instruction);
|
| - });
|
| - instruction.usedBy.clear();
|
| - }
|
| -
|
| - void removeInput(HInstruction user, HInstruction input) {
|
| - List<HInstruction> inputs = user.inputs;
|
| - for (int i = 0, length = inputs.length; i < length; i++) {
|
| - if (input == inputs[i]) {
|
| - user.inputs[i] = zapInstruction;
|
| - zapInstruction.usedBy.add(user);
|
| - }
|
| - }
|
| - }
|
| -}
|
| -
|
| -class SsaLiveBlockAnalyzer extends HBaseVisitor {
|
| - final HGraph graph;
|
| - final Compiler compiler;
|
| - final Set<HBasicBlock> live = new Set<HBasicBlock>();
|
| - final List<HBasicBlock> worklist = <HBasicBlock>[];
|
| -
|
| - SsaLiveBlockAnalyzer(this.graph, this.compiler);
|
| -
|
| - JavaScriptBackend get backend => compiler.backend;
|
| - Map<HInstruction, Range> get ranges => backend.optimizer.ranges;
|
| -
|
| - bool isDeadBlock(HBasicBlock block) => !live.contains(block);
|
| -
|
| - void analyze() {
|
| - markBlockLive(graph.entry);
|
| - while (!worklist.isEmpty) {
|
| - HBasicBlock live = worklist.removeLast();
|
| - live.last.accept(this);
|
| - }
|
| - }
|
| -
|
| - void markBlockLive(HBasicBlock block) {
|
| - if (!live.contains(block)) {
|
| - worklist.add(block);
|
| - live.add(block);
|
| - }
|
| - }
|
| -
|
| - void visitControlFlow(HControlFlow instruction) {
|
| - instruction.block.successors.forEach(markBlockLive);
|
| - }
|
| -
|
| - void visitIf(HIf instruction) {
|
| - HInstruction condition = instruction.condition;
|
| - if (condition.isConstant()) {
|
| - if (condition.isConstantTrue()) {
|
| - markBlockLive(instruction.thenBlock);
|
| - } else {
|
| - markBlockLive(instruction.elseBlock);
|
| - }
|
| - } else {
|
| - visitControlFlow(instruction);
|
| - }
|
| - }
|
| -
|
| - void visitSwitch(HSwitch node) {
|
| - if (node.expression.isInteger(compiler)) {
|
| - Range switchRange = ranges[node.expression];
|
| - if (switchRange != null &&
|
| - switchRange.lower is IntValue &&
|
| - switchRange.upper is IntValue) {
|
| - IntValue lowerValue = switchRange.lower;
|
| - IntValue upperValue = switchRange.upper;
|
| - int lower = lowerValue.value;
|
| - int upper = upperValue.value;
|
| - Set<int> liveLabels = new Set<int>();
|
| - for (int pos = 1; pos < node.inputs.length; pos++) {
|
| - HConstant input = node.inputs[pos];
|
| - if (!input.isConstantInteger()) continue;
|
| - IntConstantValue constant = input.constant;
|
| - int label = constant.primitiveValue;
|
| - if (!liveLabels.contains(label) &&
|
| - label <= upper &&
|
| - label >= lower) {
|
| - markBlockLive(node.block.successors[pos - 1]);
|
| - liveLabels.add(label);
|
| - }
|
| - }
|
| - if (liveLabels.length != upper - lower + 1) {
|
| - markBlockLive(node.defaultTarget);
|
| - }
|
| - return;
|
| - }
|
| - }
|
| - visitControlFlow(node);
|
| - }
|
| -}
|
| -
|
| -class SsaDeadPhiEliminator implements OptimizationPhase {
|
| - final String name = "SsaDeadPhiEliminator";
|
| -
|
| - void visitGraph(HGraph graph) {
|
| - final List<HPhi> worklist = <HPhi>[];
|
| - // A set to keep track of the live phis that we found.
|
| - final Set<HPhi> livePhis = new Set<HPhi>();
|
| -
|
| - // Add to the worklist all live phis: phis referenced by non-phi
|
| - // instructions.
|
| - for (final block in graph.blocks) {
|
| - block.forEachPhi((HPhi phi) {
|
| - for (final user in phi.usedBy) {
|
| - if (user is !HPhi) {
|
| - worklist.add(phi);
|
| - livePhis.add(phi);
|
| - break;
|
| - }
|
| - }
|
| - });
|
| - }
|
| -
|
| - // Process the worklist by propagating liveness to phi inputs.
|
| - while (!worklist.isEmpty) {
|
| - HPhi phi = worklist.removeLast();
|
| - for (final input in phi.inputs) {
|
| - if (input is HPhi && !livePhis.contains(input)) {
|
| - worklist.add(input);
|
| - livePhis.add(input);
|
| - }
|
| - }
|
| - }
|
| -
|
| - // Remove phis that are not live.
|
| - // Traverse in reverse order to remove phis with no uses before the
|
| - // phis that they might use.
|
| - // NOTICE: Doesn't handle circular references, but we don't currently
|
| - // create any.
|
| - List<HBasicBlock> blocks = graph.blocks;
|
| - for (int i = blocks.length - 1; i >= 0; i--) {
|
| - HBasicBlock block = blocks[i];
|
| - HPhi current = block.phis.first;
|
| - HPhi next = null;
|
| - while (current != null) {
|
| - next = current.next;
|
| - if (!livePhis.contains(current)
|
| - // TODO(ahe): Not sure the following is correct.
|
| - && current.usedBy.isEmpty) {
|
| - block.removePhi(current);
|
| - }
|
| - current = next;
|
| - }
|
| - }
|
| - }
|
| -}
|
| -
|
| -class SsaRedundantPhiEliminator implements OptimizationPhase {
|
| - final String name = "SsaRedundantPhiEliminator";
|
| -
|
| - void visitGraph(HGraph graph) {
|
| - final List<HPhi> worklist = <HPhi>[];
|
| -
|
| - // Add all phis in the worklist.
|
| - for (final block in graph.blocks) {
|
| - block.forEachPhi((HPhi phi) => worklist.add(phi));
|
| - }
|
| -
|
| - while (!worklist.isEmpty) {
|
| - HPhi phi = worklist.removeLast();
|
| -
|
| - // If the phi has already been processed, continue.
|
| - if (!phi.isInBasicBlock()) continue;
|
| -
|
| - // Find if the inputs of the phi are the same instruction.
|
| - // The builder ensures that phi.inputs[0] cannot be the phi
|
| - // itself.
|
| - assert(!identical(phi.inputs[0], phi));
|
| - HInstruction candidate = phi.inputs[0];
|
| - for (int i = 1; i < phi.inputs.length; i++) {
|
| - HInstruction input = phi.inputs[i];
|
| - // If the input is the phi, the phi is still candidate for
|
| - // elimination.
|
| - if (!identical(input, candidate) && !identical(input, phi)) {
|
| - candidate = null;
|
| - break;
|
| - }
|
| - }
|
| -
|
| - // If the inputs are not the same, continue.
|
| - if (candidate == null) continue;
|
| -
|
| - // Because we're updating the users of this phi, we may have new
|
| - // phis candidate for elimination. Add phis that used this phi
|
| - // to the worklist.
|
| - for (final user in phi.usedBy) {
|
| - if (user is HPhi) worklist.add(user);
|
| - }
|
| - phi.block.rewrite(phi, candidate);
|
| - phi.block.removePhi(phi);
|
| - }
|
| - }
|
| -}
|
| -
|
| -class GvnWorkItem {
|
| - final HBasicBlock block;
|
| - final ValueSet valueSet;
|
| - GvnWorkItem(this.block, this.valueSet);
|
| -}
|
| -
|
| -class SsaGlobalValueNumberer implements OptimizationPhase {
|
| - final String name = "SsaGlobalValueNumberer";
|
| - final Compiler compiler;
|
| - final Set<int> visited;
|
| -
|
| - List<int> blockChangesFlags;
|
| - List<int> loopChangesFlags;
|
| -
|
| - SsaGlobalValueNumberer(this.compiler) : visited = new Set<int>();
|
| -
|
| - void visitGraph(HGraph graph) {
|
| - computeChangesFlags(graph);
|
| - moveLoopInvariantCode(graph);
|
| - List<GvnWorkItem> workQueue =
|
| - <GvnWorkItem>[new GvnWorkItem(graph.entry, new ValueSet())];
|
| - do {
|
| - GvnWorkItem item = workQueue.removeLast();
|
| - visitBasicBlock(item.block, item.valueSet, workQueue);
|
| - } while (!workQueue.isEmpty);
|
| - }
|
| -
|
| - void moveLoopInvariantCode(HGraph graph) {
|
| - for (int i = graph.blocks.length - 1; i >= 0; i--) {
|
| - HBasicBlock block = graph.blocks[i];
|
| - if (block.isLoopHeader()) {
|
| - int changesFlags = loopChangesFlags[block.id];
|
| - HLoopInformation info = block.loopInformation;
|
| - // Iterate over all blocks of this loop. Note that blocks in
|
| - // inner loops are not visited here, but we know they
|
| - // were visited before because we are iterating in post-order.
|
| - // So instructions that are GVN'ed in an inner loop are in their
|
| - // loop entry, and [info.blocks] contains this loop entry.
|
| - moveLoopInvariantCodeFromBlock(block, block, changesFlags);
|
| - for (HBasicBlock other in info.blocks) {
|
| - moveLoopInvariantCodeFromBlock(other, block, changesFlags);
|
| - }
|
| - }
|
| - }
|
| - }
|
| -
|
| - void moveLoopInvariantCodeFromBlock(HBasicBlock block,
|
| - HBasicBlock loopHeader,
|
| - int changesFlags) {
|
| - assert(block.parentLoopHeader == loopHeader || block == loopHeader);
|
| - HBasicBlock preheader = loopHeader.predecessors[0];
|
| - int dependsFlags = SideEffects.computeDependsOnFlags(changesFlags);
|
| - HInstruction instruction = block.first;
|
| - bool isLoopAlwaysTaken() {
|
| - HInstruction instruction = loopHeader.last;
|
| - assert(instruction is HGoto || instruction is HLoopBranch);
|
| - return instruction is HGoto
|
| - || instruction.inputs[0].isConstantTrue();
|
| - }
|
| - bool firstInstructionInLoop = block == loopHeader
|
| - // Compensate for lack of code motion.
|
| - || (blockChangesFlags[loopHeader.id] == 0
|
| - && isLoopAlwaysTaken()
|
| - && loopHeader.successors[0] == block);
|
| - while (instruction != null) {
|
| - HInstruction next = instruction.next;
|
| - if (instruction.useGvn() && instruction.isMovable
|
| - && (!instruction.canThrow() || firstInstructionInLoop)
|
| - && !instruction.sideEffects.dependsOn(dependsFlags)) {
|
| - bool loopInvariantInputs = true;
|
| - List<HInstruction> inputs = instruction.inputs;
|
| - for (int i = 0, length = inputs.length; i < length; i++) {
|
| - if (isInputDefinedAfterDominator(inputs[i], preheader)) {
|
| - loopInvariantInputs = false;
|
| - break;
|
| - }
|
| - }
|
| -
|
| - // If the inputs are loop invariant, we can move the
|
| - // instruction from the current block to the pre-header block.
|
| - if (loopInvariantInputs) {
|
| - block.detach(instruction);
|
| - preheader.moveAtExit(instruction);
|
| - } else {
|
| - firstInstructionInLoop = false;
|
| - }
|
| - }
|
| - int oldChangesFlags = changesFlags;
|
| - changesFlags |= instruction.sideEffects.getChangesFlags();
|
| - if (oldChangesFlags != changesFlags) {
|
| - dependsFlags = SideEffects.computeDependsOnFlags(changesFlags);
|
| - }
|
| - instruction = next;
|
| - }
|
| - }
|
| -
|
| - bool isInputDefinedAfterDominator(HInstruction input,
|
| - HBasicBlock dominator) {
|
| - return input.block.id > dominator.id;
|
| - }
|
| -
|
| - void visitBasicBlock(
|
| - HBasicBlock block, ValueSet values, List<GvnWorkItem> workQueue) {
|
| - HInstruction instruction = block.first;
|
| - if (block.isLoopHeader()) {
|
| - int flags = loopChangesFlags[block.id];
|
| - values.kill(flags);
|
| - }
|
| - while (instruction != null) {
|
| - HInstruction next = instruction.next;
|
| - int flags = instruction.sideEffects.getChangesFlags();
|
| - assert(flags == 0 || !instruction.useGvn());
|
| - values.kill(flags);
|
| - if (instruction.useGvn()) {
|
| - HInstruction other = values.lookup(instruction);
|
| - if (other != null) {
|
| - assert(other.gvnEquals(instruction) && instruction.gvnEquals(other));
|
| - block.rewriteWithBetterUser(instruction, other);
|
| - block.remove(instruction);
|
| - } else {
|
| - values.add(instruction);
|
| - }
|
| - }
|
| - instruction = next;
|
| - }
|
| -
|
| - List<HBasicBlock> dominatedBlocks = block.dominatedBlocks;
|
| - for (int i = 0, length = dominatedBlocks.length; i < length; i++) {
|
| - HBasicBlock dominated = dominatedBlocks[i];
|
| - // No need to copy the value set for the last child.
|
| - ValueSet successorValues = (i == length - 1) ? values : values.copy();
|
| - // If we have no values in our set, we do not have to kill
|
| - // anything. Also, if the range of block ids from the current
|
| - // block to the dominated block is empty, there is no blocks on
|
| - // any path from the current block to the dominated block so we
|
| - // don't have to do anything either.
|
| - assert(block.id < dominated.id);
|
| - if (!successorValues.isEmpty && block.id + 1 < dominated.id) {
|
| - visited.clear();
|
| - List<HBasicBlock> workQueue = <HBasicBlock>[dominated];
|
| - int changesFlags = 0;
|
| - do {
|
| - HBasicBlock current = workQueue.removeLast();
|
| - changesFlags |=
|
| - getChangesFlagsForDominatedBlock(block, current, workQueue);
|
| - } while (!workQueue.isEmpty);
|
| - successorValues.kill(changesFlags);
|
| - }
|
| - workQueue.add(new GvnWorkItem(dominated, successorValues));
|
| - }
|
| - }
|
| -
|
| - void computeChangesFlags(HGraph graph) {
|
| - // Create the changes flags lists. Make sure to initialize the
|
| - // loop changes flags list to zero so we can use bitwise or when
|
| - // propagating loop changes upwards.
|
| - final int length = graph.blocks.length;
|
| - blockChangesFlags = new List<int>(length);
|
| - loopChangesFlags = new List<int>(length);
|
| - for (int i = 0; i < length; i++) loopChangesFlags[i] = 0;
|
| -
|
| - // Run through all the basic blocks in the graph and fill in the
|
| - // changes flags lists.
|
| - for (int i = length - 1; i >= 0; i--) {
|
| - final HBasicBlock block = graph.blocks[i];
|
| - final int id = block.id;
|
| -
|
| - // Compute block changes flags for the block.
|
| - int changesFlags = 0;
|
| - HInstruction instruction = block.first;
|
| - while (instruction != null) {
|
| - changesFlags |= instruction.sideEffects.getChangesFlags();
|
| - instruction = instruction.next;
|
| - }
|
| - assert(blockChangesFlags[id] == null);
|
| - blockChangesFlags[id] = changesFlags;
|
| -
|
| - // Loop headers are part of their loop, so update the loop
|
| - // changes flags accordingly.
|
| - if (block.isLoopHeader()) {
|
| - loopChangesFlags[id] |= changesFlags;
|
| - }
|
| -
|
| - // Propagate loop changes flags upwards.
|
| - HBasicBlock parentLoopHeader = block.parentLoopHeader;
|
| - if (parentLoopHeader != null) {
|
| - loopChangesFlags[parentLoopHeader.id] |= (block.isLoopHeader())
|
| - ? loopChangesFlags[id]
|
| - : changesFlags;
|
| - }
|
| - }
|
| - }
|
| -
|
| - int getChangesFlagsForDominatedBlock(HBasicBlock dominator,
|
| - HBasicBlock dominated,
|
| - List<HBasicBlock> workQueue) {
|
| - int changesFlags = 0;
|
| - List<HBasicBlock> predecessors = dominated.predecessors;
|
| - for (int i = 0, length = predecessors.length; i < length; i++) {
|
| - HBasicBlock block = predecessors[i];
|
| - int id = block.id;
|
| - // If the current predecessor block is on the path from the
|
| - // dominator to the dominated, it must have an id that is in the
|
| - // range from the dominator to the dominated.
|
| - if (dominator.id < id && id < dominated.id && !visited.contains(id)) {
|
| - visited.add(id);
|
| - changesFlags |= blockChangesFlags[id];
|
| - // Loop bodies might not be on the path from dominator to dominated,
|
| - // but they can invalidate values.
|
| - changesFlags |= loopChangesFlags[id];
|
| - workQueue.add(block);
|
| - }
|
| - }
|
| - return changesFlags;
|
| - }
|
| -}
|
| -
|
| -// This phase merges equivalent instructions on different paths into
|
| -// one instruction in a dominator block. It runs through the graph
|
| -// post dominator order and computes a ValueSet for each block of
|
| -// instructions that can be moved to a dominator block. These
|
| -// instructions are the ones that:
|
| -// 1) can be used for GVN, and
|
| -// 2) do not use definitions of their own block.
|
| -//
|
| -// A basic block looks at its sucessors and finds the intersection of
|
| -// these computed ValueSet. It moves all instructions of the
|
| -// intersection into its own list of instructions.
|
| -class SsaCodeMotion extends HBaseVisitor implements OptimizationPhase {
|
| - final String name = "SsaCodeMotion";
|
| -
|
| - List<ValueSet> values;
|
| -
|
| - void visitGraph(HGraph graph) {
|
| - values = new List<ValueSet>(graph.blocks.length);
|
| - for (int i = 0; i < graph.blocks.length; i++) {
|
| - values[graph.blocks[i].id] = new ValueSet();
|
| - }
|
| - visitPostDominatorTree(graph);
|
| - }
|
| -
|
| - void visitBasicBlock(HBasicBlock block) {
|
| - List<HBasicBlock> successors = block.successors;
|
| -
|
| - // Phase 1: get the ValueSet of all successors (if there are more than one),
|
| - // compute the intersection and move the instructions of the intersection
|
| - // into this block.
|
| - if (successors.length > 1) {
|
| - ValueSet instructions = values[successors[0].id];
|
| - for (int i = 1; i < successors.length; i++) {
|
| - ValueSet other = values[successors[i].id];
|
| - instructions = instructions.intersection(other);
|
| - }
|
| -
|
| - if (!instructions.isEmpty) {
|
| - List<HInstruction> list = instructions.toList();
|
| - for (HInstruction instruction in list) {
|
| - // Move the instruction to the current block.
|
| - instruction.block.detach(instruction);
|
| - block.moveAtExit(instruction);
|
| - // Go through all successors and rewrite their instruction
|
| - // to the shared one.
|
| - for (final successor in successors) {
|
| - HInstruction toRewrite = values[successor.id].lookup(instruction);
|
| - if (toRewrite != instruction) {
|
| - successor.rewriteWithBetterUser(toRewrite, instruction);
|
| - successor.remove(toRewrite);
|
| - }
|
| - }
|
| - }
|
| - }
|
| - }
|
| -
|
| - // Don't try to merge instructions to a dominator if we have
|
| - // multiple predecessors.
|
| - if (block.predecessors.length != 1) return;
|
| -
|
| - // Phase 2: Go through all instructions of this block and find
|
| - // which instructions can be moved to a dominator block.
|
| - ValueSet set_ = values[block.id];
|
| - HInstruction instruction = block.first;
|
| - int flags = 0;
|
| - while (instruction != null) {
|
| - int dependsFlags = SideEffects.computeDependsOnFlags(flags);
|
| - flags |= instruction.sideEffects.getChangesFlags();
|
| -
|
| - HInstruction current = instruction;
|
| - instruction = instruction.next;
|
| - if (!current.useGvn() || !current.isMovable) continue;
|
| - // TODO(sra): We could move throwing instructions provided we keep the
|
| - // exceptions in the same order. This requires they are in the same order
|
| - // in all successors, which is not tracked by the ValueSet.
|
| - if (current.canThrow()) continue;
|
| - if (current.sideEffects.dependsOn(dependsFlags)) continue;
|
| -
|
| - bool canBeMoved = true;
|
| - for (final HInstruction input in current.inputs) {
|
| - if (input.block == block) {
|
| - canBeMoved = false;
|
| - break;
|
| - }
|
| - }
|
| - if (!canBeMoved) continue;
|
| -
|
| - HInstruction existing = set_.lookup(current);
|
| - if (existing == null) {
|
| - set_.add(current);
|
| - } else {
|
| - block.rewriteWithBetterUser(current, existing);
|
| - block.remove(current);
|
| - }
|
| - }
|
| - }
|
| -}
|
| -
|
| -class SsaTypeConversionInserter extends HBaseVisitor
|
| - implements OptimizationPhase {
|
| - final String name = "SsaTypeconversionInserter";
|
| - final Compiler compiler;
|
| -
|
| - SsaTypeConversionInserter(this.compiler);
|
| -
|
| - void visitGraph(HGraph graph) {
|
| - visitDominatorTree(graph);
|
| - }
|
| -
|
| - // Update users of [input] that are dominated by [:dominator.first:]
|
| - // to use [TypeKnown] of [input] instead. As the type information depends
|
| - // on the control flow, we mark the inserted [HTypeKnown] nodes as
|
| - // non-movable.
|
| - void insertTypePropagationForDominatedUsers(HBasicBlock dominator,
|
| - HInstruction input,
|
| - TypeMask convertedType) {
|
| - Setlet<HInstruction> dominatedUsers = input.dominatedUsers(dominator.first);
|
| - if (dominatedUsers.isEmpty) return;
|
| -
|
| - HTypeKnown newInput = new HTypeKnown.pinned(convertedType, input);
|
| - dominator.addBefore(dominator.first, newInput);
|
| - dominatedUsers.forEach((HInstruction user) {
|
| - user.changeUse(input, newInput);
|
| - });
|
| - }
|
| -
|
| - void visitIs(HIs instruction) {
|
| - DartType type = instruction.typeExpression;
|
| - Element element = type.element;
|
| - if (!instruction.isRawCheck) {
|
| - return;
|
| - } else if (element.isTypedef) {
|
| - return;
|
| - }
|
| -
|
| - List<HInstruction> ifUsers = <HInstruction>[];
|
| - List<HInstruction> notIfUsers = <HInstruction>[];
|
| -
|
| - collectIfUsers(instruction, ifUsers, notIfUsers);
|
| -
|
| - if (ifUsers.isEmpty && notIfUsers.isEmpty) return;
|
| -
|
| - TypeMask convertedType =
|
| - new TypeMask.nonNullSubtype(element, compiler.world);
|
| - HInstruction input = instruction.expression;
|
| -
|
| - for (HIf ifUser in ifUsers) {
|
| - insertTypePropagationForDominatedUsers(ifUser.thenBlock, input,
|
| - convertedType);
|
| - // TODO(ngeoffray): Also change uses for the else block on a type
|
| - // that knows it is not of a specific type.
|
| - }
|
| - for (HIf ifUser in notIfUsers) {
|
| - insertTypePropagationForDominatedUsers(ifUser.elseBlock, input,
|
| - convertedType);
|
| - // TODO(ngeoffray): Also change uses for the then block on a type
|
| - // that knows it is not of a specific type.
|
| - }
|
| - }
|
| -
|
| - void visitIdentity(HIdentity instruction) {
|
| - // At HIf(HIdentity(x, null)) strengthens x to non-null on else branch.
|
| - HInstruction left = instruction.left;
|
| - HInstruction right = instruction.right;
|
| - HInstruction input;
|
| -
|
| - if (left.isConstantNull()) {
|
| - input = right;
|
| - } else if (right.isConstantNull()) {
|
| - input = left;
|
| - } else {
|
| - return;
|
| - }
|
| -
|
| - if (!input.instructionType.isNullable) return;
|
| -
|
| - List<HInstruction> ifUsers = <HInstruction>[];
|
| - List<HInstruction> notIfUsers = <HInstruction>[];
|
| -
|
| - collectIfUsers(instruction, ifUsers, notIfUsers);
|
| -
|
| - if (ifUsers.isEmpty && notIfUsers.isEmpty) return;
|
| -
|
| - TypeMask nonNullType = input.instructionType.nonNullable();
|
| -
|
| - for (HIf ifUser in ifUsers) {
|
| - insertTypePropagationForDominatedUsers(ifUser.elseBlock, input,
|
| - nonNullType);
|
| - // Uses in thenBlock are `null`, but probably not common.
|
| - }
|
| - for (HIf ifUser in notIfUsers) {
|
| - insertTypePropagationForDominatedUsers(ifUser.thenBlock, input,
|
| - nonNullType);
|
| - // Uses in elseBlock are `null`, but probably not common.
|
| - }
|
| - }
|
| -
|
| - collectIfUsers(HInstruction instruction,
|
| - List<HInstruction> ifUsers,
|
| - List<HInstruction> notIfUsers) {
|
| - for (HInstruction user in instruction.usedBy) {
|
| - if (user is HIf) {
|
| - ifUsers.add(user);
|
| - } else if (user is HNot) {
|
| - collectIfUsers(user, notIfUsers, ifUsers);
|
| - }
|
| - }
|
| - }
|
| -}
|
| -
|
| -/**
|
| - * Optimization phase that tries to eliminate memory loads (for
|
| - * example [HFieldGet]), when it knows the value stored in that memory
|
| - * location.
|
| - */
|
| -class SsaLoadElimination extends HBaseVisitor implements OptimizationPhase {
|
| - final Compiler compiler;
|
| - final String name = "SsaLoadElimination";
|
| - MemorySet memorySet;
|
| - List<MemorySet> memories;
|
| -
|
| - SsaLoadElimination(this.compiler);
|
| -
|
| - void visitGraph(HGraph graph) {
|
| - memories = new List<MemorySet>(graph.blocks.length);
|
| - List<HBasicBlock> blocks = graph.blocks;
|
| - for (int i = 0; i < blocks.length; i++) {
|
| - HBasicBlock block = blocks[i];
|
| - visitBasicBlock(block);
|
| - if (block.successors.isNotEmpty && block.successors[0].isLoopHeader()) {
|
| - // We've reached the ending block of a loop. Iterate over the
|
| - // blocks of the loop again to take values that flow from that
|
| - // ending block into account.
|
| - for (int j = block.successors[0].id; j <= block.id; j++) {
|
| - visitBasicBlock(blocks[j]);
|
| - }
|
| - }
|
| - }
|
| - }
|
| -
|
| - void visitBasicBlock(HBasicBlock block) {
|
| - if (block.predecessors.length == 0) {
|
| - // Entry block.
|
| - memorySet = new MemorySet(compiler);
|
| - } else if (block.predecessors.length == 1
|
| - && block.predecessors[0].successors.length == 1) {
|
| - // No need to clone, there is no other successor for
|
| - // `block.predecessors[0]`, and this block has only one
|
| - // predecessor. Since we are not going to visit
|
| - // `block.predecessors[0]` again, we can just re-use its
|
| - // [memorySet].
|
| - memorySet = memories[block.predecessors[0].id];
|
| - } else if (block.predecessors.length == 1) {
|
| - // Clone the memorySet of the predecessor, because it is also used
|
| - // by other successors of it.
|
| - memorySet = memories[block.predecessors[0].id].clone();
|
| - } else {
|
| - // Compute the intersection of all predecessors.
|
| - memorySet = memories[block.predecessors[0].id];
|
| - for (int i = 1; i < block.predecessors.length; i++) {
|
| - memorySet = memorySet.intersectionFor(
|
| - memories[block.predecessors[i].id], block, i);
|
| - }
|
| - }
|
| -
|
| - memories[block.id] = memorySet;
|
| - HInstruction instruction = block.first;
|
| - while (instruction != null) {
|
| - HInstruction next = instruction.next;
|
| - instruction.accept(this);
|
| - instruction = next;
|
| - }
|
| - }
|
| -
|
| - void visitFieldGet(HFieldGet instruction) {
|
| - if (instruction.isNullCheck) return;
|
| - Element element = instruction.element;
|
| - HInstruction receiver =
|
| - instruction.getDartReceiver(compiler).nonCheck();
|
| - HInstruction existing = memorySet.lookupFieldValue(element, receiver);
|
| - if (existing != null) {
|
| - instruction.block.rewriteWithBetterUser(instruction, existing);
|
| - instruction.block.remove(instruction);
|
| - } else {
|
| - memorySet.registerFieldValue(element, receiver, instruction);
|
| - }
|
| - }
|
| -
|
| - void visitFieldSet(HFieldSet instruction) {
|
| - HInstruction receiver =
|
| - instruction.getDartReceiver(compiler).nonCheck();
|
| - memorySet.registerFieldValueUpdate(
|
| - instruction.element, receiver, instruction.inputs.last);
|
| - }
|
| -
|
| - void visitForeignNew(HForeignNew instruction) {
|
| - memorySet.registerAllocation(instruction);
|
| - int argumentIndex = 0;
|
| - instruction.element.forEachInstanceField((_, Element member) {
|
| - memorySet.registerFieldValue(
|
| - member, instruction, instruction.inputs[argumentIndex++]);
|
| - }, includeSuperAndInjectedMembers: true);
|
| - // In case this instruction has as input non-escaping objects, we
|
| - // need to mark these objects as escaping.
|
| - memorySet.killAffectedBy(instruction);
|
| - }
|
| -
|
| - void visitInstruction(HInstruction instruction) {
|
| - memorySet.killAffectedBy(instruction);
|
| - }
|
| -
|
| - void visitLazyStatic(HLazyStatic instruction) {
|
| - handleStaticLoad(instruction.element, instruction);
|
| - }
|
| -
|
| - void handleStaticLoad(Element element, HInstruction instruction) {
|
| - HInstruction existing = memorySet.lookupFieldValue(element, null);
|
| - if (existing != null) {
|
| - instruction.block.rewriteWithBetterUser(instruction, existing);
|
| - instruction.block.remove(instruction);
|
| - } else {
|
| - memorySet.registerFieldValue(element, null, instruction);
|
| - }
|
| - }
|
| -
|
| - void visitStatic(HStatic instruction) {
|
| - handleStaticLoad(instruction.element, instruction);
|
| - }
|
| -
|
| - void visitStaticStore(HStaticStore instruction) {
|
| - memorySet.registerFieldValueUpdate(
|
| - instruction.element, null, instruction.inputs.last);
|
| - }
|
| -
|
| - void visitLiteralList(HLiteralList instruction) {
|
| - memorySet.registerAllocation(instruction);
|
| - memorySet.killAffectedBy(instruction);
|
| - }
|
| -
|
| - void visitIndex(HIndex instruction) {
|
| - HInstruction receiver = instruction.receiver.nonCheck();
|
| - HInstruction existing =
|
| - memorySet.lookupKeyedValue(receiver, instruction.index);
|
| - if (existing != null) {
|
| - instruction.block.rewriteWithBetterUser(instruction, existing);
|
| - instruction.block.remove(instruction);
|
| - } else {
|
| - memorySet.registerKeyedValue(receiver, instruction.index, instruction);
|
| - }
|
| - }
|
| -
|
| - void visitIndexAssign(HIndexAssign instruction) {
|
| - HInstruction receiver = instruction.receiver.nonCheck();
|
| - memorySet.registerKeyedValueUpdate(
|
| - receiver, instruction.index, instruction.value);
|
| - }
|
| -}
|
| -
|
| -/**
|
| - * Holds values of memory places.
|
| - */
|
| -class MemorySet {
|
| - final Compiler compiler;
|
| -
|
| - /**
|
| - * Maps a field to a map of receiver to value.
|
| - */
|
| - final Map<Element, Map<HInstruction, HInstruction>> fieldValues =
|
| - <Element, Map<HInstruction, HInstruction>> {};
|
| -
|
| - /**
|
| - * Maps a receiver to a map of keys to value.
|
| - */
|
| - final Map<HInstruction, Map<HInstruction, HInstruction>> keyedValues =
|
| - <HInstruction, Map<HInstruction, HInstruction>> {};
|
| -
|
| - /**
|
| - * Set of objects that we know don't escape the current function.
|
| - */
|
| - final Setlet<HInstruction> nonEscapingReceivers = new Setlet<HInstruction>();
|
| -
|
| - MemorySet(this.compiler);
|
| -
|
| - /**
|
| - * Returns whether [first] and [second] always alias to the same object.
|
| - */
|
| - bool mustAlias(HInstruction first, HInstruction second) {
|
| - return first == second;
|
| - }
|
| -
|
| - /**
|
| - * Returns whether [first] and [second] may alias to the same object.
|
| - */
|
| - bool mayAlias(HInstruction first, HInstruction second) {
|
| - if (mustAlias(first, second)) return true;
|
| - if (isConcrete(first) && isConcrete(second)) return false;
|
| - if (nonEscapingReceivers.contains(first)) return false;
|
| - if (nonEscapingReceivers.contains(second)) return false;
|
| - // Typed arrays of different types might have a shared buffer.
|
| - if (couldBeTypedArray(first) && couldBeTypedArray(second)) return true;
|
| - TypeMask intersection = first.instructionType.intersection(
|
| - second.instructionType, compiler.world);
|
| - if (intersection.isEmpty) return false;
|
| - return true;
|
| - }
|
| -
|
| - bool isFinal(Element element) {
|
| - return compiler.world.fieldNeverChanges(element);
|
| - }
|
| -
|
| - bool isConcrete(HInstruction instruction) {
|
| - return instruction is HForeignNew
|
| - || instruction is HConstant
|
| - || instruction is HLiteralList;
|
| - }
|
| -
|
| - bool couldBeTypedArray(HInstruction receiver) {
|
| - JavaScriptBackend backend = compiler.backend;
|
| - return backend.couldBeTypedArray(receiver.instructionType);
|
| - }
|
| -
|
| - /**
|
| - * Returns whether [receiver] escapes the current function.
|
| - */
|
| - bool escapes(HInstruction receiver) {
|
| - return !nonEscapingReceivers.contains(receiver);
|
| - }
|
| -
|
| - void registerAllocation(HInstruction instruction) {
|
| - nonEscapingReceivers.add(instruction);
|
| - }
|
| -
|
| - /**
|
| - * Sets `receiver.element` to contain [value]. Kills all potential
|
| - * places that may be affected by this update.
|
| - */
|
| - void registerFieldValueUpdate(Element element,
|
| - HInstruction receiver,
|
| - HInstruction value) {
|
| - if (element.isNative) return; // TODO(14955): Remove this restriction?
|
| - // [value] is being set in some place in memory, we remove it from
|
| - // the non-escaping set.
|
| - nonEscapingReceivers.remove(value);
|
| - Map<HInstruction, HInstruction> map = fieldValues.putIfAbsent(
|
| - element, () => <HInstruction, HInstruction> {});
|
| - map.forEach((key, value) {
|
| - if (mayAlias(receiver, key)) map[key] = null;
|
| - });
|
| - map[receiver] = value;
|
| - }
|
| -
|
| - /**
|
| - * Registers that `receiver.element` is now [value].
|
| - */
|
| - void registerFieldValue(Element element,
|
| - HInstruction receiver,
|
| - HInstruction value) {
|
| - if (element.isNative) return; // TODO(14955): Remove this restriction?
|
| - Map<HInstruction, HInstruction> map = fieldValues.putIfAbsent(
|
| - element, () => <HInstruction, HInstruction> {});
|
| - map[receiver] = value;
|
| - }
|
| -
|
| - /**
|
| - * Returns the value stored in `receiver.element`. Returns null if
|
| - * we don't know.
|
| - */
|
| - HInstruction lookupFieldValue(Element element, HInstruction receiver) {
|
| - Map<HInstruction, HInstruction> map = fieldValues[element];
|
| - return (map == null) ? null : map[receiver];
|
| - }
|
| -
|
| - /**
|
| - * Kill all places that may be affected by this [instruction]. Also
|
| - * update the set of non-escaping objects in case [instruction] has
|
| - * non-escaping objects in its inputs.
|
| - */
|
| - void killAffectedBy(HInstruction instruction) {
|
| - // Even if [instruction] does not have side effects, it may use
|
| - // non-escaping objects and store them in a new object, which
|
| - // make these objects escaping.
|
| - // TODO(ngeoffray): We need a new side effect flag to know whether
|
| - // an instruction allocates an object.
|
| - instruction.inputs.forEach((input) {
|
| - nonEscapingReceivers.remove(input);
|
| - });
|
| -
|
| - if (instruction.sideEffects.changesInstanceProperty()
|
| - || instruction.sideEffects.changesStaticProperty()) {
|
| - fieldValues.forEach((element, map) {
|
| - if (isFinal(element)) return;
|
| - map.forEach((receiver, value) {
|
| - if (escapes(receiver)) {
|
| - map[receiver] = null;
|
| - }
|
| - });
|
| - });
|
| - }
|
| -
|
| - if (instruction.sideEffects.changesIndex()) {
|
| - keyedValues.forEach((receiver, map) {
|
| - if (escapes(receiver)) {
|
| - map.forEach((index, value) {
|
| - map[index] = null;
|
| - });
|
| - }
|
| - });
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Returns the value stored in `receiver[index]`. Returns null if
|
| - * we don't know.
|
| - */
|
| - HInstruction lookupKeyedValue(HInstruction receiver, HInstruction index) {
|
| - Map<HInstruction, HInstruction> map = keyedValues[receiver];
|
| - return (map == null) ? null : map[index];
|
| - }
|
| -
|
| - /**
|
| - * Registers that `receiver[index]` is now [value].
|
| - */
|
| - void registerKeyedValue(HInstruction receiver,
|
| - HInstruction index,
|
| - HInstruction value) {
|
| - Map<HInstruction, HInstruction> map = keyedValues.putIfAbsent(
|
| - receiver, () => <HInstruction, HInstruction> {});
|
| - map[index] = value;
|
| - }
|
| -
|
| - /**
|
| - * Sets `receiver[index]` to contain [value]. Kills all potential
|
| - * places that may be affected by this update.
|
| - */
|
| - void registerKeyedValueUpdate(HInstruction receiver,
|
| - HInstruction index,
|
| - HInstruction value) {
|
| - nonEscapingReceivers.remove(value);
|
| - keyedValues.forEach((key, values) {
|
| - if (mayAlias(receiver, key)) {
|
| - // Typed arrays that are views of the same buffer may have different
|
| - // offsets or element sizes, unless they are the same typed array.
|
| - bool weakIndex = couldBeTypedArray(key) && !mustAlias(receiver, key);
|
| - values.forEach((otherIndex, otherValue) {
|
| - if (weakIndex || mayAlias(index, otherIndex)) {
|
| - values[otherIndex] = null;
|
| - }
|
| - });
|
| - }
|
| - });
|
| -
|
| - // Typed arrays may narrow incoming values.
|
| - if (couldBeTypedArray(receiver)) return;
|
| -
|
| - Map<HInstruction, HInstruction> map = keyedValues.putIfAbsent(
|
| - receiver, () => <HInstruction, HInstruction> {});
|
| - map[index] = value;
|
| - }
|
| -
|
| - /**
|
| - * Returns null if either [first] or [second] is null. Otherwise
|
| - * returns [first] if [first] and [second] are equal. Otherwise
|
| - * creates or re-uses a phi in [block] that holds [first] and [second].
|
| - */
|
| - HInstruction findCommonInstruction(HInstruction first,
|
| - HInstruction second,
|
| - HBasicBlock block,
|
| - int predecessorIndex) {
|
| - if (first == null || second == null) return null;
|
| - if (first == second) return first;
|
| - TypeMask phiType = second.instructionType.union(
|
| - first.instructionType, compiler.world);
|
| - if (first is HPhi && first.block == block) {
|
| - HPhi phi = first;
|
| - phi.addInput(second);
|
| - phi.instructionType = phiType;
|
| - return phi;
|
| - } else {
|
| - HPhi phi = new HPhi.noInputs(null, phiType);
|
| - block.addPhi(phi);
|
| - // Previous predecessors had the same input. A phi must have
|
| - // the same number of inputs as its block has predecessors.
|
| - for (int i = 0; i < predecessorIndex; i++) {
|
| - phi.addInput(first);
|
| - }
|
| - phi.addInput(second);
|
| - return phi;
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Returns the intersection between [this] and [other].
|
| - */
|
| - MemorySet intersectionFor(MemorySet other,
|
| - HBasicBlock block,
|
| - int predecessorIndex) {
|
| - MemorySet result = new MemorySet(compiler);
|
| - if (other == null) return result;
|
| -
|
| - fieldValues.forEach((element, values) {
|
| - var otherValues = other.fieldValues[element];
|
| - if (otherValues == null) return;
|
| - values.forEach((receiver, value) {
|
| - HInstruction instruction = findCommonInstruction(
|
| - value, otherValues[receiver], block, predecessorIndex);
|
| - if (instruction != null) {
|
| - result.registerFieldValue(element, receiver, instruction);
|
| - }
|
| - });
|
| - });
|
| -
|
| - keyedValues.forEach((receiver, values) {
|
| - var otherValues = other.keyedValues[receiver];
|
| - if (otherValues == null) return;
|
| - values.forEach((index, value) {
|
| - HInstruction instruction = findCommonInstruction(
|
| - value, otherValues[index], block, predecessorIndex);
|
| - if (instruction != null) {
|
| - result.registerKeyedValue(receiver, index, instruction);
|
| - }
|
| - });
|
| - });
|
| -
|
| - nonEscapingReceivers.forEach((receiver) {
|
| - if (other.nonEscapingReceivers.contains(receiver)) {
|
| - result.nonEscapingReceivers.add(receiver);
|
| - }
|
| - });
|
| - return result;
|
| - }
|
| -
|
| - /**
|
| - * Returns a copy of [this].
|
| - */
|
| - MemorySet clone() {
|
| - MemorySet result = new MemorySet(compiler);
|
| -
|
| - fieldValues.forEach((element, values) {
|
| - result.fieldValues[element] =
|
| - new Map<HInstruction, HInstruction>.from(values);
|
| - });
|
| -
|
| - keyedValues.forEach((receiver, values) {
|
| - result.keyedValues[receiver] =
|
| - new Map<HInstruction, HInstruction>.from(values);
|
| - });
|
| -
|
| - result.nonEscapingReceivers.addAll(nonEscapingReceivers);
|
| - return result;
|
| - }
|
| -}
|
|
|