[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: https://github.com/dart-lang/sdk/issues/60433 Change-Id: I25c25cb42c02cc3a9155e01ce205f63b567a1008 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/419840 Reviewed-by: Martin Kustermann <kustermann@google.com>
diff --git a/pkg/dart2wasm/lib/async.dart b/pkg/dart2wasm/lib/async.dart index 7cc64d6..a414483 100644 --- a/pkg/dart2wasm/lib/async.dart +++ b/pkg/dart2wasm/lib/async.dart
@@ -191,6 +191,22 @@ void generateInner(FunctionNode functionNode, Context? context) { // void Function(_AsyncSuspendState, Object?, Object?, StackTrace?) + // Special cases for function bodies with that directly return a constant + // expression or basic literal. These functions can't throw an exception, + // and they will have only one state so the `br_table` loop below can also + // be omitted. + // + // Note: these functions currently have two states instead of one, as we + // always generate a state for implicitly returning `null`. With #55939 we + // will be able to remove the implicit return state. After that we can check + // the function bodies to check whether we need exception handling, and + // special-case single-state state machines to omit the `br_table` loop. + final functionBody = functionNode.body!; + final simpleReturn = _getSimpleReturn(functionBody); + if (simpleReturn != null && _handleSimpleReturn(simpleReturn)) { + return; + } + // Set up locals for contexts and `this`. thisLocal = null; Context? localContext = context; @@ -255,7 +271,7 @@ thisLocal, cloneContextFor: functionNode); - translateStatement(functionNode.body!); + translateStatement(functionBody); // Final state: return. emitTargetLabel(targets.last); @@ -414,4 +430,48 @@ b, _awaitValueLocal.type, translateType(awaitValueVar.type)); }); } + + bool _handleSimpleReturn(Expression returnedExpression) { + if (returnedExpression is! BasicLiteral && + returnedExpression is! ConstantExpression) { + return false; + } + + b.local_get(_suspendStateLocal); + b.struct_get( + asyncSuspendStateInfo.struct, FieldIndex.asyncSuspendStateCompleter); + returnedExpression.accept1( + this, + asyncSuspendStateInfo.struct + .fields[FieldIndex.asyncSuspendStateCurrentReturnValue].type.unpacked, + ); + call(translator.getFunctionEntry(translator.completerComplete.reference, + uncheckedEntry: true)); + b.return_(); + b.end(); // inner function + return true; + } +} + +Expression? _getSimpleReturn(Statement functionBody) { + if (functionBody is ReturnStatement) { + if (functionBody.expression == null) { + return NullLiteral(); + } + return functionBody.expression; + } + + if (functionBody is Block) { + if (functionBody.statements.isEmpty) { + return NullLiteral(); + } + if (functionBody.statements.length == 1) { + final statement = functionBody.statements.single; + if (statement is ReturnStatement) { + return statement.expression; + } + } + } + + return null; }
diff --git a/tests/language/async/dart2wasm_const_optimization_test.dart b/tests/language/async/dart2wasm_const_optimization_test.dart new file mode 100644 index 0000000..5711815 --- /dev/null +++ b/tests/language/async/dart2wasm_const_optimization_test.dart
@@ -0,0 +1,44 @@ +// Copyright (c) 2025, 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. + +// dart2wasm special-cases async functions with some simple bodies. Check that +// calling these functions still creates a microtask, and awaiting them yields +// to the scheduler. + +import 'dart:async'; + +import 'package:expect/expect.dart'; + +f1() async => const ["a", "b", "c"]; + +f2() async { + return const ["x", "y", "z"]; +} + +f3() async {} + +f4() async => "in f4"; + +f5() async { + return "in f5"; +} + +checkScheduling(f, checkExpectedValue) async { + final List<int> log = []; + scheduleMicrotask(() { + log.add(1); + }); + log.add(0); + checkExpectedValue(await f()); + log.add(2); + Expect.listEquals([0, 1, 2], log); +} + +void main() async { + checkScheduling(f1, (value) => Expect.listEquals(["a", "b", "c"], value)); + checkScheduling(f2, (value) => Expect.listEquals(["x", "y", "z"], value)); + checkScheduling(f3, (value) => Expect.equals(null, value)); + checkScheduling(f4, (value) => Expect.equals("in f4", value)); + checkScheduling(f5, (value) => Expect.equals("in f5", value)); +}