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