Skip to content

Upgrade WebKit to 2b257999 - #30705

Merged
Jarred-Sumner merged 9 commits into
mainfrom
claude/upgrade-webkit-1f105af5bbd5
May 18, 2026
Merged

Upgrade WebKit to 2b257999#30705
Jarred-Sumner merged 9 commits into
mainfrom
claude/upgrade-webkit-1f105af5bbd5

Conversation

@sosukesuzuki

Copy link
Copy Markdown
Contributor

Upgrades WebKit to autobuild-preview-pr-231-2b257999 (commit 2b257999114b) (oven-sh/WebKit#231 — ~755 upstream commits since 5488984d20e0).

Bun-side changes

JSPromise rework (3f9955f695b6)

JSPromise is no longer JSInternalFieldObjectImpl<2>. The getPromiseInternalField/putPromiseInternalField bytecode intrinsics and the promiseField*/promiseState* intrinsic constants were removed upstream. Added three C++ host functions exposed as private globals — $peekPromiseStatus(p) (0/1/2 = pending/fulfilled/rejected), $peekPromiseSettledValue(p), and $pokePromiseAsHandled(p) — and rewrote all 26 builtin call sites across Peek.ts, CommonJS.ts, BundlerPlugin.ts, StreamInternals.ts, WritableStreamInternals.ts, ReadableStreamInternals.ts, and internal/util/inspect.js. The $isPromiseFulfilled/$isPromiseRejected/$isPromisePending codegen macros now expand to $peekPromiseStatus(...) === N.

C++ bindings (bindings.cpp, BunPlugin.cpp) that touched JSPromise::Field/internalField() switched to flags()/setFlags()/setSlot()/payloadCell().

RapidHash (ee2220df2080)

WTF replaced WyHash/SuperFastHash with RapidHash. The static-property lookup tables (*.lut.h) embed the string hash, so the create_hash_table perl script must produce the same hash the runtime computes. Replaced src/codegen/create_hash_table with the upstream RapidHash version and re-grafted Bun's ConstantInteger extension. Without this every static property on JSGlobalObject (Bun, fetch, process, ...) is invisible.

JSType enum (24cf2e544f58)

JSWebAssemblyStreamingContextType was inserted at slot 27 — src/jsc/JSType.rs/.zig re-numbered.

WebAssembly streaming hooks

compileStreaming/instantiateStreaming global hook signatures changed: the JSPromise* is now passed in instead of returned.

JSModuleNamespaceObject/AbstractModuleRecord

getModuleNamespace() gains a ModulePhase argument (import defer); Bun's shouldPreventExtensions parameter is preserved as a trailing default.

Known regression (pre-existing investigation)

Error inside minified file snapshot tests in inspect-error.test.js show an extra at require (native:50:24) frame. The require builtin's ImplementationVisibility::Private is no longer hiding the frame after the upstream PCH/builtin refactor — needs follow-up.

Upgrades the JavaScriptCore fork to upstream WebKit 49d2e914a4cc (~755 commits).

JSPromise lost its JSInternalFieldObjectImpl<2> layout. The
getPromiseInternalField/putPromiseInternalField bytecode intrinsics and the
promiseField*/promiseState* intrinsic constants are gone. Add three C++
host functions exposed as private globals -- $peekPromiseStatus(p),
$peekPromiseSettledValue(p), $pokePromiseAsHandled(p) -- and rewrite all
26 builtin call sites. Update bindings.cpp/BunPlugin.cpp to the new
flags()/setFlags()/setSlot()/payloadCell() accessors.

WTF replaced WyHash/SuperFastHash with RapidHash. Pull the upstream
create_hash_table perl script and re-graft Bun's ConstantInteger extension
so the *.lut.h string hashes match what the runtime computes; otherwise
every static property on JSGlobalObject (Bun, fetch, process) is invisible.

JSWebAssemblyStreamingContextType inserted at JSType slot 27 -- re-number
src/jsc/JSType.rs and the .zig porting reference.

compileStreaming/instantiateStreaming hooks now receive the JSPromise*
instead of returning it.

JSModuleNamespaceObject/AbstractModuleRecord gain a ModulePhase argument
for static import defer; preserve Bun's shouldPreventExtensions parameter
as a trailing default.
@sosukesuzuki
sosukesuzuki requested a review from alii as a code owner May 14, 2026 14:55
@robobun

robobun commented May 14, 2026

Copy link
Copy Markdown
Collaborator
Updated 8:40 PM PT - May 16th, 2026

@sosukesuzuki, your commit db90f70 has 1 failures in Build #55368 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 30705

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

bun-30705 --bun

@coderabbitai

coderabbitai Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Replaces direct promise internal-field access with new peek/poke intrinsics and host functions, renumbers JSType to add WebAssemblyStreamingContext, refactors WebAssembly streaming APIs to operate on provided promises, updates hash-table generator to rapidhash, and migrates builtins, C++ bindings, and tooling.

Changes

Promise Intrinsics and Infrastructure Refactoring

Layer / File(s) Summary
Host function declarations
src/jsc/bindings/ZigGlobalObject.cpp
Declare jsBunPeekPromiseStatus, jsBunPeekPromiseSettledValue, and jsBunPokePromiseAsHandled host functions.
Host function implementations
src/jsc/bindings/ZigGlobalObject.cpp
Add peekPromiseArgument helper and implement the three host functions to return promise status, settled value, and mark promises handled.
Builtin registration
src/jsc/bindings/ZigGlobalObject.cpp
Register the three new builtin private global getters that expose the host functions to JS.
WebAssembly streaming API refactor
src/jsc/bindings/ZigGlobalObject.cpp, src/jsc/bindings/ZigGlobalObject.h
Refactor handleResponseOnStreamingAction to accept an existing JSPromise*; change compileStreaming/instantiateStreaming to take a JSPromise* and return void, and forward the promise into the refactored handler.

Possibly related PRs

  • oven-sh/bun#30096: Also updates WEBKIT_VERSION in scripts/build/deps/webkit.ts.
  • oven-sh/bun#22499: Related WebKit prebuilt/tag selection change affecting WebKit version handling.

Suggested reviewers

  • alii
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Upgrade WebKit to 2b257999' directly and concisely summarizes the main change: upgrading WebKit to a specific commit hash.
Description check ✅ Passed The description comprehensively covers both required sections: 'What does this PR do?' is answered with detailed breakdown of Bun-side changes, and 'How did you verify your code works?' is addressed through the mention of test failures and CI results.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

Copy link
Copy Markdown
Contributor

Found 8 issues this PR may fix:

  1. Segfault in JIT worker thread during DFG LICM phase on macOS aarch64 (Bun v1.3.11) #28842 - DFG LICM phase segfault is entirely within JSC JIT internals; ~755 upstream commits may include fixes
  2. Crash in DFG JIT: Illegal instruction in pas_utils.h (WTF::Vector<DFG::AbstractValue> resize) #29412 - DFG CFA + bmalloc illegal instruction crash is in JSC JIT internals; upstream fixes plausible
  3. crash in JSC::MarkedBlock::Handle::sweep #24194 - JSC GC sweep crash in MarkedBlock::Handle::sweep; upstream GC fixes plausible
  4. [Bug]: std::span assertion failure in JSC::UnlinkedArrayProfile when compiling with bytecode: true #29351 - std::span assertion in JSC::UnlinkedArrayProfile during bytecode compilation; upstream fixes plausible
  5. Character set intersection of complement classes in v-mode regular expressions not considered #30183 - JSC Yarr regex engine incorrectly handles character set intersection of complement classes in v-mode; upstream spec compliance fix plausible
  6. Inconsistent escape character requirement for - in vnicode regular expressions #29003 - JSC Yarr regex parser incorrectly accepts unescaped - in v-mode character classes; upstream spec compliance fix plausible
  7. Bun throws Error instead of AggregateError on Promise.reject within Promise.any #29157 - Promise.any throws Error instead of AggregateError; JSPromise internals rework in this PR directly touches promise state handling
  8. Large performance regression in some popular CSV parsing libs (Canary vs 1.2.2) #17373 - CSV parsing performance regression explicitly traced to a prior WebKit upgrade; new upstream commits + RapidHash change may affect this

If this is helpful, copy the block below into the PR description to auto-close these issues on merge.

Fixes #28842
Fixes #29412
Fixes #24194
Fixes #29351
Fixes #30183
Fixes #29003
Fixes #29157
Fixes #17373

🤖 Generated with Claude Code

@coderabbitai coderabbitai 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.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/codegen/replacements.ts`:
- Around line 259-268: The replacement for
isPromiseFulfilled/isPromiseRejected/isPromisePending produces malformed JS
because it slices inner.result with slice(0,-1); update both occurrences so the
debug and non-debug branches use inner.result directly (leave the surrounding
template strings and status logic intact) — change the slices in the args
assignment within the isPromise* handling to use inner.result instead of
inner.result.slice(0, -1) so the generated calls to
__intrinsic__peekPromiseStatus receive the full expression.

In `@src/js/builtins.d.ts`:
- Around line 137-140: The declared return type of $peekPromiseSettledValue is
incorrect: it currently returns V unconditionally but the JSDoc says it may
return undefined for pending promises; update the declaration for function
$peekPromiseSettledValue<V>(promise: Promise<V>) to return V | undefined so
callers are forced to handle the pending case and the type matches the
documented behavior.

In `@src/js/builtins/BundlerPlugin.ts`:
- Around line 257-261: In BundlerPlugin's logic where deferred onStart()
promises are enqueued (the block checking $isPromise(ret) and
$peekPromiseStatus(ret)), mark the promise as handled before stashing it by
calling $pokePromiseAsHandled(ret) immediately prior to pushing into
self.promises; this change in the onStart/setup path (used by
loadAndResolvePluginsForServe()) prevents transient unhandled-rejection warnings
for rejected promises that are later awaited via Promise.all.

In `@src/jsc/bindings/bindings.cpp`:
- Around line 3801-3806: The code is manually setting JSC::JSPromise flags
(checking JSC::JSPromise::isFirstResolvingFunctionCalledFlag and OR-ing
JSC::JSPromise::isHandledFlag) which bypasses the proper bookkeeping; replace
the direct flag manipulation with a call to promise->markAsHandled() wherever
you currently set isHandledFlag (e.g., the branch that checks
isFirstResolvingFunctionCalledFlag around the rejected-promise handling and the
analogous site later in the file), so that markAsHandled() performs the
canonical handling transition and updates any related internal state.

In `@src/jsc/bindings/BunPlugin.cpp`:
- Around line 850-853: The code currently mutates a plugin-owned rejected
promise by calling promise->setFlags(...) to make it appear fulfilled; instead
remove that setFlags call and mark the rejection as handled by calling
promise->markAsHandled() (follow the pattern used in
ModuleLoader.cpp/ZigGlobalObject.cpp), then read result = promise->result() and
return JSValue::encode(result) without altering the promise flags or other bits
so you don't clear/shared state on the original JSPromise::Status::Rejected.

In `@src/jsc/bindings/ZigGlobalObject.cpp`:
- Around line 1738-1743: The function jsBunPeekPromiseStatus conflates "not a
promise" with the enum value 0 (pending) by returning jsNumber(0) when
peekPromiseArgument(callFrame) fails; change the guard in jsBunPeekPromiseStatus
so that when peekPromiseArgument returns null/invalid you return a sentinel
numeric value outside the promise-status enum range (e.g., -1 or a value > max
enum) instead of 0, so callers can distinguish "not a promise" from "pending" —
update the return expression and any callers that check for 0 to treat the
sentinel as the invalid-input case; reference jsBunPeekPromiseStatus and
peekPromiseArgument to locate the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 44479310-3a75-4020-8776-7776de340d02

📥 Commits

Reviewing files that changed from the base of the PR and between 19d8ade and db61eff.

📒 Files selected for processing (18)
  • scripts/build/deps/webkit.ts
  • src/codegen/create_hash_table
  • src/codegen/replacements.ts
  • src/js/builtins.d.ts
  • src/js/builtins/BunBuiltinNames.h
  • src/js/builtins/BundlerPlugin.ts
  • src/js/builtins/CommonJS.ts
  • src/js/builtins/Peek.ts
  • src/js/builtins/ReadableStreamInternals.ts
  • src/js/builtins/StreamInternals.ts
  • src/js/builtins/WritableStreamInternals.ts
  • src/js/internal/util/inspect.js
  • src/jsc/JSType.rs
  • src/jsc/JSType.zig
  • src/jsc/bindings/BunPlugin.cpp
  • src/jsc/bindings/ZigGlobalObject.cpp
  • src/jsc/bindings/ZigGlobalObject.h
  • src/jsc/bindings/bindings.cpp

Comment thread src/codegen/replacements.ts Outdated
Comment thread src/js/builtins.d.ts Outdated
Comment thread src/js/builtins/BundlerPlugin.ts
Comment thread src/jsc/bindings/bindings.cpp Outdated
Comment thread src/jsc/bindings/BunPlugin.cpp
Comment thread src/jsc/bindings/ZigGlobalObject.cpp
…on builds

The non-debug branch concatenated a stray open paren before the
sliced inner.result, leaving the expanded expression with one unclosed
group: (__intrinsic__peekPromiseStatus((promise) === 1). Use inner.result
verbatim (it already includes the surrounding parens) so the expansion
becomes (__intrinsic__peekPromiseStatus(promise) === 1).
Comment thread src/codegen/replacements.ts Outdated
Comment thread src/jsc/JSType.zig Outdated
…cord order

The promise reaction rework allocation-frees the first then/await
reaction by packing it into the JSPromise itself
(InlineReactionKind::InternalMicrotask carries the await generator
context in m_slot). getAwaitingGenerator only walked the heap
JSPromiseReaction list, so async stack frames vanished for the common
single-await case. Handle inline reactions before falling back to the
heap list.

Going through HashMap<String, String> for CookieMap's record initializer
scrambles insertion order, and the order shifted when WTF moved its
string hash to RapidHash. Build a Vector<Vector<String>> sequence
instead and update the basic-iterator snapshot to the now-deterministic
insertion order.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/build/deps/webkit.ts`:
- Line 6: The current WEBKIT_VERSION value ("autobuild-preview-pr-231-5568b798")
is too long and non-unique when prebuiltDestDir() truncates
cfg.webkitVersion.slice(0, 16), causing different autobuild tags to collide;
update either WEBKIT_VERSION or the prebuiltDestDir logic so the cache key
preserves enough entropy: change WEBKIT_VERSION to a shorter, stable identifier
(e.g., a unique build hash or numeric id) or modify prebuiltDestDir() to include
a longer slice or a hash of cfg.webkitVersion instead of slice(0,16) so
different autobuild tags (referencing WEBKIT_VERSION and
prebuiltDestDir()/cfg.webkitVersion.slice(0,16)) produce distinct prebuilt cache
directories.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0a90fbcf-d2e9-47b2-891d-e5e5a9ec038a

📥 Commits

Reviewing files that changed from the base of the PR and between 0cab2a0 and 60eac77.

📒 Files selected for processing (4)
  • scripts/build/deps/webkit.ts
  • src/jsc/bindings/bindings.cpp
  • src/jsc/bindings/webcore/JSCookieMap.cpp
  • test/js/bun/cookie/cookie-map.test.ts

Comment thread scripts/build/deps/webkit.ts Outdated
oven-sh/WebKit#231 landed; switch from the preview-build tag to the
release built from main.
Comment thread src/jsc/bindings/ZigGlobalObject.cpp
…hrows

The streaming hook used to return a freshly created promise. The host
function caller (webAssemblyCompileStreamingFunc) propagated any pending
exception into a rejected promise. After the upstream rework the caller
allocates the outer promise itself, the hook returns void, and the new
caller (the webAssemblyCompileStreaming internal microtask) does not
catch exceptions. Any throw out of the hook left the outer promise
permanently pending. Reject it before returning instead.
- JSType.zig: move the JSMicrotaskDispatcher doc comment back below the
  WebAssemblyStreamingContext entry so each entry has the right comment
  (matches the .rs mirror).
- builtins.d.ts: $peekPromiseSettledValue documents that it returns
  undefined for a pending promise — make the type signature match.
- bindings.cpp: use markAsHandled() instead of twiddling isHandledFlag
  directly in JSC__JSPromise__result and JSC__JSInternalPromise__setHandled.
  Same effect, but the intent reads more clearly.
- BundlerPlugin.ts: mark deferred onStart() promises as handled when they
  are stashed on self.promises. The aggregate Promise.all handler is only
  attached later in loadAndResolvePluginsForServe(), and a synchronous
  rejection landing while the promise is just sitting in the array would
  trip the unhandled-rejection tracker.
@Jarred-Sumner
Jarred-Sumner merged commit 390e0dc into main May 18, 2026
76 of 77 checks passed
@Jarred-Sumner
Jarred-Sumner deleted the claude/upgrade-webkit-1f105af5bbd5 branch May 18, 2026 04:36
Jarred-Sumner pushed a commit that referenced this pull request May 18, 2026
Upgrades WebKit to `autobuild-preview-pr-231-2b257999` (commit
`2b257999114b`) (oven-sh/WebKit#231 — ~755 upstream commits since
`5488984d20e0`).

## Bun-side changes

### `JSPromise` rework (`3f9955f695b6`)
`JSPromise` is no longer `JSInternalFieldObjectImpl<2>`. The
`getPromiseInternalField`/`putPromiseInternalField` bytecode intrinsics
and the `promiseField*`/`promiseState*` intrinsic constants were removed
upstream. Added three C++ host functions exposed as private globals —
`$peekPromiseStatus(p)` (`0`/`1`/`2` = pending/fulfilled/rejected),
`$peekPromiseSettledValue(p)`, and `$pokePromiseAsHandled(p)` — and
rewrote all 26 builtin call sites across `Peek.ts`, `CommonJS.ts`,
`BundlerPlugin.ts`, `StreamInternals.ts`, `WritableStreamInternals.ts`,
`ReadableStreamInternals.ts`, and `internal/util/inspect.js`. The
`$isPromiseFulfilled`/`$isPromiseRejected`/`$isPromisePending` codegen
macros now expand to `$peekPromiseStatus(...) === N`.

C++ bindings (`bindings.cpp`, `BunPlugin.cpp`) that touched
`JSPromise::Field`/`internalField()` switched to
`flags()`/`setFlags()`/`setSlot()`/`payloadCell()`.

### RapidHash (`ee2220df2080`)
WTF replaced WyHash/SuperFastHash with RapidHash. The static-property
lookup tables (`*.lut.h`) embed the string hash, so the
`create_hash_table` perl script must produce the same hash the runtime
computes. Replaced `src/codegen/create_hash_table` with the upstream
RapidHash version and re-grafted Bun's `ConstantInteger` extension.
Without this every static property on `JSGlobalObject` (`Bun`, `fetch`,
`process`, ...) is invisible.

### `JSType` enum (`24cf2e544f58`)
`JSWebAssemblyStreamingContextType` was inserted at slot 27 —
`src/jsc/JSType.rs`/`.zig` re-numbered.

### WebAssembly streaming hooks
`compileStreaming`/`instantiateStreaming` global hook signatures
changed: the `JSPromise*` is now passed in instead of returned.

### `JSModuleNamespaceObject`/`AbstractModuleRecord`
`getModuleNamespace()` gains a `ModulePhase` argument (`import defer`);
Bun's `shouldPreventExtensions` parameter is preserved as a trailing
default.

## Known regression (pre-existing investigation)
`Error inside minified file` snapshot tests in `inspect-error.test.js`
show an extra `at require (native:50:24)` frame. The `require` builtin's
`ImplementationVisibility::Private` is no longer hiding the frame after
the upstream PCH/builtin refactor — needs follow-up.

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
robobun added a commit that referenced this pull request May 18, 2026
- wtf-bindings.cpp: assert -> ASSERT (same as #30992; #30705 dropped
  the transitive <cassert> include so debug builds fail to compile)
- semver.test.ts: 30s timeout on the 1e5-iteration memory-churn test
  and the subprocess regression test; both exceed the 5s default under
  debug+ASAN on constrained runners
Jarred-Sumner pushed a commit that referenced this pull request May 19, 2026
…#31010)

A version range with >=256 `||`/whitespace-separated comparators
panicked the **debug** build (`attempt to add with overflow`, exit 133)
where released Bun fails gracefully. Debug-only: Cargo's dev profile
enables `overflow-checks`; the release profile doesn't, so release
wrapped (256 -> 0) exactly like Zig's ReleaseFast.

`SemverQuery::parse` used `let mut count: u8 = 0;` with `count += 1;` —
the 256th increment overflows. `count` is only ever compared `== 0`,
never stored or cast, so a wider type is strictly more correct (it also
avoids Zig's silent u8 wrap, which was a latent bug that merely didn't
crash).

Widen `count` to `u32`. Regression test (`Bun.semver.satisfies` with 300
`||` comparators, run in a subprocess since the failure was an
uncatchable abort) passes on the debug build and matches released Bun.

---

Also carried:
- `wtf-bindings.cpp`: `assert` → `ASSERT` — identical to #30992; the
WebKit upgrade in #30705 dropped the transitive `<cassert>` include, so
debug builds currently fail to compile on `main`. Needed here so the
regression test can run under `bun bd`.
- 30s timeout on the pre-existing `failures does not cause weird memory
issues` test (1e5 iterations) and the new subprocess test — both exceed
the 5s default under debug+ASAN.

### Verification

```
$ bun bd test test/cli/install/semver.test.ts
 23 pass  0 fail
$ ./build/release/bun test test/cli/install/semver.test.ts
 23 pass  0 fail
```

With the `u32` change reverted, the new test fails on debug with `panic:
attempt to add with overflow (src/semver/SemverQuery.rs:981)` on stderr
and exit 132.

---------

Co-authored-by: robobun <117481402+robobun@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants