Skip to content

Commit ea5016c

Browse files
author
John Messerly
committed
fixes #516, nested cascade code generation
our let* was not hygenic, due to incorrect use of named substitution [email protected] Review URL: https://codereview.chromium.org/1910233002 .
1 parent 27ed874 commit ea5016c

File tree

5 files changed

+4518
-6181
lines changed

5 files changed

+4518
-6181
lines changed

pkg/dev_compiler/lib/src/compiler/code_generator.dart

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ class CodeGenerator extends GeneralizingAstVisitor
8181

8282
final _privateNames =
8383
new HashMap<LibraryElement, HashMap<String, JS.TemporaryId>>();
84-
final _temps = new HashMap<Element, JS.TemporaryId>();
84+
final _initializingFormalTemps =
85+
new HashMap<ParameterElement, JS.TemporaryId>();
8586

8687
final _dartxVar = new JS.Identifier('dartx');
8788
final _runtimeLibVar = new JS.Identifier('dart');
@@ -1912,12 +1913,9 @@ class CodeGenerator extends GeneralizingAstVisitor
19121913
return _emitParameter(element);
19131914
}
19141915

1916+
// If this is one of our compiler's temporary variables, return its JS form.
19151917
if (element is TemporaryVariableElement) {
1916-
if (name[0] == '#') {
1917-
return new JS.InterpolatedExpression(name.substring(1));
1918-
} else {
1919-
return _getTemp(element, name);
1920-
}
1918+
return element.jsVariable;
19211919
}
19221920

19231921
return new JS.Identifier(name);
@@ -1931,16 +1929,14 @@ class CodeGenerator extends GeneralizingAstVisitor
19311929
/// Rename private names so they don't shadow the private field symbol.
19321930
/// The renamer would handle this, but it would prefer to rename the
19331931
/// temporary used for the private symbol. Instead rename the parameter.
1934-
return _getTemp(element, '${element.name.substring(1)}');
1932+
return _initializingFormalTemps.putIfAbsent(
1933+
element, () => new JS.TemporaryId(element.name.substring(1)));
19351934
}
19361935

19371936
var type = declaration ? emitTypeRef(element.type) : null;
19381937
return new JS.Identifier(element.name, type: type);
19391938
}
19401939

1941-
JS.TemporaryId _getTemp(Element key, String name) =>
1942-
_temps.putIfAbsent(key, () => new JS.TemporaryId(name));
1943-
19441940
List<Annotation> _parameterMetadata(FormalParameter p) =>
19451941
(p is NormalFormalParameter)
19461942
? p.metadata
@@ -2086,7 +2082,7 @@ class CodeGenerator extends GeneralizingAstVisitor
20862082

20872083
// Handle the left hand side, to ensure each of its subexpressions are
20882084
// evaluated only once.
2089-
var vars = <String, JS.Expression>{};
2085+
var vars = <JS.MetaLetVariable, JS.Expression>{};
20902086
var x = _bindLeftHandSide(vars, left, context: left);
20912087
// Capture the result of evaluating the left hand side in a temp.
20922088
var t = _bindValue(vars, 't', x, context: x);
@@ -2097,7 +2093,7 @@ class CodeGenerator extends GeneralizingAstVisitor
20972093

20982094
// Desugar `x += y` as `x = x + y`, ensuring that if `x` has subexpressions
20992095
// (for example, x is IndexExpression) we evaluate those once.
2100-
var vars = <String, JS.Expression>{};
2096+
var vars = <JS.MetaLetVariable, JS.Expression>{};
21012097
var lhs = _bindLeftHandSide(vars, left, context: context);
21022098
var inc = AstBuilder.binaryExpression(lhs, op, right);
21032099
inc.staticElement = element;
@@ -2146,7 +2142,7 @@ class CodeGenerator extends GeneralizingAstVisitor
21462142
//
21472143
// However with MetaLet, we get clean code in statement or void context,
21482144
// or when one of the expressions is stateless, which seems common.
2149-
var vars = <String, JS.Expression>{};
2145+
var vars = <JS.MetaLetVariable, JS.Expression>{};
21502146
var left = _bindValue(vars, 'l', node.target);
21512147
var body = js.call('# == null ? null : #',
21522148
[_visit(left), _emitSet(_stripNullAwareOp(node, left), right)]);
@@ -2722,7 +2718,7 @@ class CodeGenerator extends GeneralizingAstVisitor
27222718
// This should be a hint or warning for dead code.
27232719
if (!isNullable(left)) return _visit(left);
27242720

