Skip to content

jsc: sub-quadratic BigInt toString and multiply - #35904

Closed
robobun wants to merge 2 commits into
mainfrom
farm/fcb484e0/bigint-subquadratic
Closed

robobun wants to merge 2 commits into
mainfrom
farm/fcb484e0/bigint-subquadratic

Conversation

@robobun

@robobun robobun commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

BigInt.prototype.toString (and everything that uses it: toLocaleString, Intl.NumberFormat.prototype.format(bigint), template literals) is quadratic in the digit count. So are BigInt * and **. Every doubling of the digit count quadruples the time:

$ bun -e 'for (const k of [25000n,50000n,100000n,200000n]){const b=10n**k;const t=performance.now();b.toString();console.log(k+1n+" digits: "+(performance.now()-t).toFixed(1)+"ms")}'
25001 digits: 5.8ms
50001 digits: 23.0ms
100001 digits: 91.6ms
200001 digits: 365.3ms

Cause

JavaScriptCore's JSBigInt::toStringGeneric repeatedly divides by a one-digit chunk divisor (10^19 on 64-bit), so an n-digit value takes ~n divideSingle passes of ~n work each. The only multi-digit multiply is schoolbook, so * and ** are quadratic too.

Fix

oven-sh/WebKit#354 adds Karatsuba multiplication (used by *, **), Burnikel-Ziegler recursive division, and a divide-and-conquer toString that splits by repeatedly-squared powers of the chunk divisor. All three paths are O(n^log2(3)) instead of O(n^2).

This PR bumps WEBKIT_VERSION to that change and adds test/js/bun/jsc/bigint-subquadratic.test.ts, which checks correctness across the 32/64-digit algorithm thresholds and asserts that the 12500-to-100000-digit toString growth stays within the sub-quadratic factor.

Measured (Linux x64 release, best of three; node v26.3.0 for reference)

before after node
200k-digit toString(10) 365.3 ms 8.6 ms 12.8 ms
100k-digit toString(10) 91.6 ms 3.0 ms 5.6 ms
50k-digit toString(10) 23.0 ms 1.1 ms 2.1 ms
100k x 100k-digit multiply 26.4 ms 3.5 ms 1.9 ms

Scaling per doubling drops from x4.0 to x2.9. Decimal toString now beats node. Multiply is 7.5x faster but V8 is still ahead (it also has Toom-3 and FFT above Karatsuba).

Note: WEBKIT_VERSION is currently pointed at the PR-preview release for oven-sh/WebKit#354; it needs updating to the merged main sha before this lands.

Bumps WebKit to pick up oven-sh/WebKit#354, which adds Karatsuba
multiplication, Burnikel-Ziegler division, and a divide-and-conquer
toString to JSBigInt. BigInt.prototype.toString, toLocaleString,
Intl.NumberFormat.format(bigint), * and ** all inherited the previous
O(n^2) behavior; they are now O(n^log2(3)).

The new test covers correctness across the 32/64-digit algorithm
thresholds and asserts that decimal conversion of a 12500-digit vs
100000-digit value stays within the sub-quadratic growth factor.
@coderabbitai

coderabbitai Bot commented Jul 26, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 59 minutes

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: f27ee088-c013-4b62-8238-71ea31936816

📥 Commits

Reviewing files that changed from the base of the PR and between 44f6469 and 6d36b91.

📒 Files selected for processing (2)
  • scripts/build/deps/webkit.ts
  • test/js/bun/jsc/bigint-subquadratic.test.ts

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

@robobun

robobun commented Jul 26, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 1:17 AM PT - Jul 26th, 2026

❌ @autofix-ci[bot], your commit 6d36b91 has 2 failures in Build #82288 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 35904

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

bun-35904 --bun

@robobun

robobun commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator Author

Build currently fails because WEBKIT_VERSION points at the preview release for oven-sh/WebKit#354, which the WebKit CI is still building (all 36 lanes pending). Once that publishes as autobuild-preview-pr-354-a1b937f0, bun bd will pick up the prebuilt and the test will pass.

Verified locally against a --webkit=local build:

(pass) BigInt sub-quadratic arithmetic > decimal toString round-trips across algorithm thresholds
(pass) BigInt sub-quadratic arithmetic > Karatsuba multiplication is correct across the threshold
(pass) BigInt sub-quadratic arithmetic > large BigInt decimal conversion scales sub-quadratically
 3 pass  0 fail

I'll push to re-trigger once the WebKit autobuild is up.

@github-actions

Copy link
Copy Markdown
Contributor

Found 2 issues this PR may fix:

  1. Poor BigInt Performance in Bun compared to other runtimes #23166 - Reports BigInt arithmetic ~2x slower than Node/Deno; this PR's Karatsuba multiplication and Burnikel-Ziegler division close that algorithmic gap
  2. Huge bigints work both in Deno and Node, but not in Bun #15072 - Reports RangeError: Out of memory on huge BigInt operations that work in Node/Deno; the O(n²) → O(n^1.585) improvements drastically reduce intermediate allocation pressure

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

Fixes #23166
Fixes #15072

🤖 Generated with Claude Code

