blob: 89fb18c1451a9493e626b1f2708b68a6c579620b [file] [log] [blame]
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:kernel/ast.dart' as ir;
import '../closure.dart';
import '../elements/entities.dart';
import '../kernel/element_map.dart';
import 'closure.dart';
/// This builder walks the code to determine what variables are captured/free at
/// various points to build CapturedScope that can respond to queries
/// about how a particular variable is being used at any point in the code.
class CapturedScopeBuilder extends ir.Visitor {
/// A map of each visited call node with the associated information about what
/// variables are captured/used. Each ir.Node key corresponds to a scope that
/// was encountered while visiting a closure (initially called through
/// [translateLazyIntializer] or [translateConstructorOrProcedure]).
final Map<ir.Node, CapturedScope> _scopesCapturedInClosureMap;
/// Map entities to their corresponding scope information (such as what
/// variables are captured/used).
final Map<Entity, ScopeInfo> _scopeInfoMap;
/// A map of the nodes that we have flagged as necessary to generate closure
/// classes for in a later stage. We map that node to information ascertained
/// about variable usage in the surrounding scope.
final Map<ir.TreeNode /* ir.Field | ir.FunctionNode */, ScopeInfo>
_closuresToGenerate;
/// The local variables that have been declared in the current scope.
List<ir.VariableDeclaration> _scopeVariables;
/// Pointer to the context in which this closure is executed.
/// For example, in the expression `var foo = () => 3 + i;`, the executable
/// context as we walk the nodes in that expression is the ir.Field `foo`.
ir.Node _executableContext;
/// A flag to indicate if we are currently inside a closure.
bool _isInsideClosure = false;
/// Pointer to the original node where this closure builder started.
ir.Node _outermostNode;
/// Keep track of the mutated local variables so that we don't need to box
/// non-mutated variables.
Set<ir.VariableDeclaration> _mutatedVariables =
new Set<ir.VariableDeclaration>();
/// The set of variables that are accessed in some form, whether they are
/// mutated or not.
Set<ir.VariableDeclaration> _capturedVariables =
new Set<ir.VariableDeclaration>();
/// If true, the visitor is currently traversing some nodes that are inside a
/// try block.
bool _inTry = false;
/// Lookup the local entity that corresponds to a kernel variable declaration.
final KernelToLocalsMap _localsMap;
/// The current scope we are in.
KernelScopeInfo _currentScopeInfo;
final KernelToElementMap _kernelToElementMap;
/// The original entity from which we start the tree-walk to find closure and
/// scope information.
final Entity _originalEntity;
CapturedScopeBuilder(
this._scopesCapturedInClosureMap,
this._scopeInfoMap,
this._originalEntity,
this._closuresToGenerate,
this._localsMap,
this._kernelToElementMap);
/// Update the [CapturedScope] object corresponding to
/// this node if any variables are captured.
void attachCapturedScopeVariables(ir.Node node) {
Set<Local> capturedVariablesForScope = new Set<Local>();
for (ir.VariableDeclaration variable in _scopeVariables) {
// No need to box non-assignable elements.
if (variable.isFinal || variable.isConst) continue;
if (!_mutatedVariables.contains(variable)) continue;
if (_capturedVariables.contains(variable)) {
capturedVariablesForScope.add(_localsMap.getLocal(variable));
}
}
if (!capturedVariablesForScope.isEmpty) {
ThisLocal thisLocal = null;
if (node is ir.Member && node.isInstanceMember) {
if (node is ir.Procedure) {
thisLocal = new ThisLocal(_kernelToElementMap.getMethod(node));
} else if (node is ir.Field) {
thisLocal = new ThisLocal(_kernelToElementMap.getField(node));
}
} else if (node is ir.Constructor) {
thisLocal = new ThisLocal(_kernelToElementMap.getConstructor(node));
}
assert(_scopeInfoMap[_nodeToEntity(node)] != null);
_scopesCapturedInClosureMap[node] = new KernelCapturedScope(
capturedVariablesForScope,
_nodeToEntity(_executableContext),
thisLocal);
}
}
Entity _nodeToEntity(ir.Node node) {
if (node is ir.Member) {
return _kernelToElementMap.getMember(node);
} else {
return _kernelToElementMap.getLocalFunction(node);
}
}
/// Perform book-keeping with the current set of local variables that have
/// been seen thus far before entering this new scope.
void enterNewScope(ir.Node node, Function visitNewScope) {
List<ir.VariableDeclaration> oldScopeVariables = _scopeVariables;
_scopeVariables = <ir.VariableDeclaration>[];
visitNewScope();
attachCapturedScopeVariables(node);
_mutatedVariables.removeAll(_scopeVariables);
_scopeVariables = oldScopeVariables;
}
@override
void defaultNode(ir.Node node) {
node.visitChildren(this);
}
@override
visitTryCatch(ir.TryCatch node) {
bool oldInTry = _inTry;
_inTry = true;
node.visitChildren(this);
_inTry = oldInTry;
}
@override
visitTryFinally(ir.TryFinally node) {
bool oldInTry = _inTry;
_inTry = true;
node.visitChildren(this);
_inTry = oldInTry;
}
@override
visitVariableGet(ir.VariableGet node) {
_markVariableAsUsed(node.variable);
}
@override
visitVariableSet(ir.VariableSet node) {
_mutatedVariables.add(node.variable);
_markVariableAsUsed(node.variable);
node.visitChildren(this);
}
/// Add this variable to the set of free variables if appropriate and add to
/// the tally of variables used in try or sync blocks.
void _markVariableAsUsed(ir.VariableDeclaration variable) {
if (_isInsideClosure && !_inCurrentContext(variable)) {
// If the element is not declared in the current function and the element
// is not the closure itself we need to mark the element as free variable.
// Note that the check on [insideClosure] is not just an
// optimization: factories have type parameters as function
// parameters, and type parameters are declared in the class, not
// the factory.
_currentScopeInfo.freeVariables.add(variable);
}
if (_inTry) {
_currentScopeInfo.localsUsedInTryOrSync
.add(_localsMap.getLocal(variable));
}
}
@override
void visitForStatement(ir.ForStatement node) {
List<Local> boxedLoopVariables = <Local>[];
enterNewScope(node, () {
// First visit initialized variables and update steps so we can easily
// check if a loop variable was captured in one of these subexpressions.
node.variables
.forEach((ir.VariableDeclaration variable) => variable.accept(this));
node.updates
.forEach((ir.Expression expression) => expression.accept(this));
// Loop variables that have not been captured yet can safely be flagged as
// non-mutated, because no nested function can observe the mutation.
for (ir.VariableDeclaration variable in node.variables) {
if (!_capturedVariables.contains(variable)) {
_mutatedVariables.remove(variable);
}
}
// Visit condition and body.
// This must happen after the above, so any loop variables mutated in the
// condition or body are indeed flagged as mutated.
if (node.condition != null) node.condition.accept(this);
node.body.accept(this);
// See if we have declared loop variables that need to be boxed.
for (ir.VariableDeclaration variable in node.variables) {
// Non-mutated variables should not be boxed. The _mutatedVariables set
// gets cleared when `enterNewScope` returns, so check it here.
if (_capturedVariables.contains(variable) &&
_mutatedVariables.contains(variable)) {
boxedLoopVariables.add(_localsMap.getLocal(variable));
}
}
});
KernelCapturedScope scope = _scopesCapturedInClosureMap[node];
if (scope == null) return;
_scopesCapturedInClosureMap[node] = new KernelCapturedLoopScope(
scope.boxedVariables,
boxedLoopVariables,
scope.context,
scope.thisLocal);
}
void visitInvokable(ir.TreeNode node) {
bool oldIsInsideClosure = _isInsideClosure;
ir.Node oldExecutableContext = _executableContext;
KernelScopeInfo oldScopeInfo = _currentScopeInfo;
// _outermostNode is only null the first time we enter the body of the
// field, constructor, or method that is being analyzed.
_isInsideClosure = _outermostNode != null;
_executableContext = node;
_currentScopeInfo = new KernelScopeInfo(_nodeToThisLocal(node));
if (_isInsideClosure) {
_closuresToGenerate[node] = _currentScopeInfo;
} else {
_outermostNode = node;
_scopeInfoMap[_originalEntity] = _currentScopeInfo;
}
enterNewScope(node, () {
node.visitChildren(this);
});
KernelScopeInfo savedScopeInfo = _currentScopeInfo;
bool savedIsInsideClosure = _isInsideClosure;
// Restore old values.
_isInsideClosure = oldIsInsideClosure;
_currentScopeInfo = oldScopeInfo;
_executableContext = oldExecutableContext;
// Mark all free variables as captured and expect to encounter them in the
// outer function.
Iterable<ir.VariableDeclaration> freeVariables =
savedScopeInfo.freeVariables;
assert(freeVariables.isEmpty || savedIsInsideClosure);
for (ir.VariableDeclaration freeVariable in freeVariables) {
assert(!_capturedVariables.contains(freeVariable));
_capturedVariables.add(freeVariable);
_markVariableAsUsed(freeVariable);
}
}
/// Return true if [variable]'s context is the same as the current executable
/// context.
bool _inCurrentContext(ir.VariableDeclaration variable) {
ir.TreeNode node = variable;
while (node != _outermostNode && node != _executableContext) {
node = node.parent;
}
return node == _executableContext;
}
void translateLazyInitializer(ir.Field field) {
visitInvokable(field);
}
void translateConstructorOrProcedure(ir.Node constructorOrProcedure) {
constructorOrProcedure.accept(this);
}
void visitFunctionNode(ir.FunctionNode functionNode) {
visitInvokable(functionNode);
}
/// If [node] is an instance member return the corresponding `this` reference.
/// If not, return null.
Entity _nodeToThisLocal(
ir.TreeNode
/*ir.Field|ir.FunctionNode|ir.Constructor|ir.Procedure*/ node) {
ir.Node nodeToConvert = node;
if (nodeToConvert is ir.Field) {
if (!nodeToConvert.isInstanceMember) return null;
return new ThisLocal(_kernelToElementMap.getField(nodeToConvert));
} else {
if (nodeToConvert is ir.FunctionNode) {
// Step up one node higher to find the corresponding entity for this
// node.
nodeToConvert = node.parent;
}
if (nodeToConvert is ir.Constructor ||
(nodeToConvert is ir.Procedure &&
nodeToConvert.kind == ir.ProcedureKind.Factory &&
nodeToConvert.isInstanceMember)) {
return new ThisLocal(_kernelToElementMap.getConstructor(nodeToConvert));
} else if (nodeToConvert is ir.Procedure &&
nodeToConvert.isInstanceMember) {
return new ThisLocal(_kernelToElementMap.getMethod(nodeToConvert));
}
}
return null;
}
}