Skip to content

Commit e4676d5

Browse files
osa1Commit Queue
authored andcommitted
[dart2wasm] Convert simple async function bodies to sync Future.value calls
This transforms functions like async foo() => const ...; to foo() => Future.value(const ...); The transformation is done when the async function body is a `const` expression or a basic literal (string, int, double, bool). These expressions don't have side effects and they cannot throw, so it's safe to convert them to `Future.value`s. This makes the generated code in the ACX demo 2.5% smaller: (`-O4` with symbol names removed) - Before: 9,040,020 bytes - After: 8,805,471 bytes - Diff: -234,549 bytes, -2.59% With this we also remove the same special case in the backend to avoid generating a state machine for these functions, as the special case handled before the backend now and backend never sees this kind of functions. (Technically with inlining or other backend optimizations it could still see these cases, but the pattern it matches is too strict, and currently the special case in the backend doesn't do anything on the ACX demo.) Note: I tried implementing the same in dart2js's await lowering pass in https://dart-review.googlesource.com/c/sdk/+/422061 and reusing that pass in https://dart-review.googlesource.com/c/sdk/+/420140. However while that transformed simple programs as expected, in ACX demo it still introduced a lot of `Future.sync` calls and made the overall binary larger. I think we never want to introduce `Future.sync` calls (at least until we improve code size for closures, see relevant issue #60458), so for now we don't reuse dart2js's pass. Issue: #60433 Change-Id: I206ac8c6081201041f67e7fc91776077e52180a0 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/422120 Reviewed-by: Nate Biggs <[email protected]> Commit-Queue: Ömer Ağacan <[email protected]>
1 parent c9b0f56 commit e4676d5

File tree

2 files changed

+62
-62
lines changed

2 files changed

+62
-62
lines changed

pkg/dart2wasm/lib/async.dart

Lines changed: 0 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -191,21 +191,7 @@ class AsyncStateMachineCodeGenerator extends StateMachineCodeGenerator {
191191
void generateInner(FunctionNode functionNode, Context? context) {
192192
// void Function(_AsyncSuspendState, Object?, Object?, StackTrace?)
193193

194-
// Special cases for function bodies with that directly return a constant
195-
// expression or basic literal. These functions can't throw an exception,
196-
// and they will have only one state so the `br_table` loop below can also
197-
// be omitted.
198-
//
199-
// Note: these functions currently have two states instead of one, as we
200-
// always generate a state for implicitly returning `null`. With #55939 we
201-
// will be able to remove the implicit return state. After that we can check
202-
// the function bodies to check whether we need exception handling, and
203-
// special-case single-state state machines to omit the `br_table` loop.
204194
final functionBody = functionNode.body!;
205-
final simpleReturn = _getSimpleReturn(functionBody);
206-
if (simpleReturn != null && _handleSimpleReturn(simpleReturn)) {
207-
return;
208-
}
209195

210196
// Set up locals for contexts and `this`.
211197
thisLocal = null;
@@ -434,48 +420,4 @@ class AsyncStateMachineCodeGenerator extends StateMachineCodeGenerator {
434420
b, _awaitValueLocal.type, translateType(awaitValueVar.type));
435421
});
436422
}
437-
438-
bool _handleSimpleReturn(Expression returnedExpression) {
439-
if (returnedExpression is! BasicLiteral &&
440-
returnedExpression is! ConstantExpression) {
441-
return false;
442-
}
443-
444-
b.local_get(_suspendStateLocal);
445-
b.struct_get(
446-
asyncSuspendStateInfo.struct, FieldIndex.asyncSuspendStateCompleter);
447-
returnedExpression.accept1(
448-
this,
449-
asyncSuspendStateInfo.struct
450-
.fields[FieldIndex.asyncSuspendStateCurrentReturnValue].type.unpacked,
451-
);
452-
call(translator.getFunctionEntry(translator.completerComplete.reference,
453-
uncheckedEntry: true));
454-
b.return_();
455-
b.end(); // inner function
456-
return true;
457-
}
458-
}
459-
460-
Expression? _getSimpleReturn(Statement functionBody) {
461-
if (functionBody is ReturnStatement) {
462-
if (functionBody.expression == null) {
463-
return NullLiteral();
464-
}
465-
return functionBody.expression;
466-
}
467-
468-
if (functionBody is Block) {
469-
if (functionBody.statements.isEmpty) {
470-
return NullLiteral();
471-
}
472-
if (functionBody.statements.length == 1) {
473-
final statement = functionBody.statements.single;
474-
if (statement is ReturnStatement) {
475-
return statement.expression;
476-
}
477-
}
478-
}
479-
480-
return null;
481423
}

