blob: d779939bbe113d12f44596eef02d2058331c1c60 [file] [log] [blame]
// Copyright (c) 2018, 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.
library vm.bytecode.local_vars;
import 'dart:math' show max;
import 'package:kernel/ast.dart';
import 'package:kernel/transformations/continuation.dart'
show ContinuationVariables;
import 'package:vm/bytecode/dbc.dart';
class LocalVariables {
final Map<TreeNode, Scope> _scopes = <TreeNode, Scope>{};
final Map<VariableDeclaration, VarDesc> _vars =
<VariableDeclaration, VarDesc>{};
final Map<TreeNode, List<int>> _temps = <TreeNode, List<int>>{};
final Map<TreeNode, VariableDeclaration> _capturedSavedContextVars =
<TreeNode, VariableDeclaration>{};
final Map<TreeNode, VariableDeclaration> _capturedExceptionVars =
<TreeNode, VariableDeclaration>{};
final Map<TreeNode, VariableDeclaration> _capturedStackTraceVars =
<TreeNode, VariableDeclaration>{};
final Map<ForInStatement, VariableDeclaration> _capturedIteratorVars =
<ForInStatement, VariableDeclaration>{};
Scope _currentScope;
Frame _currentFrame;
VarDesc _getVarDesc(VariableDeclaration variable) =>
_vars[variable] ??
(throw 'Variable descriptor is not created for $variable');
int _getVarIndex(VariableDeclaration variable, bool isCaptured) {
final v = _getVarDesc(variable);
if (v.isCaptured != isCaptured) {
throw 'Mismatch in captured state of $variable';
}
return v.index ?? (throw 'Variable $variable is not allocated');
}
bool isCaptured(VariableDeclaration variable) =>
_getVarDesc(variable).isCaptured;
int getVarIndexInFrame(VariableDeclaration variable) =>
_getVarIndex(variable, false);
int getVarIndexInContext(VariableDeclaration variable) =>
_getVarIndex(variable, true);
int getOriginalParamSlotIndex(VariableDeclaration variable) =>
_getVarDesc(variable).originalParamSlotIndex ??
(throw 'Variablie $variable does not have originalParamSlotIndex');
int tempIndexInFrame(TreeNode node, {int tempIndex: 0}) {
final temps = _temps[node];
if (temps == null) {
throw 'Temp is not allocated for node ${node.runtimeType} $node';
}
return temps[tempIndex];
}
int get currentContextSize => _currentScope.contextSize;
int get currentContextLevel => _currentScope.contextLevel;
int get contextLevelAtEntry =>
_currentFrame.contextLevelAtEntry ??
(throw "Current frame is top level and it doesn't have a context at entry");
int getContextLevelOfVar(VariableDeclaration variable) {
final v = _getVarDesc(variable);
assert(v.isCaptured);
return v.scope.contextLevel;
}
int get closureVarIndexInFrame => getVarIndexInFrame(_currentFrame
.closureVar ??
(throw 'Closure variable is not declared in ${_currentFrame.function}'));
int get contextVarIndexInFrame => getVarIndexInFrame(_currentFrame
.contextVar ??
(throw 'Context variable is not declared in ${_currentFrame.function}'));
bool get hasContextVar => _currentFrame.contextVar != null;
int get scratchVarIndexInFrame => getVarIndexInFrame(_currentFrame
.scratchVar ??
(throw 'Scratch variable is not declared in ${_currentFrame.function}'));
int get typeArgsVarIndexInFrame => getVarIndexInFrame(_currentFrame
.typeArgsVar ??
(throw 'TypeArgs variable is not declared in ${_currentFrame.function}'));
bool get hasTypeArgsVar => _currentFrame.typeArgsVar != null;
VariableDeclaration get receiverVar =>
_currentFrame.receiverVar ??
(throw 'Receiver variable is not declared in ${_currentFrame.function}');
bool get hasReceiver => _currentFrame.receiverVar != null;
bool get isSyncYieldingFrame => _currentFrame.isSyncYielding;
VariableDeclaration get awaitJumpVar {
assert(_currentFrame.isSyncYielding);
return _currentFrame.parent
.getSyntheticVar(ContinuationVariables.awaitJumpVar);
}
VariableDeclaration get awaitContextVar {
assert(_currentFrame.isSyncYielding);
return _currentFrame.parent
.getSyntheticVar(ContinuationVariables.awaitContextVar);
}
VariableDeclaration capturedSavedContextVar(TreeNode node) =>
_capturedSavedContextVars[node];
VariableDeclaration capturedExceptionVar(TreeNode node) =>
_capturedExceptionVars[node];
VariableDeclaration capturedStackTraceVar(TreeNode node) =>
_capturedStackTraceVars[node];
VariableDeclaration capturedIteratorVar(ForInStatement node) =>
_capturedIteratorVars[node];
int get asyncExceptionParamIndexInFrame {
assert(_currentFrame.isSyncYielding);
final function = (_currentFrame.function as FunctionDeclaration).function;
final param = function.positionalParameters
.firstWhere((p) => p.name == ContinuationVariables.exceptionParam);
return getVarIndexInFrame(param);
}
int get asyncStackTraceParamIndexInFrame {
assert(_currentFrame.isSyncYielding);
final function = (_currentFrame.function as FunctionDeclaration).function;
final param = function.positionalParameters
.firstWhere((p) => p.name == ContinuationVariables.stackTraceParam);
return getVarIndexInFrame(param);
}
int get frameSize => _currentFrame.frameSize;
int get numParameters => _currentFrame.numParameters;
int get numParentTypeArguments => _currentFrame.parent?.numTypeArguments ?? 0;
bool get hasOptionalParameters => _currentFrame.hasOptionalParameters;
bool get hasCapturedParameters => _currentFrame.hasCapturedParameters;
LocalVariables(Member node) {
final scopeBuilder = new _ScopeBuilder(this);
node.accept(scopeBuilder);
final allocator = new _Allocator(this);
node.accept(allocator);
}
void enterScope(TreeNode node) {
_currentScope = _scopes[node];
_currentFrame = _currentScope.frame;
}
void leaveScope() {
_currentScope = _currentScope.parent;
_currentFrame = _currentScope?.frame;
}
}
class VarDesc {
final VariableDeclaration declaration;
final Scope scope;
bool isCaptured = false;
int index;
int originalParamSlotIndex;
VarDesc(this.declaration, this.scope) {
scope.vars.add(this);
}
Frame get frame => scope.frame;
bool get isAllocated => index != null;
void capture() {
if (!isCaptured) {
assert(!isAllocated);
// TODO(alexmarkov): Consider sharing context between scopes.
index = scope.contextSize++;
isCaptured = true;
}
}
}
class Frame {
final TreeNode function;
final Frame parent;
int numParameters = 0;
int numTypeArguments = 0;
bool hasOptionalParameters = false;
bool hasCapturedParameters = false;
bool hasClosures = false;
bool isDartSync = true;
bool isSyncYielding = false;
VariableDeclaration receiverVar;
VariableDeclaration typeArgsVar;
VariableDeclaration closureVar;
VariableDeclaration contextVar;
VariableDeclaration scratchVar;
Map<String, VariableDeclaration> syntheticVars;
int frameSize = 0;
List<int> temporaries = <int>[];
int contextLevelAtEntry;
Frame(this.function, this.parent);
VariableDeclaration getSyntheticVar(String name) =>
syntheticVars[name] ??
(throw '${name} variable is not declared in ${function}');
}
class Scope {
final Scope parent;
final Frame frame;
final List<VarDesc> vars = <VarDesc>[];
int localsUsed;
int tempsUsed;
int contextSize = 0;
int contextLevel;
Scope(this.parent, this.frame);
bool get hasContext => contextSize > 0;
}
class _ScopeBuilder extends RecursiveVisitor<Null> {
final LocalVariables locals;
Scope _currentScope;
Frame _currentFrame;
List<TreeNode> _enclosingTryBlocks;
List<TreeNode> _enclosingTryCatches;
_ScopeBuilder(this.locals);
void _sortNamedParameters(FunctionNode function) {
function.namedParameters.sort(
(VariableDeclaration a, VariableDeclaration b) =>
a.name.compareTo(b.name));
}
void _visitFunction(TreeNode node) {
_enterFrame(node);
final savedEnclosingTryBlocks = _enclosingTryBlocks;
_enclosingTryBlocks = <TreeNode>[];
final savedEnclosingTryCatches = _enclosingTryCatches;
_enclosingTryCatches = <TreeNode>[];
if (node is Field) {
node.initializer.accept(this);
} else {
assert(node is Procedure ||
node is Constructor ||
node is FunctionDeclaration ||
node is FunctionExpression);
FunctionNode function = (node as dynamic).function;
assert(function != null);
_currentFrame.isDartSync = function.dartAsyncMarker == AsyncMarker.Sync;
_currentFrame.isSyncYielding =
function.asyncMarker == AsyncMarker.SyncYielding;
_currentFrame.numTypeArguments =
(_currentFrame.parent?.numTypeArguments ?? 0) +
function.typeParameters.length;
if (_currentFrame.numTypeArguments > 0) {
_currentFrame.typeArgsVar =
new VariableDeclaration(':function_type_arguments_var');
_declareVariable(_currentFrame.typeArgsVar);
}
if (node is Constructor || (node is Procedure && !node.isStatic)) {
_currentFrame.receiverVar = new VariableDeclaration('this');
_declareVariable(_currentFrame.receiverVar);
} else if (_currentFrame.parent?.receiverVar != null) {
_currentFrame.receiverVar = _currentFrame.parent.receiverVar;
}
if (node is FunctionDeclaration || node is FunctionExpression) {
_currentFrame.closureVar = new VariableDeclaration(':closure');
_declareVariable(_currentFrame.closureVar);
}
_sortNamedParameters(function);
visitList(function.positionalParameters, this);
visitList(function.namedParameters, this);
if (_currentFrame.isSyncYielding) {
// The following variables from parent frame are used implicitly and need
// to be captured to preserve state across closure invocations.
_useVariable(_currentFrame.parent
.getSyntheticVar(ContinuationVariables.awaitJumpVar));
_useVariable(_currentFrame.parent
.getSyntheticVar(ContinuationVariables.awaitContextVar));
}
if (node is Constructor) {
for (var field in node.enclosingClass.fields) {
if (!field.isStatic && field.initializer != null) {
field.initializer.accept(this);
}
}
visitList(node.initializers, this);
}
function.body?.accept(this);
}
if (node is FunctionDeclaration ||
node is FunctionExpression ||
_currentFrame.hasClosures) {
_currentFrame.contextVar = new VariableDeclaration(':context');
_declareVariable(_currentFrame.contextVar);
_currentFrame.scratchVar = new VariableDeclaration(':scratch');
_declareVariable(_currentFrame.scratchVar);
}
_enclosingTryBlocks = savedEnclosingTryBlocks;
_enclosingTryCatches = savedEnclosingTryCatches;
_leaveFrame();
}
_enterFrame(TreeNode node) {
_currentFrame = new Frame(node, _currentFrame);
_enterScope(node);
}
_leaveFrame() {
_leaveScope();
_currentFrame = _currentFrame.parent;
}
void _enterScope(TreeNode node) {
_currentScope = new Scope(_currentScope, _currentFrame);
assert(locals._scopes[node] == null);
locals._scopes[node] = _currentScope;
}
void _leaveScope() {
_currentScope = _currentScope.parent;
}
void _declareVariable(VariableDeclaration variable) {
final VarDesc v = new VarDesc(variable, _currentScope);
assert(locals._vars[variable] == null);
locals._vars[variable] = v;
}
void _useVariable(VariableDeclaration variable) {
assert(variable != null);
final VarDesc v = locals._vars[variable];
if (v == null) {
throw 'Variable $variable is used before declared';
}
if (v.frame != _currentFrame) {
v.capture();
}
}
void _useThis() {
assert(_currentFrame.receiverVar != null);
_useVariable(_currentFrame.receiverVar);
}
void _captureAllVisibleVariablesInCurrentFrame() {
assert(_currentFrame.isSyncYielding);
final transient = new Set<VariableDeclaration>();
transient
..addAll([
_currentFrame.typeArgsVar,
_currentFrame.closureVar,
_currentFrame.contextVar,
_currentFrame.scratchVar,
]);
transient.addAll((_currentFrame.function as FunctionDeclaration)
.function
.positionalParameters);
for (Scope scope = _currentScope;
scope != null && scope.frame == _currentFrame;
scope = scope.parent) {
for (VarDesc v in scope.vars) {
if (!transient.contains(v.declaration)) {
v.capture();
}
}
}
}
// Capture synthetic variables for control flow statements.
void _captureSyntheticVariables() {
int depth = 0;
for (TreeNode tryBlock in _enclosingTryBlocks) {
_captureSyntheticVariable(ContinuationVariables.savedTryContextVar(depth),
tryBlock, locals._capturedSavedContextVars);
++depth;
}
depth = 0;
for (TreeNode tryBlock in _enclosingTryCatches) {
_captureSyntheticVariable(ContinuationVariables.exceptionVar(depth),
tryBlock, locals._capturedExceptionVars);
_captureSyntheticVariable(ContinuationVariables.stackTraceVar(depth),
tryBlock, locals._capturedStackTraceVars);
++depth;
}
}
void _captureSyntheticVariable(
String name, TreeNode node, Map<TreeNode, VariableDeclaration> map) {
final variable = _currentFrame.parent.getSyntheticVar(name);
_useVariable(variable);
assert(map[node] == null || map[node] == variable);
map[node] = variable;
}
void _visitWithScope(TreeNode node) {
_enterScope(node);
node.visitChildren(this);
_leaveScope();
}
@override
defaultMember(Member node) {
_visitFunction(node);
}
@override
visitFunctionDeclaration(FunctionDeclaration node) {
_currentFrame.hasClosures = true;
node.variable.accept(this);
_visitFunction(node);
}
@override
visitFunctionExpression(FunctionExpression node) {
_currentFrame.hasClosures = true;
_visitFunction(node);
}
@override
visitVariableDeclaration(VariableDeclaration node) {
_declareVariable(node);
if (!_currentFrame.isDartSync && node.name[0] == ':') {
_currentFrame.syntheticVars ??= <String, VariableDeclaration>{};
assert(_currentFrame.syntheticVars[node.name] == null);
_currentFrame.syntheticVars[node.name] = node;
}
node.visitChildren(this);
}
@override
visitVariableGet(VariableGet node) {
_useVariable(node.variable);
}
@override
visitVariableSet(VariableSet node) {
_useVariable(node.variable);
node.visitChildren(this);
}
@override
visitThisExpression(ThisExpression node) {
_useThis();
}
@override
visitSuperMethodInvocation(SuperMethodInvocation node) {
_useThis();
node.visitChildren(this);
}
@override
visitSuperPropertyGet(SuperPropertyGet node) {
_useThis();
node.visitChildren(this);
}
@override
visitSuperPropertySet(SuperPropertySet node) {
_useThis();
node.visitChildren(this);
}
@override
visitTypeParameterType(TypeParameterType node) {
if (node.parameter.parent is Class) {
_useThis();
}
node.visitChildren(this);
}
@override
visitBlock(Block node) {
_visitWithScope(node);
}
@override
visitAssertBlock(AssertBlock node) {
_visitWithScope(node);
}
@override
visitForStatement(ForStatement node) {
_visitWithScope(node);
}
@override
visitForInStatement(ForInStatement node) {
node.iterable.accept(this);
VariableDeclaration iteratorVar;
if (_currentFrame.isSyncYielding) {
// Declare a variable to hold 'iterator' so it could be captured.
iteratorVar = new VariableDeclaration(null);
_declareVariable(iteratorVar);
locals._capturedIteratorVars[node] = iteratorVar;
}
_enterScope(node);
node.variable.accept(this);
node.body.accept(this);
_leaveScope();
if (_currentFrame.isSyncYielding && !locals.isCaptured(iteratorVar)) {
// Iterator variable was not captured, as there are no yield points
// inside for-in statement body. The variable is needed only if captured,
// so undeclare it.
assert(_currentScope.vars.last == locals._vars[iteratorVar]);
_currentScope.vars.removeLast();
locals._vars.remove(iteratorVar);
locals._capturedIteratorVars.remove(node);
}
}
@override
visitCatch(Catch node) {
_visitWithScope(node);
}
@override
visitLet(Let node) {
_visitWithScope(node);
}
@override
visitYieldStatement(YieldStatement node) {
assert(_currentFrame.isSyncYielding);
_captureAllVisibleVariablesInCurrentFrame();
_captureSyntheticVariables();
node.visitChildren(this);
}
@override
visitTryCatch(TryCatch node) {
_enclosingTryBlocks.add(node);
node.body?.accept(this);
_enclosingTryBlocks.removeLast();
_enclosingTryCatches.add(node);
visitList(node.catches, this);
_enclosingTryCatches.removeLast();
}
@override
visitTryFinally(TryFinally node) {
_enclosingTryBlocks.add(node);
node.body?.accept(this);
_enclosingTryBlocks.removeLast();
_enclosingTryCatches.add(node);
node.finalizer?.accept(this);
_enclosingTryCatches.removeLast();
}
}
class _Allocator extends RecursiveVisitor<Null> {
final LocalVariables locals;
Scope _currentScope;
Frame _currentFrame;
int _contextLevel = 0;
_Allocator(this.locals);
void _enterScope(TreeNode node) {
final scope = locals._scopes[node];
assert(scope != null);
assert(scope.parent == _currentScope);
_currentScope = scope;
if (_currentScope.frame != _currentFrame) {
_currentFrame = _currentScope.frame;
if (_currentScope.parent != null) {
_currentFrame.contextLevelAtEntry = _currentScope.parent.contextLevel;
}
_currentScope.localsUsed = 0;
_currentScope.tempsUsed = 0;
} else {
_currentScope.localsUsed = _currentScope.parent.localsUsed;
_currentScope.tempsUsed = _currentScope.parent.tempsUsed;
}
if (_currentScope.parent == null || _currentScope.hasContext) {
_currentScope.contextLevel = _contextLevel++;
} else {
_currentScope.contextLevel = _currentScope.parent.contextLevel;
}
}
void _leaveScope() {
if (_currentScope.hasContext) {
--_contextLevel;
}
_currentScope = _currentScope.parent;
_currentFrame = _currentScope?.frame;
// Remove temporary variables which are out of scope.
if (_currentScope != null) {
int tempsToRetain = _currentFrame.temporaries.length;
while (tempsToRetain > 0 &&
_currentFrame.temporaries[tempsToRetain - 1] >=
_currentScope.localsUsed) {
--tempsToRetain;
}
assert(tempsToRetain >= _currentScope.tempsUsed);
_currentFrame.temporaries.length = tempsToRetain;
assert(_currentFrame.temporaries
.every((index) => index < _currentScope.localsUsed));
}
}
void _updateFrameSize() {
_currentFrame.frameSize =
max(_currentFrame.frameSize, _currentScope.localsUsed);
}
void _allocateTemp(TreeNode node, {int count: 1}) {
assert(locals._temps[node] == null);
if (_currentScope.tempsUsed + count > _currentFrame.temporaries.length) {
// Allocate new local slots for temporary variables.
final int newSlots =
(_currentScope.tempsUsed + count) - _currentFrame.temporaries.length;
int local = _currentScope.localsUsed;
_currentScope.localsUsed += newSlots;
_updateFrameSize();
for (int i = 0; i < newSlots; i++) {
_currentFrame.temporaries.add(local + i);
}
}
locals._temps[node] = _currentFrame.temporaries
.sublist(_currentScope.tempsUsed, _currentScope.tempsUsed + count);
_currentScope.tempsUsed += count;
}
void _freeTemp(TreeNode node, {int count: 1}) {
assert(_currentScope.tempsUsed >= count);
_currentScope.tempsUsed -= count;
assert(listEquals(
locals._temps[node],
_currentFrame.temporaries.sublist(
_currentScope.tempsUsed, _currentScope.tempsUsed + count)));
}
void _allocateVariable(VariableDeclaration variable, {int paramSlotIndex}) {
final VarDesc v = locals._getVarDesc(variable);
if (v.isCaptured) {
assert(v.isAllocated);
v.originalParamSlotIndex = paramSlotIndex;
return;
}
assert(!v.isAllocated);
assert(v.scope == _currentScope);
if (paramSlotIndex != null) {
assert(paramSlotIndex < 0 ||
(_currentFrame.hasOptionalParameters &&
paramSlotIndex < _currentFrame.numParameters));
v.index = paramSlotIndex;
} else {
v.index = _currentScope.localsUsed++;
}
_updateFrameSize();
}
void _ensureVariableAllocated(VariableDeclaration variable) {
if (variable != null) {
final VarDesc v = locals._getVarDesc(variable);
if (!v.isAllocated) {
_allocateVariable(variable);
}
}
}
void _allocateParameter(VariableDeclaration node, int i) {
final numParameters = _currentFrame.numParameters;
assert(0 <= i && i < numParameters);
int paramSlotIndex = _currentFrame.hasOptionalParameters
? i
: -kParamEndSlotFromFp - numParameters + i;
_allocateVariable(node, paramSlotIndex: paramSlotIndex);
}
void _allocateParameters(TreeNode node, FunctionNode function) {
final bool hasTypeArgs = function.typeParameters.isNotEmpty;
final bool isFactory = node is Procedure && node.isFactory;
final bool hasReceiver =
node is Constructor || (node is Procedure && !node.isStatic);
final bool hasClosureArg =
node is FunctionDeclaration || node is FunctionExpression;
_currentFrame.numParameters = function.positionalParameters.length +
function.namedParameters.length +
(hasTypeArgs || isFactory ? 1 : 0) +
(hasReceiver ? 1 : 0) +
(hasClosureArg ? 1 : 0);
_currentFrame.hasOptionalParameters = function.requiredParameterCount <
function.positionalParameters.length ||
function.namedParameters.isNotEmpty;
_currentFrame.hasCapturedParameters =
(hasReceiver && locals.isCaptured(_currentFrame.receiverVar)) ||
function.positionalParameters.any(locals.isCaptured) ||
function.namedParameters.any(locals.isCaptured);
int count = 0;
if (hasTypeArgs) {
assert(!locals.isCaptured(_currentFrame.typeArgsVar));
_allocateParameter(_currentFrame.typeArgsVar, count++);
} else if (isFactory) {
// Null type arguments are passed to factory constructors even if class
// is not generic. TODO(alexmarkov): Clean this up.
count++;
}
if (hasReceiver) {
_allocateParameter(_currentFrame.receiverVar, count++);
}
if (hasClosureArg) {
assert(!locals.isCaptured(_currentFrame.closureVar));
_allocateParameter(_currentFrame.closureVar, count++);
}
for (var param in function.positionalParameters) {
_allocateParameter(param, count++);
}
for (var param in function.namedParameters) {
_allocateParameter(param, count++);
}
assert(count == _currentFrame.numParameters);
if (_currentFrame.hasOptionalParameters) {
_currentScope.localsUsed = _currentFrame.numParameters;
_updateFrameSize();
}
}
void _allocateSpecialVariables() {
_ensureVariableAllocated(_currentFrame.typeArgsVar);
_ensureVariableAllocated(_currentFrame.contextVar);
_ensureVariableAllocated(_currentFrame.scratchVar);
}
void _visitFunction(TreeNode node) {
_enterScope(node);
if (node is Field) {
_allocateSpecialVariables();
node.initializer.accept(this);
} else {
assert(node is Procedure ||
node is Constructor ||
node is FunctionDeclaration ||
node is FunctionExpression);
final FunctionNode function = (node as dynamic).function;
assert(function != null);
_allocateParameters(node, function);
_allocateSpecialVariables();
if (node is Constructor) {
for (var field in node.enclosingClass.fields) {
if (!field.isStatic && field.initializer != null) {
field.initializer.accept(this);
}
}
visitList(node.initializers, this);
}
function.body?.accept(this);
}
_leaveScope();
}
void _visit(TreeNode node, {bool scope: false, int temps: 0}) {
if (scope) {
_enterScope(node);
}
if (temps > 0) {
_allocateTemp(node, count: temps);
}
node.visitChildren(this);
if (temps > 0) {
_freeTemp(node, count: temps);
}
if (scope) {
_leaveScope();
}
}
@override
defaultMember(Member node) {
_visitFunction(node);
}
@override
visitFunctionDeclaration(FunctionDeclaration node) {
_allocateVariable(node.variable);
_allocateTemp(node);
_visitFunction(node);
_freeTemp(node);
}
@override
visitFunctionExpression(FunctionExpression node) {
_allocateTemp(node);
_visitFunction(node);
_freeTemp(node);
}
@override
visitVariableDeclaration(VariableDeclaration node) {
_allocateVariable(node);
node.visitChildren(this);
}
@override
visitBlock(Block node) {
_visit(node, scope: true);
}
@override
visitAssertBlock(AssertBlock node) {
_visit(node, scope: true);
}
@override
visitForStatement(ForStatement node) {
_visit(node, scope: true);
}
@override
visitForInStatement(ForInStatement node) {
_allocateTemp(node);
node.iterable.accept(this);
_enterScope(node);
node.variable.accept(this);
node.body.accept(this);
_leaveScope();
_freeTemp(node);
}
@override
visitCatch(Catch node) {
_visit(node, scope: true);
}
@override
visitLet(Let node) {
_visit(node, scope: true);
}
// -------------- Allocation of temporaries --------------
@override
visitConstructorInvocation(ConstructorInvocation node) {
if (node.isConst) {
return;
}
_visit(node, temps: 1);
}
@override
visitListLiteral(ListLiteral node) {
if (node.isConst) {
return;
}
_visit(node, temps: 1);
}
@override
visitMapLiteral(MapLiteral node) {
if (node.isConst) {
return;
}
_visit(node, temps: 1);
}
@override
visitStringConcatenation(StringConcatenation node) {
_visit(node, temps: 1);
}
@override
visitConditionalExpression(ConditionalExpression node) {
_visit(node, temps: 1);
}
@override
visitLogicalExpression(LogicalExpression node) {
_visit(node, temps: 1);
}
@override
visitPropertySet(PropertySet node) {
_visit(node, temps: 1);
}
@override
visitSwitchStatement(SwitchStatement node) {
_visit(node, temps: 1);
}
@override
visitVariableSet(VariableSet node) {
_visit(node, temps: locals.isCaptured(node.variable) ? 1 : 0);
}
@override
visitStaticSet(StaticSet node) {
_allocateTemp(node);
super.visitStaticSet(node);
}
@override
visitTryCatch(TryCatch node) {
_visit(node, temps: 2);
}
@override
visitTryFinally(TryFinally node) {
_visit(node, temps: 2);
}
}