Repository navigation
Conversation
…ut of lists and Workers' programs; WebKit preview 7f21d5ed [skip size check] - WebKit: autobuild-preview-pr-927-7f21d5ed. - Modules: bind_modules() keeps one set of stored __esModule marks for both kinds of module and reads a changed mark from the object. limit_exports() limits the export tables of faces as it does those of files. A name taken from a JSON import asks for the files between, so files that a package lists under sideEffects run. What `delete` removes from a namespace is bound on no object. - CommonJS: under "type": "module" a default import holds what the link captured, and takes its short way only where nothing stores to module.exports behind the end of the body. A file whose namespace may be looked at and whose text does not say all its names has its object. - Names of modules out of lists and tables: a walk of a list (forEach, map, filter, find ..) leaves the list as written only where its function is written in the call, has no rest parameter, does not find itself through `arguments`, a direct eval or its own name, and holds its third parameter as every other holder of the list does. `instanceof` hands the list on. A table that writes its own toString or valueOf is not followed. - Programs under a second tsconfig take what the application's load found: import.meta.main, which file stands for which, erased sites, folded constants. An imported require that is given a path from its own folder resolves from that folder. Which file of the application a file is goes by its path and its loader. - Classes: an unused class goes whole only where its heritage is proven; otherwise it leaves its heritage and one look at it, a computed key leaves ToPropertyKey, and a static member named "prototype" keeps the class. The transpiler records which classes assign their fields under useDefineForClassFields: false and the second parse reads that, with the build's own table of defines. Methods stored on the prototype of a constructor function are members of its layout. - Syntax: the parser says where no `;` can follow a bare `declare`, so `declare` is a name on the left of for-of, for-in and for await and in front of every kind of line break; a line break behind `global` is any of the language's. - Types: only callable built-ins join a function type; the type of a well-known symbol is symbol. - Workers: whether the files and texts that Workers start from are one program with the rest is decided behind the front end, ahead of the first run of the engine. The constructor tells the engine when other threads are ending. - macOS: operator new of every piece of a trimmed executable goes to mimalloc, and the ranges of its pieces are known to stack traces. - bun check: a ring of functions without annotations is resolved in linear time. - The build: a third list of the engine's sources, compiled for minimum size. - Tests: stored programs, generated tables and fuzz families for each of the above and for the engine's changes; a NaN of every pattern of bits through N-API, bun:ffi and the serializer into fields of every kind; definitions by descriptor; host objects that come back by some road; declarations that nothing uses; contextual keywords as names. Files that read how often compiled code calls the run time run on Windows. The large build of workers-from-texts-that-the-build-knows takes its turn among the others again. Program folders have short names; two tests join the fixed step; tests that pass on both Windows lanes leave the awaited list and two units join it; the engine's native tests run on Windows. - Docs: rows of docs/compiler/compatibility.mdx follow the above.
…off an ES module, and tests for the engine's changes; WebKit preview 617ed49e [skip size check]
- WebKit: autobuild-preview-pr-927-617ed49e.
- Modules: what kind of module a file is goes by whether a top-level await is written in it, not by whether one can be reached. A CommonJS file whose getter reads, off an ES module, a name that the ES module hands on from another CommonJS file keeps its object unless the copy and the name cannot be told apart.
- The loader's fetch hook asks VM::synchronousModuleQueue().
- Strings: the natives of node:path take the width of a substring's characters from its base's storage as it is now; a string that is used as a property key takes the storage of an equal key, of either width.
- Output: a build prints neither the hint about untyped code nor the counts of property accesses; the counts end the text report of --explain.
- macOS: --aot-symbols takes a file whose table of indirect symbols has an odd number of entries.
- bun check: the calls that were resolved meanwhile are one list.
- Windows: in place, the path that an import with { type: "file" } gives has the separators of the host.
- Tests: stored programs for each of the above and for the engine's changes: module variables of files that are still running, patterns without elements, Reflect.construct, super behind a delete, closures among many entry points, counted imports under a synchronous load, a callback that stores the answer of a call that never returns into a captured variable. Own members of compiled objects that native code looks for: RegExp test and search, the first line of a stack, Bun.inspect, concat, printed class names, labels of heap snapshots. Sums over typed arrays. A family for traps that are stored on the handler after the proxy is made. Four units run the engine's flow tests under 32 other schedules of the rules. The reference run of the packages takes the reference's environment. Five more units run on every commit on Windows. The three programs that terminate a Worker inside a loop count the walks that came to their end, in place of a clock. The tests of --trim are skipped where the Bun under test cannot trim, and the one on the allocator also runs with glibc. highway-strings.test.ts lists what is wrong and calls expect() once a test: the same cases in half a second. One-loop rows for numbers that leave their slot. A way out of native code that the system refuses is left out of both runs of the N-API test. A test of lint.yml's kind runs check-unified-sources.py. Sabotages for calls that throw without a site and for bounds that are read twice. Pins of generated code that fell are restated. The pins and rows of objects of constructor functions that are not built are as they were before that step of SinkAllocations.
- Docs: a row of docs/compiler/compatibility.mdx for useDefineForClassFields under a nested tsconfig.json, and its table of platforms says what CI runs on each; a paragraph of docs/compiler/internals.mdx on calls without a site.
…30-64483dcf [skip size check]
…, the program named at start on Windows; WebKit preview-pr-930-3fdfc9b1 [skip size check]
…named without its extension; typed rows of a query; WebKit preview-pr-930-bf1133d2 [skip size check]
…each executable behind its runs; each CI step names the build it tests; the Mach-O writer hands out owned parts of a mapping; bun check keeps the members of a named union; WebKit preview-pr-930-0cb47ca7 [skip size check]
…un without debug information that the build step uploads beside bun-profile; test/aot/fuzz and its test as in 5319f52 [skip size check]
…unction made by bind() of a proxy and what is read through a proxy are followed; tests for modules of both kinds in a ring; the runner gives the compiled programs a job for each processor, deals the dearest first and runs the quick test files in a bucket of their own; the watcher of memory has a thread of its own; WebKit preview-pr-930-c2d259b5 [skip size check]
…ld that asks nothing of the library reads no file of it; process.send and Bun.spawn bring the channel in only where they are called; children and parent of a module object that goes out of sight; a moved range keeps what follows it in its last page; the runner deals the compiled programs by all their files, and the dear ones share one library; WebKit preview-pr-930-02becc12 [skip size check]
… all modules run in the cargo lane; every lane builds the light programs alone; under ASAN the compiled programs run on a Bun without debug information; a following round is asked for only where it keeps more; a frame names a function made under a symbol key by its own name; the generators of the fuzzer are sources of run.ts; WebKit preview-pr-930-430afb3a [skip size check]
… traps; the printer is loaded for a printed value only where a class that needs it may have an instance; start-up functions are handed over by need; internal/fs/streams no longer loads node:fs/promises; a list that grows by push is checked in linear time; the tests build a trimmed and a whole executable in one build [skip size check]
…at it imports; the shards are dealt whole builds; long test files run beside one another under ASAN and on Windows; the texts of the library read the members of Object, Buffer, Reflect and Bun in one order; a list that grows by push is checked in linear time whatever lies between the pushes [skip size check]
…o ask for; the exports of a module of the library are handed to nobody new as the receiver of a member call; pushes with branches between them are checked in linear time; tables that only a build reads are made at first use; one compile writes the whole and the trimmed executable in two test files [skip size check]
…that cost the plain files their form [skip size check]
…described by their paths; a star or dynamic import of a library module brings what its getters load; a call of what is no function throws wherever it stands; a name out of a list that was changed in place counts as unknown; the long test files run two at a time on the Linux lanes; every test of the compiler runs on Alpine, the other lanes run those that ask the system, and the lane with a sanitizer leaves out those that compile one text again; an executable built on a PE template chooses its cuts anew; dependencies that no code of a crate uses are gone [skip size check]
…hem; three programs run half their turns; a test file that says it runs alone stays out of the bucket [skip size check]
…piler run on a copy of Bun without its debug information, and what fails is tried again on the whole file; bun check --timing prints its queries, and the test of long rings counts them [skip size check]
"classes with a method of one name that each call it on an instance of any of them" has had its growth in instructions and in two counts in budgets.linux-x64.json. Its growth in memory stands beside them now: macOS judges it, where Linux leaves it unjudged below 96 MiB. [skip size check]
src/exe_format/macho.rs: the loop that hashes and writes pages calls `SHA256::hash(page, hash)`, as on main. [skip size check]
…ative plugins run on every lane - readMembersThatMakingStandardErrorReads(): the two lambdas are static functions with a ThrowScope of their own, and each call site checks or releases. - scripts/runner.node.ts: the three test files that compile a plugin with the system's compiler and load it are among the tests that ask the system, so they run on every lane. - test/aot/files-written-while-bundling.test.ts says why its plugins wait. [skip size check]
On macOS, an executable made by `bun build --compile` started a detached thread at boot that loaded the system trust store when a flag in the module graph said the program might ask for it. Under `--aot` the flag was set by reachability; otherwise it was set by searching the output for the text `getCACertificates`. This removes the thread, the flag, both places that set it, and the test for the flag. The system trust store is loaded on first use again, on the thread that asks for it. [skip size check]
They checked that Security.framework was loaded before the program asked for the system certificates, which no longer happens. Also updates a comment in node:tls that described the prefetch. [skip size check]
…IR tests [skip size check] Pins WebKit to the preview of oven-sh/WebKit#934. - Merges main. - Wrong answers and compiler stops found by fuzzing, by the run-time checks and by CI are fixed, each with its reproducer under test/aot. - Under --minify-identifiers a class with standard class decorators keeps its written name. - The compile object of Bun.build() takes aotValidate, aotCheck and aotTesting, hidden like aotDisable and aotHeat, with the meaning of the command line's flags. - --explain names where an escape begins. - The React compiler's lowering reads every segment of a joined string. - A compiled hello world starts with fewer instructions, page faults and system calls. - mimalloc moves to 6da53c35 and takes no patches. - The directives of an .ir test are checked strictly: a misspelled or misplaced one is an error. - The test runner leaves the engine's shell, its test program and its tests packed in a shard that starts none of them, and unpacks with tar on Windows. - The checks that read the sources are removed, with their two steps in lint.yml and the aot_check build rule. - scripts/vs-shell.ps1 keeps variables of several lines away from the Visual Studio loader.
…of CI found
The engine is autobuild-preview-pr-942-e2ad53a2.
Builds:
- Windows x64 did not link: "trim: the first link failed: duplicate symbol". The second link
put the compiled objects in front of the archives. lld-link resolves a weak alias of a
bitcode file (664 vector deleting destructors in JavaScriptCore.lib, `__global_delete`) from
what it read when it parsed the bitcode, so without the bitcode the name was undefined, the
archive's index offered it, and the bitcode member loaded beside its own compiled object.
The driver now asks first whether any such name exists. If so, the second link takes the
first link's inputs, as it did before. Only x64 has C++ as bitcode; arm64 is unchanged.
link-coff-freestanding.test.ts has a case that fails in the same words without Windows.
- Windows did not compile: E::EDQUOT is asked for on unix alone.
CI:
- scripts/runner.node.ts kept a list by hand of the tests under test/aot that need jsc and
testaot unpacked. Three new tests were missing from it and failed whenever they landed in a
shard with no listed test. The runner now follows a test's imports; a new test holds that
search against Bun's own import scan. Nothing changes for tests outside test/aot.
- Five findings of mordant: three functions generic over closures, a field that is valid only
beside another, a public method nobody calls.
Fixed since the last commit, each with a test:
- `require.cache` reloads: a file taken out of the cache and required again is run again.
- `p.versions = 5` on `node:process` is refused however the program came to `process`.
- `import { x }` of a malformed module segfaulted.
- `class Thumbnail extends Bun.Image { label() {} }` lost `label`.
- Two programs with a cycle of ES modules and CommonJS files failed at start.
- `import(`a${a}`)` took in every installed package whose name begins with "a", and one file
among them that did not parse failed the whole build.
- `bun build --compile --aot` takes one pass of the checker again; `bun test --aot` takes two
only where the memory limit asks for it. The output is the same at one, two and three.
Tests that were wrong, not the product:
- Two programs counted refills of an allocator as calls into the run time, which a build with
the address sanitizer makes more of.
- Two programs printed positions inside built-in functions, which a Bun with assertions
records and a release Bun does not.
- calls/host-getters-and-string-builtins-value.js: plain Bun itself gives two answers,
`'action()'` or `'f(a)'`, by how far its DFG thread has come. The program turns the DFG off.
- every-image-of-an-executable-has-its-map: its witness function is inlined away now.
Known to be wrong in this commit; each is fixed by an engine file younger than this engine:
- The compiler overflows its stack on typeorm's SelectQueryBuilder.buildWhere.
- `Reflect.ownKeys(f).filter(..).map(..).join(",")` aborts the compiler in InlineCalls.
- `"one" in barrel` behind `__exportStar` fails an assertion in builds that have them.
- Test runs over layers stop in builds that validate exception checks (placeInRealm).
…s and packages, mocks that change a file's branches
Moves the engine to autobuild-preview-pr-942-545a0509.
Known in that engine, and out of the next one: `let k = -1; for (const x of v) { k++;
if (x > g) { g = x; other = k; } }` over an Int16Array of 8 or more elements gives the last
index. Plain `bun run` is right.
Builds:
- Windows x64 did not link. The trim step links what the first link compiled from bitcode a
second time, as files in front of the archives, and 664 weak aliases that lld-link resolves
from bitcode came in twice (6,976 duplicate symbols). The second link takes the bitcode again
where a name would load it twice, and the aliases are read out of the bitcode's own symbol
table (scripts/build/trim/bitcode-symbols.ts).
- Windows arm64 did not compile: E::EDQUOT is spelled DQUOT there. Asked for on unix alone.
- cargo clippy (needless_pass_by_value in loads.rs) and six findings of mordant.
- CI unpacks jsc, testaot and the engine's tests for whatever test imports them. The list kept
by hand in the runner is gone.
Crashes fixed, each with a test:
- `Object.setPrototypeOf(import.meta, { inherited: 1 }); import.meta.resolve("./x.mjs")` was a
segmentation fault that no try catches. It is a TypeError, as in `bun run`.
- `where.map(item => this.buildWhere(item)).filter(..).map(..).join()`, a method that maps a
list with itself at the head of a chain, overflowed the compiler's stack.
- `Reflect.ownKeys(f).filter(..).map(..).join(",")` aborted the compiler in InlineCalls.
- `"one" in barrel`, where barrel is `__exportStar(require("./leaf"), exports)`: an assertion.
Wrong answers fixed, each with a test:
- `new Pool(new URL("./w.ts", import.meta.url))`, `pool.start(url)`,
`new Configured({ filename: url })`, `require("./spawn").start(url)`, and the same out of
node_modules: the file of the Worker is followed to the `new Worker(entry)` and compiled.
- `export const level = 1; export const pick = () => level === 1 ? "one" : "other"` and
`mock.module("./m.mjs", () => ({ level: 2 }))`: the file had decided its branch in the build.
- `writeFileSync(path, 'import { one } from "./lib.mjs"'); await import(path)` in a test, where
lib.mjs is a compiled file of the run, said "Cannot load".
- One file hands out `new Proxy(WeakMap, {})` and another stores `proxy.prototype.get = f`:
refused over layers, a plain store in `bun test`.
- `const made = { a: i }; try { slots[i] = made } catch { .. }` at a full heap: the catch got the
error of the allocation in front of its try.
- `module.parent = 7` in a package outside the executable; a file that rebinds `module`, deletes
`module.exports` or stores into `module.parent`.
- `process.versions.node = "mine"` in a .cjs file was dropped without a word. It throws there
too, as it did in a module.
- `enum Mode { Fast = 1 }; const mode = Mode.Fast` is the constant that `bun run` makes it.
- `class Holds extends [Maybe, Other][n] { #number = 1 }; new Holds(new Holds())`, where the base
returns what it is given: the second initializer throws, as in `bun run`.
- A cycle of private functions is entered by the same function whichever is asked for first.
Test runs:
- A run reports the peak memory of each of its processes. On Linux getrusage() gives a spawned
process its parent's peak as a floor, so the peak is read from /proc/self/status.
- The second pass of the checker over every file of a large layer is out again: it cost 270 MB
and 7 s. With it goes its test.
Faster, no answer changes: `require("./data.json")` in a loop (426 > 138 instructions),
a Map or Set written out in the program and asked in a loop, `list.map(f).find(g)`,
`holder[key] = value` where the name was added late, `new RegExp("^(?:" + words + ")$").test(w)`,
16-bit texts against patterns made at run time, a small function called in a loop of code that
runs once, `bytes[k] = i + k`, the text of a number asked for twice, 109 questions fewer at the
start of every executable.
Tests: three generated batteries are stored programs now (74 files, 4,084 cases): an object
that gets a second name by one of 191 roads, values compared twice, objects that escape on one
path. A program of five million operations takes the path for very large programs at default
flags. Listings of generated code follow the engine.
… time, CI's failures
WEBKIT_VERSION is autobuild-preview-pr-942-f61fbc65. The engine's commit says what it fixes:
a build that never ended, three crashes of test runs, JSON.stringify of a list that holds
undefined, objects that were kept alive. This is Bun's half of those, and what is Bun's alone.
Wrong answers fixed, each with a test:
- `console.log(process.stdout)` printed `{}` in an executable where `bun run` prints the
stream. The same for process.stderr, process.stdin and console._stdout.
- `import { db } from "./data.sqlite" with { type: "sqlite" }` was a build error, and
`import * as ns` lacked `db`.
- A name that two imports bind means the last import, as in `bun run`.
- `require("./names/" + [".hidden.js"][0])`: a file whose name begins with a dot was not found
out of a list of names.
- `await import("./data/" + name)` of a .json file with a `//` comment says what `bun run` says.
- Under a tsconfig.json whose list takes in Same.ts and same.ts, a main.ts that reaches neither
did not build (TS1149).
- `import a from "./data.json"; import b from "./data.json" with { type: "json" }` in one test
file stopped the build under the verifier.
- With `--define`, `f.toString()` and error quotes read as in `bun build --compile`:
`if (DEFINED) dead()` is `if (false) ;`, and a define of any kind prints as its value.
- A type without end as a member's type, `type Endless<T> = { next: [Endless<[T]>] }`, no
longer keeps the build from ending.
Build time and memory, no output changes:
- `bun test --aot` beside very large package files: the step in front of loading took 90 to
125 s where it had taken 8 to 13. Each of about 460,000 questions walked the same variables
again. Each variable and parameter of a file is walked once.
- A chain of 3,000 untyped functions: 677 G > 7.5 G instructions. It grew with the cube.
- 4,000 functions on one line after non-ASCII text: 24,855 M > 8,472 M.
- Rows of constants: an expression that folds was still lowered whole, so that a name in its
dead zone could throw. A name that has given way to its constant's value cannot throw.
3,734 M > 2,658 M and 4,748 M > 2,958 M.
- Writing an executable: what lies behind the payload in the carrier was written in one call
out of the mapping, so all of it stayed resident. A one-line build with a 359 MB carrier
peaks at 84 MB where it took 273. `bun build --compile` without --aot runs the same code:
22 MB where it took 218, the same bytes written.
- A CommonJS file that ends in `module.exports = exports.default`: the ring through the alias
is entered from the first file in order, so the answer does not hang on the order of tasks.
Building Bun:
- testaot knew no locale on Linux. The prebuilt ICU's data are compressed, and ICU asks a weak
hook to decompress them. Bun and jsc define it, testaot did not, so
`Intl.NumberFormat.supportedLocalesOf("de-DE")` was []. Bun's build gives it the hook.
- scripts/build/trim/archive.ts: `toSorted` is not allowed by the lint.
Tests:
- Tests that judge memory print what they read and their bound when they pass too.
- Rows that musl is forgiven are no longer printed beside a real failure, so a log that showed
six failures shows the one.
- Six stored tests that read process.env stay off Windows, where flow rightly leaves that
object out. Six programs in which Windows x64 keeps an object alive are marked todo there.
- `one-line-changes-little`: one line is listed as known. Nothing grew: the base program
shrank by 1,417 bytes, and the bound is a share of the base.
- Two node:http programs that failed now and then on macOS send again.
- 1,695 more generated cases: what a file asks about itself, and which file of a package loads.
- Listings of generated code follow the engine.
…__dirname, room set aside for test files WEBKIT_VERSION is autobuild-preview-pr-942-e01a9a37. The engine's two commits say what they fix: four silent wrong answers, a crash, mocks of data files, the depth of JSON.stringify, an abort under FailPredictions. This is Bun's half of those, and what is Bun's alone. The compiler crashed, since the commit before last: - `const isDev = false; const url = isDev ? "http://localhost" : "https://example.com"; console.log(url);` at the top level of a file ended the build with a segmentation fault. What `bun run` prints in place of what is written is told to the engine as pieces, and `a ? b : c` with `a` known is a piece over all three that copies the range of `b`. A piece that would copy itself is not written. - The front end reads its finished lists of pieces and cuts again, for every module of an executable. A piece that lies in what it copies stops the build with the file's name. - `const F = false; const b = F ? 0x10 : null; function one() { return b.x }`: the error quoted '16null.x' for 'null.x'. Executables, `bun build --compile` is the measure: - `fs.readFileSync(path.join(__dirname, "assets", "data.txt"))` was ENOENT. `__dirname` and `__filename` were lowered as import.meta.dir and import.meta.path, so they lay under /$bunfs/root. They say where the file lay when the program was built. - Behind that, a compiled Next.js server answered 500 to every request. `require(path.join(__dirname, ".next/server/middleware-manifest.json"))`, import() and require.resolve() of an asset by its build-time path find it, in place and moved away. Where a program replaces `Module._resolveFilename`, what the hook answers is translated too. - `require(" ./parts/two.js ".trim())`, `.replace("xxx", "two")`, `.replaceAll()`, `"./parts/".concat("two", ".js")`, `.slice(1)` and `String("./parts/two.js")` were MODULE_NOT_FOUND. The scan reads a name that a string method makes of a known text. - `a[300_000_000] = 1; a[0] = 2; a.sort(f)` ran out of memory: the scratch list was as long as the array. It grows with the values that are found: 18 MB. - `{ function inABlock(a) { return a + 1 } String(inABlock) }` answers `function(a) { .. }`, and `mock(inABlock).getMockName()` answers "". Test runs, `bun test` is the measure: - `const { mock } = require("bun:test"); mock.module("made-up", () => ({ v: "mocked" }))` in a preload, where no package or file has the name: `import { v } from "made-up"` said ENOENT wherever no import statement names `mock`. - `import x from "./x.json" with { type: "json" }` in a test file and in its helper were two objects beside another file's plugin, and over layers with no plugin at all. - `try { require("./mixes.cjs") } catch {}` where mixes.cjs holds an import statement beside `exports.v =`: `bun test` catches a BuildMessage and goes on. The compiled run printed TS100106 and ran no test. All nine mistakes that the transpiler finds when it visits a file are left to the loader. - `import.meta.resolve("made-up")` of a name that only a mock has throws over layers too. Memory of a test run: - `--aot-memory-limit=5GB` held by luck: a build inside a test takes 2 to 2.6 GB within five seconds, faster than anything that reads what is resident can answer. A test file starts only where what is taken and what the file took last time fit under the limit. - The watch over a build's memory asks the build before it ends it, as the time limit does, so the log says which function and pass it was in. Build time, no output changes: - A build that starts Workers loaded every file, searched them for `new Worker(file)`, threw the load away and loaded everything again. The load itself asks, once, for test runs and for executables that start node Workers. - One file of 9 MB beside 5,000 small ones: a second thread prepares it while the first checks it, and no tree is packed away once a thread finds nothing to take. - No tree is packed once every file is lowered. - The threads of a build take turns at giving memory back. - The test run says when it reaches each step. Executables that held too much: `process.stdout.write("x")` beside `console.log(error.name)` of a caught error compiled 497 functions for 169. The small stream's target has a getter and no printer. Building Bun: - `linux x64-hardened` did not link since the previous commit: the hook that lets testaot read the bundled ICU's data was compiled without that build's flags. It is built with them. The same hook on Windows. - A Bun built with `--aot=off` defines all that the bindings' header declares, and a test holds each of its 33 declarations against that half. - What Linux alone measures in the parallel test runner is expected to lie idle elsewhere, so macOS and Windows compile again. - clippy and the lock lint: std::sync::Mutex is not allowed here. Tests: - Three front-end listings follow the rows of constants of the previous commit (cargo-test). - compile-trim-locales reads its locale from the arguments. - Where Bun has assertions, `Crypto.prototype.subtle` is left out of the writes on accessors (`main` aborts there too), and the bytes of a comparison are not held to a bound. - A leak that ended a part's builder under ASAN. - The harness's `setDefaultTimeout(120_000)`, meant to raise bun:test's 5 s, lowered the 270 s that CI's runner gives a case on the ASAN lane. It never lowers what the runner gave. - carrier-with-much-behind-the-payload asked for an exact file length. Where something stands behind the old section header table, its 2,752 bytes stay in the file. - The witnesses of the wrap at 2 ** 31, upward and downward, are stored programs. - Listings of generated code follow the engine.
…ic on one file spelled two ways Merged with main at 34cbc70. GitHub's Rust checks build the pull request merged with main, and that did not compile: main's node.rs asks `get()` of the cell that our hir.rs wraps. `LazyCell` has `get()`, and `struct Checker` holds the fields of both sides. mordant reports main's own `walk_bun_glob` (src/glob/lib.rs) as an unused `pub` on every pull request since #47513: nothing outside its file names it. It is there to instantiate the walker's methods once, so it stays, and `pub(crate)` would make it dead code. The finding is allowed on it, under cfg(mordant) alone, as src/jsc/bindgen.rs does. No build changes by a byte. WEBKIT_VERSION is autobuild-preview-pr-942-fbdf8d40. The engine's commit says what it fixes. This is Bun's half of that, and what is Bun's alone. The compiler stopped: - `require(short ? "./a" : "./a.cjs")` spells two names and may load one file. In a file that a test takes out of `require.cache` the build panicked ("range end index"), and no test of the run ran. In an executable, one file spelled four ways in one `require()` aborted the build. Three places read the list of files with a range of the list of names. - `new Number(5).toLocaleString("en-US")`, and the same of a class below Number, was an internal compiler error. Executables, `bun build --compile` is the measure: - `/** @import inside (@hit()) */` above a function of a .js file ran `hit()`. A sentence in a comment of `less`, "@import inside @-rules (@media, @supports, etc.)", threw "media is not defined", so `require("less")` died at start. A decorator that no class follows becomes a statement in front of the one being parsed, and a mistake in a comment of a JavaScript file stops nothing. What the parser collects inside a comment is dropped with the comment. - `Module._extensions[".js"] = function (mod, filename) { .. }` threw, so `pirates`, and through it `@babel/register` and `sucrase/register`, died at start. The store goes through. A file of the executable never asks the loader, as in stock's executable. - `import text from "./data.json?raw"` gave the parsed object, and `import("./code.mjs?raw")` and `require()` of it ran the file. A path with a query is a module of its own, named with the file's ending, and the loader was picked from that name. The query is asked first. `bun run` is the measure here: stock's executable resolves no path with a query. - On Windows, `new Worker("data:text/javascript," + ..)` with CommonJS text read `__filename` as `data:text\javascript,..`. A text that is no file lay nowhere when the program was built. - What an error message quotes of a condition that folding decides is the operand that `bun run` prints of it. Test runs, `bun test` is the measure: - `writeFileSync(join(import.meta.dir, "state.js"), text); await import("./state.js")`, the first load of state.js in the run, ran the text of the build. A file is compared with the disk before it first loads, whoever wrote it. - `Module.prototype.require = function (id) { .. }` (require-in-the-middle) and `Module._resolveFilename = function (request) { .. }` (module-alias, tsconfig-paths) were never asked for a `require()` of a constant name or for `require.resolve()`: the wrapper patched nothing, silently. - `import os from "node:os"` behind `mock.module("node:os", () => ({ EOL: "x" }))`: the default is the mock's, for ten built-in modules in five forms, behind a preload too. - `bun test --aot --parallel=2 --cwd=sub` over layers: "Could not change directory to "sub"", and no test ran. The build of a layer starts where the run was started. - With `--parallel=1`, or one test file, `bun test` starts no worker. A run over layers handed the files to one and ended like a run of many: exit 0 for 3 after `process.exitCode = 3`, exit 0 for 1 for an inline snapshot that cannot be written, no `snapshots:` line. - `Object.getPrototypeOf(function* () {}).added = 1` in a test file threw over layers and went through as one program. The same for async functions and async generators. - `import "./bare.cjs"` of a script without `require`, `module` or `exports`: behind `import`, `bun test` goes by the package's "type", so the file is an ES module, strict, and runs behind the CommonJS file beside it. Over layers it was CommonJS. - `import * as math from "./math.ts"; (math as any).double = f` anywhere in a test file was TS2540, and every test of the file was lost. That refusal is `bun build`'s. It throws a TypeError where it runs. - `globalThis.__lookupSetter__("self").call({}, "x")` over layers did nothing and said nothing. It says that it is refused. An executable built with `--aot-validate` died before its first line where a callback leaves sight of the compiler in the frame that made it. Build time and memory, no output changes: - `class C1 extends C0 { f1 = 1; m() { return super.m() + 1 } }`, 2,000 in a row: the members of a class were a list with a copy of every member of every class above, 2 M copies. A member is found by its name. Asking whether a class has a base went up the whole row each time. - `type Reversed<T> = T extends [infer F, ...infer R] ? [...Reversed<R>, F] : []` over tuples of up to 100 elements, one to a file, was accepted in one checker pass and refused in two. Speed: - `const b = [...xs]` where xs is proven to hold integers: the reads of `b` asked for the storage of fractions, a test that never passes, so every turn took the slow path. - Spreading a list of fractions boxes an element in 8 instructions for 10. Tests: - cargo-test did not compile bun_aot's tests: two of them build `EngineOptions` by hand and lacked the new field. - All 85 files of test/aot that set a default time limit under the 270 s of CI's ASAN lane go through one helper, which never lowers what the runner gave. A test fails if one comes back. - require-cache-in-test-runs ran its 47 cases one behind another and met the file's limit under ASAN. They run at once and are dealt out over four files: 156 s for the slowest, of 360. - 84 commands at once, 63 of them builds, held 75 GB under ASAN. As many at once as processors. - The harness's memory guard has two reasons to end a run and named one. It says which. - Two tests proved that compiled code ran by writing over a file and importing it, which now rightly loads the new text. They go by `BUN_JSC_crashIfInterpreterIsUsed`. - "a later test file imports the file" expected two files of one sub-folder in the order of their names. `bun test` takes them as the disk lists them, and tmpfs lists the newest first. - A stored program with ``import(`a${a}`)`` took in every package above it whose name begins with "a": 4,603 modules, 458 s, 2.7 GB. CI's runner starts the driver beside `bun install`, and only the Macs' install was fast enough to lose that race. The program asks nothing of what is above. - carrier-with-much-behind-the-payload looked for the tail from the carrier's length on. A Bun that trims makes an executable shorter than its carrier. - Miri runs the unit tests of the packed lists, and an entry that names no test is red. - Flags that come off the wire go through their checked constructors (mordant). - Stored programs: objects grown by computed keys and spreads, 105 scenes; text + number under collection stress; a build-scaling shape for sums of fractions alive past calls. - Listings of generated code follow the engine. [skip size check]: the compiler is in the one binary. Bun grows by 2.6 to 3.2 MB on seven targets and by 0.9 MB on Windows x64.
…ft, what was red in CI
This is the previous commit's tree and only what fixes a failing test or a review finding. The
engine is the same.
The compiler stopped:
- `const x = ["a", , "c"][1]; console.log(x);` ended in "internal compiler error (LinkAndEmit)".
A hole's expression ends one byte before it starts, and a length was taken by subtracting. Since
the previous commit.
Compiled programs:
- The first read or write of `process.stdout` or `process.stderr` threw EMFILE when the process had
no descriptor left. The light streams open a sink, which duplicates the descriptor. Where that
fails they now write to descriptor 1 or 2 itself, as the whole stream does.
- `process.nextTick()` under an `async_hooks` init hook calls whichever copy of `node:async_hooks`
enabled the hook. No build we can make today has two copies, so this changes no answer yet.
- `bun test --aot` aborted on a host that had reached its limit of threads. It goes on with the
threads it has.
Test runs:
- `bun test --aot --parallel=1` over layers prints and stops as the one process of plain Bun does.
Tests ran on behind `--bail`.
- A filter beside `--files-from` was dropped in silence. It's an error now.
- `import(`specs/build/${name}`)` or `import("@/pages/" + name)`: a name whose fixed beginning can be
no module of Bun's own no longer makes a layered test run compile the whole library.
Plain Bun:
- `src/js/internal/worker/stdio.ts` is main's again, byte for byte, and our test of it is gone. What
we had put there revived a Worker's stdout after an out-of-memory write, and the stream could then
never end.
Smaller programs:
- `console.log(navigator, new Event(name), new TextEncoder())` took in `util.inspect`: 449 functions
for 3. The table described 6 of the 12 getters of `Event.prototype`, and a getter it doesn't
describe answers an object nobody knows. All twelve are described.
- `import("./own.js")` read `.resolve`, `.promise` and `.then` by name. One library function does
those steps with the intrinsics.
CI:
- `test/expected-durations.json` has our files' seconds from the last round's 96 logs. 304 of our
test files had no entry and counted as 0.4 s each: 122 s for 33,730 s on the ASAN lane. None of
main's entries moves. A file without an entry now counts as the mean of the files beside it.
- A file that the harness killed because its own commands held too much memory isn't run again. It
fails the same way each time. One file spent 13 of a shard's 45 minutes that way.
- Two test files that are thin against the time the ASAN lane gives them are dealt out over more
files. No case changes.
- Two tests that we had deleted came back in the previous commit through a fault of the script that
cuts it. They're gone again. One stored listing follows a change that was taken out.
… the compiler's size restated The engine moves to oven-sh/WebKit#942 at 44bc299d. No source of Bun's changes: the pin and 19 test files. What the engine fixes, with the tests that come with it here: - `list.push(value)` could lose `value` where the list had to grow by one element. The next read gave another value, or the program crashed. It was rare and wanted a loaded machine. `parallel/arrays/lists-that-grow-by-one-element-keep-it.js` fails once in some hundred runs without the fix. - An async function or a generator with more parameters than fit in registers crashed when it set a late one to `null` behind an `await` or a `yield` and several calls were under way. Eight programs in `parallel/parameters-on-the-stack/`. - Where `method` in `object.method()` is a getter that throws or reads a stack, the trace lacked the function that made the call. - `bun build --compile --aot` aborted on lines of pako 3 that clear a typed array and read through `undefined`. - One IR test of the flow analysis gave another answer under some orders of its rules. The last commit's change to the table of built-ins had moved such an order onto one that CI runs. - Five native tests took the shared code for less than 1 MB, which it isn't under ASAN. A leak of ICU's in `Intl`. An assertion of the register allocator. An instruction bound on Alpine. Known gaps, marked and not removed: - `gc/locals-set-to-null-let-go.ts` and `async/objects-last-read-two-awaits-collected.js`, under ASAN alone: an object that nothing refers to any more is still alive when the program looks. Compiled code leaves a copy of the pointer in a stack word that it no longer uses, and whether native code overwrites that word goes by the layout of native frames, which moved in the last commit. Release builds pass. `run-parallel.ts` learns `// Todo under ASAN:` for them. - `gc/parameter-that-came-on-the-stack-set-to-null-lets-go.ts`: the object now lives until the function returns. The store that let go of it earlier is what crashed above. - `edge-cases/a-hole-read-by-a-constant-index.js`: two error messages quote the source otherwise than plain Bun does. `test/bundler/compile-aot-size.test.ts`: the most bytes that the compiler may take of Bun goes from 22,750,000 to 23,750,000, once. It measures 23,647,988.
…ix engine fixes `bun test --aot` mapped every source file of 8 KiB or more while it built what the test files of a run share. A file saved in that time was cut short under the parser: the run stopped with "a source file was made shorter while it was being compiled", or reported syntax errors that the file did not have. Every file is read into memory again, as on main, and the code that mapped them is gone: 345 lines, among them src/sys's mapping and the SIGBUS case in the crash handler, which plain Bun ran too. `a-file-that-is-saved-while-the-layers-of-a-test-run-are-built.test.ts` cuts a file shorter every few milliseconds for a whole run. Found by the review of this pull request. A package or a test that stores its own resolver, `Module._resolveFilename = f` or `Module.prototype.require = f` (module-alias, tsconfig-paths, playwright, require-in-the-middle): - Where a test run is built in layers, as under `--parallel`, a project file that required a project file never asked `f`. Where `f` redirects, the wrong file ran, in silence. A layer now tells the layers above it that it stores a loader function. - As one program, a .cjs test file that stored one at its top and then required plain CommonJS files of the project stopped with "this executable can't load a module from a dynamic path". The engine moves to oven-sh/WebKit#942 at 03194c54. What that fixes, with the tests that come here: - After `import "./shared.mjs"; import "./throws.cjs"` had failed, `shared.mjs` never ran for whoever imported it next. The importer got a wrong answer in silence, "Cannot access 'x' before initialization", or a segfault where it read through `export * from`. Six programs in `parallel/modules/` and a case in `harness/modules-in-a-test-run.ts`. - `function f() { return console instanceof Object; }` answered false, and as the initializer of a class field it aborted. The same for Bun.sql, Bun.postgres, SlowBuffer and node:console. `every-built-in-object-is-an-instance-of-what-plain-bun-says.test.ts` asks one question a program: several in one program passed on the broken build. - `while (i < n) { .. n = n + 1; .. }` could abort `bun build --compile --aot`. - Classes written as ES5 constructor functions ran 2.5 to 3 times slower since the last commit. - Under ASAN about one validating process in 3,300 stopped with "FrameCellEscaped", in another test each time. - The rest of the leak in `Intl`: 50 bytes in 2 objects, which `no-leak-records-at-exit.test.ts` still reported.
…equire, a build that overflowed its stack, and 97 tests back on every lane The engine stays at oven-sh/WebKit#942 03194c54. `bun test --aot --parallel`: what a package does to the module loader now reaches the project's own files. - A package that takes a file out of `require.cache` by a path it is handed (`delete require.cache[filePath]`: import-fresh, clear-module, proxyquire; decache, which walks `module.children`) failed the test when the file belonged to the project: "Cannot take '../src/dep.js' out of require.cache: the build found nothing that does". - A package that stores a handler in `Module._extensions` (pirates, @babel/register, ts-node) failed it with "Cannot replace a loader in Module._extensions: imports in this executable were resolved at build time". Plain `bun test`, and a run compiled as one program, were right. Only the program that held the package knew what it does; now it tells the programs built above it. `require.main` of a created require was `undefined` in an executable: const made = require("node:module").createRequire(__filename); console.log(arguments.length, typeof made.main, made.main === module); console.log(require(process.argv[2] ?? "./" + ["other", "cjs"].join("."))); printed `5 undefined false`; Bun prints `5 object true`. It took both a file that uses its own `arguments` and a `require` under a computed name: a module loaded by a computed name was counted as code the build cannot see. A `createRequire` whose argument calls what is being made overflowed the stack of the build: const load = createRequire(load("node:url").fileURLToPath(import.meta.url)); `bun build --compile --aot` followed `load` to what it is made of, found a call of `load` there, and went round. Bun throws a ReferenceError at run time, and so does the executable now. One form more is followed with it: `import url from "node:url"; url.fileURLToPath(..)`. CI: tests outside test/aot and test/bundler/trim-by-use run on every lane again. scripts/runner.node.ts splits the compiler's tests across the lanes. The pattern that decided what is split had grown past those two folders and claimed 97 test files that main runs on every lane: test/cli/compile, test/cli/aot-*, test/cli/run/run-aot, test/cli/test/test-aot*, test/bundler/compile-aot-*, compile-trim-*, trim-runtime-icu, test/internal/builtins-table and test/internal/trim. 45 of them ran on Debian alone, 8 more on no macOS or Alpine lane, all 97 on no Ubuntu lane. test/internal/trim/link-coff.test.ts links only on Windows, or where it finds a Windows SDK: in CI it ran nowhere. Now only those two folders are split, and `tests-outside-the-folders-of-the-compiler-run-on-every-lane` holds that. Some of these files have not run on macOS, Windows, Alpine or under ASAN for a while, so this commit's CI may show failures that the split had hidden. Found by the review of this pull request. The layered runs of modules-in-a-test-run are dealt over five test files instead of four.
…ten faults that the working tree had already fixed The engine moves to oven-sh/WebKit#942 at 5803df62. What that fixes, with the tests that come here: - A counted loop could run one turn too many. for (let i = a.length - 1; i >= 0; i--) { if (Number.isInteger(a[i])) a.fill(3, i, i + 1); s += a[i]; } over a typed array of one element took a second turn: a wrong sum, no error. It took a loop that the compiler splits into a fast copy and an unlikely way out of it, where the unlikely way counts for itself and comes back to the fast copy's test. Four programs under parallel/loops-values-behind-tests/. - `bun test --aot --isolate` crashed where a test file caught an error thrown by a function of an earlier test file. Executables: - The text of a function around a function declared in a block did not parse again, a regression of this branch. function outer() { { function declared() { return 1 } return declared() } } (0, eval)("(" + String(outer) + ")")() // SyntaxError: Function statements must have a name Bun and Node answer 1. That is how a function is sent to a worker or a page. - A JSDoc tag with a dotted name inside a function stopped the build; lunr has them. (function () { var lunr = {}; /** @callback lunr.Token~updateFunction */ lunr.x = 1; })(); // error: TS1235: A namespace declaration is only allowed at the top level of a namespace or module - `createRequire(__filename).resolve("hides/hidden.js")`, where the package's "exports" hide that file, throws MODULE_NOT_FOUND under Bun. An executable built with `--root` above the importer, and run in a folder that has node_modules/hides/hidden.js, answered that file: it went by the working directory of the run. `bun test --aot`: - A file that a plugin loads is the plugin's for `require` too. With `b.onLoad({ filter: /\.custom$/ }, ..)` in a preload, a test that both imports and requires "./a.custom" got the plugin's module from `import` and the file's raw text, run as CommonJS, from `require`. - `require.cache[require.resolve("./counter.cjs")].children` and `.parent` were undefined in an ES module and an empty list in a CommonJS file, so code that clears a module with all below it threw. - `const { test } = require("bun:test")` counted as the whole of bun:test going out of sight, `mock` and `spyOn` with it, so every module of the run counted as mocked. nock 15 did not load: "Cannot assign to fetch because built-in objects are read-only in compiled code". - `mock.module("bun:sqlite", () => made); require(name()); require("bun:sqlite")` answered `made` from the second require on; bun test answers the mock's namespace object each time. - An installed package of test helpers that mocks or spies with what a test hands it failed under `--parallel`: exports.mockModule = (path, factory) => mock.module(path, factory) // cannot replace a module that was compiled ahead of time exports.spy = object => spyOn(object, "name") // Cannot spy on 'name' because module exports can't be replaced In practice it is a shim that takes `vi` from "vitest" or `jest` from "@jest/globals", which Bun answers with its own test runner. Of eleven such uses nine were wrong under --parallel and none as one program; none is wrong now. CI: - test/internal/trim/link-coff-freestanding.test.ts timed out on Alpine x64, the first lane to show it since the file runs on every lane again. Its two links of 4,000 functions ran beside twenty other tests and got a twentieth of the processors: 40 and 46 of their 60 seconds on two processors, 4 seconds alone. They run by themselves now. No limit is raised. - Two tests of the compiler ask the system and now run on Alpine, macOS and Windows too: a file saved while the layers of a test run are built, and every-built-in-object-is-an-instance-of-what-plain-bun-says, which walks Bun's own built-ins, and those differ by platform. - Seven new test files that take over fifteen seconds on some lane run in a process of their own. - A correction to the last commit's message: link-coff.test.ts skips itself on CI's Windows machines too, which lack what it needs, so the split of the lanes took no real run of it away. What the change gave back is test/cli/compile on macOS and Alpine, run-aot on Windows, and internal/builtins-table and link-elf under ASAN.
…er layers, comments read as code; one fix taken back for Windows The engine moves to oven-sh/WebKit#942 at 3b261100. Taken back: - The last commit's fix for `createRequire(__filename).resolve("hides/hidden.js")` in an executable built with --root. On Windows it made relative imports of code from disk fail. main.ts, built with --aot --aot-allow-code-from-disk, with plain.js in the working directory: await import([".", "plain.js"].join("/")) // ResolveMessage ERR_MODULE_NOT_FOUND require([".", "plain.js"].join("/")) // MODULE_NOT_FOUND for .js, .mjs, .cjs, .ts, .tsx, .jsx and .json alike. The executable built without --aot loads all fourteen. `import(path.resolve("typed.ts"))` went on working. A name that its importer does not find is looked for once more from the root of the executable, and the fix let that second look answer only with what the executable holds. The likely cause, read in the sources and not run: on Windows the folder of a module is spelled B:\~BUN\root\ and the root B:/~BUN/root/, so a module that lies in the root takes the second look too, and the file that is found there in the working directory was thrown away. test/aot/code-from-disk.test.ts caught it on both Windows lanes. src/jsc/VirtualMachine.rs is what it was before that commit, and the fix's own test goes with it. The fix comes again with both spellings handled. Executables: - `globalThis.Map = globalThis.Map || class FallbackMap {};` threw "Cannot assign to Map because built-in objects are read-only in a compiled executable". A store of the value that a property already holds changes nothing and is let through (`Object.assign = Object.assign || polyfill`), but the value was looked for only among the properties that the global object had made by then. Map, Set, WeakSet, Number, Boolean, ArrayBuffer and the typed arrays are made at their first read, and compiled code has the class without reading it. - While file.cjs is being required, something calls `import("./file.cjs")`; then the file throws. Bun rejects the import with the file's error. Compiled it was fulfilled with what the file had exported by then, in six of eight forms. Where the host reads an imported file's text itself (plugins, the compile cache) Bun fulfills, and so does compiled code. Comments are not code (JavaScript files with JSDoc): - `/** @import { f } from "./nothing.js" */ function f() { return 1; } export { f };` and, elsewhere, `import { f } from "./lib.mjs"`: error TS100200: No matching export. With `import * as all` the build went through and the namespace lacked the name: Object.keys(all) was []. - `/** @import Foo#bar */ export const one = 1;` stopped a build. A mistake inside a comment is no mistake of the program. - a.test.cjs: `/** @license MIT */ const path = require("node:path"); test("x", () => {});` threw ReferenceError: Cannot access 'test' before initialization. In a CommonJS file that a test requires, behind such a comment, `typeof jest` was "undefined" for "object". react-dom's development build asks that to find out whether act() applies. `bun test --aot`: - A test file written for jest or vitest, without an import of the runner: jest.mock("validator", () => ({ isEmail() { return true; } })) vi.mock("../src/report.ts", () => ({ .. })) Over layers (--parallel): "TypeError: mock(module, fn) cannot replace a module that was compiled ahead of time", and every test of the file was lost. With `import { jest } from "bun:test"` it worked. `bun test` hands every file the names of the runner, and a build gives such a file the import; the look through the test files that tells the layers below what is mocked did not. Two files of xstate's suite: 0 tests ran, now 25 pass and 15 fail, which is what bun test says. One of clean-ts-api's: 0, now 3 of 3. - A dependency's tsconfig.json stopped a whole run over layers. node_modules/pkg/tsconfig.json with `{ "extends": "../../not-published/tsconfig.json" }`: "TS5083: Cannot read file" and "None of the 1 test files ran". @redocly/openapi-core and @did-plc/lib ship such a file; openapi-typescript's own suite lost 248 of 248 tests. A tsconfig.json under node_modules is never the configuration of the program. The package's own files are still checked by it. - `require("./m.cjs"); process.stderr.write("x\n")` in an ES module test file failed with "'EventEmitter' was not compiled into this executable because your code never calls it", a regression of this branch. And a file that a test writes anew and requires again may ask a standard stream for more than the build saw (`process.stdout.listenerCount("drain")`). - `const key = Buffer.from("75726c", "hex").toString(); require.cache[key] = { exports: { parse: 1 } }; typeof require("url").parse`: bun test says "number"; compiled it threw "Cannot store an entry under 'url' in require.cache". The same through a helper in another file, Reflect.set, Object.assign and Module._cache. Over layers, a package that read a built-in module under such a key crashed. - A package that patches fetch or the timers through a helper, `registry.applyPatch(globalThis, "fetch", wrap)` (@mswjs/interceptors, under nock), was refused where the test file keeps the whole of bun:test (`const runner = require("bun:test")`). Eight new test files that take over fifteen seconds on some lane run in a process of their own.
…e; a test file's own time limit The engine stays at oven-sh/WebKit#942's 3b261100. CI: - test/aot/the-default-time-limit-never-lowers-what-the-runner-gave.test.ts was red on every lane that runs it. A test file of the last commit set its default limit by itself, `setDefaultTimeout(120_000)`, under the 270 seconds that the runner gives a Bun with assertions. It says raiseDefaultTimeout(120_000) now, as the others do. Executables, the file of a Worker: - In a CommonJS file of the project: const worker = new Worker(__dirname + "/job.cjs"); worker.onmessage = event => console.log(event.data); The Worker never ran, nothing was printed, and the executable ended with exit 0, a regression of this branch. Bun runs it, and so does an executable built without --aot. With an error listener it said "Cannot load '<where the file lay>/job.cjs': it isn't in this executable". The same as a template (`${__dirname}/job.cjs`), through a variable, with __dirname + "/deeper/../job.cjs" and __filename + "/../job.cjs". path.join(__dirname, "job.cjs") was right all along. The build takes the file of a Worker into the executable where the path comes from the file's own. It told such a path from one that the program simply wrote down by reading the expression twice, as the program sees its files and as they lie on disk, and asking whether the two differ. `__dirname + "/job.cjs"` had been folded to one constant before anybody saw its parts, so both readings agreed. Now the parts of a sum, a template or a variable say where the path comes from. A path that is written down whole is still a file of the machine that the program runs on, and what a package in node_modules names is still left out. - `new Worker(__dirname + require("node:path").sep + "job.cjs")`, `const { sep } = require("node:path"); new Worker(__dirname + sep + "job.cjs")` and the same in a template gave an error event, "Cannot load ..: it isn't in this executable". `sep` of node:path, as a member or under a name of its own, is a text that the build knows. - `new Worker(__dirname + "/none.cjs")`, a file that is not there, in an executable that holds another Worker's file: 670 bytes in two allocations were never freed for each such call. Where a Worker is created a compiled program asks the resolver twice for the Worker's own name, and what the resolver said of a name that it did not find went into a log that nobody dropped. Each of the two questions has a log of its own now. Both stand behind "is this a compiled program": Bun without --aot does not reach them. Test: a-worker-starts-from-a-path-made-of-dirname-or-filename (six ways; the executable runs with its sources moved away).
The global object adds the name of a class, and its `Bun`, at the first
read of the name, and the engine locks the object then. A program can
come to the object without the name: `instance.constructor`, the
exports of a module, require("bun"). The object was then open. A write
that the build does not follow was made, and the executable ended with
"AOT: built-in cell .. is not in this realm", or compiled code called
the replacement as if it were the host's function.
getDOMConstructor() now stores a new constructor through
Zig::GlobalObject::setDOMConstructor(), which reports it with
JSC::AOT::hostDidCreateObjectOfGlobal() under the class name of the
constructor. The initializer of the `Bun` object reports it the same
way, as the one of `process` does.
Not in this change: the constructor of a Temporal or WebAssembly class
that a program takes from an instance, and a DOM prototype that it
takes with Object.getPrototypeOf(instance). The engine hears of neither
where it is made. The test lists both as rows that are not met.
|
Updated 8:04 AM PT - Oct 31st, 2026
❌ @robobun, your commit c93ba23 has 2 failures in
Add 🧪 To try this PR locally: bunx bun-pr 47545That installs a local version of the PR into your bun-47545 --bun |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Status Closed, not merged. The problem, as reproduced on canary const u = Bun.pathToFileURL("/a");
globalThis.u = u;
(0, eval)(["u.constructor.canParse", "= () => 'mine'"].join(" "));
console.log(URL.canParse("x:y"));
On canary PR: #47545 |
There was a problem hiding this comment.
Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.
Beyond the inline finding, I also checked two things and ruled them out: removing the non-const constructors() accessor leaves no stale callers (the only other reference in src/ is a commented-out line in ZigGlobalObject.cpp), and Identifier::fromString(vm(), constructor->classInfo()->className) atomizes from an ASCIILiteral rather than a shared StringImpl, so it does not atomize another thread's string in place.
Extended reasoning...
The change touches the DOM constructor cache in src/jsc/bindings/webcore/JSDOMGlobalObjectInlines.h and the AOT lock reporting in src/jsc/bindings/ZigGlobalObject.cpp/.h, plus one new test file; it touches no auth, crypto, or input-parsing surface. The new setDOMConstructor and Bun reports are gated on vm().hasCompiledCode() while the pre-existing process report at ZigGlobalObject.cpp:3154 is not, which is the gap the inline finding covers.
|
Closing: #47942 rewrites the front end of the ahead-of-time compiler and its tests for the engine's new compiler (oven-sh/WebKit#997, merged), so this no longer applies and would conflict with it. If the problem still shows once that is in, please open a new pull request against |
|
Understood. I checked both builds. The problem shows on neither, so there is nothing to open a new pull request for.
One other thing from the run on #47942. The debug build crashed in 7 of 16 builds of a 21-line program, with Program and stacks
const attempt = (label, text, read) => {
let how = "stored";
try { (0, eval)(text.join(" ")); } catch (error) { how = `${error.constructor.name}: ${error.message.split(" because ")[0]}`; }
let answer; try { answer = String(read()); } catch (error) { answer = `threw ${error.constructor.name}: ${error.message}`; }
console.log(`${label}: ${how} | ${answer}`);
};
globalThis.response = await fetch("data:text/plain,x");
attempt("Response.json", ["response.constructor.json", "= () => 'mine'"], () => typeof Response.json({}));
globalThis.stream = Bun.file(import.meta.path).stream();
attempt("ReadableStream.from", ["stream.constructor.from", "= () => 'mine'"], () => typeof ReadableStream.from([1]));
globalThis.signal = new AbortController().signal;
attempt("AbortSignal.any", ["signal.constructor.any", "= () => 42"], () => AbortSignal.any([]));
attempt("AbortSignal.prototype.throwIfAborted", ["delete", "Object.getPrototypeOf(signal).throwIfAborted"], () => typeof signal.throwIfAborted);
globalThis.load = import.meta.require;
attempt("Bun.pathToFileURL", ["load('bun').pathToFileURL", "= () => 'mine'"], () => Bun.pathToFileURL("/a"));
globalThis.instant = Temporal.Now.instant();
attempt("Temporal.Instant.compare", ["instant.constructor.compare", "= () => 'mine'"], () => Temporal.Instant.compare(instant, instant));
globalThis.headers = response.headers;
attempt("Headers.prototype.get", ["headers.constructor.prototype.get", "= () => 'mine'"], () => new Headers({ a: "b" }).get("a"));
globalThis.blob = await response.blob();
attempt("Blob (constructor.prototype.text)", ["blob.constructor.prototype.text", "= () => 'mine'"], () => typeof new Blob(["x"]).text());When a build succeeds, the executable prints eight lines that each hold |
Problem
bun build --compile --aotmakes a write toURL.canParsethat the build does not follow, when the program came toURLthroughinstance.constructor. It then ends withAOT: built-in cell 952 (key 4165, URL.canParse) is not in this realm: 9, exit code 134.getDOMConstructor()makes it earlier, forinstance.constructorand module exports.require("bun")hands outBunthe same way.Fix
getDOMConstructor()stores a new constructor throughZig::GlobalObject::setDOMConstructor(). That reports it withJSC::AOT::hostDidCreateObjectOfGlobal(), the hook that AOT: bun test --aot on large suites, module loading fixes, fuzzer fixes #47316 added forprocess. The initializer ofBunreports it too.test/aot/a-constructor-is-locked-whichever-way-a-program-comes-to-it.test.ts. Withsrc/of the base, 6 of its 9 tests fail.Background
URL.canParsewith no test if no compiled statement writes it. So the engine locksURL: an unfollowed write throws aTypeError.ClassInfoof the instances. It names 19 of 51 DOM constructors. The class name reaches all 48 with a global name.Downsides
AbortSignal.abortorURL.createObjectURLthroughinstance.constructornow throws. Before, 40 of 129 tested rows made the write.deleteon a DOM prototype from an instance (Notes).Notes
Where this goes
claude/compile-aot-on-check(AOT: bun test --aot on large suites, module loading fixes, fuzzer fixes #47316).hostDidCreateObjectOfGlobalis only there and in AOT: per-global program state, mockable exports, heap-allocated generator frames, bug fixes WebKit#942, so this cannot go tomainbefore them. The change is one commit and can be cherry-picked.didCreateBuiltinnotice on the same line ofgetDOMConstructor().setDOMConstructor()is now the one place for such a notice.The reported program
bun a.jsmineAOT: built-in cell 952 (key 4165, URL.canParse) is not in this realm: 9, exit code 134TypeError: Cannot assign to URL.canParse because built-in objects are read-only in a compiled executableglobalThis.keep = URLin front of theeval, before and afterTypeError, thentruemineNo evaluated text is needed. A receiver that the build cannot follow is enough:
hide(u).constructor.canParse = fwithhide = v => new CustomEvent("x", { detail: v }).detail.What the test holds
URL.canParse,.parse,.createObjectURL,AbortSignal.abort,.any,.timeout,AbortSignal.prototype.throwIfAborted,ReadableStream.from,SubtleCrypto.supports,Bun.pathToFileURL), each way to the holder (the global name,instance.constructor,Object.getPrototypeOf(instance).constructor, the export ofabort-controller,require("bun")in the text of the write) and each write (a store byeval, a store bynew Function,Object.defineProperty,delete).src/of the base (release build of 5a98d88): 54 rows refuse, 35 end withis not in this realm(exit code 134) and 40 make the write. With this change all 129 refuse.signal.constructor.any = () => 42from evaluated text,AbortSignal.any([s]).abortedthrowsTypeError: 42 is not an object, where plain Bun printsundefined.URLandAbortSignalreplaced by a store that the build follows (the lock is on the constructor, not on what the name holds), a followed store that stays,bun test --aotas one program and over two layers, and the class name of the constructor of each of the 48 rows ofZigGlobalObject.lut.txt.instance.constructor.prototypeis locked too (4 rows).Still open: the same abort, other makers
const i = Temporal.Now.instant(); globalThis.i = i; (0, eval)(["i.constructor.compare", "= () => 'mine'"].join(" ")); Temporal.Instant.compare(i, i).. (key .., Temporal.Instant.compare) is not in this realm: 9, exit code 134didCreateClasscannot name themdelete Object.getPrototypeOf(hide(signal))[hide("throwIfAborted")]; AbortSignal.prototype.throwIfAborted.call(signal).. AbortSignal.prototype.throwIfAborted) is not in this realm: 1, exit code 134. A store is madehostDidCreateStructuredrops the report for a class whose row names no StructureURL.canParse = f, then an unfolloweddelete URL.canParsedeletefaults onmaintooThe test lists the first two as rows that are not met (
gap). Both are handed to follow-up work.Other behavior changes
spyOn(hide(u).constructor, "canParse")throwsCannot spy on URL.canParse ... Before:spied, then exit code 134.bun test --aot: as one program, the store throughheld.constructorended the whole run with SIGABRT (URL.canParse) or was made (AbortSignal.abort). Over two layers it was made. Both forms now answer theTypeError.Measurements
Release builds without LTO (the container cannot link the LTO build), linux-x64, base 5a98d88 against the same commit with this change. Instruction counts are user-mode counts of the first thread (
test/aot/startup-scaling/measure-startup.ts, perf counters).bloaty,valgrind,perfandstraceare not installed.instance.constructorread in plain Bun, JIT off (slope of 1e7 to 2e7 reads, 3 runs)jsDOMURLConstructoron a hit (disassembly).textofbun(size -A)URL)helloto its end (median of 7)heapStats().objectTypeCounts)test/aot/tools/same-output.ts, 289 programs).constructorof 16 instances and so makes 7 constructors without their nameThe slow path of
getDOMConstructor()was inlined at every call site. It is now one function for each class (51), which is where the 5,376 bytes go.Self-review
6 concerns raised, 5 addressed: a test of the class name that the report uses, the two forms of
bun test --aot, rows for what stays open, a followed store to the global names, and more static members with a prototype row. Not done here: Structure rows for the DOM prototypes, which are the follow-up above.no test proof · iteration 0 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/aot/a-constructor-is-locked-whichever-way-a-program-comes-to-it.test.ts