96d8359e32
Move the creation of * name of a lambda to `translator.functions.getLambdaFunctionName` * type of a lambda to `translator.functions.getLambdaFunctionType` Removes dependency on `w.FunctionBuilder` in the lambda code generators - as they don't need access to the wasm function. This may allow inlining closure calls in the future. Avoid creating `w.FunctionBuilder` eagerly when analyzing closures and instead create it only when there's a call to it (or the closure object gets instantiated). Use `CallTarget` abstraction when invoking lambdas. Avoid carrying around `(lambda, enclosingMember, enclosingMemberClosures)` throughout the codebase and instead store this information on `Lambda`. => All these changes make the codebase more uniform between lambdas and normal functions and also is net code removal. Change-Id: Ib69566ea9580827be0ed52c0c179884e84599d88 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/501983 Commit-Queue: Martin Kustermann <kustermann@google.com> Reviewed-by: Srujan Gaddam <srujzs@google.com>
355 lines
11 KiB
Dart
355 lines
11 KiB
Dart
// 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,
|
|
],
|
|
),
|
|
"$functionName inner",
|
|
);
|
|
}
|
|
|
|
/// Generates code for sync* procedures.
|
|
class SyncStarProcedureCodeGenerator
|
|
extends ProcedureStateMachineEntryCodeGenerator
|
|
with SyncStarCodeGeneratorMixin {
|
|
SyncStarProcedureCodeGenerator(
|
|
super.translator,
|
|
super.signature,
|
|
super.functionName,
|
|
super.enclosingMember,
|
|
);
|
|
}
|
|
|
|
/// Generates code for sync* closures.
|
|
class SyncStarLambdaCodeGenerator extends LambdaStateMachineEntryCodeGenerator
|
|
with SyncStarCodeGeneratorMixin {
|
|
SyncStarLambdaCodeGenerator(super.translator, super.lambda);
|
|
}
|
|
|
|
/// 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 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)
|
|
.instantiateLocalDummyValue(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.getDartExceptionTag(b.moduleBuilder));
|
|
b.end(); // exceptionCheck
|
|
}
|
|
}
|
|
}
|