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

Unified Diff: sdk/lib/_internal/compiler/implementation/cps_ir/shrinking_reductions.dart

Issue 417043003: Implement shrinking reductions in CPS IR (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Addressed comments, fixed several bugs exposed by later commits Created 6 years, 4 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: sdk/lib/_internal/compiler/implementation/cps_ir/shrinking_reductions.dart
diff --git a/sdk/lib/_internal/compiler/implementation/cps_ir/shrinking_reductions.dart b/sdk/lib/_internal/compiler/implementation/cps_ir/shrinking_reductions.dart
new file mode 100644
index 0000000000000000000000000000000000000000..8e98d35ba2e19800e948755aebe4eed448943222
--- /dev/null
+++ b/sdk/lib/_internal/compiler/implementation/cps_ir/shrinking_reductions.dart
@@ -0,0 +1,449 @@
+// Copyright (c) 2014, 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.
+
+part of dart2js.optimizers;
+
+/**
+ * [[ShrinkingReducer]] applies shrinking reductions to CPS terms as described
+ * in 'Compiling with Continuations, Continued' by Andrew Kennedy.
+ */
+class ShrinkingReducer implements Pass {
+ _RedexVisitor _redexVisitor;
+ Set<_ReductionTask> _worklist;
+
+ static final _DeletedNode _DELETED = new _DeletedNode();
+
+ /// Applies shrinking reductions to root, mutating root in the process.
+ void rewrite(FunctionDefinition root) {
+ _worklist = new Set<_ReductionTask>();
+ _redexVisitor = new _RedexVisitor(_worklist);
+
+ // Set all parent pointers.
+ new _ParentVisitor().visit(root);
+
+ // Sweep over the term, collecting redexes into the worklist.
+ _redexVisitor.visitFunctionDefinition(root);
+
+ // Process the worklist.
+ while (_worklist.isNotEmpty) {
+ _ReductionTask task = _worklist.first;
+ _worklist.remove(task);
+ _processTask(task);
+ }
+ }
+
+ /// Removes the given node from the CPS graph, replacing it with its body
+ /// and marking it as deleted. The node's parent must be a [[InteriorNode]].
+ void _removeNode(InteriorNode node) {
Kevin Millikin (Google) 2014/08/11 08:59:02 Could we make this a method on the InteriorNode cl
jgruber1 2014/08/11 13:00:58 Acknowledged.
+ Node body = node.body;
+ InteriorNode parent = node.parent;
+ assert(parent.body == node);
+
+ body.parent = parent;
+ parent.body = body;
+ node.parent = _DELETED;
+ }
+
+ void _processTask(_ReductionTask task) {
+ // Lazily skip tasks for deleted nodes.
+ if (task.node.parent == _DELETED) {
+ return;
+ }
+
+ switch (task.kind) {
+ case _ReductionKind.DEAD_VAL:
+ _reduceDeadVal(task);
+ break;
+ case _ReductionKind.DEAD_CONT:
+ _reduceDeadCont(task);
+ break;
+ case _ReductionKind.BETA_CONT_LIN:
+ _reduceBetaContLin(task);
+ break;
+ case _ReductionKind.ETA_CONT:
+ _reduceEtaCont(task);
+ break;
+ default:
+ assert(false);
+ }
+ }
+
+ /// Applies the dead-val reduction:
+ /// letprim x = V in K -> K (x not free in K).
Kevin Millikin (Google) 2014/08/11 08:59:03 Picky: I would not use K as a metavariable ranging
jgruber1 2014/08/11 13:00:58 Done.
+ void _reduceDeadVal(_ReductionTask task) {
+ assert(_redexVisitor.isDeadVal(task.node));
+
+ // Remove dead primitive.
+ LetPrim letPrim = task.node;;
+ _removeNode(letPrim);
+
+ // Perform bookkeeping on removed body and scan for new redexes.
+ new _RemovalRedexVisitor(_worklist).visit(letPrim.primitive);
+ }
+
+ /// Applies the dead-cont reduction:
+ /// letcont k x = L in K -> K (k not free in K).
Kevin Millikin (Google) 2014/08/11 08:59:02 Picky: Instead of L and K, use E0 and E1 (or E1 an
jgruber1 2014/08/11 13:00:58 Done.
+ void _reduceDeadCont(_ReductionTask task) {
+ assert(_redexVisitor.isDeadCont(task.node));
+
+ // Remove dead continuation.
+ LetCont letCont = task.node;
+ _removeNode(letCont);
+
+ // Perform bookkeeping on removed body and scan for new redexes.
+ new _RemovalRedexVisitor(_worklist).visit(letCont.continuation);
+ }
+
+ /// Applies the beta-cont-lin reduction:
+ /// letcont k x = K in C[k y] -> C[K[y/x]] (k not free in C).
+ void _reduceBetaContLin(_ReductionTask task) {
+ // Might have been mutated, recheck if reduction is still valid.
Kevin Millikin (Google) 2014/08/11 08:59:02 Can we give a crisp characterization of how a Beta
jgruber1 2014/08/11 13:00:58 Done. A simple case is using dead-cont: letcont k
+ if (!_redexVisitor.isBetaContLin(task.node)) {
+ return;
+ }
+
+ // Remove the continuation.
+ LetCont letCont = task.node;
Kevin Millikin (Google) 2014/08/11 08:59:02 No need to align the = on subsequent lines.
jgruber1 2014/08/11 13:00:58 Done.
+ Continuation cont = letCont.continuation;
+ _removeNode(letCont);
+
+ // Replace its invocation with the continuation body.
+ Reference ref = cont.firstRef..unlink();
Kevin Millikin (Google) 2014/08/11 08:59:02 No need for this alignment of =.
jgruber1 2014/08/11 13:00:59 Done.
+ InvokeContinuation invoke = ref.parent;
+ InteriorNode invokeParent = invoke.parent;
+ assert(invoke != null && invokeParent != null);
Kevin Millikin (Google) 2014/08/11 08:59:02 It's a bit strange to assert invoke != null after
jgruber1 2014/08/11 13:00:58 Removed the assert since both invoke and invokePar
+
+ cont.body.parent = invokeParent;
+ invokeParent.body = cont.body;
+
+ // Substitute the invocation argument for the continuation parameter.
+ for (int i = 0; i < invoke.arguments.length; i++) {
+ Reference argRef = invoke.arguments[i]..unlink();
Kevin Millikin (Google) 2014/08/11 08:59:02 Can't we achieve this unlinking (and above for con
jgruber1 2014/08/11 13:00:58 Good point, done. Added an additional check for de
+ argRef.definition.substituteFor(cont.parameters[i]);
+ // Scan for new redexes in substituted references.
+ _redexVisitor.processReference(argRef);
+ }
+
+ // Do not scan for new redexes in the continuation body to avoid quadratic
+ // blowup.
Kevin Millikin (Google) 2014/08/11 08:59:02 Hmmm. Is it possible to scan the body after beta
jgruber1 2014/08/11 13:00:57 Acknowledged.
+ }
+
+ /// Applies the eta-cont reduction:
+ /// letcont k x = j x in K -> K[j/k].
+ /// If k is unused, degenerates to dead-cont.
+ void _reduceEtaCont(_ReductionTask task) {
+ // Might have been mutated, recheck if reduction is still valid.
+ if (!_redexVisitor.isEtaCont(task.node)) {
+ return;
+ }
+
+ // Remove the continuation.
+ LetCont letCont = task.node;
+ Continuation cont = letCont.continuation;
+ _removeNode(letCont);
+
+ InvokeContinuation invoke = cont.body;
+ Continuation wrappedCont = invoke.continuation.definition;
+
+ // Replace all occurrences with the wrapped continuation.
+ wrappedCont.substituteFor(cont);
+
+ // Perform bookkeeping on removed body and scan for new redexes.
+ new _RemovalRedexVisitor(_worklist).visit(cont);
+ }
+}
+
+/// Traverses a term and adds any found redexes to the worklist.
+class _RedexVisitor extends RecursiveVisitor {
+ final Set<_ReductionTask> worklist;
+
+ _RedexVisitor(this.worklist);
+
+ void processLetPrim(LetPrim node) {
+ if (isDeadVal(node)) {
+ worklist.add(new _ReductionTask(_ReductionKind.DEAD_VAL, node));
+ }
+ }
+
+ void processLetCont(LetCont node) {
Kevin Millikin (Google) 2014/08/11 08:59:02 DeadCont and BetaContLin/EtaCont are mutually excl
jgruber1 2014/08/11 13:00:57 Done.
+ if (isDeadCont(node)) {
+ worklist.add(new _ReductionTask(_ReductionKind.DEAD_CONT, node));
+ }
+ if (isBetaContLin(node)){
+ worklist.add(new _ReductionTask(_ReductionKind.BETA_CONT_LIN, node));
+ }
+ if (isEtaCont(node)) {
+ worklist.add(new _ReductionTask(_ReductionKind.ETA_CONT, node));
+ }
+ }
+
+ void processReference(Reference reference) {
Kevin Millikin (Google) 2014/08/11 08:59:02 This seems fishy to me. It finds a reference duri
jgruber1 2014/08/11 13:00:58 Done.
+ if (reference.definition is Primitive) {
+ Primitive primitive = reference.definition;
+ Node parent = primitive.parent;
+ if (parent is LetPrim && isDeadVal(parent)) {
+ worklist.add(new _ReductionTask(_ReductionKind.DEAD_VAL, parent));
+ }
+ } else if (reference.definition is Continuation) {
+ Continuation continuation = reference.definition;
+ Node parent = continuation.parent;
+ if (parent is LetCont && isDeadCont(parent)) {
+ worklist.add(new _ReductionTask(_ReductionKind.DEAD_CONT, parent));
+ }
+ }
+ }
+
+ bool isDeadVal(LetPrim node) => !node.primitive.hasAtLeastOneUse;
Kevin Millikin (Google) 2014/08/11 08:59:02 This predicate and the other three don't need acce
jgruber1 2014/08/11 13:00:58 Done.
+
+ bool isDeadCont(LetCont node) => !node.continuation.hasAtLeastOneUse;
+
+ bool isBetaContLin(LetCont node) {
+ Continuation cont = node.continuation;
+ if (!cont.hasExactlyOneUse) {
+ return false;
+ }
+
+ if (!(cont.firstRef.parent is InvokeContinuation)) {
Kevin Millikin (Google) 2014/08/11 08:59:02 Maybe simpler: Reference use = cont.firstRef; ret
jgruber1 2014/08/11 13:00:57 Needs a cast of use.parent to InvokeContinuation i
+ return false;
+ }
+
+ InvokeContinuation invoke = cont.firstRef.parent;
+ return (cont == invoke.continuation.definition);
+ }
+
+ bool isEtaCont(LetCont node) {
+ Continuation cont = node.continuation;
+ if (!(cont.body is InvokeContinuation)) {
+ return false;
+ }
+
+ // Special case for continuations passed into one of { InvokeConstructor,
+ // InvokeMethod, InvokeStatic, ConcatenateStrings }, since
+ // these require a continuation that is used exactly once.
Kevin Millikin (Google) 2014/08/11 08:59:03 This comment should say why they require such a co
jgruber1 2014/08/11 13:00:58 Done.
+ if (cont.hasExactlyOneUse) {
+ if (cont.firstRef.parent is InvokeConstructor) {
+ InvokeConstructor parent = cont.firstRef.parent;
+ if (parent.continuation == cont.firstRef) {
+ return false;
+ }
+ } else if (cont.firstRef.parent is InvokeMethod) {
+ InvokeMethod parent = cont.firstRef.parent;
+ if (parent.continuation == cont.firstRef) {
+ return false;
+ }
+ } else if (cont.firstRef.parent is InvokeStatic) {
+ InvokeStatic parent = cont.firstRef.parent;
+ if (parent.continuation == cont.firstRef) {
+ return false;
+ }
+ } else if (cont.firstRef.parent is ConcatenateStrings) {
+ ConcatenateStrings parent = cont.firstRef.parent;
+ if (parent.continuation == cont.firstRef) {
+ return false;
+ }
+ }
+ }
+
+ InvokeContinuation invoke = cont.body;
+ if (invoke.isRecursive) {
Kevin Millikin (Google) 2014/08/11 08:59:02 Is it possible to eta-reduce recursive invocations
jgruber1 2014/08/11 13:00:59 At the moment, eta-cont should be applicable to re
+ return false;
+ }
+
+ if (cont.parameters.length != invoke.arguments.length) {
+ return false;
+ }
+
+ // TODO(jgruber): Linear in the parameter count. Can be improved to near
+ // constant time by using union-find data structure.
+ for (int i = 0; i < cont.parameters.length; i++) {
+ if (invoke.arguments[i].definition != cont.parameters[i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
+
+/// Traverses a deleted CPS term, marking existing tasks associated with a node
+/// within the term as deleted (which causes them to be skipped lazily when
+/// popped from the worklist), and adding newly created redexes to the worklist.
+class _RemovalRedexVisitor extends _RedexVisitor {
+ _RemovalRedexVisitor(Set<_ReductionTask> worklist) : super(worklist);
+
+ void processLetPrim(LetPrim node) {
+ node.parent = ShrinkingReducer._DELETED;
+ }
+
+ void processLetCont(LetCont node) {
+ node.parent = ShrinkingReducer._DELETED;
+ }
+
+ void processReference(Reference reference) {
+ reference.unlink();
+
+ // Convert recursive to nonrecursive continuations.
+ if (reference.definition is Continuation) {
+ Continuation cont = reference.definition;
+ if (cont.isRecursive && cont.hasAtMostOneUse) {
+ // If the continuation is still in use, it is either dead and will be
+ // removed, or it is called nonrecursively outside its body.
+ cont.isRecursive = false;
+ }
+ }
+ super.processReference(reference); // Scan for new dead-* redexes.
+ }
+}
+
+/// Traverses the CPS term and sets node.parent for each visited node.
+class _ParentVisitor extends RecursiveVisitor {
+
+ void setParent(Node parent, Node child) {
Kevin Millikin (Google) 2014/08/11 08:59:02 The order of arguments seems wrong, I would expect
jgruber1 2014/08/11 13:00:58 Done, inlined both methods.
+ child.parent = parent;
+ }
+
+ void setRefParent(Node parent, Reference child) {
+ child.parent = parent;
+ }
+
+ processFunctionDefinition(FunctionDefinition node) {
+ setParent(node, node.body);
+ node.parameters.forEach((Parameter p) => setParent(node, p));
+ }
+
+ // Expressions.
+
+ processLetPrim(LetPrim node) {
+ setParent(node, node.primitive);
+ setParent(node, node.body);
+ }
+
+ processLetCont(LetCont node) {
+ setParent(node, node.continuation);
+ setParent(node, node.body);
+ }
+
+ processInvokeStatic(InvokeStatic node) {
+ setRefParent(node, node.continuation);
+ node.arguments.forEach((Reference ref) => setRefParent(node, ref));
+ }
+
+ processInvokeContinuation(InvokeContinuation node) {
+ setRefParent(node, node.continuation);
+ node.arguments.forEach((Reference ref) => setRefParent(node, ref));
+ }
+
+ processInvokeMethod(InvokeMethod node) {
+ setRefParent(node, node.receiver);
+ setRefParent(node, node.continuation);
+ node.arguments.forEach((Reference ref) => setRefParent(node, ref));
+ }
+
+ processInvokeSuperMethod(InvokeSuperMethod node) {
+ setRefParent(node, node.continuation);
+ node.arguments.forEach((Reference ref) => setRefParent(node, ref));
+ }
+
+ processInvokeConstructor(InvokeConstructor node) {
+ setRefParent(node, node.continuation);
+ node.arguments.forEach((Reference ref) => setRefParent(node, ref));
+ }
+
+ processConcatenateStrings(ConcatenateStrings node) {
+ setRefParent(node, node.continuation);
+ node.arguments.forEach((Reference ref) => setRefParent(node, ref));
+ }
+
+ processBranch(Branch node) {
+ setParent(node, node.condition);
+ setRefParent(node, node.trueContinuation);
+ setRefParent(node, node.falseContinuation);
+ }
+
+ processTypeOperator(TypeOperator node) {
+ setRefParent(node, node.continuation);
+ setRefParent(node, node.receiver);
+ }
+
+ processSetClosureVariable(SetClosureVariable node) {
+ setParent(node, node.body);
+ setRefParent(node, node.value);
+ }
+
+ processDeclareFunction(DeclareFunction node) {
+ setParent(node, node.definition);
+ setParent(node, node.body);
+ }
+
+ // Definitions.
+
+ processLiteralList(LiteralList node) {
+ node.values.forEach((Reference ref) => setRefParent(node, ref));
+ }
+
+ processLiteralMap(LiteralMap node) {
+ node.values.forEach((Reference ref) => setRefParent(node, ref));
+ node.keys.forEach((Reference ref) => setRefParent(node, ref));
+ }
+
+ processCreateFunction(CreateFunction node) {
+ setParent(node, node.definition);
+ }
+
+ processContinuation(Continuation node) {
+ setParent(node, node.body);
+ node.parameters.forEach((Parameter param) => setParent(node, param));
+ }
+
+ // Conditions.
+
+ processIsTrue(IsTrue node) {
+ setRefParent(node, node.value);
+ }
+}
+
+class _ReductionKind {
+ final String name;
+ final int hashCode;
+
+ const _ReductionKind(this.name, this.hashCode);
+
+ static const _ReductionKind DEAD_VAL = const _ReductionKind('dead-val', 0);
+ static const _ReductionKind DEAD_CONT = const _ReductionKind('dead-cont', 1);
+ static const _ReductionKind BETA_CONT_LIN =
+ const _ReductionKind('beta-cont-lin', 2);
+ static const _ReductionKind ETA_CONT = const _ReductionKind('eta-cont', 3);
+
+ String toString() => name;
+}
+
+/// Represents a reduction task on the worklist. Implements both hashCode and
+/// operator== since instantiations are used as Set elements.
+class _ReductionTask {
+ final _ReductionKind kind;
+ final Node node;
+
+ int get hashCode {
+ assert(kind.hashCode < (1 << 2));
+ return (node.hashCode << 2) | kind.hashCode;
+ }
+
+ _ReductionTask(this.kind, this.node) {
+ // If new node types are added, they must be marked as deleted in
+ // [[_RemovalRedexVisitor]].
+ assert(node is LetCont || node is LetPrim);
+ }
+
+ bool operator==(_ReductionTask that) {
+ return (that.kind == this.kind && that.node == this.node);
+ }
+
+ String toString() => "$kind: $node";
+}
+
+/// A dummy class used solely to mark nodes as deleted once they are removed
+/// from a term.
+class _DeletedNode extends Node {
+ accept(_) => null;
+}

Powered by Google App Engine
This is Rietveld 408576698