| Index: lib/compiler/implementation/ssa/optimize.dart
|
| diff --git a/lib/compiler/implementation/ssa/optimize.dart b/lib/compiler/implementation/ssa/optimize.dart
|
| index b019498dbf3ad920d500bb87f646ccb58298080c..568355aefd170c4d6c4218b21cee18ef629cc081 100644
|
| --- a/lib/compiler/implementation/ssa/optimize.dart
|
| +++ b/lib/compiler/implementation/ssa/optimize.dart
|
| @@ -26,7 +26,7 @@ class SsaOptimizerTask extends CompilerTask {
|
| compiler.tracer.traceGraph(phase.name, graph);
|
| }
|
|
|
| - void optimize(WorkItem work, HGraph graph) {
|
| + void optimize(WorkItem work, HGraph graph, bool speculative) {
|
| ConstantSystem constantSystem = compiler.backend.constantSystem;
|
| JavaScriptItemCompilationContext context = work.compilationContext;
|
| HTypeMap types = context.types;
|
| @@ -46,9 +46,11 @@ class SsaOptimizerTask extends CompilerTask {
|
| // Previous optimizations may have generated new
|
| // opportunities for constant folding.
|
| new SsaConstantFolder(constantSystem, backend, work, types),
|
| - new SsaDeadCodeEliminator(types),
|
| - new SsaRegisterRecompilationCandidates(backend, work, types)];
|
| + new SsaDeadCodeEliminator(types)];
|
| runPhases(graph, phases);
|
| + if (!speculative) {
|
| + runPhase(graph, new SsaConstructionFieldTypes(backend, work, types));
|
| + }
|
| });
|
| }
|
|
|
| @@ -62,7 +64,6 @@ class SsaOptimizerTask extends CompilerTask {
|
| return measure(() {
|
| // Run the phases that will generate type guards.
|
| List<OptimizationPhase> phases = <OptimizationPhase>[
|
| - new SsaRecompilationFieldTypePropagator(backend, work, types),
|
| new SsaSpeculativeTypePropagator(compiler, types),
|
| new SsaTypeGuardInserter(compiler, work, types),
|
| new SsaEnvironmentBuilder(compiler),
|
| @@ -612,23 +613,15 @@ class SsaConstantFolder extends HBaseVisitor implements OptimizationPhase {
|
| // initializer list.
|
| isFinalOrConst = true;
|
| }
|
| - if (!isFinalOrConst &&
|
| - !compiler.codegenWorld.hasInvokedSetter(field, compiler) &&
|
| - !compiler.codegenWorld.hasFieldSetter(field, compiler)) {
|
| - switch (compiler.phase) {
|
| - case Compiler.PHASE_COMPILING:
|
| - compiler.enqueuer.codegen.registerRecompilationCandidate(
|
| - work.element);
|
| - break;
|
| - case Compiler.PHASE_RECOMPILING:
|
| - // If field is not final or const but no setters are used then the
|
| - // field might be considered final anyway as it will be either
|
| - // un-initialized or initialized in the constructor initializer list.
|
| - isFinalOrConst = true;
|
| - break;
|
| - }
|
| + HFieldGet result = new HFieldGet(
|
| + field, node.inputs[0], isAssignable: !isFinalOrConst);
|
| + HType type = backend.optimisticFieldType(field);
|
| + if (type != null) {
|
| + result.guaranteedType = type;
|
| + backend.registerFieldTypesOptimization(
|
| + work.element, field, result.guaranteedType);
|
| }
|
| - return new HFieldGet(field, node.inputs[0], isAssignable: !isFinalOrConst);
|
| + return result;
|
| }
|
|
|
| HInstruction visitInvokeDynamicSetter(HInvokeDynamicSetter node) {
|
| @@ -1220,159 +1213,146 @@ class SsaTypeConversionInserter extends HBaseVisitor
|
| }
|
|
|
|
|
| -// Base class for the handling of recompilation based on inferred
|
| -// field types.
|
| -class BaseRecompilationVisitor extends HBaseVisitor {
|
| +// Analyze the constructors to see if some fields will always have a specific
|
| +// type after construction. If this is the case we can ignore the type given
|
| +// by the field initializer. This is especially useful when the field
|
| +// initializer is initializing the field to null.
|
| +class SsaConstructionFieldTypes
|
| + extends HBaseVisitor implements OptimizationPhase {
|
| final JavaScriptBackend backend;
|
| final WorkItem work;
|
| final HTypeMap types;
|
| - Compiler get compiler => backend.compiler;
|
| + final String name = "SsaConstructionFieldTypes";
|
| + final Set<HInstruction> thisUsers;
|
| + final Set<Element> allSetters;
|
| + final Map<HBasicBlock, Map<Element, HType>> blockFieldSetters;
|
| + bool thisExposed = false;
|
| + HGraph currentGraph;
|
| + HInstruction thisInstruction;
|
| + Map<Element, HType> currentFieldSetters;
|
| +
|
| + SsaConstructionFieldTypes(JavaScriptBackend this.backend,
|
| + WorkItem this.work,
|
| + HTypeMap this.types)
|
| + : thisUsers = new Set<HInstruction>(),
|
| + allSetters = new Set<Element>(),
|
| + blockFieldSetters = new Map<HBasicBlock, Map<Element, HType>>();
|
|
|
| - BaseRecompilationVisitor(this.backend, this.work, this.types);
|
| -
|
| - abstract void handleFieldGet(HFieldGet node, HType type);
|
| - abstract void handleFieldNumberOperation(HFieldGet field, HType type);
|
| -
|
| - // Checks if the binary invocation operates on a field and a
|
| - // constant number. If it does [handleFieldNumberOperation] is
|
| - // called with the field and the type inferred for the field so far.
|
| - void checkFieldNumberOperation(HInvokeBinary node) {
|
| - // Determine if one of the operands is an HFieldGet.
|
| - HFieldGet field;
|
| - HInstruction other;
|
| - if (node.left is HFieldGet) {
|
| - field = node.left;
|
| - other = node.right;
|
| - } else if (node.right is HFieldGet) {
|
| - field = node.right;
|
| - other = node.left;
|
| - }
|
| - // Try to optimize the case where a field which is known to always
|
| - // be an integer is compared with a constant number.
|
| - if (other != null &&
|
| - other.isConstantNumber() &&
|
| - field.element != null &&
|
| - field.element.isMember()) {
|
| - // Calculate the field type from the information available. If
|
| - // we have type information for the field and it contains NUMBER
|
| - // we use it as a candidate for recompilation.
|
| - Element fieldElement = field.element;
|
| - HType fieldSettersType = backend.fieldSettersTypeSoFar(fieldElement);
|
| - HType initializersType = backend.typeFromInitializersSoFar(fieldElement);
|
| - HType fieldType = fieldSettersType.union(initializersType);
|
| - HType type = HType.NUMBER.union(fieldType);
|
| - if (type == HType.NUMBER) {
|
| - handleFieldNumberOperation(field, fieldType);
|
| - }
|
| + void visitGraph(HGraph graph) {
|
| + currentGraph = graph;
|
| + if (!work.element.isGenerativeConstructorBody() &&
|
| + !work.element.isGenerativeConstructor()) return;
|
| + visitDominatorTree(graph);
|
| + if (work.element.isGenerativeConstructor()) {
|
| + backend.registerConstructor(work.element);
|
| }
|
| }
|
|
|
| - void visitFieldGet(HFieldGet node) {
|
| - if (!node.element.isInstanceMember()) return;
|
| - Element field = node.element;
|
| - if (field != null) {
|
| - HType type = backend.optimisticFieldTypeAfterConstruction(field);
|
| - if (!type.isUnknown()) {
|
| - // Allow handling even if we haven't seen any types for this
|
| - // field yet. There might still be only one setter in an
|
| - // initializer list or constructor body and recompilation
|
| - // can therefore pay off.
|
| - handleFieldGet(node, type);
|
| + visitBasicBlock(HBasicBlock block) {
|
| + if (block.predecessors.length == 0) {
|
| + // Create a new empty map for the first block.
|
| + currentFieldSetters = new Map<Element, HType>();
|
| + } else {
|
| + // Build a map which intersects the fields from all predecessors. For
|
| + // each field in this intersection it unions the types.
|
| + currentFieldSetters =
|
| + new Map.from(blockFieldSetters[block.predecessors[0]]);
|
| + // Loop headers are the only nodes with back edges.
|
| + if (!block.isLoopHeader()) {
|
| + for (int i = 1; i < block.predecessors.length; i++) {
|
| + Map<Element, HType> predecessorsFieldSetters =
|
| + blockFieldSetters[block.predecessors[i]];
|
| + Map<Element, HType> newFieldSetters = new Map<Element, HType>();
|
| + predecessorsFieldSetters.forEach((Element element, HType type) {
|
| + HType currentType = currentFieldSetters[element];
|
| + if (currentType != null) {
|
| + newFieldSetters[element] = currentType.union(type);
|
| + }
|
| + });
|
| + currentFieldSetters = newFieldSetters;
|
| + }
|
| + } else {
|
| + assert(block.predecessors.length <= 2);
|
| }
|
| }
|
| + block.forEachPhi((HPhi phi) => phi.accept(this));
|
| + block.forEachInstruction(
|
| + (HInstruction instruction) => instruction.accept(this));
|
| + assert(currentFieldSetters != null);
|
| + blockFieldSetters[block] = currentFieldSetters;
|
| }
|
|
|
| - HInstruction visitEquals(HEquals node) {
|
| - checkFieldNumberOperation(node);
|
| - }
|
| -
|
| - HInstruction visitBinaryArithmetic(HBinaryArithmetic node) {
|
| - checkFieldNumberOperation(node);
|
| + visitInstruction(HInstruction instruction) {
|
| + // All instructions not explicitly handled below will flag the this
|
| + // exposure if using this.
|
| + thisExposed = thisExposed || thisUsers.contains(instruction);
|
| }
|
| -}
|
| -
|
| -
|
| -// Visitor that registers candidates for recompilation.
|
| -class SsaRegisterRecompilationCandidates
|
| - extends BaseRecompilationVisitor implements OptimizationPhase {
|
| - final String name = "SsaRegisterRecompileCandidates";
|
| - HGraph graph;
|
| -
|
| - SsaRegisterRecompilationCandidates(JavaScriptBackend backend,
|
| - WorkItem work,
|
| - HTypeMap types)
|
| - : super(backend, work, types);
|
|
|
| - void visitGraph(HGraph visitee) {
|
| - graph = visitee;
|
| - if (compiler.phase == Compiler.PHASE_COMPILING) {
|
| - visitDominatorTree(visitee);
|
| + visitPhi(HPhi phi) {
|
| + if (phi.inputs.indexOf(thisInstruction) != -1) {
|
| + thisUsers.addAll(phi.usedBy);
|
| }
|
| }
|
|
|
| - void handleFieldGet(HFieldGet node, HType type) {
|
| - assert(compiler.phase == Compiler.PHASE_COMPILING);
|
| - compiler.enqueuer.codegen.registerRecompilationCandidate(
|
| - work.element);
|
| + visitThis(HThis instruction) {
|
| + thisInstruction = instruction;
|
| + // Collect all users of this in a set to make the this exposed check simple
|
| + // and cheap.
|
| + thisUsers.addAll(instruction.usedBy);
|
| }
|
|
|
| - void handleFieldNumberOperation(HFieldGet node, HType type) {
|
| - assert(compiler.phase == Compiler.PHASE_COMPILING);
|
| - compiler.enqueuer.codegen.registerRecompilationCandidate(
|
| - work.element);
|
| + visitFieldGet(HInstruction _) {
|
| + // The field get instruction is allowed to use this.
|
| }
|
| -}
|
| -
|
|
|
| -// Visitor that sets the known or suspected type of fields during
|
| -// recompilation.
|
| -class SsaRecompilationFieldTypePropagator
|
| - extends BaseRecompilationVisitor implements OptimizationPhase {
|
| - final String name = "SsaRecompilationFieldTypePropagator";
|
| - HGraph graph;
|
| -
|
| - SsaRecompilationFieldTypePropagator(JavaScriptBackend backend,
|
| - WorkItem work,
|
| - HTypeMap types)
|
| - : super(backend, work, types);
|
| -
|
| - void visitGraph(HGraph visitee) {
|
| - graph = visitee;
|
| - if (compiler.phase == Compiler.PHASE_RECOMPILING) {
|
| - visitDominatorTree(visitee);
|
| - }
|
| - }
|
| -
|
| - void handleFieldGet(HFieldGet field, HType type) {
|
| - assert(compiler.phase == Compiler.PHASE_RECOMPILING);
|
| - if (!type.isConflicting()) {
|
| - // If there are no invoked setters with this name, the union of
|
| - // the types of the initializers and the setters is guaranteed
|
| - // otherwise it is only speculative.
|
| - Element element = field.element;
|
| - assert(!element.isGenerativeConstructorBody());
|
| - if (!compiler.codegenWorld.hasInvokedSetter(element, compiler)) {
|
| - field.guaranteedType =
|
| - type.union(backend.fieldSettersTypeSoFar(element));
|
| - } else {
|
| - types[field] = type.union(backend.fieldSettersTypeSoFar(element));
|
| - }
|
| - }
|
| + visitForeignNew(HForeignNew node) {
|
| + // The HForeignNew instruction is used in the generative constructor to
|
| + // initialize all fields in newly created objects. The fields are
|
| + // initialized to the value present in the initializer list or set to null
|
| + // if not otherwise initialized.
|
| + // Here we handle members in superclasses as well, as the handling of
|
| + // the generative constructor bodies will ensure, that the initializer
|
| + // type will not be used if the field is in any of these.
|
| + int j = 0;
|
| + node.element.forEachInstanceField(
|
| + includeBackendMembers: false,
|
| + includeSuperMembers: true,
|
| + f: (ClassElement enclosingClass, Element element) {
|
| + backend.registerFieldInitializer(element, types[node.inputs[j]]);
|
| + j++;
|
| + });
|
| }
|
|
|
| - void handleFieldNumberOperation(HFieldGet field, HType type) {
|
| - assert(compiler.phase == Compiler.PHASE_RECOMPILING);
|
| - if (compiler.codegenWorld.hasInvokedSetter(field.element, compiler)) {
|
| - // If there are invoked setters we don't know for sure
|
| - // that the field will hold a value of the calculated
|
| - // type, but the fact that the class itself sticks to
|
| - // this type for the field is still a strong signal
|
| - // indicating the expected type of the field.
|
| - types[field] = type;
|
| - } else {
|
| - // If there are no invoked setters we know the type of
|
| - // this field for sure.
|
| - field.guaranteedType = type;
|
| + visitFieldSet(HFieldSet node) {
|
| + Element field = node.element;
|
| + HInstruction value = node.value;
|
| + HType type = types[value];
|
| + allSetters.add(field);
|
| + // Don't handle fields defined in superclasses. Given that the field is
|
| + // always added to the [allSetters] set, setting a field defined in a
|
| + // superclass will get an inferred type of UNKNOWN.
|
| + if (work.element.getEnclosingClass() === field.getEnclosingClass() &&
|
| + value.hasGuaranteedType()) {
|
| + currentFieldSetters[field] = type;
|
| + }
|
| + }
|
| +
|
| + visitExit(HExit node) {
|
| + // If this has been exposed then we cannot say anything about types after
|
| + // construction.
|
| + if (!thisExposed) {
|
| + // Register the known field types.
|
| + currentFieldSetters.forEach((Element element, HType type) {
|
| + backend.registerFieldConstructor(element, type);
|
| + allSetters.remove(element);
|
| + });
|
| }
|
| +
|
| + // For other fields having setters in the generative constructor body, set
|
| + // the type to UNKNOWN to avoid relying on the type set in the initializer
|
| + // list.
|
| + allSetters.forEach((Element element) {
|
| + backend.registerFieldConstructor(element, HType.UNKNOWN);
|
| + });
|
| }
|
| }
|
|
|