mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 15:47:08 +00:00
[dart2wasm] New async implementation
This CL re-implements the async function compilation without using JSPI or any other platform features. This implementation is faster than the JSPI-based one in all benchmarks, in some cases up to 200x (benchmark results at the end). So we remove the JSPI-based implementation as there's no point in maintaining a much slower implementation and supporting two implementations at the same time (which is tricky because these implementations need different libraries, all scripts need to support two modes etc.) that also requires experimental platform features. # Main changes - A new pass `AwaitTransformer` transforms `await` expressions to top-level statements in form `var <fresh variable> = await <simple expr>`, where `<simple expr>` is an expression without `await`. After this pass all `await` expressions have the simple continuation of "assign the value of the awaited future to this variable and continue with the next statement". This simplifies `await` compilation. - A new code generator `AsyncCodeGenerator` (inherits from `CodeGenerator`) compiles `async` functions. The `_YieldFinder` class is copied from `sync*` code generator but modified to handle `async` expressions. - Mentions to the V8 flag `--experimental-wasm-stack-switching` is removed from all scripts and documents. # Future work - Control flow handling in `AsyncCodeGenerator` needs to be implemented in a similar way in `SyncStarCodeGenerator`. Doing this without duplicating a lot of code will require some refactoring. # New passing tests - co19/Language/Statements/Yield_and_Yield_Each/Yield/execution_async_A05_t01 - co19/Language/Statements/For/Asynchronous_For_in/execution_A02_t02 - language/regress/regress23996_test - language/sync_star/dcall_type_test # Benchmarks Current implementation: ``` AsyncLiveVars.LiveObj1(RunTime): 1586000.0 us. AsyncLiveVars.LiveObj2(RunTime): 2114000.0 us. AsyncLiveVars.LiveObj4(RunTime): 1972500.0 us. AsyncLiveVars.LiveObj8(RunTime): 2212000.0 us. AsyncLiveVars.LiveObj16(RunTime): 2238000.0 us. AsyncLiveVars.LiveInt1(RunTime): 2362000.0 us. AsyncLiveVars.LiveInt4(RunTime): 2470000.0 us. AsyncLiveVars.LiveObj2Int2(RunTime): 2575000.0 us. AsyncLiveVars.LiveObj4Int4(RunTime): 2820000.0 us. Calls.AwaitAsyncCall(RunTimeRaw): 35676.15658362989 ns. Calls.AwaitAsyncCallClosureTargetPolymorphic(RunTimeRaw): 38934.108527131786 ns. Calls.AwaitAsyncCallInstanceTargetPolymorphic(RunTimeRaw): 42617.02127659575 ns. Calls.AwaitFutureCall(RunTimeRaw): 2832.058906825262 ns. Calls.AwaitFutureCallClosureTargetPolymorphic(RunTimeRaw): 3665.8125915080527 ns. Calls.AwaitFutureCallInstanceTargetPolymorphic(RunTimeRaw): 4420.449537241076 ns. Calls.AwaitFutureOrCall(RunTimeRaw): 3692.7621861152143 ns. Calls.AwaitFutureOrCallClosureTargetPolymorphic(RunTimeRaw): 4625.346901017576 ns. Calls.AwaitFutureOrCallInstanceTargetPolymorphic(RunTimeRaw): 4514.6726862302485 ns. Calls.AwaitFutureOrCallInstanceTargetPolymorphicManyAwaits(RunTimeRaw): 345172.4137931034 ns. Calls.AwaitForAsyncStarStreamPolymorphic(RunTimeRaw): 697000.0 ns. Calls.AwaitForAsyncStarStreamPolymorphicManyYields(RunTimeRaw): 704666.6666666666 ns. Calls.AwaitForManualStreamPolymorphic(RunTimeRaw): 11010.989010989011 ns. Calls.SyncCall(RunTimeRaw): 0.40275240996973316 ns. Calls.SyncCallClosureTarget(RunTimeRaw): 0.3989591156672242 ns. Calls.SyncCallInstanceTargetPolymorphic(RunTimeRaw): 3.2632549336335526 ns. Calls.IterableSyncStarIterablePolymorphic(RunTimeRaw): 353.3980582524272 ns. Calls.IterableManualIterablePolymorphic(RunTimeRaw): 332.1161825726141 ns. Calls.IterableManualIterablePolymorphicManyYields(RunTimeRaw): 354.28067078552516 ns. ``` New implementation: ``` AsyncLiveVars.LiveObj1(RunTime): 11327.683615819209 us. AsyncLiveVars.LiveObj2(RunTime): 10923.91304347826 us. AsyncLiveVars.LiveObj4(RunTime): 10956.284153005465 us. AsyncLiveVars.LiveObj8(RunTime): 11286.516853932584 us. AsyncLiveVars.LiveObj16(RunTime): 11445.714285714286 us. AsyncLiveVars.LiveInt1(RunTime): 11016.483516483517 us. AsyncLiveVars.LiveInt4(RunTime): 11327.683615819209 us. AsyncLiveVars.LiveObj2Int2(RunTime): 10918.478260869566 us. AsyncLiveVars.LiveObj4Int4(RunTime): 10737.967914438503 us. Calls.AwaitAsyncCall(RunTimeRaw): 1082.2510822510822 ns. Calls.AwaitAsyncCallClosureTargetPolymorphic(RunTimeRaw): 1056.4124234100993 ns. Calls.AwaitAsyncCallInstanceTargetPolymorphic(RunTimeRaw): 1134.1726210729273 ns. Calls.AwaitFutureCall(RunTimeRaw): 865.6509695290858 ns. Calls.AwaitFutureCallClosureTargetPolymorphic(RunTimeRaw): 841.3967185527977 ns. Calls.AwaitFutureCallInstanceTargetPolymorphic(RunTimeRaw): 839.066957543212 ns. Calls.AwaitFutureOrCall(RunTimeRaw): 397.9941096871766 ns. Calls.AwaitFutureOrCallClosureTargetPolymorphic(RunTimeRaw): 406.17384240454913 ns. Calls.AwaitFutureOrCallInstanceTargetPolymorphic(RunTimeRaw): 393.7472929873607 ns. Calls.AwaitFutureOrCallInstanceTargetPolymorphicManyAwaits(RunTimeRaw): 1095.0503723171266 ns. Calls.AwaitForAsyncStarStreamPolymorphic(RunTimeRaw): 6643.426294820717 ns. Calls.AwaitForAsyncStarStreamPolymorphicManyYields(RunTimeRaw): 7178.750897343863 ns. Calls.AwaitForManualStreamPolymorphic(RunTimeRaw): 1456.23998835008 ns. Calls.SyncCall(RunTimeRaw): 0.3919935321067202 ns. Calls.SyncCallClosureTarget(RunTimeRaw): 0.3906669661780074 ns. Calls.SyncCallInstanceTargetPolymorphic(RunTimeRaw): 3.1676143112814583 ns. Calls.IterableSyncStarIterablePolymorphic(RunTimeRaw): 104.4932079414838 ns. Calls.IterableManualIterablePolymorphic(RunTimeRaw): 104.57516339869281 ns. Calls.IterableManualIterablePolymorphicManyYields(RunTimeRaw): 116.92487576731949 ns. ``` TEST=ci CoreLibraryReviewExempt: Added entry-point pragmas. Change-Id: I02fbd08141f51c00fb37b6fa0304dc25d6afdb71 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/301020 Commit-Queue: Ömer Ağacan <omersa@google.com> Reviewed-by: William Hesse <whesse@google.com> Reviewed-by: Johnni Winther <johnniwinther@google.com> Reviewed-by: Joshua Litt <joshualitt@google.com>
This commit is contained in:
parent
7419e25966
commit
c74387a3a2
|
@ -44,7 +44,7 @@ where *options* include:
|
|||
|
||||
Dart2Wasm will output a `wasm` file, containing Dart compiled to Wasm, as well as an `mjs` file containing the runtime. The result can be run with:
|
||||
|
||||
`d8 --experimental-wasm-gc --experimental-wasm-stack-switching --experimental-wasm-type-reflection pkg/dart2wasm/bin/run_wasm.js -- `*outfile*`.wasm` /abs/path/to/`*outfile*`.mjs
|
||||
`d8 --experimental-wasm-gc --experimental-wasm-type-reflection pkg/dart2wasm/bin/run_wasm.js -- `*outfile*`.wasm` /abs/path/to/`*outfile*`.mjs
|
||||
|
||||
Where `d8` is the [V8 developer shell](https://v8.dev/docs/d8).
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
//
|
||||
// Run as follows:
|
||||
//
|
||||
// $> d8 --experimental-wasm-gc --experimental-wasm-stack-switching \
|
||||
// $> d8 --experimental-wasm-gc \
|
||||
// --experimental-wasm-type-reflection run_wasm.js \
|
||||
// -- /abs/path/to/<dart_module>.mjs <dart_module>.wasm [<ffi_module>.wasm] \
|
||||
// [-- Dart commandline arguments...]
|
||||
|
|
1459
pkg/dart2wasm/lib/async.dart
Normal file
1459
pkg/dart2wasm/lib/async.dart
Normal file
File diff suppressed because it is too large
Load diff
1277
pkg/dart2wasm/lib/await_transformer.dart
Normal file
1277
pkg/dart2wasm/lib/await_transformer.dart
Normal file
File diff suppressed because it is too large
Load diff
|
@ -17,6 +17,14 @@ import 'package:wasm_builder/wasm_builder.dart' as w;
|
|||
/// [ClassInfo._addField] (for manually added fields) or by a line in
|
||||
/// [FieldIndex.validate] (for fields declared in Dart code).
|
||||
class FieldIndex {
|
||||
static const asyncSuspendStateResume = 2;
|
||||
static const asyncSuspendStateContext = 3;
|
||||
static const asyncSuspendStateTargetIndex = 4;
|
||||
static const asyncSuspendStateCompleter = 5;
|
||||
static const asyncSuspendStateCurrentException = 6;
|
||||
static const asyncSuspendStateCurrentExceptionStackTrace = 7;
|
||||
static const asyncSuspendStateCurrentReturnValue = 8;
|
||||
|
||||
static const classId = 0;
|
||||
static const boxValue = 1;
|
||||
static const identityHash = 1;
|
||||
|
@ -61,6 +69,21 @@ class FieldIndex {
|
|||
"Unexpected field index for ${cls.name}.$name");
|
||||
}
|
||||
|
||||
check(translator.asyncSuspendStateClass, "_resume",
|
||||
FieldIndex.asyncSuspendStateResume);
|
||||
check(translator.asyncSuspendStateClass, "_context",
|
||||
FieldIndex.asyncSuspendStateContext);
|
||||
check(translator.asyncSuspendStateClass, "_targetIndex",
|
||||
FieldIndex.asyncSuspendStateTargetIndex);
|
||||
check(translator.asyncSuspendStateClass, "_completer",
|
||||
FieldIndex.asyncSuspendStateCompleter);
|
||||
check(translator.asyncSuspendStateClass, "_currentException",
|
||||
FieldIndex.asyncSuspendStateCurrentException);
|
||||
check(translator.asyncSuspendStateClass, "_currentExceptionStackTrace",
|
||||
FieldIndex.asyncSuspendStateCurrentExceptionStackTrace);
|
||||
check(translator.asyncSuspendStateClass, "_currentReturnValue",
|
||||
FieldIndex.asyncSuspendStateCurrentReturnValue);
|
||||
|
||||
check(translator.boxedBoolClass, "value", FieldIndex.boxValue);
|
||||
check(translator.boxedIntClass, "value", FieldIndex.boxValue);
|
||||
check(translator.boxedDoubleClass, "value", FieldIndex.boxValue);
|
||||
|
|
|
@ -1078,9 +1078,9 @@ class CaptureFinder extends RecursiveVisitor {
|
|||
final Closures closures;
|
||||
final Member member;
|
||||
final Map<TreeNode, int> variableDepth = {};
|
||||
final List<bool> functionIsSyncStar = [false];
|
||||
final List<bool> functionIsSyncStarOrAsync = [false];
|
||||
|
||||
int get depth => functionIsSyncStar.length - 1;
|
||||
int get depth => functionIsSyncStarOrAsync.length - 1;
|
||||
|
||||
CaptureFinder(this.closures, this.member);
|
||||
|
||||
|
@ -1091,15 +1091,23 @@ class CaptureFinder extends RecursiveVisitor {
|
|||
@override
|
||||
void visitFunctionNode(FunctionNode node) {
|
||||
assert(depth == 0); // Nested function nodes are skipped by [_visitLambda].
|
||||
functionIsSyncStar[0] = node.asyncMarker == AsyncMarker.SyncStar;
|
||||
functionIsSyncStarOrAsync[0] = node.asyncMarker == AsyncMarker.SyncStar ||
|
||||
node.asyncMarker == AsyncMarker.Async;
|
||||
node.visitChildren(this);
|
||||
functionIsSyncStar[0] = false;
|
||||
functionIsSyncStarOrAsync[0] = false;
|
||||
}
|
||||
|
||||
@override
|
||||
void visitAssertStatement(AssertStatement node) {
|
||||
if (translator.options.enableAsserts) {
|
||||
node.visitChildren(this);
|
||||
super.visitAssertStatement(node);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitAssertBlock(AssertBlock node) {
|
||||
if (translator.options.enableAsserts) {
|
||||
super.visitAssertBlock(node);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1124,9 +1132,9 @@ class CaptureFinder extends RecursiveVisitor {
|
|||
void _visitVariableUse(TreeNode variable) {
|
||||
int declDepth = variableDepth[variable] ?? 0;
|
||||
assert(declDepth <= depth);
|
||||
if (declDepth < depth || functionIsSyncStar[declDepth]) {
|
||||
if (declDepth < depth || functionIsSyncStarOrAsync[declDepth]) {
|
||||
final capture = closures.captures[variable] ??= Capture(variable);
|
||||
if (functionIsSyncStar[declDepth]) capture.written = true;
|
||||
if (functionIsSyncStarOrAsync[declDepth]) capture.written = true;
|
||||
} else if (variable is VariableDeclaration &&
|
||||
variable.parent is FunctionDeclaration) {
|
||||
closures.closurizedFunctions.add(variable.parent as FunctionDeclaration);
|
||||
|
@ -1146,7 +1154,7 @@ class CaptureFinder extends RecursiveVisitor {
|
|||
}
|
||||
|
||||
void _visitThis() {
|
||||
if (depth > 0 || functionIsSyncStar[0]) {
|
||||
if (depth > 0 || functionIsSyncStarOrAsync[0]) {
|
||||
closures.isThisCaptured = true;
|
||||
}
|
||||
}
|
||||
|
@ -1200,9 +1208,10 @@ class CaptureFinder extends RecursiveVisitor {
|
|||
m.addFunction(type, "$member closure at ${node.location}");
|
||||
closures.lambdas[node] = Lambda(node, function);
|
||||
|
||||
functionIsSyncStar.add(node.asyncMarker == AsyncMarker.SyncStar);
|
||||
functionIsSyncStarOrAsync.add(node.asyncMarker == AsyncMarker.SyncStar ||
|
||||
node.asyncMarker == AsyncMarker.Async);
|
||||
node.visitChildren(this);
|
||||
functionIsSyncStar.removeLast();
|
||||
functionIsSyncStarOrAsync.removeLast();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1232,7 +1241,14 @@ class ContextCollector extends RecursiveVisitor {
|
|||
@override
|
||||
void visitAssertStatement(AssertStatement node) {
|
||||
if (enableAsserts) {
|
||||
node.visitChildren(this);
|
||||
super.visitAssertStatement(node);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitAssertBlock(AssertBlock node) {
|
||||
if (enableAsserts) {
|
||||
super.visitAssertBlock(node);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
import 'dart:collection' show LinkedHashMap;
|
||||
|
||||
import 'package:dart2wasm/async.dart';
|
||||
import 'package:dart2wasm/class_info.dart';
|
||||
import 'package:dart2wasm/closures.dart';
|
||||
import 'package:dart2wasm/dispatch_table.dart';
|
||||
|
@ -98,9 +99,17 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
|
|||
Reference reference) {
|
||||
bool isSyncStar = functionNode?.asyncMarker == AsyncMarker.SyncStar &&
|
||||
!reference.isTearOffReference;
|
||||
return isSyncStar
|
||||
? SyncStarCodeGenerator(translator, function, reference)
|
||||
: CodeGenerator(translator, function, reference);
|
||||
bool isAsync = functionNode?.asyncMarker == AsyncMarker.Async &&
|
||||
!reference.isTearOffReference;
|
||||
bool isTypeChecker = reference.isTypeCheckerReference;
|
||||
|
||||
if (!isTypeChecker && isSyncStar) {
|
||||
return SyncStarCodeGenerator(translator, function, reference);
|
||||
} else if (!isTypeChecker && isAsync) {
|
||||
return AsyncCodeGenerator(translator, function, reference);
|
||||
} else {
|
||||
return CodeGenerator(translator, function, reference);
|
||||
}
|
||||
}
|
||||
|
||||
w.Module get m => translator.m;
|
||||
|
@ -209,19 +218,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
|
|||
}
|
||||
|
||||
assert(member.function!.asyncMarker != AsyncMarker.SyncStar);
|
||||
|
||||
if (member.function!.asyncMarker == AsyncMarker.Async &&
|
||||
!reference.isAsyncInnerReference) {
|
||||
// Generate the async wrapper function, i.e. the function that gets
|
||||
// called when an async function is called. The inner function, containing
|
||||
// the body of the async function, is marked as an async inner reference
|
||||
// and is generated separately.
|
||||
Procedure procedure = member as Procedure;
|
||||
w.BaseFunction inner =
|
||||
translator.functions.getFunction(procedure.asyncInnerReference);
|
||||
int parameterOffset = _initializeThis(member);
|
||||
return generateAsyncWrapper(procedure.function, inner, parameterOffset);
|
||||
}
|
||||
assert(member.function!.asyncMarker != AsyncMarker.Async);
|
||||
|
||||
translator.membersBeingGenerated.add(member);
|
||||
generateBody(member);
|
||||
|
@ -295,94 +292,6 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
|
|||
b.end();
|
||||
}
|
||||
|
||||
/// Generate the async wrapper for an async function and its associated
|
||||
/// stub function.
|
||||
///
|
||||
/// The async wrapper is the outer function that gets called when the async
|
||||
/// function is called. It bundles up the arguments to the function into an
|
||||
/// arguments struct along with a reference to the stub function.
|
||||
///
|
||||
/// This struct is passed to the async helper, which allocates a new stack and
|
||||
/// calls the stub function on that stack.
|
||||
///
|
||||
/// The stub function unwraps the arguments from the struct and calls the
|
||||
/// inner function, containing the implementation of the async function.
|
||||
void generateAsyncWrapper(
|
||||
FunctionNode functionNode, w.BaseFunction inner, int parameterOffset) {
|
||||
w.DefinedFunction stub =
|
||||
m.addFunction(translator.functions.asyncStubFunctionType);
|
||||
w.BaseFunction asyncHelper =
|
||||
translator.functions.getFunction(translator.asyncHelper.reference);
|
||||
|
||||
w.Instructions stubBody = stub.body;
|
||||
w.Local stubArguments = stub.locals[0];
|
||||
w.Local stubStack = stub.locals[1];
|
||||
|
||||
// Set up the parameter to local mapping, for type checks and in case a
|
||||
// type parameter is used in the return type.
|
||||
int paramIndex = parameterOffset;
|
||||
for (TypeParameter typeParam in functionNode.typeParameters) {
|
||||
typeLocals[typeParam] = paramLocals[paramIndex++];
|
||||
}
|
||||
for (VariableDeclaration param in functionNode.positionalParameters) {
|
||||
locals[param] = paramLocals[paramIndex++];
|
||||
}
|
||||
for (VariableDeclaration param in functionNode.namedParameters) {
|
||||
locals[param] = paramLocals[paramIndex++];
|
||||
}
|
||||
|
||||
generateTypeChecks(functionNode.typeParameters, functionNode,
|
||||
ParameterInfo.fromLocalFunction(functionNode));
|
||||
|
||||
// Push the type argument to the async helper, specifying the type argument
|
||||
// of the returned `Future`.
|
||||
DartType returnType = functionNode.returnType;
|
||||
DartType innerType = returnType is InterfaceType &&
|
||||
returnType.classNode == translator.coreTypes.futureClass
|
||||
? returnType.typeArguments.single
|
||||
: const DynamicType();
|
||||
types.makeType(this, innerType);
|
||||
|
||||
// Create struct for stub reference and arguments
|
||||
w.StructType baseStruct = translator.functions.asyncStubBaseStruct;
|
||||
w.StructType argsStruct = m.addStructType("${function.functionName} (args)",
|
||||
fields: baseStruct.fields, superType: baseStruct);
|
||||
|
||||
// Push stub reference
|
||||
w.Global stubGlobal = translator.makeFunctionRef(stub);
|
||||
b.global_get(stubGlobal);
|
||||
|
||||
// Transfer function arguments to inner
|
||||
w.Local argsLocal =
|
||||
stub.addLocal(w.RefType.def(argsStruct, nullable: false));
|
||||
stubBody.local_get(stubArguments);
|
||||
translator.convertType(stub, stubArguments.type, argsLocal.type);
|
||||
stubBody.local_set(argsLocal);
|
||||
int arity = function.type.inputs.length;
|
||||
for (int i = 0; i < arity; i++) {
|
||||
int fieldIndex = argsStruct.fields.length;
|
||||
w.ValueType type = function.locals[i].type;
|
||||
argsStruct.fields.add(w.FieldType(type, mutable: false));
|
||||
b.local_get(function.locals[i]);
|
||||
stubBody.local_get(argsLocal);
|
||||
stubBody.struct_get(argsStruct, fieldIndex);
|
||||
}
|
||||
b.struct_new(argsStruct);
|
||||
|
||||
// Call async helper
|
||||
b.call(asyncHelper);
|
||||
translator.convertType(
|
||||
function, asyncHelper.type.outputs.single, outputs.single);
|
||||
b.end();
|
||||
|
||||
// Call inner function from stub
|
||||
stubBody.local_get(stubStack);
|
||||
stubBody.call(inner);
|
||||
translator.convertType(
|
||||
stub, inner.type.outputs.single, stub.type.outputs.single);
|
||||
stubBody.end();
|
||||
}
|
||||
|
||||
void setupParametersAndContexts(Member member) {
|
||||
ParameterInfo paramInfo = translator.paramInfoFor(reference);
|
||||
int parameterOffset = _initializeThis(member);
|
||||
|
@ -533,15 +442,11 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
|
|||
if (member is Constructor) {
|
||||
generateInitializerList(member);
|
||||
}
|
||||
// Async function type checks are generated in the wrapper functions, in
|
||||
// [generateAsyncWrapper].
|
||||
if (member.function!.asyncMarker != AsyncMarker.Async) {
|
||||
final List<TypeParameter> typeParameters = member is Constructor
|
||||
? member.enclosingClass.typeParameters
|
||||
: member.function!.typeParameters;
|
||||
generateTypeChecks(
|
||||
typeParameters, member.function!, translator.paramInfoFor(reference));
|
||||
}
|
||||
final List<TypeParameter> typeParameters = member is Constructor
|
||||
? member.enclosingClass.typeParameters
|
||||
: member.function!.typeParameters;
|
||||
generateTypeChecks(
|
||||
typeParameters, member.function!, translator.paramInfoFor(reference));
|
||||
Statement? body = member.function!.body;
|
||||
if (body != null) {
|
||||
visitStatement(body);
|
||||
|
@ -574,10 +479,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
|
|||
// Initialize closure information from enclosing member.
|
||||
this.closures = closures;
|
||||
|
||||
if (lambda.functionNode.asyncMarker == AsyncMarker.Async &&
|
||||
lambda.function == function) {
|
||||
return generateAsyncLambdaWrapper(lambda);
|
||||
}
|
||||
assert(lambda.functionNode.asyncMarker != AsyncMarker.Async);
|
||||
|
||||
setupLambdaParametersAndContexts(lambda);
|
||||
|
||||
|
@ -588,15 +490,6 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
|
|||
return function;
|
||||
}
|
||||
|
||||
w.DefinedFunction generateAsyncLambdaWrapper(Lambda lambda) {
|
||||
_initializeContextLocals(lambda.functionNode);
|
||||
w.DefinedFunction inner =
|
||||
translator.functions.addAsyncInnerFunctionFor(function);
|
||||
generateAsyncWrapper(lambda.functionNode, inner, 1);
|
||||
return CodeGenerator(translator, inner, reference)
|
||||
.generateLambda(lambda, closures);
|
||||
}
|
||||
|
||||
/// Initialize locals containing `this` in constructors and instance members.
|
||||
/// Returns the number of parameter locals taken up by the receiver parameter,
|
||||
/// i.e. the parameter offset for the first type parameter (or the first
|
||||
|
@ -937,7 +830,13 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
|
|||
}
|
||||
|
||||
@override
|
||||
void visitAssertBlock(AssertBlock node) {}
|
||||
void visitAssertBlock(AssertBlock node) {
|
||||
if (!options.enableAsserts) return;
|
||||
|
||||
for (Statement statement in node.statements) {
|
||||
visitStatement(statement);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitTryCatch(TryCatch node) {
|
||||
|
@ -1369,81 +1268,25 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
|
|||
return;
|
||||
}
|
||||
|
||||
final switchExprClass =
|
||||
translator.classForType(dartTypeOf(node.expression));
|
||||
|
||||
bool check<L extends Expression, C extends Constant>() =>
|
||||
node.cases.expand((c) => c.expressions).every((e) =>
|
||||
e is L ||
|
||||
e is NullLiteral ||
|
||||
(e is ConstantExpression &&
|
||||
(e.constant is C || e.constant is NullConstant) &&
|
||||
(translator.hierarchy.isSubtypeOf(
|
||||
translator.classForType(dartTypeOf(e)), switchExprClass))));
|
||||
|
||||
// Identify kind of switch. One of `nullableType` or `nonNullableType` will
|
||||
// be the type for Wasm local that holds the switch value.
|
||||
late final w.ValueType nullableType;
|
||||
late final w.ValueType nonNullableType;
|
||||
late final void Function() compare;
|
||||
if (node.cases.every((c) =>
|
||||
c.expressions.isEmpty && c.isDefault ||
|
||||
c.expressions.every((e) =>
|
||||
e is NullLiteral ||
|
||||
e is ConstantExpression && e.constant is NullConstant))) {
|
||||
// default-only switch
|
||||
nonNullableType = w.RefType.eq(nullable: false);
|
||||
nullableType = w.RefType.eq(nullable: true);
|
||||
compare = () => throw "Comparison in default-only switch";
|
||||
} else if (check<BoolLiteral, BoolConstant>()) {
|
||||
// bool switch
|
||||
nonNullableType = w.NumType.i32;
|
||||
nullableType =
|
||||
translator.classInfo[translator.boxedBoolClass]!.nullableType;
|
||||
compare = () => b.i32_eq();
|
||||
} else if (check<IntLiteral, IntConstant>()) {
|
||||
// int switch
|
||||
nonNullableType = w.NumType.i64;
|
||||
nullableType =
|
||||
translator.classInfo[translator.boxedIntClass]!.nullableType;
|
||||
compare = () => b.i64_eq();
|
||||
} else if (check<StringLiteral, StringConstant>()) {
|
||||
// String switch
|
||||
nonNullableType =
|
||||
translator.classInfo[translator.stringBaseClass]!.nonNullableType;
|
||||
nullableType = nonNullableType.withNullability(true);
|
||||
compare = () => call(translator.stringEquals.reference);
|
||||
} else {
|
||||
// Object switch
|
||||
nonNullableType = translator.topInfo.nonNullableType;
|
||||
nullableType = translator.topInfo.nullableType;
|
||||
compare = () => b.call(translator.functions
|
||||
.getFunction(translator.coreTypes.identicalProcedure.reference));
|
||||
}
|
||||
final switchInfo = SwitchInfo(this, node);
|
||||
|
||||
bool isNullable = dartTypeOf(node.expression).isPotentiallyNullable;
|
||||
|
||||
// When the type is nullable we use two variables: one for the nullable
|
||||
// value, one after the null check, with non-nullable type.
|
||||
w.Local switchValueNonNullableLocal = addLocal(nonNullableType);
|
||||
w.Local switchValueNonNullableLocal = addLocal(switchInfo.nonNullableType);
|
||||
w.Local? switchValueNullableLocal =
|
||||
isNullable ? addLocal(nullableType) : null;
|
||||
isNullable ? addLocal(switchInfo.nullableType) : null;
|
||||
|
||||
// Initialize switch value local
|
||||
wrap(node.expression, isNullable ? nullableType : nonNullableType);
|
||||
wrap(node.expression,
|
||||
isNullable ? switchInfo.nullableType : switchInfo.nonNullableType);
|
||||
b.local_set(
|
||||
isNullable ? switchValueNullableLocal! : switchValueNonNullableLocal);
|
||||
|
||||
// Special cases
|
||||
SwitchCase? defaultCase = node.cases
|
||||
.cast<SwitchCase?>()
|
||||
.firstWhere((c) => c!.isDefault, orElse: () => null);
|
||||
|
||||
SwitchCase? nullCase = node.cases.cast<SwitchCase?>().firstWhere(
|
||||
(c) => c!.expressions.any((e) =>
|
||||
e is NullLiteral ||
|
||||
e is ConstantExpression && e.constant is NullConstant),
|
||||
orElse: () => null);
|
||||
SwitchCase? defaultCase = switchInfo.defaultCase;
|
||||
SwitchCase? nullCase = switchInfo.nullCase;
|
||||
|
||||
// Create `loop` for backward jumps
|
||||
w.Label loopLabel = b.loop();
|
||||
|
@ -1472,7 +1315,9 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
|
|||
b.local_get(switchValueNullableLocal!);
|
||||
b.br_on_null(nullLabel);
|
||||
translator.convertType(
|
||||
function, nullableType.withNullability(false), nonNullableType);
|
||||
function,
|
||||
switchInfo.nullableType.withNullability(false),
|
||||
switchInfo.nonNullableType);
|
||||
b.local_set(switchValueNonNullableLocal);
|
||||
}
|
||||
|
||||
|
@ -1483,9 +1328,9 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
|
|||
exp is ConstantExpression && exp.constant is NullConstant) {
|
||||
// Null already checked, skip
|
||||
} else {
|
||||
wrap(exp, nonNullableType);
|
||||
wrap(exp, switchInfo.nonNullableType);
|
||||
b.local_get(switchValueNonNullableLocal);
|
||||
compare();
|
||||
switchInfo.compare();
|
||||
b.br_if(switchLabels[c]!);
|
||||
}
|
||||
}
|
||||
|
@ -1560,18 +1405,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
|
|||
@override
|
||||
w.ValueType visitAwaitExpression(
|
||||
AwaitExpression node, w.ValueType expectedType) {
|
||||
w.BaseFunction awaitHelper =
|
||||
translator.functions.getFunction(translator.awaitHelper.reference);
|
||||
|
||||
// The stack for the suspension is the last parameter to the function.
|
||||
w.Local stack = function.locals[function.type.inputs.length - 1];
|
||||
assert(stack.type == translator.functions.asyncStackType);
|
||||
|
||||
wrap(node.operand, translator.topInfo.nullableType);
|
||||
b.local_get(stack);
|
||||
b.call(awaitHelper);
|
||||
|
||||
return translator.topInfo.nullableType;
|
||||
throw 'Await expression in code generator: $node (${node.location})';
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -3322,6 +3156,14 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
|
|||
// evaluator.
|
||||
throw new UnsupportedError("CodeGenerator.visitIfCaseStatement");
|
||||
}
|
||||
|
||||
void debugRuntimePrint(String s) {
|
||||
final printFunction =
|
||||
translator.functions.getFunction(translator.printToConsole.reference);
|
||||
translator.constants.instantiateConstant(
|
||||
function, b, StringConstant(s), printFunction.type.inputs[0]);
|
||||
b.call(printFunction);
|
||||
}
|
||||
}
|
||||
|
||||
class TryBlockFinalizer {
|
||||
|
@ -3364,6 +3206,90 @@ class SwitchBackwardJumpInfo {
|
|||
: defaultLoopLabel = null;
|
||||
}
|
||||
|
||||
class SwitchInfo {
|
||||
/// Non-nullable Wasm type of the `switch` expression. Used when the
|
||||
/// expression is not nullable, and after the null check.
|
||||
late final w.ValueType nullableType;
|
||||
|
||||
/// Nullable Wasm type of the `switch` expression. Only used when the
|
||||
/// expression is nullable.
|
||||
late final w.ValueType nonNullableType;
|
||||
|
||||
/// Generates code that compares value of a `case` expression with the
|
||||
/// `switch` expression's value. Expects `case` and `switch` values to be on
|
||||
/// stack, in that order.
|
||||
late final void Function() compare;
|
||||
|
||||
/// The `default: ...` case, if exists.
|
||||
late final SwitchCase? defaultCase;
|
||||
|
||||
/// The `null: ...` case, if exists.
|
||||
late final SwitchCase? nullCase;
|
||||
|
||||
SwitchInfo(CodeGenerator codeGen, SwitchStatement node) {
|
||||
final translator = codeGen.translator;
|
||||
|
||||
final switchExprClass =
|
||||
translator.classForType(codeGen.dartTypeOf(node.expression));
|
||||
|
||||
bool check<L extends Expression, C extends Constant>() =>
|
||||
node.cases.expand((c) => c.expressions).every((e) =>
|
||||
e is L ||
|
||||
e is NullLiteral ||
|
||||
(e is ConstantExpression &&
|
||||
(e.constant is C || e.constant is NullConstant) &&
|
||||
(translator.hierarchy.isSubtypeOf(
|
||||
translator.classForType(codeGen.dartTypeOf(e)),
|
||||
switchExprClass))));
|
||||
|
||||
if (node.cases.every((c) =>
|
||||
c.expressions.isEmpty && c.isDefault ||
|
||||
c.expressions.every((e) =>
|
||||
e is NullLiteral ||
|
||||
e is ConstantExpression && e.constant is NullConstant))) {
|
||||
// default-only switch
|
||||
nonNullableType = w.RefType.eq(nullable: false);
|
||||
nullableType = w.RefType.eq(nullable: true);
|
||||
compare = () => throw "Comparison in default-only switch";
|
||||
} else if (check<BoolLiteral, BoolConstant>()) {
|
||||
// bool switch
|
||||
nonNullableType = w.NumType.i32;
|
||||
nullableType =
|
||||
translator.classInfo[translator.boxedBoolClass]!.nullableType;
|
||||
compare = () => codeGen.b.i32_eq();
|
||||
} else if (check<IntLiteral, IntConstant>()) {
|
||||
// int switch
|
||||
nonNullableType = w.NumType.i64;
|
||||
nullableType =
|
||||
translator.classInfo[translator.boxedIntClass]!.nullableType;
|
||||
compare = () => codeGen.b.i64_eq();
|
||||
} else if (check<StringLiteral, StringConstant>()) {
|
||||
// String switch
|
||||
nonNullableType =
|
||||
translator.classInfo[translator.stringBaseClass]!.nonNullableType;
|
||||
nullableType = nonNullableType.withNullability(true);
|
||||
compare = () => codeGen.call(translator.stringEquals.reference);
|
||||
} else {
|
||||
// Object switch
|
||||
nonNullableType = translator.topInfo.nonNullableType;
|
||||
nullableType = translator.topInfo.nullableType;
|
||||
compare = () => codeGen.b.call(translator.functions
|
||||
.getFunction(translator.coreTypes.identicalProcedure.reference));
|
||||
}
|
||||
|
||||
// Special cases
|
||||
defaultCase = node.cases
|
||||
.cast<SwitchCase?>()
|
||||
.firstWhere((c) => c!.isDefault, orElse: () => null);
|
||||
|
||||
nullCase = node.cases.cast<SwitchCase?>().firstWhere(
|
||||
(c) => c!.expressions.any((e) =>
|
||||
e is NullLiteral ||
|
||||
e is ConstantExpression && e.constant is NullConstant),
|
||||
orElse: () => null);
|
||||
}
|
||||
}
|
||||
|
||||
enum _VirtualCallKind {
|
||||
Get,
|
||||
Set,
|
||||
|
|
|
@ -28,17 +28,6 @@ class FunctionCollector {
|
|||
// allocation of that class is encountered
|
||||
final Map<int, List<Reference>> _pendingAllocation = {};
|
||||
|
||||
final w.ValueType asyncStackType = const w.RefType.extern(nullable: true);
|
||||
|
||||
late final w.FunctionType asyncStubFunctionType = m.addFunctionType(
|
||||
[const w.RefType.struct(nullable: false), asyncStackType],
|
||||
[translator.topInfo.nullableType]);
|
||||
|
||||
late final w.StructType asyncStubBaseStruct = m.addStructType("#AsyncStub",
|
||||
fields: [
|
||||
w.FieldType(w.RefType.def(asyncStubFunctionType, nullable: false))
|
||||
]);
|
||||
|
||||
FunctionCollector(this.translator);
|
||||
|
||||
w.Module get m => translator.m;
|
||||
|
@ -167,27 +156,11 @@ class FunctionCollector {
|
|||
"${target.asMember}");
|
||||
}
|
||||
|
||||
if (target.isAsyncInnerReference) {
|
||||
w.BaseFunction outer = getFunction(target.asProcedure.reference);
|
||||
return action(
|
||||
_asyncInnerFunctionTypeFor(outer), "${outer.functionName} inner");
|
||||
}
|
||||
|
||||
final ftype =
|
||||
target.asMember.accept1(_FunctionTypeGenerator(translator), target);
|
||||
return action(ftype, "${target.asMember}");
|
||||
}
|
||||
|
||||
w.DefinedFunction addAsyncInnerFunctionFor(w.BaseFunction outer) {
|
||||
w.FunctionType ftype = _asyncInnerFunctionTypeFor(outer);
|
||||
return m.addFunction(ftype, "${outer.functionName} inner");
|
||||
}
|
||||
|
||||
w.FunctionType _asyncInnerFunctionTypeFor(w.BaseFunction outer) {
|
||||
return m.addFunctionType([...outer.type.inputs, asyncStackType],
|
||||
[translator.topInfo.nullableType]);
|
||||
}
|
||||
|
||||
void activateSelector(SelectorInfo selector) {
|
||||
selector.targets.forEach((classId, target) {
|
||||
if (!target.asMember.isAbstract) {
|
||||
|
|
|
@ -1517,23 +1517,6 @@ class Intrinsifier {
|
|||
}
|
||||
}
|
||||
|
||||
// _asyncBridge2
|
||||
if (member.enclosingLibrary.name == "dart.async" &&
|
||||
name == "_asyncBridge2") {
|
||||
w.Local args = paramLocals[0];
|
||||
w.Local stack = paramLocals[1];
|
||||
const int stubFieldIndex = 0;
|
||||
|
||||
b.local_get(args);
|
||||
b.local_get(stack);
|
||||
b.local_get(args);
|
||||
b.ref_cast(w.RefType.def(translator.functions.asyncStubBaseStruct,
|
||||
nullable: false));
|
||||
b.struct_get(translator.functions.asyncStubBaseStruct, stubFieldIndex);
|
||||
b.call_ref(translator.functions.asyncStubFunctionType);
|
||||
return true;
|
||||
}
|
||||
|
||||
// int members
|
||||
if (member.enclosingClass == translator.boxedIntClass &&
|
||||
member.function.body == null) {
|
||||
|
|
|
@ -13,7 +13,6 @@ let buildArgsList;
|
|||
// the module will be instantiated.
|
||||
// This function returns a promise to the instantiated module.
|
||||
export const instantiate = async (modulePromise, importObjectPromise) => {
|
||||
let asyncBridge;
|
||||
let dartInstance;
|
||||
function stringFromDartString(string) {
|
||||
const totalLength = dartInstance.exports.$stringLength(string);
|
||||
|
@ -101,11 +100,6 @@ const jsRuntimeBlobPart2 = r'''
|
|||
...(await importObjectPromise),
|
||||
});
|
||||
|
||||
// Initialize async bridge.
|
||||
asyncBridge = new WebAssembly.Function(
|
||||
{parameters: ['anyref', 'anyref'], results: ['externref']},
|
||||
dartInstance.exports.$asyncBridge,
|
||||
{promising: 'first'});
|
||||
return dartInstance;
|
||||
}
|
||||
|
||||
|
|
|
@ -87,6 +87,22 @@ mixin KernelNodes {
|
|||
late final Class syncStarIteratorClass =
|
||||
index.getClass("dart:core", "_SyncStarIterator");
|
||||
|
||||
// async support classes
|
||||
late final Class asyncSuspendStateClass =
|
||||
index.getClass("dart:async", "_AsyncSuspendState");
|
||||
late final Procedure makeAsyncCompleter =
|
||||
index.getTopLevelProcedure("dart:async", "_makeAsyncCompleter");
|
||||
late final Field completerFuture =
|
||||
index.getField("dart:async", "_Completer", "future");
|
||||
late final Procedure completerComplete =
|
||||
index.getProcedure("dart:async", "_AsyncCompleter", "complete");
|
||||
late final Procedure completerCompleteError =
|
||||
index.getProcedure("dart:async", "_Completer", "completeError");
|
||||
late final Procedure awaitHelper =
|
||||
index.getTopLevelProcedure("dart:async", "_awaitHelper");
|
||||
late final Procedure newAsyncSuspendState =
|
||||
index.getTopLevelProcedure("dart:async", "_newAsyncSuspendState");
|
||||
|
||||
// dart:ffi classes
|
||||
late final Class ffiCompoundClass = index.getClass("dart:ffi", "_Compound");
|
||||
late final Class ffiPointerClass = index.getClass("dart:ffi", "Pointer");
|
||||
|
@ -133,12 +149,6 @@ mixin KernelNodes {
|
|||
late final Procedure checkLibraryIsLoaded =
|
||||
index.getTopLevelProcedure("dart:_internal", "checkLibraryIsLoaded");
|
||||
|
||||
// dart:async procedures
|
||||
late final Procedure asyncHelper =
|
||||
index.getTopLevelProcedure("dart:async", "_asyncHelper");
|
||||
late final Procedure awaitHelper =
|
||||
index.getTopLevelProcedure("dart:async", "_awaitHelper");
|
||||
|
||||
// dart:collection procedures
|
||||
late final Procedure mapFactory =
|
||||
index.getProcedure("dart:collection", "LinkedHashMap", "_default");
|
||||
|
@ -249,4 +259,8 @@ mixin KernelNodes {
|
|||
index.getProcedure("dart:_wasm", "WasmFunction", "get:call");
|
||||
late final Procedure wasmTableCallIndirect =
|
||||
index.getProcedure("dart:_wasm", "WasmTable", "callIndirect");
|
||||
|
||||
// Debugging
|
||||
late final Procedure printToConsole =
|
||||
index.getTopLevelProcedure("dart:_internal", "printToConsole");
|
||||
}
|
||||
|
|
|
@ -32,23 +32,15 @@ extension GetterSetterReference on Reference {
|
|||
// implementation for that procedure. This enables a Reference to refer to any
|
||||
// implementation relating to a member, including its tear-off, which it can't
|
||||
// do in plain kernel.
|
||||
// Also add an asyncInnerReference that refers to the inner, suspendable
|
||||
// body of an async function, which returns the value to be put into a future.
|
||||
// This can be called directly from other async functions if the result is
|
||||
// directly awaited.
|
||||
|
||||
// Use Expandos to avoid keeping the procedure alive.
|
||||
final Expando<Reference> _tearOffReference = Expando();
|
||||
final Expando<Reference> _asyncInnerReference = Expando();
|
||||
final Expando<Reference> _typeCheckerReference = Expando();
|
||||
|
||||
extension CustomReference on Member {
|
||||
Reference get tearOffReference =>
|
||||
_tearOffReference[this] ??= Reference()..node = this;
|
||||
|
||||
Reference get asyncInnerReference =>
|
||||
_asyncInnerReference[this] ??= Reference()..node = this;
|
||||
|
||||
Reference get typeCheckerReference =>
|
||||
_typeCheckerReference[this] ??= Reference()..node = this;
|
||||
}
|
||||
|
@ -56,8 +48,6 @@ extension CustomReference on Member {
|
|||
extension IsCustomReference on Reference {
|
||||
bool get isTearOffReference => _tearOffReference[asMember] == this;
|
||||
|
||||
bool get isAsyncInnerReference => _asyncInnerReference[asMember] == this;
|
||||
|
||||
bool get isTypeCheckerReference => _typeCheckerReference[asMember] == this;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,18 +21,18 @@ import 'package:wasm_builder/wasm_builder.dart' as w;
|
|||
/// 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 }
|
||||
enum StateTargetPlacement { Inner, After }
|
||||
|
||||
/// Representation of target in the `sync*` control flow graph.
|
||||
class _StateTarget {
|
||||
class StateTarget {
|
||||
int index;
|
||||
TreeNode node;
|
||||
_StateTargetPlacement placement;
|
||||
StateTargetPlacement placement;
|
||||
|
||||
_StateTarget(this.index, this.node, this.placement);
|
||||
StateTarget(this.index, this.node, this.placement);
|
||||
|
||||
String toString() {
|
||||
String place = placement == _StateTargetPlacement.Inner ? "in" : "after";
|
||||
String place = placement == StateTargetPlacement.Inner ? "in" : "after";
|
||||
return "$index: $place $node";
|
||||
}
|
||||
}
|
||||
|
@ -49,15 +49,15 @@ class _YieldFinder extends StatementVisitor<void> {
|
|||
|
||||
_YieldFinder(this.codeGen);
|
||||
|
||||
List<_StateTarget> get targets => codeGen.targets;
|
||||
List<StateTarget> get targets => codeGen.targets;
|
||||
|
||||
void find(FunctionNode function) {
|
||||
// Initial state
|
||||
addTarget(function.body!, _StateTargetPlacement.Inner);
|
||||
addTarget(function.body!, StateTargetPlacement.Inner);
|
||||
assert(function.body is Block || function.body is ReturnStatement);
|
||||
recurse(function.body!);
|
||||
// Final state
|
||||
addTarget(function.body!, _StateTargetPlacement.After);
|
||||
addTarget(function.body!, StateTargetPlacement.After);
|
||||
}
|
||||
|
||||
/// Recurse into a statement and then remove any targets added by the
|
||||
|
@ -69,8 +69,8 @@ class _YieldFinder extends StatementVisitor<void> {
|
|||
if (yieldCount == yieldCountIn) targets.length = targetsIn;
|
||||
}
|
||||
|
||||
void addTarget(TreeNode node, _StateTargetPlacement placement) {
|
||||
targets.add(_StateTarget(targets.length, node, placement));
|
||||
void addTarget(TreeNode node, StateTargetPlacement placement) {
|
||||
targets.add(StateTarget(targets.length, node, placement));
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -89,71 +89,71 @@ class _YieldFinder extends StatementVisitor<void> {
|
|||
|
||||
@override
|
||||
void visitDoStatement(DoStatement node) {
|
||||
addTarget(node, _StateTargetPlacement.Inner);
|
||||
addTarget(node, StateTargetPlacement.Inner);
|
||||
recurse(node.body);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitForStatement(ForStatement node) {
|
||||
addTarget(node, _StateTargetPlacement.Inner);
|
||||
addTarget(node, StateTargetPlacement.Inner);
|
||||
recurse(node.body);
|
||||
addTarget(node, _StateTargetPlacement.After);
|
||||
addTarget(node, StateTargetPlacement.After);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitIfStatement(IfStatement node) {
|
||||
recurse(node.then);
|
||||
if (node.otherwise != null) {
|
||||
addTarget(node, _StateTargetPlacement.Inner);
|
||||
addTarget(node, StateTargetPlacement.Inner);
|
||||
recurse(node.otherwise!);
|
||||
}
|
||||
addTarget(node, _StateTargetPlacement.After);
|
||||
addTarget(node, StateTargetPlacement.After);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitLabeledStatement(LabeledStatement node) {
|
||||
recurse(node.body);
|
||||
addTarget(node, _StateTargetPlacement.After);
|
||||
addTarget(node, StateTargetPlacement.After);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitSwitchStatement(SwitchStatement node) {
|
||||
for (SwitchCase c in node.cases) {
|
||||
addTarget(c, _StateTargetPlacement.Inner);
|
||||
addTarget(c, StateTargetPlacement.Inner);
|
||||
recurse(c.body);
|
||||
}
|
||||
addTarget(node, _StateTargetPlacement.After);
|
||||
addTarget(node, StateTargetPlacement.After);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitTryCatch(TryCatch node) {
|
||||
recurse(node.body);
|
||||
for (Catch c in node.catches) {
|
||||
addTarget(c, _StateTargetPlacement.Inner);
|
||||
addTarget(c, StateTargetPlacement.Inner);
|
||||
recurse(c.body);
|
||||
}
|
||||
addTarget(node, _StateTargetPlacement.After);
|
||||
addTarget(node, StateTargetPlacement.After);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitTryFinally(TryFinally node) {
|
||||
recurse(node.body);
|
||||
addTarget(node, _StateTargetPlacement.Inner);
|
||||
addTarget(node, StateTargetPlacement.Inner);
|
||||
recurse(node.finalizer);
|
||||
addTarget(node, _StateTargetPlacement.After);
|
||||
addTarget(node, StateTargetPlacement.After);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitWhileStatement(WhileStatement node) {
|
||||
addTarget(node, _StateTargetPlacement.Inner);
|
||||
addTarget(node, StateTargetPlacement.Inner);
|
||||
recurse(node.body);
|
||||
addTarget(node, _StateTargetPlacement.After);
|
||||
addTarget(node, StateTargetPlacement.After);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitYieldStatement(YieldStatement node) {
|
||||
yieldCount++;
|
||||
addTarget(node, _StateTargetPlacement.After);
|
||||
addTarget(node, StateTargetPlacement.After);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -176,11 +176,11 @@ class SyncStarCodeGenerator extends CodeGenerator {
|
|||
SyncStarCodeGenerator(super.translator, super.function, super.reference);
|
||||
|
||||
/// Targets of the CFG, indexed by target index.
|
||||
final List<_StateTarget> targets = [];
|
||||
final List<StateTarget> targets = [];
|
||||
|
||||
// Targets categorized by placement and indexed by node.
|
||||
final Map<TreeNode, _StateTarget> innerTargets = {};
|
||||
final Map<TreeNode, _StateTarget> afterTargets = {};
|
||||
final Map<TreeNode, StateTarget> innerTargets = {};
|
||||
final Map<TreeNode, StateTarget> afterTargets = {};
|
||||
|
||||
/// The loop around the switch.
|
||||
late final w.Label masterLoop;
|
||||
|
@ -226,10 +226,10 @@ class SyncStarCodeGenerator extends CodeGenerator {
|
|||
_YieldFinder(this).find(functionNode);
|
||||
for (final target in targets) {
|
||||
switch (target.placement) {
|
||||
case _StateTargetPlacement.Inner:
|
||||
case StateTargetPlacement.Inner:
|
||||
innerTargets[target.node] = target;
|
||||
break;
|
||||
case _StateTargetPlacement.After:
|
||||
case StateTargetPlacement.After:
|
||||
afterTargets[target.node] = target;
|
||||
break;
|
||||
}
|
||||
|
@ -374,7 +374,7 @@ class SyncStarCodeGenerator extends CodeGenerator {
|
|||
b.unreachable();
|
||||
|
||||
// Initial state, executed on first [moveNext] on the iterator.
|
||||
_StateTarget initialTarget = targets.first;
|
||||
StateTarget initialTarget = targets.first;
|
||||
emitTargetLabel(initialTarget);
|
||||
|
||||
// Clone context on first execution.
|
||||
|
@ -390,7 +390,7 @@ class SyncStarCodeGenerator extends CodeGenerator {
|
|||
b.end();
|
||||
}
|
||||
|
||||
void emitTargetLabel(_StateTarget target) {
|
||||
void emitTargetLabel(StateTarget target) {
|
||||
currentTargetIndex++;
|
||||
assert(target.index == currentTargetIndex);
|
||||
b.end();
|
||||
|
@ -407,7 +407,7 @@ class SyncStarCodeGenerator extends CodeGenerator {
|
|||
b.return_();
|
||||
}
|
||||
|
||||
void jumpToTarget(_StateTarget target,
|
||||
void jumpToTarget(StateTarget target,
|
||||
{Expression? condition, bool negated = false}) {
|
||||
if (condition == null && negated) return;
|
||||
if (target.index > currentTargetIndex) {
|
||||
|
@ -457,7 +457,7 @@ class SyncStarCodeGenerator extends CodeGenerator {
|
|||
|
||||
@override
|
||||
void visitDoStatement(DoStatement node) {
|
||||
_StateTarget? inner = innerTargets[node];
|
||||
StateTarget? inner = innerTargets[node];
|
||||
if (inner == null) return super.visitDoStatement(node);
|
||||
|
||||
emitTargetLabel(inner);
|
||||
|
@ -468,9 +468,9 @@ class SyncStarCodeGenerator extends CodeGenerator {
|
|||
|
||||
@override
|
||||
void visitForStatement(ForStatement node) {
|
||||
_StateTarget? inner = innerTargets[node];
|
||||
StateTarget? inner = innerTargets[node];
|
||||
if (inner == null) return super.visitForStatement(node);
|
||||
_StateTarget after = afterTargets[node]!;
|
||||
StateTarget after = afterTargets[node]!;
|
||||
|
||||
allocateContext(node);
|
||||
for (VariableDeclaration variable in node.variables) {
|
||||
|
@ -488,9 +488,9 @@ class SyncStarCodeGenerator extends CodeGenerator {
|
|||
|
||||
@override
|
||||
void visitIfStatement(IfStatement node) {
|
||||
_StateTarget? after = afterTargets[node];
|
||||
StateTarget? after = afterTargets[node];
|
||||
if (after == null) return super.visitIfStatement(node);
|
||||
_StateTarget? inner = innerTargets[node];
|
||||
StateTarget? inner = innerTargets[node];
|
||||
|
||||
jumpToTarget(inner ?? after, condition: node.condition, negated: true);
|
||||
visitStatement(node.then);
|
||||
|
@ -504,7 +504,7 @@ class SyncStarCodeGenerator extends CodeGenerator {
|
|||
|
||||
@override
|
||||
void visitLabeledStatement(LabeledStatement node) {
|
||||
_StateTarget? after = afterTargets[node];
|
||||
StateTarget? after = afterTargets[node];
|
||||
if (after == null) return super.visitLabeledStatement(node);
|
||||
|
||||
visitStatement(node.body);
|
||||
|
@ -513,7 +513,7 @@ class SyncStarCodeGenerator extends CodeGenerator {
|
|||
|
||||
@override
|
||||
void visitBreakStatement(BreakStatement node) {
|
||||
_StateTarget? target = afterTargets[node.target];
|
||||
StateTarget? target = afterTargets[node.target];
|
||||
if (target == null) return super.visitBreakStatement(node);
|
||||
|
||||
jumpToTarget(target);
|
||||
|
@ -521,7 +521,7 @@ class SyncStarCodeGenerator extends CodeGenerator {
|
|||
|
||||
@override
|
||||
void visitSwitchStatement(SwitchStatement node) {
|
||||
_StateTarget? after = afterTargets[node];
|
||||
StateTarget? after = afterTargets[node];
|
||||
if (after == null) return super.visitSwitchStatement(node);
|
||||
|
||||
// TODO(51342): Implement this.
|
||||
|
@ -530,7 +530,7 @@ class SyncStarCodeGenerator extends CodeGenerator {
|
|||
|
||||
@override
|
||||
void visitTryCatch(TryCatch node) {
|
||||
_StateTarget? after = afterTargets[node];
|
||||
StateTarget? after = afterTargets[node];
|
||||
if (after == null) return super.visitTryCatch(node);
|
||||
|
||||
// TODO(51343): implement this.
|
||||
|
@ -539,7 +539,7 @@ class SyncStarCodeGenerator extends CodeGenerator {
|
|||
|
||||
@override
|
||||
void visitTryFinally(TryFinally node) {
|
||||
_StateTarget? after = afterTargets[node];
|
||||
StateTarget? after = afterTargets[node];
|
||||
if (after == null) return super.visitTryFinally(node);
|
||||
|
||||
// TODO(51343): implement this.
|
||||
|
@ -548,9 +548,9 @@ class SyncStarCodeGenerator extends CodeGenerator {
|
|||
|
||||
@override
|
||||
void visitWhileStatement(WhileStatement node) {
|
||||
_StateTarget? inner = innerTargets[node];
|
||||
StateTarget? inner = innerTargets[node];
|
||||
if (inner == null) return super.visitWhileStatement(node);
|
||||
_StateTarget after = afterTargets[node]!;
|
||||
StateTarget after = afterTargets[node]!;
|
||||
|
||||
emitTargetLabel(inner);
|
||||
jumpToTarget(after, condition: node.condition, negated: true);
|
||||
|
@ -562,7 +562,7 @@ class SyncStarCodeGenerator extends CodeGenerator {
|
|||
|
||||
@override
|
||||
void visitYieldStatement(YieldStatement node) {
|
||||
_StateTarget after = afterTargets[node]!;
|
||||
StateTarget after = afterTargets[node]!;
|
||||
|
||||
// Evaluate operand and store it to `_current` for `yield` or
|
||||
// `_yieldStarIterable` for `yield*`.
|
||||
|
|
|
@ -29,9 +29,10 @@ import 'package:front_end/src/api_prototype/constant_evaluator.dart'
|
|||
import 'package:front_end/src/api_prototype/const_conditional_simplifier.dart'
|
||||
show ConstConditionalSimplifier;
|
||||
|
||||
import 'package:dart2wasm/await_transformer.dart' as awaitTrans;
|
||||
import 'package:dart2wasm/ffi_native_transformer.dart' as wasmFfiNativeTrans;
|
||||
import 'package:dart2wasm/transformers.dart' as wasmTrans;
|
||||
import 'package:dart2wasm/records.dart' show RecordShape;
|
||||
import 'package:dart2wasm/transformers.dart' as wasmTrans;
|
||||
|
||||
class WasmTarget extends Target {
|
||||
WasmTarget({this.constantBranchPruning = true});
|
||||
|
@ -222,6 +223,8 @@ class WasmTarget extends Target {
|
|||
|
||||
wasmTrans.transformLibraries(
|
||||
libraries, coreTypes, hierarchy, diagnosticReporter);
|
||||
|
||||
awaitTrans.transformLibraries(libraries, hierarchy, coreTypes);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -26,7 +26,7 @@ SDK_DIR="$(cd "${PROG_DIR}/../../.." ; pwd -P)"
|
|||
|
||||
# Hardcoded to x64 Linux for now.
|
||||
D8="$SDK_DIR/third_party/d8/linux/x64/d8"
|
||||
D8_OPTIONS="--experimental-wasm-gc --experimental-wasm-stack-switching --experimental-wasm-type-reflection"
|
||||
D8_OPTIONS="--experimental-wasm-gc --experimental-wasm-type-reflection"
|
||||
|
||||
RUN_WASM="$SDK_DIR/pkg/dart2wasm/bin/run_wasm.js"
|
||||
|
||||
|
|
|
@ -15,20 +15,38 @@ static method method(core::Iterable<core::int> iterable) → dynamic {
|
|||
}
|
||||
}
|
||||
static method asyncMethod(asy::Stream<core::int> stream) → dynamic async /* futureValueType= dynamic */ {
|
||||
core::bool :async_temporary_0;
|
||||
dynamic :async_temporary_1;
|
||||
{
|
||||
synthesized asy::_StreamIterator<core::int> #forIterator = new asy::_StreamIterator::•<core::int>(stream);
|
||||
synthesized core::bool #jumpSentinel = #C1;
|
||||
try {
|
||||
for (; #jumpSentinel = await #forIterator.{asy::_StreamIterator::moveNext}(){() → asy::Future<core::bool>}; ) {
|
||||
core::int i = #forIterator.{asy::_StreamIterator::current}{core::int};
|
||||
{
|
||||
core::print(i);
|
||||
{
|
||||
core::int #t1 = 0;
|
||||
core::Object #t2;
|
||||
core::StackTrace #t3;
|
||||
try {
|
||||
#L1:
|
||||
for (; ; ) {
|
||||
:async_temporary_0 = await #forIterator.{asy::_StreamIterator::moveNext}(){() → asy::Future<core::bool>};
|
||||
if(#jumpSentinel = :async_temporary_0 as dynamic) {
|
||||
core::int i = #forIterator.{asy::_StreamIterator::current}{core::int};
|
||||
{
|
||||
core::print(i);
|
||||
}
|
||||
}
|
||||
else
|
||||
break #L1;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if(#jumpSentinel)
|
||||
await #forIterator.{asy::_StreamIterator::cancel}(){() → asy::Future<dynamic>};
|
||||
finally {
|
||||
if(#jumpSentinel) {
|
||||
:async_temporary_1 = await #forIterator.{asy::_StreamIterator::cancel}(){() → asy::Future<dynamic>};
|
||||
:async_temporary_1;
|
||||
}
|
||||
#t1;
|
||||
#t2;
|
||||
#t3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,39 +11,65 @@ static method method(core::Iterable<core::int> iterable) → core::Iterable<core
|
|||
static method asyncMethod(asy::Stream<core::int> stream) → asy::Stream<core::int> {
|
||||
synthesized asy::StreamController<core::Object?> #controller = asy::StreamController::•<core::Object?>();
|
||||
synthesized () → asy::Future<void> #body = () → asy::Future<void> async /* futureValueType= void */ {
|
||||
core::bool :async_temporary_0;
|
||||
core::bool :async_temporary_1;
|
||||
core::bool :async_temporary_2;
|
||||
core::bool :async_temporary_3;
|
||||
core::bool :async_temporary_4;
|
||||
dynamic :async_temporary_5;
|
||||
synthesized asy::Completer<core::bool> #completer = asy::Completer::•<core::bool>();
|
||||
#controller.{asy::StreamController::add}(#completer){(core::Object?) → void};
|
||||
await #completer.{asy::Completer::future}{asy::Future<core::bool>};
|
||||
:async_temporary_0 = await #completer.{asy::Completer::future}{asy::Future<core::bool>};
|
||||
:async_temporary_0 as dynamic;
|
||||
{
|
||||
{
|
||||
#controller.{asy::StreamController::add}(1){(core::Object?) → void};
|
||||
#completer = asy::Completer::•<core::bool>();
|
||||
#controller.{asy::StreamController::add}(#completer){(core::Object?) → void};
|
||||
await #completer.{asy::Completer::future}{asy::Future<core::bool>};
|
||||
:async_temporary_1 = await #completer.{asy::Completer::future}{asy::Future<core::bool>};
|
||||
:async_temporary_1 as dynamic;
|
||||
}
|
||||
{
|
||||
#controller.{asy::StreamController::add}(2){(core::Object?) → void};
|
||||
#completer = asy::Completer::•<core::bool>();
|
||||
#controller.{asy::StreamController::add}(#completer){(core::Object?) → void};
|
||||
await #completer.{asy::Completer::future}{asy::Future<core::bool>};
|
||||
:async_temporary_2 = await #completer.{asy::Completer::future}{asy::Future<core::bool>};
|
||||
:async_temporary_2 as dynamic;
|
||||
}
|
||||
{
|
||||
synthesized asy::_StreamIterator<core::int> #forIterator = new asy::_StreamIterator::•<core::int>(stream);
|
||||
synthesized core::bool #jumpSentinel = #C1;
|
||||
try {
|
||||
for (; #jumpSentinel = await #forIterator.{asy::_StreamIterator::moveNext}(){() → asy::Future<core::bool>}; ) {
|
||||
synthesized core::int #awaitForVar = #forIterator.{asy::_StreamIterator::current}{core::int};
|
||||
{
|
||||
#controller.{asy::StreamController::add}(#awaitForVar){(core::Object?) → void};
|
||||
#completer = asy::Completer::•<core::bool>();
|
||||
#controller.{asy::StreamController::add}(#completer){(core::Object?) → void};
|
||||
await #completer.{asy::Completer::future}{asy::Future<core::bool>};
|
||||
{
|
||||
core::int #t1 = 0;
|
||||
core::Object #t2;
|
||||
core::StackTrace #t3;
|
||||
try {
|
||||
#L1:
|
||||
for (; ; ) {
|
||||
:async_temporary_4 = await #forIterator.{asy::_StreamIterator::moveNext}(){() → asy::Future<core::bool>};
|
||||
if(#jumpSentinel = :async_temporary_4 as dynamic) {
|
||||
synthesized core::int #awaitForVar = #forIterator.{asy::_StreamIterator::current}{core::int};
|
||||
{
|
||||
#controller.{asy::StreamController::add}(#awaitForVar){(core::Object?) → void};
|
||||
#completer = asy::Completer::•<core::bool>();
|
||||
#controller.{asy::StreamController::add}(#completer){(core::Object?) → void};
|
||||
:async_temporary_3 = await #completer.{asy::Completer::future}{asy::Future<core::bool>};
|
||||
:async_temporary_3 as dynamic;
|
||||
}
|
||||
}
|
||||
else
|
||||
break #L1;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if(#jumpSentinel)
|
||||
await #forIterator.{asy::_StreamIterator::cancel}(){() → asy::Future<dynamic>};
|
||||
finally {
|
||||
if(#jumpSentinel) {
|
||||
:async_temporary_5 = await #forIterator.{asy::_StreamIterator::cancel}(){() → asy::Future<dynamic>};
|
||||
:async_temporary_5;
|
||||
}
|
||||
#t1;
|
||||
#t2;
|
||||
#t3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -593,7 +593,6 @@ class Dart2WasmCompilerConfiguration extends CompilerConfiguration {
|
|||
final args = testFile.dartOptions;
|
||||
return [
|
||||
'--experimental-wasm-gc',
|
||||
'--experimental-wasm-stack-switching',
|
||||
'--experimental-wasm-type-reflection',
|
||||
'pkg/dart2wasm/bin/run_wasm.js',
|
||||
'--',
|
||||
|
|
|
@ -259,6 +259,10 @@ class _FallthroughDetector extends ast.StatementVisitor<bool> {
|
|||
bool visitBlock(Block node) =>
|
||||
node.statements.isEmpty || node.statements.last.accept(this);
|
||||
|
||||
@override
|
||||
bool visitAssertBlock(AssertBlock node) =>
|
||||
node.statements.isEmpty || node.statements.last.accept(this);
|
||||
|
||||
@override
|
||||
bool visitEmptyStatement(EmptyStatement node) => true;
|
||||
|
||||
|
|
|
@ -36,6 +36,6 @@ if [[ $D8_OPTIONS ]]; then
|
|||
fi
|
||||
|
||||
# Find the JS runtime based on the input wasm file.
|
||||
exec "$D8_EXEC" --experimental-wasm-gc --experimental-wasm-stack-switching \
|
||||
exec "$D8_EXEC" --experimental-wasm-gc \
|
||||
--experimental-wasm-type-reflection "${EXTRA_D8_OPTIONS[@]}" \
|
||||
"$SDK_DIR/pkg/dart2wasm/bin/run_wasm.js" -- "$(realpath -- "${1%.*}.mjs")" "$@"
|
||||
|
|
|
@ -1,137 +1,86 @@
|
|||
// Copyright (c) 2022, 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.
|
||||
|
||||
// Machinery for async and await.
|
||||
//
|
||||
// The implementation is based on two mechanisms in the JS Promise integration:
|
||||
//
|
||||
// The export wrapper: Allocates a new stack and calls the wrapped export on the
|
||||
// new stack, passing a suspender object as an extra first argument that
|
||||
// represents the new stack.
|
||||
//
|
||||
// The import wrapper: Takes a suspender object as an extra first argument and
|
||||
// calls the wrapped import. If the wrapped import returns a `Promise`, the
|
||||
// current stack is suspended, and the `Promise` is forwarded to the
|
||||
// corresponding call of the export wrapper, where execution resumes on the
|
||||
// original stack. When the `Promise` is resolved, execution resumes on the
|
||||
// suspended stack, with the call to the import wrapper returning the value the
|
||||
// `Promise` was resolved with.
|
||||
//
|
||||
// The call sequence when calling an async function is:
|
||||
//
|
||||
// Caller
|
||||
// -> Outer (function specific, generated by `generateAsyncWrapper`)
|
||||
// -> `_asyncHelper`
|
||||
// -> `_callAsyncBridge` (imported JS function)
|
||||
// -> `_asyncBridge` (via the Promise integration export wrapper)
|
||||
// -> `_asyncBridge2` (intrinsic function)
|
||||
// -> Stub (function specific, generated by `generateAsyncWrapper`)
|
||||
// -> Inner (contains implementation, generated from async inner reference)
|
||||
//
|
||||
// The call sequence on await is:
|
||||
//
|
||||
// Function containing await
|
||||
// -> `_awaitHelper`
|
||||
// -> `_futurePromise` (via the Promise integration import wrapper)
|
||||
// -> `new Promise`
|
||||
// -> `Promise` constructor callback
|
||||
// -> `_awaitCallback`
|
||||
// -> `Future.then`
|
||||
// `futurePromise` returns the newly created `Promise`, suspending the
|
||||
// current execution.
|
||||
//
|
||||
// When the `Future` completes:
|
||||
//
|
||||
// `Future.then` callback
|
||||
// -> `_callResolve` (imported JS function)
|
||||
// -> `Promise` resolve function
|
||||
// Resolving the `Promise` causes the suspended execution to resume.
|
||||
|
||||
import 'dart:_internal' show patch, scheduleCallback, unsafeCastOpaque;
|
||||
import 'dart:_js_helper' show JS;
|
||||
import 'dart:_internal' show scheduleCallback, patch, _AsyncCompleter;
|
||||
|
||||
import 'dart:_wasm';
|
||||
|
||||
part 'timer_patch.dart';
|
||||
|
||||
@pragma("wasm:entry-point")
|
||||
Future<T> _asyncHelper<T>(WasmStructRef args) {
|
||||
Completer<T> completer = Completer();
|
||||
_callAsyncBridge(args, completer);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
void _callAsyncBridge(WasmStructRef args, Completer<Object?> completer) =>
|
||||
// This trampoline is needed because [asyncBridge] is a function wrapped
|
||||
// by `returnPromiseOnSuspend`, and the stack-switching functionality of
|
||||
// that wrapper is implemented as part of the export adapter.
|
||||
JS<void>(
|
||||
"(args, completer) => asyncBridge(args, completer)", args, completer);
|
||||
|
||||
@pragma("wasm:export", "\$asyncBridge")
|
||||
WasmExternRef? _asyncBridge(
|
||||
WasmExternRef? stack, WasmStructRef args, Completer<Object?> completer) {
|
||||
try {
|
||||
Object? result = _asyncBridge2(args, stack);
|
||||
completer.complete(result);
|
||||
} catch (e, s) {
|
||||
completer.completeError(e, s);
|
||||
}
|
||||
}
|
||||
|
||||
external Object? _asyncBridge2(WasmStructRef args, WasmExternRef? stack);
|
||||
|
||||
class _FutureError {
|
||||
final Object exception;
|
||||
final StackTrace stackTrace;
|
||||
|
||||
_FutureError(this.exception, this.stackTrace);
|
||||
}
|
||||
typedef _AsyncResumeFun = WasmFunction<
|
||||
void Function(
|
||||
_AsyncSuspendState,
|
||||
// Value of the last `await`
|
||||
Object?,
|
||||
// If the last `await` throwed an error, the error value
|
||||
Object?,
|
||||
// If the last `await` throwed an error, the stack trace
|
||||
StackTrace?)>;
|
||||
|
||||
@pragma("wasm:entry-point")
|
||||
Object? _awaitHelper(Object? operand, WasmExternRef? stack) {
|
||||
// Save the existing zone in a local, and restore('_leave') upon returning. We
|
||||
// ensure that the zone will be restored in the event of an exception by
|
||||
// restoring the original zone before we throw the exception.
|
||||
_Zone current = Zone._current;
|
||||
class _AsyncSuspendState {
|
||||
// The inner function.
|
||||
//
|
||||
// Note: this function never throws. Any uncaught exceptions are passed to
|
||||
// `_completer.completeError`.
|
||||
@pragma("wasm:entry-point")
|
||||
final _AsyncResumeFun _resume;
|
||||
|
||||
// Context containing the local variables of the function.
|
||||
@pragma("wasm:entry-point")
|
||||
final WasmStructRef? _context;
|
||||
|
||||
// CFG target index for the next resumption.
|
||||
@pragma("wasm:entry-point")
|
||||
WasmI32 _targetIndex;
|
||||
|
||||
// The completer. The inner function calls `_completer.complete` or
|
||||
// `_completer.onError` on completion.
|
||||
@pragma("wasm:entry-point")
|
||||
final _AsyncCompleter _completer;
|
||||
|
||||
// When a called function throws this stores the thrown exception. Used when
|
||||
// performing type tests in catch blocks.
|
||||
@pragma("wasm:entry-point")
|
||||
Object? _currentException;
|
||||
|
||||
// When a called function throws this stores the stack trace.
|
||||
@pragma("wasm:entry-point")
|
||||
StackTrace? _currentExceptionStackTrace;
|
||||
|
||||
// When running finalizers and the continuation is "return", the value to
|
||||
// return after the last finalizer.
|
||||
//
|
||||
// Used in finalizer blocks.
|
||||
@pragma("wasm:entry-point")
|
||||
Object? _currentReturnValue;
|
||||
|
||||
@pragma("wasm:entry-point")
|
||||
_AsyncSuspendState(this._resume, this._context, this._completer)
|
||||
: _targetIndex = WasmI32.fromInt(0),
|
||||
_currentException = null,
|
||||
_currentExceptionStackTrace = null,
|
||||
_currentReturnValue = null;
|
||||
}
|
||||
|
||||
// Note: [_AsyncCompleter] is taken as an argument to be able to pass the type
|
||||
// parameter to [_AsyncCompleter] without having to add a type parameter to
|
||||
// [_AsyncSuspendState].
|
||||
//
|
||||
// TODO (omersa): I'm not sure if the type parameter is necessary?
|
||||
@pragma("wasm:entry-point")
|
||||
_AsyncSuspendState _newAsyncSuspendState(_AsyncResumeFun resume,
|
||||
WasmStructRef? context, _AsyncCompleter completer) =>
|
||||
_AsyncSuspendState(resume, context, completer);
|
||||
|
||||
@pragma("wasm:entry-point")
|
||||
_AsyncCompleter<T> _makeAsyncCompleter<T>() => _AsyncCompleter<T>();
|
||||
|
||||
@pragma("wasm:entry-point")
|
||||
void _awaitHelper(_AsyncSuspendState suspendState, Object? operand) {
|
||||
if (operand is! Future) {
|
||||
operand = Future.value(operand);
|
||||
}
|
||||
Object? result = _futurePromise(stack, operand);
|
||||
Zone._leave(current);
|
||||
if (result is _FutureError) {
|
||||
// TODO(joshualitt): `result.stackTrace` is not currently the complete stack
|
||||
// trace. We might be able to stitch together `result.stackTrace` with
|
||||
// `StackTrace.current`, but we would need to be able to handle the case
|
||||
// where `result.stackTrace` is supplied by the user and must then be exact.
|
||||
// Alternatively, we may be able to fix this when we actually generate stack
|
||||
// traces.
|
||||
Error.throwWithStackTrace(result.exception, result.stackTrace);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Object? _futurePromise(WasmExternRef? stack, Future<Object?> future) =>
|
||||
JS<Object?>("""new WebAssembly.Function(
|
||||
{parameters: ['externref', 'anyref'], results: ['anyref']},
|
||||
function(future) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
dartInstance.exports.\$awaitCallback(future, resolve);
|
||||
});
|
||||
},
|
||||
{suspending: 'first'})""", stack, future);
|
||||
|
||||
@pragma("wasm:export", "\$awaitCallback")
|
||||
void _awaitCallback(Future<Object?> future, WasmExternRef? resolve) {
|
||||
future.then((value) {
|
||||
_callResolve(resolve, value);
|
||||
operand.then((value) {
|
||||
suspendState._resume.call(suspendState, value, null, null);
|
||||
}, onError: (exception, stackTrace) {
|
||||
_callResolve(resolve, _FutureError(exception, stackTrace));
|
||||
suspendState._resume.call(suspendState, null, exception, stackTrace);
|
||||
});
|
||||
}
|
||||
|
||||
void _callResolve(WasmExternRef? resolve, Object? result) =>
|
||||
// This trampoline is needed because [resolve] is a JS function that
|
||||
// can't be called directly from Wasm.
|
||||
JS<void>("(resolve, result) => resolve(result)", resolve, result);
|
||||
|
|
|
@ -5,11 +5,13 @@
|
|||
part of dart.async;
|
||||
|
||||
abstract class _Completer<T> implements Completer<T> {
|
||||
@pragma("wasm:entry-point")
|
||||
final _Future<T> future = new _Future<T>();
|
||||
|
||||
// Overridden by either a synchronous or asynchronous implementation.
|
||||
void complete([FutureOr<T>? value]);
|
||||
|
||||
@pragma("wasm:entry-point")
|
||||
void completeError(Object error, [StackTrace? stackTrace]) {
|
||||
// TODO(40614): Remove once non-nullability is sound.
|
||||
checkNotNullable(error, "error");
|
||||
|
@ -34,6 +36,7 @@ abstract class _Completer<T> implements Completer<T> {
|
|||
|
||||
/// Completer which completes future asynchronously.
|
||||
class _AsyncCompleter<T> extends _Completer<T> {
|
||||
@pragma("wasm:entry-point")
|
||||
void complete([FutureOr<T>? value]) {
|
||||
if (!future._mayComplete) throw new StateError("Future already completed");
|
||||
future._asyncComplete(value == null ? value as dynamic : value);
|
||||
|
|
Loading…
Reference in a new issue