Skip to content

AOT: front end, test runs and tests for the rewritten compiler - #47942

Merged
Jarred-Sumner merged 56 commits into
mainfrom
claude/one-walk
Nov 7, 2026
Merged

Jarred-Sumner merged 56 commits into
mainfrom
claude/one-walk

Conversation

@Jarred-Sumner

@Jarred-Sumner Jarred-Sumner commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

What this does

The Bun side of the rewritten ahead-of-time compiler. The engine side is oven-sh/WebKit#997, which has the design and the numbers.

  • src/aot: the lowering writes the program that the engine's new scan, link and walk read. Captured values are copied into closures where nothing stores to them later, straight-line blocks are joined, and CommonJS and ES module graphs are resolved at build time, cycles included.
  • bun test --aot: module mocks, spies, hoisted factories, re-evaluated modules and --rerun-each behave as they do without --aot in many more cases.
  • Type errors stop an --aot build, in TypeScript only: bun compile and bun build --compile --aot stop at an error that tsc would report under the project's own tsconfig; bun test --aot prints it as a warning and runs. JavaScript, node_modules and .d.ts files are never checked. In a project that does not list "bun" in types this first form still misses ordinary mistakes and has known false stops; the newest commit's message lists both.
  • A computed import no longer embeds data files that its written name does not ask for. docs/compiler/compatibility.mdx says under "Dynamic imports" what a path that isn't a constant embeds.
  • bun:ffi: no limit on the number of open callbacks in compiled programs (it was 256).
  • Changes to plain Bun. They are many more than this description used to say: see "What Bun without --aot does otherwise than main" below. src/sema is shared with bun check, bun lint, bun fmt, --check and bun build --dts, which run it without the compiler.

Size

Against the main it is merged with (b1305dd, November 5): 35,326 files, +523,094 −1,761,628. Most of both numbers is tests.

Tests Added Changed Deleted
test/aot 15,361 2,378 16,106
Everywhere else under test/ 35 147 3

The deleted files under test/aot are the old compiler's: tests of its passes and its counters. The three deleted elsewhere are this project's own (test/internal/builtins-table). Each stored program under test/aot is compared with the same program run without --aot.

What Bun without --aot does otherwise than main

This PR is to change nothing that Bun does without --aot. It still does. A reading of every hunk from main (b1007f11) to d08cc055 outside test/, docs/, src/aot/ and src/js/ahead-of-time/, 627 files, +52,509 -21,855, 4,555 hunks, nothing built for it, counts: 334 hunks take out code of the previous compiler, 1,715 add what only an --aot build, an --aot test run or a compiled executable reaches, 914 are comments, declarations and unit tests, and 1,592 change code that runs without --aot: 1,238 in src/sema, 354 elsewhere. In the 354 it finds no changed answer of bun file.js, bun test, bun install or bun build beyond those that are marked below. The lines are d08cc055's. "By reading" holds for all of it.

The engine.

bun check, bun lint, bun fmt, --check, bun build --dts run src/sema (304 files, +35,826 -8,769), which earlier forms of this text counted among the compiler's folders and never read with this question. Of its 1,238 hunks 1,087 are judged harmless, 44 can change what is printed, 38 could not be judged by reading, 69 cost memory or time.

  • By intent: TS2502 and TS7022 no longer hang on the order of threads; function (this) is typed as tsc types it (check/expr.rs:3004); several quadratic and worse cases end in seconds (check/unions.rs, check/flow.rs, check/shape.rs, check/union_property.rs, check/context.rs: new caches and shortcuts for large unions); files may nest deeper before they are refused (bind/binder.rs:4986, parser/parser/expr.rs:435); a type-only import x = require(..) keeps the directive prologue open and a type-only statement in a function body ends it (hir/runtime_syntax.rs:89, parser/parser/runtime_syntax.rs:117); fourteen errors of the parser are labeled early errors (parser/parser/ty.rs:822).
  • Not by intent, fixed at ce10cc29: with @types/bun installed, a question that compile builds ask of every file's tree before their workers start was asked by bun check while they run (check/aot/program_symbols.rs:96): it could end bun check. It is asked in compile builds alone.
  • Not by intent, main's answers again at the head: two template literal types, one a subtype of the other, were no longer reduced to one where a class stands beside them in a union (check/unions.rs); TS2563 struck in a very large function where main passes (check/flow.rs). test/cli/check/answers-that-no-shortcut-may-change.test.ts holds both: 1 case passes and 4 fail without the change, 5 pass with it.
  • Not by intent, not fixed at the head: type-aware bun lint no longer closes a ring through a written type (check/expr.rs:4858).
  • Cost: three reads of the clock and five atomic additions for each commit, for logs that only the compiler prints (check/commit.rs:191), a pass over all files for each round (driver/lib.rs:4978); a file with a TS2339, TS2305 or TS2694 on its own declarations keeps its tree to the end (program/retire.rs:436); every file's node lists lie in shared chunks of 4 MiB (hir.rs:638, src/bun_alloc/MimallocArena.rs:408); memo tables that are never emptied (check/flow.rs:4489, :5266, check/instantiate.rs:702, types.rs:1310); every checker is about 6 KB larger (check/mod.rs:1046). The peak memory of bun check was not measured.
  • New unsafe on their road: hir::Fixed, ListBlock, Listed::lent_by, the thread-local of recent arenas in Session::arena(), and a new crate, src/sort_by_number, which moves elements as bytes.

Bun.build, flags, types.

  • Corrected: the messages said that "an aotHeat or an aotDisable of the wrong type is no TypeError any more". It is the other way round: Bun.build({ compile: { aotHeat: anything } }) always throws now (compile.aotHeat is not a valid option): every key of compile that begins with aot and is not known is refused before any is read, and aotHeat, aotDisable, aotValidate, aotLogTypeMismatches left the table (src/runtime/api/JSBundler.rs:346, src/runtime/cli/compile_options.rs:28). At the top level: Bun.build({ aotHeat }) threw "goes inside compile" on main and is passed over now; ({ aotOut }) and ({ aotHarden }) throw it now.
  • Flags of main's compiler that this one does not have are not known any more; three are new: --aot-harden, --aot-cpu, --aot-stored-strings, the last of which works without --aot. bun check loses --aot-report and --aot-report-detail. The help of --explain and --obfuscate is reworded (src/runtime/cli/Arguments.rs, completions/). bun install --aot-log-type-mismatches is refused where main passes it over (src/install/PackageManager/CommandLineArguments.rs:88).
  • packages/bun-types/bun.d.ts:3862, :4047, :4056: the types of Bun.build({ compile }) follow, but for aotOut, which is read and not declared; aotExplain loses "missed-optimizations" and gains "forms" and "statistics".
  • An --aot build that imports a macro (with { type: "macro" }), which main's compiler ran, stops with TS100053 (docs/compiler/compatibility.mdx).

--trim, the resolver, executables, the bundler.

  • src/trim/features/engine.def:121: bun build --compile --trim without --aot removes 38 more groups of engine code, on the word that only compiled code enters them: by reading, never run. The engine's removable features go from 56 to 94, and their tables are in every bun. src/trim/apply.rs:17: removed code is filled with hlt (x64, but Mach-O) and zeros (arm64), not breakpoints; on FreeBSD x64 the crash handler may not know the new byte. src/trim/decide.rs:88.
  • src/resolver/resolver.rs:6215: every file that is found by its exact name passes one more test; :1602, :1705, :1769: every resolve sets and tests a flag, every success looks at the graph once more; :2220, :2365: require.resolve(name, { paths }) makes three more calls for each folder. Every Resolver is 16 bytes larger.
  • Not by intent: import styles from "./a.module.css" under plain bun runs about 230 lines that moved from src/runtime/jsc_hooks.rs:3620, :3648 to src/bundler/css_module_graph.rs; one error that main throws to JavaScript ended the process; at the head it is an error again (by reading: no run reaches it on either build).
  • src/standalone_graph/executable_file.rs:470 (21 hunks): bun build --compile without --aot writes its executable through a new type. StandaloneModuleGraph.rs:212: such an executable passes new tests in its lookups of files and folders while it runs; src/runtime/jsc_hooks.rs:3891, cli/startup_options.rs:566: one more filter for each module.
  • src/js_parser/lower/import_meta_glob.rs:1044: two thread-local cells are read for each import.meta.glob(). src/bundler/ParseTask.rs:2498. src/exe_format/mapped_file.rs:226: one more mmap for each moved range, on a Linux host.

