blob: 3dea17c0ba40cf1418c74a096b7e34dcecd095c0 [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library dart2js.cps_ir.mutable_ssa;
import 'cps_ir_nodes.dart';
import 'optimizers.dart';
/// Determines which mutable variables should be rewritten to phi assignments
/// in this pass.
///
/// We do not rewrite variables that have an assignment inside a try block that
/// does not contain its declaration.
class MutableVariablePreanalysis extends RecursiveVisitor {
// Number of try blocks enclosing the current position.
int currentDepth = 0;
/// Number of try blocks enclosing the declaration of a given mutable
/// variable.
///
/// All mutable variables seen will be present in the map after the analysis.
Map<MutableVariable, int> variableDepth = <MutableVariable, int>{};
/// Variables with an assignment inside a try block that does not contain
/// its declaration.
Set<MutableVariable> hasAssignmentInTry = new Set<MutableVariable>();
@override
Expression traverseLetHandler(LetHandler node) {
push(node.handler);
++currentDepth;
pushAction(() => --currentDepth);
return node.body;
}
void processLetMutable(LetMutable node) {
variableDepth[node.variable] = currentDepth;
}
void processSetMutable(SetMutable node) {
MutableVariable variable = node.variable.definition;
if (currentDepth > variableDepth[variable]) {
hasAssignmentInTry.add(variable);
}
}
/// True if there are no mutable variables or they are all assigned inside
/// a try block. In this case, there is nothing to do and the pass should
/// be skipped.
bool get allMutablesAreAssignedInTryBlocks {
return hasAssignmentInTry.length == variableDepth.length;
}
}
/// Replaces mutable variables with continuation parameters, effectively
/// bringing them into SSA form.
///
/// This pass is intended to clean up mutable variables that were introduced
/// by an optimization in the type propagation pass.
///
/// This implementation potentially creates a lot of redundant and dead phi
/// parameters. These will be cleaned up by redundant phi elimination and
/// shrinking reductions.
///
/// Discussion:
/// For methods with a lot of mutable variables, creating all the spurious
/// parameters might be too expensive. If this is the case, we should
/// improve this pass to avoid most spurious parameters in practice.
class MutableVariableEliminator implements Pass {
String get passName => 'Mutable variable elimination';
/// Mutable variables currently in scope, in order of declaration.
/// This list determines the order of the corresponding phi parameters.
final List<MutableVariable> mutableVariables = <MutableVariable>[];
/// Number of phi parameters added to the given continuation.
final Map<Continuation, int> continuationPhiCount = <Continuation, int>{};
/// Stack of yet unprocessed continuations interleaved with the
/// mutable variables currently in scope.
///
/// Continuations are processed when taken off the stack and mutable
/// variables fall out of scope (i.e. removed from [mutableVariables]) when
/// taken off the stack.
final List<StackItem> stack = <StackItem>[];
MutableVariablePreanalysis analysis;
void rewrite(FunctionDefinition node) {
analysis = new MutableVariablePreanalysis()..visit(node);
if (analysis.allMutablesAreAssignedInTryBlocks) {
// Skip the pass if there is nothing to do.
return;
}
processBlock(node.body, <MutableVariable, Primitive>{});
while (stack.isNotEmpty) {
StackItem item = stack.removeLast();
if (item is ContinuationItem) {
processBlock(item.continuation.body, item.environment);
} else {
assert(item is VariableItem);
mutableVariables.removeLast();
}
}
}
bool shouldRewrite(MutableVariable variable) {
return !analysis.hasAssignmentInTry.contains(variable);
}
bool isJoinContinuation(Continuation cont) {
return !cont.hasExactlyOneUse ||
cont.firstRef.parent is InvokeContinuation;
}
void removeNode(InteriorNode node) {
InteriorNode parent = node.parent;
parent.body = node.body;
node.body.parent = parent;
}
/// If some useful source information is attached to exactly one of the
/// two definitions, the information is copied onto the other.
void mergeHints(MutableVariable variable, Primitive value) {
if (variable.hint == null) {
variable.hint = value.hint;
} else if (value.hint == null) {
value.hint = variable.hint;
}
}
/// Processes a basic block, replacing mutable variable uses with direct
/// references to their values.
///
/// [environment] is the current value of each mutable variable. The map
/// will be mutated during the processing.
///
/// Continuations to be processed are put on the stack for later processing.
void processBlock(Expression node,
Map<MutableVariable, Primitive> environment) {
for (; node is! TailExpression; node = node.next) {
if (node is LetMutable && shouldRewrite(node.variable)) {
// Put the new mutable variable on the stack while processing the body,
// and pop it off again when done with the body.
mutableVariables.add(node.variable);
stack.add(new VariableItem());
// Put the initial value into the environment.
Primitive value = node.value.definition;
environment[node.variable] = value;
// Preserve variable names.
mergeHints(node.variable, value);
// Remove the mutable variable binding.
node.value.unlink();
removeNode(node);
} else if (node is LetPrim && node.primitive is SetMutable) {
SetMutable setter = node.primitive;
MutableVariable variable = setter.variable.definition;
if (shouldRewrite(variable)) {
// As above, update the environment, preserve variables and remove
// the mutable variable assignment.
environment[variable] = setter.value.definition;
mergeHints(variable, setter.value.definition);
setter.value.unlink();
removeNode(node);
}
} else if (node is LetPrim && node.primitive is GetMutable) {
GetMutable getter = node.primitive;
MutableVariable variable = getter.variable.definition;
if (shouldRewrite(variable)) {
// Replace with the reaching definition from the environment.
Primitive value = environment[variable];
value.substituteFor(getter);
mergeHints(variable, value);
removeNode(node);
}
} else if (node is LetCont) {
// Create phi parameters for each join continuation bound here, and put
// them on the stack for later processing.
// Note that non-join continuations are handled at the use-site.
for (Continuation cont in node.continuations) {
if (!isJoinContinuation(cont)) continue;
// Create a phi parameter for every mutable variable in scope.
// At the same time, build the environment to use for processing
// the continuation (mapping mutables to phi parameters).
continuationPhiCount[cont] = mutableVariables.length;
Map<MutableVariable, Primitive> environment =
<MutableVariable, Primitive>{};
for (MutableVariable variable in mutableVariables) {
Parameter phi = new Parameter(variable.hint);
phi.type = variable.type;
cont.parameters.add(phi);
phi.parent = cont;
environment[variable] = phi;
}
stack.add(new ContinuationItem(cont, environment));
}
} else if (node is LetHandler) {
// Process the catch block later and continue into the try block.
// We can use the same environment object for the try and catch blocks.
// The current environment bindings cannot change inside the try block
// because we exclude all variables assigned inside a try block.
// The environment might be extended with more bindings before we
// analyze the catch block, but that's ok.
stack.add(new ContinuationItem(node.handler, environment));
}
}
// Analyze the terminal node.
if (node is InvokeContinuation) {
Continuation cont = node.continuation.definition;
if (cont.isReturnContinuation) return;
// This is a call to a join continuation. Add arguments for the phi
// parameters that were added to this continuation.
int phiCount = continuationPhiCount[cont];
for (int i = 0; i < phiCount; ++i) {
Primitive value = environment[mutableVariables[i]];
Reference<Primitive> arg = new Reference<Primitive>(value);
node.arguments.add(arg);
arg.parent = node;
}
} else if (node is Branch) {
// Enqueue both branches with the current environment.
// Clone the environments once so the processing of one branch does not
// mutate the environment needed to process the other branch.
stack.add(new ContinuationItem(
node.trueContinuation.definition,
new Map<MutableVariable, Primitive>.from(environment)));
stack.add(new ContinuationItem(
node.falseContinuation.definition,
environment));
} else {
assert(node is Throw || node is Unreachable);
}
}
}
abstract class StackItem {}
/// Represents a mutable variable that is in scope.
///
/// The topmost mutable variable falls out of scope when this item is
/// taken off the stack.
class VariableItem extends StackItem {}
/// Represents a yet unprocessed continuation together with the
/// environment in which to process it.
class ContinuationItem extends StackItem {
final Continuation continuation;
final Map<MutableVariable, Primitive> environment;
ContinuationItem(this.continuation, this.environment);
}