2725-
var vars = <String, JS.Expression>{};
2721+
var vars = <JS.MetaLetVariable, JS.Expression>{};
27262722
// Desugar `l ?? r` as `l != null ? l : r`
27272723
var l = _visit(_bindValue(vars, 'l', left, context: left));
27282724
return new JS.MetaLet(vars, [
@@ -2771,7 +2767,7 @@ class CodeGenerator extends GeneralizingAstVisitor
27712767
bool _isNull(Expression expr) => expr is NullLiteral;
27722768

27732769
SimpleIdentifier _createTemporary(String name, DartType type,
2774-
{bool nullable: true}) {
2770+
{bool nullable: true, JS.Expression variable}) {
27752771
// We use an invalid source location to signal that this is a temporary.
27762772
// See [_isTemporary].
27772773
// TODO(jmesserly): alternatives are
@@ -2781,7 +2777,10 @@ class CodeGenerator extends GeneralizingAstVisitor
27812777
// * create a new subtype of LocalVariableElementImpl to mark a temp.
27822778
var id =
27832779
new SimpleIdentifier(new StringToken(TokenType.IDENTIFIER, name, -1));
2784-
id.staticElement = new TemporaryVariableElement.forNode(id);
2780+
2781+
variable ??= new JS.TemporaryId(name);
2782+
2783+
id.staticElement = new TemporaryVariableElement.forNode(id, variable);
27852784
id.staticType = type;
27862785
DynamicInvoke.set(id, type.isDynamic);
27872786
addTemporaryVariable(id.staticElement, nullable: nullable);
@@ -2811,7 +2810,7 @@ class CodeGenerator extends GeneralizingAstVisitor
28112810
/// unless [expr] is a SimpleIdentifier, in which case a temporary is not
28122811
/// needed.
28132812
Expression _bindLeftHandSide(
2814-
Map<String, JS.Expression> scope, Expression expr,
2813+
Map<JS.MetaLetVariable, JS.Expression> scope, Expression expr,
28152814
{Expression context}) {
28162815
Expression result;
28172816
if (expr is IndexExpression) {
@@ -2855,14 +2854,15 @@ class CodeGenerator extends GeneralizingAstVisitor
28552854
/// variables), then the resulting code will be simplified automatically.
28562855
///
28572856
/// [scope] will be mutated to contain the new temporary's initialization.
2858-
Expression _bindValue(
2859-
Map<String, JS.Expression> scope, String name, Expression expr,
2857+
Expression _bindValue(Map<JS.MetaLetVariable, JS.Expression> scope,
2858+
String name, Expression expr,
28602859
{Expression context}) {
28612860
// No need to do anything for stateless expressions.
28622861
if (isStateless(_currentFunction, expr, context)) return expr;
28632862

2864-
var t = _createTemporary('#$name', getStaticType(expr));
2865-
scope[name] = _visit(expr);
2863+
var variable = new JS.MetaLetVariable(name);
2864+
var t = _createTemporary(name, getStaticType(expr), variable: variable);
2865+
scope[variable] = _visit(expr);
28662866
return t;
28672867
}
28682868

@@ -2900,7 +2900,7 @@ class CodeGenerator extends GeneralizingAstVisitor
29002900

29012901
// Handle the left hand side, to ensure each of its subexpressions are
29022902
// evaluated only once.
2903-
var vars = <String, JS.Expression>{};
2903+
var vars = <JS.MetaLetVariable, JS.Expression>{};
29042904
var left = _bindLeftHandSide(vars, expr, context: expr);
29052905

29062906
// Desugar `x++` as `(x1 = x0 + 1, x0)` where `x0` is the original value
@@ -2927,7 +2927,7 @@ class CodeGenerator extends GeneralizingAstVisitor
29272927
return js.call('$op#', _visit(expr));
29282928
} else if (op.lexeme == '++' || op.lexeme == '--') {
29292929
// We need a null check, so the increment must be expanded out.
2930-
var vars = <String, JS.Expression>{};
2930+
var vars = <JS.MetaLetVariable, JS.Expression>{};
29312931
var x = _bindLeftHandSide(vars, expr, context: expr);
29322932

29332933
var one = AstBuilder.integerLiteral(1)..staticType = types.intType;
@@ -2960,7 +2960,7 @@ class CodeGenerator extends GeneralizingAstVisitor
29602960
JS.Node visitCascadeExpression(CascadeExpression node) {
29612961
var savedCascadeTemp = _cascadeTarget;
29622962

2963-
var vars = <String, JS.Expression>{};
2963+
var vars = <JS.MetaLetVariable, JS.Expression>{};
29642964
_cascadeTarget = _bindValue(vars, '_', node.target, context: node);
29652965
var sections = _visitList(node.cascadeSections) as List<JS.Expression>;
29662966
sections.add(_visit(_cascadeTarget));
@@ -3779,7 +3779,9 @@ JS.LiteralString _propertyName(String name) => js.string(name, "'");
37793779
/// variable. These objects use instance equality, and should be shared
37803780
/// everywhere in the tree where they are treated as the same variable.
37813781
class TemporaryVariableElement extends LocalVariableElementImpl {
3782-
TemporaryVariableElement.forNode(Identifier name) : super.forNode(name);
3782+
final JS.Expression jsVariable;
3783+
TemporaryVariableElement.forNode(Identifier name, this.jsVariable)
3784+
: super.forNode(name);
37833785

37843786
int get hashCode => identityHashCode(this);
37853787
bool operator ==(Object other) => identical(this, other);

pkg/dev_compiler/lib/src/compiler/js_metalet.dart

Lines changed: 74 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class MetaLet extends Expression {
3636
/// If the expression does not end up using `x` more than once, or if those
3737
/// expressions can be treated as [stateless] (e.g. they are non-mutated
3838
/// variables), then the resulting code will be simplified automatically.
39-
final Map<String, Expression> variables;
39+
final Map<MetaLetVariable, Expression> variables;
4040

4141
/// A list of expressions in the body.
4242
/// The last value should represent the returned value.
@@ -171,49 +171,40 @@ class MetaLet extends Expression {
171171
}
172172

173173
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) {
191174
// Visit the tree and count how many times each temp was used.
192175
var counter = new _VariableUseCounter();
176+
var node = new Block(statements);
193177
node.accept(counter);
194178
// Also count the init expressions.
195179
for (var init in variables.values) init.accept(counter);
196180

197-
var substitutions = {};
198-
_substitute(node) => new Template(null, node).safeCreate(substitutions);
199-
200-
variables.forEach((name, init) {
181+
var initializers = <VariableInitialization>[];
182+
var substitutions = <MetaLetVariable, Expression>{};
183+
variables.forEach((variable, init) {
201184
// Since this is let*, subsequent variables can refer to previous ones,
202185
// 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);
186+
init = _substitute(init, substitutions);
187+
int n = counter.counts[variable];
188+
if (n == 1) {
189+
// Replace interpolated exprs with their value, if it only occurs once.
190+
substitutions[variable] = init;
207191
} else {
208-
params.add(substitutions[name] = new TemporaryId(name));
209-
values.add(init);
192+
// Otherwise replace it with a temp, which will be assigned once.
193+
var temp = new TemporaryId(variable.displayName);
194+
substitutions[variable] = temp;
195+
initializers.add(new VariableInitialization(temp, init));
210196
}
211197
});
212198

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);
199+
// Interpolate the body.
200+
node = _substitute(node, substitutions);
201+
if (initializers.isNotEmpty) {
202+
node = new Block([
203+
new VariableDeclarationList('let', initializers).toStatement(),
204+
node
205+
]);
206+
}
207+
return node;
217208
}
218209

219210
/// If we finish with an assignment to an identifier, try to simplify the
@@ -231,11 +222,10 @@ class MetaLet extends Expression {
231222
///
232223
MetaLet _simplifyAssignment(Identifier left, {bool isDeclaration: false}) {
233224
// See if the result value is a let* temporary variable.
234-
if (body.last is! InterpolatedExpression) return null;
225+
if (body.last is! MetaLetVariable) return null;
235226

236-
InterpolatedExpression last = body.last;
237-
String name = last.nameOrPosition;
238-
if (!variables.containsKey(name)) return null;
227+
MetaLetVariable result = body.last;
228+
if (!variables.containsKey(result)) return null;
239229

240230
// Variables declared can't be used inside their initializer, so make
241231
// sure we don't transform an assignment into an initializer.
@@ -252,8 +242,8 @@ class MetaLet extends Expression {
252242
if (finder.found) return null;
253243
}
254244

255-
var vars = new Map<String, Expression>.from(variables);
256-
var value = vars.remove(name);
245+
var vars = new Map<MetaLetVariable, Expression>.from(variables);
246+
var value = vars.remove(result);
257247
Expression assign;
258248
if (isDeclaration) {
259249
// Technically, putting one of these in a comma expression is not
@@ -266,18 +256,59 @@ class MetaLet extends Expression {
266256
}
267257

268258
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(),
259+
newBody = _substitute(newBody, {result: left});
260+
return new MetaLet(vars, newBody.commaToExpressionList(),
271261
statelessResult: statelessResult);
272262
}
273263
}
274264

265+
/// Similar to [Template.instantiate] but works with free variables.
266+
Node _substitute(Node tree, Map<MetaLetVariable, Expression> substitutions) {
267+
var generator = new InstantiatorGeneratorVisitor(/*forceCopy:*/ false);
268+
var instantiator = generator.compile(tree);
269+
var nodes = new List<MetaLetVariable>.from(generator
270+
.analysis.containsInterpolatedNode
271+
.where((n) => n is MetaLetVariable));
272+
if (nodes.isEmpty) return tree;
273+
274+
return instantiator(new Map.fromIterable(nodes,
275+
key: (v) => (v as MetaLetVariable).nameOrPosition,
276+
value: (v) => substitutions[v] ?? v));
277+
}
278+
279+
/// A temporary variable used in a [MetaLet].
280+
///
281+
/// Each instance of this class represents a fresh variable. The same object
282+
/// should be used everywhere to refer to the same variable. Different variables
283+
/// with the same name are different, and will be renamed later on, if needed.
284+
///
285+
/// These variables will be replaced when the `let*` is complete, depending on
286+
/// how often they occur and whether they can be optimized away. See [MetaLet]
287+
/// for more information.
288+
///
289+
/// This class should never reach our final JS code.
290+
class MetaLetVariable extends InterpolatedExpression {
291+
/// The suggested display name of this variable.
292+
///
293+
/// This name should not be used
294+
final String displayName;
295+
296+
/// Compute fresh IDs to avoid
297+
static int _uniqueId = 0;
298+
299+
MetaLetVariable(String displayName)
300+
: displayName = displayName,
301+
super(displayName + '@${++_uniqueId}');
302+
}
303+
275304
class _VariableUseCounter extends BaseVisitor {
276-
final counts = <String, int>{};
305+
final counts = <MetaLetVariable, int>{};
277306
@override
278307
visitInterpolatedExpression(InterpolatedExpression node) {
279-
int n = counts[node.nameOrPosition];
280-
counts[node.nameOrPosition] = n == null ? 1 : n + 1;
308+
if (node is MetaLetVariable) {
309+
int n = counts[node];
310+
counts[node] = n == null ? 1 : n + 1;
311+
}
281312
}
282313
}
283314

pkg/dev_compiler/lib/src/js_ast/template.dart

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -121,15 +121,6 @@ class Template {
121121
}
122122
return instantiator(arguments);
123123
}
124-
125-
/// Like [instantiate] but works with free variables.
126-
Node safeCreate(Map arguments) {
127-
if (holeNames.isEmpty) return ast;
128-
return instantiator(new Map.fromIterable(holeNames, value: (name) {
129-
var a = arguments[name];
130-
return a != null ? a : new InterpolatedExpression(name);
131-
}));
132-
}
133124
}
134125

135126
/**

0 commit comments

Comments
 (0)