Version 3.5.0-176.0.dev
Merge 829261e2ba3c47f681a26887c2ad6f7908d4bef7 into dev
diff --git a/pkg/dart2wasm/lib/async.dart b/pkg/dart2wasm/lib/async.dart
index e741372..8a9ec41 100644
--- a/pkg/dart2wasm/lib/async.dart
+++ b/pkg/dart2wasm/lib/async.dart
@@ -8,525 +8,7 @@
import 'class_info.dart';
import 'closures.dart';
import 'code_generator.dart';
-import 'sync_star.dart' show StateTarget, StateTargetPlacement;
-
-/// Identify which statements contain `await` statements, and assign target
-/// indices to all control flow targets of these.
-///
-/// Target indices are assigned in program order.
-class _YieldFinder extends RecursiveVisitor {
- final List<StateTarget> targets = [];
- final bool enableAsserts;
-
- // The number of `await` statements seen so far.
- int yieldCount = 0;
-
- _YieldFinder(this.enableAsserts);
-
- List<StateTarget> find(FunctionNode function) {
- // Initial state
- addTarget(function.body!, StateTargetPlacement.Inner);
- assert(function.body is Block || function.body is ReturnStatement);
- recurse(function.body!);
- // Final state
- addTarget(function.body!, StateTargetPlacement.After);
- return targets;
- }
-
- /// Recurse into a statement and then remove any targets added by the
- /// statement if it doesn't contain any `await` statements.
- void recurse(Statement statement) {
- final yieldCountIn = yieldCount;
- final targetsIn = targets.length;
- statement.accept(this);
- if (yieldCount == yieldCountIn) {
- targets.length = targetsIn;
- }
- }
-
- void addTarget(TreeNode node, StateTargetPlacement placement) {
- targets.add(StateTarget(targets.length, node, placement));
- }
-
- @override
- void visitBlock(Block node) {
- for (Statement statement in node.statements) {
- recurse(statement);
- }
- }
-
- @override
- void visitDoStatement(DoStatement node) {
- addTarget(node, StateTargetPlacement.Inner);
- recurse(node.body);
- }
-
- @override
- void visitForStatement(ForStatement node) {
- addTarget(node, StateTargetPlacement.Inner);
- recurse(node.body);
- addTarget(node, StateTargetPlacement.After);
- }
-
- @override
- void visitIfStatement(IfStatement node) {
- recurse(node.then);
- if (node.otherwise != null) {
- addTarget(node, StateTargetPlacement.Inner);
- recurse(node.otherwise!);
- }
- addTarget(node, StateTargetPlacement.After);
- }
-
- @override
- void visitLabeledStatement(LabeledStatement node) {
- recurse(node.body);
- addTarget(node, StateTargetPlacement.After);
- }
-
- @override
- void visitSwitchStatement(SwitchStatement node) {
- for (SwitchCase c in node.cases) {
- addTarget(c, StateTargetPlacement.Inner);
- recurse(c.body);
- }
- addTarget(node, StateTargetPlacement.After);
- }
-
- @override
- void visitTryFinally(TryFinally node) {
- // [TryFinally] blocks are always compiled to as CFG, even when they don't
- // have awaits. This is to keep the code size small: with normal
- // compilation finalizer blocks need to be duplicated based on
- // continuations, which we don't need in the CFG implementation.
- yieldCount += 1;
- recurse(node.body);
- addTarget(node, StateTargetPlacement.Inner);
- recurse(node.finalizer);
- addTarget(node, StateTargetPlacement.After);
- }
-
- @override
- void visitTryCatch(TryCatch node) {
- // Also always compile [TryCatch] blocks to the CFG to be able to set
- // finalizer continuations.
- yieldCount += 1;
- recurse(node.body);
- for (Catch c in node.catches) {
- addTarget(c, StateTargetPlacement.Inner);
- recurse(c.body);
- }
- addTarget(node, StateTargetPlacement.After);
- }
-
- @override
- void visitWhileStatement(WhileStatement node) {
- addTarget(node, StateTargetPlacement.Inner);
- recurse(node.body);
- addTarget(node, StateTargetPlacement.After);
- }
-
- @override
- void visitYieldStatement(YieldStatement node) {
- throw 'Yield statement in async function: $node (${node.location})';
- }
-
- // Handle awaits. After the await transformation await can only appear in a
- // RHS of a top-level assignment, or as a top-level statement.
- @override
- void visitVariableSet(VariableSet node) {
- if (node.value is AwaitExpression) {
- yieldCount++;
- addTarget(node, StateTargetPlacement.After);
- } else {
- super.visitVariableSet(node);
- }
- }
-
- @override
- void visitExpressionStatement(ExpressionStatement node) {
- if (node.expression is AwaitExpression) {
- yieldCount++;
- addTarget(node, StateTargetPlacement.After);
- } else {
- super.visitExpressionStatement(node);
- }
- }
-
- @override
- void visitFunctionExpression(FunctionExpression node) {}
-
- @override
- void visitFunctionDeclaration(FunctionDeclaration node) {}
-
- // Any other await expression means the await transformer is buggy and didn't
- // transform the expression as expected.
- @override
- void visitAwaitExpression(AwaitExpression node) {
- // Await expressions should've been converted to `VariableSet` statements
- // by `_AwaitTransformer`.
- throw 'Unexpected await expression: $node (${node.location})';
- }
-
- @override
- void visitAssertStatement(AssertStatement node) {
- if (enableAsserts) {
- super.visitAssertStatement(node);
- }
- }
-
- @override
- void visitAssertBlock(AssertBlock node) {
- if (enableAsserts) {
- super.visitAssertBlock(node);
- }
- }
-}
-
-class _ExceptionHandlerStack {
- /// Current exception handler stack. A CFG block generated when this is not
- /// empty should have a Wasm `try` instruction wrapping the block.
- ///
- /// A `catch` block will jump to the next handler on the stack (the last
- /// handler in the list), which then jumps to the next if the exception type
- /// test fails.
- ///
- /// Because CFG blocks for [Catch] blocks and finalizers will have Wasm `try`
- /// blocks for the parent handlers, we can use a Wasm `throw` instruction
- /// (instead of jumping to the parent handler) in [Catch] blocks and
- /// finalizers for rethrowing.
- final List<_ExceptionHandler> _handlers = [];
-
- /// Maps Wasm `try` blocks to number of handlers in [_handlers] that they
- /// cover for.
- final List<int> _tryBlockNumHandlers = [];
-
- final AsyncCodeGenerator codeGen;
-
- _ExceptionHandlerStack(this.codeGen);
-
- void pushTryCatch(TryCatch node) {
- final catcher = Catcher.fromTryCatch(
- codeGen, node, codeGen.innerTargets[node.catches.first]!);
- _handlers.add(catcher);
- }
-
- Finalizer pushTryFinally(TryFinally node) {
- final finalizer =
- Finalizer(codeGen, node, nextFinalizer, codeGen.innerTargets[node]!);
- _handlers.add(finalizer);
- return finalizer;
- }
-
- void pop() {
- _handlers.removeLast();
- }
-
- int get numHandlers => _handlers.length;
-
- int get coveredHandlers => _tryBlockNumHandlers.fold(0, (i1, i2) => i1 + i2);
-
- int get numFinalizers {
- int i = 0;
- for (final handler in _handlers) {
- if (handler is Finalizer) {
- i += 1;
- }
- }
- return i;
- }
-
- Finalizer? get nextFinalizer {
- for (final handler in _handlers.reversed) {
- if (handler is Finalizer) {
- return handler;
- }
- }
- return null;
- }
-
- void forEachFinalizer(
- void Function(Finalizer finalizer, bool lastFinalizer) f) {
- Finalizer? finalizer = nextFinalizer;
- while (finalizer != null) {
- Finalizer? next = finalizer.parentFinalizer;
- f(finalizer, next == null);
- finalizer = next;
- }
- }
-
- /// Generates Wasm `try` blocks for Dart `try` blocks wrapping the current
- /// CFG block.
- ///
- /// Call this when generating a new CFG block.
- void generateTryBlocks(w.InstructionsBuilder b) {
- final handlersToCover = _handlers.length - coveredHandlers;
-
- if (handlersToCover == 0) {
- return;
- }
-
- b.try_();
- _tryBlockNumHandlers.add(handlersToCover);
- }
-
- /// Terminates Wasm `try` blocks generated by [generateTryBlocks].
- ///
- /// Call this right before terminating a CFG block.
- void terminateTryBlocks() {
- int nextHandlerIdx = _handlers.length - 1;
- for (final int nCoveredHandlers in _tryBlockNumHandlers.reversed) {
- final stackTraceLocal = codeGen
- .addLocal(codeGen.translator.stackTraceInfo.repr.nonNullableType);
-
- final exceptionLocal =
- codeGen.addLocal(codeGen.translator.topInfo.nonNullableType);
-
- void generateCatchBody() {
- // Set continuations of finalizers that can be reached by this `catch`
- // (or `catch_all`) as "rethrow".
- for (int i = 0; i < nCoveredHandlers; i += 1) {
- final handler = _handlers[nextHandlerIdx - i];
- if (handler is Finalizer) {
- handler.setContinuationRethrow(
- () => codeGen.b.local_get(exceptionLocal),
- () => codeGen.b.local_get(stackTraceLocal));
- }
- }
-
- // Set the untyped "current exception" variable. Catch blocks will do the
- // type tests as necessary using this variable and set their exception
- // and stack trace locals.
- codeGen._setCurrentException(() => codeGen.b.local_get(exceptionLocal));
- codeGen._setCurrentExceptionStackTrace(
- () => codeGen.b.local_get(stackTraceLocal));
-
- codeGen.jumpToTarget(_handlers[nextHandlerIdx].target);
- }
-
- codeGen.b.catch_(codeGen.translator.exceptionTag);
- codeGen.b.local_set(stackTraceLocal);
- codeGen.b.local_set(exceptionLocal);
-
- generateCatchBody();
-
- // Generate a `catch_all` to catch JS exceptions if any of the covered
- // handlers can catch JS exceptions.
- bool canHandleJSExceptions = false;
- for (int handlerIdx = nextHandlerIdx;
- handlerIdx > nextHandlerIdx - nCoveredHandlers;
- handlerIdx -= 1) {
- final handler = _handlers[handlerIdx];
- canHandleJSExceptions |= handler.canHandleJSExceptions;
- }
-
- if (canHandleJSExceptions) {
- codeGen.b.catch_all();
-
- // We can't inspect the thrown object in a `catch_all` and get a stack
- // trace, so we just attach the current stack trace.
- codeGen.call(codeGen.translator.stackTraceCurrent.reference);
- codeGen.b.local_set(stackTraceLocal);
-
- // We create a generic JavaScript error.
- codeGen.call(codeGen.translator.javaScriptErrorFactory.reference);
- codeGen.b.local_set(exceptionLocal);
-
- generateCatchBody();
- }
-
- codeGen.b.end(); // end catch
-
- nextHandlerIdx -= nCoveredHandlers;
- }
-
- _tryBlockNumHandlers.clear();
- }
-}
-
-/// Represents an exception handler (`catch` or `finally`).
-///
-/// Note: for a [TryCatch] with multiple [Catch] blocks we jump to the first
-/// [Catch] block on exception, which checks the exception type and jumps to
-/// the next one if necessary.
-abstract class _ExceptionHandler {
- /// CFG block for the `catch` or `finally` block.
- final StateTarget target;
-
- _ExceptionHandler(this.target);
-
- bool get canHandleJSExceptions;
-}
-
-class Catcher extends _ExceptionHandler {
- final List<VariableDeclaration> _exceptionVars = [];
- final List<VariableDeclaration> _stackTraceVars = [];
- final AsyncCodeGenerator codeGen;
- bool _canHandleJSExceptions = false;
-
- Catcher.fromTryCatch(this.codeGen, TryCatch node, super.target) {
- for (Catch catch_ in node.catches) {
- _exceptionVars.add(catch_.exception!);
- _stackTraceVars.add(catch_.stackTrace!);
- _canHandleJSExceptions |=
- guardCanMatchJSException(codeGen.translator, catch_.guard);
- }
- }
-
- @override
- bool get canHandleJSExceptions => _canHandleJSExceptions;
-
- void setException(void Function() pushException) {
- for (final exceptionVar in _exceptionVars) {
- codeGen._setVariable(exceptionVar, pushException);
- }
- }
-
- void setStackTrace(void Function() pushStackTrace) {
- for (final stackTraceVar in _stackTraceVars) {
- codeGen._setVariable(stackTraceVar, pushStackTrace);
- }
- }
-}
-
-const int continuationFallthrough = 0;
-const int continuationReturn = 1;
-const int continuationRethrow = 2;
-
-// For larger continuation values, `value - continuationJump` gives us the
-// target block index to jump.
-const int continuationJump = 3;
-
-class Finalizer extends _ExceptionHandler {
- final VariableDeclaration _continuationVar;
- final VariableDeclaration _exceptionVar;
- final VariableDeclaration _stackTraceVar;
- final Finalizer? parentFinalizer;
- final AsyncCodeGenerator codeGen;
-
- Finalizer(this.codeGen, TryFinally node, this.parentFinalizer, super.target)
- : _continuationVar =
- (node.parent as Block).statements[0] as VariableDeclaration,
- _exceptionVar =
- (node.parent as Block).statements[1] as VariableDeclaration,
- _stackTraceVar =
- (node.parent as Block).statements[2] as VariableDeclaration;
-
- @override
- bool get canHandleJSExceptions => true;
-
- void setContinuationFallthrough() {
- codeGen._setVariable(_continuationVar, () {
- codeGen.b.i64_const(continuationFallthrough);
- });
- }
-
- void setContinuationReturn() {
- codeGen._setVariable(_continuationVar, () {
- codeGen.b.i64_const(continuationReturn);
- });
- }
-
- void setContinuationRethrow(
- void Function() pushException, void Function() pushStackTrace) {
- codeGen._setVariable(_continuationVar, () {
- codeGen.b.i64_const(continuationRethrow);
- });
- codeGen._setVariable(_exceptionVar, pushException);
- codeGen._setVariable(_stackTraceVar, pushStackTrace);
- }
-
- void setContinuationJump(int index) {
- codeGen._setVariable(_continuationVar, () {
- codeGen.b.i64_const(index + continuationJump);
- });
- }
-
- /// Push continuation of the finalizer block onto the stack as `i32`.
- void pushContinuation() {
- codeGen.visitVariableGet(VariableGet(_continuationVar), w.NumType.i64);
- codeGen.b.i32_wrap_i64();
- }
-
- void pushException() {
- codeGen._getVariable(_exceptionVar);
- }
-
- void pushStackTrace() {
- codeGen._getVariable(_stackTraceVar);
- }
-}
-
-/// Represents target of a `break` statement.
-abstract class LabelTarget {
- void jump(AsyncCodeGenerator codeGen);
-}
-
-/// Target of a [BreakStatement] that can be implemented with a Wasm `br`
-/// instruction.
-///
-/// This [LabelTarget] is used when the [LabeledStatement] is compiled using
-/// the normal code generator (instead of async code generator).
-class DirectLabelTarget implements LabelTarget {
- final w.Label label;
-
- DirectLabelTarget(this.label);
-
- @override
- void jump(AsyncCodeGenerator codeGen) {
- codeGen.b.br(label);
- }
-}
-
-/// Target of a [BreakStatement] when the [LabeledStatement] is compiled to
-/// CFG.
-class IndirectLabelTarget implements LabelTarget {
- /// Number of finalizers wrapping the [LabeledStatement].
- final int finalizerDepth;
-
- /// CFG state for the [LabeledStatement] continuation.
- final StateTarget stateTarget;
-
- IndirectLabelTarget(this.finalizerDepth, this.stateTarget);
-
- @override
- void jump(AsyncCodeGenerator codeGen) {
- final currentFinalizerDepth = codeGen._exceptionHandlers.numFinalizers;
- final finalizersToRun = currentFinalizerDepth - finalizerDepth;
-
- // Control flow overridden by a `break`, reset finalizer continuations.
- var i = finalizersToRun;
- codeGen._exceptionHandlers.forEachFinalizer((finalizer, last) {
- if (i <= 0) {
- // Finalizer won't be run by the `break`, reset continuation.
- finalizer.setContinuationFallthrough();
- } else {
- // Finalizer will be run by the `break`. Each finalizer jumps to the
- // next, last finalizer jumps to the `break` target.
- finalizer.setContinuationJump(i == 1
- ? stateTarget.index
- : finalizer.parentFinalizer!.target.index);
- }
- i -= 1;
- });
-
- if (finalizersToRun == 0) {
- codeGen.jumpToTarget(stateTarget);
- } else {
- codeGen.jumpToTarget(codeGen._exceptionHandlers.nextFinalizer!.target);
- }
- }
-}
-
-/// Exception and stack trace variables of a [Catch] block. These variables are
-/// used to get the exception and stack trace to throw when compiling
-/// [Rethrow].
-class CatchVariables {
- final VariableDeclaration exception;
- final VariableDeclaration stackTrace;
-
- CatchVariables(this.exception, this.stackTrace);
-}
+import 'state_machine.dart';
class AsyncCodeGenerator extends CodeGenerator {
AsyncCodeGenerator(super.translator, super.function, super.reference);
@@ -560,7 +42,7 @@
/// Exception handlers wrapping the current CFG block. Used to generate Wasm
/// `try` and `catch` blocks around the CFG blocks.
- late final _ExceptionHandlerStack _exceptionHandlers;
+ late final ExceptionHandlerStack exceptionHandlers;
/// Maps jump targets to their CFG targets. Used when jumping to a CFG block
/// on `break`. Keys are [LabeledStatement]s or [SwitchCase]s.
@@ -593,7 +75,7 @@
void _generateBodies(FunctionNode functionNode) {
// Number and categorize CFG targets.
- targets = _YieldFinder(translator.options.enableAsserts).find(functionNode);
+ targets = YieldFinder(translator.options.enableAsserts).find(functionNode);
for (final target in targets) {
switch (target.placement) {
case StateTargetPlacement.Inner:
@@ -605,7 +87,7 @@
}
}
- _exceptionHandlers = _ExceptionHandlerStack(this);
+ exceptionHandlers = ExceptionHandlerStack(this);
// Wasm function containing the body of the `async` function
// (`_AyncResumeFun`).
@@ -823,9 +305,9 @@
'target.index = ${target.index}, '
'currentTargetIndex = $currentTargetIndex, '
'target.node.location = ${target.node.location}');
- _exceptionHandlers.terminateTryBlocks();
+ exceptionHandlers.terminateTryBlocks();
b.end();
- _exceptionHandlers.generateTryBlocks(b);
+ exceptionHandlers.generateTryBlocks(b);
}
void jumpToTarget(StateTarget target,
@@ -903,7 +385,7 @@
b.end();
} else {
labelTargets[node] =
- IndirectLabelTarget(_exceptionHandlers.numFinalizers, after);
+ IndirectLabelTarget(exceptionHandlers.numFinalizers, after);
visitStatement(node.body);
labelTargets.remove(node);
_emitTargetLabel(after);
@@ -990,7 +472,7 @@
// Add jump infos
for (final SwitchCase case_ in node.cases) {
labelTargets[case_] = IndirectLabelTarget(
- _exceptionHandlers.numFinalizers, innerTargets[case_]!);
+ exceptionHandlers.numFinalizers, innerTargets[case_]!);
}
// Emit case bodies
@@ -1029,12 +511,12 @@
}
}
- _exceptionHandlers.pushTryCatch(node);
- _exceptionHandlers.generateTryBlocks(b);
+ exceptionHandlers.pushTryCatch(node);
+ exceptionHandlers.generateTryBlocks(b);
visitStatement(node.body);
jumpToTarget(after);
- _exceptionHandlers.terminateTryBlocks();
- _exceptionHandlers.pop();
+ exceptionHandlers.terminateTryBlocks();
+ exceptionHandlers.pop();
void emitCatchBlock(Catch catch_, Catch? nextCatch, bool emitGuard) {
if (emitGuard) {
@@ -1057,10 +539,10 @@
b.ref_as_non_null();
// TODO (omersa): When there is a finalizer we can jump to it
// directly, instead of via throw/catch. Would that be faster?
- _exceptionHandlers.forEachFinalizer(
+ exceptionHandlers.forEachFinalizer(
(finalizer, last) => finalizer.setContinuationRethrow(
() => _getVariableBoxed(catch_.exception!),
- () => _getVariable(catch_.stackTrace!),
+ () => getVariable(catch_.stackTrace!),
));
b.throw_(translator.exceptionTag);
}
@@ -1068,7 +550,7 @@
}
// Set exception and stack trace variables.
- _setVariable(catch_.exception!, () {
+ setVariable(catch_.exception!, () {
_getCurrentException();
// Type test already passed, convert the exception.
translator.convertType(
@@ -1080,7 +562,7 @@
.unpacked,
translator.translateType(catch_.exception!.type));
});
- _setVariable(catch_.stackTrace!, () => _getCurrentExceptionStackTrace());
+ setVariable(catch_.stackTrace!, () => _getCurrentExceptionStackTrace());
catchVariableStack
.add(CatchVariables(catch_.exception!, catch_.stackTrace!));
@@ -1131,16 +613,16 @@
final StateTarget fallthroughContinuationTarget = afterTargets[node]!;
// Body
- final finalizer = _exceptionHandlers.pushTryFinally(node);
- _exceptionHandlers.generateTryBlocks(b);
+ final finalizer = exceptionHandlers.pushTryFinally(node);
+ exceptionHandlers.generateTryBlocks(b);
visitStatement(node.body);
// Set continuation of the finalizer.
finalizer.setContinuationFallthrough();
jumpToTarget(finalizerTarget);
- _exceptionHandlers.terminateTryBlocks();
- _exceptionHandlers.pop();
+ exceptionHandlers.terminateTryBlocks();
+ exceptionHandlers.pop();
// Finalizer
{
@@ -1216,7 +698,7 @@
@override
void visitReturnStatement(ReturnStatement node) {
- final Finalizer? firstFinalizer = _exceptionHandlers.nextFinalizer;
+ final Finalizer? firstFinalizer = exceptionHandlers.nextFinalizer;
if (firstFinalizer == null) {
b.local_get(suspendStateLocal);
@@ -1246,7 +728,7 @@
// Update continuation variables of finalizers. Last finalizer returns
// the value.
- _exceptionHandlers.forEachFinalizer((finalizer, last) {
+ exceptionHandlers.forEachFinalizer((finalizer, last) {
if (last) {
finalizer.setContinuationReturn();
} else {
@@ -1271,7 +753,7 @@
call(translator.stackTraceCurrent.reference);
b.local_set(stackTraceLocal);
- _exceptionHandlers.forEachFinalizer((finalizer, last) {
+ exceptionHandlers.forEachFinalizer((finalizer, last) {
finalizer.setContinuationRethrow(() => b.local_get(exceptionLocal),
() => b.local_get(stackTraceLocal));
});
@@ -1291,10 +773,10 @@
w.ValueType visitRethrow(Rethrow node, w.ValueType expectedType) {
final catchVars = catchVariableStack.last;
- _exceptionHandlers.forEachFinalizer((finalizer, last) {
+ exceptionHandlers.forEachFinalizer((finalizer, last) {
finalizer.setContinuationRethrow(
() => _getVariableBoxed(catchVars.exception),
- () => _getVariable(catchVars.stackTrace),
+ () => getVariable(catchVars.stackTrace),
);
});
@@ -1344,7 +826,7 @@
}
// Set state target to label after await.
- final StateTarget after = afterTargets[node.parent]!;
+ final StateTarget after = afterTargets[node]!;
b.local_get(suspendStateLocal);
b.i32_const(after.index);
b.struct_set(
@@ -1387,7 +869,7 @@
b.local_get(pendingExceptionLocal);
b.br_on_null(exceptionBlock);
- _exceptionHandlers.forEachFinalizer((finalizer, last) {
+ exceptionHandlers.forEachFinalizer((finalizer, last) {
finalizer.setContinuationRethrow(() {
b.local_get(pendingExceptionLocal);
b.ref_as_non_null();
@@ -1400,14 +882,14 @@
b.throw_(translator.exceptionTag);
b.end(); // exceptionBlock
- _setVariable(awaitValueVar, () {
+ setVariable(awaitValueVar, () {
b.local_get(awaitValueLocal);
translator.convertType(
function, awaitValueLocal.type, translateType(awaitValueVar.type));
});
}
- void _setVariable(VariableDeclaration variable, void Function() pushValue) {
+ void setVariable(VariableDeclaration variable, void Function() pushValue) {
final w.Local? local = locals[variable];
final Capture? capture = closures.captures[variable];
if (capture != null) {
@@ -1424,7 +906,7 @@
}
}
- w.ValueType _getVariable(VariableDeclaration variable) {
+ w.ValueType getVariable(VariableDeclaration variable) {
final w.Local? local = locals[variable];
final Capture? capture = closures.captures[variable];
if (capture != null) {
@@ -1445,9 +927,9 @@
}
}
- /// Same as [_getVariable], but boxes the value if it's not already boxed.
+ /// Same as [getVariable], but boxes the value if it's not already boxed.
void _getVariableBoxed(VariableDeclaration variable) {
- final varType = _getVariable(variable);
+ final varType = getVariable(variable);
translator.convertType(function, varType, translator.topInfo.nullableType);
}
@@ -1457,7 +939,7 @@
FieldIndex.asyncSuspendStateCurrentException);
}
- void _setCurrentException(void Function() emitValue) {
+ void setCurrentException(void Function() emitValue) {
b.local_get(suspendStateLocal);
emitValue();
b.struct_set(asyncSuspendStateInfo.struct,
@@ -1470,7 +952,7 @@
FieldIndex.asyncSuspendStateCurrentExceptionStackTrace);
}
- void _setCurrentExceptionStackTrace(void Function() emitValue) {
+ void setCurrentExceptionStackTrace(void Function() emitValue) {
b.local_get(suspendStateLocal);
emitValue();
b.struct_set(asyncSuspendStateInfo.struct,
diff --git a/pkg/dart2wasm/lib/await_transformer.dart b/pkg/dart2wasm/lib/await_transformer.dart
index e882385..7175891 100644
--- a/pkg/dart2wasm/lib/await_transformer.dart
+++ b/pkg/dart2wasm/lib/await_transformer.dart
@@ -7,7 +7,7 @@
import 'package:kernel/core_types.dart';
import 'package:kernel/type_environment.dart';
-import 'async.dart' as asyncCodeGen;
+import 'state_machine.dart' as stateMachineCodeGen;
/// This pass lifts `await` expressions to the top-level. After the pass, all
/// `await` expressions will have the form:
@@ -462,7 +462,7 @@
// Variable for the finalizer block continuation.
final continuationVar = VariableDeclaration(null,
- initializer: IntLiteral(asyncCodeGen.continuationFallthrough),
+ initializer: IntLiteral(stateMachineCodeGen.continuationFallthrough),
type: InterfaceType(coreTypes.intClass, Nullability.nonNullable),
isSynthesized: true);
diff --git a/pkg/dart2wasm/lib/state_machine.dart b/pkg/dart2wasm/lib/state_machine.dart
new file mode 100644
index 0000000..0e174b4
--- /dev/null
+++ b/pkg/dart2wasm/lib/state_machine.dart
@@ -0,0 +1,562 @@
+// 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 'package:kernel/ast.dart';
+import 'package:wasm_builder/wasm_builder.dart' as w;
+
+import 'async.dart';
+import 'code_generator.dart';
+
+/// Placement of a control flow graph target within a statement. This
+/// distinction is necessary since some statements need to have two targets
+/// associated with them.
+///
+/// The meanings of the variants are:
+///
+/// - [Inner]: Loop entry of a [DoStatement], condition of a [ForStatement] or
+/// [WhileStatement], the `else` branch of an [IfStatement], or the
+/// initial entry target for a function body.
+///
+/// - [After]: After a statement, the resumption point of a suspension point
+/// ([YieldStatement] or [AwaitExpression]), or the final state
+/// (iterator done) of a function body.
+enum StateTargetPlacement { Inner, After }
+
+/// Representation of target in the `sync*` control flow graph.
+class StateTarget {
+ final int index;
+ final TreeNode node;
+ final StateTargetPlacement placement;
+
+ StateTarget(this.index, this.node, this.placement);
+
+ @override
+ String toString() {
+ String place = placement == StateTargetPlacement.Inner ? "in" : "after";
+ return "$index: $place $node";
+ }
+}
+
+/// Identify which statements contain `await` or `yield` statements, and assign
+/// target indices to all control flow targets of these.
+///
+/// Target indices are assigned in program order.
+class YieldFinder extends RecursiveVisitor {
+ final List<StateTarget> targets = [];
+ final bool enableAsserts;
+
+ // The number of `await` statements seen so far.
+ int yieldCount = 0;
+
+ YieldFinder(this.enableAsserts);
+
+ List<StateTarget> find(FunctionNode function) {
+ // Initial state
+ addTarget(function.body!, StateTargetPlacement.Inner);
+ assert(function.body is Block || function.body is ReturnStatement);
+ recurse(function.body!);
+ // Final state
+ addTarget(function.body!, StateTargetPlacement.After);
+ return targets;
+ }
+
+ /// Recurse into a statement and then remove any targets added by the
+ /// statement if it doesn't contain any `await` statements.
+ void recurse(Statement statement) {
+ final yieldCountIn = yieldCount;
+ final targetsIn = targets.length;
+ statement.accept(this);
+ if (yieldCount == yieldCountIn) {
+ targets.length = targetsIn;
+ }
+ }
+
+ void addTarget(TreeNode node, StateTargetPlacement placement) {
+ targets.add(StateTarget(targets.length, node, placement));
+ }
+
+ @override
+ void visitBlock(Block node) {
+ for (Statement statement in node.statements) {
+ recurse(statement);
+ }
+ }
+
+ @override
+ void visitDoStatement(DoStatement node) {
+ addTarget(node, StateTargetPlacement.Inner);
+ recurse(node.body);
+ }
+
+ @override
+ void visitForStatement(ForStatement node) {
+ addTarget(node, StateTargetPlacement.Inner);
+ recurse(node.body);
+ addTarget(node, StateTargetPlacement.After);
+ }
+
+ @override
+ void visitIfStatement(IfStatement node) {
+ recurse(node.then);
+ if (node.otherwise != null) {
+ addTarget(node, StateTargetPlacement.Inner);
+ recurse(node.otherwise!);
+ }
+ addTarget(node, StateTargetPlacement.After);
+ }
+
+ @override
+ void visitLabeledStatement(LabeledStatement node) {
+ recurse(node.body);
+ addTarget(node, StateTargetPlacement.After);
+ }
+
+ @override
+ void visitSwitchStatement(SwitchStatement node) {
+ for (SwitchCase c in node.cases) {
+ addTarget(c, StateTargetPlacement.Inner);
+ recurse(c.body);
+ }
+ addTarget(node, StateTargetPlacement.After);
+ }
+
+ @override
+ void visitTryFinally(TryFinally node) {
+ // [TryFinally] blocks are always compiled to as CFG, even when they don't
+ // have awaits. This is to keep the code size small: with normal
+ // compilation finalizer blocks need to be duplicated based on
+ // continuations, which we don't need in the CFG implementation.
+ yieldCount++;
+ recurse(node.body);
+ addTarget(node, StateTargetPlacement.Inner);
+ recurse(node.finalizer);
+ addTarget(node, StateTargetPlacement.After);
+ }
+
+ @override
+ void visitTryCatch(TryCatch node) {
+ // Also always compile [TryCatch] blocks to the CFG to be able to set
+ // finalizer continuations.
+ yieldCount++;
+ recurse(node.body);
+ for (Catch c in node.catches) {
+ addTarget(c, StateTargetPlacement.Inner);
+ recurse(c.body);
+ }
+ addTarget(node, StateTargetPlacement.After);
+ }
+
+ @override
+ void visitWhileStatement(WhileStatement node) {
+ addTarget(node, StateTargetPlacement.Inner);
+ recurse(node.body);
+ addTarget(node, StateTargetPlacement.After);
+ }
+
+ @override
+ void visitYieldStatement(YieldStatement node) {
+ yieldCount++;
+ addTarget(node, StateTargetPlacement.After);
+ }
+
+ // Handle awaits. After the await transformation await can only appear in a
+ // RHS of a top-level assignment, or as a top-level statement.
+ @override
+ void visitExpressionStatement(ExpressionStatement node) {
+ final expression = node.expression;
+
+ // Handle awaits in RHS of assignments.
+ if (expression is VariableSet) {
+ final value = expression.value;
+ if (value is AwaitExpression) {
+ yieldCount++;
+ addTarget(value, StateTargetPlacement.After);
+ return;
+ }
+ }
+
+ // Handle top-level awaits.
+ if (expression is AwaitExpression) {
+ yieldCount++;
+ addTarget(node, StateTargetPlacement.After);
+ return;
+ }
+
+ super.visitExpressionStatement(node);
+ }
+
+ @override
+ void visitFunctionExpression(FunctionExpression node) {}
+
+ @override
+ void visitFunctionDeclaration(FunctionDeclaration node) {}
+
+ // Any other await expression means the await transformer is buggy and didn't
+ // transform the expression as expected.
+ @override
+ void visitAwaitExpression(AwaitExpression node) {
+ // Await expressions should've been converted to `VariableSet` statements
+ // by `_AwaitTransformer`.
+ throw 'Unexpected await expression: $node (${node.location})';
+ }
+
+ @override
+ void visitAssertStatement(AssertStatement node) {
+ if (enableAsserts) {
+ super.visitAssertStatement(node);
+ }
+ }
+
+ @override
+ void visitAssertBlock(AssertBlock node) {
+ if (enableAsserts) {
+ super.visitAssertBlock(node);
+ }
+ }
+}
+
+class ExceptionHandlerStack {
+ /// Current exception handler stack. A CFG block generated when this is not
+ /// empty should have a Wasm `try` instruction wrapping the block.
+ ///
+ /// A `catch` block will jump to the next handler on the stack (the last
+ /// handler in the list), which then jumps to the next if the exception type
+ /// test fails.
+ ///
+ /// Because CFG blocks for [Catch] blocks and finalizers will have Wasm `try`
+ /// blocks for the parent handlers, we can use a Wasm `throw` instruction
+ /// (instead of jumping to the parent handler) in [Catch] blocks and
+ /// finalizers for rethrowing.
+ final List<_ExceptionHandler> _handlers = [];
+
+ /// Maps Wasm `try` blocks to number of handlers in [_handlers] that they
+ /// cover for.
+ final List<int> _tryBlockNumHandlers = [];
+
+ final AsyncCodeGenerator codeGen;
+
+ ExceptionHandlerStack(this.codeGen);
+
+ void pushTryCatch(TryCatch node) {
+ final catcher = Catcher.fromTryCatch(
+ codeGen, node, codeGen.innerTargets[node.catches.first]!);
+ _handlers.add(catcher);
+ }
+
+ Finalizer pushTryFinally(TryFinally node) {
+ final finalizer =
+ Finalizer(codeGen, node, nextFinalizer, codeGen.innerTargets[node]!);
+ _handlers.add(finalizer);
+ return finalizer;
+ }
+
+ void pop() {
+ _handlers.removeLast();
+ }
+
+ int get numHandlers => _handlers.length;
+
+ int get coveredHandlers => _tryBlockNumHandlers.fold(0, (i1, i2) => i1 + i2);
+
+ int get numFinalizers {
+ int i = 0;
+ for (final handler in _handlers) {
+ if (handler is Finalizer) {
+ i += 1;
+ }
+ }
+ return i;
+ }
+
+ Finalizer? get nextFinalizer {
+ for (final handler in _handlers.reversed) {
+ if (handler is Finalizer) {
+ return handler;
+ }
+ }
+ return null;
+ }
+
+ void forEachFinalizer(
+ void Function(Finalizer finalizer, bool lastFinalizer) f) {
+ Finalizer? finalizer = nextFinalizer;
+ while (finalizer != null) {
+ Finalizer? next = finalizer.parentFinalizer;
+ f(finalizer, next == null);
+ finalizer = next;
+ }
+ }
+
+ /// Generates Wasm `try` blocks for Dart `try` blocks wrapping the current
+ /// CFG block.
+ ///
+ /// Call this when generating a new CFG block.
+ void generateTryBlocks(w.InstructionsBuilder b) {
+ final handlersToCover = _handlers.length - coveredHandlers;
+
+ if (handlersToCover == 0) {
+ return;
+ }
+
+ b.try_();
+ _tryBlockNumHandlers.add(handlersToCover);
+ }
+
+ /// Terminates Wasm `try` blocks generated by [generateTryBlocks].
+ ///
+ /// Call this right before terminating a CFG block.
+ void terminateTryBlocks() {
+ int nextHandlerIdx = _handlers.length - 1;
+ for (final int nCoveredHandlers in _tryBlockNumHandlers.reversed) {
+ final stackTraceLocal = codeGen
+ .addLocal(codeGen.translator.stackTraceInfo.repr.nonNullableType);
+
+ final exceptionLocal =
+ codeGen.addLocal(codeGen.translator.topInfo.nonNullableType);
+
+ void generateCatchBody() {
+ // Set continuations of finalizers that can be reached by this `catch`
+ // (or `catch_all`) as "rethrow".
+ for (int i = 0; i < nCoveredHandlers; i += 1) {
+ final handler = _handlers[nextHandlerIdx - i];
+ if (handler is Finalizer) {
+ handler.setContinuationRethrow(
+ () => codeGen.b.local_get(exceptionLocal),
+ () => codeGen.b.local_get(stackTraceLocal));
+ }
+ }
+
+ // Set the untyped "current exception" variable. Catch blocks will do the
+ // type tests as necessary using this variable and set their exception
+ // and stack trace locals.
+ codeGen.setCurrentException(() => codeGen.b.local_get(exceptionLocal));
+ codeGen.setCurrentExceptionStackTrace(
+ () => codeGen.b.local_get(stackTraceLocal));
+
+ codeGen.jumpToTarget(_handlers[nextHandlerIdx].target);
+ }
+
+ codeGen.b.catch_(codeGen.translator.exceptionTag);
+ codeGen.b.local_set(stackTraceLocal);
+ codeGen.b.local_set(exceptionLocal);
+
+ generateCatchBody();
+
+ // Generate a `catch_all` to catch JS exceptions if any of the covered
+ // handlers can catch JS exceptions.
+ bool canHandleJSExceptions = false;
+ for (int handlerIdx = nextHandlerIdx;
+ handlerIdx > nextHandlerIdx - nCoveredHandlers;
+ handlerIdx -= 1) {
+ final handler = _handlers[handlerIdx];
+ canHandleJSExceptions |= handler.canHandleJSExceptions;
+ }
+
+ if (canHandleJSExceptions) {
+ codeGen.b.catch_all();
+
+ // We can't inspect the thrown object in a `catch_all` and get a stack
+ // trace, so we just attach the current stack trace.
+ codeGen.call(codeGen.translator.stackTraceCurrent.reference);
+ codeGen.b.local_set(stackTraceLocal);
+
+ // We create a generic JavaScript error.
+ codeGen.call(codeGen.translator.javaScriptErrorFactory.reference);
+ codeGen.b.local_set(exceptionLocal);
+
+ generateCatchBody();
+ }
+
+ codeGen.b.end(); // end catch
+
+ nextHandlerIdx -= nCoveredHandlers;
+ }
+
+ _tryBlockNumHandlers.clear();
+ }
+}
+
+/// Represents an exception handler (`catch` or `finally`).
+///
+/// Note: for a [TryCatch] with multiple [Catch] blocks we jump to the first
+/// [Catch] block on exception, which checks the exception type and jumps to
+/// the next one if necessary.
+abstract class _ExceptionHandler {
+ /// CFG block for the `catch` or `finally` block.
+ final StateTarget target;
+
+ _ExceptionHandler(this.target);
+
+ bool get canHandleJSExceptions;
+}
+
+class Catcher extends _ExceptionHandler {
+ final List<VariableDeclaration> _exceptionVars = [];
+ final List<VariableDeclaration> _stackTraceVars = [];
+ final AsyncCodeGenerator codeGen;
+ bool _canHandleJSExceptions = false;
+
+ Catcher.fromTryCatch(this.codeGen, TryCatch node, super.target) {
+ for (Catch catch_ in node.catches) {
+ _exceptionVars.add(catch_.exception!);
+ _stackTraceVars.add(catch_.stackTrace!);
+ _canHandleJSExceptions |=
+ guardCanMatchJSException(codeGen.translator, catch_.guard);
+ }
+ }
+
+ @override
+ bool get canHandleJSExceptions => _canHandleJSExceptions;
+
+ void setException(void Function() pushException) {
+ for (final exceptionVar in _exceptionVars) {
+ codeGen.setVariable(exceptionVar, pushException);
+ }
+ }
+
+ void setStackTrace(void Function() pushStackTrace) {
+ for (final stackTraceVar in _stackTraceVars) {
+ codeGen.setVariable(stackTraceVar, pushStackTrace);
+ }
+ }
+}
+
+const int continuationFallthrough = 0;
+const int continuationReturn = 1;
+const int continuationRethrow = 2;
+
+// For larger continuation values, `value - continuationJump` gives us the
+// target block index to jump.
+const int continuationJump = 3;
+
+class Finalizer extends _ExceptionHandler {
+ final VariableDeclaration _continuationVar;
+ final VariableDeclaration _exceptionVar;
+ final VariableDeclaration _stackTraceVar;
+ final Finalizer? parentFinalizer;
+ final AsyncCodeGenerator codeGen;
+
+ Finalizer(this.codeGen, TryFinally node, this.parentFinalizer, super.target)
+ : _continuationVar =
+ (node.parent as Block).statements[0] as VariableDeclaration,
+ _exceptionVar =
+ (node.parent as Block).statements[1] as VariableDeclaration,
+ _stackTraceVar =
+ (node.parent as Block).statements[2] as VariableDeclaration;
+
+ @override
+ bool get canHandleJSExceptions => true;
+
+ void setContinuationFallthrough() {
+ codeGen.setVariable(_continuationVar, () {
+ codeGen.b.i64_const(continuationFallthrough);
+ });
+ }
+
+ void setContinuationReturn() {
+ codeGen.setVariable(_continuationVar, () {
+ codeGen.b.i64_const(continuationReturn);
+ });
+ }
+
+ void setContinuationRethrow(
+ void Function() pushException, void Function() pushStackTrace) {
+ codeGen.setVariable(_continuationVar, () {
+ codeGen.b.i64_const(continuationRethrow);
+ });
+ codeGen.setVariable(_exceptionVar, pushException);
+ codeGen.setVariable(_stackTraceVar, pushStackTrace);
+ }
+
+ void setContinuationJump(int index) {
+ codeGen.setVariable(_continuationVar, () {
+ codeGen.b.i64_const(index + continuationJump);
+ });
+ }
+
+ /// Push continuation of the finalizer block onto the stack as `i32`.
+ void pushContinuation() {
+ codeGen.visitVariableGet(VariableGet(_continuationVar), w.NumType.i64);
+ codeGen.b.i32_wrap_i64();
+ }
+
+ void pushException() {
+ codeGen.getVariable(_exceptionVar);
+ }
+
+ void pushStackTrace() {
+ codeGen.getVariable(_stackTraceVar);
+ }
+}
+
+/// Represents target of a `break` statement.
+abstract class LabelTarget {
+ void jump(AsyncCodeGenerator codeGen);
+}
+
+/// Target of a [BreakStatement] that can be implemented with a Wasm `br`
+/// instruction.
+///
+/// This [LabelTarget] is used when the [LabeledStatement] is compiled using
+/// the normal code generator (instead of async code generator).
+class DirectLabelTarget implements LabelTarget {
+ final w.Label label;
+
+ DirectLabelTarget(this.label);
+
+ @override
+ void jump(AsyncCodeGenerator codeGen) {
+ codeGen.b.br(label);
+ }
+}
+
+/// Target of a [BreakStatement] when the [LabeledStatement] is compiled to
+/// CFG.
+class IndirectLabelTarget implements LabelTarget {
+ /// Number of finalizers wrapping the [LabeledStatement].
+ final int finalizerDepth;
+
+ /// CFG state for the [LabeledStatement] continuation.
+ final StateTarget stateTarget;
+
+ IndirectLabelTarget(this.finalizerDepth, this.stateTarget);
+
+ @override
+ void jump(AsyncCodeGenerator codeGen) {
+ final currentFinalizerDepth = codeGen.exceptionHandlers.numFinalizers;
+ final finalizersToRun = currentFinalizerDepth - finalizerDepth;
+
+ // Control flow overridden by a `break`, reset finalizer continuations.
+ var i = finalizersToRun;
+ codeGen.exceptionHandlers.forEachFinalizer((finalizer, last) {
+ if (i <= 0) {
+ // Finalizer won't be run by the `break`, reset continuation.
+ finalizer.setContinuationFallthrough();
+ } else {
+ // Finalizer will be run by the `break`. Each finalizer jumps to the
+ // next, last finalizer jumps to the `break` target.
+ finalizer.setContinuationJump(i == 1
+ ? stateTarget.index
+ : finalizer.parentFinalizer!.target.index);
+ }
+ i -= 1;
+ });
+
+ if (finalizersToRun == 0) {
+ codeGen.jumpToTarget(stateTarget);
+ } else {
+ codeGen.jumpToTarget(codeGen.exceptionHandlers.nextFinalizer!.target);
+ }
+ }
+}
+
+/// Exception and stack trace variables of a [Catch] block. These variables are
+/// used to get the exception and stack trace to throw when compiling
+/// [Rethrow].
+class CatchVariables {
+ final VariableDeclaration exception;
+ final VariableDeclaration stackTrace;
+
+ CatchVariables(this.exception, this.stackTrace);
+}
diff --git a/pkg/dart2wasm/lib/sync_star.dart b/pkg/dart2wasm/lib/sync_star.dart
index 257dc31..6518942 100644
--- a/pkg/dart2wasm/lib/sync_star.dart
+++ b/pkg/dart2wasm/lib/sync_star.dart
@@ -8,155 +8,7 @@
import 'class_info.dart';
import 'closures.dart';
import 'code_generator.dart';
-
-/// Placement of a control flow graph target within a statement. This
-/// distinction is necessary since some statements need to have two targets
-/// associated with them.
-///
-/// The meanings of the variants are:
-///
-/// - [Inner]: Loop entry of a [DoStatement], condition of a [ForStatement] or
-/// [WhileStatement], the `else` branch of an [IfStatement], or the
-/// initial entry target for a function body.
-/// - [After]: After a statement, the resumption point of a [YieldStatement],
-/// or the final state (iterator done) of a function body.
-enum StateTargetPlacement { Inner, After }
-
-/// Representation of target in the `sync*` control flow graph.
-class StateTarget {
- int index;
- TreeNode node;
- StateTargetPlacement placement;
-
- StateTarget(this.index, this.node, this.placement);
-
- @override
- String toString() {
- String place = placement == StateTargetPlacement.Inner ? "in" : "after";
- return "$index: $place $node";
- }
-}
-
-/// Identify which statements contain `yield` or `yield*` statements, and assign
-/// target indices to all control flow targets of these.
-///
-/// Target indices are assigned in program order.
-class _YieldFinder extends StatementVisitor<void>
- with StatementVisitorDefaultMixin<void> {
- final SyncStarCodeGenerator codeGen;
-
- // The number of `yield` or `yield*` statements seen so far.
- int yieldCount = 0;
-
- _YieldFinder(this.codeGen);
-
- List<StateTarget> get targets => codeGen.targets;
-
- void find(FunctionNode function) {
- // Initial state
- addTarget(function.body!, StateTargetPlacement.Inner);
- assert(function.body is Block || function.body is ReturnStatement);
- recurse(function.body!);
- // Final state
- addTarget(function.body!, StateTargetPlacement.After);
- }
-
- /// Recurse into a statement and then remove any targets added by the
- /// statement if it doesn't contain any `yield` or `yield*` statements.
- void recurse(Statement statement) {
- int yieldCountIn = yieldCount;
- int targetsIn = targets.length;
- statement.accept(this);
- if (yieldCount == yieldCountIn) targets.length = targetsIn;
- }
-
- void addTarget(TreeNode node, StateTargetPlacement placement) {
- targets.add(StateTarget(targets.length, node, placement));
- }
-
- @override
- void defaultStatement(Statement node) {
- // Statements not explicitly handled in this visitor can never contain any
- // `yield` or `yield*` statements. It is assumed that this holds for all
- // [BlockExpression]s in the function.
- }
-
- @override
- void visitBlock(Block node) {
- for (Statement statement in node.statements) {
- recurse(statement);
- }
- }
-
- @override
- void visitDoStatement(DoStatement node) {
- addTarget(node, StateTargetPlacement.Inner);
- recurse(node.body);
- }
-
- @override
- void visitForStatement(ForStatement node) {
- addTarget(node, StateTargetPlacement.Inner);
- recurse(node.body);
- addTarget(node, StateTargetPlacement.After);
- }
-
- @override
- void visitIfStatement(IfStatement node) {
- recurse(node.then);
- if (node.otherwise != null) {
- addTarget(node, StateTargetPlacement.Inner);
- recurse(node.otherwise!);
- }
- addTarget(node, StateTargetPlacement.After);
- }
-
- @override
- void visitLabeledStatement(LabeledStatement node) {
- recurse(node.body);
- addTarget(node, StateTargetPlacement.After);
- }
-
- @override
- void visitSwitchStatement(SwitchStatement node) {
- for (SwitchCase c in node.cases) {
- addTarget(c, StateTargetPlacement.Inner);
- recurse(c.body);
- }
- addTarget(node, StateTargetPlacement.After);
- }
-
- @override
- void visitTryCatch(TryCatch node) {
- recurse(node.body);
- for (Catch c in node.catches) {
- addTarget(c, StateTargetPlacement.Inner);
- recurse(c.body);
- }
- addTarget(node, StateTargetPlacement.After);
- }
-
- @override
- void visitTryFinally(TryFinally node) {
- recurse(node.body);
- addTarget(node, StateTargetPlacement.Inner);
- recurse(node.finalizer);
- addTarget(node, StateTargetPlacement.After);
- }
-
- @override
- void visitWhileStatement(WhileStatement node) {
- addTarget(node, StateTargetPlacement.Inner);
- recurse(node.body);
- addTarget(node, StateTargetPlacement.After);
- }
-
- @override
- void visitYieldStatement(YieldStatement node) {
- yieldCount++;
- addTarget(node, StateTargetPlacement.After);
- }
-}
+import 'state_machine.dart';
/// A specialized code generator for generating code for `sync*` functions.
///
@@ -177,7 +29,7 @@
SyncStarCodeGenerator(super.translator, super.function, super.reference);
/// Targets of the CFG, indexed by target index.
- final List<StateTarget> targets = [];
+ late final List<StateTarget> targets;
// Targets categorized by placement and indexed by node.
final Map<TreeNode, StateTarget> innerTargets = {};
@@ -209,20 +61,20 @@
void generate() {
closures = Closures(translator, member);
setupParametersAndContexts(member.reference);
- generateBodies(member.function!);
+ _generateBodies(member.function!);
}
@override
w.BaseFunction generateLambda(Lambda lambda, Closures closures) {
this.closures = closures;
setupLambdaParametersAndContexts(lambda);
- generateBodies(lambda.functionNode);
+ _generateBodies(lambda.functionNode);
return function;
}
- void generateBodies(FunctionNode functionNode) {
+ void _generateBodies(FunctionNode functionNode) {
// Number and categorize CFG targets.
- _YieldFinder(this).find(functionNode);
+ targets = YieldFinder(translator.options.enableAsserts).find(functionNode);
for (final target in targets) {
switch (target.placement) {
case StateTargetPlacement.Inner:
diff --git a/tools/VERSION b/tools/VERSION
index 93e2e3f..cd65412 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 3
MINOR 5
PATCH 0
-PRERELEASE 175
+PRERELEASE 176
PRERELEASE_PATCH 0
diff --git a/tools/spec_parser/Dart.g b/tools/spec_parser/Dart.g
index d921e2c..c20183f 100644
--- a/tools/spec_parser/Dart.g
+++ b/tools/spec_parser/Dart.g
@@ -4,6 +4,12 @@
// CHANGES:
//
+// v0.43 Change rule structure such that the association of metadata
+// with non-terminals can be explained in a simple and consistent way.
+// The derivable terms do not change. Remove `metadata` from the kind
+// of `forLoopParts` where the iteration variable is an existing variable
+// in scope (this is not implemented, is inconsistent anyway).
+//
// v0.42 Support updated augmented `extensionDeclaration`.
//
// v0.41 Add missing `enumEntry` update for augmentations.
@@ -451,7 +457,7 @@
;
representationDeclaration
- : ('.' identifierOrNew)? '(' metadata type identifier ')'
+ : ('.' identifierOrNew)? '(' metadata typedIdentifier ')'
;
@@ -1197,7 +1203,7 @@
;
patternVariableDeclaration
- : (FINAL | VAR) outerPattern '=' expression
+ : outerPatternDeclarationPrefix '=' expression
;
outerPattern
@@ -1208,6 +1214,10 @@
| objectPattern
;
+outerPatternDeclarationPrefix
+ : (FINAL | VAR) outerPattern
+ ;
+
patternAssignment
: outerPattern '=' expression
;
@@ -1275,12 +1285,15 @@
: AWAIT? FOR '(' forLoopParts ')' statement
;
-// TODO: Include `metadata` in the pattern form?
forLoopParts
- : metadata declaredIdentifier IN expression
- | metadata identifier IN expression
+ : forInLoopPrefix IN expression
| forInitializerStatement expression? ';' expressionList?
- | metadata (FINAL | VAR) outerPattern IN expression
+ ;
+
+forInLoopPrefix
+ : metadata declaredIdentifier
+ | metadata outerPatternDeclarationPrefix
+ | identifier
;
// The localVariableDeclaration cannot be CONST, but that can
diff --git a/tools/spec_parser/dart_spec_parser/Dart.g4 b/tools/spec_parser/dart_spec_parser/Dart.g4
index 1980ee0..c7673f7 100644
--- a/tools/spec_parser/dart_spec_parser/Dart.g4
+++ b/tools/spec_parser/dart_spec_parser/Dart.g4
@@ -4,6 +4,12 @@
// CHANGES:
//
+// v0.44 Change rule structure such that the association of metadata
+// with non-terminals can be explained in a simple and consistent way.
+// The derivable terms do not change. Remove `metadata` from the kind
+// of `forLoopParts` where the iteration variable is an existing variable
+// in scope (this is not implemented, is inconsistent anyway).
+//
// v0.43 Support updated augmented `extensionDeclaration`.
//
// v0.42 Add missing `enumEntry` update for augmentations.
@@ -457,7 +463,7 @@
;
representationDeclaration
- : ('.' identifierOrNew)? '(' metadata type identifier ')'
+ : ('.' identifierOrNew)? '(' metadata typedIdentifier ')'
;
@@ -1203,7 +1209,7 @@
;
patternVariableDeclaration
- : (FINAL | VAR) outerPattern '=' expression
+ : outerPatternDeclarationPrefix '=' expression
;
outerPattern
@@ -1214,6 +1220,10 @@
| objectPattern
;
+outerPatternDeclarationPrefix
+ : (FINAL | VAR) outerPattern
+ ;
+
patternAssignment
: outerPattern '=' expression
;
@@ -1281,12 +1291,15 @@
: AWAIT? FOR '(' forLoopParts ')' statement
;
-// TODO: Include `metadata` in the pattern form?
forLoopParts
- : metadata declaredIdentifier IN expression
- | metadata identifier IN expression
+ : forInLoopPrefix IN expression
| forInitializerStatement expression? ';' expressionList?
- | metadata (FINAL | VAR) outerPattern IN expression
+ ;
+
+forInLoopPrefix
+ : metadata declaredIdentifier
+ | metadata outerPatternDeclarationPrefix
+ | identifier
;
// The localVariableDeclaration cannot be CONST, but that can