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