| 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) { |
| 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 E -> E (x not free in E). |
| 74 void _reduceDeadVal(_ReductionTask task) { |
| 75 assert(_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 = E0 in E1 -> E1 (k not free in E1). |
| 87 void _reduceDeadCont(_ReductionTask task) { |
| 88 assert(_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 = E0 in E1[k y] -> E1[E0[y/x]] (k not free in E1). |
| 100 void _reduceBetaContLin(_ReductionTask task) { |
| 101 // Might have been mutated, recheck if reduction is still valid. |
| 102 // In the following example, the beta-cont-lin reduction of k0 could have |
| 103 // been invalidated by removal of the dead continuation k1: |
| 104 // |
| 105 // letcont k0 x0 = E0 in |
| 106 // letcont k1 x1 = k0 x1 in |
| 107 // return x2 |
| 108 if (!_isBetaContLin(task.node)) { |
| 109 return; |
| 110 } |
| 111 |
| 112 // Remove the continuation. |
| 113 LetCont letCont = task.node; |
| 114 Continuation cont = letCont.continuation; |
| 115 _removeNode(letCont); |
| 116 |
| 117 // Replace its invocation with the continuation body. |
| 118 InvokeContinuation invoke = cont.firstRef.parent; |
| 119 InteriorNode invokeParent = invoke.parent; |
| 120 |
| 121 cont.body.parent = invokeParent; |
| 122 invokeParent.body = cont.body; |
| 123 |
| 124 // Substitute the invocation argument for the continuation parameter. |
| 125 for (int i = 0; i < invoke.arguments.length; i++) { |
| 126 Reference argRef = invoke.arguments[i]; |
| 127 argRef.definition.substituteFor(cont.parameters[i]); |
| 128 } |
| 129 |
| 130 // Perform bookkeeping on removed body and scan for new redexes. |
| 131 new _RemovalRedexVisitor(_worklist).visit(invoke); |
| 132 } |
| 133 |
| 134 /// Applies the eta-cont reduction: |
| 135 /// letcont k x = j x in E -> E[j/k]. |
| 136 /// If k is unused, degenerates to dead-cont. |
| 137 void _reduceEtaCont(_ReductionTask task) { |
| 138 // Might have been mutated, recheck if reduction is still valid. |
| 139 // In the following example, the eta-cont reduction of k1 could have been |
| 140 // invalidated by an earlier beta-cont-lin reduction of k0. |
| 141 // |
| 142 // letcont k0 x0 = E0 in |
| 143 // letcont k1 x1 = k0 x1 in E1 |
| 144 if (!_isEtaCont(task.node)) { |
| 145 return; |
| 146 } |
| 147 |
| 148 // Remove the continuation. |
| 149 LetCont letCont = task.node; |
| 150 Continuation cont = letCont.continuation; |
| 151 _removeNode(letCont); |
| 152 |
| 153 InvokeContinuation invoke = cont.body; |
| 154 Continuation wrappedCont = invoke.continuation.definition; |
| 155 |
| 156 // Replace all occurrences with the wrapped continuation. |
| 157 wrappedCont.substituteFor(cont); |
| 158 |
| 159 // Perform bookkeeping on removed body and scan for new redexes. |
| 160 new _RemovalRedexVisitor(_worklist).visit(cont); |
| 161 } |
| 162 } |
| 163 |
| 164 /// Returns true iff the bound primitive is unused. |
| 165 bool _isDeadVal(LetPrim node) => !node.primitive.hasAtLeastOneUse; |
| 166 |
| 167 /// Returns true iff the bound continuation is unused. |
| 168 bool _isDeadCont(LetCont node) => !node.continuation.hasAtLeastOneUse; |
| 169 |
| 170 /// Returns true iff the bound continuation is used exactly once, and that |
| 171 /// use is as the receiver of a continuation invocation. |
| 172 bool _isBetaContLin(LetCont node) { |
| 173 Continuation cont = node.continuation; |
| 174 if (!cont.hasExactlyOneUse) { |
| 175 return false; |
| 176 } |
| 177 |
| 178 if (cont.firstRef.parent is InvokeContinuation) { |
| 179 InvokeContinuation invoke = cont.firstRef.parent; |
| 180 return (cont == invoke.continuation.definition); |
| 181 } |
| 182 |
| 183 return false; |
| 184 |
| 185 } |
| 186 |
| 187 /// Returns true iff the bound continuation consists of a continuation |
| 188 /// invocation, passing on all parameters. Special cases exist (see below). |
| 189 bool _isEtaCont(LetCont node) { |
| 190 Continuation cont = node.continuation; |
| 191 if (!(cont.body is InvokeContinuation)) { |
| 192 return false; |
| 193 } |
| 194 |
| 195 // Special case for continuations passed into one of { InvokeConstructor, |
| 196 // InvokeMethod, InvokeStatic, ConcatenateStrings, TypeOperator, |
| 197 // InvokeSuperMethod }, since their direct-style translation require a |
| 198 // continuation that is used exactly once. |
| 199 // TODO(kmillikin): Modify direct-style translation to handle multiply-used |
| 200 // continuations for Invoke |
| 201 // (see [[tree_ir_builder.Builder.continueWithExpression]]), and subsequently |
| 202 // mark the following forms as eta-cont: |
| 203 // let cont k(v) = k'(v) in ... InvokeMethod(v, f, args, k). |
| 204 if (cont.hasExactlyOneUse) { |
| 205 if (cont.firstRef.parent is InvokeConstructor) { |
| 206 InvokeConstructor parent = cont.firstRef.parent; |
| 207 if (parent.continuation == cont.firstRef) { |
| 208 return false; |
| 209 } |
| 210 } else if (cont.firstRef.parent is InvokeMethod) { |
| 211 InvokeMethod parent = cont.firstRef.parent; |
| 212 if (parent.continuation == cont.firstRef) { |
| 213 return false; |
| 214 } |
| 215 } else if (cont.firstRef.parent is InvokeStatic) { |
| 216 InvokeStatic parent = cont.firstRef.parent; |
| 217 if (parent.continuation == cont.firstRef) { |
| 218 return false; |
| 219 } |
| 220 } else if (cont.firstRef.parent is ConcatenateStrings) { |
| 221 ConcatenateStrings parent = cont.firstRef.parent; |
| 222 if (parent.continuation == cont.firstRef) { |
| 223 return false; |
| 224 } |
| 225 } else if (cont.firstRef.parent is TypeOperator) { |
| 226 TypeOperator parent = cont.firstRef.parent; |
| 227 if (parent.continuation == cont.firstRef) { |
| 228 return false; |
| 229 } |
| 230 } else if (cont.firstRef.parent is InvokeSuperMethod) { |
| 231 InvokeSuperMethod parent = cont.firstRef.parent; |
| 232 if (parent.continuation == cont.firstRef) { |
| 233 return false; |
| 234 } |
| 235 } |
| 236 } |
| 237 |
| 238 InvokeContinuation invoke = cont.body; |
| 239 |
| 240 // Translation to direct style generates different statements for recursive |
| 241 // and non-recursive invokes. It should be possible to apply eta-cont, but |
| 242 // higher order continuations require escape analysis, left as a possibility |
| 243 // for future improvements. |
| 244 if (invoke.isRecursive) { |
| 245 return false; |
| 246 } |
| 247 |
| 248 if (cont.parameters.length != invoke.arguments.length) { |
| 249 return false; |
| 250 } |
| 251 |
| 252 // TODO(jgruber): Linear in the parameter count. Can be improved to near |
| 253 // constant time by using union-find data structure. |
| 254 for (int i = 0; i < cont.parameters.length; i++) { |
| 255 if (invoke.arguments[i].definition != cont.parameters[i]) { |
| 256 return false; |
| 257 } |
| 258 } |
| 259 |
| 260 return true; |
| 261 } |
| 262 |
| 263 /// Traverses a term and adds any found redexes to the worklist. |
| 264 class _RedexVisitor extends RecursiveVisitor { |
| 265 final Set<_ReductionTask> worklist; |
| 266 |
| 267 _RedexVisitor(this.worklist); |
| 268 |
| 269 void processLetPrim(LetPrim node) { |
| 270 if (node.parent == ShrinkingReducer._DELETED) { |
| 271 return; |
| 272 } else if (_isDeadVal(node)) { |
| 273 worklist.add(new _ReductionTask(_ReductionKind.DEAD_VAL, node)); |
| 274 } |
| 275 } |
| 276 |
| 277 void processLetCont(LetCont node) { |
| 278 if (node.parent == ShrinkingReducer._DELETED) { |
| 279 return; |
| 280 } else if (_isDeadCont(node)) { |
| 281 worklist.add(new _ReductionTask(_ReductionKind.DEAD_CONT, node)); |
| 282 } else if (_isEtaCont(node)) { |
| 283 worklist.add(new _ReductionTask(_ReductionKind.ETA_CONT, node)); |
| 284 } else if (_isBetaContLin(node)){ |
| 285 worklist.add(new _ReductionTask(_ReductionKind.BETA_CONT_LIN, node)); |
| 286 } |
| 287 } |
| 288 } |
| 289 |
| 290 /// Traverses a deleted CPS term, marking existing tasks associated with a node |
| 291 /// within the term as deleted (which causes them to be skipped lazily when |
| 292 /// popped from the worklist), and adding newly created redexes to the worklist. |
| 293 class _RemovalRedexVisitor extends _RedexVisitor { |
| 294 _RemovalRedexVisitor(Set<_ReductionTask> worklist) : super(worklist); |
| 295 |
| 296 void processLetPrim(LetPrim node) { |
| 297 node.parent = ShrinkingReducer._DELETED; |
| 298 } |
| 299 |
| 300 void processLetCont(LetCont node) { |
| 301 node.parent = ShrinkingReducer._DELETED; |
| 302 } |
| 303 |
| 304 void processReference(Reference reference) { |
| 305 reference.unlink(); |
| 306 |
| 307 if (reference.definition is Primitive) { |
| 308 Primitive primitive = reference.definition; |
| 309 Node parent = primitive.parent; |
| 310 if (parent is LetPrim && _isDeadVal(parent)) { |
| 311 worklist.add(new _ReductionTask(_ReductionKind.DEAD_VAL, parent)); |
| 312 } |
| 313 } else if (reference.definition is Continuation) { |
| 314 Continuation cont = reference.definition; |
| 315 if (cont.isRecursive && cont.hasAtMostOneUse) { |
| 316 // Convert recursive to nonrecursive continuations. |
| 317 // If the continuation is still in use, it is either dead and will be |
| 318 // removed, or it is called nonrecursively outside its body. |
| 319 cont.isRecursive = false; |
| 320 } |
| 321 Node parent = cont.parent; |
| 322 if (parent is LetCont && _isDeadCont(parent)) { |
| 323 worklist.add(new _ReductionTask(_ReductionKind.DEAD_CONT, parent)); |
| 324 } |
| 325 } |
| 326 } |
| 327 } |
| 328 |
| 329 /// Traverses the CPS term and sets node.parent for each visited node. |
| 330 class _ParentVisitor extends RecursiveVisitor { |
| 331 |
| 332 processFunctionDefinition(FunctionDefinition node) { |
| 333 node.body.parent = node; |
| 334 node.parameters.forEach((Parameter p) => p.parent = node); |
| 335 } |
| 336 |
| 337 // Expressions. |
| 338 |
| 339 processLetPrim(LetPrim node) { |
| 340 node.primitive.parent = node; |
| 341 node.body.parent = node; |
| 342 } |
| 343 |
| 344 processLetCont(LetCont node) { |
| 345 node.continuation.parent = node; |
| 346 node.body.parent = node; |
| 347 } |
| 348 |
| 349 processInvokeStatic(InvokeStatic node) { |
| 350 node.continuation.parent = node; |
| 351 node.arguments.forEach((Reference ref) => ref.parent = node); |
| 352 } |
| 353 |
| 354 processInvokeContinuation(InvokeContinuation node) { |
| 355 node.continuation.parent = node; |
| 356 node.arguments.forEach((Reference ref) => ref.parent = node); |
| 357 } |
| 358 |
| 359 processInvokeMethod(InvokeMethod node) { |
| 360 node.receiver.parent = node; |
| 361 node.continuation.parent = node; |
| 362 node.arguments.forEach((Reference ref) => ref.parent = node); |
| 363 } |
| 364 |
| 365 processInvokeSuperMethod(InvokeSuperMethod node) { |
| 366 node.continuation.parent = node; |
| 367 node.arguments.forEach((Reference ref) => ref.parent = node); |
| 368 } |
| 369 |
| 370 processInvokeConstructor(InvokeConstructor node) { |
| 371 node.continuation.parent = node; |
| 372 node.arguments.forEach((Reference ref) => ref.parent = node); |
| 373 } |
| 374 |
| 375 processConcatenateStrings(ConcatenateStrings node) { |
| 376 node.continuation.parent = node; |
| 377 node.arguments.forEach((Reference ref) => ref.parent = node); |
| 378 } |
| 379 |
| 380 processBranch(Branch node) { |
| 381 node.condition.parent = node; |
| 382 node.trueContinuation.parent = node; |
| 383 node.falseContinuation.parent = node; |
| 384 } |
| 385 |
| 386 processTypeOperator(TypeOperator node) { |
| 387 node.continuation.parent = node; |
| 388 node.receiver.parent = node; |
| 389 } |
| 390 |
| 391 processSetClosureVariable(SetClosureVariable node) { |
| 392 node.body.parent = node; |
| 393 node.value.parent = node; |
| 394 } |
| 395 |
| 396 processDeclareFunction(DeclareFunction node) { |
| 397 node.definition.parent = node; |
| 398 node.body.parent = node; |
| 399 } |
| 400 |
| 401 // Definitions. |
| 402 |
| 403 processLiteralList(LiteralList node) { |
| 404 node.values.forEach((Reference ref) => ref.parent = node); |
| 405 } |
| 406 |
| 407 processLiteralMap(LiteralMap node) { |
| 408 node.values.forEach((Reference ref) => ref.parent = node); |
| 409 node.keys.forEach((Reference ref) => ref.parent = node); |
| 410 } |
| 411 |
| 412 processCreateFunction(CreateFunction node) { |
| 413 node.definition.parent = node; |
| 414 } |
| 415 |
| 416 processContinuation(Continuation node) { |
| 417 node.body.parent = node; |
| 418 node.parameters.forEach((Parameter param) => param.parent = node); |
| 419 } |
| 420 |
| 421 // Conditions. |
| 422 |
| 423 processIsTrue(IsTrue node) { |
| 424 node.value.parent = node; |
| 425 } |
| 426 } |
| 427 |
| 428 class _ReductionKind { |
| 429 final String name; |
| 430 final int hashCode; |
| 431 |
| 432 const _ReductionKind(this.name, this.hashCode); |
| 433 |
| 434 static const _ReductionKind DEAD_VAL = const _ReductionKind('dead-val', 0); |
| 435 static const _ReductionKind DEAD_CONT = const _ReductionKind('dead-cont', 1); |
| 436 static const _ReductionKind BETA_CONT_LIN = |
| 437 const _ReductionKind('beta-cont-lin', 2); |
| 438 static const _ReductionKind ETA_CONT = const _ReductionKind('eta-cont', 3); |
| 439 |
| 440 String toString() => name; |
| 441 } |
| 442 |
| 443 /// Represents a reduction task on the worklist. Implements both hashCode and |
| 444 /// operator== since instantiations are used as Set elements. |
| 445 class _ReductionTask { |
| 446 final _ReductionKind kind; |
| 447 final Node node; |
| 448 |
| 449 int get hashCode { |
| 450 assert(kind.hashCode < (1 << 2)); |
| 451 return (node.hashCode << 2) | kind.hashCode; |
| 452 } |
| 453 |
| 454 _ReductionTask(this.kind, this.node) { |
| 455 // If new node types are added, they must be marked as deleted in |
| 456 // [[_RemovalRedexVisitor]]. |
| 457 assert(node is LetCont || node is LetPrim); |
| 458 } |
| 459 |
| 460 bool operator==(_ReductionTask that) { |
| 461 return (that.kind == this.kind && that.node == this.node); |
| 462 } |
| 463 |
| 464 String toString() => "$kind: $node"; |
| 465 } |
| 466 |
| 467 /// A dummy class used solely to mark nodes as deleted once they are removed |
| 468 /// from a term. |
| 469 class _DeletedNode extends Node { |
| 470 accept(_) => null; |
| 471 } |
| OLD | NEW |