Skip to content

test(aot): an import() of a module that a require() still evaluates settles - #47538

Draft
robobun wants to merge 4 commits into
mainfrom
robobun/8fc1579a/aot-import-of-required-module-settles
Draft

robobun wants to merge 4 commits into
mainfrom
robobun/8fc1579a/aot-import-of-required-module-settles

Conversation

@robobun

@robobun robobun commented Oct 31, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • In an executable built by bun build --compile --aot, an import() of an ES module that a require() is still evaluating never settled. No .then callback ran, and a top-level await of it never ended. bun run fulfils it.
  • The engine fix is on oven-sh/WebKit main: AOT: follow-up to #982: 48 fixes WebKit#983 (Modules::evaluateWithOwnStack, aot/runtime/AOTModuleEvaluation.cpp). main here does not pin an engine with it yet, and has no test for these cases.

Fix

  • Tests only: ten programs under test/aot/parallel/modules/ and one row of a-module-that-has-run-keeps-no-record.test.ts. Each must print and exit as plain Bun does.
  • The WEBKIT_VERSION commit is temporary. It pins a preview build with the same engine change on the commit that main pins, so that the programs pass today. It goes away when main pins oven-sh/WebKit b83d8a12ae or later.
  • Verified: nine programs and the row fail on an engine without the change. All pass on oven-sh/WebKit main at b83d8a12ae (Notes).

Background

Downsides

  • None found. The diff that lands is tests: the pin commit goes away first (Fix).
Notes

The report

@sosukesuzuki, bun 1.4.4-canary.1+da867c60b, linux-x64, every run. a.mjs requires c.mjs. c.mjs imports d.mjs and requires x.mjs. x.mjs calls import("./c.mjs").then(..). bun a.mjs prints the line of the .then callback, the executable does not. Without the import of d.mjs the executable is right, which modules/import-of-module-require-running-settles already covers.

The programs

Each is a folder under test/aot/parallel/modules/. run-parallel.ts builds it with --compile --aot and compares what the executable prints with plain Bun. "Fails" is without the engine change.

