Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 part of dart2js.optimizers; | |
| 6 | |
| 7 /** | |
| 8 * [[ShrinkingReducer]] applies shrinking reductions to CPS terms as described | |
| 9 * in 'Compiling with Continuations, Continued' by Andrew Kennedy. | |
| 10 */ | |
| 11 class ShrinkingReducer implements Pass { | |
| 12 _RedexVisitor _redexVisitor; | |
| 13 Set<_ReductionTask> _worklist; | |
| 14 | |
| 15 static final _DeletedNode _DELETED = new _DeletedNode(); | |
| 16 | |
| 17 /// Applies shrinking reductions to root, mutating root in the process. | |
| 18 void rewrite(FunctionDefinition root) { | |
| 19 _worklist = new Set<_ReductionTask>(); | |
| 20 _redexVisitor = new _RedexVisitor(_worklist); | |
| 21 | |
| 22 // Set all parent pointers. | |
| 23 new _ParentVisitor().visit(root); | |
| 24 | |
| 25 // Sweep over the term, collecting redexes into the worklist. | |
| 26 _redexVisitor.visitFunctionDefinition(root); | |
| 27 | |
| 28 // Process the worklist. | |
| 29 while (_worklist.isNotEmpty) { | |
| 30 _ReductionTask task = _worklist.first; | |
| 31 _worklist.remove(task); | |
| 32 _processTask(task); | |
| 33 } | |
| 34 } | |
| 35 | |
| 36 /// Removes the given node from the CPS graph, replacing it with its body | |
| 37 /// and marking it as deleted. The node's parent must be a [[InteriorNode]]. | |
| 38 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.
| |
| 39 Node body = node.body; | |
| 40 InteriorNode parent = node.parent; | |
| 41 assert(parent.body == node); | |
| 42 | |
| 43 body.parent = parent; | |
| 44 parent.body = body; | |
| 45 node.parent = _DELETED; | |
| 46 } | |
| 47 | |
| 48 void _processTask(_ReductionTask task) { | |
| 49 // Lazily skip tasks for deleted nodes. | |
| 50 if (task.node.parent == _DELETED) { | |
| 51 return; | |
| 52 } | |
| 53 | |
| 54 switch (task.kind) { | |
| 55 case _ReductionKind.DEAD_VAL: | |
| 56 _reduceDeadVal(task); | |
| 57 break; | |
| 58 case _ReductionKind.DEAD_CONT: | |
| 59 _reduceDeadCont(task); | |
| 60 break; | |
| 61 case _ReductionKind.BETA_CONT_LIN: | |
| 62 _reduceBetaContLin(task); | |
| 63 break; | |
| 64 case _ReductionKind.ETA_CONT: | |
| 65 _reduceEtaCont(task); | |
| 66 break; | |
| 67 default: | |
| 68 assert(false); | |
| 69 } | |
| 70 } | |
| 71 | |
| 72 /// Applies the dead-val reduction: | |
| 73 /// 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.
| |
| 74 void _reduceDeadVal(_ReductionTask task) { | |
| 75 assert(_redexVisitor.isDeadVal(task.node)); | |
| 76 | |
| 77 // Remove dead primitive. | |
| 78 LetPrim letPrim = task.node;; | |
| 79 _removeNode(letPrim); | |
| 80 | |
| 81 // Perform bookkeeping on removed body and scan for new redexes. | |
| 82 new _RemovalRedexVisitor(_worklist).visit(letPrim.primitive); | |
| 83 } | |
| 84 | |
| 85 /// Applies the dead-cont reduction: | |
| 86 /// 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.
| |
| 87 void _reduceDeadCont(_ReductionTask task) { | |
| 88 assert(_redexVisitor.isDeadCont(task.node)); | |
| 89 | |
| 90 // Remove dead continuation. | |
| 91 LetCont letCont = task.node; | |
| 92 _removeNode(letCont); | |
| 93 | |
| 94 // Perform bookkeeping on removed body and scan for new redexes. | |
| 95 new _RemovalRedexVisitor(_worklist).visit(letCont.continuation); | |
| 96 } | |
| 97 | |
| 98 /// Applies the beta-cont-lin reduction: | |
| 99 /// letcont k x = K in C[k y] -> C[K[y/x]] (k not free in C). | |
| 100 void _reduceBetaContLin(_ReductionTask task) { | |
| 101 // 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
| |
| 102 if (!_redexVisitor.isBetaContLin(task.node)) { | |
| 103 return; | |
| 104 } | |
| 105 | |
| 106 // Remove the continuation. | |
| 107 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.
| |
| 108 Continuation cont = letCont.continuation; | |
| 109 _removeNode(letCont); | |
| 110 | |
| 111 // Replace its invocation with the continuation body. | |
| 112 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.
| |
| 113 InvokeContinuation invoke = ref.parent; | |
| 114 InteriorNode invokeParent = invoke.parent; | |
| 115 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
| |
| 116 | |
| 117 cont.body.parent = invokeParent; | |
| 118 invokeParent.body = cont.body; | |
| 119 | |
| 120 // Substitute the invocation argument for the continuation parameter. | |
| 121 for (int i = 0; i < invoke.arguments.length; i++) { | |
| 122 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
| |
| 123 argRef.definition.substituteFor(cont.parameters[i]); | |
| 124 // Scan for new redexes in substituted references. | |
| 125 _redexVisitor.processReference(argRef); | |
| 126 } | |
| 127 | |
| 128 // Do not scan for new redexes in the continuation body to avoid quadratic | |
| 129 // 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.
| |
| 130 } | |
| 131 | |
| 132 /// Applies the eta-cont reduction: | |
| 133 /// letcont k x = j x in K -> K[j/k]. | |
| 134 /// If k is unused, degenerates to dead-cont. | |
| 135 void _reduceEtaCont(_ReductionTask task) { | |
| 136 // Might have been mutated, recheck if reduction is still valid. | |
| 137 if (!_redexVisitor.isEtaCont(task.node)) { | |
| 138 return; | |
| 139 } | |
| 140 | |
| 141 // Remove the continuation. | |
| 142 LetCont letCont = task.node; | |
| 143 Continuation cont = letCont.continuation; | |
| 144 _removeNode(letCont); | |
| 145 | |
| 146 InvokeContinuation invoke = cont.body; | |
| 147 Continuation wrappedCont = invoke.continuation.definition; | |
| 148 | |
| 149 // Replace all occurrences with the wrapped continuation. | |
| 150 wrappedCont.substituteFor(cont); | |
| 151 | |
| 152 // Perform bookkeeping on removed body and scan for new redexes. | |
| 153 new _RemovalRedexVisitor(_worklist).visit(cont); | |
| 154 } | |
| 155 } | |
| 156 | |
| 157 /// Traverses a term and adds any found redexes to the worklist. | |
| 158 class _RedexVisitor extends RecursiveVisitor { | |
| 159 final Set<_ReductionTask> worklist; | |
| 160 | |
| 161 _RedexVisitor(this.worklist); | |
| 162 | |
| 163 void processLetPrim(LetPrim node) { | |
| 164 if (isDeadVal(node)) { | |
| 165 worklist.add(new _ReductionTask(_ReductionKind.DEAD_VAL, node)); | |
| 166 } | |
| 167 } | |
| 168 | |
| 169 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.
| |
| 170 if (isDeadCont(node)) { | |
| 171 worklist.add(new _ReductionTask(_ReductionKind.DEAD_CONT, node)); | |
| 172 } | |
| 173 if (isBetaContLin(node)){ | |
| 174 worklist.add(new _ReductionTask(_ReductionKind.BETA_CONT_LIN, node)); | |
| 175 } | |
| 176 if (isEtaCont(node)) { | |
| 177 worklist.add(new _ReductionTask(_ReductionKind.ETA_CONT, node)); | |
| 178 } | |
| 179 } | |
| 180 | |
| 181 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.
| |
| 182 if (reference.definition is Primitive) { | |
| 183 Primitive primitive = reference.definition; | |
| 184 Node parent = primitive.parent; | |
| 185 if (parent is LetPrim && isDeadVal(parent)) { | |
| 186 worklist.add(new _ReductionTask(_ReductionKind.DEAD_VAL, parent)); | |
| 187 } | |
| 188 } else if (reference.definition is Continuation) { | |
| 189 Continuation continuation = reference.definition; | |
| 190 Node parent = continuation.parent; | |
| 191 if (parent is LetCont && isDeadCont(parent)) { | |
| 192 worklist.add(new _ReductionTask(_ReductionKind.DEAD_CONT, parent)); | |
| 193 } | |
| 194 } | |
| 195 } | |
| 196 | |
| 197 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.
| |
| 198 | |
| 199 bool isDeadCont(LetCont node) => !node.continuation.hasAtLeastOneUse; | |
| 200 | |
| 201 bool isBetaContLin(LetCont node) { | |
| 202 Continuation cont = node.continuation; | |
| 203 if (!cont.hasExactlyOneUse) { | |
| 204 return false; | |
| 205 } | |
| 206 | |
| 207 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
| |
| 208 return false; | |
| 209 } | |
| 210 | |
| 211 InvokeContinuation invoke = cont.firstRef.parent; | |
| 212 return (cont == invoke.continuation.definition); | |
| 213 } | |
| 214 | |
| 215 bool isEtaCont(LetCont node) { | |
| 216 Continuation cont = node.continuation; | |
| 217 if (!(cont.body is InvokeContinuation)) { | |
| 218 return false; | |
| 219 } | |
| 220 | |
| 221 // Special case for continuations passed into one of { InvokeConstructor, | |
| 222 // InvokeMethod, InvokeStatic, ConcatenateStrings }, since | |
| 223 // 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.
| |
| 224 if (cont.hasExactlyOneUse) { | |
| 225 if (cont.firstRef.parent is InvokeConstructor) { | |
| 226 InvokeConstructor parent = cont.firstRef.parent; | |
| 227 if (parent.continuation == cont.firstRef) { | |
| 228 return false; | |
| 229 } | |
| 230 } else if (cont.firstRef.parent is InvokeMethod) { | |
| 231 InvokeMethod parent = cont.firstRef.parent; | |
| 232 if (parent.continuation == cont.firstRef) { | |
| 233 return false; | |
| 234 } | |
| 235 } else if (cont.firstRef.parent is InvokeStatic) { | |
| 236 InvokeStatic parent = cont.firstRef.parent; | |
| 237 if (parent.continuation == cont.firstRef) { | |
| 238 return false; | |
| 239 } | |
| 240 } else if (cont.firstRef.parent is ConcatenateStrings) { | |
| 241 ConcatenateStrings parent = cont.firstRef.parent; | |
| 242 if (parent.continuation == cont.firstRef) { | |
| 243 return false; | |
| 244 } | |
| 245 } | |
| 246 } | |
| 247 | |
| 248 InvokeContinuation invoke = cont.body; | |
| 249 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
| |
| 250 return false; | |
| 251 } | |
| 252 | |
| 253 if (cont.parameters.length != invoke.arguments.length) { | |
| 254 return false; | |
| 255 } | |
| 256 | |
| 257 // TODO(jgruber): Linear in the parameter count. Can be improved to near | |
| 258 // constant time by using union-find data structure. | |
| 259 for (int i = 0; i < cont.parameters.length; i++) { | |
| 260 if (invoke.arguments[i].definition != cont.parameters[i]) { | |
| 261 return false; | |
| 262 } | |
| 263 } | |
| 264 | |
| 265 return true; | |
| 266 } | |
| 267 } | |
| 268 | |
| 269 /// Traverses a deleted CPS term, marking existing tasks associated with a node | |
| 270 /// within the term as deleted (which causes them to be skipped lazily when | |
| 271 /// popped from the worklist), and adding newly created redexes to the worklist. | |
| 272 class _RemovalRedexVisitor extends _RedexVisitor { | |
| 273 _RemovalRedexVisitor(Set<_ReductionTask> worklist) : super(worklist); | |
| 274 | |
| 275 void processLetPrim(LetPrim node) { | |
| 276 node.parent = ShrinkingReducer._DELETED; | |
| 277 } | |
| 278 | |
| 279 void processLetCont(LetCont node) { | |
| 280 node.parent = ShrinkingReducer._DELETED; | |
| 281 } | |
| 282 | |
| 283 void processReference(Reference reference) { | |
| 284 reference.unlink(); | |
| 285 | |
| 286 // Convert recursive to nonrecursive continuations. | |
| 287 if (reference.definition is Continuation) { | |
| 288 Continuation cont = reference.definition; | |
| 289 if (cont.isRecursive && cont.hasAtMostOneUse) { | |
| 290 // If the continuation is still in use, it is either dead and will be | |
| 291 // removed, or it is called nonrecursively outside its body. | |
| 292 cont.isRecursive = false; | |
| 293 } | |
| 294 } | |
| 295 super.processReference(reference); // Scan for new dead-* redexes. | |
| 296 } | |
| 297 } | |
| 298 | |
| 299 /// Traverses the CPS term and sets node.parent for each visited node. | |
| 300 class _ParentVisitor extends RecursiveVisitor { | |
| 301 | |
| 302 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.
| |
| 303 child.parent = parent; | |
| 304 } | |
| 305 | |
| 306 void setRefParent(Node parent, Reference child) { | |
| 307 child.parent = parent; | |
| 308 } | |
| 309 | |
| 310 processFunctionDefinition(FunctionDefinition node) { | |
| 311 setParent(node, node.body); | |
| 312 node.parameters.forEach((Parameter p) => setParent(node, p)); | |
| 313 } | |
| 314 | |
| 315 // Expressions. | |
| 316 | |
| 317 processLetPrim(LetPrim node) { | |
| 318 setParent(node, node.primitive); | |
| 319 setParent(node, node.body); | |
| 320 } | |
| 321 | |
| 322 processLetCont(LetCont node) { | |
| 323 setParent(node, node.continuation); | |
| 324 setParent(node, node.body); | |
| 325 } | |
| 326 | |
| 327 processInvokeStatic(InvokeStatic node) { | |
| 328 setRefParent(node, node.continuation); | |
| 329 node.arguments.forEach((Reference ref) => setRefParent(node, ref)); | |
| 330 } | |
| 331 | |
| 332 processInvokeContinuation(InvokeContinuation node) { | |
| 333 setRefParent(node, node.continuation); | |
| 334 node.arguments.forEach((Reference ref) => setRefParent(node, ref)); | |
| 335 } | |
| 336 | |
| 337 processInvokeMethod(InvokeMethod node) { | |
| 338 setRefParent(node, node.receiver); | |
| 339 setRefParent(node, node.continuation); | |
| 340 node.arguments.forEach((Reference ref) => setRefParent(node, ref)); | |
| 341 } | |
| 342 | |
| 343 processInvokeSuperMethod(InvokeSuperMethod node) { | |
| 344 setRefParent(node, node.continuation); | |
| 345 node.arguments.forEach((Reference ref) => setRefParent(node, ref)); | |
| 346 } | |
| 347 | |
| 348 processInvokeConstructor(InvokeConstructor node) { | |
| 349 setRefParent(node, node.continuation); | |
| 350 node.arguments.forEach((Reference ref) => setRefParent(node, ref)); | |
| 351 } | |
| 352 | |
| 353 processConcatenateStrings(ConcatenateStrings node) { | |
| 354 setRefParent(node, node.continuation); | |
| 355 node.arguments.forEach((Reference ref) => setRefParent(node, ref)); | |
| 356 } | |
| 357 | |
| 358 processBranch(Branch node) { | |
| 359 setParent(node, node.condition); | |
| 360 setRefParent(node, node.trueContinuation); | |
| 361 setRefParent(node, node.falseContinuation); | |
| 362 } | |
| 363 | |
| 364 processTypeOperator(TypeOperator node) { | |
| 365 setRefParent(node, node.continuation); | |
| 366 setRefParent(node, node.receiver); | |
| 367 } | |
| 368 | |
| 369 processSetClosureVariable(SetClosureVariable node) { | |
| 370 setParent(node, node.body); | |
| 371 setRefParent(node, node.value); | |
| 372 } | |
| 373 | |
| 374 processDeclareFunction(DeclareFunction node) { | |
| 375 setParent(node, node.definition); | |
| 376 setParent(node, node.body); | |
| 377 } | |
| 378 | |
| 379 // Definitions. | |
| 380 | |
| 381 processLiteralList(LiteralList node) { | |
| 382 node.values.forEach((Reference ref) => setRefParent(node, ref)); | |
| 383 } | |
| 384 | |
| 385 processLiteralMap(LiteralMap node) { | |
| 386 node.values.forEach((Reference ref) => setRefParent(node, ref)); | |
| 387 node.keys.forEach((Reference ref) => setRefParent(node, ref)); | |
| 388 } | |
| 389 | |
| 390 processCreateFunction(CreateFunction node) { | |
| 391 setParent(node, node.definition); | |
| 392 } | |
| 393 | |
| 394 processContinuation(Continuation node) { | |
| 395 setParent(node, node.body); | |
| 396 node.parameters.forEach((Parameter param) => setParent(node, param)); | |
| 397 } | |
| 398 | |
| 399 // Conditions. | |
| 400 | |
| 401 processIsTrue(IsTrue node) { | |
| 402 setRefParent(node, node.value); | |
| 403 } | |
| 404 } | |
| 405 | |
| 406 class _ReductionKind { | |
| 407 final String name; | |
| 408 final int hashCode; | |
| 409 | |
| 410 const _ReductionKind(this.name, this.hashCode); | |
| 411 | |
| 412 static const _ReductionKind DEAD_VAL = const _ReductionKind('dead-val', 0); | |
| 413 static const _ReductionKind DEAD_CONT = const _ReductionKind('dead-cont', 1); | |
| 414 static const _ReductionKind BETA_CONT_LIN = | |
| 415 const _ReductionKind('beta-cont-lin', 2); | |
| 416 static const _ReductionKind ETA_CONT = const _ReductionKind('eta-cont', 3); | |
| 417 | |
| 418 String toString() => name; | |
| 419 } | |
| 420 | |
| 421 /// Represents a reduction task on the worklist. Implements both hashCode and | |
| 422 /// operator== since instantiations are used as Set elements. | |
| 423 class _ReductionTask { | |
| 424 final _ReductionKind kind; | |
| 425 final Node node; | |
| 426 | |
| 427 int get hashCode { | |
| 428 assert(kind.hashCode < (1 << 2)); | |
| 429 return (node.hashCode << 2) | kind.hashCode; | |
| 430 } | |
| 431 | |
| 432 _ReductionTask(this.kind, this.node) { | |
| 433 // If new node types are added, they must be marked as deleted in | |
| 434 // [[_RemovalRedexVisitor]]. | |
| 435 assert(node is LetCont || node is LetPrim); | |
| 436 } | |
| 437 | |
| 438 bool operator==(_ReductionTask that) { | |
| 439 return (that.kind == this.kind && that.node == this.node); | |
| 440 } | |
| 441 | |
| 442 String toString() => "$kind: $node"; | |
| 443 } | |
| 444 | |
| 445 /// A dummy class used solely to mark nodes as deleted once they are removed | |
| 446 /// from a term. | |
| 447 class _DeletedNode extends Node { | |
| 448 accept(_) => null; | |
| 449 } | |
| OLD | NEW |