blob: 82aefbda612a7433d1e5723e6777c01f6ad87b6b [file] [log] [blame] [edit]
// Copyright (c) 2024, 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 'dart:math' show min, max;
import 'package:kernel/ast.dart';
import 'package:kernel/type_environment.dart';
import 'dbc.dart';
import 'options.dart' show BytecodeOptions;
class LocalVariables {
final _scopes = new Map<TreeNode, Scope>();
final _vars = new Map<VariableDeclaration, VarDesc>();
Map<TreeNode, List<int>>? _temps;
Map<TreeNode, VariableDeclaration>? _capturedSavedContextVars;
Map<TreeNode, VariableDeclaration>? _capturedExceptionVars;
Map<TreeNode, VariableDeclaration>? _capturedStackTraceVars;
Map<ForInStatement, VariableDeclaration>? _capturedIteratorVars;
final BytecodeOptions options;
final StaticTypeContext staticTypeContext;
Scope? _currentScopeInternal;
Scope get _currentScope => _currentScopeInternal!;
Frame? _currentFrameInternal;
Frame get _currentFrame => _currentFrameInternal!;
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 getParamIndexInFrame(VariableDeclaration variable) => isCaptured(variable)
? getOriginalParamSlotIndex(variable)
: getVarIndexInFrame(variable);
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 suspendStateVarIndexInFrame => getVarIndexInFrame(_currentFrame
.suspendStateVar ??
(throw 'Suspend state variable is not declared in ${_currentFrame.function}'));
VariableDeclaration get functionTypeArgsVar =>
_currentFrame.functionTypeArgsVar ??
(throw 'FunctionTypeArgs variable is not declared in ${_currentFrame.function}');
int get functionTypeArgsVarIndexInFrame =>
getVarIndexInFrame(functionTypeArgsVar);
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;
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 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;
bool get isSuspendableFunction => _currentFrame.isSuspendableFunction;
bool get makesCopyOfParameters => _currentFrame.makesCopyOfParameters;
List<VariableDeclaration> get originalNamedParameters =>
_currentFrame.originalNamedParameters;
List<VariableDeclaration> get sortedNamedParameters =>
_currentFrame.sortedNamedParameters;
LocalVariables(Member node, this.options, this.staticTypeContext) {
final scopeBuilder = new _ScopeBuilder(this);
node.accept(scopeBuilder);
final allocator = new _Allocator(this);
node.accept(allocator);
}
void enterScope(TreeNode node) {
_currentScopeInternal = _scopes[node];
_currentFrameInternal = _currentScope.frame;
}
void leaveScope() {
_currentScopeInternal = _currentScope.parent;
_currentFrameInternal = _currentScopeInternal?.frame;
}
void withTemp(TreeNode node, int temp, void action()) {
final old = _temps![node];
assert(old == null || old.length == 1);
_temps![node] = [temp];
action();
if (old == null) {
_temps!.remove(node);
} else {
_temps![node] = old;
}
}
}
class VarDesc {
final VariableDeclaration declaration;
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;
}
void moveToScope(Scope newScope) {
assert(index == null);
scope.vars.remove(this);
newScope.vars.add(this);
scope = newScope;
}
String toString() => 'var ${declaration.name}';
}
class Frame {
final TreeNode function;
final Frame? parent;
late Scope topScope;
late List<VariableDeclaration> originalNamedParameters;
late List<VariableDeclaration> sortedNamedParameters;
int numParameters = 0;
int numTypeArguments = 0;
bool hasOptionalParameters = false;
bool hasCapturedParameters = false;
bool hasClosures = false;
VariableDeclaration? receiverVar;
VariableDeclaration? capturedReceiverVar;
VariableDeclaration? functionTypeArgsVar;
VariableDeclaration? factoryTypeArgsVar;
VariableDeclaration? closureVar;
VariableDeclaration? contextVar;
VariableDeclaration? scratchVar;
VariableDeclaration? returnVar;
VariableDeclaration? suspendStateVar;
Map<String, VariableDeclaration>? syntheticVars;
int frameSize = 0;
List<int> temporaries = <int>[];
int? contextLevelAtEntry;
Frame(this.function, this.parent);
VariableDeclaration getSyntheticVar(String name) {
final syntheticVars = this.syntheticVars;
if (syntheticVars == null) {
throw 'No synthetic variables declared in ${function}!';
}
final v = syntheticVars[name];
if (v == null) {
throw '${name} variable is not declared in ${function}';
}
return v;
}
bool get isSuspendableFunction => suspendStateVar != null;
bool get makesCopyOfParameters =>
hasOptionalParameters || isSuspendableFunction;
}
class Scope {
final Scope? parent;
final Frame frame;
final int loopDepth;
final List<VarDesc> vars = <VarDesc>[];
int localsUsed = 0;
int tempsUsed = 0;
Scope? contextOwner;
int contextUsed = 0;
int contextSize = 0;
int? contextLevel;
int? contextId;
Scope(this.parent, this.frame, this.loopDepth);
bool get hasContext => contextSize > 0;
}
bool _hasReceiverParameter(TreeNode node) {
return node is Constructor ||
(node is Procedure && !node.isStatic) ||
(node is Field && !node.isStatic);
}
class _ScopeBuilder extends RecursiveVisitor {
final LocalVariables locals;
Scope? _currentScopeInternal;
Scope get _currentScope => _currentScopeInternal!;
Frame? _currentFrameInternal;
Frame get _currentFrame => _currentFrameInternal!;
List<TreeNode> _enclosingTryBlocks = const [];
List<TreeNode> _enclosingTryCatches = const [];
int _loopDepth = 0;
_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) {
if (_hasReceiverParameter(node)) {
final receiverVar =
_currentFrame.receiverVar = VariableDeclaration('this');
_declareVariable(receiverVar);
}
node.initializer?.accept(this);
} else {
assert(node is Procedure ||
node is Constructor ||
node is FunctionDeclaration ||
node is FunctionExpression);
FunctionNode function = (node as dynamic).function;
if (function.dartAsyncMarker != AsyncMarker.Sync) {
final suspendStateVar = _currentFrame.suspendStateVar =
VariableDeclaration(':suspend_state');
_declareVariable(suspendStateVar);
if (function.dartAsyncMarker != AsyncMarker.SyncStar) {
final returnVar =
_currentFrame.returnVar = VariableDeclaration(':return');
_declareVariable(returnVar);
}
}
if (node is Procedure && node.isFactory) {
assert(_currentFrame.parent == null);
_currentFrame.numTypeArguments = 0;
final factoryTypeArgsVar = _currentFrame.factoryTypeArgsVar =
VariableDeclaration(':type_arguments');
_declareVariable(factoryTypeArgsVar);
} else {
_currentFrame.numTypeArguments =
(_currentFrame.parent?.numTypeArguments ?? 0) +
function.typeParameters.length;
if (_currentFrame.numTypeArguments > 0) {
final functionTypeArgsVar = _currentFrame.functionTypeArgsVar =
VariableDeclaration(':function_type_arguments_var')
..fileOffset = function.fileOffset;
_declareVariable(functionTypeArgsVar);
}
final parentFactoryTypeArgsVar =
_currentFrame.parent?.factoryTypeArgsVar;
if (parentFactoryTypeArgsVar != null) {
_currentFrame.factoryTypeArgsVar = parentFactoryTypeArgsVar;
}
}
if (_hasReceiverParameter(node)) {
final receiverVar =
_currentFrame.receiverVar = VariableDeclaration('this');
_declareVariable(receiverVar);
} else {
final parentReceiverVar = _currentFrame.parent?.receiverVar;
if (parentReceiverVar != null) {
_currentFrame.receiverVar = parentReceiverVar;
}
}
if (node is FunctionDeclaration || node is FunctionExpression) {
final closureVar =
_currentFrame.closureVar = VariableDeclaration(':closure');
_declareVariable(closureVar);
}
_currentFrame.originalNamedParameters = function.namedParameters;
_currentFrame.sortedNamedParameters = _sortNamedParameters(function);
visitList(function.positionalParameters, this);
visitList(_currentFrame.sortedNamedParameters, this);
function.emittedValueType?.accept(this);
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) {
final contextVar =
_currentFrame.contextVar = VariableDeclaration(':context');
_declareVariable(contextVar);
final scratchVar =
_currentFrame.scratchVar = VariableDeclaration(':scratch');
_declareVariable(scratchVar);
}
if (_hasReceiverParameter(node)) {
if (locals.isCaptured(_currentFrame.receiverVar!)) {
// Duplicate receiver variable for local use.
_currentFrame.capturedReceiverVar = _currentFrame.receiverVar;
final localReceiverVar =
_currentFrame.receiverVar = VariableDeclaration('this');
_declareVariable(localReceiverVar);
}
}
_leaveFrame();
_enclosingTryBlocks = savedEnclosingTryBlocks;
_enclosingTryCatches = savedEnclosingTryCatches;
_loopDepth = saveLoopDepth;
}
void _enterFrame(TreeNode node) {
_currentFrameInternal = new Frame(node, _currentFrameInternal);
_enterScope(node);
_currentFrame.topScope = _currentScope;
}
void _leaveFrame() {
_leaveScope();
_currentFrameInternal = _currentFrame.parent;
}
void _enterScope(TreeNode node) {
_currentScopeInternal =
new Scope(_currentScopeInternal, _currentFrame, _loopDepth);
assert(locals._scopes[node] == null);
locals._scopes[node] = _currentScope;
}
void _leaveScope() {
_currentScopeInternal = _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,
'Double declaring variable ${variable}!');
locals._vars[variable] = v;
}
void _useVariable(VariableDeclaration variable) {
final VarDesc? v = locals._vars[variable];
if (v == null) {
throw 'Variable $variable is used before declared';
}
if (v.frame != _currentFrame) {
v.capture();
}
}
void _useThis() {
_useVariable(_currentFrame.receiverVar!);
}
void _visitWithScope(TreeNode node) {
_enterScope(node);
node.visitChildren(this);
_leaveScope();
}
@override
void defaultMember(Member node) {
_visitFunction(node);
}
@override
void 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
void visitFunctionExpression(FunctionExpression node) {
_currentFrame.hasClosures = true;
if (_currentFrame.receiverVar != null) {
// Closure creation may load receiver to get instantiator type arguments.
_useThis();
}
_visitFunction(node);
}
@override
void visitLocalFunctionInvocation(LocalFunctionInvocation node) {
_useVariable(node.variable);
node.visitChildren(this);
}
@override
void visitVariableDeclaration(VariableDeclaration node) {
_declareVariable(node);
node.visitChildren(this);
}
@override
void visitVariableGet(VariableGet node) {
_useVariable(node.variable);
if (node.variable.isLate) {
node.variable.initializer?.accept(this);
}
}
@override
void visitVariableSet(VariableSet node) {
_useVariable(node.variable);
node.visitChildren(this);
}
@override
void visitThisExpression(ThisExpression node) {
_useThis();
}
@override
void visitSuperMethodInvocation(SuperMethodInvocation node) {
_useThis();
node.visitChildren(this);
}
@override
void visitSuperPropertyGet(SuperPropertyGet node) {
_useThis();
node.visitChildren(this);
}
@override
void visitSuperPropertySet(SuperPropertySet node) {
_useThis();
node.visitChildren(this);
}
@override
void visitTypeParameterType(TypeParameterType node) {
var parent = node.parameter.declaration;
if (parent is Class) {
_useThis();
} else if (parent is Procedure && parent.isFactory) {
_useVariable(_currentFrame.factoryTypeArgsVar!);
}
node.visitChildren(this);
}
@override
void visitBlock(Block node) {
_visitWithScope(node);
}
@override
void 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
void visitAssertStatement(AssertStatement node) {
if (!locals.options.enableAsserts) {
return;
}
super.visitAssertStatement(node);
}
@override
void visitAssertBlock(AssertBlock node) {
if (!locals.options.enableAsserts) {
return;
}
_visitWithScope(node);
}
@override
void visitForStatement(ForStatement node) {
++_loopDepth;
_visitWithScope(node);
--_loopDepth;
}
@override
void visitForInStatement(ForInStatement node) {
node.iterable.accept(this);
++_loopDepth;
_enterScope(node);
node.variable.accept(this);
node.body.accept(this);
_leaveScope();
--_loopDepth;
}
@override
void visitCatch(Catch node) {
_visitWithScope(node);
}
@override
void visitLet(Let node) {
_visitWithScope(node);
}
@override
void visitTryCatch(TryCatch node) {
_enclosingTryBlocks.add(node);
node.body.accept(this);
_enclosingTryBlocks.removeLast();
_enclosingTryCatches.add(node);
visitList(node.catches, this);
_enclosingTryCatches.removeLast();
}
@override
void visitTryFinally(TryFinally node) {
_enclosingTryBlocks.add(node);
node.body.accept(this);
_enclosingTryBlocks.removeLast();
_enclosingTryCatches.add(node);
node.finalizer.accept(this);
_enclosingTryCatches.removeLast();
}
@override
void 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)) {
final returnVar =
_currentFrame.returnVar = VariableDeclaration(':return');
_declareVariable(returnVar, _currentFrame.topScope);
}
node.visitChildren(this);
}
@override
void visitWhileStatement(WhileStatement node) {
++_loopDepth;
node.visitChildren(this);
--_loopDepth;
}
@override
void visitDoStatement(DoStatement node) {
++_loopDepth;
node.visitChildren(this);
--_loopDepth;
}
}
// Allocate context slots for each local variable.
class _Allocator extends RecursiveVisitor {
final LocalVariables locals;
Scope? _currentScopeInternal;
Scope get _currentScope => _currentScopeInternal!;
Frame? _currentFrameInternal;
Frame get _currentFrame => _currentFrameInternal!;
int _contextIdCounter = 0;
_Allocator(this.locals);
void _enterScope(TreeNode node) {
final scope = locals._scopes[node];
assert(scope!.parent == _currentScopeInternal);
_currentScopeInternal = scope;
final parentScope = _currentScope.parent;
if (_currentScope.frame != _currentFrameInternal) {
_currentFrameInternal = _currentScope.frame;
if (parentScope != null) {
_currentFrame.contextLevelAtEntry = parentScope.contextLevel;
}
_currentScope.localsUsed = 0;
_currentScope.tempsUsed = 0;
} else {
_currentScope.localsUsed = parentScope!.localsUsed;
_currentScope.tempsUsed = parentScope.tempsUsed;
}
assert(_currentScope.contextOwner == null);
assert(_currentScope.contextLevel == null);
assert(_currentScope.contextId == null);
final int parentContextLevel =
parentScope != null ? parentScope.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;
}
}
final contextOwner = _currentScope.contextOwner!;
contextOwner.contextSize += numCaptured;
if (contextOwner == _currentScope) {
_currentScope.contextLevel = parentContextLevel + 1;
int saturatedContextId = min(_contextIdCounter++, contextIdLimit - 1);
_currentScope.contextId = saturatedContextId;
} else {
_currentScope.contextLevel = contextOwner.contextLevel;
_currentScope.contextId = contextOwner.contextId;
}
} else {
_currentScope.contextLevel = parentContextLevel;
}
}
void _leaveScope() {
assert(_currentScope.contextUsed == _currentScope.contextSize);
_currentScopeInternal = _currentScope.parent;
_currentFrameInternal = _currentScopeInternal?.frame;
// Remove temporary variables which are out of scope.
if (_currentScopeInternal != 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}) {
locals._temps ??= new Map<TreeNode, List<int>>();
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) {
final index = v.index = _currentScope.contextOwner!.contextUsed++;
if (index >= capturedVariableIndexLimit) {
throw new LocalVariableIndexOverflowException();
}
v.originalParamSlotIndex = paramSlotIndex;
return;
}
if (paramSlotIndex != null) {
assert(paramSlotIndex < 0 ||
(_currentFrame.makesCopyOfParameters &&
paramSlotIndex <
_currentScope.localsUsed + _currentFrame.numParameters));
v.index = paramSlotIndex;
} else {
final index = v.index = _currentScope.localsUsed++;
if (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);
assert(_currentScope.localsUsed ==
(_currentFrame.isSuspendableFunction ? 1 : 0));
int paramSlotIndex = _currentFrame.makesCopyOfParameters
? _currentScope.localsUsed + 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 = _hasReceiverParameter(node);
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.makesCopyOfParameters) {
_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) {
if (_hasReceiverParameter(node)) {
_currentFrame.numParameters = 1;
_allocateParameter(_currentFrame.receiverVar!, 0);
if (_currentFrame.capturedReceiverVar != null) {
_allocateVariable(_currentFrame.capturedReceiverVar!);
_currentFrame.hasCapturedParameters = true;
}
}
_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;
if (_currentFrame.isSuspendableFunction) {
// Allocate suspend state variable at fixed slot before parameters.
_ensureVariableAllocated(_currentFrame.suspendStateVar);
}
_allocateParameters(node, function);
_allocateSpecialVariables();
if (node is Constructor) {
for (var field in node.enclosingClass.fields) {
if (!field.isStatic) {
field.initializer?.accept(this);
}
}
visitList(node.initializers, this);
}
// The visit the function body.
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
void defaultMember(Member node) {
_visitFunction(node);
}
@override
void visitFunctionDeclaration(FunctionDeclaration node) {
_allocateVariable(node.variable);
_allocateTemp(node);
_visitFunction(node);
_freeTemp(node);
}
@override
void visitFunctionExpression(FunctionExpression node) {
_allocateTemp(node);
_visitFunction(node);
_freeTemp(node);
}
@override
void visitVariableDeclaration(VariableDeclaration node) {
_allocateVariable(node);
node.visitChildren(this);
}
@override
void visitBlock(Block node) {
_visit(node, scope: true);
}
@override
void 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
void visitAssertStatement(AssertStatement node) {
if (!locals.options.enableAsserts) {
return;
}
super.visitAssertStatement(node);
}
@override
void visitAssertBlock(AssertBlock node) {
if (!locals.options.enableAsserts) {
return;
}
_visit(node, scope: true);
}
@override
void visitForStatement(ForStatement node) {
_visit(node, scope: true);
}
@override
void visitForInStatement(ForInStatement node) {
_allocateTemp(node);
if (locals._capturedIteratorVars != null) {
_ensureVariableAllocated(locals._capturedIteratorVars![node]);
}
node.iterable.accept(this);
_enterScope(node);
node.variable.accept(this);
node.body.accept(this);
_leaveScope();
_freeTemp(node);
}
@override
void visitCatch(Catch node) {
_visit(node, scope: true);
}
@override
void visitLet(Let node) {
_visit(node, scope: true);
}
// -------------- Allocation of temporaries --------------
@override
void visitConstructorInvocation(ConstructorInvocation node) {
if (node.isConst) {
return;
}
_visit(node, temps: 1);
}
@override
void visitListLiteral(ListLiteral node) {
if (node.isConst) {
return;
}
_visit(node, temps: 1);
}
@override
void visitMapLiteral(MapLiteral node) {
if (node.isConst) {
return;
}
_visit(node, temps: 1);
}
@override
void visitStringConcatenation(StringConcatenation node) {
_visit(node, temps: 1);
}
@override
void visitConditionalExpression(ConditionalExpression node) {
_visit(node, temps: 1);
}
@override
void visitLogicalExpression(LogicalExpression node) {
_visit(node, temps: 1);
}
@override
void visitFunctionInvocation(FunctionInvocation node) {
int numTemps = 0;
if (node.kind == FunctionAccessKind.FunctionType) {
numTemps = 1;
}
_visit(node, temps: numTemps);
}
@override
void visitLocalFunctionInvocation(LocalFunctionInvocation node) {
_visit(node, temps: 1);
}
@override
void visitDynamicSet(DynamicSet node) {
_visit(node, temps: 1);
}
@override
void visitInstanceSet(InstanceSet node) {
_visit(node, temps: 1);
}
@override
void visitSuperMethodInvocation(SuperMethodInvocation node) {
_visit(node, temps: 1);
}
@override
void visitSuperPropertyGet(SuperPropertyGet node) {
_visit(node, temps: 1);
}
@override
void visitSuperPropertySet(SuperPropertySet node) {
_visit(node, temps: 1);
}
@override
void visitSwitchStatement(SwitchStatement node) {
_visit(node, temps: 1);
}
@override
void visitVariableGet(VariableGet node) {
_visit(node, temps: node.variable.isLate ? 1 : 0);
}
@override
void visitVariableSet(VariableSet node) {
final v = node.variable;
final bool needsTemp = locals.isCaptured(v) || v.isLate && v.isFinal;
_visit(node, temps: needsTemp ? 1 : 0);
}
@override
void visitStaticSet(StaticSet node) {
_visit(node, temps: 1);
}
@override
void visitTryCatch(TryCatch node) {
_visit(node, temps: 2);
}
@override
void visitTryFinally(TryFinally node) {
_visit(node, temps: 2);
}
@override
void visitInstantiation(Instantiation node) {
_visit(node, temps: 3);
}
@override
void visitNullCheck(NullCheck node) {
_visit(node, temps: 1);
}
@override
void visitAwaitExpression(AwaitExpression node) {
_visit(node, temps: 1);
}
}
class LocalVariableIndexOverflowException
extends BytecodeLimitExceededException {}