Chromium Code Reviews| 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; |
| + } |
| +} |