Skip to content

Add DFG/FTL intrinsics for DataView BigInt64/BigUint64 accessors#294

Open
robobun wants to merge 1 commit into
mainfrom
bun/dataview-bigint-intrinsics
Open

Add DFG/FTL intrinsics for DataView BigInt64/BigUint64 accessors#294
robobun wants to merge 1 commit into
mainfrom
bun/dataview-bigint-intrinsics

Conversation

@robobun

@robobun robobun commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Problem

getBigInt64 / getBigUint64 / setBigInt64 / setBigUint64 on DataView have no JIT intrinsics, so every call is a C++ host call that also heap-allocates a JSBigInt. That makes them ~25x slower than the equivalent pair of inlined Uint32 DataView accesses. Reported in oven-sh/bun#34231.

Change

Wires the four methods into the existing DataViewGetInt / DataViewSet DFG nodes as the byteSize == 8 integer case:

  • set: the value child is speculated as a heap BigInt (HeapBigIntUse) and unboxed inline: low digit with the sign applied, which is exactly the modulo-2^64 wrap the spec requires, for any digit count. Fully inline in both DFG and FTL.
  • get: the 64-bit load and byte swap are inline. The DFG tier boxes the result via operationInt64ToBigInt / a new operationUint64ToBigInt; the FTL inline-allocates a one-digit heap BigInt (zero stays canonical with length 0, m_hash zeroed, sign via the per-cell bit) and only calls out on allocator slow paths, following the compileMakeRope pattern.
  • JSBigInt_length / new JSBigInt_data0 abstract heaps are Mutable since the FTL fast path now stores to them; JSBigInt grows offsetOfHash().
  • Inlining of the set intrinsics is disabled under USE(BIGINT32) (heap-BigInt speculation would always exit for small BigInts).

Results

1 MiB byte-swap microbenchmark from the bun issue (x64 Linux, release JSC):

benchmark before after
setBigUint64(o, getBigUint64(o, false), true) loop 199 MiB/s 1.59 GiB/s
set-only loop 418 MiB/s 5.23 GiB/s
two-Uint32 equivalent (reference) 4.7 GiB/s 4.7 GiB/s

Testing

  • New JSTests/stress/dataview-jit-bigint64.js: round-trips interesting values (int64/uint64 boundaries, multi-digit BigInts wrapping mod 2^64, zero canonicalization) across all endianness shapes (constant true/false, omitted, variable), checks OOB RangeError, TypeError/SyntaxError coercion paths after optimization, detached and resizable ArrayBuffers, and a GC stress loop. Passes on the ASAN debug build with --useConcurrentJIT=0, --jitPolicyScale=0 --validateGraph=1, and default options.
  • All existing JSTests/stress/dataview-*.js and v8-dataview-*.js pass on the ASAN debug build.
  • bun built against this WebKit passes a 200k-iteration JIT round-trip smoke test.

getBigInt64/getBigUint64/setBigInt64/setBigUint64 were plain host calls,
making them ~25x slower than the equivalent pair of Uint32 DataView
accesses, which are inlined by the JITs.

This wires the four methods into the existing DataViewGetInt/DataViewSet
DFG nodes as the byteSize == 8 integer case:

- set: the value child is speculated as a heap BigInt and unboxed inline
  (low digit with the sign applied, which is exactly the required
  modulo-2^64 wrap). The store is fully inline in both DFG and FTL.
- get: the 64-bit load and byte swap are inline; the BigInt result is
  boxed via operationInt64ToBigInt / a new operationUint64ToBigInt in
  the DFG tier, while the FTL inline-allocates a one-digit heap BigInt
  (zero stays canonical with length 0) and only calls out on allocator
  slow paths.

The JSBigInt length/digit abstract heaps become Mutable since the FTL
fast path now stores to them, and JSBigInt grows an offsetOfHash()
accessor so the inline allocation can zero m_hash.

On a 1 MiB byte-swap microbenchmark (setBigUint64(o, getBigUint64(o,
false), true) over the whole buffer), throughput goes from ~200 MiB/s
to ~1.7 GiB/s on x64, and a set-only loop goes from ~420 MiB/s to
~5.2 GiB/s.

See oven-sh/bun#34231.
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Changes

DataView BigInt64 and BigUint64 intrinsics are registered and integrated into DFG and FTL JIT lowering. The implementation adds BigInt conversion and storage paths with endian handling, updates BigInt heap metadata, and introduces extensive stress and correctness coverage.

DataView BigInt JIT support

