Skip to content

Upgrade WebKit to e8f704815e94 - #47112

Open
robobun wants to merge 14 commits into
mainfrom
robobun/81708f38/upgrade-webkit-e8f704815e94
Open

robobun wants to merge 14 commits into
mainfrom
robobun/81708f38/upgrade-webkit-e8f704815e94

Conversation

@robobun

@robobun robobun commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #44697

Problem

  • Bun's JavaScriptCore is 360 upstream commits behind WebKit/WebKit@e8f704815e94. 59 touch JavaScriptCore, WTF or bmalloc.
  • RegExp.lastMatch after a test() that matched can end the process: panic(main thread): Segmentation fault at address 0x4.
  • Two results are wrong: Math.sumPrecise([-1]) is -1.0000000000000002, and an async function that awaits itself through Promise.all repeats its frame in Error.stack.

Fix

  • WEBKIT_VERSION is a7b59e61de2e, the merge commit of Upgrade to upstream WebKit e8f704815e94 WebKit#923: the engine of main plus the upstream merge.
  • Upstream deleted one builtin: in test/aot/engine-tests, 2,648 builtin numbers are one lower and 19 instance offsets eight bytes lower. Loop bytecode changes, so bundler_bytecode_portable.test.ts has new snapshots.
  • Two test bounds go up, which needs a maintainer's yes: the --trim size in compile-aot-size.test.ts and the function count of one program (Notes).
  • Verified, local debug build: test/js/bun/jsc/webkit-upgrade-e8f704815e.test.ts (new, 17 cases, Bun 1.4.3-canary fails 4) and test/js/bun/jsc/regexp-legacy-static-properties.test.ts (new, the panic on Bun 1.4.3-canary). test/aot/engine-tests passes with a release testaot.

Background

  • WEBKIT_VERSION (scripts/build/deps/webkit.ts) names the oven-sh/WebKit release whose JavaScriptCore the build downloads.
  • The ahead-of-time compiler numbers the engine's JavaScript builtins by name order, and the instance of a compiled program has one word per link-time constant.

Downsides

  • Size: hello world with --aot --trim maps 167,808 bytes more. A compiled program that calls Array.from holds 22 functions, was 6.
  • A thread that breaks the collector's stop-the-world protocol now crashes a release build (Notes).
  • The own keys of the Proxy.revocable revoke function now start with length and name.
Notes

The WebKit side

oven-sh/WebKit#923 merges upstream e8f704815e94 into the fork. Its description lists the 16 conflicts and how each one is resolved.

The first CI run of that pull request built every lane and published a preview release. Its test jobs failed for three reasons. None of them is in the libraries that Bun links:

  • Upstream now links its programs with -z pack-relative-relocs (WebKit/WebKit@c47b7c073c). The jsc and testFFI programs of the Linux lanes then need glibc 2.36. The arm64 test runner has glibc 2.35.
  • One new upstream test (stress/regexp-legacy-static-properties-rematch-failure.js) cannot pass in two test modes that only the fork has.
  • Upstream deleted one JavaScript builtin. The tests of the ahead-of-time compiler name builtins by number, and the numbers behind the deleted one moved.

