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

Side by Side Diff: lib/src/codegen/js_metalet.dart

Issue 1879373004: Implement modular compilation (Closed) Base URL: git@github.com:dart-lang/dev_compiler.git@master
Patch Set: Created 4 years, 8 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
« no previous file with comments | « lib/src/codegen/js_interop.dart ('k') | lib/src/codegen/js_module_item_order.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2015, 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 // TODO(jmesserly): import from its own package
6 import '../js/js_ast.dart';
7 import '../js/precedence.dart';
8
9 import 'js_names.dart' show TemporaryId;
10
11 /// A synthetic `let*` node, similar to that found in Scheme.
12 ///
13 /// For example, postfix increment can be desugared as:
14 ///
15 /// // psuedocode mix of Scheme and JS:
16 /// (let* (x1=expr1, x2=expr2, t=x1[x2]) { x1[x2] = t + 1; t })
17 ///
18 /// [MetaLet] will simplify itself automatically when [toExpression],
19 /// [toStatement], [toReturn], or [toYieldStatement] is called.
20 ///
21 /// * variables used once will be inlined.
22 /// * if used in a statement context they can emit as blocks.
23 /// * if return value is not used it can be eliminated, see [statelessResult].
24 /// * if there are no variables, the codegen will be simplified.
25 ///
26 /// Because this deals with JS AST nodes, it is not aware of any Dart semantics
27 /// around statelessness (such as `final` variables). [variables] should not
28 /// be created for these Dart expressions.
29 ///
30 class MetaLet extends Expression {
31 /// Creates a temporary to contain the value of [expr]. The temporary can be
32 /// used multiple times in the resulting expression. For example:
33 /// `expr ** 2` could be compiled as `expr * expr`. The temporary scope will
34 /// ensure `expr` is only evaluated once: `(x => x * x)(expr)`.
35 ///
36 /// If the expression does not end up using `x` more than once, or if those
37 /// expressions can be treated as [stateless] (e.g. they are non-mutated
38 /// variables), then the resulting code will be simplified automatically.
39 final Map<String, Expression> variables;
40
41 /// A list of expressions in the body.
42 /// The last value should represent the returned value.
43 final List<Expression> body;
44
45 /// True if the final expression in [body] can be skipped in [toStatement].
46 final bool statelessResult;
47
48 /// We run [toExpression] implicitly when the JS AST is visited, to get the
49 /// transformation to happen before the tree is printed.
50 /// This happens multiple times, so ensure the expression form is cached.
51 Expression _expression;
52
53 MetaLet(this.variables, this.body, {this.statelessResult: false});
54
55 /// Returns an expression that ignores the result. This is a cross between
56 /// [toExpression] and [toStatement]. Used for C-style for-loop updaters,
57 /// which is an expression syntactically, but functions more like a statement.
58 Expression toVoidExpression() {
59 var block = toStatement();
60 var s = block.statements;
61 if (s.length == 1 && s.first is ExpressionStatement) {
62 ExpressionStatement es = s.first;
63 return es.expression;
64 }
65
66 return _toInvokedFunction(block);
67 }
68
69 Expression toAssignExpression(Expression left) {
70 if (left is Identifier) {
71 var simple = _simplifyAssignment(left);
72 if (simple != null) return simple;
73
74 var exprs = body.toList();
75 exprs.add(exprs.removeLast().toAssignExpression(left));
76 return new MetaLet(variables, exprs);
77 }
78 return super.toAssignExpression(left);
79 }
80
81 Statement toVariableDeclaration(Identifier name) {
82 var simple = _simplifyAssignment(name, isDeclaration: true);
83 if (simple != null) return simple.toStatement();
84 return super.toVariableDeclaration(name);
85 }
86
87 Expression toExpression() {
88 if (_expression != null) return _expression;
89 var block = toReturn();
90 var s = block.statements;
91 if (s.length == 1 && s.first is Return) {
92 Return es = s.first;
93 return _expression = es.value;
94 }
95 // Wrap it in an immediately called function to get in expression context.
96 return _expression = _toInvokedFunction(block);
97 }
98
99 Block toStatement() {
100 // Skip return value if not used.
101 var statements = body.map((e) => e.toStatement()).toList();
102 if (statelessResult) statements.removeLast();
103 return _finishStatement(statements);
104 }
105
106 Block toReturn() {
107 var statements = body
108 .map((e) => e == body.last ? e.toReturn() : e.toStatement())
109 .toList();
110 return _finishStatement(statements);
111 }
112
113 Block toYieldStatement({bool star: false}) {
114 var statements = body
115 .map((e) =>
116 e == body.last ? e.toYieldStatement(star: star) : e.toStatement())
117 .toList();
118 return _finishStatement(statements);
119 }
120
121 accept(NodeVisitor visitor) {
122 // TODO(jmesserly): we special case vistors from js_ast.Template, because it
123 // doesn't know about MetaLet. Should we integrate directly?
124 if (visitor is InstantiatorGeneratorVisitor) {
125 return _templateVisitMetaLet(visitor);
126 } else if (visitor is InterpolatedNodeAnalysis) {
127 return visitor.visitNode(this);
128 } else {
129 return toExpression().accept(visitor);
130 }
131 }
132
133 void visitChildren(NodeVisitor visitor) {
134 // TODO(jmesserly): we special case vistors from js_ast.Template, because it
135 // doesn't know about MetaLet. Should we integrate directly?
136 if (visitor is InterpolatedNodeAnalysis ||
137 visitor is InstantiatorGeneratorVisitor) {
138 variables.values.forEach((v) => v.accept(visitor));
139 body.forEach((v) => v.accept(visitor));
140 } else {
141 toExpression().visitChildren(visitor);
142 }
143 }
144
145 /// This generates as either a comma expression or a call.
146 int get precedenceLevel => variables.isEmpty ? EXPRESSION : CALL;
147
148 /// Patch to pretend [Template] supports visitMetaLet.
149 Instantiator _templateVisitMetaLet(InstantiatorGeneratorVisitor visitor) {
150 var valueInstantiators = variables.values.map(visitor.visit);
151 var bodyInstantiators = body.map(visitor.visit);
152
153 return (args) => new MetaLet(
154 new Map.fromIterables(
155 variables.keys, valueInstantiators.map((i) => i(args))),
156 bodyInstantiators.map((i) => i(args)).toList(),
157 statelessResult: statelessResult);
158 }
159
160 Expression _toInvokedFunction(Statement block) {
161 var finder = new _YieldFinder();
162 block.accept(finder);
163 if (!finder.hasYield) {
164 return new Call(new ArrowFun([], block), []);
165 }
166 // If we have a yield, it's more tricky. We'll create a `function*`, which
167 // we `yield*` to immediately invoke. We also may need to bind this:
168 Expression fn = new Fun([], block, isGenerator: true);
169 if (finder.hasThis) fn = js.call('#.bind(this)', fn);
170 return new Yield(new Call(fn, []), star: true);
171 }
172
173 Block _finishStatement(List<Statement> statements) {
174 var params = <TemporaryId>[];
175 var values = <Expression>[];
176 var block = _build(params, values, new Block(statements));
177 if (params.isEmpty) return block;
178
179 var vars = [];
180 for (int i = 0; i < params.length; i++) {
181 vars.add(new VariableInitialization(params[i], values[i]));
182 }
183
184 return new Block(<Statement>[
185 new VariableDeclarationList('let', vars).toStatement(),
186 block
187 ]);
188 }
189
190 Node _build(List<TemporaryId> params, List<Expression> values, Node node) {
191 // Visit the tree and count how many times each temp was used.
192 var counter = new _VariableUseCounter();
193 node.accept(counter);
194 // Also count the init expressions.
195 for (var init in variables.values) init.accept(counter);
196
197 var substitutions = {};
198 _substitute(node) => new Template(null, node).safeCreate(substitutions);
199
200 variables.forEach((name, init) {
201 // Since this is let*, subsequent variables can refer to previous ones,
202 // so we need to substitute here.
203 init = _substitute(init);
204 int n = counter.counts[name];
205 if (n == null || n < 2) {
206 substitutions[name] = _substitute(init);
207 } else {
208 params.add(substitutions[name] = new TemporaryId(name));
209 values.add(init);
210 }
211 });
212
213 // Interpolate the body:
214 // Replace interpolated exprs with their value, if it only occurs once.
215 // Otherwise replace it with a temp, which will be assigned once.
216 return _substitute(node);
217 }
218
219 /// If we finish with an assignment to an identifier, try to simplify the
220 /// block. For example:
221 ///
222 /// ((_) => _.add(1), _.add(2), result = _)([])
223 ///
224 /// Can be transformed to:
225 ///
226 /// (result = [], result.add(1), result.add(2), result)
227 ///
228 /// However we should not simplify in this case because `result` is read:
229 ///
230 /// ((_) => _.addAll(result), _.add(2), result = _)([])
231 ///
232 MetaLet _simplifyAssignment(Identifier left, {bool isDeclaration: false}) {
233 // See if the result value is a let* temporary variable.
234 if (body.last is! InterpolatedExpression) return null;
235
236 InterpolatedExpression last = body.last;
237 String name = last.nameOrPosition;
238 if (!variables.containsKey(name)) return null;
239
240 // Variables declared can't be used inside their initializer, so make
241 // sure we don't transform an assignment into an initializer.
242 // If this already was a declaration, then we know it's legal, so we can
243 // skip the check.
244 if (!isDeclaration) {
245 var finder = new _IdentFinder(left.name);
246 for (var expr in body) {
247 if (finder.found) break;
248 expr.accept(finder);
249 }
250 // If the identifier was used elsewhere, bail, because we're going to
251 // change the order of when the assignment happens.
252 if (finder.found) return null;
253 }
254
255 var vars = new Map<String, Expression>.from(variables);
256 var value = vars.remove(name);
257 Expression assign;
258 if (isDeclaration) {
259 // Technically, putting one of these in a comma expression is not
260 // legal. However when isDeclaration is true, toStatement will be
261 // called immediately on the MetaLet, which results in legal JS.
262 assign = new VariableDeclarationList(
263 'let', [new VariableInitialization(left, value)]);
264 } else {
265 assign = value.toAssignExpression(left);
266 }
267
268 var newBody = new Expression.binary([assign]..addAll(body), ',');
269 Binary comma = new Template(null, newBody).safeCreate({name: left});
270 return new MetaLet(vars, comma.commaToExpressionList(),
271 statelessResult: statelessResult);
272 }
273 }
274
275 class _VariableUseCounter extends BaseVisitor {
276 final counts = <String, int>{};
277 @override
278 visitInterpolatedExpression(InterpolatedExpression node) {
279 int n = counts[node.nameOrPosition];
280 counts[node.nameOrPosition] = n == null ? 1 : n + 1;
281 }
282 }
283
284 class _IdentFinder extends BaseVisitor {
285 final String name;
286 bool found = false;
287 _IdentFinder(this.name);
288
289 @override
290 visitIdentifier(Identifier node) {
291 if (node.name == name) found = true;
292 }
293
294 @override
295 visitNode(Node node) {
296 if (!found) super.visitNode(node);
297 }
298 }
299
300 class _YieldFinder extends BaseVisitor {
301 bool hasYield = false;
302 bool hasThis = false;
303 bool _nestedFunction = false;
304 @override
305 visitThis(This node) {
306 hasThis = true;
307 }
308
309 @override
310 visitFunctionExpression(FunctionExpression node) {
311 var savedNested = _nestedFunction;
312 _nestedFunction = true;
313 super.visitFunctionExpression(node);
314 _nestedFunction = savedNested;
315 }
316
317 @override
318 visitYield(Yield node) {
319 if (!_nestedFunction) hasYield = true;
320 }
321
322 @override
323 visitNode(Node node) {
324 if (!hasYield) super.visitNode(node);
325 }
326 }
OLDNEW
« no previous file with comments | « lib/src/codegen/js_interop.dart ('k') | lib/src/codegen/js_module_item_order.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698