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

Side by Side 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, pt. 2 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698