Layer / File(s) Summary
Intrinsic registration and DFG contracts
Source/JavaScriptCore/runtime/Intrinsic.h, Source/JavaScriptCore/runtime/JSDataViewPrototype.cpp, Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp, Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h, Source/JavaScriptCore/dfg/DFGFixupPhase.cpp, Source/JavaScriptCore/dfg/DFGDoesGC.cpp
Registers BigInt DataView getters and setters and propagates their 8-byte BigInt type and GC behavior through DFG analysis.
DFG BigInt conversion and stores
Source/JavaScriptCore/dfg/DFGOperations.h, Source/JavaScriptCore/dfg/DFGOperations.cpp, Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
Adds unsigned 64-bit-to-BigInt conversion and emits endian-aware BigInt loads and heap BigInt stores.
FTL BigInt allocation and storage
Source/JavaScriptCore/runtime/JSBigInt.h, Source/JavaScriptCore/b3/B3AbstractHeapRepository.h, Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
Adds BigInt field offsets and lowers 64-bit loads through fast and runtime allocation paths, plus heap BigInt stores.
DataView BigInt stress validation
JSTests/stress/dataview-jit-bigint64.js
Exercises endian variants, wrapping, errors, detached and resizable buffers, GC, zero handling, and optimized execution completion.

Suggested reviewers: jarred-sumner, constellation

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is detailed, but it does not follow the repository template and omits the required bug title, Bugzilla link, Reviewed by line, and file list. Rewrite it in the commit-message template, adding a Bugzilla ID/link, Reviewed by NOBODY (OOPS!), the bug explanation, and the changed paths.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding DFG/FTL intrinsics for DataView BigInt64/BigUint64 accessors.
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.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 `@JSTests/stress/dataview-jit-bigint64.js`:
- Around line 160-172: Update the offset-coercion coverage block around oobGet
and oobSet to invoke the BigInt64 DataView accessors with non-plain-integer
offsets, such as a fractional number, numeric string, and object implementing
valueOf, while preserving the detached-buffer assertions. Ensure these cases
exercise ToIndex coercion for both get and set rather than only passing integer
offsets.
🪄 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: dc63bfb8-d569-4ec0-82fd-375ac43e7d02

📥 Commits

Reviewing files that changed from the base of the PR and between 4895f45 and 92ed0b7.

📒 Files selected for processing (13)
  • JSTests/stress/dataview-jit-bigint64.js
  • Source/JavaScriptCore/b3/B3AbstractHeapRepository.h
  • Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
  • Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
  • Source/JavaScriptCore/dfg/DFGDoesGC.cpp
  • Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
  • Source/JavaScriptCore/dfg/DFGOperations.cpp
  • Source/JavaScriptCore/dfg/DFGOperations.h
  • Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
  • Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
  • Source/JavaScriptCore/runtime/Intrinsic.h
  • Source/JavaScriptCore/runtime/JSBigInt.h
  • Source/JavaScriptCore/runtime/JSDataViewPrototype.cpp

