Files
sdk/pkg/dart2bytecode/lib/local_vars.dart
T
Tess Strickland a02596b28f [vm,dyn_modules] Source position cleanups and fewer temp uses.
Reduces the usage of temps in the following cases to remove unneeded
StoreLocal/Push instructions:

Refactors visitVariableSet so that a temporary is used only if the
variable is captured and the result is not ignored. (That is,
sets of uncaptured late final fields no longer need a temporary.)

Only allocate a temp for FunctionDeclarations and FunctionExpressions
if there are function type arguments to capture or if the function
is generic (in which case the delayed type arguments field must be
empty-initialized, not null initialized).

-----

Clean up how source positions are emitted to both be more consistent
and to also move some source position emissions closer to the actual
instruction that performs the operation:

Move emission of debugging information for FunctionDeclaration,
and VariableSet to when the variable is actually set. The emission
in VariableDeclaration stays before the initializer as that's when
debugger tests expect to pause at it.

Change the implementation of _genConditionAndJumpIf to use if/else
chaining instead of early returns so that the condition's source
position can be recorded once before the if/then chain and then the
previous source position restored afterward the chain. This means
that _emitDebuggerInformation can be used.

_generateNonLocalControlTransfer is only called during visit methods
where the parent call to _generateNode has already recorded the from
node's position, so there's no need to record it during that call.
Instead, move the emission of the source position to the jump within the
passed continuation.

Remove _emitLocalSourcePosition, since it is no longer used.

Cq-Include-Trybots: luci.dart.try:vm-dyn-linux-debug-x64-try,vm-dyn-mac-debug-arm64-try
Change-Id: Id7722146769c57713ae466d67401258c010cee37
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/473380
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Commit-Queue: Tess Strickland <sstrickl@google.com>
2026-01-19 04:04:43 -08:00

1237 lines
35 KiB
Dart

// 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;
}
bool get capturesOnlyFinalNotLateVars =>
_currentFrame.capturesOnlyFinalNotLateVars;
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;
bool get isFinalNotLate => declaration.isFinal && !declaration.isLate;
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;
bool capturesOnlyFinalNotLateVars = true;
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();
if (!v.isFinalNotLate) {
_currentFrame.capturesOnlyFinalNotLateVars = false;
}
}
}
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);
}
// A temporary is only needed for function declarations or expressions when:
// * There are function type arguments to capture.
// * The function is generic and so the delayed type arguments field of the
// closure must be empty-initialized, not null-initialized.
bool _closureAllocationNeedsTemp(FunctionNode function) =>
_currentFrame.functionTypeArgsVar != null ||
function.typeParameters.isNotEmpty;
@override
void visitFunctionDeclaration(FunctionDeclaration node) {
_allocateVariable(node.variable);
final needsTemp = _closureAllocationNeedsTemp(node.function);
if (needsTemp) {
_allocateTemp(node);
}
_visitFunction(node);
if (needsTemp) {
_freeTemp(node);
}
}
@override
void visitFunctionExpression(FunctionExpression node) {
final needsTemp = _closureAllocationNeedsTemp(node.function);
if (needsTemp) {
_allocateTemp(node);
}
_visitFunction(node);
if (needsTemp) {
_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 bool needsTemp =
node.parent is! ExpressionStatement && locals.isCaptured(node.variable);
_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 {}