diff --git a/JSTests/modules/dynamic-import-tla-cycle.js b/JSTests/modules/dynamic-import-tla-cycle.js index 8de474a4f873..45a5b3489534 100644 --- a/JSTests/modules/dynamic-import-tla-cycle.js +++ b/JSTests/modules/dynamic-import-tla-cycle.js @@ -2,11 +2,11 @@ import { shouldBe } from "./resources/assert.js"; let settled = 0; setTimeout(() => { - if (settled != 4) - throw new Error("dynamic import() inside a TLA cycle deadlocked: " + settled + "/4 settled"); + if (settled != 5) + throw new Error("dynamic import() inside a TLA cycle deadlocked: " + settled + "/5 settled"); }, 1000); -for (const name of ["direct", "helper", "helper-after-await", "all"]) { +for (const name of ["direct", "helper", "helper-after-await", "all", "for-await"]) { const { ns } = await import("./dynamic-import-tla-cycle/entry-" + name + ".js"); shouldBe(ns.v, name); ++settled; diff --git a/JSTests/modules/dynamic-import-tla-cycle/child-for-await.js b/JSTests/modules/dynamic-import-tla-cycle/child-for-await.js new file mode 100644 index 000000000000..eac82e75c48b --- /dev/null +++ b/JSTests/modules/dynamic-import-tla-cycle/child-for-await.js @@ -0,0 +1,2 @@ +import { helper } from "./entry-for-await.js"; +export const v = helper(); diff --git a/JSTests/modules/dynamic-import-tla-cycle/entry-for-await.js b/JSTests/modules/dynamic-import-tla-cycle/entry-for-await.js new file mode 100644 index 000000000000..74bae5b36735 --- /dev/null +++ b/JSTests/modules/dynamic-import-tla-cycle/entry-for-await.js @@ -0,0 +1,3 @@ +import { loadForAwait } from "./loader.js"; +export function helper() { return "for-await"; } +export const ns = await loadForAwait("./child-for-await.js"); diff --git a/JSTests/modules/dynamic-import-tla-cycle/loader.js b/JSTests/modules/dynamic-import-tla-cycle/loader.js index b78769628eca..8a11acb82625 100644 --- a/JSTests/modules/dynamic-import-tla-cycle/loader.js +++ b/JSTests/modules/dynamic-import-tla-cycle/loader.js @@ -11,3 +11,10 @@ export async function loadAfterAwait(specifier) { export async function loadAll(specifier) { return await Promise.all([import(specifier), Promise.resolve(1)]); } + +export async function loadForAwait(specifier) { + await null; + function* lazily() { yield import(specifier); } + for await (const ns of lazily()) + return ns; +} diff --git a/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp b/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp index aa5b1afbd845..5c5139235daf 100644 --- a/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp +++ b/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp @@ -38,6 +38,7 @@ #include "JSPromise.h" #if USE(BUN_JSC_ADDITIONS) #include "InternalFieldTuple.h" +#include "JSAsyncFromSyncIterator.h" #include "JSAsyncFunctionGenerator.h" #include "JSPromiseCombinatorsGlobalContext.h" #endif @@ -1254,23 +1255,32 @@ static bool importPromiseGatesAsyncDependency(JSPromise* importPromise, CyclicMo } }; + // A promise, or the async function or module body that an await or for-await resumes. + auto followPromiseOrDriver = [&](JSCell* cell) { + if (!cell) + return; + if (auto* promise = dynamicDowncast(cell)) + work.append(promise); + else if (auto* generator = dynamicDowncast(cell)) + follow(generator->context()); + else if (auto* module = dynamicDowncast(cell)) + found = resumesDependency(module); + }; + auto visitReaction = [&](InternalMicrotask task, JSValue cell, JSValue context) -> bool { switch (task) { case InternalMicrotask::AsyncFunctionResume: - case InternalMicrotask::AsyncGeneratorDriverResume: { - JSCell* driver = unwrapContext(context); - if (!driver) - break; - if (auto* generator = dynamicDowncast(driver)) - follow(generator->context()); - else if (auto* module = dynamicDowncast(driver)) - found = resumesDependency(module); + case InternalMicrotask::AsyncModuleExecutionResume: + case InternalMicrotask::AsyncGeneratorDriverResume: + followPromiseOrDriver(unwrapContext(context)); break; - } - case InternalMicrotask::AsyncModuleExecutionResume: { - JSCell* driver = unwrapContext(context); - if (auto* module = driver ? dynamicDowncast(driver) : nullptr) - found = resumesDependency(module); + case InternalMicrotask::AsyncFromSyncIteratorContinue: + case InternalMicrotask::AsyncFromSyncIteratorDone: { + // for-await over sync values that are promises: the pending step settles the + // iterator's result promise, or resumes its driver, with this promise's value. + JSCell* iteratorCell = unwrapContext(context); + if (auto* iterator = iteratorCell ? dynamicDowncast(iteratorCell) : nullptr) + followPromiseOrDriver(iterator->target()); break; } case InternalMicrotask::PromiseAllResolveJob: diff --git a/Source/JavaScriptCore/runtime/JSAsyncFromSyncIterator.h b/Source/JavaScriptCore/runtime/JSAsyncFromSyncIterator.h index 4d9939a9d4f5..dfba5f3fc62c 100644 --- a/Source/JavaScriptCore/runtime/JSAsyncFromSyncIterator.h +++ b/Source/JavaScriptCore/runtime/JSAsyncFromSyncIterator.h @@ -67,6 +67,11 @@ class JSAsyncFromSyncIterator final : public JSNonFinalObject { return { target, result }; } +#if USE(BUN_JSC_ADDITIONS) + // The promise or driver the pending step settles or resumes; null between steps. + JSObject* target() const { return m_target.pointer(); } +#endif + JSValue cachedDriverResult() const { return m_cachedResult.get(); } void setCachedDriverResult(VM& vm, JSObject* result) { m_cachedResult.set(vm, this, result); }