[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:
Ömer Sinan Ağacan 2023-05-22 08:32:12 +00:00 committed by Commit Queue
parent 7419e25966
commit c74387a3a2
22 changed files with 3139 additions and 482 deletions

View file

@ -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).

View file

@ -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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -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);

View file

@ -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);
}
}

View file

@ -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,

View file

@ -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) {

View file

@ -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) {

View file

@ -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;
}

View file

@ -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");
}

View file

@ -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;
}

View file

@ -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*`.

View file

@ -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

View file

@ -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"

View file

@ -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;
}
}
}
}

View file

@ -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;
}
}
}
}

View file

@ -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',
'--',

View file

@ -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;

View file

@ -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")" "$@"

View file

@ -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);

View file

@ -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);