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

Unified Diff: pkg/kernel/lib/transformations/closure/converter.dart

Issue 2991853002: Fix duplicate context creation when closures appear in initializers. (Closed)
Patch Set: Review comments: don't use the redirecting technique unless necessary. Created 3 years, 5 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 side-by-side diff with in-line comments
Download patch
Index: pkg/kernel/lib/transformations/closure/converter.dart
diff --git a/pkg/kernel/lib/transformations/closure/converter.dart b/pkg/kernel/lib/transformations/closure/converter.dart
index 713ab38cdc6995e812fabb5dad3932f2fabd491d..3772328961a711496ee484e0ed36f20e3d5df43f 100644
--- a/pkg/kernel/lib/transformations/closure/converter.dart
+++ b/pkg/kernel/lib/transformations/closure/converter.dart
@@ -28,6 +28,7 @@ import '../../ast.dart'
InvocationExpression,
Library,
LocalInitializer,
+ RedirectingInitializer,
Member,
MethodInvocation,
Name,
@@ -61,7 +62,7 @@ import '../../type_algebra.dart' show substitute;
import 'clone_without_body.dart' show CloneWithoutBody;
-import 'context.dart' show Context, NoContext;
+import 'context.dart' show Context, NoContext, LocalContext;
import 'info.dart' show ClosureInfo;
@@ -69,7 +70,8 @@ import 'rewriter.dart' show AstRewriter, BlockRewriter, InitializerListRewriter;
class ClosureConverter extends Transformer {
final CoreTypes coreTypes;
- final Set<VariableDeclaration> capturedVariables;
+ final Map<VariableDeclaration, int /*capture flags -- see info.dart*/ >
Dmitry Stefantsov 2017/07/31 15:05:35 Please, add put this comment on its own line above
sjindel 2017/07/31 15:32:16 Done.
+ capturedVariables;
final Map<FunctionNode, Set<TypeParameter>> capturedTypeVariables;
final Map<FunctionNode, VariableDeclaration> thisAccess;
final Map<FunctionNode, String> localNames;
@@ -181,44 +183,125 @@ class ClosureConverter extends Transformer {
return node;
}
- void extendContextWith(VariableDeclaration parameter) {
- context.extend(parameter, new VariableGet(parameter));
- }
+ extendContextConditionally(bool inInitializer) =>
Dmitry Stefantsov 2017/07/31 15:05:35 As Karl pointed out, the arrow notation is usually
Dmitry Stefantsov 2017/07/31 15:05:35 I would suggest to make [inInitializer] a named pa
sjindel 2017/07/31 15:32:16 Done.
sjindel 2017/07/31 15:32:16 Done.
+ (VariableDeclaration parameter) {
+ var captured = capturedVariables[parameter];
Dmitry Stefantsov 2017/07/31 15:05:35 Also I think that the name [captured] is a bit mis
Dmitry Stefantsov 2017/07/31 15:05:35 I think using type annotation `int` here helps wit
sjindel 2017/07/31 15:32:16 Done.
sjindel 2017/07/31 15:32:16 Done.
+
+ // When moving variables into the context while scanning initializers,
+ // we need to add the variable if it's captured in an initializer,
+ // whether or not it's used/captured in the body. However, in the body,
+ // we only need to add the variable into the context if it's *not*
+ // captured in an initializer.
+ if (captured != null && inInitializer
+ ? (captured & ClosureInfo.INSIDE_INITIALIZER) > 0
+ : captured == ClosureInfo.OUTSIDE_INITIALIZER) {
+ context.extend(parameter, new VariableGet(parameter));
+ }
+ return captured ?? 0;
+ };
TreeNode visitConstructor(Constructor node) {
assert(isEmptyContext);
currentMember = node;
+ // If we created a context for the initializers, we need to re-use that
+ // context in the body of the function. Unfortunately, the context is
+ // declared in a local initializer and local initializers aren't visible
+ // in the body of the constructor. To work around this issue, we move the
+ // body into a new constructor and make this constructor redirect to that
+ // one, passing the context as an argument to the new constructor.
+ var movingCtor = false;
+
// Transform initializers.
if (node.initializers.length > 0) {
var initRewriter = new InitializerListRewriter(node);
rewriter = initRewriter;
context = new NoContext(this);
+ int capturedBoth =
+ ClosureInfo.OUTSIDE_INITIALIZER | ClosureInfo.INSIDE_INITIALIZER;
+
// TODO(karlklose): add a fine-grained analysis of captured parameters.
- node.function.positionalParameters
- .where(capturedVariables.contains)
- .forEach(extendContextWith);
- node.function.namedParameters
- .where(capturedVariables.contains)
- .forEach(extendContextWith);
+ movingCtor = node.function.positionalParameters
+ .map(extendContextConditionally(true))
+ .any((flags) => flags == capturedBoth) ||
+ node.function.namedParameters
+ .map(extendContextConditionally(true))
+ .any((flags) => flags == capturedBoth);
transformList(node.initializers, this, node);
node.initializers.insertAll(0, initRewriter.prefix);
- context = rewriter = null;
+ rewriter = null;
}
// Transform constructor body.
FunctionNode function = node.function;
if (function.body != null && function.body is! EmptyStatement) {
setupContextForFunctionBody(function);
+ if (!movingCtor) context = new NoContext(this);
VariableDeclaration self = thisAccess[currentMemberFunction];
if (self != null) {
context.extend(self, new ThisExpression());
}
node.function.accept(this);
- resetContext();
+
+ if (movingCtor) {
+ var contextDecl = (context as LocalContext).self;
+ var newCtorName = new Name("${node.name.name}#redir");
+ var newCtor = new Constructor(node.function, name: newCtorName);
+ newClassMembers.add(newCtor);
+
+ LocalInitializer contextDeclInit = null;
+ for (var init in node.initializers) {
Dmitry Stefantsov 2017/07/31 15:05:35 This version is definitely better. I was thinking
sjindel 2017/07/31 15:32:16 True, but I feel that this approach keeps the logi
Dmitry Stefantsov 2017/08/01 09:36:13 But it's not completely isolated. I think that su
+ if (init is LocalInitializer && init.variable == contextDecl) {
+ contextDeclInit = init;
+ } else {
+ newCtor.initializers.add(init);
+ }
+ }
+
+ node.initializers = node.initializers.take(1).toList();
+
+ var cv = new CloneVisitor();
+ var oldCtorParams = function.positionalParameters
+ .map(cv.visitVariableDeclaration)
+ .toList();
+ var oldCtorNamedParams =
+ function.namedParameters.map(cv.visitVariableDeclaration).toList();
+
+ function.positionalParameters.addAll(function.namedParameters);
+ function.namedParameters = [];
+
+ var args = <Expression>[];
+ assert(function.typeParameters.length == 0);
+ args.addAll(oldCtorParams.map((decl) => new VariableGet(decl)));
+ args.addAll(oldCtorNamedParams.map((decl) => new VariableGet(decl)));
+
+ node.function = new FunctionNode(new EmptyStatement(),
+ typeParameters: [],
+ positionalParameters: oldCtorParams,
+ namedParameters: oldCtorNamedParams,
+ requiredParameterCount: function.requiredParameterCount,
+ returnType: function.returnType,
+ asyncMarker: function.asyncMarker,
+ dartAsyncMarker: function.dartAsyncMarker);
+ node.function.parent = node;
+
+ var oldCtorDecl = cv.visitVariableDeclaration(contextDecl);
+ contextDecl.initializer = null;
+ function.positionalParameters.add(contextDecl);
+ function.requiredParameterCount++;
+
+ contextDeclInit.variable = oldCtorDecl;
+ oldCtorDecl.parent = contextDeclInit;
+
+ args.add(new VariableGet(oldCtorDecl));
+ var redirInit =
+ new RedirectingInitializer(newCtor, new Arguments(args));
+ node.initializers.add(redirInit);
+ }
}
+ resetContext();
return node;
}
@@ -274,7 +357,7 @@ class ClosureConverter extends Transformer {
TreeNode visitFunctionDeclaration(FunctionDeclaration node) {
/// Is this closure itself captured by a closure?
- bool isCaptured = capturedVariables.contains(node.variable);
+ bool isCaptured = capturedVariables.containsKey(node.variable);
if (isCaptured) {
context.extend(node.variable, new InvalidExpression());
}
@@ -369,6 +452,10 @@ class ClosureConverter extends Transformer {
FunctionNode function = node.function;
if (function.body != null) {
setupContextForFunctionBody(function);
+ // Start with no context. This happens after setting up _currentBlock
+ // so statements can be emitted into _currentBlock if necessary.
+ context = new NoContext(this);
Dmitry Stefantsov 2017/07/31 15:05:35 Why was [context] initialization moved here?
sjindel 2017/07/31 15:32:16 It didn't really make sense to do it in the functi
Dmitry Stefantsov 2017/08/01 09:36:13 If we had a special kind of rewriter for the case
+
VariableDeclaration self = thisAccess[currentMemberFunction];
if (self != null) {
context.extend(self, new ThisExpression());
@@ -386,9 +473,6 @@ class ClosureConverter extends Transformer {
currentMemberFunction = function;
// Ensure that the body is a block which becomes the current block.
rewriter = makeRewriterForBody(function);
- // Start with no context. This happens after setting up _currentBlock
- // so statements can be emitted into _currentBlock if necessary.
- context = new NoContext(this);
}
void resetContext() {
@@ -403,21 +487,17 @@ class ClosureConverter extends Transformer {
}
TreeNode visitLocalInitializer(LocalInitializer node) {
- assert(!capturedVariables.contains(node.variable));
+ assert(!capturedVariables.containsKey(node.variable));
node.transformChildren(this);
return node;
}
TreeNode visitFunctionNode(FunctionNode node) {
transformList(node.typeParameters, this, node);
- // TODO: Can parameters contain initializers (e.g., for optional ones) that
- // need to be closure converted?
- node.positionalParameters
- .where(capturedVariables.contains)
- .forEach(extendContextWith);
- node.namedParameters
- .where(capturedVariables.contains)
- .forEach(extendContextWith);
+ // Initializers for optional parameters must be compile-time constants,
+ // which excludes closures.
+ node.positionalParameters.forEach(extendContextConditionally(false));
+ node.namedParameters.forEach(extendContextConditionally(false));
assert(node.body != null);
node.body = node.body.accept(this);
node.body.parent = node;
@@ -435,7 +515,7 @@ class ClosureConverter extends Transformer {
TreeNode visitVariableDeclaration(VariableDeclaration node) {
node.transformChildren(this);
- if (!capturedVariables.contains(node)) return node;
+ if (!capturedVariables.containsKey(node)) return node;
if (node.initializer == null && node.parent is FunctionNode) {
// If the variable is a function parameter and doesn't have an
// initializer, just use this variable name to put it into the context.
@@ -455,7 +535,7 @@ class ClosureConverter extends Transformer {
}
TreeNode visitVariableGet(VariableGet node) {
- return capturedVariables.contains(node.variable)
+ return capturedVariables.containsKey(node.variable)
? context.lookup(node.variable)
: node;
}
@@ -463,7 +543,7 @@ class ClosureConverter extends Transformer {
TreeNode visitVariableSet(VariableSet node) {
node.transformChildren(this);
- return capturedVariables.contains(node.variable)
+ return capturedVariables.containsKey(node.variable)
? context.assign(node.variable, node.value,
voidContext: isInVoidContext(node))
: node;
@@ -499,7 +579,7 @@ class ClosureConverter extends Transformer {
}
TreeNode visitForStatement(ForStatement node) {
- if (node.variables.any(capturedVariables.contains)) {
+ if (node.variables.any(capturedVariables.containsKey)) {
// In Dart, loop variables are new variables on each iteration of the
// loop. This is only observable when a loop variable is captured by a
// closure, which is the situation we're in here. So we transform the
@@ -536,7 +616,7 @@ class ClosureConverter extends Transformer {
}
TreeNode visitForInStatement(ForInStatement node) {
- if (capturedVariables.contains(node.variable)) {
+ if (capturedVariables.containsKey(node.variable)) {
// In Dart, loop variables are new variables on each iteration of the
// loop. This is only observable when the loop variable is captured by a
// closure, so we need to transform the for-in loop when `node.variable`
@@ -573,7 +653,7 @@ class ClosureConverter extends Transformer {
TreeNode visitCatch(Catch node) {
VariableDeclaration exception = node.exception;
VariableDeclaration stackTrace = node.stackTrace;
- if (stackTrace != null && capturedVariables.contains(stackTrace)) {
+ if (stackTrace != null && capturedVariables.containsKey(stackTrace)) {
Block block = node.body = ensureBlock(node.body);
block.parent = node;
node.stackTrace = new VariableDeclaration(null);
@@ -582,7 +662,7 @@ class ClosureConverter extends Transformer {
block.statements.insert(0, stackTrace);
stackTrace.parent = block;
}
- if (exception != null && capturedVariables.contains(exception)) {
+ if (exception != null && capturedVariables.containsKey(exception)) {
Block block = node.body = ensureBlock(node.body);
block.parent = node;
node.exception = new VariableDeclaration(null);
« no previous file with comments | « no previous file | pkg/kernel/lib/transformations/closure/info.dart » ('j') | pkg/kernel/lib/transformations/closure/info.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698