pkg/dart2wasm/lib/transformers.dart

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,34 @@ class _WasmTransformer extends Transformer {
619619
dartAsyncMarker: AsyncMarker.Sync);
620620
}
621621

622+
void _lowerAsync(FunctionNode functionNode) {
623+
/*
624+
Convert `async` functions with "simple" bodies to `sync` functions, using
625+
`Future.value`.
626+
627+
"Simple" means: constant or basic literal. In general, this transformation
628+
can be done on any function body that doesn't `await` and doesn't throw.
629+
630+
Example:
631+
632+
foo() async { return const ...; }
633+
==>
634+
foo() { return Future.value(const ...); }
635+
*/
636+
final functionBody = functionNode.body!;
637+
final simpleReturn = _getSimpleReturn(functionBody);
638+
if (simpleReturn is BasicLiteral || simpleReturn is ConstantExpression) {
639+
final futureValueType = functionNode.emittedValueType!;
640+
final newBody = ReturnStatement(StaticInvocation(
641+
coreTypes.futureValueFactory,
642+
Arguments([simpleReturn!], types: [futureValueType]),
643+
));
644+
newBody.parent = functionNode;
645+
functionNode.body = newBody;
646+
functionNode.asyncMarker = AsyncMarker.Sync;
647+
}
648+
}
649+
622650
@override
623651
TreeNode visitYieldStatement(YieldStatement yield) {
624652
// We currently ignore yields in 'sync*'.
@@ -716,17 +744,24 @@ class _WasmTransformer extends Transformer {
716744
@override
717745
TreeNode visitFunctionNode(FunctionNode functionNode) {
718746
final previousEnclosing = _enclosingIsAsyncStar;
719-
if (functionNode.dartAsyncMarker == AsyncMarker.AsyncStar) {
747+
final FunctionNode transformed;
748+
749+
if (functionNode.asyncMarker == AsyncMarker.AsyncStar) {
720750
_enclosingIsAsyncStar = true;
721751
functionNode = _lowerAsyncStar(functionNode) as FunctionNode;
722752
_enclosingIsAsyncStar = previousEnclosing;
723-
return super.visitFunctionNode(functionNode);
753+
transformed = super.visitFunctionNode(functionNode) as FunctionNode;
724754
} else {
725755
_enclosingIsAsyncStar = false;
726-
TreeNode result = super.visitFunctionNode(functionNode);
756+
transformed = super.visitFunctionNode(functionNode) as FunctionNode;
727757
_enclosingIsAsyncStar = previousEnclosing;
728-
return result;
729758
}
759+
760+
if (transformed.asyncMarker == AsyncMarker.Async) {
761+
_lowerAsync(transformed);
762+
}
763+
764+
return transformed;
730765
}
731766

732767
@override
@@ -1051,3 +1086,26 @@ class _VariableCollector extends RecursiveVisitor {
10511086
variables.add(node.variable);
10521087
}
10531088
}
1089+
1090+
Expression? _getSimpleReturn(Statement functionBody) {
1091+
if (functionBody is ReturnStatement) {
1092+
if (functionBody.expression == null) {
1093+
return NullLiteral();
1094+
}
1095+
return functionBody.expression;
1096+
}
1097+
1098+
if (functionBody is Block) {
1099+
if (functionBody.statements.isEmpty) {
1100+
return NullLiteral();
1101+
}
1102+
if (functionBody.statements.length == 1) {
1103+
final statement = functionBody.statements.single;
1104+
if (statement is ReturnStatement) {
1105+
return statement.expression;
1106+
}
1107+
}
1108+
}
1109+
1110+
return null;
1111+
}

0 commit comments

Comments
 (0)