Skip to content

Commit 68fd76f

Browse files
committed
[dart2wasm] Special case async functions with simple bodies
When an async function directly returns a `const` value, a basic literal, or implicit `null` (empty body), omit the state machine loop and exception handling. See the new test for the cases this simplifies. Issue: #60433 Change-Id: I25c25cb42c02cc3a9155e01ce205f63b567a1008 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/419840 Reviewed-by: Martin Kustermann <[email protected]>
1 parent 82282b6 commit 68fd76f

File tree

2 files changed

+105
-1
lines changed

2 files changed

+105
-1
lines changed

pkg/dart2wasm/lib/async.dart

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,22 @@ 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.
204+
final functionBody = functionNode.body!;
205+
final simpleReturn = _getSimpleReturn(functionBody);
206+
if (simpleReturn != null && _handleSimpleReturn(simpleReturn)) {
207+
return;
208+
}
209+
194210
// Set up locals for contexts and `this`.
195211
thisLocal = null;
196212
Context? localContext = context;
@@ -255,7 +271,7 @@ class AsyncStateMachineCodeGenerator extends StateMachineCodeGenerator {
255271
thisLocal,
256272
cloneContextFor: functionNode);
257273

258-
translateStatement(functionNode.body!);
274+
translateStatement(functionBody);
259275

260276
// Final state: return.
261277
emitTargetLabel(targets.last);
@@ -414,4 +430,48 @@ class AsyncStateMachineCodeGenerator extends StateMachineCodeGenerator {
414430
b, _awaitValueLocal.type, translateType(awaitValueVar.type));
415431
});
416432
}
433+
434+
bool _handleSimpleReturn(Expression returnedExpression) {
435+
if (returnedExpression is! BasicLiteral &&
436+
returnedExpression is! ConstantExpression) {
437+
return false;
438+
}
439+
440+
b.local_get(_suspendStateLocal);
441+
b.struct_get(
442+
asyncSuspendStateInfo.struct, FieldIndex.asyncSuspendStateCompleter);
443+
returnedExpression.accept1(
444+
this,
445+
asyncSuspendStateInfo.struct
446+
.fields[FieldIndex.asyncSuspendStateCurrentReturnValue].type.unpacked,
447+
);
448+
call(translator.getFunctionEntry(translator.completerComplete.reference,
449+
uncheckedEntry: true));
450+
b.return_();
451+
b.end(); // inner function
452+
return true;
453+
}
454+
}
455+
456+
Expression? _getSimpleReturn(Statement functionBody) {
457+
if (functionBody is ReturnStatement) {
458+
if (functionBody.expression == null) {
459+
return NullLiteral();
460+
}
461+
return functionBody.expression;
462+
}
463+
464+
if (functionBody is Block) {
465+
if (functionBody.statements.isEmpty) {
466+
return NullLiteral();
467+
}
468+
if (functionBody.statements.length == 1) {
469+
final statement = functionBody.statements.single;
470+
if (statement is ReturnStatement) {
471+
return statement.expression;
472+
}
473+
}
474+
}
475+
476+
return null;
417477
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// dart2wasm special-cases async functions with some simple bodies. Check that
6+
// calling these functions still creates a microtask, and awaiting them yields
7+
// to the scheduler.
8+
9+
import 'dart:async';
10+
11+
import 'package:expect/expect.dart';
12+
13+
f1() async => const ["a", "b", "c"];
14+
15+
f2() async {
16+
return const ["x", "y", "z"];
17+
}
18+
19+
f3() async {}
20+
21+
f4() async => "in f4";
22+
23+
f5() async {
24+
return "in f5";
25+
}
26+
27+
checkScheduling(f, checkExpectedValue) async {
28+
final List<int> log = [];
29+
scheduleMicrotask(() {
30+
log.add(1);
31+
});
32+
log.add(0);
33+
checkExpectedValue(await f());
34+
log.add(2);
35+
Expect.listEquals([0, 1, 2], log);
36+
}
37+
38+
void main() async {
39+
checkScheduling(f1, (value) => Expect.listEquals(["a", "b", "c"], value));
40+
checkScheduling(f2, (value) => Expect.listEquals(["x", "y", "z"], value));
41+
checkScheduling(f3, (value) => Expect.equals(null, value));
42+
checkScheduling(f4, (value) => Expect.equals("in f4", value));
43+
checkScheduling(f5, (value) => Expect.equals("in f5", value));
44+
}

0 commit comments

Comments
 (0)