|  | // Copyright (c) 2023, 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 'class_info.dart'; | 
|  | import 'closures.dart'; | 
|  | import 'code_generator.dart'; | 
|  | import 'state_machine.dart'; | 
|  | import 'translator.dart' show CompilationTask; | 
|  |  | 
|  | mixin SyncStarCodeGeneratorMixin on StateMachineEntryAstCodeGenerator { | 
|  | late final ClassInfo suspendStateInfo = | 
|  | translator.classInfo[translator.suspendStateClass]!; | 
|  |  | 
|  | late final ClassInfo syncStarIterableInfo = | 
|  | translator.classInfo[translator.syncStarIterableClass]!; | 
|  |  | 
|  | @override | 
|  | void generateOuter( | 
|  | FunctionNode functionNode, Context? context, Source functionSource) { | 
|  | final resumeFun = _defineInnerBodyFunction(functionNode); | 
|  |  | 
|  | // Instantiate a [_SyncStarIterable] containing the context and resume | 
|  | // function for this `sync*` function. | 
|  | DartType elementType = functionNode.emittedValueType!; | 
|  | translator.functions.recordClassAllocation(syncStarIterableInfo.classId); | 
|  | b.pushObjectHeaderFields(translator, syncStarIterableInfo); | 
|  | types.makeType(this, elementType); | 
|  | if (context != null) { | 
|  | assert(!context.isEmpty); | 
|  | b.local_get(context.currentLocal); | 
|  | } else { | 
|  | b.ref_null(w.HeapType.struct); | 
|  | } | 
|  | translator.globals.readGlobal(b, translator.makeFunctionRef(resumeFun)); | 
|  | b.struct_new(syncStarIterableInfo.struct); | 
|  | b.return_(); | 
|  | b.end(); | 
|  |  | 
|  | translator.compilationQueue.add(CompilationTask( | 
|  | resumeFun, | 
|  | SyncStarStateMachineCodeGenerator(translator, resumeFun, | 
|  | enclosingMember, functionNode, functionSource, closures))); | 
|  | } | 
|  |  | 
|  | w.FunctionBuilder _defineInnerBodyFunction(FunctionNode functionNode) => | 
|  | translator.moduleForReference(enclosingMember.reference).functions.define( | 
|  | translator.typesBuilder.defineFunction([ | 
|  | suspendStateInfo.nonNullableType, // _SuspendState | 
|  | translator.topType, // Object?, error value | 
|  | translator.stackTraceTypeNullable // StackTrace?, error stack trace | 
|  | ], const [ | 
|  | // bool for whether the generator has more to do | 
|  | w.NumType.i32 | 
|  | ]), | 
|  | "${function.functionName} inner"); | 
|  | } | 
|  |  | 
|  | /// Generates code for sync* procedures. | 
|  | class SyncStarProcedureCodeGenerator | 
|  | extends ProcedureStateMachineEntryCodeGenerator | 
|  | with SyncStarCodeGeneratorMixin { | 
|  | SyncStarProcedureCodeGenerator( | 
|  | super.translator, super.function, super.enclosingMember); | 
|  | } | 
|  |  | 
|  | /// Generates code for sync* closures. | 
|  | class SyncStarLambdaCodeGenerator extends LambdaStateMachineEntryCodeGenerator | 
|  | with SyncStarCodeGeneratorMixin { | 
|  | SyncStarLambdaCodeGenerator( | 
|  | super.translator, super.enclosingMember, super.lambda, super.closures); | 
|  | } | 
|  |  | 
|  | /// A specialized code generator for generating code for `sync*` functions. | 
|  | /// | 
|  | /// This will create an "outer" function which is a small function that just | 
|  | /// instantiates and returns a [_SyncStarIterable], plus an "inner" function | 
|  | /// containing the body of the `sync*` function. | 
|  | /// | 
|  | /// For the inner function, all statements containing any `yield` or `yield*` | 
|  | /// statements will be translated to an explicit control flow graph implemented | 
|  | /// via a switch (via the Wasm `br_table` instruction) in a loop. This enables | 
|  | /// the function to suspend its execution at yield points and jump back to the | 
|  | /// point of suspension when the execution is resumed. | 
|  | /// | 
|  | /// Local state is preserved via the closure contexts, which will implicitly | 
|  | /// capture all local variables in a `sync*` function even if they are not | 
|  | /// captured by any lambdas. | 
|  | class SyncStarStateMachineCodeGenerator extends StateMachineCodeGenerator { | 
|  | SyncStarStateMachineCodeGenerator( | 
|  | super.translator, | 
|  | super.function, | 
|  | super.enclosingMember, | 
|  | super.functionNode, | 
|  | super.functionSource, | 
|  | super.closures); | 
|  |  | 
|  | late final ClassInfo suspendStateInfo = | 
|  | translator.classInfo[translator.suspendStateClass]!; | 
|  |  | 
|  | late final ClassInfo syncStarIterableInfo = | 
|  | translator.classInfo[translator.syncStarIterableClass]!; | 
|  |  | 
|  | late final ClassInfo syncStarIteratorInfo = | 
|  | translator.classInfo[translator.syncStarIteratorClass]!; | 
|  |  | 
|  | // Note: These locals are only available in "inner" functions. | 
|  | w.Local get _suspendStateLocal => function.locals[0]; | 
|  | w.Local get _pendingExceptionLocal => function.locals[1]; | 
|  | w.Local get _pendingStackTraceLocal => function.locals[2]; | 
|  |  | 
|  | @override | 
|  | void setSuspendStateCurrentException(void Function() emitValue) { | 
|  | b.local_get(_suspendStateLocal); | 
|  | emitValue(); | 
|  | b.struct_set( | 
|  | suspendStateInfo.struct, FieldIndex.suspendStateCurrentException); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void getSuspendStateCurrentException() { | 
|  | b.local_get(_suspendStateLocal); | 
|  | b.struct_get( | 
|  | suspendStateInfo.struct, FieldIndex.suspendStateCurrentException); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void setSuspendStateCurrentStackTrace(void Function() emitValue) { | 
|  | b.local_get(_suspendStateLocal); | 
|  | emitValue(); | 
|  | b.struct_set(suspendStateInfo.struct, | 
|  | FieldIndex.suspendStateCurrentExceptionStackTrace); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void getSuspendStateCurrentStackTrace() { | 
|  | b.local_get(_suspendStateLocal); | 
|  | b.struct_get(suspendStateInfo.struct, | 
|  | FieldIndex.suspendStateCurrentExceptionStackTrace); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void setSuspendStateCurrentReturnValue(void Function() emitValue) {} | 
|  |  | 
|  | @override | 
|  | void getSuspendStateCurrentReturnValue() {} | 
|  |  | 
|  | @override | 
|  | void emitReturn(void Function() emitValue) { | 
|  | // Set state target to final state. | 
|  | b.local_get(_suspendStateLocal); | 
|  | b.i32_const(targets.last.index); | 
|  | b.struct_set(suspendStateInfo.struct, FieldIndex.suspendStateTargetIndex); | 
|  |  | 
|  | // Return `false`. | 
|  | b.i32_const(0); | 
|  | b.return_(); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void generateInner(FunctionNode functionNode, Context? context) { | 
|  | // Set up locals for contexts and `this`. | 
|  | thisLocal = null; | 
|  | Context? localContext = context; | 
|  | while (localContext != null) { | 
|  | if (!localContext.isEmpty) { | 
|  | localContext.currentLocal = b.addLocal( | 
|  | w.RefType.def(localContext.struct, nullable: true), | 
|  | name: "context"); | 
|  | if (localContext.containsThis) { | 
|  | assert(thisLocal == null); | 
|  | thisLocal = b.addLocal( | 
|  | localContext | 
|  | .struct.fields[localContext.thisFieldIndex].type.unpacked | 
|  | .withNullability(false), | 
|  | name: "this"); | 
|  | translator | 
|  | .getDummyValuesCollectorForModule(b.moduleBuilder) | 
|  | .instantiateDummyValue(b, thisLocal!.type); | 
|  | b.local_set(thisLocal!); | 
|  |  | 
|  | preciseThisLocal = thisLocal; | 
|  | } | 
|  | } | 
|  | localContext = localContext.parent; | 
|  | } | 
|  |  | 
|  | // Read target index from the suspend state. | 
|  | targetIndexLocal = addLocal(w.NumType.i32, name: "targetIndex"); | 
|  | b.local_get(_suspendStateLocal); | 
|  | b.struct_get(suspendStateInfo.struct, FieldIndex.suspendStateTargetIndex); | 
|  | b.local_set(targetIndexLocal); | 
|  |  | 
|  | // Switch on the target index. | 
|  | masterLoop = b.loop(const [], const [w.NumType.i32]); | 
|  | labels = List.generate(targets.length, (_) => b.block()).reversed.toList(); | 
|  |  | 
|  | // There should be at least two states: inner and after targets for the | 
|  | // [FunctionNode]. | 
|  | assert(labels.length >= 2); | 
|  |  | 
|  | // Use the last target label as the default `br_table` target. | 
|  | final brTableLabels = labels.sublist(0, labels.length - 1); | 
|  | final brTableDefaultLabel = labels.last; | 
|  | b.local_get(targetIndexLocal); | 
|  | b.br_table(brTableLabels, brTableDefaultLabel); | 
|  |  | 
|  | // Initial state, executed on first [moveNext] on the iterator. | 
|  | StateTarget initialTarget = targets.first; | 
|  | emitTargetLabel(initialTarget); | 
|  |  | 
|  | // Clone context on first execution. | 
|  | b.restoreSuspendStateContext(_suspendStateLocal, suspendStateInfo.struct, | 
|  | FieldIndex.suspendStateContext, closures, context, thisLocal, | 
|  | cloneContextFor: functionNode); | 
|  |  | 
|  | translateStatement(functionNode.body!); | 
|  |  | 
|  | // Final state: just keep returning. | 
|  | emitTargetLabel(targets.last); | 
|  | emitReturn(() {}); | 
|  | b.end(); // masterLoop | 
|  |  | 
|  | b.return_(); | 
|  | b.end(); // inner function | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitYieldStatement(YieldStatement node) { | 
|  | // Evaluate operand and store it to `_current` for `yield` or | 
|  | // `_yieldStarIterable` for `yield*`. | 
|  | b.local_get(_suspendStateLocal); | 
|  | b.struct_get(suspendStateInfo.struct, FieldIndex.suspendStateIterator); | 
|  | translateExpression(node.expression, translator.topType); | 
|  | if (node.isYieldStar) { | 
|  | b.ref_cast(translator.objectInfo.nonNullableType); | 
|  | b.struct_set(syncStarIteratorInfo.struct, | 
|  | FieldIndex.syncStarIteratorYieldStarIterable); | 
|  | } else { | 
|  | b.struct_set( | 
|  | syncStarIteratorInfo.struct, FieldIndex.syncStarIteratorCurrent); | 
|  | } | 
|  |  | 
|  | // Find the current context. | 
|  | Context? context; | 
|  | TreeNode contextOwner = node; | 
|  | do { | 
|  | contextOwner = contextOwner.parent!; | 
|  | context = closures.contexts[contextOwner]; | 
|  | } while ( | 
|  | contextOwner.parent != null && (context == null || context.isEmpty)); | 
|  |  | 
|  | // Store context. | 
|  | if (context != null) { | 
|  | assert(!context.isEmpty); | 
|  | b.local_get(_suspendStateLocal); | 
|  | b.local_get(context.currentLocal); | 
|  | b.struct_set(suspendStateInfo.struct, FieldIndex.suspendStateContext); | 
|  | } | 
|  |  | 
|  | // Set state target to label after yield. | 
|  | final StateTarget after = afterTargets[node]!; | 
|  | b.local_get(_suspendStateLocal); | 
|  | b.i32_const(after.index); | 
|  | b.struct_set(suspendStateInfo.struct, FieldIndex.suspendStateTargetIndex); | 
|  |  | 
|  | // Return `true`. | 
|  | b.i32_const(1); | 
|  | b.return_(); | 
|  |  | 
|  | // Resume. | 
|  | emitTargetLabel(after); | 
|  |  | 
|  | b.restoreSuspendStateContext(_suspendStateLocal, suspendStateInfo.struct, | 
|  | FieldIndex.suspendStateContext, closures, context, thisLocal); | 
|  |  | 
|  | // For `yield*`, check for pending exception. | 
|  | if (node.isYieldStar) { | 
|  | w.Label exceptionCheck = b.block(); | 
|  | b.local_get(_pendingExceptionLocal); | 
|  | b.br_on_null(exceptionCheck); | 
|  |  | 
|  | exceptionHandlers.forEachFinalizer((finalizer, last) { | 
|  | finalizer.setContinuationRethrow(() { | 
|  | b.local_get(_pendingExceptionLocal); | 
|  | b.ref_as_non_null(); | 
|  | }, () => b.local_get(_pendingStackTraceLocal)); | 
|  | }); | 
|  |  | 
|  | b.local_get(_suspendStateLocal); | 
|  | b.i32_const(targets.last.index); | 
|  | b.struct_set(suspendStateInfo.struct, FieldIndex.suspendStateTargetIndex); | 
|  |  | 
|  | b.local_get(_pendingStackTraceLocal); | 
|  | b.ref_as_non_null(); | 
|  |  | 
|  | b.throw_(translator.getExceptionTag(b.moduleBuilder)); | 
|  | b.end(); // exceptionCheck | 
|  | } | 
|  | } | 
|  | } |