That pull request has a commit for each of them. After that, the fork's main got oven-sh/WebKit#914, #886 and #926, and Bun's main got the ahead-of-time compiler (#45631) with that WebKit (#47125). Both are merged in, over there and here.

A review of the merged head found one defect of the merge itself: with the object loop of JSON.parse that upstream rewrote, Bun.JSONL could give an object a Structure that a collection had freed. bcd362b31e over there repairs it and adds a stress test. With the jsc of the build before it, that test ends in SIGSEGV in 6 runs of 6.

The fork's main then moved three more times, and each time its Bun side had to be on Bun's main before this branch could pin a build with it:

oven-sh/WebKit main Bun side merged into oven-sh/WebKit#923 at
#927 #47129 be93e08e36
#930 #47129 35b64c4afb
#919, #936 (ICU 79.1), #937 #47281 b67a0b1a3a

The release zip sizes of an earlier version of this description compared builds 133970 and 133646: between -0.15% and +0.12% over the 12 release zips (bun-linux-x64.zip -38,546 bytes).

What Bun's CI showed

The head is e22cf6a525 now. On top of 3688162c4c it has the pin of the merge commit, the test for #44697 and one corrected comment. The engine source is the same as in build 135015.

Build 135015 ran 3688162c4c, the head with the answers below. 184 of its 188 jobs passed and none failed: every build, and every test job on Linux, Windows and macOS on ARM64. The four macOS x64 test jobs had not started when this was written. No agent of that queue was free, and three attempts of each job expired after an hour of waiting. Four tests failed once and passed on a retry or alone (bun-serve-html.test.ts, no-leak-records-at-exit.test.ts, hot.test.ts, process-stdin.test.ts).

Build 134977 ran the head before that, with main up to #47129 and the preview of 35b64c4afb. Three things failed that do not pass on a retry. 3688162c4c answers each:

Test Failure Answer
test/aot/engine/ir-1, ir-3, ir-5, ir-6 (nine .ir files, Linux x64) lower-to-b3: output line 7 does not match: Int64 b@22 = Load(b@10, offset = 8264, ..); nearest: .. offset = 8256 .. The offsets are written anew (see "The engine tests")
test/aot/parallel/arrays/searches-folds-and-flat-maps-of-a-list-bring-in-only-the-functions-that-do-them.js ImageFunctionsEmitted <= 40, but the build counted 42 The bound is 48. A maintainer has to agree
test/bundler/compile-aot-size.test.ts hello world with --trim maps 25,777,292 bytes, and the bound is 25,700,000 The bound is 25,880,000. A maintainer has to agree

Eight more tests failed and passed on a retry or alone (plugin-requests-run-ahead.test.ts, no-leak-records-at-exit.test.ts, compile-aot-run-time.test.ts, 43831.test.ts, filesink.test.ts, process-stdin.test.ts, body.test.ts, test-cluster-shared-leak.js).

The function count. The program calls Array.from({ length: 16 }) and six array methods. Its executable holds 24 functions with the canary of main and 42 here.

  • Upstream's Array.from (WebKit/WebKit@37aa2b5bd94d) steps an iterator by a call of its next and closes it by a call of its return. The old text was a for-of loop.
  • One function of the library runs for every call of Array.from, so the compiler keeps what that call of next can reach by name. The 18 more functions are the two helpers iteratorGenericNext and iteratorGenericClose, one part of Array.from that is moved out of line, and the next, return and throw of array iterators, generators, iterator helpers and wrapped iterators.
  • A one-line console.log(Array.from([1, 2, 3], x => x + 1).join()) holds 22 functions where main holds 6. Its bound is 24.
  • AOT: bun test --aot on large suites, module loading fixes, fuzzer fixes #47316 expands the two helpers in line in the front end (its engine half is [JSC] AOT: handlers at the end of the stack, text constants in flow, samples in native code WebKit#934). A release build of it keeps 25 functions for this program and 7 for the one-line program, so the bound of 40 holds again once that pull request is on main. The bound of 48 is needed only until then.

The size. test/bundler/compile-aot-size.test.ts, Linux x64 with glibc. Build 135015 is the commit of the canary plus this upgrade:

hello world canary of main (081ace731d) build 135015 bound
--aot 68,138,180 68,305,988 68,440,000
--aot --trim 25,584,836 25,752,644 25,700,000, now 25,880,000

Both grow by 167,808 bytes. .text of jsc grows by 143,376 bytes with the 360 upstream commits. The test says that its bounds only go down, so this is a maintainer's call. The new bound has about the slack the old one had (127,356 bytes, it was 115,164). The comment beside it says 183,000 bytes, which was measured on an older commit of main. The next push corrects it to 168,000.

Earlier runs (builds 134164 and 134232, before #47129, with the test names of that time) showed two more causes. Both are answered on the WebKit side:

  • A stack frame after { ...fn }. Bun names a frame by the own property name of the callee first, and by displayName after it. A copy that looks every key up makes name a property of a function. Upstream WebKit/WebKit@e04a6f9faecf copies from a function by its Structure and leaves name alone, and the run time of compiled programs still looks the keys up. Three programs printed another frame compiled than plain. A maintainer chose to turn upstream's fast path off in builds with the ahead-of-time compiler (780dc90982 of Upgrade to upstream WebKit e8f704815e94 WebKit#923). With a local debug build of this branch, errors/frames-functions-display-name-statement.js, errors/frames-functions-given-display-name-far.mjs and errors/names-frames-new-function-objects-names.mjs print the same compiled and plain. The cost: { ...fn } does not get upstream's speedup (160 ms against 50 ms on JSTests/microbenchmarks/object-spread-function.js).
  • Array.from in a compiled program held 441 functions. The flow analysis took the call of next inside the helper iteratorGenericNext for a call of a value nobody knows. [JSC] AOT: flow follows Proxy and the library helpers; typed JSON.parse; the main module WebKit#930 changed that rule, and 22 functions are left (above).

Build 134164 also showed a crash under collector stress (SIGSEGV at address 0 in WTF::findAtomBeforeAdding). Its cause was on main too: AOT::nameOfBuiltin() makes an atom while a collection ends, where Heap::pruneDeadReferences() has set the atom table of the thread to null.

The engine tests

test/aot/engine-tests has the IR text tests of the engine that need entries of Bun's table of builtins (1,601 files since #47129).

  • A test names a builtin that is written in JavaScript by its runtime function number. Upstream WebKit/WebKit@37aa2b5bd94d deleted defaultAsyncFromAsyncIteratorWithNext, which was number 23. Every number from 24 on is one lower: 2,648 numbers in 371 files. every is 30 (was 31), find 34, from 46, map 64 and some 88. The numbers of Bun's own functions start at 0x10000 and do not move. No file names a number that the engine's list does not have.
  • The instance of a compiled program holds one word for every link-time constant of the engine, and the deleted builtin was one. Every field behind that list is eight bytes lower. Nine tests check the B3 of a function line by line and had such offsets in 19 lines (8040, 8168, 8192, 8216, 8264). test/aot/tools/update-ir-tests.ts wrote them anew with a release testaot on Linux x64, and nothing else in those files changed.
  • With a release testaot and jsc built from this branch, testaot run test/aot/engine-tests passes: 1,599 files, 2 todo.
  • Upgrade to upstream WebKit e8f704815e94 WebKit#923 makes the same change of numbers in the engine's own tests.

The new test file

test/js/bun/jsc/webkit-upgrade-e8f704815e.test.ts has 17 cases. Four of them fail on Bun 1.4.3-canary, because the engine changes there:

Case Old engine New engine
Math.sumPrecise([-1]) -1.0000000000000002 -1
Own keys of the revoke function of Proxy.revocable, after length is deleted and defined again, against those of an arrow function after the same steps another order (name, length) the same order (length, name)
Error.stack of an async function that awaits its own promise through Promise.all the frame repeats one frame
[x, y].sort(boundComparator) when the comparator leaves optimized code (the program of oven-sh/WebKit#885) the caller gets Array.prototype.sort itself the caller gets the sorted array

The other 13 cases pass on both engines. They cover the code that the merge rewrote by hand: spread and rest of a function, the iterator protocol of Array.from, Object.groupBy, Map.groupBy and the iterator helpers, Array.fromAsync, Array.prototype.join, JSON.parse of objects with the same keys (also -0, invalid input and a reviver), Bun.JSONL.parseChunk cut at every position, loops that end in a null test or a negated comparison, and an anchored RegExp and a switch on a substring.

The bytecode snapshots

test/bundler/bundler_bytecode_portable.test.ts compares a hash of the serialized bytecode. 22 entries come from a local debug build with the merged engine. The entry bun build --bytecode libraries.js comes from CI: the bundle of that entry differs on the machine the other values come from. All ten test platforms of build 133970 reported the same value for it, and accepted the other 22.

Bun's CI on the first preview build

Build 133970 ran this branch with autobuild-preview-pr-923-b6e34894. One test failed on every platform: the snapshot entry above, which had its old value then. Eight other tests failed and passed on a retry (bake.test.ts, hot.test.ts, no-orphans.test.ts, spawn_waiter_thread.test.ts, test-zlib-invalid-input-memory.js, nth-anplusb-ident.test.ts, process.test.js, bun.test.ts).

The zip sizes in "The WebKit side" compare the release zips of that build with those of build 133646, which is main at the commit this branch started from. The range is over the 12 release zips. The unpacked binaries were not measured: the artifact store of CI is not reachable from the machine this was written on.

The crash of #44697

test() records only where the match is. A read of RegExp.lastMatch, RegExp.$1 or another legacy static property runs the match again for the captures. That second run can need more stack than the first, and when it failed, RegExpGlobalData::getBackref read through a null pointer. Upstream WebKit/WebKit@191803bb2304, which is in this range, throws a RangeError there.

BUN_JSC_maxRegExpStackSize=65536 bun -e '
  const re = new RegExp("^" + "(z)?".repeat(8) + "(?:a|b)+$");
  console.log(re.test(Buffer.alloc(100000, "a").toString()));
  console.log(RegExp.lastMatch.length);'
  • Bun 1.4.3-canary: true, then panic(main thread): Segmentation fault at address 0x4.
  • A debug build of this branch: true, then RangeError: Out of memory, also for RegExp.$1, RegExp.$9, RegExp["$&"] and RegExp.lastParen.

test/js/bun/jsc/regexp-legacy-static-properties.test.ts runs the program of upstream's stress test in a child with BUN_JSC_maxRegExpStackSize=65536 and compares every line that the child prints: matched: true for the first test(), a RangeError for each read and for an assignment to RegExp.input, and the values of the properties after the next match. USE_SYSTEM_BUN=1 bun test fails it with the panic, and bun bd test passes it. The test comes from #47160, which had the upstream commit alone (oven-sh/WebKit#929, closed now).

What ran locally

  • With the pin of the merge commit, a debug build with ASAN: webkit-upgrade-e8f704815e.test.ts passes 17 of 17, and regexp-legacy-static-properties.test.ts passes.
  • bun run build:local against the merged WebKit (Debug, no ASAN). -p '42' prints 42.
  • test/js/bun/jsc: 215 pass. Two domjit.test.ts cases pass their 20 s limit in this Debug build. The same two take 42 s on a debug build of main.
  • test/js/bun/jsonl, the worker tests, the GC tests and the heap snapshot tests pass.
  • JSType.h, OptionsList.h, the WebCore bindings scripts and the structured clone sources have no upstream change in this range. src/jsc/JSType.rs needs no change.

The three assertion commits

WebKit/WebKit@0bf94cf0c1, WebKit/WebKit@4bc9ad4d7f and WebKit/WebKit@cf89f1571e check the bits of Heap::m_worldState with RELEASE_ASSERT, in the functions through which a thread takes or gives up its access to the heap. Bun gives up heap access while the event loop waits (Bun__JSC_onBeforeWait) and takes it again after the wait. That order is the one the assertions check.

Upstream changes

Range: f59345e04a8d (2026-10-22) .. e8f704815e94 (2026-10-27). 59 of the 360 upstream commits touch Source/JavaScriptCore, Source/WTF or Source/bmalloc. 20 of the 59 change nothing that a JSCOnly build compiles: they touch a preferences yaml, a sandbox profile, the Xcode project, or code for Swift, GLib or Cocoa only.

Highlights

  • JSON.parse reads the keys it already knows in a loop of their own (6f5f583b55). A key found in JSONCache's name table, and a value that is a cached string or an integer of at most nine digits, are read through a local cursor. The lexer is only used for the other values. Upstream measures 1.13x on its JSON benchmarks (geometric mean), up to 1.32x.
  • Object spread and rest of a function take the fast path (e04a6f9fae): { ...fn } and let { a, ...rest } = fn are 5x faster. PostCSS does this once per LazyResult.
  • Array.from, %TypedArray%.from, Object.groupBy, Map.groupBy and Iterator.prototype.flatMap/some/every/find drive the iterator themselves (37aa2b5bd9). They no longer build a wrapper object for a for-of loop: 1.1x to 1.6x on the Array.from microbenchmarks.
  • Proxy.revocable is 2.9x faster (fad04e2527). The revoke function is a JSFunctionWithFields with a cached executable, and the result object has a cached Structure. The class ProxyRevoke is gone.
  • Loop conditions are one instruction more often (bd50166362). A null test or a negated compare that feeds a backward conditional jump is fused into the jump (jneq_null, jnless, ...). No opcode is new, but the bytecode of most loops changes.
  • The RegExp first-character filter reads substring ropes in place (b92d6a6bea), and String#match with a constant anchored RegExp uses the filter in the DFG. The same commit makes DFG/FTL switch on a substring rope read the width of the base string, not the bit cached in the rope.
  • Two DFG/FTL and Wasm miscompile fixes: an OSR exit in a bound Array#sort comparator returned past the sort call (f274ef4c03), and a cross-instance Wasm tail call copied its frame from one restoreFrameSize too low (0927910cf9).
  • The GC asserts its stop-the-world protocol in release builds (0bf94cf0c1, 4bc9ad4d7f, cf89f1571e). A mutator that takes or gives up heap access in a state the protocol does not allow crashes now.
  • Math.sumPrecise is correct for negative sums (8ee560d748): Math.sumPrecise([-1]) was -1.0000000000000002.

Runtime & builtins

  • e04a6f9fae (328286): @copyDataProperties and @cloneObject accept a JSFunction source on the path that reads the Structure, through the new JSFunction::canPerformFastEnumerablePropertyEnumeration(VM&). It needs an unmodified name and length, no accessors and no own __proto__. object-spread-function 4.99x, object-rest-function 5.54x.
  • 37aa2b5bd9 (328563): Five builtin files call @iteratorGenericNext in a for (;;) loop and close the iterator with @ifAbruptCloseIterator. The link-time constant defaultAsyncFromAsyncIteratorWithNext is removed, and defaultAsyncFromAsyncIterator has its body now.
  • fad04e2527 (328635): Proxy.revocable: see Highlights. Removed: ProxyRevoke.{h,cpp}, ProxyRevokeInlines.h, proxyRevokeSpace, JSGlobalObject::proxyRevokeStructure(). Added: proxyRevocableResultStructure(), VM::proxyRevokeExecutable(), JSFunctionWithFields::Field::RevocableProxy, the identifiers proxy and revoke. Observable: the own keys of the revoke function list length and name first, like any function.
  • e81441a5ec (328769): JSOnlyStringsAndInt32sJoiner is renamed JSExactLengthJoiner. Array#join keeps its exact-length path for arrays that also hold booleans, undefined or null.
  • 8ee560d748 (315450): WTF::Xsum::XsumSmall::compute() rounded a negative sum away from zero when it must not. Math.sumPrecise([-Number.MAX_VALUE]) was -Infinity.
  • 071ce01fcb (328147): Interpreter::getAsyncStackTrace stops at the second visit of a promise combinator. An async function that awaits its own promise through Promise.all no longer repeats its frame up to Error.stackTraceLimit, or hangs.
  • be583048f3 (328788): Identifier::add no longer does one atomic ref and one deref of the AtomStringImpl it returns.
  • ca48e0119c (328786): The last two USE(BIGINT32) blocks are deleted. Dead code.

Parser / bytecode generator

  • bd50166362 (328623): emitJumpIfTrue and emitJumpIfFalse no longer require a forward target for their fusions. while (node != null) and do {} while (!(a < b)) end in one fused jump. The subject names only the null tests. The diff also covers the ten negated compares.
  • 4e5e850636 (328770): BytecodeGeneratorBase<Traits> is folded into BytecodeGenerator. GenericLabel and GenericBoundLabel are the plain classes Label and BoundLabel, and Label::bind takes the instruction writer. BytecodeGeneratorBase.h is deleted and BytecodeGeneratorBaseInlines.h is renamed BytecodeGeneratorInlines.h. BytecodeList.rb loses the unused type :WasmBoundLabel, and no opcode.

DFG / FTL

  • f274ef4c03 (327983): An inlined bound function keeps the call kind of its caller. InlineCallFrame::BoundFunctionCall and BoundFunctionTailCall are removed. A bound comparator inlined into Array#sort is an ArraySortComparatorCall, so an OSR exit inside it restarts the sort. A recursive tail call through a bound function can become a jump in the FTL.
  • b92d6a6bea (328389): See Highlights. compileRegExpMatchFast (DFG) returns null without a call when the first byte cannot start a match.

RegExp

  • 59e81f3e12 (328828): A match array gets index, input, groups and indices written into its butterfly before the array cell exists. tryCreateUninitializedRegExpMatchesArray has a fifth parameter, and JSArray::tryCreateUninitializedRestrictedButterfly is new.
  • 1984003fd9 (328895): On x86_64 the Yarr JIT copies fewer than 16 ParenContext frame slots with straight-line code, and eight per loop iteration above that. regexp-parencontext-frame-slots 1.80x.
  • 191803bb23 (328417): RegExp.lastMatch, RegExp.$1 and the other legacy properties throw a RangeError when the lazy re-match fails. Before, they read a null array.

JSON

  • 6f5f583b55 (328615): See Highlights. The object branch of LiteralParser::parseRecursively is one loop with the lambdas parseValue, putWithTransition and finishObject. Lexer::skipWhitespaceBeforeKey() is removed, Lexer::setPtr() and end() are new, and numberOfDigitsForSafeInt32 moves to ParseInt.h.
  • 2dff3f1dbf (329046): Compile fix for the commit above with clang 20: three lambdas capture vm by name.

GC / heap

  • 0bf94cf0c1 (328987): Heap::stopTheMutator asserts that no mutator holds the conn, where it had a branch that returned false.
  • 4bc9ad4d7f (329005): Heap::resumeTheMutator asserts that the mutator is stopped, has no heap access and no conn. It returned early when the mutator was not stopped.
  • cf89f1571e (329038): Release asserts on Heap::m_worldState in acquireAccessSlow, releaseAccessSlow, setNeedCollectionEpilogue, setMutatorWaiting, clearMutatorWaiting, Collector::beginCollectionInEachHeap, finishChangingPhase and stopThePeriphery. Collector::requestCollection gets two debug asserts. Heap::needCollectionEpilogue() is new.

WebAssembly

  • 0927910cf9 (329002): Wasm::emitRestoreInstanceFrameIfNeeded starts its copy at the stack pointer from before the adjustment.

WTF

  • 45f193ae80 (328940): Thread::tryCreate() returns null when the thread cannot be created. Thread::create() calls it and still crashes. $.agent.start in the jsc shell throws.
  • e8f704815e (329066): The defaults of LOG_DISABLED, ASSERT_MSG_DISABLED, ASSERT_ARG_DISABLED, BACKTRACE_DISABLED, FATAL_DISABLED and ERROR_DISABLED move from Assertions.h to PlatformEnable.h.
  • bbfbc41dc9 (327419): The RELEASE_LOG* macros are defined once, on top of a WTF_RELEASE_LOG_SEND per backend. LOGF, SD_JOURNAL_SEND and LOG_ANDROID_SEND are removed.
  • 3757158274 (328861): allowCompactPointers<T>() looks at the AllowCompactPointers<T*> specialization first. WTF_ALLOW_COMPACT_POINTERS_TO_INCOMPLETE_TYPE(JSC::JSCell) moves from MicrotaskQueue.h to JSCJSValue.h.
  • 2b85a5e547 (328859): printInternal(PrintStream&, String&) is defined in PrintStream.cpp, not inline.
  • 38418ba096 (328643): NeverDestroyed and LazyNeverDestroyed read their storage through std::launder.
  • 701620c269 (328791): Vector::appendRange(Range&&) for a sized range. The rest of the commit is WebCore.
  • 272972fe8c (328652): WTF::initializeObjectIdentifierDomain(). Only WebKit's GPU process calls it.
  • Fixes in headers that nothing in JavaScriptCore uses: 961981e9e8 (MediaTime ordering), 2c813011e9 (WeakListHashSet const iterator), 3e90cc907c (LoggingHashMap), 474bb3fa62 (BitSet::setEachNthBit asserts n).

bmalloc / libpas

Bun's JavaScriptCore allocates through mimalloc, and the ASAN builds through the system allocator, so none of these changes what a shipped bun does.

  • c8ce68fbaa (328247): Each page config of a heap has its own minimum of objects per page. PAS_MIN_OBJECTS_PER_PAGE is renamed PAS_DEFAULT_MIN_OBJECTS_PER_PAGE, and PAS_MAX_OBJECT_SIZE takes the minimum as an argument.
  • 37e9021372 (328674): Medium segregated pages take objects up to 18432 bytes (from 11776).
  • ad3ad2c6ea (328277): Free small segregated pages stay committed 450 ms longer (900 ms on macOS).
  • 1ae8574624 (328282): Four more size classes in the designated intrinsic heap: 112, 144, 272 and 384 bytes.

Build system

  • 9c5e8c5fc1 (328481): CMake Debug builds compile bmalloc and FastMalloc.cpp with -O3, except with MSVC and clang-cl.
  • 3c41f3fb2f (329015): Symbols that only offlineasm output references get REFERENCED_FROM_ASM, for GCC LTO.

Nothing for a JSCOnly build (20)

2fe794df22, a46f812bfd, 0ae4c9d112, da8e478d08, 1d91ff5512, ac53330cbd, 319a9b6c84, 8655634bf3, e381818e3a, 280384b995, 933708cada, 3b48f12459, 17d262face, 4e2a07134b, d71f3d702b, 500be61de4, ad490a9bef, 58ebe7bde3, 8bd119f86f, 48fd938fe4: web preferences, sandbox profiles, the Xcode project, Swift bridging, GLib and GTK/WPE build files.


no test proof · iteration 7 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/bundler/compile-aot-size.test.ts, test/bundler/bundler_bytecode_portable.test.ts

WEBKIT_VERSION points at the preview build of oven-sh/WebKit#923, which
merges upstream WebKit e8f704815e94 into the fork. Move it to the release of
the merge commit before this lands.

test/js/bun/jsc/webkit-upgrade-e8f704815e.test.ts pins four things the new
engine changes and the behavior it had to keep in code that the merge
rewrote. The snapshot of bundler_bytecode_portable.test.ts has the new
serialized bytecode. Its entry for libraries.js keeps the old value until CI
reports the new one: the bundle of that entry differs on the machine the
other values come from.
@robobun

robobun commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 7:05 PM PT - Oct 29th, 2026

❌ @robobun, your commit 75bc370 has some failures in Build #135475 (All Failures)


🧪   To try this PR locally:

bunx bun-pr 47112

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

bun-47112 --bun

@robobun

robobun commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status: ready for review. oven-sh/WebKit#923 is merged, and WEBKIT_VERSION is its merge commit a7b59e61de. CI build 135475 on the head 75bc370f69 is finished: no test failed, and the four macOS x64 test jobs never ran. This needs a maintainer's yes to the two bounds below.

  • New on top of 3688162c4c: the pin of the merge commit, the regression test for Reading RegExp.lastMatch or RegExp.$1 after a long search segfaults at address 0x4 #44697 (test/js/bun/jsc/regexp-legacy-static-properties.test.ts, from Bump WebKit (oven-sh/WebKit#929 preview): a legacy static property of RegExp throws when its second match fails #47160) and the corrected comment beside the size bound. The merge commit has the same tree as the head whose preview build was pinned before, so the engine source did not change.
  • New in 75bc370f69, after a review comment: the case for Proxy.revocable no longer writes down the order of the keys of the revoke function. It expects the order of an arrow function that is treated the same way. Bun 1.4.3-canary still fails the case, and a local debug build passes it.
  • CI build 135475 on 75bc370f69 is finished. 184 of 188 jobs passed, which is every build and every test job on Linux, Windows and macOS on ARM64. The four macOS x64 test jobs never started: no agent of that queue was free for five hours, and each attempt expired after an hour of waiting. The build counts as failed for that reason alone. Eight tests failed once and passed on a retry or alone: testFFI.test.ts, no-leak-records-at-exit.test.ts, jsonl-parse-chunk-utf8.test.ts, bun-serve-html.test.ts, run-aot.test.ts, build-scaling-6.test.ts, no-orphans.test.ts and process-stdin.test.ts. The first three also show in the builds of other branches that day, in 40, 45 and 12 of 51 builds, and the build-scaling tests in 11 of 49.
  • CI build 135015 on 3688162c4c had no failed job: 184 of 188 jobs passed, which is every build and every test job on Linux, Windows and macOS on ARM64. The four macOS x64 test jobs did not start. No agent of that queue was free, and three attempts of each job expired after an hour of waiting.
  • main is merged in up to Remove the address space ballast and the mimalloc patches, upgrade mimalloc to v3.6.0 and ICU to 79.1 #47281. WEBKIT_VERSION is a7b59e61de2e, the merge commit of Upgrade to upstream WebKit e8f704815e94 WebKit#923: the engine that main pins (8c6c963e3c) plus the upstream merge. The CI of that pull request was green at its head.
  • Reproduced: USE_SYSTEM_BUN=1 bun test test/js/bun/jsc/webkit-upgrade-e8f704815e.test.ts fails 4 of 17 cases on Bun 1.4.3-canary. A local debug build of this branch passes 17 of 17, also with the pin of the merge commit.
  • Reproduced, Reading RegExp.lastMatch or RegExp.$1 after a long search segfaults at address 0x4 #44697: USE_SYSTEM_BUN=1 bun test test/js/bun/jsc/regexp-legacy-static-properties.test.ts ends with panic(main thread): Segmentation fault at address 0x4 after matched: true. A local debug build of this branch passes it.
  • Two bounds went up and need a maintainer's yes. They are one commit, 3688162c4c, which can be reverted alone:
    • test/bundler/compile-aot-size.test.ts: hello world with --aot --trim maps 25,752,644 bytes in build 135015. The canary of the same commit of main maps 25,584,836, so the upgrade adds 167,808 bytes. The bound was 25,700,000 and is 25,880,000 now.
    • test/aot/parallel/arrays/searches-folds-and-flat-maps-of-a-list-bring-in-only-the-functions-that-do-them.js: 42 functions where the bound was 40, and 24 on main. Upstream's Array.from calls next and return of its iterator by name, which keeps 18 more functions. The bound is 48 now. AOT: bun test --aot on large suites, module loading fixes, fuzzer fixes #47316 expands the two helpers in line and keeps 25 functions for this program, so the bound can be 40 again once that pull request is on main.
  • The nine IR text tests of the engine that failed in build 134977 pass: 19 offsets into the instance of a compiled program are eight bytes lower, because upstream deleted one link-time constant.
  • The two causes that earlier runs showed are answered on the WebKit side: the stack frame after { ...fn } (upstream's fast path is off in builds with the compiler) and the 441 functions of a compiled Array.from ([JSC] AOT: flow follows Proxy and the library helpers; typed JSON.parse; the main module WebKit#930).
  • Done of the steps to land: Upgrade to upstream WebKit e8f704815e94 WebKit#923 is merged as a merge commit, WEBKIT_VERSION is that commit, and the comment beside the size bound names the measured growth of 167,808 bytes. Left: the yes to the two bounds, and a green CI run on 75bc370f69.

Every platform of build 133970 serializes the same bytes for this entry:
21177768 bytes, sha256 620db040f8b0...194fced. The other 22 entries, which
came from a local build, match CI as they are.
…7c88a8

That pull request has four commits after the merge commit, for what its
first CI run found. None of them changes a library that Bun links: they
change how the programs of the Linux lanes are linked, and tests. Its CI is
green at this head, and this is the preview build of that head.
main got the ahead-of-time compiler (#45631) and WebKit fcc1ea0973a4
(#47125) after this branch was cut.

scripts/build/deps/webkit.ts is the one conflict. WEBKIT_VERSION is the
preview build of oven-sh/WebKit#923 at 0e103663, which has main's WebKit
(oven-sh/WebKit#914) merged into the upstream upgrade.

Upstream deleted one JavaScript builtin of the engine
(defaultAsyncFromAsyncIteratorWithNext, runtime function 23), so every
engine runtime function behind it is one lower. Five tests under
test/aot/engine-tests name such a function by number: every is 30, filter
32, from 46, map 64 and some 88. The numbers of Bun's own functions start at
0x10000 and stay.
…d362b3

That head has a repair for Bun.JSONL: with the object loop that upstream
rewrote, a streaming parse could give an object a Structure that a
collection had freed (bcd362b31e over there, with a stress test). It also
has the fork's main merged in again (oven-sh/WebKit#886 and #926). Its CI is
green.
main has #47129, the Bun side of oven-sh/WebKit#927 and #930.

The one conflict is WEBKIT_VERSION. It is now the preview build of
oven-sh/WebKit#923 at 35b64c4a: the upstream merge, the fork's main up to
f495db1d24 (what main pins) and the commit that keeps the generic copy for a
spread of a function.
Upstream WebKit 37aa2b5bd94d deleted the builtin
defaultAsyncFromAsyncIteratorWithNext, which was number 23 of the engine's
functions that are written in JavaScript. Every later number is one lower.

#47129 brought 1,222 more files to test/aot/engine-tests. 2,636 numbers in
366 of them change: each number from 24 on behind `runtimefunction id=` and
`enclosingRuntimeFunction=`, and behind `runtimefunction` in a comment. The
five files that this branch had already changed stay as they are.

Checked against the list of the pinned engine: no file names a number
that the list does not have, and 1,165 functions carry the number of the
builtin they are named like.
main has #47281 and pins oven-sh/WebKit 8c6c963e3c (ICU 79.1, no address
space ballast).

The one conflict is WEBKIT_VERSION. It is now the preview build of
oven-sh/WebKit#923 at b67a0b1a, which is the upstream merge on top of that
same 8c6c963e3c.
The instance of a compiled program holds one word for every link-time
constant of the engine. Upstream WebKit 37aa2b5bd94d deleted one
(defaultAsyncFromAsyncIteratorWithNext), so every field behind that list is
eight bytes lower: 8040 is 8032, 8168 is 8160, 8192 is 8184, 8216 is 8208
and 8264 is 8256.

Nine tests check the B3 of a function line by line and have those offsets
in 19 lines. test/aot/tools/update-ir-tests.ts wrote them anew with a
release testaot on Linux x64. Nothing else in the files changed.

With that testaot, test/aot/engine-tests passes: 1599 files, 2 todo.
compile-aot-size.test.ts: hello world with --aot --trim maps 25,777,292
bytes in CI where the bound is 25,700,000. The canary of main maps
25,584,836. The engine is larger: .text of jsc grows by 143,376 bytes with
the 360 upstream commits. The bound is 25,880,000 now, with the slack it
had before. The bound without --trim still holds (68,285,580 of 68,440,000).

test/aot/parallel/arrays/searches-folds-and-flat-maps-...: the executable
holds 42 functions where the bound is 40, and 24 on main. Upstream's
Array.from (37aa2b5bd94d) steps an iterator by a call of its `next` and
closes it by a call of its `return`. The 18 more functions are the two
helpers that do that and the `next`, `return` and `throw` of the library's
iterators. The bound is 48 now.

Both are a maintainer's call. This commit can be reverted alone.
@robobun

robobun commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator Author

A note on the function counts, from a build of this branch (3688162c4c, engine autobuild-preview-pr-923-b67a0b1a, Linux x64, release).

The 18 functions behind the new bound of 48 are the smaller part of what the hand-stepped Array.from of upstream costs a compiled program. Beside Object.groupBy the closed world opens:

One-line program This branch With the texts of main With the step and the close written in the caller's body
Object.groupBy(Array.from(new Set([1, 2, 3])), f) 193 functions, 133,769,696 bytes 7, 133,605,856 8, 133,605,856
Object.groupBy(list, f) and Map.groupBy(list, f) 195 9 10
list.values().some(f) and Object.groupBy(list, f) 194 69 11
Array.from([1, 2, 3], f) 22 6 7
searches-folds-and-flat-maps-of-a-list-...js 42 24 25
  • The counts are ImageFunctionsEmitted of bun build --compile --aot <file> --explain=statistics.
  • Columns two and three are the same binary with other engine texts in builtin_sources.bin. Nothing else differs. Column two has the texts of main for ArrayConstructor, MapConstructor, TypedArrayConstructor and JSIteratorPrototype.
  • The third row shows that main has the cause already: the iterator helpers call @iteratorGenericNext there.

The cause is how $iteratorGenericNext and $iteratorGenericClose reach the IR. The front end calls the linked functions of aot/builtins/handwritten/IteratorOperations.ts through a closure. Inside them the callee is a parameter, so the flow analysis does not follow it to the iterator of the calling statement. With nextMethod.$call(iterator) and the test of its result in the caller's body, the call is a member called on its holder, and the counts return.

With that form, 390 lines of iterator protocol probes print the same compiled and under bun run. 132 rows of test/aot/perf/compare-builds.ts (array-from, group-by, typed arrays) stay within 1%.

I am preparing this as a separate pull request against main of oven-sh/WebKit. With it the bound of 40 holds. The handwritten copy of Object.groupBy can then be derived from the text of upstream at no cost: today upstream's body through the linked function costs everyday-arrays/group-by 8.0% to 9.8% on six rows.

oven-sh/WebKit#923 is merged. Its merge commit has the same tree as the head
that the preview build autobuild-preview-pr-923-b67a0b1a was made from, and
that preview release goes away now that the pull request is merged.
… runs out of stack

test() records only where the match is. A read of RegExp.lastMatch, RegExp.$1
and the other legacy static properties runs the match again for the captures.
When that second run failed, the read ended the process: Segmentation fault at
address 0x4 (#44697). Upstream WebKit 191803bb2304, which this upgrade brings,
throws a RangeError there.

The test runs the program of upstream's stress test in a child with
BUN_JSC_maxRegExpStackSize=65536 and compares every line it prints.
CI build 135015 maps 25,752,644 bytes for hello world with --aot --trim, and
the canary of the same commit of main maps 25,584,836: 167,808 bytes more. The
comment said 183,000.
@sosukesuzuki
sosukesuzuki marked this pull request as ready for review October 29, 2026 20:29

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 that the old WebKit SHA (8c6c963e3c49) has no remaining duplicates anywhere in the repo, and that the new RegExp legacy-static-properties crash test is a spawned fixture that drains both pipes concurrently and asserts exact stdout/stderr before the exit code. The two raised test bounds (--trim mapped bytes and ImageFunctionsEmitted) are the parts that still need a maintainer's judgment.

Extended reasoning...

The change pins WEBKIT_VERSION to a7b59e61de2e in scripts/build/deps/webkit.ts, mechanically renumbers builtin ids and shifts nine B3 load offsets across 378 .ir engine tests, refreshes bytecode snapshots, raises two size/function-count bounds, and adds two new jsc tests. It touches no security-sensitive surface in this repo, but it swaps the JS engine itself, and the author explicitly flags two loosened test bounds as needing maintainer agreement, so a human look is warranted.

Comment thread test/js/bun/jsc/webkit-upgrade-e8f704815e.test.ts Outdated
…tion's

The case for Proxy.revocable expected ["length", "name"] from
Reflect.ownKeys(revoke) after `length` was deleted and defined again. That
list is JavaScriptCore's order for every function, and the specification
asks for the order of creation. A test that writes the list down would fail
the day the engine follows the specification.

What the upstream change guarantees is that the revoke function is an
ordinary function. The case now treats an arrow function the same way and
expects the two lists of keys to be equal. The old engine still fails it:
its revoke function lists name before length, and its arrow function does
not.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code review found no issues

No high-confidence issues detected in this change.

@robobun

robobun commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator Author

An update to my note above.

#47316 (commit b49dbef) now expands $iteratorGenericNext and $iteratorGenericClose in line in the front end, and its engine half is merged (oven-sh/WebKit#934). That is the change I described in the last column of my table. So I do not open the pull request that I announced here.

ImageFunctionsEmitted on a release build of b49dbef (Linux x64), beside this branch at 3688162c4c:

One-line program This branch #47316
Object.groupBy(Array.from(new Set([1, 2, 3])), f) 193 8
Object.groupBy(list, f) and Map.groupBy(list, f) 195 10
list.values().some(f) and Object.groupBy(list, f) 194 11
Array.from([1, 2, 3], f) 22 7
searches-folds-and-flat-maps-of-a-list-...js 42 25

The last row is the test whose bound this branch raises from 40 to 48. With #47316 it keeps 25 functions, so the bound of 40 holds again once that pull request is in.

This branch has not been deployed

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reading RegExp.lastMatch or RegExp.$1 after a long search segfaults at address 0x4

2 participants