Comment on lines +160 to +172
// --- number value to get offset coercion & detached buffer ---
{
const buf = new ArrayBuffer(16);
const v = new DataView(buf);
v.setBigUint64(0, 0x1122334455667788n, true);
transferArrayBuffer(buf);
let threw = false;
try { oobGet(v, 0); } catch (e) { threw = e instanceof TypeError || e instanceof RangeError; }
assert(threw, "get on detached buffer should throw");
threw = false;
try { oobSet(v, 0); } catch (e) { threw = e instanceof TypeError || e instanceof RangeError; }
assert(threw, "set on detached buffer should throw");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Comment promises offset-coercion coverage that isn't tested.

The block comment says "number value to get offset coercion & detached buffer" but only exercises the detached-buffer path; offset/byteOffset is always a plain integer everywhere in this file (main loop offsets, OOB offsets, resizable-buffer offsets). No test passes a non-Int32 offset (a float like 16.0, a numeric string, or an object with valueOf) to getBigInt64/setBigUint64, so ToIndex coercion on the offset operand of the new intrinsic is untested. Given this PR adds new DFG/FTL type speculation for the offset operand of DataViewGetInt/DataViewSet, an offset that isn't a plain small integer is exactly the kind of input likely to hit a different type-check/OSR-exit path.

Suggested addition
 {
     const buf = new ArrayBuffer(16);
     const v = new DataView(buf);
     v.setBigUint64(0, 0x1122334455667788n, true);
+    // offset coercion via ToIndex
+    assert(v.getBigUint64(0.0, true) === 0x1122334455667788n, "float offset coerces");
+    assert(v.getBigUint64("0", true) === 0x1122334455667788n, "string offset coerces");
+    assert(v.getBigUint64({ valueOf() { return 0; } }, true) === 0x1122334455667788n, "object offset coerces via valueOf");
     transferArrayBuffer(buf);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// --- number value to get offset coercion & detached buffer ---
{
const buf = new ArrayBuffer(16);
const v = new DataView(buf);
v.setBigUint64(0, 0x1122334455667788n, true);
transferArrayBuffer(buf);
let threw = false;
try { oobGet(v, 0); } catch (e) { threw = e instanceof TypeError || e instanceof RangeError; }
assert(threw, "get on detached buffer should throw");
threw = false;
try { oobSet(v, 0); } catch (e) { threw = e instanceof TypeError || e instanceof RangeError; }
assert(threw, "set on detached buffer should throw");
}
// --- number value to get offset coercion & detached buffer ---
{
const buf = new ArrayBuffer(16);
const v = new DataView(buf);
v.setBigUint64(0, 0x1122334455667788n, true);
// offset coercion via ToIndex
assert(v.getBigUint64(0.0, true) === 0x1122334455667788n, "float offset coerces");
assert(v.getBigUint64("0", true) === 0x1122334455667788n, "string offset coerces");
assert(v.getBigUint64({ valueOf() { return 0; } }, true) === 0x1122334455667788n, "object offset coerces via valueOf");
transferArrayBuffer(buf);
let threw = false;
try { oobGet(v, 0); } catch (e) { threw = e instanceof TypeError || e instanceof RangeError; }
assert(threw, "get on detached buffer should throw");
threw = false;
try { oobSet(v, 0); } catch (e) { threw = e instanceof TypeError || e instanceof RangeError; }
assert(threw, "set on detached buffer should throw");
}
🤖 Prompt for 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.

In `@JSTests/stress/dataview-jit-bigint64.js` around lines 160 - 172, Update the
offset-coercion coverage block around oobGet and oobSet to invoke the BigInt64
DataView accessors with non-plain-integer offsets, such as a fractional number,
numeric string, and object implementing valueOf, while preserving the
detached-buffer assertions. Ensure these cases exercise ToIndex coercion for
both get and set rather than only passing integer offsets.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues found, but this adds new DFG/FTL codegen paths including inline JSBigInt heap allocation in FTL and flips the JSBigInt_length abstract heap to Mutable — JIT/GC-correctness-critical enough that it warrants a human reviewer familiar with the JIT tiers.

What was reviewed:

  • FTL inline BigInt allocation: cell header/sign bit/m_length/m_hash/digit init, mutatorFence(), and the allocateCell slow-path fallback to operationInt64ToBigInt/operationUint64ToBigInt.
  • doesGC now returns true for DataViewGetInt at byteSize == 8; DataViewSet stays non-GC (set path is a pure store).
  • Set path uses the existing toBigInt64 / lowHeapBigInt helpers (low digit with sign applied → correct mod-2^64 wrap, zero-length case handled).
  • Abstract-interpreter/fixup result types for the new 8-byte case, and the USE(BIGINT32) opt-out for the set intrinsic.
Extended reasoning...

Overview

Adds DFG/FTL intrinsics for DataView.prototype.{get,set}Big{Int,Uint}64 by extending the existing DataViewGetInt/DataViewSet nodes with a byteSize == 8 integer case. Touches 13 files across the intrinsic table, bytecode parser, abstract interpreter, fixup, doesGC, DFG SpeculativeJIT64 codegen, FTL lowering (including a new inline one-digit JSBigInt allocation fast path), the B3 abstract-heap repository, and JSBigInt.h. Includes a comprehensive stress test.

Security risks

JIT codegen and inline GC-cell allocation are security-sensitive. Specific hazards here: (a) manually constructing a JSBigInt cell in FTL — wrong field initialization or missing fence could yield a malformed cell visible to the GC/other code; (b) the JSBigInt_length abstract heap changed from Immutable to Mutable, which globally affects B3 alias analysis for BigInt length loads; (c) doesGC modeling — a wrong false here can corrupt the heap. None of these looked wrong on inspection, but they are exactly the class of change that benefits from a second pair of expert eyes.

Level of scrutiny

High. This is hand-written JIT lowering across two tiers plus GC-interaction metadata, not a mechanical or config change. Bugs in this area tend to be subtle, tier-specific, and can manifest as memory corruption rather than test failures.

Other factors

The PR is well-structured, follows existing patterns (compileMakeRope-style inline allocation, existing toBigInt64/lowHeapBigInt helpers, existing DataView byte-size dispatch), and ships a thorough stress test covering endianness variants, boundary values, multi-digit wrap, OOB/detached/resizable buffers, type-error OSR exits, and a GC loop. The bug-hunting pass found nothing. Still, the scope and criticality put this outside the bar for auto-approval.

@github-actions

Copy link
Copy Markdown

Preview Builds

Commit Release Date
92ed0b7e autobuild-preview-pr-294-92ed0b7e 2026-07-15 16:53:53 UTC

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.

1 participant