Start, modules, exit.

  • Every start of bun compares its first argument with the prefix --aot-program-file; bun <file> and bun run test for an environment file once more, bun run twice (src/runtime/cli/run_command.rs:1042, :2415); the getter of process.execPath has one more test (node/node_process.rs:33); on Linux two flags are compared, not three (src/bun_core/output.rs:662).
  • src/runtime/jsc_hooks.rs:4431, :4439: the check that every import and require() passes before a file is transpiled is reordered; an executable without the run-time transpiler loads an embedded .node file now.
  • src/jsc/bindings/ZigGlobalObject.cpp:4165: three to four tests for each module that is resolved. JSCommonJSModule.cpp:428: one for each first require() of a CommonJS module and one for each module object that is destroyed. BunProcess.cpp:385: one at exit. src/jsc/VirtualMachine.rs:5999, web_worker.rs:1684, bindings/NodeAsyncHooks.cpp:112, BunSignalActions.cpp:149: a few instructions at a failed resolve, at the start of a Worker, where tracking of the async context is turned on, where a SIGILL action is set.
  • BUN_REQUIRE_INTERNAL_MODULE(.., "name") gives the export, not the module (src/jsc/bindings/InternalModuleRegistry.h:62); ten plain roads are rewritten to match (Bun.sql, SQL errors, util.inspect from native code, the timers' promisify.custom, the inspector, fake timers).
  • Larger: every arena (+16 bytes, src/bun_alloc/MimallocArena.rs:76), every global object (about +80), import.meta (+8), process (+8), each module mock (about +48), each covered file under --coverage (+24, src/sourcemap_jsc/CodeCoverage.rs:634).

bun test.

  • Under bun test the runner's own load of a test file, which has no importer, keeps its key while a mock's factory has not settled. Three lines of this branch in keyOfImportWhileModuleMocksRun() (src/jsc/bindings/BunPlugin.cpp). By intent.
  • Calls around preloads and a compare at every step of the runner (src/runtime/jsc_hooks.rs:780, :789, :894, test_runner/Execution.rs:652, :1233); a call at every failed test and more work at the end of a run (cli/test_command.rs:2205, :3619); one native call before each test file (src/jsc/VirtualMachine.rs:6198); one more for each function that expect or a snapshot prints (test_runner/pretty_format.rs:520, :1431, dom_format.rs:176); toEqual(), toMatchObject(), Bun.deepEquals(), assert.deepStrictEqual() test for compiled objects (src/jsc/bindings/bindings.cpp:703); bookkeeping of mock.module(), vi.mock(), jest.mock() that runs on data which only a compiled run reads (BunPlugin.cpp:528, :1724); how test.extend() reads the parameter of a callback (test_runner/test_context_parameter.rs:43).
  • --parallel: a worker that waits for its next file sleeps instead of polling (cli/test/parallel/runner.rs:713); the two messages between coordinator and worker have another shape (Coordinator.rs:750, Worker.rs:379, runner.rs:646); a worker reads an environment variable behind each file (runner.rs:608). --timings reads a new key, "workers", and a plain --update-timings drops it (cli/test/Timings.rs:37, :129). --isolate=modules and the environment files ask one more question (cli/test_command.rs:2734).

Native functions and the library.

  • 150 to 200 native functions whose results are declared to the compiler (sockets, Bun.serve, Response, Blob, expect; src/codegen/bindgen-emit.ts:832, src/jsc/bindgen.rs:1374, :1437) test a flag before they return: 3 to 8 instructions a call in plain Bun, measured (Blob.text +3.0 of 1,777; Blob.slice +6 of 926; Blob.type +8.0 of 342, where the test behind the call takes the thunk's tail call away); about 55 KB of thunks. In a release build the flag is false unless compiled code runs, and a native function that answers outside its declared type answers as before (run: bun <file>, a plain executable). A build that asserts (debug, cargo test, ASan, or assertions with canary; CI publishes none of them) starts with the flag set: there such an answer throws a TypeError, and a value cached outside its type panics. Measured on Linux x64, a build of main against two of the working tree; only the +8.0 is laid to the test by the disassembly, the other two are differences of whole builds. Corrected: the messages said "a load and a branch". Corrected: "no released build has it" holds only as long as no release-safe canary is published: the switch is Environment::CI_ASSERT (src/bun_core/env.rs:49).
  • Nine library files and one header give plain Bun another text than main's: src/js/node/worker_threads.ts:771, node/events.ts:271, internal/streams/utils.ts:207, end-of-stream.ts:147, internal/net/symbols.ts:25, internal/streams/load_all.ts (new), internal-for-testing.ts:235, :917, builtins/CommonJS.ts:27, :270, builtins/ProcessObjectInternals.ts:706, builtins/BunBuiltinNames.h. No answer changes by reading; names and lines in stack traces do. 12 other changed library files, internal/streams/readable.ts among them, come out byte for byte as main's from bun build with the shipped definitions: the one thing that was run.
  • console.log and Bun.inspect ask one more question of native constructors (src/jsc/ConsoleObject.rs:2104). Threads that read a large file or write an executable are named "Bun Parallel" (src/bun_core/util.rs:2787).
  • Bytes that every bun carries: 38 more records of removable features, the first text of 16 library modules for a testing option (417 KB before compression, src/codegen/builtins-manifest.ts:275), natives for testing alone in release builds (src/jsc/declared_results_testing.rs, src/runtime/api/crash_handler_jsc.rs:98), one more internal module.
  • The crash handler: one more kind of segmentation fault is looked up in the table of removed code (src/crash_handler/lib.rs:1692, :1702); build_id() reads two more words (build_id.rs:80); a Rust panic asks of up to 20 addresses whether each lies in compiled code.

For who builds Bun. The link of every released binary goes through scripts/build/trim/*, which changes: symbols of size zero give no edges (elf-graph.ts:167), weak aliases no longer force the fallback on Windows (linker-coff.ts:220, coff-graph.ts:105), inputs outside the build folder are linked through symbolic links (linker-elf.ts:69, :104). The build fails on = (0, function () {}) in src/js, and where a static const HashTableValue X[] under src/jsc/bindings is described to nobody (src/codegen/builtins/collect-webcore-tables.ts:673). scripts/build/flags.ts:93, rust.ts:603: flags of two configurations that nothing ships. binary-expectations.ts:374, scripts/rust-test.ts:58, rust-miri.ts:42: CI's checks. Cargo.toml, Cargo.lock: the new crate src/sort_by_number, linked through bun_sema.

What it no longer pays. 38 hunks in src/jsc put a test of a flag in line where main calls into the engine's ahead-of-time run time: at every load of a built-in module, every property read by name from Rust, every walk of console.log over properties, every strict deep comparison, the making of process. Response.json(), .bytes(), blob.json() and Postgres in Bun.sql lose hooks of the previous compiler (src/runtime/webcore/Response.rs:821, Body.rs:1949, Blob.rs:2456, src/sql_jsc/postgres/). Since 4954cae0 Bun.jest() is main's function again.

State

  • src/aot, src/sema, src/bundler/aot, src/js/ahead-of-time, src/codegen/builtins, docs/compiler and test/aot are this branch's, whole.
  • scripts/build/deps/webkit.ts names 42067fce, the engine PR as it is merged.
  • CI passed at ce10cc29 (63 minutes, no red file). The head 1bbb52e2 changes the pin's line and the limit of one test case and nothing else; its run is the first with the released engine. Each commit's message lists what is known to be wrong. The way there: At c367dfe5 two files were red and nothing else: test/aot/compiler/names-compared-as-text.test.ts on ten lanes (the test reads the engine's C++; the zip of the build holds no aot-sources, because no tarball of the engine's release holds its C++) and vendor/elysia/test/response/stream.test.ts under ASAN (the test races a timer of 5 ms, and a sanitizer's build, which takes the system's allocator on this branch, loses it). d0482105 put main's text back where this branch had changed Bun without --aot, and turned nine more checks red by it: tests of this branch that still pinned the old behavior, places where the compiled side did not follow, one assertion of the compiler under ASAN, one lint. 9fb4aa95 mends eight of them and turned one sweep of the library red on the lane of cargo test; eee70d05 mends that: there the two files were red and nothing else, the first on nine lanes; the case on Linux arm64 that its message foretold red was green. 0a0d53cd ended at 34 red files: 31 on the x64 ASAN lane alone by one assertion, elysia's stream test there as before, names-compared-as-text on ten lanes, and test/aot/test-runs-in-folders-3.test.ts on three (Debian 13 x64, arm64, ASAN). The last is one stored test run: the fix that calls a mock's factory after import() stood on an older change of the working tree that did not come with it, so two loads in one turn of a module whose mock's factory throws came in the other order than under bun test. The files on the ASAN lane are nearly every one that builds a program: the compiler of the pinned engine left exceptions unlooked at while it works out the texts of regular expressions. 39c6f344 pins the engine's e8d13866, which looks at them, and brought a red of its own on the ASAN lane: two stored test runs of test/aot/test-runs-in-folders-1.test.ts, by the line in BunPlugin.cpp that is named below. 186ac75a pins a6c0a9a0, takes the fix on factories back and puts that line back. 186ac75a ended with three red files: names-compared-as-text on nine lanes, elysia's stream test under ASAN, and test/aot/compiler/independence.test.ts on Alpine arm64; test-runs-in-folders-1 and -3 were green on every lane. The third is a race in the compiler that the engine's e8d13866 brought: it ended now and then without a message, at build time: in 7 of the 23 tries of independence.test.ts and determinism.test.ts at 39c6f344, on 5 lanes, where the retries passed and hid all but one; in 4 of 18 at 186ac75a when they were last counted; in none of 18 at 0a0d53cd. 89e8b687 pins the engine's 461dc671, which fixes it: with 30 microseconds of sleep laid where the race is, 38 of 48 runs end so, and 0 of 48 with the fix; whether it is the only cause is not known. The engine PR's body has the whole. Since that pin the two files ran 26 times on eight lanes, in the builds at 89e8b687 and ba47224d, and passed each time at the first try; at 39c6f344 and 186ac75a the compiler exited in 11 of 43 tries. The build at bb7d7922 ended with three red files and nothing else: names-compared-as-text on nine lanes, test/aot/ci-lists.test.ts on three (new with that commit, ours; 61b3e18f mends it) and elysia's stream test under ASAN. At 241642de ci-lists was green again, and test/aot/a-require-that-goes-through-the-host-keeps-no-module-object.test.ts was red on x64 ASAN; c3231d2a mends it. The build at bcb9df46 has one red file more, ours: test/aot/runtime-use-of-built-in-code.test.ts, one case of twelve, on Debian 13 x64 and arm64: for a program that requires the other built-in modules the report names globalThis, process and Bun as escaped, where the test expects none. It came with the engine's 42121540, by its fix of Buffer.concat([], null): the part of a call of a native function of the host that fits no row of the table goes out of sight, with its receiver and its arguments, and such calls stand in the library's own code. No answer of a program changes by it. What a user can see: --trim keeps more. The global object goes out of sight for 13 more built-in modules, all that load node:net; for console.log(typeof require("net")) the report of --explain=trim grows from 7,004 to 8,497 lines, all that hangs off those three. The bytes under --trim were not measured. A two-line node:http server shows nothing (the same report, the same size), because its report was open before. The build at bc4394e9 ran to its end with three red files: names-compared-as-text, elysia's stream test under ASAN and this one; no test that bc4394e9 brought is among them. 9d2ddacf fixes it by its pin: 12 pass; a build of console.log(typeof require("net")) with --explain=trim prints 7,551 lines on the engine's e8d187c4, 8,488 on bd8bd487 and 7,551 here (all that the build prints). The same pin turns two other files red, no answer wrong, too much is kept: test/aot/runtime-use-prototypes.test.ts (25 pass on the last pin; 24 pass and 1 fails) and test/aot/asking-about-a-class-keeps-none-of-its-code.test.ts (14 pass; 12 pass and 2 fail): the engine's e75efb3b has the engine's half of a fix for a crash (below), that half lets go of too much, and it alone turns the two red: found after the engine's push, so the front end's half is held back. What a user sees: for a program that stores the prototype object of a built-in class as the prototype of a function of its own, the report names globalThis, process and Bun as escaped; for one that asks whether an object made over such a prototype object is an instance of the class, 13 lines of the report say what blocks --trim where 3 did. The bytes under --trim were not measured. The build at 9d2ddacf showed a third file red by the same half, which nobody had foretold: test/aot/subclasses-of-collections-stay-tracked.test.ts (5 pass, 1 fails: Map.prototype.set escapes where a subclass of Map overrides set). The engine's dacd2d81 takes that half back, and 900ca033 pins it: 25, 14 and 6 cases pass. New in the build at 9d2ddacf, under ASAN alone: test/js/bun/http/serve-body-leak.test.ts, one case of 15 (a body streamed and echoed over HTTP/2): the memory between start and peak read 262 to 278 MiB at four tries on one machine where the bound is 256; the five builds before read 49 to 125. Nothing that 9d2ddacf or its pin changes lies on that road, by reading. Eleven runs of one ASAN build of this branch's text read 24 to 254 MiB, in two heaps, and pass: the number hangs on the run. Not fixed at 900ca033. At d08cc055 the case of test/aot/compiler/names-compared-as-text.test.ts that read the engine's C++ is deleted with all that it alone needed, scripts/build/ci.ts's 14 lines among it, which is main's text again: CI never had the engine's C++. The case on the front end's Rust stays. With bf18bbfc the engine takes out the 13 lines by which a build with AddressSanitizer took the system's allocator; test/harness.ts and test/internal/address-sanitizer-sees-wtf-allocations.test.ts are main's again. Ten rounds on two ASAN builds of one tree: vendor/elysia/test/response/stream.test.ts passes 0 of 10 with the lines and 7 of 10 without, and a build of main wavers alike; serve-body-leak passes 10 of 10 on both. At 4954cae0: Six of the twelve test files that this branch had added under test/internal go (-552 lines). They tested tools of the build on made-up inputs, or read sources by regular expressions, and no program's answer hangs on them: source-lints/stand-ins-without-the-compiler.test.ts (a reading of Bun's C++ by regular expressions; what it looked for is a compile error in a build with ENABLE_AOT=0); builtins-table/native-loads.test.ts, tables-in-cpp.test.ts and values-of-properties.test.ts (parts of the generator of the table of built-ins, which runs over the real table in every build); builtins-table/a-method-that-a-schema-gains-needs-no-other-prebuilt-engine.test.ts and the-table-that-a-prebuilt-engine-was-linked-with-is-not-compared.test.ts (two of four whole runs of that generator against a changed copy of an engine's files). What stays of the twelve: that a prebuilt engine with another position for a built-in is refused and one with this table accepted, with their helper; the two checks that refuse an unsound row written by hand; and that Symbol and BigInt are constructors, which asks Windows' linker. No test ran after the six went.
  • Bun without --aot. This PR is to change nothing that bun <file>, bun test, bun build and the rest do without --aot. Readings of the whole diff with that question found places where it did. d0482105 took most of them back (21 files are main's text again and leave the diff; in a dozen more the lines that plain Bun runs are main's again); eee70d05 takes back more: bun:ffi is main's text; flags and options that begin with aot in another case are passed over as on main; an executable built without --aot is handed every word of its command line; --update-timings writes main's columns; two declarations in bun-types; the object of a CommonJS module is as large as it was; only the threads of a build get a stack for signals; two tables of the compiler's run time lie no longer in every Bun; 39c6f344: one test that only compiled code needs (src/bun_core/debug.rs) is made only where the process has compiled code. It did the same to a second one, in src/jsc/bindings/BunPlugin.cpp. That was wrong: two stored test runs, in which a mock's factory never settles while the next test file loads, were red on the ASAN lane on their side without --aot; 186ac75a puts that line back as it was at 0a0d53cd. 9d2ddacf: the loop of Bun__deepMatch (expect().toMatchObject() and its kin) is main's again, word for word (found by the review on this PR), and a folder given to require.resolve(name, { paths }) is borrowed, not copied, where the process has no compiled program (no answer changes; by reading, no time was measured). bc4394e9: the chainable methods of a timer hand on what Rust answered, as on main, and createRequire() asks first whether the process has compiled modules; with the engine's bd8bd487 the generic road of Object.assign() asks the target's own bit first and String.prototype.replace() with a function copies a replacement of 8-bit characters by the 8-bit road again (no answer changes; by reading, no time was measured). bcb9df46: four roads of every process (a resolution through the plugins' table, each resolved import, each load of a module of code, each lookup of a file in an executable built without --aot) ask first whether the process has compiled modules; and with the engine's 42121540 one answer goes back to main's, const m = new Map(); const it = m.keys(); Array.from(it); m.set("a", 1); it.next(), with what eight files of the engine cost a program that is not compiled. What is still otherwise has a section of its own below. The list that stood here, and stands in the commits' messages up to 4954cae0 under "What plain Bun still does otherwise than main", had ten items; it was offered as all, and it was not: a reading of every hunk finds 1,592 that a run without --aot passes through. Corrected: until now this text, and the messages of d0482105, eee70d05, 0a0d53cd, 39c6f344, 186ac75a, 89e8b687, ba47224d and bb7d7922, named two differences that the branch does not have: a shorter road for console.log of one string, and Bun.inspect of what toJSON gives as globalThis. Both print as on main: src/jsc/ConsoleObject.rs differs from main by nine lines, which ask a question that only a process with compiled code answers with yes. The two changes were made in a working tree and are in no pushed commit. The same messages said of the threads of bun test --parallel what is true of the threads named here.
  • Fixed at ce10cc29, with the engine's 3c42aae2: three test files that were red in CI at 4954cae0, no answer wrong: test/aot/no-leak-records-at-exit.test.ts under ASAN (leak records that 13 lines of bmalloc had hidden until the engine's bf18bbfc; 2 pass and 4 fail, 6 pass with the engine's change), test/aot/builtins/differential.test.ts (1 case of 6,875: the test called a helper that only compiled code can run) and test/aot/mocks-are-hoisted-above-the-imports.test.ts (a known difference that 4954cae0 had ended was still listed). And the question that plain bun check asked while its workers run, above. Corrected: the messages of 900ca033, d08cc055 and 4954cae0 said of some test files that they fail because the machine on which they are run before a push lacks packages or files. That was a hole in our own list: 23 of the 1,044 test files below test/aot ran blind there. It holds the whole of test/ and the packages now. Corrected too: where those messages count test files that pass there, they count 39 of the 1,044 that run no case on that machine's build, which is not trimmable, not hardened and no sanitizer's: "ran no case" is not "passed". For the head the 27 test files that need a trimmable bun were run on a second build of the same sources: 1,346 cases pass, none fails, none is skipped. The 7 on hardened executables ran on no build of these sources before the push.
  • Fixed at 4954cae0, with the engine's bf18bbfc, each seen red on a build of d08cc055 with dacd2d81 and as bun <file> on a build of the head's text (38 new stored programs). New with d08cc055: nine right programs stopped: a program that finds addEventListener and dispatchEvent by walking the keys of a prototype, calls them without a receiver and hands EventTarget.prototype on in one of its lines (19 programs of another hand: 11 answer and 8 stop at 900ca033, 2 and 17 at d08cc055, 11 and 8 at the head). Silent: fake timers in an executable (0 0 for 1 0; one executable did not end); a module mock behind two export * (both=undefined,kept=10 for both=file,kept=10); (E.B as any) = 9 on an enum in the form that tsc emits: a case E.B: kept the old number; [import.meta.path].map(Bun.jest) answered a number. Loud: exit 134 on an async generator that relays a sync one and is resumed by a throw that nobody writes; spyOn(holder, 0, "get") on an object of compiled code threw. bun test --aot pointed at another line than bun test for a throw over lines. A const of a file read in a loop of a function of the file no longer stays in every turn (new with the engine's 42121540).
  • Fixed at d08cc055, on the same preview of the engine: new with 900ca033, no answer wrong, too much was kept, red in CI on three lanes: a program that prints performance and names no event kept all that the global object leads to (test/aot/parallel/host-objects/performance-printed.js: ImageFunctionsEmitted <= 8, but the build counted 3306). performance inherits EventTarget.prototype, and since 900ca033 such a holder counted as handed on wherever it was an operand of a call; it takes the method out only at a built-in that hands out what lies under keys. No bound moves. 58 stored programs: 56 pass and 2 fail on a build of 900ca033, 58 pass on a build of the head's text. Of the 21 programs of another hand the same nine still stop by name.
  • Fixed at 900ca033, with the engine's dacd2d81, each seen red on a build of e75efb3b with 9d2ddacf and as bun <file> on a build of the head's text (57 stored programs: 43 fail and 14 pass before, 57 pass here). Most forms, not all: a right program ended with exit 134 where one of the two methods that take the global object for their target comes out without a receiver: addEventListener("ping", event => { console.log(typeof event.target.process.finalization); }); const { dispatchEvent: send } = new EventTarget(); send(new Event("ping"));; so EventTarget.prototype.dispatchEvent.call(undefined, event), the method bound to nothing, read off a signal, under a constant or a name that is written as a string anywhere in the program, behind a comma, handed out by Reflect.get() or a describer, or called through super where this is nothing (30 stored programs: 22 fail and 8 pass before; 21 more of another hand, each built alone: 21 end with exit 134 before, 12 answer as bun <file> here and 9 still end so: all of the kinds that docs/compiler/compatibility.mdx declares since this commit: a name that is put together while the program runs or comes out of for .. in, Object.keys() or the name of a function, and the method found without any name, out of Object.values() of the prototype; void dispatchEvent once anywhere keeps all that the global object leads to). Silent: a direct eval in a method of a class whose text is evaluated more than once told private names apart by their spelling alone: function make() { return class { #m() {} has(o) { return eval("#m in o"); } }; }, asked of an instance of the class that another call of make() gave, answered true for false (13 stored programs: 10 fail and 3 pass before). Silent: a key added behind delete made[4294967295] by the number took the deleted key's place in the order of the keys. The build of a right generator stopped with "the walk is over and an edge is open". A build could hang on a program that lays a built-in function where the engine calls it: more forms (7 stored programs: 4 fail before, by their limits of time). The text of a message: a call that fails in the value of a field of a class was quoted at the constructor (In 'callee()' for In 'callee(a, b)'; found by the review on this PR). node:http in an executable: http.OutgoingMessage.call(res) on the response of a request that is being answered threw out of the library or lost the body, as http.ServerResponse.call(res, req) did before 9d2ddacf.
  • Fixed at 9d2ddacf, with the engine's e75efb3b, each seen red on a build without the change and as bun <file> on a build of the head's text (18 stored programs: 12 fail and 6 pass, the controls and those of changes that move no answer, on a build of bd8bd487; 18 pass here). Silent: a wrong value was stored: a whole number that is boxed as a fraction, stored into a list of whole numbers behind a comparison that says that the number is whole: const parsed = JSON.parse("[0.5,1,2,3,4]"); parsed.push("text"); function fill(list, value) { switch (value) { case 3: for (let i = 0; i < list.length; i++) list[i] = value; } return list; } const list = fill([9, 9, 9, 9, 9, 9, 9].slice(), parsed[3]);: a loop read 3 out of the list; list.join() and JSON.stringify(list) said 0, list.indexOf(3) -1, list.includes(3) false; so behind (x | 0) === x. On the engine's branch since a1cb2d06, by the history of the line that makes such a store. With --aot-harden=proof-checks alone: the false alarm that ended a build with exit 134 (reported by a user of the last commits). A build could hang on a program that lays a built-in function where the engine calls it (46 small programs, 15 s given: 22 builds do not end before, all 46 build here; one of them builds slower than before: 0.99 s and 544 MB for 0.21 s and 123 MB). In an executable built with --aot, require.resolve(name, { paths: [module.path] }) with a relative name made at run time answered a path under the working directory where a file of that name lay there; so for a package asked for from a folder of the executable. node:http in an executable: http.ServerResponse.call(res, req) on the response of a request that is being answered threw out of the library or lost the body; what a request class of the program's is handed, passed on as the options of new http.ServerResponse(req, kept), left the client without an answer. A closure that a direct eval in a computed key of a class makes, and that reads a private method which stands behind the key, threw a TypeError where bun <file> answers. docs/compiler/compatibility.mdx says that a direct eval in a computed key or the extends clause of a class is strict code, and which built-ins have no frame of their own in a stack trace.
  • Fixed at bc4394e9, with the engine's bd8bd487, each seen red on a build of 42121540 with bcb9df46 and as bun <file> on a build of the head's text (28 stored programs: 23 fail and the 5 controls pass before, 28 pass here). A right program crashed: a function that stands in a computed key of a class, or in the expression of a decorator, calls a private method of that class in a loop while the class is still being defined (12 stored programs). A direct eval in the extends clause of a class was offered the private names of that class: false for a SyntaxError, and with a static private method a right program crashed (4 stored programs). Silent: enum Level { Warn = Base.Warn, Error } where Base is an enum of another file: warning: disk for error: disk (reported by a user of the last commits). Exit 134: an index laid on a Map, a Set, a Date, a RegExp, an Error or a Promise, then a walk over its values: const cache = new Map([["a", 1]]); cache[0] = "x"; cache[1] = { v: 5 }; for (const value of Object.values(cache)) kinds += typeof value; (5 stored programs; 13 more built alone: 6 exit 134 before, 13 answer alike here). Exit 134: one form that was left of the crash on a read in front of a copy by a spread: a class that is never made bears the names of the literals (reported by a user of the last commits). New with 42121540, loud, no wrong answer: the build of a program with a class that has private fields, a method of which is called in a loop, printed AOT: TypeTest, operand 0: OperandTypeMismatch .. and compiled the loop in its slower form. node:http in an executable, silent: the body of a request that nobody has read was dumped though the program says under req._readableState.resumeScheduled that a resume is scheduled; null or undefined that a handler lays over the abort function on req.socket was handed on where bun <file> lays the function anew. In an executable built with --aot, a file above the entry point's folder that climbs with path.join(import.meta.dirname, "../../..", name) past everything the executable holds no longer gets a same-named file that lies beside the entry point's folder; /$bunfs//root/x and /$bunfs/./root/x name what /$bunfs/root/x names. Found by the review on this PR, with a patch: in an executable built with --no-compile-function-source the name of a callee in a message lost the round brackets that end an optional chain (o?.W is not a function for (o?.W) is not a function).
  • Fixed at bcb9df46, with the engine's 42121540, each seen red on a build of e8d187c4 with c3231d2a and as bun <file> on a build of the head's text. A right program crashed: a function that reads a variable of its file is called, in a loop of another function, before the variable is filled: function go(turns, record) { let sum = 0; for (let turn = 0; turn < turns; turn++) { JSON.parse("1"); sum += record.read(turn); } return sum; } console.log(go(Number("0"), null)); const held = { width: Number("5") }; function read(turn) { return held.width + turn; } console.log(go(Number("3"), { read })); (13 stored programs, 2 more built alone, 2 IR tests). Exit 134: Buffer.concat([], null) and Buffer.copyBytesFrom(new Uint8Array(0), null), which return a Buffer under bun <file> (5 stored programs: 4 fail and the control passes before; 24 more built alone: 6 exit 134 before, 24 answer alike here). Exit 134: the two forms that were left of the crash on a copy by a spread (reported by a user of the last commits). node:http in an executable: stream.Stream = Mine or stream.Duplex = Mine in front of require("node:http") went on with Bun's own classes in silence: the load throws a TypeError that says what to do instead, and docs/compiler/compatibility.mdx has the row; stream.Readable = undefined; require("node:http") threw at the load where bun <file> loads. New with this branch, found by the review on this PR: in the metafile of a build with --aot the import of an HTML page had another kind than without.
  • Fixed at c3231d2a, with the engine's e8d187c4, each seen red on a build of 7016427a with 61b3e18f and as bun <file> on a build of the head's text; all three old. A right program ended with exit 134 where an event hands out the global object: addEventListener("ping", e => console.log(typeof e.target.process.finalization)); dispatchEvent(new Event("ping")) ("process.finalization was left out of the executable, because nothing seemed to use it"); so currentTarget, srcElement, composedPath(), an object with handleEvent, and a worker's onmessage: the load, which decides what goes into the executable, did not read the rows that say which built-ins hand out the global object (12 stored programs: 9 fail and the 3 controls pass before, 12 pass here). Silent: const k = {}, v = new Float64Array(1); v[Symbol.iterator] = function* () { yield* [[k, 0.5]]; }; new WeakMap(v).get(k) + 1 answered NaN for 1.5 (2 stored programs). Exit 134: function T() {} new T().constructor.a = 1 (2 stored programs, 2 more built alone; reported by a user of the last commits). One test that was red on x64 ASAN at 241642de, because its inner run passed bun test's five seconds, gets the time.
  • Fixed at 61b3e18f, with the engine's 7016427a, seen red on a build of 779e86aa with bb7d7922 and as bun <file> on a build of the head's text. A right, everyday program ended with exit 134: a read of a field in front of a copy by a spread, where the objects that come differ in their fields: const k = []; function f(o) { if (!o.a) return; k.push({ ...o }); } f({ a: 1 }); f({ a: "x", b: 3 });; so a reducer that returns early on one action.type and copies the action behind it. 9 stored programs: 8 fail and the control passes before, 9 pass here. Reported by a user of the last commits. bb7d7922 had turned test/aot/ci-lists.test.ts red (four new test files were in neither of CI's lists): they are entered. Three test files that were green only at a later try get the time or the memory that they lacked. The list of what plain Bun does otherwise is corrected (above). 241642de, a commit of the repository's formatting bot, stands on it: one test file laid otherwise.
  • Fixed at bb7d7922, with the engine's 779e86aa, each seen red on a build of 461dc671 with 89e8b687 and as bun <file> on a build of the head's text. A right, everyday program ended with exit 134: a spread over an object whose members null and undefined came by stores, const o: Record<string, number | null | undefined> = {}; o.a = null; o.b = undefined; ({ z: 1, ...o, a: 0.5 }). The build stopped on const K = "kk"; class A { [K] = 1; } const { [K]: v } = new A();. node:http in an executable, silent: what an own accessor _httpMessage on req.socket throws behind the end of a response was swallowed (stored null | ok for escaped: made up | ok). 9 stored programs: the 6 witnesses fail before and pass here, the 3 controls pass on both. Two notes of the review; the programs that tell their answers run in eight test files for four.
  • Fixed at ba47224d, on the same preview of the engine: new with the engine's 714174b0, bun compile stopped with internal compiler error (Load): the program enters functions of Bun's library that the load has left out: Promise of PromiseConstructor on a program that writes constructor outside a class: function constructor() {} as the whole program, { constructor() { return 1; } }, Object.keys({ constructor: 1 }), a JSON file that holds the text, an HTML import whose page names an asset so. Three changes of the front end go with that commit of the engine and had not come with it. 5 stored programs: 5 fail at 89e8b687, 5 pass here. Reported by a user of the last commits.
  • Fixed at 89e8b687, with the engine's 461dc671: the race in the compiler that is named above; while (map.size) map.delete(map.keys().next().value) on a Map or a Set whose type the compiler does not know took time that grew quadratically with the size (2 stored programs: the one that bounds the growth fails on a build of the last commit and passes here). Old, silent, node:http in an executable: res.write(new String(..)) whose toString ends the response, throws, or destroys the response or the connection amid the call answered false where bun <file> throws the RangeError, gives ERR_STREAM_WRITE_AFTER_END or answers true (7 stored programs: 4 fail without the line and pass with it, 3 pass on both). Tests: a tool of the engine's tests that is ended by a signal now says who ended it; determinism.test.ts compared nothing in one of its halves since November 4 (its call of testaot facts lacked a word, the tool printed "bad argument" and exited 0, and three copies of that line were held against each other): it compares facts now, and a tool that refuses its arguments fails the test that started it.
  • Fixed at 39c6f344, with the engine's e8d13866: new with the engine's f87bb065, the key of an object literal and the name of a function did not count as names that the program writes: for (const key of Object.keys({ eval: true, label: "made" })) { const found = holder[key]; if (typeof found === "function") found(text); }, with the global object for holder, threw an EvalError where bun <file> answers (5 stored programs: 1 passes, 4 fail | 5 pass); a for await at the top level of a module over an async generator went on in the module's context of AsyncLocalStorage (26 stored programs: 11 pass, 15 fail | 26 pass).
  • Fixed at 0a0d53cd, with the engine's 1607a183, each run on a build of the last commit and on a build of this one: bun compile ended with an abort where subclasses of one class are held in the fields of an object, one is picked at run time and made with new, and one has a member that nothing calls (const v = { k: K, l: L }; new (i & 1 ? v.k : v.l)(); 25 stored programs fail | pass); an enum whose first member is a constant from outside it, inside a namespace or a function, counted on from a wrong value in silence (100 undefined NaN for 100 101 102), and so an enum that is declared again and names a member of the earlier declaration; under bun test --aot the factory of vi.mock(), vi.doMock() or mock.module() ran inside the import() of the mocked module, where bun test calls it in a later job (TAKEN BACK at 186ac75a, see above); what a program lays on a request of node:http while its body arrives was passed over; two builds grew quadratically (many calls of a member of require, many direct evals), one could hang (a function made out of a text; a regular expression with a redefined property handed on by apply()), one never ended (classes that a statement which never runs would join into a ring). Faster, the answers the same: await it.next(), and for await over an object whose method makes the async generator, where the piece arrives later.
  • Fixed at eee70d05, with the engine's c563e0de: of two test files under bun test --aot that each mock one module partly by spreading the real one, the second lost what its mock adds (new with d0482105; a family of stored test runs: 3 pass, 4 fail | 7 pass); for await over an object whose method makes an async generator calls next() and awaits in its own context, as the language says (5 stored programs fail | pass; their known-failure mark is gone); under bun test --aot a test that lays a class of its own in the place of Map or Set could not load node:stream; an executable built with --aot says nothing of a preload in a bunfig.toml that nobody named, and no longer ends with exit 1 where --config names such a file. Taken back: the labels of a switch are heard as every text that the program writes again (case "eval": threw an EvalError where === "eval" answered).
  • Fixed at d0482105, with the engine's f87bb065, each run on a build of the last pushed pair and on a build of this one (140 stored programs: 43 pass, 96 fail, 1 is skipped | 139 pass, the same one is skipped; 38 projects of stored test runs: 37 pass, 31 fail | 68 pass): bun test --aot over two test files that each mock one module partly by spreading the real one crashed; AsyncLocalStorage: a store entered behind mock.module() or new Bun.ModuleGraph() was seen by the next callback, under bun test --aot by the next test, the consumer of an async generator did not always go on in Bun's context, and under --isolate a context that a test entered was seen by the next test (all silent); node:http: with cork, write and uncork called by hand what one response held back was sent by the uncork() of another (silent), and a status message that is an object is checked at every response; nine families of module mocks under bun test --aot; a loader of the program that lays module.exports itself; a function assigned to a name in parentheses lost its name (silent); a name such as "constructor" that comes as a key of constant data, a label or a function's name is spelled by the program (an EvalError before); bun:os out of a list or a variable; names of built-in modules in an imported .json file.
  • Fixed at c367dfe5, with the engine's 2e069424, each run on a build of the last pushed pair and on a build of this one, every program alone (51 stored programs: 38 answered otherwise, 6 ended the process, the builds of 3 did not end, 4 were right | all 51 are right): the key of a for .. in over a list was a number, not a text, in a catch or finally around the loop or behind a yield, and list[key] = key stored 0 for -0; a module that exports then and is awaited, or is an entry point, got a namespace without the exports that nobody else reads; node:http asked what a program lays on the prototypes otherwise than Bun's other text of the library does (all silent); super in a method laid over a member of process ended the process; the build of a program that hands a regular expression with a redefined property of its own to replace() or match(), called as written, did not end; a computed import with a written type left .sql and .jsonc files out, and TS100089 named a file that the executable held; a computed path written in an installed package left that package's data files out.
  • Fixed at 10965b95, with the engine's 2ca46e05: where a call of a built-in is refused for one operand, code of the program that another operand runs in front of the refusal runs (silent, old); a build could hang on a program that makes a function out of a text; a name out of a list, a record or a template of constants is spelled by the program (an EvalError before). The four stored programs on Workers that were red on both Windows lanes are green since c61532e5.
  • Fixed at c61532e5, each with the program that showed it in the commit's message: with the engine's afaf7209, a loop that stores into a list of integers and reads the same list again in the same turn could get the element from before the store (silent); an import that only dropped decorators name was loaded, a decorated abstract or declared field was never initialized, a .cjs file that nobody requires was taken for CommonJS (all silent, old); import("./" + name, { with: { type: "json" } }), createRequire() with a written folder and "@scope/" + name embed their .json files again, and the build warns of the data files that a computed import leaves out (TS100089); the build warns where it loses sight of a Worker of node:worker_threads.
  • New with d08cc055 and not fixed at ce10cc29: a program that finds the two methods by walking performance itself (both(performance), Object.getPrototypeOf(performance), performance.__proto__, through a constructor) and calls them without a receiver ends with exit 134: 8 programs of another hand answer at 900ca033 and stop since. void dispatchEvent once anywhere keeps all that the global object leads to.
  • Reported by a user of the last commits and not fixed at ce10cc29, each seen on a build of the head's text, each program built alone and held against bun <file>. The build does not end in 40 s: a type alias that grows where it names itself, type F<T> = { inner: T } & { or?: F<{ inner: T }> } (6 programs). Type errors that tsc does not report stop the build: TS2322 at keyof (typeof rows)[number] over rows out of a .js file, TS7006 in a literal of type Crypto, TS2416 in subclasses of Blob and Headers (4 programs).
  • Found and not fixed at bb7d7922, each run on a build of a pushed pair or of the head's text. New with this branch: in an executable native.dlopen of bun:ffi is an accessor where plain Bun has a data member (stored as a known failure); one script that checks by Bun.build({ check: true }) and then compiles lowers on 32 MB of stack a thread, not 64. Silent: under bun test --aot the factory of vi.mock(), vi.doMock() or mock.module() runs inside the import() of the mocked module, where bun test runs it in a later job; module.exports = function () { return typeof this.children }; module.exports() answers undefined in an executable, with or without --aot, where bun <file> answers object; under "useDefineForClassFields": false one computed key that has to be folded makes all fields of the class defined; one data file imported under several types, one of them by a name made at run time, can get what another type made of it; node:http: code of the program that runs while a header's value is looked at comes too late, another object stored as req._readableState between a chunk and its event is not looked at, accessors above a server, a response or a request are called another number of times; streams and EventEmitter: an accessor under emit, write, push or __proto__ is called twice for once; under bun test --aot a module that one test file mocks partly hands its state on to a later file that loads it without a mock. Under bun test --aot, on mocks, in no text of this PR until now (114 small projects in eight families on a build of the pinned engine, against bun test by the same binary: 84 answer otherwise): old: the original of a mock that takes it (importOriginal, a mock without a factory, a spy) runs before the modules that its importer imports in front of it: import "./setup.js"; import { part } from "./part.js", where part.js reads what setup.js sets: mock real undefined for mock real true; old: once a mock of a CommonJS file has ended, a namespace that passes the file's names on by export * shows the names that the mock left out, with the file's values, and the file's body runs: kept,leftOut 1 2 for kept 10 undefined; a factory that imports another mocked module whose factory still waits gets the mock where bun test hands it the file (mock 1 < mock 2 for mock 1 < real 2); a mock laid over a mock whose factory has not answered reaches the load that began under the old one; old: vi.doMock("./part.js", () => new Promise((resolve, reject) => { fail = reject; })); try { require("./part.js") } catch {}; fail(new RangeError("late")): bun test reports the error and the test fails, compiled it passes. node:http, old: a listener that a program writes into res._events is not heard (close/1 for close/1,close/2,close/3). Loud: old: beside a mock whose factory never answers, a load that does not lead to the mocked module waits for ever under bun test --aot ((await import("./side.js")).y times out where bun test prints real side); in a folder without a tsconfig.json a build stops with TS2339 at request.body.text() where the request has a written type Request; the text of a function that holds JSX, an experimental decorator or import.meta.glob(), evaluated again, is a SyntaxError; require() of a conditional name with one written arm like "bun:os" and one unknown arm is refused; process instanceof a class whose Symbol.hasInstance lists the keys of holder.stdout ends the process ("was left out of the executable"); require() of a made name where the file is a symbolic link beside a folder of the same name throws MODULE_NOT_FOUND; under bun test --aot 97 more stored test runs on mocks fail where bun test passes (two factories that import each other's module, vi.importActual() inside a running factory, a mock over the mock of a preload, and others). Large, the answers right: a loop that fills a list up to a bound held in a variable asks for the room of the whole bound though it ends early (1,195 MB for 23 MB).
  • Fixed at b08c1788: a false build stop on a sloppy generator or async function in a CommonJS file that hands arguments on; a computed import() or require() no longer embeds Markdown, HTML or shell files that its written name does not ask for; five tests that were red on the head before.
  • Fixed at 649a797d, each with the program that showed it in the commit's message: computed imports and data files; an executable built again in one folder, which doubled or grew at every build; Workers started from files above the entry point; a test run with a preload that mocks a module, and mocks over a file whose first load threw; a UDP socket's handler that hands its chunk to JSON.stringify(); the kept text of static readonly [key]; an arithmetic overflow in the checker.
  • 236 test cases in 134 files are marked as known failures, not fixed. 170 of them are bounds on how much of Bun's library a small program brings along and on what --trim removes, 15 are on how builds grow with their programs, 51 are others. They are test.failing with the measured number and the bound in the name, or Todo: behind a stored program's bound, so each one runs every time and turns red the day it passes. test/aot/goals-not-met-yet.md lists them all; bun test/aot/tools/list-goals-not-met-yet.ts writes that page from the marks in the tree.
  • 4 more marks, in 3 files, stand on wrong answers, not goals. Three are on module mocks under bun test --aot (test/aot/mock-file-url.test.ts, test/aot/unmock-while-loading.test.ts); the fourth is the name of the error for a file whose import does not parse.
  • 57 cases in 18 files are test.todo and never run. 5 stored programs are skipped on one platform.

- src/aot: the lowering writes the program that the engine's new scan, link
  and walk read. Scopes are made where they are first needed, captured values
  are copied into closures where nothing stores to them later, straight-line
  blocks are joined, and CommonJS and ES module graphs are resolved at build
  time, cycles included.
- bun test --aot: module mocks, spies, hoisted factories, re-evaluated modules
  and --rerun-each behave as they do without --aot in many more cases. Names
  that tests add to built-in objects are tracked file by file.
- bun:ffi: no limit on the number of open callbacks in compiled programs.
- Built-in prototypes other than Object.prototype accept new names in compiled
  programs. Replacing an existing member is still refused.
- test/aot: about 29,000 more programs, each compared with the same program
  run without --aot.
@Jarred-Sumner
Jarred-Sumner requested a review from alii as a code owner November 4, 2026 19:44
@robobun

robobun commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator
Updated 12:04 AM PT - Nov 7th, 2026

✅ @Jarred-Sumner, your commit 1bbb52e2112015fcd3614402dcce061a0376f5d0 passed in Build #138175! 🎉


🧪   To try this PR locally:

bunx bun-pr 47942

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

bun-47942 --bun

Comment thread src/js/node/net.ts Resolved
Comment thread src/runtime/cli/Arguments.rs Resolved

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional findings (no inline location, or outside the current diff):

  • 🔴 scripts/build/compile.ts — Maintainers running the build-script type check get an error after this change, and a dead script stays in the tree. scripts/build/compile.ts:589 still reads opts.embedsCompiledLibrary and cfg.compiledLibrary, both of which this diff removes from LinkOpts and Config, and embedsLibrary is no longer used. Fix: delete line 589, and delete scripts/build/embed-compiled-library.ts, whose last caller this diff removed, so the removal of the compiledLibrary option is complete. [also at: scripts/build/aot-source-lists.ts:88 - Anyone who runs scripts/build/embed-compiled-library.ts after this merge gets a bun test that exits 1 on an unknown flag, which is a different failure from the TypeScript error already filed at scripts/build/compile.ts:589.]

    Why this was flagged

    This diff removes compiledLibrary from Config (scripts/build/config.ts) and embedsCompiledLibrary from LinkOpts (scripts/build/compile.ts:558-563), and removes every use of embedsLibrary in link(). scripts/build/compile.ts:589 keeps const embedsLibrary = opts.embedsCompiledLibrary === true && cfg.compiledLibrary;, which references two properties that no longer exist on their types and is never read. Under Bun's type stripping the line evaluates to false at run time, so ninja generation still works, but a TypeScript check of scripts/ reports unknown properties, and the base branch had no such error. scripts/build/embed-compiled-library.ts has no remaining caller after this diff (only its own usage string mentions it), which the repo review rules treat as dead code to delete in the same PR.

    Verification: Triggers on every pull request, because .github/workflows/lint.yml:35 runs bun x tsc --noEmit -p scripts/build/tsconfig.json. The diff removes embedsCompiledLibrary from LinkOpts and compiledLibrary from Config, yet scripts/build/compile.ts:589 still reads both; under strict tsc these are TS2339 errors, so the Lint typecheck step fails on this PR, whereas it passes on the base commit.

  • 🔴 src/runtime/cli/test/parallel/runner.rs:691 — Anyone building this merge gets compile errors in several Rust crates and the C++ bindings, beyond the already-filed path_watcher.rs:849 site. Conflict hunks kept both parents' definitions, or kept one parent's callers of a symbol the other parent deleted. Fix: re-resolve every conflict region of merge 1bf73f0 so each item is defined once and every reference resolves, which covers the 10 sites listed. Same pattern at 10 sites (src/runtime/cli/test/parallel/runner.rs:682, src/runtime/cli/test/Timings.rs:213, src/runtime/cli/Arguments.rs:2362, src/resolver/resolver.rs:4047, src/runtime/webcore/Blob.rs:1886, src/runtime/webcore/Response.rs:912, +10 more). [also at: src/jsc/bindings/webcrypto/JsonWebKey.h:77 - Anyone building this branch gets a failed C++ and Rust compile: the merge with main left both sides of several…; src/spawn/process.rs:1408 - Anyone building this merge gets a Rust compile failure: the merge of main into the branch restored pre-#47893/#47895…; +2 more]

    Why this was flagged

    HEAD 1bf73f0 merges 7f49a19 into main b9f2ec3; cargo build or the CMake C++ build is the entry point. runner.rs:658 and :682 both define struct RunningFile, :667 and :691 both define WorkerCommands (E0428); runner.rs no longer defines worker_ends_at_bail, which ParallelRunner.rs:19 re-exports and test_command.rs:2314 calls (base defines it at runner.rs:1153). Timings.rs:180-258 defines growth_of, is_heavy, largest_growth, growth_of_average_files, record_growth twice and :220 reads self.heavy_over, a field the merged struct replaced with workers (:40). Arguments.rs:2362 calls crate::cli::test::memory_of_job::parse but src/runtime/cli/mod.rs:84-86 no longer declares mod memory_of_job (base does). ZigGlobalObject.cpp:589-592 leaves checkIfNextTickWasCalledDuringMicrotask without its closing brace and :594/:602 define Bun::updateOnEachMicrotaskTick twice. On the base branch every one of these compiles. Fixing path_watcher.rs:849 alone leaves all of these; each needs its own resolution.

    Verification: Triggers on any cargo build of HEAD 1bf73f0 (merge of 7f49a19 into b9f2ec3); the Rust crate cannot compile. In src/runtime/cli/test/parallel/runner.rs, struct RunningFile is defined at line 658 and again at line 682; pub(crate) struct WorkerCommands is defined at line 667 and again at line 691, in the same module (E0428). This is a hard build break the base branch does not have.

  • 🔴 src/runtime/cli/Arguments.rs:2361 — Every bun test invocation will panic while parsing its arguments once the crate compiles, instead of running tests as on the base branch. The diff removes --aot-memory-limit from TEST_ONLY_PARAMS but Arguments.rs:2361 still calls args.option(b"--aot-memory-limit") unconditionally, and the clap table lookup hits unreachable!("{} is not a parameter.") for an undeclared name. Fix: delete the parse block at Arguments.rs:2361-2370 together with the unused aot_memory_limit field and memory_of_job::parse, or re-declare the parameter, so that no runtime lookup names a parameter absent from the command's table.

    Why this was flagged

    bun test with any arguments reaches parse_test_command_options (Arguments.rs:1321-1322, unconditional for CommandTag::TestCommand). Arguments.rs:2360-2361 runs args.flag(b"--update-timings") then args.option(b"--aot-memory-limit") outside any if. The diff deleted the only declaration of that option, parse_param!("--aot-memory-limit <STR> ..."), from TEST_ONLY_PARAMS (base Arguments.rs:1030). clap::Args::option (src/clap/lib.rs:486) calls self.table.find(name) (src/clap/comptime.rs:625-637); find (comptime.rs:441-470) returns only when a long name matches and otherwise ends in unreachable!("{} is not a parameter."), and the const-table fallback find_param_index (comptime.rs:148-181) ends in panic!("clap: no such parameter"). Neither is debug-only, so release builds panic too and the crash handler reports a crash before any test file is read. On the base branch the parameter is declared and bun test proceeds.

    Verification: Triggers on every bun test invocation. The diff deletes the only declaration parse_param!("--aot-memory-limit <STR> ...") from TEST_ONLY_PARAMS, yet Arguments.rs:2361 still runs args.option(b"--aot-memory-limit") outside any guard. ConvertedTable::find ends in unreachable!("{} is not a parameter.", ...) at comptime.rs:469. unreachable! is not debug-only, so a release bun test would abort during argument parsing.

  • 🔴 test/js/node/crypto/node-crypto.test.js:1836 — Users of node:crypto and webcrypto lose Node-compat fixes that main already shipped, and the tests that proved them are deleted without replacement. The merge dropped 413 lines from test/js/node/crypto/node-crypto.test.js and 155 from test/js/web/crypto/web-crypto.test.ts and reverted the matching source. Fix: restore main's versions of the crypto sources and the deleted describe blocks, so that once the build compiles the behavior of main (#47671, #47836) is kept rather than the branch's older code.

    Why this was flagged

    Main commit 01ec085 (#47671) added Node-compatible digest aliases, JWK checks and tests; the merge 1bf73f0 reverted them while keeping the branch's older code. src/jsc/bindings/ncrypto.cpp:1623 findDigestByName no longer consults the removed digestNames table, so names like "RSA-SHA256", "sha-256" or "sha512-256" that main accepts in createHash/pbkdf2/hkdf stop resolving. src/jsc/bindings/node/crypto/KeyObject.cpp:1316 drops the check that threw "JWK does not contain private key material" when a private key is created from a public JWK. src/jsc/bindings/node/crypto/JSSign.cpp:229 and JSVerify.cpp:273 drop the m_mdCtx null guard that returned ERR_CRYPTO_INVALID_STATE. The describe blocks "%s is taken" (ArrayBuffer/SharedArrayBuffer inputs), "encodings of DiffieHellman and ECDH" and "key_ops of a JWK" that asserted these behaviors are deleted outright, so no test will flag the regression once the compile errors from the same merge are patched over.

    Verification: normal — triggers whenever node:crypto/webcrypto is built from this merge: the merge commit 1bf73f0 resolved the crypto conflicts by dropping main's #47671 changes while keeping their callers. HEAD's findDigestByName (ncrypto.cpp:1623-1652) now only checks dss1, the six otherNames. 413 lines deleted from test/js/node/crypto/node-crypto.test.js with no replacement.

  • 🔴 test/cli/compile/compile.test.ts:7 — Thirty-eight test files fail to load after merging: they import describe and test from "../aot/listed" (or "../../aot/listed"), but no test/aot/listed.ts exists in the tree. Both merge parents lack the file and nothing generates it, so every importer, including test/cli/compile/compile.test.ts:7 and test/cli/aot-check-and-build-flags.test.ts, errors with a module-not-found before any test runs. Fix: add the test/aot/listed module the new imports expect (exporting describe and test), or revert the 38 files to importing from "bun:test".

    Why this was flagged

    test/cli/compile/compile.test.ts:7 reads import { describe, test } from "../../aot/listed";, added by this diff (the base file imported them from "bun:test"). git ls-tree HEAD test/aot/ has no listed.ts, listed/index.ts or similar, git check-ignore does not ignore such a path, and neither 1bf73f0^1 nor 1bf73f0^2 contains test/aot/listed.ts. A grep for aot/listed finds 39 test files with the import, including test/cli/aot-check-and-build-flags.test.ts, test/bundler/compile-trim-runtime-refused-options.test.ts, test/bundler/compile-aot-size.test.ts and test/js/node/http/probes/node-http-probes.test.ts. Each fails at load with a resolve error, so none of their assertions run on CI, whereas the base branch ran them from bun:test.

    Verification: normal — triggered whenever any of the 38 importing test files is run. The diff adds test/cli/compile/compile.test.ts:7 import { describe, test } from "../../aot/listed";. No module satisfies that specifier: Glob test/aot/listed* returns nothing. Each of the 38 files will fail at module resolution before any test registers. Base branch imported these names from "bun:test" and worked.

  • 🔴 src/js/internal/util/deprecate.ts:12 — Maintainers running bun run typecheck get errors in src/js after this merge: the merge kept main's function (arg: Arg) body while dropping the <Arg> generic from the signature. At src/js/internal/util/deprecate.ts:12 Arg is an unknown name, line 13 calls the zero-parameter default shouldEmitWarning with one argument, and src/js/node/crypto.ts:238 passes undefined where deprecated is now non-optional. Fix: restore the base signature (<Arg = void>, deprecated: ... | undefined, shouldEmitWarning: (arg: Arg) => boolean) so the crypto.ts DEP0198 caller still type-checks, and give src/js/builtins/CommonJS.ts:286 a key or make key optional again in builtins.d.ts.

    Why this was flagged

    The trigger is tsc --noEmit from the repo root (package.json:58 typecheck), whose tsconfig.json references ./src/js with strict mode on (tsconfig.base.json:22). On the base branch deprecate.ts declares getDeprecationWarningEmitter<Arg = void>(..., deprecated: ((...args: unknown[]) => unknown) | undefined, shouldEmitWarning: (arg: Arg) => boolean = () => true) and returns function (arg: Arg). The merge commit 1bf73f0 took the branch's signature (no generic, deprecated non-optional, shouldEmitWarning = () => true) but main's body, so HEAD deprecate.ts:12 references the undeclared type Arg (TS2304), deprecate.ts:13 calls shouldEmitWarning(arg) on a function typed with zero parameters (TS2554), and src/js/node/crypto.ts:235-244 passes undefined as deprecated and a one-parameter predicate, both rejected under strict. Runtime behaviour is unchanged because types are stripped, but the typecheck step fails where it passed on the base.

    Verification: Merge commit 1bf73f0 produced src/js/internal/util/deprecate.ts with the branch's signature and main's body. Arg is an undeclared name → TS2304 at line 12; shouldEmitWarning(arg) at line 13 → TS2554; src/js/node/crypto.ts:238 passes undefined for the now non-optional deprecated parameter → TS2345. lint.yml:51-57 runs src/codegen/check-types-of-modules.ts on every pull_request, and this check passes on the base.

  • 🔴 scripts/build/deps/webkit.ts:6 — Every build after this merges will stop downloading WebKit once the oven-sh/WebKit PR behind the preview tag merges or closes. WEBKIT_VERSION at scripts/build/deps/webkit.ts:6 is set to "autobuild-preview-pr-997-40597149", a preview release that GitHub deletes when its PR ends, as scripts/build/download.ts:319-336 documents. Fix: before merging, point WEBKIT_VERSION at the merged main sha or its autobuild-<sha> release so the prebuilt tarball stays downloadable; the base pins the 40-hex sha 4d45755fed4aeef19e95beb90d3d0c884a2812bd.

    Why this was flagged

    The pin at scripts/build/deps/webkit.ts:6 changes from a 40-hex commit sha to "autobuild-preview-pr-997-40597149". scripts/build/config.ts:64 passes it as webkitVersion, and the prebuilt download builds a release URL containing /autobuild-preview-pr-. scripts/build/download.ts:318-322 states that GitHub deletes the preview release when the WebKit PR merges or closes, and prebuiltDownloadError at download.ts:328-337 exists only to explain that 404 and tell the maintainer to edit this line. .claude/commands/upgrade-webkit.md:34 says the bump to the merge commit's autobuild-<sha> must be done before merging the bun PR. After this merges, every CI and local build that fetches the prebuilt WebKit fails with "WebKit preview release is gone" as soon as that WebKit PR ends, where the base pin keeps resolving indefinitely.

    Verification: Triggers once the oven-sh/WebKit PR #997 behind the preview tag merges or closes; after that every prebuilt-mode build 404s. The diff changes scripts/build/deps/webkit.ts:6 from the base 40-hex sha to "autobuild-preview-pr-997-40597149". Nothing in the diff repoints the pin, and the source-lint test (test/internal/source-lints/webkit-prebuilt-url.test.ts:183-185) accepts any autobuild-* tag, so no check stops the merge.

@Jarred-Sumner

Copy link
Copy Markdown
Collaborator Author

Don't merge this yet, and don't review the shared files at this head. The merge with main was made from the wrong base.

The first commit is parented on main as of Oct 27, but its tree already contains main up to 2b0ace8 (Oct 31). Merging from the older base produced hybrids in 103 files outside the compiler (28 in src/runtime, 26 in src/jsc). Among other things this head drops recent node:crypto and WebCrypto changes and their tests: ncrypto.cpp, JSCipherPrototype.cpp, JSSign.cpp, JSVerify.cpp, KeyObject.cpp, JsonWebKey.h, node-crypto.test.js, web-crypto.test.ts. It is also why cargo fmt and the source lints fail.

git merge-tree --merge-base=2b0ace8c6870 gives 8 real conflicts (bun_alloc/lib.rs, bun_core/debug.rs, crash_handler/compiled_frames.rs, jsc/compiled_program.rs, jsc/web_worker.rs, resolver/lib.rs, runtime/webcore/Body.rs, standalone_graph/StandaloneModuleGraph.rs). They are being resolved and the result built; it goes up as a new merge commit on top.

CI: every job that builds Bun also fails until oven-sh/WebKit#997's preview build is published.

Comment thread src/runtime/jsc_hooks.rs Resolved
Comment thread src/runtime/cli/compile_options.rs Resolved
Comment thread packages/bun-types/bun.d.ts Resolved

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings marked 🟡 are optional suggestions and need no follow-up push.

Still open from earlier reviews (3):

  • 🔴 src/js/ahead-of-time/node/_http_agent.ts:2 — Compiled (--aot) programs lose the keep-alive fix that plain bun run has: an idle pooled socket with unread bytes is ha…
  • 🔴 src/js/node/net.ts:2604 — Users of node:net and node:http lose the 'timeout' event for a write that waits on a connecting socket, which main alre…
  • 🔴 src/runtime/cli/Arguments.rs:750 — Test files still pass --aot flags this diff deletes from Arguments.rs, so those suites fail after merging while they pa…

If you have decided not to act on one of these findings, resolve its thread (a reply alone leaves it open) and the next review stops counting it. To review this commit again now, use Re-run on its "Claude Code Review" check.

Additional findings (outside the current diff — GitHub can't attach inline comments there):

  • 🟡 scripts/build/binary-expectations.ts — nit: dead code left by the merge. symbolsOfIRText at scripts/build/binary-expectations.ts:413 has no caller: binaryExpectations() at line 519 now calls the new testOnlySymbols (line 367) that supersedes it, and the Windows-shim doc comment at lines 404-408 is now stranded above the dead function instead of above shimExpectations. Fix: delete symbolsOfIRText and its doc comment so the shim comment again sits on shimExpectations.

    Why this was flagged

    The diff adds testOnlySymbols at scripts/build/binary-expectations.ts:367-403 and changes line 519 from symbolsOfIRText(cfg) to testOnlySymbols(cfg). A repo-wide grep for symbolsOfIRText finds only its definition at line 413, so it is unreachable. The build-script tsconfig has noUnusedLocals false, so nothing flags it. The doc comment for the Windows .bin/ shim at lines 404-408 now precedes the dead function rather than shimExpectations() at line 442. No runtime effect; a maintainer reading the file sees two overlapping forbidden-symbol lists and a misplaced comment.

    Verification: nit. Dead code introduced by this diff, which the repository's REVIEW.md explicitly asks to be deleted in the same PR. The diff for scripts/build/binary-expectations.ts rewires the only call site at line 519 from symbolsOfIRText(cfg) to testOnlySymbols(cfg), while leaving function symbolsOfIRText(cfg: Config) at line 413 untouched. No runtime effect.

  • 🔴 src/sql_jsc/shared/RowDecoder.rs:1 — Anyone building this merge gets a compile error in the bun_sql_jsc crate: the file is deleted but still declared and used. src/sql_jsc/lib.rs:33 keeps #[path = "RowDecoder.rs"] pub mod row_decoder;, and src/sql_jsc/postgres/PostgresSQLStatement.rs:9 and PostgresSQLQuery.rs:6 still import RowDecoder from it. Fix: restore src/sql_jsc/shared/RowDecoder.rs (the base's version, blob c01609af7) or remove the module declaration and move its users to whatever replaced it, so that every row_decoder reference in src/sql_jsc resolves. The base branch has the file and builds.

    Why this was flagged

    The diff deletes src/sql_jsc/shared/RowDecoder.rs (present at base 05a0ec9 as blob c01609af7fb1baed97c5432cd6039a1076f95601, absent in HEAD's tree and working directory). No other file under src/sql_jsc changes in the diff. src/sql_jsc/lib.rs:33-34 still reads #[path = "RowDecoder.rs"] pub mod row_decoder;, so rustc fails with a missing-file error for the module. src/sql_jsc/postgres/PostgresSQLStatement.rs:9 (use crate::shared::row_decoder::{RowColumn, RowDecoder, WireFormat};, used at :30, :47, :146) and src/sql_jsc/postgres/PostgresSQLQuery.rs:6 and :454 also depend on it. The base branch compiles bun_sql_jsc; after the merge any cargo build of the workspace stops at this crate. This is a deleted-file breakage, distinct from the already-filed conflict-hunk duplicates in runner.rs and path_watcher.rs: the fix is to restore the file or delete the module and its users, not to resolve a kept-both-sides hunk.

    Verification: normal — triggers on any build of the merged tree. src/sql_jsc/shared/RowDecoder.rs is deleted. Nothing replaces it. Yet src/sql_jsc/lib.rs:33-34 still has #[path = "RowDecoder.rs"] pub mod row_decoder;, and the module is actively used: PostgresSQLStatement.rs:9 and PostgresSQLQuery.rs:6. So rustc fails with a missing module file error (E0583) and the whole Bun build breaks at HEAD.

  • 🔴 src/bun_alloc/lib.rs:1560 — Debug builds of the bundler now carry a data race in the resolver's directory cache: a thread answering a known directory reads overflow-list counters that another thread is appending to. Since this change read_dir_info at src/resolver/resolver.rs:4632 calls at_index with no resolver lock, and the debug_assert! in OverflowList::at_index_mut at src/bun_alloc/lib.rs:1557-1560 reads list.used and the block's used, which put_unpublished (lib.rs:2363, under only the map's own mutex) writes at the same time. Fix: make every counter a lock-free reader touches atomic, as backing_buf_used was made here, or drop the counter reads from at_index_mut; the same read happens at lib.rs:1635 under the shared entries lock.

    Why this was flagged

    A project whose resolver has cached more than 2048 directories (DirInfo::HashMap is BSSMapInner<DirInfo, 2048, true>, so later slots go to overflow_list) is bundled by several worker threads in a debug build. Thread A resolves under a directory already published and takes the new lock-free path in read_dir_info: src/resolver/resolver.rs:4626-4633 calls dir_cache_mut().at_index(known.index) without RESOLVER_MUTEX or DIRECTORY_MUTEXES. at_index (src/bun_alloc/lib.rs:2280) goes to OverflowList::at_index_mut, whose debug_assert! at lib.rs:1557-1560 reads self.list.used and ptrs[block_id].used with plain loads. Thread B at the same time reads a new directory and calls put_unpublished (lib.rs:2324) -> overflow_list.append (lib.rs:2363), which writes block.used at lib.rs:1491 and list.used at lib.rs:1447 under the map's mutex alone, which the reader does not take. Before this change every at_index ran under the global RESOLVER_MUTEX, so no such unsynchronized read existed. Release builds read no counter in at_index_mut, so the consequence is confined to debug builds.

    Verification: Base held let _unlock = self.mutex.lock_guard(); at the top of read_dir_info (old resolver.rs:4438). The PR removes it; src/resolver/resolver.rs:4626-4633 now calls self.dir_cache_mut().at_index(known.index) with no RESOLVER_MUTEX. OverflowList::at_index_mut (src/bun_alloc/lib.rs:1552-1560) does plain reads of used that overflow_list.append (2363) writes under only the map's mutex.

The earlier merges of main into this branch were made from a merge base older
than what the branch's tree really holds, and took this branch's side wherever
the two differed. That dropped recent changes of main in files this branch has
no reason to touch, among them fixes and tests in node:crypto and WebCrypto.

This redoes the merge over the commit of main that the tree stands on
(2b0ace8), up to main at 05a0ec9. Outside the compiler's own
directories every file is main's again, byte for byte, except for the 347 that
this branch changes on purpose. Of the crypto, TLS and X.509 files that is one,
node_crypto_binding.bind.ts, which gains a declaration of what becomes of
randomInt's callback.

WEBKIT_VERSION names the preview build of oven-sh/WebKit#997 at 1a9ffc0a.

The tree is formatted: cargo fmt, bun fmt, clang-format.
@Jarred-Sumner

Copy link
Copy Markdown
Collaborator Author

The merge of main is redone in 4aff2f6, so the note above no longer describes the head of this branch.

What was wrong: the earlier merges used a merge base older than what the branch's tree really holds and took this branch's side wherever the two differed. That dropped recent changes of main in files this branch has no reason to touch, among them fixes and tests in node:crypto and WebCrypto.

What holds now, checked on the pushed tree:

  • Outside the compiler's own directories every file is main's (05a0ec9) byte for byte, except for 347 that the branch changes on purpose.
  • Of the crypto, TLS and X.509 files that is one: src/runtime/node/node_crypto_binding.bind.ts, +5 lines, a declaration of what becomes of randomInt's callback.
  • .github and .buildkite are main's.
  • WEBKIT_VERSION names the preview build of AOT: replace the flow passes and the B3 back end with a scan, a link and one walk WebKit#997 at 1a9ffc0a, which has every asset.

Expected to fail in this run, fixed locally, coming with the next push: three source lints, and 35 test files under test/cli and test/bundler that import a helper module that no longer exists.

Still not ready to merge.

The redone merge was made from this branch's first commit and so left out the
one line that c47b364 had added since. Without it the table of built-ins is
not generated, and nothing builds.
Comment thread test/aot/ci-lists.test.ts Resolved
Comment thread src/runtime/cli/mod.rs Resolved

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last 3 reviews of this pull request each found new blocking issues. Where they share a root cause, one fix may close them together.

Findings marked 🟡 are optional suggestions and need no follow-up push.

Still open from earlier reviews (3):

  • 🔴 src/runtime/cli/Arguments.rs:750 — Test files still pass --aot flags this diff deletes from Arguments.rs, so those suites fail after merging while they pa…
  • 🔴 src/runtime/cli/compile_options.rs:26 — Maintainers running the --explain tests get assertion failures after this merge: the allowed-values message changed but…
  • Also unresolved: 1 minor or pre-existing.

If you have decided not to act on one of these findings, resolve its thread (a reply alone leaves it open) and the next review stops counting it. To review this commit again now, use Re-run on its "Claude Code Review" check.

Additional findings (outside the current diff — GitHub can't attach inline comments there):

  • 🔴 test/parallel-allowlist.json:715 — Maintainers running test/internal/parallel-allowlist.test.ts get a failure after this merge: its "excludeFiles are real files inside listed dirs" test finds entries for test files this diff deletes. test/parallel-allowlist.json:715 still lists aot/bun-build-takes-aot-disable-and-aot-heat.test.ts, which the diff removes from test/aot, and at least five more deleted files stay in excludeFiles (lines 671, 861, 881, 1431, 1439). Fix: drop every excludeFiles entry whose file no longer exists in test/aot, or regenerate the list with scripts/update-parallel-allowlist.mjs, so the existsSync check at parallel-allowlist.test.ts:22 passes.

    Why this was flagged

    Running bun test test/internal/parallel-allowlist.test.ts on the merged tree. The test at test/internal/parallel-allowlist.test.ts:20-24 filters table.excludeFiles by existsSync(join(testDir, f)) and expects the bad list toEqual([]). test/parallel-allowlist.json:715 (inside "excludeFiles", which starts at line 285) names aot/bun-build-takes-aot-disable-and-aot-heat.test.ts, and the diff deletes test/aot/bun-build-takes-aot-disable-and-aot-heat.test.ts (present as a blob in base 05a0ec9). The same holds for the entries at lines 671, 861, 881, 1431 and 1439 (a-function-constructor-kept-under-a-computed-key-costs-the-analysis-a-fixed-amount, a-file-that-is-saved-while-the-layers-of-a-test-run-are-built, a-layered-test-run-goes-by-the-flag-cwd, a-build-that-holds-too-much-memory-is-asked-what-it-compiles, a-field-that-holds-undefined-outside-the-cell-fails-no-test), none of which exist under test/aot at HEAD. On the base branch those files existed and the assertion held. Nothing else in the runner prunes missing entries, so the test fails on every lane.

    Verification: normal — triggers whenever test/internal/parallel-allowlist.test.ts runs on the merged tree. test/internal/parallel-allowlist.test.ts:20-23 asserts expect(bad).toEqual([]). test/parallel-allowlist.json:715 still lists aot/bun-build-takes-aot-disable-and-aot-heat.test.ts; existsSync alone makes these entries land in bad, so the test fails. Result: a repository self-check test that passes on base fails after merge.

Jarred-Sumner and others added 4 commits November 4, 2026 16:10
Points WEBKIT_VERSION at autobuild-preview-pr-997-73da0369.

Tests. test/aot loses 1.5 million lines. A family of generated programs
now keeps a few witnesses in the tree, each of which has failed at least
once or is the only program that reaches some code; the generator that
makes the whole family stays beside them. test/expected-durations.json and
test/parallel-allowlist.json are regenerated for the test files that are
left.

Lints.
- cargo clippy: 22 errors fixed, and PROGRAM_FILE_FLAG is behind the cfg
  that its only user is behind, which also fixes the hardened x64 build.
- tsc -p src: seven type errors.
- unused_pub: 42 items that nothing used are deleted, 7 move under
  cfg(test). One stays until the engine's generator changes with it.
- ThreadPool.rs's test code compiles against main again, and miri finds
  the module it could not find.
- parts.rs, MemoryOfJob.rs, two environment variables nobody reads and
  three design notes are removed.

AddressSanitizer. test/harness.ts and test/aot/run-parallel.ts set Malloc=1
again, as main does. The branch had dropped both lines, so the ASAN lane
did not see WTF's fastMalloc memory.
test/internal/address-sanitizer-sees-wtf-allocations.test.ts is main's.

Compiler. The front end of the same tree as the engine. Together they fix,
among others:
- `if (path.startsWith(dir + "/")) .. else if (path.startsWith(dir))` took
  the first test's answer for the second
- `Bun.sleep(0).finally(() => { throw "late" }).catch(e => e)` lost the
  reason of the rejection
- `const { shown } = await import("./crate.cjs")` was undefined for a
  CommonJS file marked __esModule that also sets exports.default
- `require("./levels.ts").Level.High` folded the enum member and dropped
  the require, so the file never ran
- a `const` read in its dead zone across an ES module cycle and a CommonJS
  file read garbage instead of throwing a ReferenceError
- `for (; i < 2; i++) { if (..) break; .. }` was unrolled with the wrong
  final value of `i`
- a function reached through `Object.values(namespace)` and called before
  a `const` that it reads is initialized
- `require` shadowed by a parameter, or replaced through `given || require`
The conflicts are all in src/aot and all come from main's removal of
unsafe blocks:
- statistics.rs: mi_process_info takes Option<&mut usize>.
- engine.rs: Bun__AOT__initializeOptions is a safe fn that takes
  &mut CompileOptions. The fake that a test build calls in its place
  takes the same.
- engine/testing.rs, engine/tests.rs: bytes_of is gone;
  bun_core::slice_as_bytes, with the element type written out where it
  was inferred from bytes_of's parameter.
- compile.rs: the code that main touched is no longer in this file.
test/internal/source-lints/byte-search.test.ts refuses a scalar loop over
bytes where bun_core::strings has the search:
- load/modules_with_queries.rs: index_of_any for '#', '%' and '\'
- load/react_compiler/module_items.rs: index_of_char_usize for ':'
- lower/commonjs/variables.rs: contains_char for '?'
Comment thread src/crash_handler/report_file.rs Resolved

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last 4 reviews of this pull request each found new blocking issues. Where they share a root cause, one fix may close them together.

Findings marked 🟡 are optional suggestions and need no follow-up push.

Still open from earlier reviews (1):

  • Unresolved: 1 minor or pre-existing.

Additional findings (outside the current diff — GitHub can't attach inline comments there):

  • 🟡 src/install/PackageManager/CommandLineArguments.rs:89 — nit: two stale mentions of the deleted --aot-log-type-mismatches flag stay in src after this change removes it from AUTO_PARAMS. sweep:aot-log-type-mismatches The ignored-flags table at src/install/PackageManager/CommandLineArguments.rs:89 says it mirrors AUTO_PARAMS of Arguments.rs but still lists the flag, so bun install --aot-log-type-mismatches is silently accepted while bun run now rejects it; src/bun_core/output.rs:664 still matches it in compiles_in_place. Fix: drop the name from both lists, as the diff did for completions and docs. [also at: src/bun_core/output.rs:664 - nit: maintainers keep a reference to a CLI flag this change deletes. compiles_in_place at src/bun_core/output.rs:664 still matches b"--aot-log-type-mismatches", which the diff removes from Arguments.rs, compile_options.rs, the completions and the types.]

    Why this was flagged

    The diff deletes --aot-log-type-mismatches from RUNTIME_PARAMS_, AUTO_REQUIREMENT_ROWS and BUILD_ONLY_PARAMS in src/runtime/cli/Arguments.rs and from completions/bun.bash, bun.fish, bun.zsh and bun-cli.json. A repo-wide grep (no path filter) still finds the name at src/install/PackageManager/CommandLineArguments.rs:89, in RUNTIME_FLAGS_PARAM whose comment at :86-87 says it lists the no-value flags of AUTO_PARAMS, and at src/bun_core/output.rs:664 in compiles_in_place(), which keeps inherited file descriptors open for a flag nobody can pass any more. A user running bun install --aot-log-type-mismatches gets the flag swallowed instead of the unknown flag error that bun run gives; on the base branch both commands accepted it. Nothing else breaks; this is a dead-code cleanup the repository guidance asks for in the same PR.

    Verification: nit. Triggering condition: whenever anyone passes --aot-log-type-mismatches to an install-family command. src/install/PackageManager/CommandLineArguments.rs:89 inside RUNTIME_FLAGS_PARAM still lists a flag AUTO_PARAMS no longer has, so install-family commands silently accept it while run/compile reject it; src/bun_core/output.rs:661-665 compiles_in_place still matches it.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings marked 🟡 are optional suggestions and need no follow-up push.

The last 5 reviews of this pull request each found new blocking issues. Where they share a root cause, one fix may close them together.

Still open from earlier reviews (2):

  • 🔴 src/crash_handler/report_file.rs:46 — A compiled program that assigns process.env.BUN_CRASH_REPORT_TAG or BUN_CRASH_REPORT_DIR more than once leaks the previ…
  • Also unresolved: 1 minor or pre-existing.

If you have decided not to act on one of these findings, resolve its thread (a reply alone leaves it open) and the next review stops counting it. To review this commit again now, use Re-run on its "Claude Code Review" check.

Comment thread src/runtime/cli/test/ShardsByReach.rs Resolved
Comment thread scripts/runner.node.ts Resolved
…dead code

Lints:
- Every finding of mordant on the last push has its fix: 25 small refactors,
  three types that said one state in two or three fields (BunTypes, Getter,
  ImporterCopy), one enum in place of two bare bools, and `pub` items that
  nobody used, with two dead chains behind them (-184 lines).
- Four unit tests of bun_sema did work that grew as the square of their rounds.
  Miri ran one of them for over an hour. They ask the same at 100 rounds.

Tests of plain Bun that the last push turned red:
- Fourteen tests start programs stored in test/aot/parallel, some by names that
  they put together. Thinning that folder had taken 35 of them away.

Back to main's text, because the compiler does not need the change:
- How an ELF executable is written (clones, shared blocks, copies in the
  kernel, punched holes). `bun build --compile` without --aot wrote by it too.
- The vectorized kernels of node:path, the file sink's close, sleepSync, eleven
  sort calls, the JIT for the filters of plugins, src/bundler/options.rs, the
  opt-level of the React Compiler.
- The tests that set Malloc=1: an ASAN build takes WTF's memory from the system
  allocator by its build.

Fixed:
- `bun test --aot --shard=i/n` crashed where the system refused a thread. That
  share of the deal is now done on the calling thread, and the deal is the same.

Deleted:
- The crash report file (--aot-crash-report-dir, two environment variables).
- 2 MB pages for the memory of a build.
- The positions and names of Bun's own modules, which nothing read (-1,171
  lines of TypeScript).
- --aot-log-type-mismatches in two tables, five entries of runner.node.ts and 76
  lines of test/expectations.txt that named files that are gone.
- About 82,000 lines of stored test programs.
- Seven files of tests that held only the frames of a stack trace, or a feature
  that is gone.

test/expected-durations.json is main's text with a measured time for each new
test file.

test/js/node/http/node-http-fast-path-coverage-fixture.ts no longer replaces
process.nextTick: it tells a finish within one tick by its frame.
CI starts ./scripts/runner.node.ts directly. The last commit stored it as 100644,
so every test step ended with "Permission denied" (exit 126) before a test ran.
test/aot/tools/generate-turns-that-start-over.py lost its bit the same way.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants