372b02d97f
Change-Id: I22bbd3b7ea0cccc0a8d721c3766a80ae1c72060d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/87701 Commit-Queue: Alexander Markov <alexmarkov@google.com> Reviewed-by: Régis Crelier <regis@google.com> Reviewed-by: Ryan Macnak <rmacnak@google.com>
1220 lines
35 KiB
Dart
1220 lines
35 KiB
Dart
// 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 '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>{};
|
|
final bool enableAsserts;
|
|
|
|
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 'Variable $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 currentContextId => _currentScope.contextId;
|
|
|
|
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 getVarContextId(VariableDeclaration variable) {
|
|
final v = _getVarDesc(variable);
|
|
assert(v.isCaptured);
|
|
return v.scope.contextId;
|
|
}
|
|
|
|
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 returnVarIndexInFrame => getVarIndexInFrame(_currentFrame.returnVar ??
|
|
(throw 'Return variable is not declared in ${_currentFrame.function}'));
|
|
|
|
int get functionTypeArgsVarIndexInFrame => getVarIndexInFrame(_currentFrame
|
|
.functionTypeArgsVar ??
|
|
(throw 'FunctionTypeArgs variable is not declared in ${_currentFrame.function}'));
|
|
|
|
bool get hasFunctionTypeArgsVar => _currentFrame.functionTypeArgsVar != null;
|
|
|
|
VariableDeclaration get factoryTypeArgsVar =>
|
|
_currentFrame.factoryTypeArgsVar ??
|
|
(throw 'FactoryTypeArgs variable is not declared in ${_currentFrame.function}');
|
|
|
|
bool get hasFactoryTypeArgsVar => _currentFrame.factoryTypeArgsVar != null;
|
|
|
|
VariableDeclaration get receiverVar =>
|
|
_currentFrame.receiverVar ??
|
|
(throw 'Receiver variable is not declared in ${_currentFrame.function}');
|
|
|
|
bool get hasCapturedReceiverVar => _currentFrame.capturedReceiverVar != null;
|
|
|
|
VariableDeclaration get capturedReceiverVar =>
|
|
_currentFrame.capturedReceiverVar ??
|
|
(throw 'Captured 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;
|
|
|
|
List<VariableDeclaration> get originalNamedParameters =>
|
|
_currentFrame.originalNamedParameters;
|
|
List<VariableDeclaration> get sortedNamedParameters =>
|
|
_currentFrame.sortedNamedParameters;
|
|
|
|
LocalVariables(Member node, this.enableAsserts) {
|
|
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() {
|
|
assert(!isAllocated);
|
|
isCaptured = true;
|
|
}
|
|
|
|
String toString() => 'var ${declaration.name}';
|
|
}
|
|
|
|
class Frame {
|
|
final TreeNode function;
|
|
final Frame parent;
|
|
Scope topScope;
|
|
|
|
List<VariableDeclaration> originalNamedParameters;
|
|
List<VariableDeclaration> sortedNamedParameters;
|
|
int numParameters = 0;
|
|
int numTypeArguments = 0;
|
|
bool hasOptionalParameters = false;
|
|
bool hasCapturedParameters = false;
|
|
bool hasClosures = false;
|
|
bool isDartSync = true;
|
|
bool isSyncYielding = false;
|
|
VariableDeclaration receiverVar;
|
|
VariableDeclaration capturedReceiverVar;
|
|
VariableDeclaration functionTypeArgsVar;
|
|
VariableDeclaration factoryTypeArgsVar;
|
|
VariableDeclaration closureVar;
|
|
VariableDeclaration contextVar;
|
|
VariableDeclaration scratchVar;
|
|
VariableDeclaration returnVar;
|
|
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 int loopDepth;
|
|
final List<VarDesc> vars = <VarDesc>[];
|
|
|
|
int localsUsed;
|
|
int tempsUsed;
|
|
|
|
Scope contextOwner;
|
|
int contextUsed = 0;
|
|
int contextSize = 0;
|
|
int contextLevel;
|
|
int contextId;
|
|
|
|
Scope(this.parent, this.frame, this.loopDepth);
|
|
|
|
bool get hasContext => contextSize > 0;
|
|
}
|
|
|
|
class _ScopeBuilder extends RecursiveVisitor<Null> {
|
|
final LocalVariables locals;
|
|
|
|
Scope _currentScope;
|
|
Frame _currentFrame;
|
|
List<TreeNode> _enclosingTryBlocks;
|
|
List<TreeNode> _enclosingTryCatches;
|
|
int _loopDepth;
|
|
|
|
_ScopeBuilder(this.locals);
|
|
|
|
List<VariableDeclaration> _sortNamedParameters(FunctionNode function) {
|
|
final params = function.namedParameters.toList();
|
|
params.sort((VariableDeclaration a, VariableDeclaration b) =>
|
|
a.name.compareTo(b.name));
|
|
return params;
|
|
}
|
|
|
|
void _visitFunction(TreeNode node) {
|
|
final savedEnclosingTryBlocks = _enclosingTryBlocks;
|
|
_enclosingTryBlocks = <TreeNode>[];
|
|
final savedEnclosingTryCatches = _enclosingTryCatches;
|
|
_enclosingTryCatches = <TreeNode>[];
|
|
final saveLoopDepth = _loopDepth;
|
|
_loopDepth = 0;
|
|
|
|
_enterFrame(node);
|
|
|
|
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;
|
|
|
|
if (node is Procedure && node.isFactory) {
|
|
assert(_currentFrame.parent == null);
|
|
_currentFrame.numTypeArguments = 0;
|
|
_currentFrame.factoryTypeArgsVar =
|
|
new VariableDeclaration(':type_arguments');
|
|
_declareVariable(_currentFrame.factoryTypeArgsVar);
|
|
} else {
|
|
_currentFrame.numTypeArguments =
|
|
(_currentFrame.parent?.numTypeArguments ?? 0) +
|
|
function.typeParameters.length;
|
|
|
|
if (_currentFrame.numTypeArguments > 0) {
|
|
_currentFrame.functionTypeArgsVar =
|
|
new VariableDeclaration(':function_type_arguments_var');
|
|
_declareVariable(_currentFrame.functionTypeArgsVar);
|
|
}
|
|
|
|
if (_currentFrame.parent?.factoryTypeArgsVar != null) {
|
|
_currentFrame.factoryTypeArgsVar =
|
|
_currentFrame.parent.factoryTypeArgsVar;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
_currentFrame.originalNamedParameters = function.namedParameters;
|
|
_currentFrame.sortedNamedParameters = _sortNamedParameters(function);
|
|
|
|
visitList(function.positionalParameters, this);
|
|
visitList(_currentFrame.sortedNamedParameters, 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);
|
|
}
|
|
|
|
if (node is Constructor || (node is Procedure && !node.isStatic)) {
|
|
if (locals.isCaptured(_currentFrame.receiverVar)) {
|
|
// Duplicate receiver variable for local use.
|
|
_currentFrame.capturedReceiverVar = _currentFrame.receiverVar;
|
|
_currentFrame.receiverVar = new VariableDeclaration('this');
|
|
_declareVariable(_currentFrame.receiverVar);
|
|
}
|
|
}
|
|
|
|
_leaveFrame();
|
|
|
|
_enclosingTryBlocks = savedEnclosingTryBlocks;
|
|
_enclosingTryCatches = savedEnclosingTryCatches;
|
|
_loopDepth = saveLoopDepth;
|
|
}
|
|
|
|
_enterFrame(TreeNode node) {
|
|
_currentFrame = new Frame(node, _currentFrame);
|
|
_enterScope(node);
|
|
_currentFrame.topScope = _currentScope;
|
|
}
|
|
|
|
_leaveFrame() {
|
|
_leaveScope();
|
|
_currentFrame = _currentFrame.parent;
|
|
}
|
|
|
|
void _enterScope(TreeNode node) {
|
|
_currentScope = new Scope(_currentScope, _currentFrame, _loopDepth);
|
|
assert(locals._scopes[node] == null);
|
|
locals._scopes[node] = _currentScope;
|
|
}
|
|
|
|
void _leaveScope() {
|
|
_currentScope = _currentScope.parent;
|
|
}
|
|
|
|
void _declareVariable(VariableDeclaration variable, [Scope scope]) {
|
|
if (scope == null) {
|
|
scope = _currentScope;
|
|
}
|
|
final VarDesc v = new VarDesc(variable, scope);
|
|
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.functionTypeArgsVar,
|
|
_currentFrame.closureVar,
|
|
_currentFrame.contextVar,
|
|
_currentFrame.scratchVar,
|
|
_currentFrame.returnVar,
|
|
]);
|
|
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;
|
|
if (_currentFrame.receiverVar != null) {
|
|
// Closure creation may load receiver to get instantiator type arguments.
|
|
_useThis();
|
|
}
|
|
node.variable.accept(this);
|
|
_visitFunction(node);
|
|
}
|
|
|
|
@override
|
|
visitFunctionExpression(FunctionExpression node) {
|
|
_currentFrame.hasClosures = true;
|
|
if (_currentFrame.receiverVar != null) {
|
|
// Closure creation may load receiver to get instantiator type arguments.
|
|
_useThis();
|
|
}
|
|
_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) {
|
|
var parent = node.parameter.parent;
|
|
if (parent is Class) {
|
|
_useThis();
|
|
} else if (parent is FunctionNode) {
|
|
parent = parent.parent;
|
|
if (parent is Procedure && parent.isFactory) {
|
|
assert(_currentFrame.factoryTypeArgsVar != null);
|
|
_useVariable(_currentFrame.factoryTypeArgsVar);
|
|
}
|
|
}
|
|
node.visitChildren(this);
|
|
}
|
|
|
|
@override
|
|
visitBlock(Block node) {
|
|
_visitWithScope(node);
|
|
}
|
|
|
|
@override
|
|
visitBlockExpression(BlockExpression node) {
|
|
// Not using _visitWithScope as Block inside BlockExpression does not have
|
|
// a scope.
|
|
_enterScope(node);
|
|
visitList(node.body.statements, this);
|
|
node.value.accept(this);
|
|
_leaveScope();
|
|
}
|
|
|
|
@override
|
|
visitAssertStatement(AssertStatement node) {
|
|
if (!locals.enableAsserts) {
|
|
return;
|
|
}
|
|
super.visitAssertStatement(node);
|
|
}
|
|
|
|
@override
|
|
visitAssertBlock(AssertBlock node) {
|
|
if (!locals.enableAsserts) {
|
|
return;
|
|
}
|
|
_visitWithScope(node);
|
|
}
|
|
|
|
@override
|
|
visitForStatement(ForStatement node) {
|
|
++_loopDepth;
|
|
_visitWithScope(node);
|
|
--_loopDepth;
|
|
}
|
|
|
|
@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(':iterator');
|
|
_declareVariable(iteratorVar);
|
|
locals._capturedIteratorVars[node] = iteratorVar;
|
|
}
|
|
|
|
++_loopDepth;
|
|
_enterScope(node);
|
|
node.variable.accept(this);
|
|
node.body.accept(this);
|
|
_leaveScope();
|
|
--_loopDepth;
|
|
|
|
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();
|
|
}
|
|
|
|
@override
|
|
visitReturnStatement(ReturnStatement node) {
|
|
// If returning from within a try-finally block, need to allocate
|
|
// an extra variable to hold a return value.
|
|
// Return value can't be kept on the stack as try-catch statements
|
|
// inside finally can zap expression stack.
|
|
// Literals (including implicit 'null' in 'return;') do not require
|
|
// an extra variable as they can be generated after all finally blocks.
|
|
if (_enclosingTryBlocks.isNotEmpty &&
|
|
(node.expression != null && node.expression is! BasicLiteral)) {
|
|
_currentFrame.returnVar = new VariableDeclaration(':return');
|
|
_declareVariable(_currentFrame.returnVar, _currentFrame.topScope);
|
|
}
|
|
node.visitChildren(this);
|
|
}
|
|
|
|
@override
|
|
visitWhileStatement(WhileStatement node) {
|
|
++_loopDepth;
|
|
node.visitChildren(this);
|
|
--_loopDepth;
|
|
}
|
|
|
|
@override
|
|
visitDoStatement(DoStatement node) {
|
|
++_loopDepth;
|
|
node.visitChildren(this);
|
|
--_loopDepth;
|
|
}
|
|
}
|
|
|
|
class _Allocator extends RecursiveVisitor<Null> {
|
|
final LocalVariables locals;
|
|
|
|
Scope _currentScope;
|
|
Frame _currentFrame;
|
|
int _contextIdCounter = 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;
|
|
|
|
if (_currentFrame.isSyncYielding) {
|
|
// _Closure._clone(), which is used to clone sync-yielding closures
|
|
// only clones 1 level of a context. So parent frame of a
|
|
// sync-yielding closure should have exactly 1 context level.
|
|
final parentFrame = _currentFrame.parent;
|
|
final currentLevel = _currentFrame.contextLevelAtEntry;
|
|
final parentLevel = parentFrame.contextLevelAtEntry ?? -1;
|
|
if (currentLevel != parentLevel + 1) {
|
|
throw 'Unexpected context allocation in ${parentFrame.function}\n'
|
|
' - context level at parent entry: ${parentLevel}\n'
|
|
' - context level at synthetic closure entry: ${currentLevel}\n';
|
|
}
|
|
}
|
|
}
|
|
|
|
_currentScope.localsUsed = 0;
|
|
_currentScope.tempsUsed = 0;
|
|
} else {
|
|
_currentScope.localsUsed = _currentScope.parent.localsUsed;
|
|
_currentScope.tempsUsed = _currentScope.parent.tempsUsed;
|
|
}
|
|
|
|
assert(_currentScope.contextOwner == null);
|
|
assert(_currentScope.contextLevel == null);
|
|
assert(_currentScope.contextId == null);
|
|
|
|
final int parentContextLevel =
|
|
_currentScope.parent != null ? _currentScope.parent.contextLevel : -1;
|
|
|
|
final int numCaptured =
|
|
_currentScope.vars.where((v) => v.isCaptured).length;
|
|
if (numCaptured > 0) {
|
|
// Share contexts between scopes which belong to the same frame and
|
|
// have the same loop depth.
|
|
_currentScope.contextOwner = _currentScope;
|
|
for (Scope contextOwner = _currentScope;
|
|
contextOwner != null &&
|
|
contextOwner.frame == _currentScope.frame &&
|
|
contextOwner.loopDepth == _currentScope.loopDepth;
|
|
contextOwner = contextOwner.parent) {
|
|
if (contextOwner.hasContext) {
|
|
_currentScope.contextOwner = contextOwner;
|
|
break;
|
|
}
|
|
}
|
|
|
|
_currentScope.contextOwner.contextSize += numCaptured;
|
|
|
|
if (_currentScope.contextOwner == _currentScope) {
|
|
_currentScope.contextLevel = parentContextLevel + 1;
|
|
_currentScope.contextId = _contextIdCounter++;
|
|
if (_currentScope.contextId >= contextIdLimit) {
|
|
throw new ContextIdOverflowException();
|
|
}
|
|
} else {
|
|
_currentScope.contextLevel = _currentScope.contextOwner.contextLevel;
|
|
_currentScope.contextId = _currentScope.contextOwner.contextId;
|
|
}
|
|
} else {
|
|
_currentScope.contextLevel = parentContextLevel;
|
|
}
|
|
}
|
|
|
|
void _leaveScope() {
|
|
assert(_currentScope.contextUsed == _currentScope.contextSize);
|
|
|
|
_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;
|
|
if (_currentScope.localsUsed > localVariableIndexLimit) {
|
|
throw new LocalVariableIndexOverflowException();
|
|
}
|
|
_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);
|
|
|
|
assert(!v.isAllocated);
|
|
assert(v.scope == _currentScope);
|
|
|
|
if (v.isCaptured) {
|
|
v.index = _currentScope.contextOwner.contextUsed++;
|
|
if (v.index >= capturedVariableIndexLimit) {
|
|
throw new LocalVariableIndexOverflowException();
|
|
}
|
|
v.originalParamSlotIndex = paramSlotIndex;
|
|
return;
|
|
}
|
|
|
|
if (paramSlotIndex != null) {
|
|
assert(paramSlotIndex < 0 ||
|
|
(_currentFrame.hasOptionalParameters &&
|
|
paramSlotIndex < _currentFrame.numParameters));
|
|
v.index = paramSlotIndex;
|
|
} else {
|
|
v.index = _currentScope.localsUsed++;
|
|
if (v.index >= localVariableIndexLimit) {
|
|
throw new LocalVariableIndexOverflowException();
|
|
}
|
|
}
|
|
_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 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 +
|
|
(isFactory ? 1 : 0) +
|
|
(hasReceiver ? 1 : 0) +
|
|
(hasClosureArg ? 1 : 0);
|
|
|
|
_currentFrame.hasOptionalParameters = function.requiredParameterCount <
|
|
function.positionalParameters.length ||
|
|
function.namedParameters.isNotEmpty;
|
|
|
|
_currentFrame.hasCapturedParameters =
|
|
(isFactory && locals.isCaptured(_currentFrame.factoryTypeArgsVar)) ||
|
|
(hasReceiver && _currentFrame.capturedReceiverVar != null) ||
|
|
function.positionalParameters.any(locals.isCaptured) ||
|
|
function.namedParameters.any(locals.isCaptured);
|
|
|
|
int count = 0;
|
|
if (isFactory) {
|
|
_allocateParameter(_currentFrame.factoryTypeArgsVar, count++);
|
|
}
|
|
if (hasReceiver) {
|
|
assert(!locals.isCaptured(_currentFrame.receiverVar));
|
|
_allocateParameter(_currentFrame.receiverVar, count++);
|
|
|
|
if (_currentFrame.capturedReceiverVar != null) {
|
|
_allocateVariable(_currentFrame.capturedReceiverVar);
|
|
}
|
|
}
|
|
if (hasClosureArg) {
|
|
assert(!locals.isCaptured(_currentFrame.closureVar));
|
|
_allocateParameter(_currentFrame.closureVar, count++);
|
|
}
|
|
for (var param in function.positionalParameters) {
|
|
_allocateParameter(param, count++);
|
|
}
|
|
for (var param in _currentFrame.sortedNamedParameters) {
|
|
_allocateParameter(param, count++);
|
|
}
|
|
assert(count == _currentFrame.numParameters);
|
|
|
|
if (_currentFrame.hasOptionalParameters) {
|
|
_currentScope.localsUsed = _currentFrame.numParameters;
|
|
_updateFrameSize();
|
|
}
|
|
}
|
|
|
|
void _allocateSpecialVariables() {
|
|
_ensureVariableAllocated(_currentFrame.functionTypeArgsVar);
|
|
_ensureVariableAllocated(_currentFrame.contextVar);
|
|
_ensureVariableAllocated(_currentFrame.scratchVar);
|
|
_ensureVariableAllocated(_currentFrame.returnVar);
|
|
}
|
|
|
|
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
|
|
visitBlockExpression(BlockExpression node) {
|
|
// Not using _visit as Block inside BlockExpression does not have a scope.
|
|
_enterScope(node);
|
|
visitList(node.body.statements, this);
|
|
node.value.accept(this);
|
|
_leaveScope();
|
|
}
|
|
|
|
@override
|
|
visitAssertStatement(AssertStatement node) {
|
|
if (!locals.enableAsserts) {
|
|
return;
|
|
}
|
|
super.visitAssertStatement(node);
|
|
}
|
|
|
|
@override
|
|
visitAssertBlock(AssertBlock node) {
|
|
if (!locals.enableAsserts) {
|
|
return;
|
|
}
|
|
_visit(node, scope: true);
|
|
}
|
|
|
|
@override
|
|
visitForStatement(ForStatement node) {
|
|
_visit(node, scope: true);
|
|
}
|
|
|
|
@override
|
|
visitForInStatement(ForInStatement node) {
|
|
_allocateTemp(node);
|
|
_ensureVariableAllocated(locals._capturedIteratorVars[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
|
|
visitDirectPropertySet(DirectPropertySet node) {
|
|
_visit(node, temps: 1);
|
|
}
|
|
|
|
@override
|
|
visitSuperMethodInvocation(SuperMethodInvocation node) {
|
|
_visit(node, temps: 1);
|
|
}
|
|
|
|
@override
|
|
visitSuperPropertyGet(SuperPropertyGet node) {
|
|
_visit(node, temps: 1);
|
|
}
|
|
|
|
@override
|
|
visitSuperPropertySet(SuperPropertySet 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) {
|
|
_visit(node, temps: 1);
|
|
}
|
|
|
|
@override
|
|
visitTryCatch(TryCatch node) {
|
|
_visit(node, temps: 2);
|
|
}
|
|
|
|
@override
|
|
visitTryFinally(TryFinally node) {
|
|
_visit(node, temps: 2);
|
|
}
|
|
|
|
@override
|
|
visitInstantiation(Instantiation node) {
|
|
_visit(node, temps: 3);
|
|
}
|
|
}
|
|
|
|
class LocalVariableIndexOverflowException
|
|
extends BytecodeLimitExceededException {}
|
|
|
|
class ContextIdOverflowException extends BytecodeLimitExceededException {}
|