Repository navigation
AOT: faster loops, async code and WebAssembly, smaller executables, stack traces as in bun run - #1028
Conversation
…stack traces as in bun run, assembler fixes On 873ab3a, the merge of #1025. 545 files, +23,771 -4,315: Source/JavaScriptCore/aot 476 files +21,514 -3,920 . Source/JavaScriptCore/assembler 7 files +553 -60 . Source/JavaScriptCore/b3 24 files +456 -72 . Source/JavaScriptCore/wasm 21 files +950 -197 . Source/JavaScriptCore/runtime 10 files +125 -4 . Source/JavaScriptCore/yarr 3 files +123 -62 . Source/JavaScriptCore/llint 3 files +46 -0 . Source/JavaScriptCore/interpreter 1 file +4 -0. Nothing in heap/, WTF or bmalloc. Executables - Stack traces and error messages are closer to `bun run`: positions of constructions, destructuring and `await`, names of methods under computed keys, paths relative to the folder of the build. An async function that awaits itself through `Promise.all` is in a trace once. - Modules: `require()` of an ES module whose graph holds a file that cannot be loaded runs the CommonJS files in front of it. Errors reach every load of a graph. A file that is imported as text and as JSON no longer mixes up its forms. After an imported CommonJS file throws, the next one that an import loads is the main module, as under `bun run`. `bun test --aot` - Mocks: a package module that failed at its first load, chains of mocks whose factories take their original, mocks of files that import each other in a cycle or that fail, `resetModules()` inside a running graph. WebAssembly in the image - Frames are shrink-wrapped, the bounds checking register is not saved, direct calls pass the first mutable i32 global in a register, functions of Go's compiler are entered behind the switch on their parameter, an access that a succeeded one covers needs no check, on x64 comparisons read memory. A trap takes no lock. The build of esbuild-wasm takes 77.1 G instructions, 80.2 before its three changes to B3 and Air. Plain Bun too - arm64 with DotProd: `i32x4.relaxed_dot_i8x16_i7x16_add_s(x, x, c)` in the optimizing JIT multiplied by the addend where the result took x's register. Fixed. - The data segments of a WebAssembly module lie in one buffer, each behind a short header: esbuild.wasm has 98,450 of them. Compiled ahead of time, its short job takes 6,800 page faults instead of 8,170. - Corrected assembler operations that nothing calls with such operands: equal registers, negative immediates and masks, pairs of floats at far offsets, blinded additions, shifts by 32 and more. Eleven native tests (assembler/testmasm.cpp). Speed (instructions, by our own benchmark rows) - Async code and generators: an awaited call of a compiled function continues it in place, 687 to 202 a call; a pipeline of generators is joined, 81 to 35 an element; iterators and generators in nested loops are closed and stepped in place. - Sorting: `sort((a, b) => a - b)` from 65 numbers on goes by digits, 1,286 to 52 an element for 20,000 integers; `sort(comparator)` of typed arrays is compiled. - Strings: substrings, parseInt(), keys of a `Map` that are joined, one character of a string, split(), destructuring, TextEncoder and TextDecoder (3,811 to 1,465 a line). - Loops: lists in fields, rows of rows, nested loops, counters of any kind, views under strides and masks, sums of unknown values, BigInts, remainders, method chains, throwing callees. - Objects: comparisons of one layout, `instanceof`, `Object.hasOwn()` and `Object.keys()` of records, getters and setters of host and class objects, static fields, spread copies, constructor functions below native classes. - Results of functions of node:path and node:fs have types. JSON.stringify() of arrays of primitives, json() bodies of a declared type, regular expressions that search for two units. - arm64: shifted, extended and constant operands, shared sums, proven integers. Size and build - Image tables are coded: positions, fault sites, side tables, function records, identifiers, shown strings, names for heap snapshots. Constants are compared and loaded in place, and element accesses under a constant index take fewer bytes. - Builds that took quadratic time: chains of classes and interfaces, unions with many tests, barrels with many stars under mocks, nested array patterns. - Dead stack words are no longer cleared at entries from native code. Checks - --aot-harden=proof-checks verifies each stage. `Wire::schemaHash` changes, so this engine and Bun's side go together. Not built as one tree. The known differences from main are in Bun's message.
On 7ddc761. 23 files, +106 -118: Source/JavaScriptCore/aot 2 files +2 -14 . Source/JavaScriptCore/assembler 2 files +25 -25 . Source/JavaScriptCore/b3 11 files +33 -33 . Source/JavaScriptCore/wasm 4 files +17 -17 . Source/JavaScriptCore/runtime 4 files +29 -29. - macOS did not compile aot/machine/AOTMachineEncoderMoves.cpp: `isRepresentableAs<uint32_t>()` of a uint64_t is ambiguous where size_t is another type. The calls pass a size_t. - A constant list of zeros that a loop walks and reads under a computed index stopped the build with an internal compiler error (BuildImage). The change that gave such lists a storage of their own, new in 7ddc761, is out again. - Names of helpers, members, locals and native tests, and comments, in the files that #1028 changes outside aot/: JSONObject.cpp, the data segments in WasmFormat.h, testmasm.cpp, the b3 and Air tests, and single lines elsewhere. No logic changes with the names.
There was a problem hiding this comment.
Beyond the inline findings, I also checked the long-array shift path in AOTArrayBuiltinEmitter.cpp (shiftLongArray misses when mutatorShouldBeFenced is set and only moves existing elements, so no missed barrier), the worker URL witness relocation in AOTResultSections.cpp / AOTFactsOfProgram.cpp (consumer-side contract change, not an engine bug), and the StackFrame.cpp URL override for helper frames (display-only) — none of these turned up a defect.
Extended reasoning...
The change is a 543-file, ~23k-line update across the JSC AOT compiler, runtime stubs, assembler, Yarr, and WebAssembly glue, with no auth, crypto, or injection surface. Three inline findings are already posted, so this body only records the additional candidates that were examined and ruled out.
Findings marked 🟡 are optional suggestions and need no follow-up push.
| const sortingOfNumbers = { | ||
| ...sortingWithComparator(self, changesElements), | ||
| this: typedArrayOfNumbers, | ||
| implementation: b.compiled("prelude/array-sort", "sortTypedArray"), |
There was a problem hiding this comment.
🟡 (optional) If this merges before the host prelude exports sortTypedArray, every table build on that host fails with 'prelude/array-sort: no runtime function is named sortTypedArray'. typedarray.builtins.ts:198 makes the comparator row of TypedArray.prototype.sort b.compiled("prelude/array-sort", "sortTypedArray"), a function that only the test stand-in (tests/host/stand-in.builtins.ts:24) declares in this checkout. Fix: land the prelude export first or keep the b.hostCall comparator row as the fallback until the prelude on every host provides sortTypedArray.
Why this was flagged
The new row sortingOfNumbers at typedarray.builtins.ts:195-199 is placed first in the sort list at typedarray.builtins.ts:426 and names a compiled prelude function sortTypedArray. The only declaration of that name in the repository is the test stand-in at Source/JavaScriptCore/aot/tests/host/stand-in.builtins.ts:24; the real prelude lives in the embedder. When the embedder's prelude/array-sort does not yet export sortTypedArray, model-builder.ts:329 raises 'prelude/array-sort: no runtime function is named sortTypedArray' and the builtin table cannot be generated, so no program compiles on that host. Every build of the embedder that picks up this WebKit revision without the matching prelude is affected. Even when the prelude is updated, the comparator sort of all ten numeric typed array kinds now runs compiled JS instead of typedArrayViewProtoFuncSort, so any divergence in sortTypedArray (NaN comparator results, detached buffers) is now user-visible; this checkout cannot verify that function.
Verification: nit — triggers when the embedder's host build consumes this engine change before its own prelude/array-sort exports sortTypedArray. The diff adds implementation: b.compiled("prelude/array-sort", "sortTypedArray") at typedarray.builtins.ts:198. model-builder.ts:328-329 reports no runtime function of that name and build-model.ts:54-57 problems.throwIfAny() aborts the table build.
|
Preview build of 9ad6efd: |
…oader names On b7288b6. 23 files, +343 -132: Source/JavaScriptCore/aot 14 files +82 -86 . Source/JavaScriptCore/assembler 2 files +9 -1 . Source/JavaScriptCore/b3 4 files +132 -2 . Source/JavaScriptCore/wasm 2 files +119 -42 . Source/JavaScriptCore/interpreter 1 file +1 -1. - Plain Bun too: on x64 a WebAssembly load whose low bytes alone are used returned a value at the end of the memory where it should trap, in the optimizing JIT. Fixed. - Executables: a store in a WebAssembly loop that only a trap ends could be dropped as covered by a later iteration's store to another address. Fixed. - x64: a WebAssembly function entered behind its entry test could read stale flags. Fixed; found in review. - `additionBlindedConstant(0)` no longer divides by zero. - With `--aot-harden=proof-checks`, two kinds of loop stopped the build: a loop in a loop that ends at a test for `undefined`, and a `for..of` loop that also reads the list by a remainder. Fixed. - `bun test --aot --rerun-each`: in the second pass, an import() that is refused again behind an unrelated mock is rejected with the same error object as under `bun test`. - `[0, 0, 0, 0]` is made to be filled, so it is built with a storage of its own, up to 16 elements. A list that a variable of the file or the body of a module holds shares its storage as before; built in line it stopped the build. - Two functions that Bun calls and a few members and locals of the modules' run-time state have plainer names. No logic changes with the names. Bun's side follows them, so this engine and Bun's side go together.
…ixpoint check at super() calls On 02c32c5. 6 files, +17 -7: Source/JavaScriptCore/aot 6 files +17 -7. - A field that a loop does not store into is read in front of the loop again where it has no type, or a declared type and no nullish value. - The fault that a test injects into the check of the fixpoint stands at every super() call, so that the test sees it.
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.
The newer commits address the two assembler-level points from my earlier pass: directEntryWithZero is now taken from an explicit label (AOTWasmModules.cpp:494 via emitZeroEntry) instead of byte-matching the entry test, and additionBlindedConstant guards the magnitude-zero case before the modulo (MacroAssembler.h:2028). Beyond the inline finding, I also checked the new in-bounds elision in OMGIRGenerator::memoryKind (WasmOMGIRGenerator.cpp:3432) for a non-trapping load being speculated onto an unexecuted path — the proof comes only from a dominating access on the same local in the same scope, is reset on every set and at loop entry, and is restored at scope exit, so it is not affected. Given the size of this change (548 files across the AOT compiler, the WebAssembly OMG tier and the assembler), a human look is still worthwhile.
Extended reasoning...
This revision adds two commits on top of the version reviewed earlier; the diff now spans 548 files with about 24k insertions across Source/JavaScriptCore/aot, the WebAssembly OMG IR generator, the assembler and Yarr. It touches JIT code generation and bounds-check elision, which is memory-safety-sensitive, but no auth, injection or data-exposure surface. The previously flagged byte-scanned zero entry and the blinding division-by-zero are gone in the current tree, while the prelude sortTypedArray dependency at typedarray.builtins.ts:198 is unchanged. The change is far too large and low-level to approve without a human owner's review.
Still open from earlier reviews (1):
- Unresolved: 1 minor or pre-existing.
… length check for written indexes, a leak report On 348972f. 9 files, +219 -14: Source/JavaScriptCore/aot 2 files +24 -1 . Source/JavaScriptCore/b3 4 files +168 -3 . Source/JavaScriptCore/wasm 3 files +27 -10. - Plain Bun too, in the optimizing JIT: `f32.demote_f64` of a converted 64-bit integer rounded once where it rounds twice, and a signaling NaN stayed signaling through `f32.demote_f64` of `f64.promote_f32`. Fixed. - A module whose data segments outgrow the buffer's first reservation fails validation when memory runs out, as before, instead of ending the process. - One check of a list's length stands for all written indexes of a block: the time of a build grew with more than the number of statements. - The decoded texts of an image are allocated as the image is, so that LeakSanitizer reports nothing.
…ct for a refused import, tests On 80771f5. 96 files, +2,668 -1,413: scripts 4 files +45 -1 . src 24 files +1,467 -1,342 . test 68 files +1,156 -70. Pins the preview build of oven-sh/WebKit#1028's commit 348972fa. [skip size check], as in 607519a. - `bun test --aot --rerun-each`: in the second pass, an import() that is refused again behind an unrelated mock is rejected with the same error object as under `bun test`. - BunPlugin.cpp and what calls it: 210 plainer names and shorter comments. No logic changes with the names. Two functions of the engine change their names with them, so this needs 348972fa. - scripts/verify-baseline-static: the three allowlists name PackWordsImpl and UnpackWordsImpl, two Highway functions that are new in this pull request and are called by run-time dispatch alone. The check was red without them. The Windows lines are by analogy with x64. - The counters of a page's units in MimallocArena.rs are 32 bits wide instead of 16, an atomic that the lists for arm64 already name. - A thread that returned the last block of a page while the page's owner laid another block there could clear the new block. Fixed; found in review. - Findings of the lints: ten of mordant and three of clippy. No behavior changes. Three stored records of the lowering are restated, and the lint of names that are compared as text allows the namespaces of hoisted imports. - On macOS a descriptor of an embedded file is known again after its times are set back, and on Windows paths of assets are compared with the separators of the platform. Tests that CI showed to be wrong are corrected (sizes, positions inside built-ins of builds with assertions, tables of three reads, folders without a test, rows of node:fs). Tests of what the engine's commits fix: WebAssembly loads at the end of the memory, stores in loops that only a trap ends, calls behind an entry test, two kinds of loop under `--aot-harden=proof-checks`, lists of zeros. Linux: the four libc signal functions that ask who called them are no longer inlined, so a chained handler gets the mask that the kernel would have set. Plain Bun too: on x64 a WebAssembly load whose low bytes alone are used returned a value at the end of the memory where it should trap, in the optimizing JIT. Fixed. Known issues: in executables, a bare .cjs file loaded with import() runs in sloppy mode when a computed require() can also name it, as on main. A program whose heap is exhausted can hang in the collection that runs without helper threads, as on main.
…ings of the review, tests On 8fa04f2. 93 files, +1,124 -118: scripts 1 file +1 -1 . src 16 files +482 -116 . test 76 files +641 -1. Pins the preview build of oven-sh/WebKit#1028's commit 83468e92. [skip size check], as in 607519a. Plain Bun too, in the optimizing JIT: `f32.demote_f64` of a converted 64-bit integer rounded once where it rounds twice, and a signaling NaN stayed signaling through `f32.demote_f64` of `f64.promote_f32`. Fixed. - Findings of the review: `Bun.sql` of an executable without it throws the error of a Proxy's trap instead of a second one over it. Exception checks of deep equality are in functions, not lambdas. The helpers on paths of embedded files stand in one crate. - Executables: a `require()` whose computed name the build has followed gives way to an `import()` that names the same CommonJS file, so a bare .cjs file that the import loads runs in strict mode as in `bun run`. - Tests of the conversions and of data segments of WebAssembly. Known issues: in executables, a text that grows by adding elements of the list that it is stored into, over many turns, can take gigabytes where `bun run` takes about 60 MB, and with `--aot-harden=proof-checks` a generator that stores into a `Float64Array` in a loop stops the build; main does neither. A bare .cjs file loaded with import() runs in sloppy mode when a require() that never runs also names it, as on main. A program whose heap is exhausted can hang in the collection that runs without helper threads, as on main.
…data segments of WebAssembly, mocks below originals On 83468e9. 11 files, +102 -33: Source/JavaScriptCore/aot 6 files +92 -14 . Source/JavaScriptCore/wasm 2 files +4 -16 . Source/JavaScriptCore/assembler 1 file +2 -0 . Source/JavaScriptCore/b3 1 file +2 -2 . Source/JavaScriptCore/heap 1 file +2 -1. - A text that grows by adding elements of the list that it is stored into took gigabytes: its buffer copied what ropes share. A text grows in a buffer by short additions alone. - Plain Bun too: the headers of a module's passive data segments are reserved with the others, so a module with active segments before a passive one is not copied once more while it is compiled. - `bun test --aot`: with mocks of a file that takes its original, of a file below it, and of a file that imports that one, the last mock did nothing and the test got the real file. - A collection of compiled code that has no helper threads could wait for helpers, so a program whose heap is exhausted, or that may not start threads, could hang. The collector's thread marks there itself; plain Bun is unchanged. - `bun test --aot`: where an import() fails to link, the modules of its graph that were fetched are not fetched again by a later import(), so that a mock with an async factory settles as in `bun test`. - Small corrections in the x64 assembler and in the register allocator. Plain Bun too.
… of growing texts on arm64 On 75a5b48. 3 files, +23 -10: Source/JavaScriptCore/aot 3 files +23 -10. - With `--aot-harden=proof-checks`, a generator that stores into a `Float64Array` in a loop stopped the build: a store into a view ended what was known of the elements of fresh lists, which lie in no view. Fixed. - The native test of growing texts failed on Linux arm64, where a block is 64 KB: it added more characters than are copied into a buffer. It adds the same number on every machine now.
There was a problem hiding this comment.
I re-reviewed the two newest pushes and found no bugs; given the size of this change (554 files across the assembler, B3/Air, GC, Yarr and Wasm), a human look is still worthwhile before merging.
What was reviewed:
- The newest pushes: Segments::tryReserve in WasmFormat.cpp now reserves a LongHeader per indexed segment and tryAppend reports allocation failure to the parser instead of asserting; the Air greedy allocator's gapTmp interval now starts at the Pre point of the hole; SlotVisitor falls back to drainInParallel when collector helpers start lazily; testl_i32m guards the near-INT32_MAX offset split in MacroAssemblerX86_64.h.
- Checked whether the OMG bounds facts recorded by didAccess (WasmOMGIRGenerator.cpp) could leak into a catch handler and skip a trap: ruled out, since addTry declines image compilation and addressInLocal only records facts when codeForImage is set.
Extended reasoning...
The range covers 554 files and roughly 24k added lines, mostly under Source/JavaScriptCore/aot but also touching the assembler (MacroAssembler blinding and x86-64 test encodings), B3/Air register allocation, SlotVisitor, Yarr and the Wasm OMG tier and module format parser. It touches no auth, injection or data-exposure surface, but JIT code generation and the Wasm binary parser are memory-safety-sensitive. The two commits since the last review are small follow-up fixes (WasmFormat reservation, Air gap interval, lazy GC helpers, an x86-64 encoding guard) and look correct on reading. The scale and the fact that the change spans several JIT subsystems, with the earlier typedarray sortTypedArray prelude nit still unaddressed in this checkout, are what decide defer rather than approve.
Still open from earlier reviews (1):
- Unresolved: 1 minor or pre-existing.
On dd4008c. 69 files, +301 -5: scripts 1 file +1 -1 . src 3 files +24 -4 . test 65 files +276 -0. Pins the preview build of oven-sh/WebKit#1028's commit 33c7b694. [skip size check], as in 607519a. A file of at least 1 MB is embedded at a multiple of 16 bytes, so that the Buffer of `readFile()` is aligned as before. Tests of texts that grow by long additions, of lists beside stores into views, of mocks below originals, and of an import() after one whose graph failed to link. Known issues: in executables, with `--aot-harden=proof-checks` a loop over a list of the file that calls a function which keeps the list, and a coroutine that passes a parameter on, can stop the build; main builds them. A bare .cjs file loaded with import() runs in sloppy mode when a require() that never runs also names it, as on main.
… originals On 33c7b69. 14 files, +206 -18: Source/JavaScriptCore/aot 13 files +126 -18 . Source/JavaScriptCore/b3 1 file +80 -0. - Two tests of bounds in a row, such as `if (v < 3) { a = 5; continue; } if (v > 9) { b = 7; continue; }`, took the first's arm for both where both arms are short. Fixed. - A value that a class lays over a member of a constructor function below it could be taken for false. Fixed. - A loop of three turns that compares an element with a number could stop the build. Fixed. - With `--aot-harden=proof-checks`: a loop that calls a function which keeps its list, an async function that passes a wider type to a parameter, and a `.cts` file with a loop stopped the build; a method that reads an element beyond the end of a list aborted. Fixed. - `bun test --aot`: the originals of two mocked files on one ring of imports ran in another order, around the first factory. Fixed. - A frame part is closed under dominance. - The stub that finds a text names every register that it writes. - A test of values that live across rare clobbers in the register allocator.
…ts of the engine's fixes On 793f226. 50 files, +465 -7: scripts 1 file +1 -1 . src 3 files +13 -5 . test 46 files +451 -1. Pins the preview build of oven-sh/WebKit#1028's commit 4f3f6314. [skip size check], as in 607519a. - A worker of a compiled program frees what its functions of node:fs made: CI's leak check reported 16 bytes a worker. - `bun build --compile --aot --compile-inspector` over `export *` no longer stops the build. - Tests of two bounds in a row, of values that a class lays over a member of a constructor function, of loops that compare elements with numbers, of mocked files on one ring of imports, and of builds under `--aot-harden=proof-checks`. One test no longer asks for an order that `bun test` does not keep. Known issues: with `--aot-harden=proof-checks` a method that is replaced by that of another class stops a build; main builds it. A bare .cjs file loaded with import() runs in sloppy mode when a require() that never runs also names it, as on main.
On 4f3f631. 1 file, +4 -1: Source/JavaScriptCore/aot 1 file +4 -1. A variable that a function captures and that a loop of several versions asks for could stop the build: `const f = make(1);` and a counted loop that sums what `f(a, b)` gives. Fixed.
On e9687e9. 7 files, +123 -1: scripts 1 file +1 -1 . test 6 files +122 -0. Pins the preview build of oven-sh/WebKit#1028's commit 5473c680. [skip size check], as in 607519a. A variable that a function captures and that a loop of several versions asks for could stop the build: `const f = make(1);` and a counted loop that sums what `f(a, b)` gives. Fixed. Two stored programs: of captured variables that counted loops sum, and of results of calls that are unsigned integers. Four stored programs of CommonJS and ES modules that load each other are compared with stored outputs, since `bun run` itself does not always give the same. Known issues: in `bun test --aot`, a test file that mocks, with its original, a module of its own running graph, beside another such mock in a helper, can end with "never finished loading"; main does not. With `--aot-harden=proof-checks` a method that is replaced by that of another class stops a build, and a replaced generator method that is stepped by hand and some loops over lists of numbers can stop one; main builds all three. In `bun test --aot`, with `vi.doMock()`, a factory that imports its own file and `vi.importActual()`, and with a mock of a file that a package has imported before, the answer is wrong as on main, without main's notice. A bare .cjs file loaded with import() runs in sloppy mode when a require() that never runs also names it, as on main. With the React Compiler in the same build, a callback that isn't memoized can be left out of the executable, and calling it ends the program, as on main.
…omponents On 0e96f1b. 15 files, +188 -32: src 4 files +107 -32 . test 11 files +81 -0. Pins the same preview build, of oven-sh/WebKit#1028's commit 5473c680. [skip size check], as in 607519a. With the React Compiler in the same build, a callback that isn't memoized could be left out of the executable, and calling it ended the program. Fixed. With the React Compiler, a component beside an `export *` could stop the build. Fixed. Two more stored programs of CommonJS and ES modules are compared with stored outputs, since `bun run` itself does not always give the same. Known issues: in `bun test --aot`, a test file that mocks, with its original, a module of its own running graph, beside another such mock in a helper, can end with "never finished loading"; main does not. With `--aot-harden=proof-checks` a method that is replaced by that of another class stops a build, and a replaced generator method that is stepped by hand and some loops over lists of numbers can stop one; main builds all three. In `bun test --aot`, with `vi.doMock()`, a factory that imports its own file and `vi.importActual()`, and with a mock of a file that a package has imported before, the answer is wrong as on main, without main's notice. A bare .cjs file loaded with import() runs in sloppy mode when a require() that never runs also names it, as on main. A build's time doubles with each conditional join onto one field that a file read uses; main's does not.
On 01881b8. 12 files, +90 -8: src 2 files +57 -7 . test 10 files +33 -1. Pins the same preview build, of oven-sh/WebKit#1028's commit 5473c680. [skip size check], as in 607519a. With the React Compiler, a file that is imported only for code that the compiler drops did not run. Fixed. A stored program of how builds grow passes; its Todo line goes out. The tests of tsconfig globs compare fewer paths under Miri, where they took hours. Known issues: in `bun test --aot`, a test file that mocks, with its original, a module of its own running graph, beside another such mock in a helper, can end with "never finished loading"; main does not. With `--aot-harden=proof-checks` a method that is replaced by that of another class stops a build, and a replaced generator method that is stepped by hand and some loops over lists of numbers can stop one; main builds all three. In `bun test --aot`, with `vi.doMock()`, a factory that imports its own file and `vi.importActual()`, and with a mock of a file that a package has imported before, the answer is wrong as on main, without main's notice. A bare .cjs file loaded with import() runs in sloppy mode when a require() that never runs also names it, as on main. A build's time doubles with each conditional join onto one field that a file read uses; main's does not.
On 5473c68. 7 files, +186 -25: Source/JavaScriptCore/aot 3 files +45 -6 . Source/JavaScriptCore/b3 4 files +141 -19. In a loop, a remainder took the integer behind a sum past 2 ** 53 in place of the rounded number: where y + i reaches 2 ** 53 + 1, (y + i) % 3 was the remainder of 2 ** 53 + 1 instead of that of 2 ** 53. Fixed. With `--aot-harden=proof-checks`, a list that is read at its older length behind a change of its storage stopped a build: the verifier took the position of the access for a ground of it. Fixed. B3, plain Bun too: a Phi that follows its own Upsilon in a block is folded, so a Phi of that kind in a loop header is not taken for one that the loop carries. Assertion builds poison Value::owner pointers that are not up to date. This is #986.
On 087029c. 6 files, +168 -7: scripts 1 file +1 -1 . test 5 files +167 -6. Pins the preview build of oven-sh/WebKit#1028's commit 9ad6efd7. [skip size check], as in 607519a. In a loop, a remainder took the integer behind a sum past 2 ** 53 in place of the rounded number: where y + i reaches 2 ** 53 + 1, (y + i) % 3 was the remainder of 2 ** 53 + 1. Fixed in the engine; one stored program. With `--aot-harden=proof-checks`, a list that is read at its older length behind a change of its storage stopped a build. Fixed in the engine; two stored programs. The reader of Mach-O files that a test uses takes only sections of instructions for code: constants in __TEXT read as instructions. The scaling row of getters that a helper lays off a table is asked at 4,000 targets, where the peak of a build of nothing no longer decides the reading. Known issues: in `bun test --aot`, a test file that mocks, with its original, a module of its own running graph, beside another such mock in a helper, can end with "never finished loading"; main does not. With `--aot-harden=proof-checks` a method that is replaced by that of another class stops a build, a replaced generator method that is stepped by hand can stop one, and so can a test of a list's length against the length before a push() or pop(); main builds all three. In `bun test --aot`, with `vi.doMock()`, a factory that imports its own file and `vi.importActual()`, and with a mock of a file that a package has imported before, the answer is wrong as on main, without main's notice. A bare .cjs file loaded with import() runs in sloppy mode when a require() that never runs also names it, as on main. A build does not end where some eight or more branches of one if / else chain join onto the same field, or some 24 objects' fields are joined twice each from the next one's, and a file read uses the field; main's does.
On dfb5a20. 1 file, +1 -1: scripts 1 file +1 -1. Pins the merged commit of oven-sh/WebKit#1028, d837f045. [skip size check], as in 607519a. Known issues: in `bun test --aot`, a test file that mocks, with its original, a module of its own running graph, beside another such mock in a helper, can end with "never finished loading"; main does not. With `--aot-harden=proof-checks` a method that is replaced by that of another class stops a build, a replaced generator method that is stepped by hand can stop one, and so can a test of a list's length against the length before a push() or pop(); main builds all three. In `bun test --aot`, with `vi.doMock()`, a factory that imports its own file and `vi.importActual()`, and with a mock of a file that a package has imported before, the answer is wrong as on main, without main's notice. A bare .cjs file loaded with import() runs in sloppy mode when a require() that never runs also names it, as on main. A build does not end where some eight or more branches of one if / else chain join onto the same field, or some 24 objects' fields are joined twice each from the next one's, and a file read uses the field; main's does. In a stack trace, where the conversion of an operand throws, or a constructor whose object is kept in a variable, the caller's line names the start of the expression and not the operator; main names the operator.
main is at #1028 (d837f04). Conflicts: - heap/SlotVisitor.cpp, heap/Collector.cpp: #1028 and this branch each made the collector thread mark where a collection of compiled code has no helper thread. #1028 adds Collector::startsHelpersLazily() to the condition under which drainInParallelPassively() marks itself. Upstream moved that condition into Collector::runConcurrentPhase(), so the term of #1028 goes there. The change of 3b77c53 in drainInParallelPassively() has nothing left to decide and is removed: the function is upstream's again. - aot/tests/AOTCollectorTests.cpp: the test of #1028 and the test of 3b77c53 both stay. CollectorPause of #1028 gets a third value for the options of the second one. - assembler/MacroAssembler.h, MacroAssemblerARM64.h and MacroAssemblerX86_64.h: #1028 corrects operations that upstream corrected in WebKit/WebKit@40aa4a427250: a blinded addition, or32() of an immediate to an address, branchTest64() with the mask INT32_MIN, test32() and branchTest32() with a condition on the sign. The code is upstream's. What only #1028 has stays: generateTest32() does not narrow a test to one byte at an offset that would overflow. - assembler/ARM64Assembler.h: #1028 removes fmov_top(), whose size bits were wrong. Upstream corrected the bits and checks the encoding in testmasm, so the function stays. - b3/B3ReduceDoubleToFloat.cpp: isExactInDouble() of #1028 in front of a function that upstream marks NODELETE. - aot/facts/AOTUnitedFamilies.h: #1028 reads and writes the parents through a pointer, with WTF::atomicLoad() and WTF::atomicStore() in the form that upstream removed. The order is a template argument. - wasm/WasmOMGIRGenerator.cpp: #1028 puts a path in front of the context switch of an indirect call, for code that restores the pinned registers after the call. Upstream moved that context switch into the generator of the call, so the new path stands in front of the call patchpoint and the block of the old context switch stays removed.
The JavaScriptCore side of the next round of work on Bun's ahead-of-time compiler (
bun build --compile --aotandbun test --aot, both experimental). Bun's side is oven-sh/bun#48478. This follows #1025.Merge order
This merges first; then Bun's pull request pins the merged commit and runs CI once more.
What's in it
565 files, +24,718 −4,414 against main.
aot/walk: the main passaot/image: the image, WebAssembly in itaot/runtimeaot/testsaot/codegen,stubs,machine: code generationaot/aot/Outside
aot/In
heap/one condition and one comment, for compiled images; nothing in WTF or bmalloc, and nothing outsideSource/JavaScriptCore.wasm/WasmOMGIRGenerator.cpp,wasm/WasmOMGIRGenerator.hwasm/WasmCodeInImage.cpp,wasm/WasmCodeInImage.hand 11 moreb3/air/AirShrinkWrapFrame.cpp,b3/air/AirReuseConditionFlags.cppand 9 moreb3/B3FixSSA.cpp,b3/B3ThreadJumps.cppand 2 moreB3ThreadJumps.cpprun in plain Bun's optimizing JIT too.b3/B3EliminateRedundantPhiCycles.cpp,b3/B3Generate.cppb3/B3LowerToAir.cpp,b3/B3RangeReductions.cppand 3 moreb3/B3ReduceDoubleToFloat.cpp,b3/B3LowerMacros.cppand 1 moreassembler/MacroAssemblerX86_64.h,assembler/MacroAssemblerARM64.hand 4 moreassembler/testmasm.cpp,b3/testb3_bun_ranges.cppand 8 moreruntime/JSONObject.cpp,runtime/LiteralParser.cppJSON.stringify()writes an array of primitives of a compiled object in place; parses of a declared type ask the compiled parseryarr/YarrJIT.cpp,yarr/YarrSharedRoutines.cppand 1 moreENABLE(AOT)llint/LowLevelInterpreterAOT.asm,llint/LLIntSlowPaths.cppand 1 more[]in code made at run time takes one cellruntime/JSModuleLoader.cpp,runtime/JSMicrotask.cppinterpreter/Interpreter.cppruntime/JSCustomGetterFunction.h,runtime/JSFunction.cppand 4 morewasm/WasmFormat.cpp,wasm/WasmFormat.hand 4 moreheap/SlotVisitor.cppesbuild.wasm has 98,450 data segments; compiled ahead of time, its short job takes 6,800 page faults instead of 8,170. Plain Bun too, in the optimizing JIT: on x64 a WebAssembly load whose low bytes alone are used returned a value at the end of the memory where it should trap,
f32.demote_f64of a converted 64-bit integer rounded once where it rounds twice, and a signaling NaN stayed signaling throughf32.demote_f64off64.promote_f32. Fixed. On arm64 with DotProd,i32x4.relaxed_dot_i8x16_i7x16_add_s(x, x, c)in the optimizing JIT multiplied by the addend where the result tookx's register; that is fixed. The other corrections in the assembler are to operations that nothing calls with such operands.Changes
Executables
if (v < 3) { a = 5; continue; } if (v > 9) { b = 7; continue; }, took the first's arm for both where both arms are short. Fixed.bun run: positions, names of methods under computed keys, paths relative to the folder of the build.require()of an ES module whose graph cannot be loaded runs the CommonJS files in front of it, errors reach every load of a graph, and a file that is imported as text and as JSON no longer mixes up its forms.bun test --aotresetModules()inside a running graph.WebAssembly in the image
Speed, in instructions of our own benchmark rows
sort((a, b) => a - b), an element of 20,000 integersTextEncoderandTextDecoderFurther: lists in fields, rows of rows, nested loops, counters of any kind, views, BigInts, substrings and joined keys,
instanceof, own-property tests, getters and setters of host and class objects, results ofnode:pathandnode:fs.Size and build
Checks
--aot-harden=proof-checksverifies each stage.Wire::schemaHashchanges, so this engine and Bun's side go together.Known issues
bun test --aot--aot-harden=proof-checksbun test --aotvi.doMock(), a factory that imports its own file andvi.importActual(), and with a mock of a file that a package has imported before, the answer is wrong as on main, without main's notice.A line of a stack trace names the source relative to the folder of the build, so it no longer includes
import.meta.path; that is by design.Tests
Nothing was run on this tree before the push; CI is its first full run.