Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(35)

Unified Diff: pkg/compiler/lib/src/tree_ir/optimization/variable_merger.dart

Issue 1007103003: cps-ir: Merge variables based on set-based liveness and graph coloring. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 5 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: pkg/compiler/lib/src/tree_ir/optimization/variable_merger.dart
diff --git a/pkg/compiler/lib/src/tree_ir/optimization/variable_merger.dart b/pkg/compiler/lib/src/tree_ir/optimization/variable_merger.dart
new file mode 100644
index 0000000000000000000000000000000000000000..7b5d0057a58ac2117b1fb58c9928c5c2533dbc4e
--- /dev/null
+++ b/pkg/compiler/lib/src/tree_ir/optimization/variable_merger.dart
@@ -0,0 +1,518 @@
+library tree_ir.optimization.variable_merger;
+
+import 'optimization.dart' show Pass, PassMixin;
+import '../tree_ir_nodes.dart';
+import '../../elements/elements.dart' show Local;
+
+/// Merges variables based on liveness and source variable information.
+///
+/// This phase cleans up artifacts introduced by the translation through CPS,
+/// where each source variable is translated into several copies. The copies
+/// are merged again when they are not live simultaneously.
+class VariableMerger extends RecursiveVisitor with PassMixin {
+ String get passName => 'Variable merger';
+
+ @override
+ void rewriteExecutableDefinition(ExecutableDefinition node) {
+ visitExecutableDefinition(node);
+ }
+
+ /// Rewrites the given function.
+ /// This is called for the outermost function and inner functions.
+ void rewriteFunction(ExecutableDefinition node) {
+ BlockGraphBuilder builder = new BlockGraphBuilder();
+ builder.visitExecutableDefinition(node);
+ _computeLiveness(builder.blocks);
+ Map<Variable, Variable> subst = _computeRegisterAllocation(builder.blocks);
+ new SubstVariables(subst).visitExecutableDefinition(node);
+ }
+
+ visitFunctionDefinition(FunctionDefinition node) {
+ super.visitFunctionDefinition(node); // Recurse to visit inner functions.
+ rewriteFunction(node);
+ }
+
+ visitFieldDefinition(FieldDefinition node) {
+ super.visitFieldDefinition(node);
+ rewriteFunction(node);
+ }
+
+ visitConstructorDefinition(ConstructorDefinition node) {
+ super.visitConstructorDefinition(node);
+ rewriteFunction(node);
+ }
+}
+
+/// Basic block in a control-flow graph.
+///
+/// Each block consists of a sequence of reads or a sequence of writes.
Kevin Millikin (Google) 2015/03/26 14:35:54 Flesh out this comment a bit to say explicitly tha
asgerf 2015/03/27 15:18:26 Changed to basic blocks with interleaved operation
+class Block {
+ /// List of predecessors in the control-flow graph.s
Kevin Millikin (Google) 2015/03/26 14:35:53 There's an extra 's'.
asgerf 2015/03/27 15:18:27 Done.
+ final List<Block> predecessors = <Block>[];
+
+ /// Entry to the catch block for the enclosing try, or `null`.
+ final Block catchBlock;
+
+ /// List of nodes with this block as [catchBlock].
+ final List<Block> catchPredecessors = <Block>[];
+
+ /// True if this is a sequence of read operations, false if write operations.
+ bool isRead = true;
+ bool get isWrite => !isRead;
+
+ /// Variables being read or written in this block.
Kevin Millikin (Google) 2015/03/26 14:35:53 I'd mention either "Sequence of variables" or "in
asgerf 2015/03/27 15:18:27 Done.
+ final List<Variable> variables = <Variable>[];
+
+ /// Auxilliary fields used by the liveness analysis.
Kevin Millikin (Google) 2015/03/26 14:35:54 One 'l' in auxiliary.
asgerf 2015/03/27 15:18:27 Done.
+ bool inWorklist = true;
+ Set<Variable> liveBefore = new Set<Variable>();
Kevin Millikin (Google) 2015/03/26 14:35:54 Consider liveIn and liveOut, which are fairly stan
asgerf 2015/03/27 15:18:26 Done.
+ Set<Variable> liveAfter = new Set<Variable>();
+
+ Block(this.catchBlock) {
+ if (catchBlock != null) {
+ catchBlock.catchPredecessors.add(this);
+ }
+ }
+}
+
+/// Builds a control-flow graph suitable for performing liveness analysis.
+class BlockGraphBuilder extends RecursiveVisitor {
Kevin Millikin (Google) 2015/03/26 14:35:55 The blank line after class isn't necessary.
asgerf 2015/03/27 15:18:26 Done.
+
+ Map<Label, Block> jumpTarget = <Label, Block>{};
Kevin Millikin (Google) 2015/03/26 14:35:53 I'd make most of these private names, except for t
asgerf 2015/03/27 15:18:27 Done.
+ Block currentBlock;
+ List<Block> blocks = <Block>[];
+
+ /// Variables with an assignment that should be treated as final.
+ ///
+ /// Such variables cannot be merged with any other variables, so we exclude
+ /// them from the control-flow graph entirely.
+ Set<Variable> finalVariables = new Set<Variable>();
+
+ BlockGraphBuilder() {
+ currentBlock = newBlock();
+ }
+
+ /// Creates a new block with the current exception handler or [catchBlock]
+ /// if provided.
+ Block newBlock({Block catchBlock}) {
+ if (catchBlock == null && currentBlock != null) {
+ catchBlock = currentBlock.catchBlock;
+ }
+ Block block = new Block(catchBlock);
+ blocks.add(block);
+ return block;
+ }
+
+ /// Starts a new branch after the end of [block].
Kevin Millikin (Google) 2015/03/26 14:35:53 Not necessary a branch (at least, not a non-trivia
asgerf 2015/03/27 15:18:28 Done.
+ void branchFrom(Block block) {
+ currentBlock = newBlock()..predecessors.add(block);
+ }
+
+ /// Called when reading from [v].
+ ///
+ /// Appends a read operation to the current basic block, or starts a new
+ /// block if the current block is a write block.
+ void read(Variable v) {
Kevin Millikin (Google) 2015/03/26 14:35:54 Go ahead and spell out 'variable', here and below.
asgerf 2015/03/27 15:18:27 Done.
+ if (v.isCaptured) return;
+ if (finalVariables.contains(v)) return;
+ if (!currentBlock.isRead) {
+ branchFrom(currentBlock);
+ currentBlock.isRead = true;
Kevin Millikin (Google) 2015/03/26 14:35:53 true is the default and we do rely on that a bit b
asgerf 2015/03/27 15:18:26 Not relevant anymore.
+ }
+ currentBlock.variables.add(v);
+ }
+
+ /// Called when writing to [v].
+ ///
+ /// Appends a write operation to the current basic block, or starts a new
+ /// block if the current block is a read block.
+ void write(Variable v) {
+ if (v.isCaptured) return;
+ if (finalVariables.contains(v)) return;
+ if (currentBlock.isRead) {
+ if (!currentBlock.variables.isEmpty) {
Kevin Millikin (Google) 2015/03/26 14:35:54 This needs a short comment to explain that this co
asgerf 2015/03/27 15:18:27 Not relevant anymore.
+ branchFrom(currentBlock);
+ }
+ currentBlock.isRead = false;
+ }
+ currentBlock.variables.add(v);
+ }
+
+ /// Called to indicate that [v] has a final assignment, and should therefore
+ /// be ignored. Subsequent calls to [read] and [write] will ignore the it.
+ void finalWrite(Variable v) {
+ finalVariables.add(v);
+ }
+
+ visitVariableUse(VariableUse node) {
+ read(node.variable);
+ }
+
+ visitAssign(Assign node) {
+ visitExpression(node.value);
+ write(node.variable);
+ visitStatement(node.next);
+ }
+
+ visitIf(If node) {
+ visitExpression(node.condition);
+ Block afterCondition = currentBlock;
+ branchFrom(afterCondition);
+ visitStatement(node.thenStatement);
+ branchFrom(afterCondition);
+ visitStatement(node.elseStatement);
+ }
+
+ visitLabeledStatement(LabeledStatement node) {
+ Block join = jumpTarget[node.label] = newBlock();
+ visitStatement(node.body); // visitBreak will add predecessors to join.
+ currentBlock = join;
+ visitStatement(node.next);
+ }
+
+ visitBreak(Break node) {
+ jumpTarget[node.target].predecessors.add(currentBlock);
+ }
+
+ visitContinue(Continue node) {
+ jumpTarget[node.target].predecessors.add(currentBlock);
+ }
+
+ visitWhileTrue(WhileTrue node) {
+ Block join = jumpTarget[node.label] = newBlock();
+ join.predecessors.add(currentBlock);
+ currentBlock = join;
+ visitStatement(node.body); // visitContinue will add predecessors to join.
+ }
+
+ visitWhileCondition(WhileCondition node) {
+ Block join = jumpTarget[node.label] = newBlock();
+ join.predecessors.add(currentBlock);
+ currentBlock = join;
+ visitExpression(node.condition);
+ Block afterCondition = currentBlock;
+ branchFrom(afterCondition);
+ visitStatement(node.body); // visitContinue will add predecessors to join.
+ branchFrom(afterCondition);
+ visitStatement(node.next);
+ }
+
+ visitTry(Try node) {
+ Block catchBlock = newBlock();
+ Block tryBlock = newBlock(catchBlock: catchBlock);
Kevin Millikin (Google) 2015/03/26 14:35:53 The three lines starting here are branchFrom with
asgerf 2015/03/27 15:18:27 Done.
+ tryBlock.predecessors.add(currentBlock);
+ currentBlock = tryBlock;
+ visitStatement(node.tryBody);
+ currentBlock = catchBlock;
+ node.catchParameters.forEach(finalWrite);
Kevin Millikin (Google) 2015/03/26 14:35:53 Is this necessary or defensive? It seems like sin
asgerf 2015/03/27 15:18:27 Both. Something smarter could be done, but it's no
+ visitStatement(node.catchBody);
+ }
+
+ visitConditional(Conditional node) {
+ visitExpression(node.condition);
+ // TODO(asgerf): When assignment expressions are added, this is no longer
+ // sound; then we need to handle as a branch.
+ visitExpression(node.thenExpression);
+ visitExpression(node.elseExpression);
+ }
+
+ visitLogicalOperator(LogicalOperator node) {
+ visitExpression(node.left);
+ // TODO(asgerf): When assignment expressions are added, this is no longer
+ // sound; then we need to handle as a branch.
+ visitExpression(node.right);
+ }
+
+ visitFunctionDeclaration(FunctionDeclaration node) {
+ finalWrite(node.variable);
+ visitStatement(node.next);
+ // Do not traverse inner function.
+ }
+
+ visitFunctionExpression(FunctionExpression node) {
+ // Do not traverse inner function.
+ }
+
+ visitFunctionDefinition(FunctionDefinition node) {
+ // Function parameters are treated as write operations at the entry point,
+ // so they can potentially be merged with other copies of the parameter.
+ // Note that function parameters always have distinct source variables,
+ // so we don't risk accidentally merging two parameters.
+ node.parameters.forEach(write);
+ visitStatement(node.body);
+ }
+
+ visitConstructorDefinition(ConstructorDefinition node) {
+ node.parameters.forEach(write);
+ node.initializers.forEach(visitInitializer);
+ visitStatement(node.body);
+ }
+}
+
+/// Computes liveness information of the given control-flow graph.
+///
+/// The results are stored in [Block.liveBefore] and [Block.liveAfter].
+void _computeLiveness(List<Block> blocks) {
+ List<Block> worklist = new List<Block>.from(blocks);
Kevin Millikin (Google) 2015/03/26 14:35:53 Comment that blocks are initially in AST order and
asgerf 2015/03/27 15:18:26 Done.
+ while (!worklist.isEmpty) {
+ Block block = worklist.removeLast();
+ block.inWorklist = false;
+ Set<Variable> live = new Set<Variable>.from(block.liveAfter);
Kevin Millikin (Google) 2015/03/26 14:35:53 This is potentially expensive, it's making an iter
asgerf 2015/03/27 15:18:27 Done.
+ if (block.isRead) {
+ // Reading a variable makes it live before that point.
+ live.addAll(block.variables);
Kevin Millikin (Google) 2015/03/26 14:35:54 I think it is worth pointing out that our blocks a
asgerf 2015/03/27 15:18:28 Not relevant anymore.
+ } else {
+ // Assigning to a variable makes it dead before that point.
+ // Note that when a variable is live at entry to the current catch block,
+ // it remains live before the assignment.
+ // When a variable becomes live at the catch block, it will be removed
+ // from block.variables, so here we can safely mark them all as dead.
+ live.removeAll(block.variables);
+ }
+
+ // If anything changed, propagate liveness backwards.
+ if (block.liveBefore.length < live.length) {
Kevin Millikin (Google) 2015/03/26 14:35:54 Comment that this is a monotone analysis: the live
asgerf 2015/03/27 15:18:27 I agree, but there is now an explicit changed flag
+ block.liveBefore = live;
+
+ // Propagate live variables to predecessors.
+ for (Block pred in block.predecessors) {
Kevin Millikin (Google) 2015/03/26 14:35:53 Spell out predecessor.
asgerf 2015/03/27 15:18:27 Done.
+ int size = pred.liveAfter.length;
Kevin Millikin (Google) 2015/03/26 14:35:54 size ==> length, or originalLength or the like.
asgerf 2015/03/27 15:18:28 Done.
+ pred.liveAfter.addAll(live);
+ if (pred.liveAfter.length > size && !pred.inWorklist) {
+ worklist.add(pred);
+ pred.inWorklist = true;
+ }
+ }
+
+ // Propagate live variables to catch predecessors.
+ for (Block pred in block.catchPredecessors) {
+ bool changed = false;
+ int size = pred.liveAfter.length;
Kevin Millikin (Google) 2015/03/26 14:35:54 Use 'length' in the name, not 'size'.
asgerf 2015/03/27 15:18:27 Done.
+ pred.liveAfter.addAll(live);
+ if (size < pred.liveAfter.length) {
Kevin Millikin (Google) 2015/03/26 14:35:54 The analogous comparison in the loop above has siz
asgerf 2015/03/31 12:14:18 Done.
+ changed = true;
+ }
+ if (pred.isWrite) {
+ // Remove assignments to variables that are live in the catch block.
+ size = pred.variables.length;
+ pred.variables.removeWhere(block.liveBefore.contains);
Kevin Millikin (Google) 2015/03/26 14:35:55 block.liveBefore is the same as live, isn't it? I
asgerf 2015/03/27 15:18:27 Done.
+ if (pred.variables.length < size) {
+ changed = true;
+ }
+ }
+ if (!pred.inWorklist && changed) {
Kevin Millikin (Google) 2015/03/26 14:35:54 Probably cheaper to check changed before !pred.inW
asgerf 2015/03/27 15:18:28 Done.
+ worklist.add(pred);
+ pred.inWorklist = true;
+ }
+ }
+ }
+ }
+}
+
+/// Based on liveness information, computes a map of variable substitutions to
+/// merge variables.
+///
+/// Constructs a register interference graph. This is an undirected graph of
+/// variables, with an edge between two variables if they cannot be merged
+/// (because they are live simultaneously).
+///
+/// We then compute a graph coloring, where the color of a node denotes which
+/// variable it will be substituted by.
+///
+/// We never merge variables that originated from distinct source variables,
+/// so we build a separate register interference graph for each source variable.
+Map<Variable, Variable> _computeRegisterAllocation(List<Block> blocks) {
+ Map<Variable, Set<Variable>> edges = new Map<Variable, Set<Variable>>();
Kevin Millikin (Google) 2015/03/26 14:35:55 edges ==> interferences
asgerf 2015/03/27 15:18:28 Done.
+
+ // At the assignment to a variable x, add an edge to every variable that is
+ // live after the assignment (if it came from the same source variable).
+ for (Block block in blocks) {
+ if (block.isWrite) {
+ // Group the liveAfter set by source variable.
+ Map<Local, List<Variable>> liveAfter = <Local, List<Variable>>{};
+ for (Variable x in block.liveAfter) {
Kevin Millikin (Google) 2015/03/26 14:35:53 x ==> variable
asgerf 2015/03/27 15:18:27 Done.
+ liveAfter.putIfAbsent(x.element, () => <Variable>[]).add(x);
+ edges.putIfAbsent(x, () => new Set<Variable>());
+ }
+ // Add edges for each variable being assigned here.
+ for (Variable x in block.variables.reversed) {
+ edges.putIfAbsent(x, () => new Set<Variable>());
+ List<Variable> live = liveAfter[x.element];
+ if (live != null) {
+ live.remove(x); // Hide from earlier assignments in the block.
+ for (Variable y in live) {
+ edges[x].add(y);
+ edges[y].add(x);
+ }
+ }
+ }
+ }
+ }
+
+ // Sort the variables by descending degree.
+ // The most constrained variables will be assigned a color first.
+ List<Variable> variables = edges.keys.toList();
+ variables.sort((x, y) => edges[y].length - edges[x].length);
+
+ Map<Local, List<Variable>> registers = <Local, List<Variable>>{};
+ Map<Variable, Variable> subst = <Variable, Variable>{};
+
+ for (Variable v1 in variables) {
+ List<Variable> register =
+ registers.putIfAbsent(v1.element, () => <Variable>[]);
+
+ // Find an unused color.
+ Set<Variable> potential = new Set<Variable>.from(register);
+ for (Variable v2 in edges[v1]) {
Kevin Millikin (Google) 2015/03/26 14:35:54 I don't have a feel for how big the sets of interf
asgerf 2015/03/27 15:18:27 When compiling swarm, it's empty about 90% of the
+ Variable v2subst = subst[v2];
+ if (v2subst != null) {
+ potential.remove(v2subst);
+ }
+ }
+ if (potential.isEmpty) {
+ // If no free color was found, add this variable as a new color.
+ register.add(v1);
+ subst[v1] = v1;
+ } else {
+ subst[v1] = potential.first;
+ }
+ }
+
+ return subst;
+}
+
+/// Performs variable substitution and removes redundant assignments.
+class SubstVariables extends RecursiveVisitor {
Kevin Millikin (Google) 2015/03/26 14:35:53 I'm not sure what Subst is supposed to be. The ve
asgerf 2015/03/27 15:18:26 Done.
+
+ Map<Variable, Variable> subst;
+
+ SubstVariables(this.subst);
+
+ Variable replaceRead(Variable v) {
Kevin Millikin (Google) 2015/03/26 14:35:54 v ==> variable, w ==> other.
asgerf 2015/03/27 15:18:27 Done.
+ Variable w = subst[v];
+ if (w == null) return v;
Kevin Millikin (Google) 2015/03/26 14:35:54 This is the case for final assignments, or does it
asgerf 2015/03/27 15:18:26 Done.
+ w.readCount++;
+ v.readCount--;
+ return w;
+ }
+
+ Variable replaceWrite(Variable v) {
+ Variable w = subst[v];
+ if (w == null) return v;
+ w.writeCount++;
+ v.writeCount--;
+ return w;
+ }
+
+ void replaceParameters(List<Variable> parameters) {
+ for (int i=0; i < parameters.length; i++) {
Kevin Millikin (Google) 2015/03/26 14:35:54 'i = 0'.
asgerf 2015/03/27 15:18:27 Done.
+ parameters[i] = replaceWrite(parameters[i]);
+ }
+ }
+
+ visitVariableUse(VariableUse node) {
+ node.variable = replaceRead(node.variable);
+ }
+
+ visitFunctionDefinition(FunctionDefinition node) {
+ replaceParameters(node.parameters);
+ node.body = visitStatement(node.body);
+ }
+
+ visitConstructorDefinition(ConstructorDefinition node) {
+ replaceParameters(node.parameters);
+ node.initializers.forEach(visitInitializer);
+ node.body = visitStatement(node.body);
+ }
+
+ visitFieldInitializer(FieldInitializer node) {
+ node.body = visitStatement(node.body);
+ }
+
+ visitSuperInitializer(SuperInitializer node) {
+ for (int i=0; i<node.arguments.length; i++) {
Kevin Millikin (Google) 2015/03/26 14:35:54 'i = 0', 'i < node.arguments.length'. And '++i' :
asgerf 2015/03/27 15:18:26 Done.
+ node.arguments[i] = visitStatement(node.arguments[i]);
+ }
+ }
+
+ // Statement visitors should return the transformed statement so we
+ // can remove redundant assignments.
+ Statement visitStatement(Statement node) => super.visitStatement(node);
+
+ Statement visitAssign(Assign node) {
+ node.variable = replaceWrite(node.variable);
+
+ visitExpression(node.value);
+ node.next = visitStatement(node.next);
+
+ // Remove assignments of form "x := x"
+ if (node.value is VariableUse) {
+ VariableUse value = node.value;
+ if (value.variable == node.variable) {
+ value.variable.readCount--;
+ node.variable.writeCount--;
+ return node.next;
+ }
+ }
+
+ return node;
+ }
+
+ Statement visitLabeledStatement(LabeledStatement node) {
+ node.body = visitStatement(node.body);
+ node.next = visitStatement(node.next);
+ return node;
+ }
+
+ Statement visitReturn(Return node) {
+ visitExpression(node.value);
+ return node;
+ }
+
+ Statement visitBreak(Break node) => node;
Kevin Millikin (Google) 2015/03/26 14:35:54 I usually make all the 'related' method bodies hav
asgerf 2015/03/27 15:18:27 Done.
+
+ Statement visitContinue(Continue node) => node;
+
+ Statement visitIf(If node) {
+ visitExpression(node.condition);
+ node.thenStatement = visitStatement(node.thenStatement);
+ node.elseStatement = visitStatement(node.elseStatement);
+ return node;
+ }
+
+ Statement visitWhileTrue(WhileTrue node) {
+ node.body = visitStatement(node.body);
+ return node;
+ }
+
+ Statement visitWhileCondition(WhileCondition node) {
+ visitExpression(node.condition);
+ node.body = visitStatement(node.body);
+ node.next = visitStatement(node.next);
+ return node;
+ }
+
+ Statement visitFunctionDeclaration(FunctionDeclaration node) {
+ node.next = visitStatement(node.next);
+ return node;
+ }
+
+ Statement visitExpressionStatement(ExpressionStatement node) {
+ visitExpression(node.expression);
+ node.next = visitStatement(node.next);
+ return node;
+ }
+
+ Statement visitTry(Try node) {
+ node.tryBody = visitStatement(node.tryBody);
+ node.catchBody = visitStatement(node.catchBody);
+ return node;
+ }
+
+ Statement visitSetField(SetField node) {
+ visitExpression(node.object);
+ visitExpression(node.value);
+ node.next = visitStatement(node.next);
+ return node;
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698