* From https://github.com/oven-sh/WebKit releases.
*/
export const WEBKIT_VERSION = "549170099226f816a4b204ea1d8fa102fb79eefa";
export const WEBKIT_VERSION = "autobuild-preview-pr-354-a1b937f0";

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.

🔴 WEBKIT_VERSION is set to autobuild-preview-pr-354-a1b937f0, a PR-preview release tag rather than a merged main-branch sha. Preview autobuild releases on oven-sh/WebKit are ephemeral and get deleted when the upstream PR closes, at which point prebuiltUrl() will 404 for every build at this commit. As the PR description already notes, this needs updating to the merged main sha before landing.

Extended reasoning...

What the bug is

scripts/build/deps/webkit.ts:6 sets WEBKIT_VERSION = "autobuild-preview-pr-354-a1b937f0", replacing the previous 40-hex commit sha. This is a PR-preview autobuild tag for oven-sh/WebKit#354, not a durable release ref. The file's own doc comment (lines 1-5) says the value is a "WebKit commit — determines prebuilt download URL + what to checkout for local mode … From https://github.com/oven-sh/WebKit releases", and the PR description explicitly states: "WEBKIT_VERSION is currently pointed at the PR-preview release for oven-sh/WebKit#354; it needs updating to the merged main sha before this lands."

Code path that triggers it

In prebuilt mode (the default for CI and most contributors), webkit.source(cfg) returns { kind: "prebuilt", url: prebuiltUrl(cfg), … }. prebuiltUrl() computes:

const tag = version.startsWith("autobuild-") ? version : `autobuild-${version}`;
return `https://github.com/oven-sh/WebKit/releases/download/${tag}/${name}.tar.gz`;

So the download URL becomes …/releases/download/autobuild-preview-pr-354-a1b937f0/bun-webkit-<os>-<arch><suffix>.tar.gz. That release exists right now because the WebKit PR is open, but preview autobuild releases are cleaned up once the PR merges/closes. After cleanup, this URL 404s and every fresh build (bun bd, CI, release builds) fails at the WebKit fetch step.

Why existing code doesn't prevent it

The build system does have first-class handling for autobuild- tags — prebuiltUrl() skips re-prefixing, and prebuiltDestDir() uses the whole tag as the cache-dir key rather than slice(0, 16). That handling exists precisely so preview tags can be tested via --webkit-version=<tag> (per the doc comment) without corrupting the cache. It makes the preview tag work today; it does nothing to make the ref durable. There is no fallback URL, no pinned mirror, and local mode (vendor/WebKit/) checks out the same value, which is not a valid commit sha to git checkout.

Impact

Landing as-is pins main to an artifact with a lifetime measured in days. Once oven-sh/WebKit#354 merges and its preview release is deleted:

  • Every CI build on any branch based on this commit fails at dependency fetch.
  • Every contributor without a cached webkit-preview-pr-354-a1b937f0* extraction cannot build.
  • git bisect across this commit becomes impossible without manual WebKit surgery.

This is a concrete regression in something that worked before (builds at any historical commit can fetch their pinned WebKit), which is why it's marked normal rather than nit despite the author already being aware — flagging it here prevents an accidental merge before the sha swap.

Step-by-step proof

  1. cfg.webkit === "prebuilt" (default) → source() returns prebuiltUrl(cfg).
  2. cfg.webkitVersion = "autobuild-preview-pr-354-a1b937f0"; version.startsWith("autobuild-") is true, so tag = version verbatim.
  3. Fetch URL: https://github.com/oven-sh/WebKit/releases/download/autobuild-preview-pr-354-a1b937f0/bun-webkit-linux-amd64.tar.gz.
  4. JSBigInt: sub-quadratic multiply, divide, and toString WebKit#354 merges → its autobuild-preview-pr-354-* release is garbage-collected per the WebKit repo's preview-release policy.
  5. GitHub returns 404 for the URL in step 3 → build aborts before compilation.

Fix

After oven-sh/WebKit#354 merges, replace the value with the resulting 40-hex main-branch commit sha (the one that carries the Karatsuba/Burnikel-Ziegler/D&C-toString changes), matching the format of the previous value 549170099226f816a4b204ea1d8fa102fb79eefa. No other code changes needed — prebuiltUrl() and prebuiltDestDir() already handle plain shas correctly.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Acknowledged; this is intentional while oven-sh/WebKit#354 is open so CI builds against the change. It will be swapped to the merged main sha before this lands (also noted in the PR description).

@robobun

robobun commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator Author

Closing in favor of #40299. This PR bumps WebKit to oven-sh/WebKit#354. That PR is superseded by oven-sh/WebKit#507, which is merged. #507 ports V8's sub-quadratic multiply, divide, toString and BigInt(string), adds an interruption check inside them, and raises the BigInt cap to 1 << 30 bits.

#40299 is the WebKit bump for #507 and fixes #39964. Its test/js/bun/jsc/bigint-large.test.ts checks multiplication, division and toString at every algorithm crossover of the new code, so the coverage in bigint-subquadratic.test.ts is included there.

@robobun robobun closed this Aug 25, 2026
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