blob: 36d6f68c1948a060bd165691c14f3ac1bd1e0389 [file] [log] [blame]
// Copyright (c) 2011, 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.
interface OptimizationPhase {
String get name();
void visitGraph(HGraph graph);
}
class SsaOptimizerTask extends CompilerTask {
SsaOptimizerTask(Compiler compiler) : super(compiler);
String get name() => 'SSA optimizer';
void runPhases(HGraph graph, List<OptimizationPhase> phases) {
for (OptimizationPhase phase in phases) {
phase.visitGraph(graph);
compiler.tracer.traceGraph(phase.name, graph);
}
}
void optimize(WorkItem work, HGraph graph) {
measure(() {
List<OptimizationPhase> phases = <OptimizationPhase>[
new SsaConstantFolder(compiler),
new SsaRedundantPhiEliminator(),
new SsaDeadPhiEliminator(),
new SsaGlobalValueNumberer(compiler),
new SsaCodeMotion(),
new SsaDeadCodeEliminator()];
runPhases(graph, phases);
});
}
bool trySpeculativeOptimizations(WorkItem work, HGraph graph) {
return measure(() {
// Run the phases that will generate type guards. We must also run
// [SsaCheckInserter] because the type propagator also propagates
// types non-speculatively. For example, it propagates the type
// array for a call to the List constructor.
List<OptimizationPhase> phases = <OptimizationPhase>[
new SsaTypePropagator(compiler),
new SsaTypeGuardBuilder(compiler, work),
new SsaCheckInserter(compiler)];
runPhases(graph, phases);
return !work.guards.isEmpty();
});
}
void prepareForSpeculativeOptimizations(WorkItem work, HGraph graph) {
measure(() {
// In order to generate correct code for the bailout version, we did not
// propagate types from the instruction to the type guard. We do it
// now to be able to optimize further.
work.guards.forEach((HTypeGuard guard) {
guard.type = guard.guarded.type;
guard.guarded.type = HType.UNKNOWN;
});
// We also need to insert range and integer checks for the type guards,
// now that they know their type. We did not need to do that
// before because instructions that reference a guard would
// have not tried to use, e.g. native array access, since the
// guard was not typed.
runPhases(graph, <OptimizationPhase>[new SsaCheckInserter(compiler)]);
});
}
}
/**
* If both inputs to known operations are available execute the operation at
* compile-time.
*/
class SsaConstantFolder extends HBaseVisitor implements OptimizationPhase {
final String name = "SsaConstantFolder";
final Compiler compiler;
HGraph graph;
SsaConstantFolder(this.compiler);
void visitGraph(HGraph graph) {
this.graph = graph;
visitDominatorTree(graph);
}
visitBasicBlock(HBasicBlock block) {
HInstruction instruction = block.first;
while (instruction !== null) {
HInstruction next = instruction.next;
HInstruction replacement = instruction.accept(this);
if (replacement !== instruction) {
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);
}
block.rewrite(instruction, replacement);
block.remove(instruction);
// Because the constant folder runs after type propagation, we
// must update the type of this instruction manually. Later
// phases can then optimize this instruction based on its
// type.
replacement.updateType();
}
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()) return input;
// All values !== true are boolified to false.
if (input.type.isKnown()) {
return graph.addConstantBool(false);
}
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);
}
return node;
}
HInstruction visitInvokeUnary(HInvokeUnary node) {
HInstruction operand = node.operand;
if (operand is HConstant) {
UnaryOperation operation = node.operation;
HConstant receiver = operand;
Constant folded = operation.fold(receiver.constant);
if (folded !== null) return graph.addConstant(folded);
}
return node;
}
HInstruction visitInvokeInterceptor(HInvokeInterceptor node) {
if (node.name == const SourceString('length') &&
node.inputs[1].isConstantString()) {
HConstant input = node.inputs[1];
StringConstant constant = input.constant;
DartString string = constant.value;
return graph.addConstantInt(string.length);
}
return node;
}
HInstruction visitInvokeBinary(HInvokeBinary node) {
HInstruction left = node.left;
HInstruction right = node.right;
if (left is HConstant && right is HConstant) {
BinaryOperation operation = node.operation;
HConstant op1 = left;
HConstant op2 = right;
Constant folded = operation.fold(op1.constant, op2.constant);
if (folded !== null) return graph.addConstant(folded);
}
return node;
}
HInstruction visitEquals(HEquals node) {
HInstruction left = node.left;
HInstruction right = node.right;
if (!left.isConstant() && right.isConstantNull()) {
// TODO(floitsch): cache interceptors.
HStatic target = new HStatic(
compiler.builder.interceptors.getEqualsNullInterceptor());
node.block.addBefore(node,target);
return new HEquals(target, node.left, node.right);
}
// All other cases are dealt with by the [visitInvokeBinary].
return visitInvokeBinary(node);
}
HInstruction visitTypeGuard(HTypeGuard node) {
HInstruction value = node.guarded;
return (value.type.combine(node.type) == value.type) ? value : node;
}
HInstruction visitIntegerCheck(HIntegerCheck node) {
HInstruction value = node.value;
return value.isInteger() ? value : node;
}
HInstruction visitIs(HIs node) {
Element element = node.typeExpression;
if (element.kind === ElementKind.TYPE_VARIABLE) {
compiler.unimplemented("visitIs for type variables");
}
HType expressionType = node.expression.type;
if (element === compiler.objectClass
|| element === compiler.dynamicClass) {
return graph.addConstantBool(true);
} else if (expressionType.isInteger()) {
if (element === compiler.intClass || element === compiler.numClass) {
return graph.addConstantBool(true);
} else if (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);
}
} else if (expressionType.isDouble()) {
if (element === compiler.doubleClass || element === compiler.numClass) {
return graph.addConstantBool(true);
} else if (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.
return node;
} else {
return graph.addConstantBool(false);
}
} else if (expressionType.isNumber()) {
if (element === compiler.numClass) {
return graph.addConstantBool(true);
}
// We cannot just return false, because the expression may be of
// type int or double.
} else if (expressionType.isString()) {
if (element === compiler.stringClass
|| Elements.isStringSupertype(element, compiler)) {
return graph.addConstantBool(true);
} else {
return graph.addConstantBool(false);
}
} else if (expressionType.isArray()) {
if (element === compiler.listClass
|| Elements.isListSupertype(element, compiler)) {
return graph.addConstantBool(true);
} else {
return graph.addConstantBool(false);
}
}
return node;
}
}
class SsaCheckInserter extends HBaseVisitor implements OptimizationPhase {
final String name = "SsaCheckInserter";
Element lengthInterceptor;
SsaCheckInserter(Compiler compiler) {
SourceString lengthString = const SourceString('length');
lengthInterceptor =
compiler.builder.interceptors.getStaticGetInterceptor(lengthString);
}
void visitGraph(HGraph 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 node,
HInstruction receiver,
HInstruction index) {
HStatic interceptor = new HStatic(lengthInterceptor);
node.block.addBefore(node, interceptor);
HInvokeInterceptor length = new HInvokeInterceptor(
Selector.INVOCATION_0,
const SourceString("length"),
true,
<HInstruction>[interceptor, receiver]);
length.type = HType.NUMBER;
node.block.addBefore(node, length);
HBoundsCheck check = new HBoundsCheck(length, index);
node.block.addBefore(node, check);
return check;
}
HIntegerCheck insertIntegerCheck(HInstruction node, HInstruction value) {
HIntegerCheck check = new HIntegerCheck(value);
node.block.addBefore(node, check);
return check;
}
void visitIndex(HIndex node) {
if (!node.builtin) return;
HInstruction index = insertIntegerCheck(node, node.index);
index = insertBoundsCheck(node, node.receiver, index);
HIndex newInstruction = new HIndex(node.target, node.receiver, index);
node.block.addBefore(node, newInstruction);
node.block.rewrite(node, newInstruction);
node.block.remove(node);
}
void visitIndexAssign(HIndexAssign node) {
if (!node.builtin) return;
HInstruction index = insertIntegerCheck(node, node.index);
index = insertBoundsCheck(node, node.receiver, index);
HIndexAssign newInstruction =
new HIndexAssign(node.target, node.receiver, index, node.value);
node.block.addBefore(node, newInstruction);
node.block.rewrite(node, newInstruction);
node.block.remove(node);
}
}
class SsaDeadCodeEliminator extends HGraphVisitor implements OptimizationPhase {
final String name = "SsaDeadCodeEliminator";
static bool isDeadCode(HInstruction instruction) {
// TODO(ngeoffray): the way we handle side effects is not right
// (e.g. branching instructions have side effects).
return !instruction.hasSideEffects()
&& instruction.usedBy.isEmpty()
&& instruction is !HCheck
&& instruction is !HTypeGuard;
}
void visitGraph(HGraph graph) {
visitPostDominatorTree(graph);
}
void visitBasicBlock(HBasicBlock block) {
HInstruction instruction = block.last;
while (instruction !== null) {
var previous = instruction.previous;
if (isDeadCode(instruction)) block.remove(instruction);
instruction = previous;
}
}
}
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(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 (input !== candidate && 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 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);
visitBasicBlock(graph.entry, new ValueSet());
}
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];
HBasicBlock last = block.loopInformation.getLastBackEdge();
for (int j = block.id; j <= last.id; j++) {
moveLoopInvariantCodeFromBlock(graph.blocks[j], block, changesFlags);
}
}
}
}
void moveLoopInvariantCodeFromBlock(HBasicBlock block,
HBasicBlock loopHeader,
int changesFlags) {
HBasicBlock preheader = loopHeader.predecessors[0];
int dependsFlags = HInstruction.computeDependsOnFlags(changesFlags);
HInstruction instruction = block.first;
while (instruction != null) {
HInstruction next = instruction.next;
if (instruction.useGvn()
&& (instruction is !HCheck)
&& (instruction.flags & dependsFlags) == 0) {
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);
}
}
instruction = next;
}
}
bool isInputDefinedAfterDominator(HInstruction input,
HBasicBlock dominator) {
return input.block.id > dominator.id;
}
void visitBasicBlock(HBasicBlock block, ValueSet values) {
HInstruction instruction = block.first;
while (instruction !== null) {
HInstruction next = instruction.next;
int flags = instruction.getChangesFlags();
if (flags != 0) {
assert(!instruction.useGvn());
values.kill(flags);
} else if (instruction.useGvn()) {
HInstruction other = values.lookup(instruction);
if (other !== null) {
assert(other.gvnEquals(instruction) && instruction.gvnEquals(other));
block.rewrite(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();
int changesFlags = getChangesFlagsForDominatedBlock(block, dominated);
successorValues.kill(changesFlags);
}
visitBasicBlock(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) {
instruction.prepareGvn();
changesFlags |= instruction.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) {
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];
changesFlags |= getChangesFlagsForDominatedBlock(dominator, 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, compute the
// intersection and move the instructions of the intersection into
// this block.
if (successors.length != 0) {
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.rewrite(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 = HInstruction.computeDependsOnFlags(flags);
flags |= instruction.getChangesFlags();
HInstruction current = instruction;
instruction = instruction.next;
// TODO(ngeoffray): this check is needed because we currently do
// not have flags to express 'Gvn'able', but not movable.
if (current is HCheck) continue;
if (!current.useGvn()) continue;
if ((current.flags & dependsFlags) != 0) continue;
bool canBeMoved = true;
for (final HInstruction input in current.inputs) {
if (input.block == block) {
canBeMoved = false;
break;
}
}
if (!canBeMoved) continue;
// This is safe because we are running after GVN.
// TODO(ngeoffray): ensure GVN has been run.
set_.add(current);
}
}
}