commit 937372ef80f62051b192a00b5dfd22bc837420d0
parent 93b53376966526c2ce560d98052ce3ab292e9093
Author: Tooru Fujisawa <arai_a@mac.com>
Date: Mon, 10 Nov 2025 11:00:21 +0000
Bug 1998179 - Part 2: Use loop instead of recursion for the continuation after yield in async generator. r=mgaudet
Differential Revision: https://phabricator.services.mozilla.com/D271352
Diffstat:
2 files changed, 53 insertions(+), 16 deletions(-)
diff --git a/js/src/jit-test/tests/async/async-gen-yield-loop.js b/js/src/jit-test/tests/async/async-gen-yield-loop.js
@@ -0,0 +1,11 @@
+// Continuation after yield shouldn't consume extra stack space.
+async function* asyncGen() {
+ while (true) {
+ yield;
+ }
+}
+
+const iter = asyncGen();
+for (let i = 0; i < 3000; i++) {
+ iter.next();
+}
diff --git a/js/src/vm/AsyncIteration.cpp b/js/src/vm/AsyncIteration.cpp
@@ -338,16 +338,10 @@ AsyncGeneratorRequest* AsyncGeneratorRequest::create(
// AsyncGeneratorUnwrapYieldResumption ( resumptionValue )
// https://tc39.es/ecma262/#sec-asyncgeneratorunwrapyieldresumption
//
-// Steps 1-2.
-[[nodiscard]] static bool AsyncGeneratorUnwrapYieldResumption(
+// Step 2.
+[[nodiscard]] static bool AsyncGeneratorUnwrapYieldResumptionWithReturn(
JSContext* cx, Handle<AsyncGeneratorObject*> generator,
- CompletionKind completionKind, JS::Handle<JS::Value> value) {
- // Step 1. If resumptionValue is not a return completion, return ?
- // resumptionValue.
- if (completionKind != CompletionKind::Return) {
- return AsyncGeneratorResume(cx, generator, completionKind, value);
- }
-
+ JS::Handle<JS::Value> value) {
// Step 2. Let awaited be Completion(Await(resumptionValue.[[Value]])).
//
// NOTE: Given that Await needs to be performed asynchronously,
@@ -367,8 +361,17 @@ AsyncGeneratorRequest* AsyncGeneratorRequest::create(
// https://tc39.es/ecma262/#sec-asyncgeneratoryield
//
// Stesp 9-12.
+//
+// AsyncGeneratorUnwrapYieldResumption ( resumptionValue )
+// https://tc39.es/ecma262/#sec-asyncgeneratorunwrapyieldresumption
+//
+// Step 1.
[[nodiscard]] static bool AsyncGeneratorYield(
- JSContext* cx, Handle<AsyncGeneratorObject*> generator, HandleValue value) {
+ JSContext* cx, Handle<AsyncGeneratorObject*> generator, HandleValue value,
+ bool* resumeAgain, CompletionKind* resumeCompletionKind,
+ JS::MutableHandle<JS::Value> resumeValue) {
+ *resumeAgain = false;
+
// Step 9. Perform
// ! AsyncGeneratorCompleteStep(generator, completion, false,
// previousRealm).
@@ -401,8 +404,20 @@ AsyncGeneratorRequest* AsyncGeneratorRequest::create(
// Step 11.d. Return ?
// AsyncGeneratorUnwrapYieldResumption(resumptionValue).
- return AsyncGeneratorUnwrapYieldResumption(cx, generator, completionKind,
- completionValue);
+ //
+ // AsyncGeneratorUnwrapYieldResumption
+ // Step 1. If resumptionValue is not a return completion, return ?
+ // resumptionValue.
+ if (completionKind != CompletionKind::Return) {
+ *resumeAgain = true;
+ *resumeCompletionKind = completionKind;
+ resumeValue.set(completionValue);
+ return true;
+ }
+
+ // Step 2.
+ return AsyncGeneratorUnwrapYieldResumptionWithReturn(cx, generator,
+ completionValue);
}
// Step 12. Else,
@@ -1074,8 +1089,8 @@ bool js::AsyncGeneratorReturn(JSContext* cx, unsigned argc, Value* vp) {
// Step 12.f. Return ?
// AsyncGeneratorUnwrapYieldResumption(resumptionValue).
//
- if (!AsyncGeneratorUnwrapYieldResumption(
- cx, generator, CompletionKind::Return, completionValue)) {
+ if (!AsyncGeneratorUnwrapYieldResumptionWithReturn(cx, generator,
+ completionValue)) {
// The failure path here is for the Await inside
// AsyncGeneratorUnwrapYieldResumption, where a corrupted Promise is
// passed and called there.
@@ -1212,6 +1227,8 @@ bool js::AsyncGeneratorThrow(JSContext* cx, unsigned argc, Value* vp) {
[[nodiscard]] static bool AsyncGeneratorResume(
JSContext* cx, Handle<AsyncGeneratorObject*> generator,
CompletionKind completionKind, HandleValue argument) {
+ // Given that yield can resume again, we implement it as a loop.
+ JS::Rooted<JS::Value> resumeArgument(cx, argument);
while (true) {
MOZ_ASSERT(!generator->isClosed(),
"closed generator when resuming async generator");
@@ -1248,7 +1265,7 @@ bool js::AsyncGeneratorThrow(JSContext* cx, unsigned argc, Value* vp) {
? cx->names().AsyncGeneratorThrow
: cx->names().AsyncGeneratorReturn;
FixedInvokeArgs<1> args(cx);
- args[0].set(argument);
+ args[0].set(resumeArgument);
RootedValue thisOrRval(cx, ObjectValue(*generator));
if (!CallSelfHostedFunction(cx, funName, thisOrRval, args, &thisOrRval)) {
if (!generator->isClosed()) {
@@ -1262,7 +1279,16 @@ bool js::AsyncGeneratorThrow(JSContext* cx, unsigned argc, Value* vp) {
}
if (generator->isAfterYield()) {
- return AsyncGeneratorYield(cx, generator, thisOrRval);
+ bool resumeAgain = false;
+ if (!AsyncGeneratorYield(cx, generator, thisOrRval, &resumeAgain,
+ &completionKind, &resumeArgument)) {
+ return false;
+ }
+ if (resumeAgain) {
+ MOZ_ASSERT(completionKind != CompletionKind::Return);
+ continue;
+ }
+ return true;
}
return AsyncGeneratorReturned(cx, generator, thisOrRval);