Program What it adds
import-of-required-module-with-import-settles the report, with .then. It also counts one module with a start of its own (// STATISTIC: ModulesWithState = 1): without that start it passes on any engine fails
import-of-required-module-with-import-awaited an async function awaits the import() fails
import-of-required-module-with-import-rejects the module throws behind the require: the promise is rejected with the same error passes
import-of-required-module-with-import-job four chains of jobs around the end of the module: the fulfilment lands between the same jobs as under plain Bun fails
import-of-imported-module-with-import-job the same where an import() is the root, where no synchronous queue of a require() takes the jobs of the loader fails
import-of-required-module-with-import-again a later import() of the module inside another require() fails
import-of-imported-module-with-import-settles the module is the root of an import(), not of a require fails
import-of-required-module-without-code-settles the required module has no code of its own and gets no start fails
imports-of-nested-required-modules-settle two nested required modules fails
import-of-required-cycle-root-settles the module is the root of an import cycle fails

The row of a-module-that-has-run-keeps-no-record.test.ts: while the promise of such an import() is pending, the run time retires no record. Without the engine change the executable holds 240 records behind 120 modules.

Programs marked // Todo:

They print something else than plain Bun before and after the engine change, also on oven-sh/WebKit main at b83d8a12ae. run-parallel.ts reports them as skipped, and fails them when they pass.

  • import-by-name-of-entry-point-require-runs-settles: no import() is compiled, so the build makes the require() a call of the start of the module. An import() of it from text that the build did not read stays pending. That needs a second engine change, in callModules.
  • import-again-of-cycle-member-imported-while-it-runs: a later import() of a member of a cycle gets the promise that an earlier one left pending.
  • import-of-member-of-cycle-that-fails-is-rejected: the executable fulfils an import() of a member whose body has ended when its cycle fails.

How it was run

The pin

autobuild-preview-pr-961-fedb2fee is oven-sh/WebKit main at 4d45755fed, which main pins since #47316, plus the engine change as oven-sh/WebKit#961 had it. That pull request is closed, and the release stays. The pin is temporary: see Fix.

What does not change

An import() of a module that is a dependency of the running evaluation, not its root, stays pending in plain Bun and in an executable. Node.js v26.3.0 fulfils it. oven-sh/WebKit#871 is a draft for that.


no test proof · iteration 2 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/aot/a-module-that-has-run-keeps-no-record.test.ts

@robobun

robobun commented Oct 31, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 3:48 PM PT - Oct 31st, 2026

✅ @robobun, your commit 0ae6b714b51df7bfb1cf27b061a0af7324e671f3 passed in Build #136195! 🎉


🧪   To try this PR locally:

bunx bun-pr 47538

That installs a local version of the PR into your bun-47538 executable, so you can run:

bun-47538 --bun

@robobun

robobun commented Oct 31, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status: reproduced on bun 1.4.4-canary.1+da867c60b (linux-x64) with the four files of the report. The executable built with --compile --aot prints five of the six lines that bun a.mjs prints. Nine of the ten new programs and the new row of a-module-that-has-run-keeps-no-record.test.ts fail on an engine without the fix.

The engine fix landed on oven-sh/WebKit main as oven-sh/WebKit#983 (b83d8a12ae). On that engine the ten programs and the row pass. oven-sh/WebKit#961, which made the same change, is closed.

This pull request is now tests only. Its WEBKIT_VERSION commit is temporary and goes away when main pins oven-sh/WebKit b83d8a12ae or later. It stays a draft until then.

…ettles

Eight programs for `bun build --compile --aot`. A `require()` or an
`import()` runs an ES module that imports another one, and a module that
it requires calls `import()` on it while it runs. The promise settles
when the first module has run, in the same job as under plain Bun.

- import-of-required-module-with-import-settles: the `.then` form
- import-of-required-module-with-import-awaited: the `await` form
- import-of-required-module-with-import-rejects: the module throws
- import-of-required-module-with-import-job: the job of the fulfilment
- import-of-required-module-with-import-again: a later `import()` inside
  another `require()`
- import-of-imported-module-with-import-settles: the root of an `import()`
- imports-of-nested-required-modules-settle: two nested roots, CommonJS
  entry point
- import-of-required-cycle-root-settles: the root of an import cycle
The AOT run time fulfils an `import()` of a module that a `require()`
or an `import()` is still evaluating, where the module keeps its status
in the table of status bytes.
… code, and the records

- import-of-required-module-with-import-settles counts one module with a
  start of its own (`// STATISTIC: ModulesWithState = 1`): without that
  start the program passes on any engine.
- import-of-required-module-with-import-job: the imported module queues
  jobs too, so its importer keeps the start.
- import-of-imported-module-with-import-job: the job of the fulfilment
  where an `import()` is the root. No synchronous queue is open there.
- import-of-required-module-without-code-settles: the required module has
  no code of its own and gets no start.
- imports-of-nested-required-modules-settle starts from an ES module.
- a-module-that-has-run-keeps-no-record.test.ts: a sixth shape. While the
  promise of such an `import()` is pending no record goes (240 records
  behind 120 modules).
…nge leaves as they are

- import-again-of-cycle-member-imported-while-it-runs: a later `import()`
  of a member of a cycle gets the promise that an earlier one left
  pending.
- import-of-member-of-cycle-that-fails-is-rejected: the executable fulfils
  an `import()` of a member whose body has ended when its cycle fails.
- import-by-name-of-entry-point-require-runs-settles: with no `import()`
  in compiled code the `require()` is a call of the start of the module,
  and an `import()` from text that the build did not read stays pending.
@robobun
robobun force-pushed the robobun/8fc1579a/aot-import-of-required-module-settles branch from 858d546 to 0ae6b71 Compare October 31, 2026 20:37
@robobun robobun changed the title Bump WebKit (oven-sh/WebKit#961 preview): --aot settles an import() of a module that a require() still evaluates test(aot): an import() of a module that a require() still evaluates settles Nov 2, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants