Skip to content

Bump WebKit (oven-sh/WebKit#458 preview): Math.sumPrecise rounds negative sums to nearest - #39455

Open
robobun wants to merge 1 commit into
mainfrom
farm/4b6365ed/webkit-sum-precise-negative-rounding
Open

Bump WebKit (oven-sh/WebKit#458 preview): Math.sumPrecise rounds negative sums to nearest#39455
robobun wants to merge 1 commit into
mainfrom
farm/4b6365ed/webkit-sum-precise-negative-rounding

Conversation

@robobun

@robobun robobun commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

Problem

  • Math.sumPrecise returns every exactly representable negative sum one ulp too large in magnitude: Math.sumPrecise([-1]) is -1.0000000000000002, [-0.5] is -0.5000000000000001, [1, -2] is -1.0000000000000002, [-Number.MAX_VALUE] is -Infinity. A negative sum just inside a power of two is rounded onto it too ([-2, 2 ** -52] gives -2 instead of -1.9999999999999998). Positive sums and inexact negative sums are right. Same on Bun 1.3.14, so not a 1.4 regression; found by a differential fuzz run against an exact reference, where all 468 of 10,000 mismatching arrays fell into this one bucket.
  • The cause is in JavaScriptCore, not in Bun: WTF::Xsum::XsumSmall::compute() (Source/WTF/wtf/PreciseSum.cpp, the port of xsum that Math.sumPrecise sums into; arrays longer than 1000 elements use XsumLarge, which is rounded by the same function). Its negative branch rounds away from zero whenever no bit below the two guard bits is set, where the original xsum only does so for guard bits 11, or 10 on an odd mantissa (a tie). An exactly representable sum has no bits set below the mantissa, so it always took that path. Upstream WebKit has the same code.

Fix

  • WTF: PreciseSum rounds negative sums to nearest, ties to even (Math.sumPrecise was one ulp off on every exact negative sum) WebKit#458 restores xsum's decision table for negative sums; the positive branch is unchanged. Verified there by linking the changed file against the c6cfe90c prebuilt WTF and comparing it bit for bit with Radford Neal's xsum.c on 3.75 million generated inputs (805,856 with a negative, exactly representable sum; the unmodified file mismatches on 20% of them), plus a 30,000 input sample against exact rational arithmetic.
  • This PR pins WEBKIT_VERSION at that PR's preview build, autobuild-preview-pr-458-ffe26339, so CI runs Bun against it. The WebKit branch sits on current fork main (eeab0404, which is also what main pins since Upgrade WebKit to 47f7250137c6 #39371), so the preview is exactly the current pin plus this fix.
  • Before landing, WTF: PreciseSum rounds negative sums to nearest, ties to even (Math.sumPrecise was one ulp off on every exact negative sum) WebKit#458 has to merge and WEBKIT_VERSION has to be repointed at the resulting main sha (the build prints that instruction itself once the preview release disappears); I will push that repoint when the merge happens. If another bump that already contains next always using default port for stylesheet, even when running on another port #458 lands first, this PR reduces to the fixture and its registration.
  • Test: test/js/bun/jsc-stress/fixtures/math-sum-precise-negative-rounding.js, the JSTests/stress file from the WebKit PR with the usual // @bun first line, registered in jsc-stress.test.ts. It checks 58 cases: the sums from the report, each rounding outcome for a negative sum (quarter ulp, three quarters, ties on even and odd mantissas, the same ties with one subnormal added or removed so the scan of the lower accumulator chunks decides, magnitudes just inside a power of two, the -MAX_VALUE neighbourhood including the -Infinity tie) and the positive mirror images. Each case runs as an array, as the same array padded past the 1000 element XsumLarge threshold with cancelling pairs, and as a generator (always XsumSmall), plus three plain 1001 element arrays. All 119 expected values were checked against exact rational arithmetic independently of any engine.
  • Fail before: on the current pin (eeab0404, process.versions.webkit checked), bun bd test test/js/bun/jsc-stress/jsc-stress.test.ts -t math-sum-precise fails at the fixture's first case with Math.sumPrecise([-1]) returned -1.0000000000000002, expected -1; the same happens on the previous c6cfe90c pin and on the released bun, and 92 of the fixture's 238 assertions fail on those engines.
  • Pass after: with this pin (process.versions.webkit reports preview-pr-458-ffe26339), the fixture passes and the whole jsc-stress.test.ts passes its 116 fixtures (debug + ASAN build; the new fixture takes about 1.2 s there and 30 ms on the release jsc shell); test/js/bun/jsc/temporal-global.test.ts passes as well.

Background

  • Bun links a prebuilt JavaScriptCore from oven-sh/WebKit; scripts/build/deps/webkit.ts pins which build. An engine fix lands as a WebKit PR plus a pin bump here, and the autobuild-preview-pr-* releases let the bump PR run Bun's suite against the WebKit PR before it merges.
  • test/js/bun/jsc-stress/ runs files taken verbatim from WebKit's JSTests/stress under bun, so the engine test is shared with the WebKit PR as is.
  • xsum keeps the sum exactly, in an array of 64-bit chunks that each hold a window of the sum's binary expansion, and rounds once at the end. After carry propagation the top chunk carries the sign and all chunks below it are non-negative, so for a negative sum the bits that get discarded reduce the magnitude rather than add to it, which is why the rounding rules for the two signs are different tables and the negative one could be wrong on its own.
  • A sum is "exactly representable" when it fits in a double's 53 bit mantissa, which is the common case for real inputs (integers, money in cents, anything with few significant bits); those are exactly the inputs with nothing below the guard bits, so the bug hit the common case and spared the inexact one.
Earlier revision of this PR

The first revision pinned autobuild-preview-pr-458-7051f3b5, built from a WebKit branch based just before the 47f7250137c6 upstream upgrade so that it would build against Bun main before #39371 landed. #39371 has landed (main now pins eeab0404), so the WebKit branch was rebased onto current fork main (same three files, identical content) and this PR was rebased onto main; the only conflict was the WEBKIT_VERSION line, resolved to the new preview tag.

Repro
for (const c of [[-1], [-0.5], [-1, -1], [1, -2], [-(2 ** 53)], [-Number.MAX_VALUE], [-0.1], [-0.1, -0.2], [1]])
  console.log(JSON.stringify(c), Math.sumPrecise(c));
bun 1.4.0 (WebKit c6cfe90c):
[-1] -1.0000000000000002
[-0.5] -0.5000000000000001
[-1,-1] -2.0000000000000004
[1,-2] -1.0000000000000002
[-9007199254740992] -9007199254740994
[-1.7976931348623157e+308] -Infinity
[-0.1] -0.10000000000000002
[-0.1,-0.2] -0.30000000000000004
[1] 1

this PR:
[-1] -1
[-0.5] -0.5
[-1,-1] -2
[1,-2] -1
[-9007199254740992] -9007199254740992
[-1.7976931348623157e+308] -1.7976931348623157e+308
[-0.1] -0.1
[-0.1,-0.2] -0.30000000000000004
[1] 1

[decide:webkit] gate passed · iteration 0 · 3 files touched

passes on PR (with fix)
Test-only change.

Debug/ASAN (expected pass):
$ bun bd test 'test/js/bun/jsc-stress/jsc-stress.test.ts'
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test test/js/bun/jsc-stress/jsc-stress.test.ts
bun test v1.4.0 (8326d1bd3)

test/js/bun/jsc-stress/jsc-stress.test.ts:
(pass) JSC JIT Stress Tests > JS (Baseline/DFG/FTL) > ftl-arithsqrt.js [352.02ms]
(pass) JSC JIT Stress Tests > JS (Baseline/DFG/FTL) > ftl-arithcos.js [368.71ms]
(pass) JSC JIT Stress Tests > JS (Baseline/DFG/FTL) > ftl-arithsin.js [445.73ms]
(pass) JSC JIT Stress Tests > JS (Baseline/DFG/FTL) > ftl-arithtan.js [428.30ms]
(pass) JSC JIT Stress Tests > JS (Baseline/DFG/FTL) > ftl-string-equality.js [432.51ms]
(pass) JSC JIT Stress Tests > JS (Baseline/DFG/FTL) > ftl-string-strict-equality.js [379.15ms]
(pass) JSC JIT Stress Tests > JS (Baseline/DFG/FTL) > ftl-string-ident-equality.js [380.11ms]
(pass) JSC JIT Stress Tests > JS (Baseline/DFG/FTL) > ftl-library-substring.js [346.87ms]
(pass) JSC JIT Stress Tests > JS (Baseline/DFG/FTL) > ftl-regexp-exec.js [406.21ms]
(pass) JSC JIT Stress Tests > JS (Baseline/DFG/FTL) > ftl-regexp-test.js [440.84ms]
(pass) JSC JIT Stress Tests > JS (Baseline/DFG/FTL) > ftl-getmyargumentslength.js [358.76ms]
(pass) JSC JIT Stress Tests > JS (Baseline/DFG/FTL) > ftl-getmyargumentslength-inline.js [388.82ms]
(pass) JSC JIT Stress Tests > JS (Baseline/DFG/FTL) > ftl-get-my-argument-by-val.js [395.11ms]
(pass) JSC JIT Stress Tests > JS (Baseline/DFG/FTL) > ftl-get-my-argument-by-val-inlined.js [388.72ms]
(pass) JSC JIT Stress Tests > JS (Baseline/DFG/FTL) > ftl-get-my-argument-by-val-inlined-and-not-inlined.js [453.82ms]
(pass) JSC JIT Stress Tests > JS (Baseline/DFG/FTL) > ftl-call-exception.js [408.78ms]
(pass) JSC JIT Stress Tests > JS (Baseline/DFG/FTL) > ftl-call-varargs-exception.js [371.13ms]
(pass) JSC JIT Stress Tests > JS (Baseline/DFG/FTL) > ftl-call-exception-no-catch.js [399.61ms]
(pass) JSC JIT Stress Tests > JS (Baseline/DFG/FTL) > ftl-try-catch-arith-sub-exception.js [493.29ms]
(pass) JSC JIT Stress Tests > JS (Baseline/DF
... (truncated)
Exit: 0
diff hotspot
scripts/build/deps/webkit.ts                       |   2 +-
 .../fixtures/math-sum-precise-negative-rounding.js | 123 +++++++++++++++++++++
 test/js/bun/jsc-stress/jsc-stress.test.ts          |   2 +
 3 files changed, 126 insertions(+), 1 deletion(-)

gate history · 2 passed · 0 rejected · iteration 0

evidence per changed file
file                                                      reads  edits  tests
scripts/build/deps/webkit.ts                                  1      2      0
…c-stress/fixtures/math-sum-precise-negative-rounding.js      0      0      0
test/js/bun/jsc-stress/jsc-stress.test.ts                     1      1      0

@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 2 minutes

Limit details: You’ve used all 5 included reviews currently available under your plan.

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 69636a71-e933-4cb4-9c88-b368147097ce

📥 Commits

Reviewing files that changed from the base of the PR and between dc59d3e and 3b4cb6e.

📒 Files selected for processing (3)
  • scripts/build/deps/webkit.ts
  • test/js/bun/jsc-stress/fixtures/math-sum-precise-negative-rounding.js
  • test/js/bun/jsc-stress/jsc-stress.test.ts

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

@robobun

robobun commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator Author

Status

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

I reviewed this PR and didn't find any bugs in the Bun-side changes. Deferring to a human because the PR description states WEBKIT_VERSION must be repointed from the autobuild-preview-pr-458-* tag to a main sha (and coordinated with #39371) before landing — that sequencing decision needs a maintainer.

What was reviewed:

  • prebuiltUrl/prebuiltDestDir in webkit.ts handle the autobuild- prefix correctly, so the preview tag resolves and caches without collision.
  • The new fixture follows the existing jsc-stress pattern (// @bun header, registered in jsFixtures, throws on mismatch so exit code drives the assertion).
  • Spot-checked several expected values ([-2, ULP/2] tie-to-even → -2, [-MAX, -(2**970)]-Infinity, [-1, 1]+0 via Object.is) — they match IEEE-754 round-to-nearest-even.
Extended reasoning...

Overview

The PR bumps WEBKIT_VERSION in scripts/build/deps/webkit.ts from sha c6cfe90c… to the preview tag autobuild-preview-pr-458-7051f3b5, adds a 123-line JSC stress fixture exercising Math.sumPrecise rounding of negative sums, and registers it in jsc-stress.test.ts. The actual fix lives in oven-sh/WebKit#458; the Bun-side change is the pin plus the regression test.

Security risks

None. The change is a dependency version pin to a build produced by Bun's own oven-sh/WebKit CI, plus a pure-computation test fixture. No user input handling, auth, crypto, or network paths are touched.

Level of scrutiny

Medium-high. While the Bun-side diff is mechanically simple (a one-line pin change, a fixture list append, and a self-contained test file), a WebKit pin bump swaps the entire JS engine underneath Bun. Per the repo's "Dependencies & vendoring" guidance, dep bumps warrant maintainer review. More importantly, the PR description is explicit that this pin is temporary: oven-sh/WebKit#458 must merge first, then WEBKIT_VERSION must be repointed at the resulting main sha, and landing order must be coordinated with #39371 (which carries the Bun-side changes for the post-upgrade WebKit main). Landing the preview tag as-is would ship a non-main WebKit build.

Other factors

  • The build script already special-cases autobuild- prefixed versions in both prebuiltUrl (uses the tag verbatim) and prebuiltDestDir (strips the prefix for the cache key), so the preview tag is handled correctly for CI purposes.
  • The test fixture is thorough: 58 base cases covering exact/inexact negative sums, quarter/half/three-quarter-ulp rounding, ties on even and odd mantissas, power-of-two boundaries, the MAX_VALUE/-Infinity edge, positive mirrors, and the +0 cancellation case — each run through both XsumSmall (array + generator) and XsumLarge (padded past 1000 elements). Assertions use Object.is so -0/+0 and NaN are compared correctly.
  • The fixture matches the harness convention exactly (throws on failure → nonzero exit → expect(exitCode).toBe(0) fails with stdout/stderr logged).
  • No prior reviewer comments to address; the only timeline entry is a CodeRabbit rate-limit notice.

@robobun

robobun commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator Author

Confirming the sequencing for whoever lands this: the preview tag is not meant to be merged. Once oven-sh/WebKit#458 merges I will push the repoint of WEBKIT_VERSION to the resulting main sha here; because fork main already contains the 47f7250137c6 upgrade, that repoint can only build once #39371's Bun-side changes are in. If #39371 is repointed after #458 merges instead, it carries the fix and I will reduce this PR to the fixture and its registration. The engine change itself is reviewed in oven-sh/WebKit#458; the status comment above tracks the state.

…tive sums to nearest

Every exactly representable negative sum came back one ulp too large in
magnitude (Math.sumPrecise([-1]) was -1.0000000000000002 and
[-Number.MAX_VALUE] was -Infinity): the rounding step of WTF's xsum port
rounded a negative sum away from zero whenever no bit below the guard
bits was set. oven-sh/WebKit#458 restores xsum's decision table; this
pins its preview build and runs the stress test from that PR as a
jsc-stress fixture.
@robobun
robobun force-pushed the farm/4b6365ed/webkit-sum-precise-negative-rounding branch from 387bd73 to 3b4cb6e Compare August 18, 2026 05:14
@robobun

robobun commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto main (3b4cb6e). #39371 moved main's WEBKIT_VERSION to eeab0404, which was the one conflict; it is resolved to autobuild-preview-pr-458-ffe26339, the preview of oven-sh/WebKit#458 after re-applying that commit on top of eeab0404, so the pinned engine is now exactly main's engine plus the fix and the earlier dependency on #39371's landing order is gone. Re-verified on the rebased branch: the fixture fails on the eeab0404 pin and passes on the new preview, where the whole jsc-stress file passes (116/116, debug + ASAN). PR description updated; the previous revision is summarized in a details block there.

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

I reviewed this PR and didn't find any issues in the Bun-side changes. Since WEBKIT_VERSION is currently pointed at a preview tag that (per the description) must be repointed to a merged oven-sh/WebKit main sha before landing, and the engine change itself lives in oven-sh/WebKit#458, a maintainer should coordinate the landing sequence rather than this being auto-approved.

What was reviewed:

  • The new fixture's expected values (round-to-nearest-even at ±half-ulp, ties on even/odd mantissas, power-of-two boundaries, -MAX_VALUE overflow tie, +0 on cancellation via Object.is) — spot-checked against IEEE 754 semantics.
  • The >1000-element padding and generator variants correctly exercise the XsumLarge and forced-XsumSmall paths; cases.slice() avoids mutating during iteration.
  • prebuiltUrl() / prebuiltDestDir() in webkit.ts already handle the autobuild- prefix, so the preview pin resolves correctly.
Extended reasoning...

Overview

This PR changes three files: (1) scripts/build/deps/webkit.ts bumps WEBKIT_VERSION from the eeab0404… sha to the preview tag autobuild-preview-pr-458-ffe26339; (2) a new 123-line JSC stress fixture math-sum-precise-negative-rounding.js covering Math.sumPrecise rounding of negative sums; (3) a two-line addition registering that fixture in jsc-stress.test.ts. The actual behavioral fix is in oven-sh/WebKit#458 (the xsum negative-branch rounding table in WTF::Xsum::XsumSmall::compute), not in this repo.

Security risks

None. The change is a dependency pin plus a test fixture. The fixture is plain arithmetic with no I/O, network, or filesystem access; it runs in a spawned subprocess like every other jsc-stress fixture.

Level of scrutiny

High — not because the Bun-side diff is complex (it isn't; the fixture and registration are straightforward and follow the existing jsc-stress conventions exactly), but because:

  • WEBKIT_VERSION controls which JavaScriptCore engine every Bun build links. Bumping it is effectively a dependency upgrade of the JS engine.
  • The pin is currently at a preview tag. The PR description and the robobun status comment both explicitly state the preview tag is not meant to be merged and must be repointed to the resulting oven-sh/WebKit main sha once #458 lands there.
  • The correctness of the engine change (the xsum rounding-table fix) can only be reviewed in oven-sh/WebKit#458, not here.

Other factors

The fixture itself is well-constructed: it uses Object.is so -0/+0 and NaN are distinguished, covers each rounding outcome for negative sums plus positive mirrors, exercises both accumulator sizes (array vs. >1000-element padded array vs. generator), and the expected values I spot-checked are consistent with IEEE 754 round-to-nearest-even. The registration in jsc-stress.test.ts matches the neighboring entries. webkit.ts already special-cases autobuild--prefixed versions in both prebuiltUrl() and prebuiltDestDir(), so the preview tag is handled by existing machinery. No bugs were found by the bug-hunting system. Given the explicit landing-order dependency on an unmerged upstream PR and that WebKit bumps are maintainer-coordinated, deferring rather than auto-approving.

@robobun

robobun commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator Author

Agreed, this stays on the preview tag until oven-sh/WebKit#458 merges; I will repoint WEBKIT_VERSION at the resulting main sha as soon as it does. CI for the rebased head is build 100401, running now; the status comment above is kept current, so there is nothing else pending on this side.

@robobun

robobun commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 10:34 PM PT - Aug 17th, 2026

@robobun, your commit 3b4cb6e1eeb6ab768f4a913035090d04ae2884bc passed in Build #100401! 🎉


🧪   To try this PR locally:

bunx bun-pr 39455

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

bun-39455 --bun

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.

2 participants