Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
55d4103
Add DFG/FTL nodes for byte-offset scalar accessors on ArrayBufferView…
Jarred-Sumner Jul 24, 2026
5bbbc48
Fixup: keep BufferReadInt/BufferWrite edges well-formed on the ForceE…
Jarred-Sumner Jul 24, 2026
7e4aaed
Address review: parser resizable fallback, FTL guards, test assertion
Jarred-Sumner Jul 24, 2026
a2e6f99
Buffer accessors: variable-width descriptors, inlined when the byteLe…
Jarred-Sumner Jul 24, 2026
19b10a6
JSDollarVM: mark the buffer accessor test host functions JSC_HOST_CAL…
Jarred-Sumner Jul 24, 2026
c55c1f9
Buffer accessors: graph-level value range checks, dead return value
Jarred-Sumner Jul 24, 2026
c9e6271
JSDollarVM: purify NaN on buffer accessor float reads, reject NaN writes
Jarred-Sumner Jul 24, 2026
c22ee38
Remove the buffer accessor comments
Jarred-Sumner Jul 24, 2026
db49576
Buffer accessors: use the Int52 typed array length in the FTL
Jarred-Sumner Jul 25, 2026
fb5ddb5
Buffer accessors: stress test views with 2GB / ~4GB byteOffsets
Jarred-Sumner Jul 25, 2026
e360a21
Buffer accessors: differential fuzzer stress test, restore Overflow gate
Jarred-Sumner Jul 25, 2026
336f33e
Buffer accessors: drop the 32-bit guard (DFG is 64-bit only now)
Jarred-Sumner Aug 22, 2026
08e13c3
Buffer accessors: gate re-inlining on Int52Overflow, inline BigInt64 …
Jarred-Sumner Aug 22, 2026
9b4a93a
Buffer accessors: also gate re-inlining on Uncountable exits
Jarred-Sumner Aug 22, 2026
d729a96
Buffer accessors: stress test that float reads treat the loaded value…
Jarred-Sumner Aug 22, 2026
c35752b
Buffer accessors: stress test detached / resized receivers at hot cal…
Jarred-Sumner Aug 22, 2026
20fe99f
Buffer accessors: fail closed on a bad registration or a missing stor…
Jarred-Sumner Aug 22, 2026
7e0f4d6
Buffer accessor stress tests: scale with testLoopCount, detach with t…
Jarred-Sumner Aug 23, 2026
cde40c1
Buffer accessors: int32 length + CheckInBounds in the FTL unless the …
Jarred-Sumner Aug 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions JSTests/stress/buffer-accessor-jit-bigint-write.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//@ requireOptions("--useDollarVM=1")

function shouldBe(actual, expected, message) {
if (actual !== expected) throw new Error(message + ": expected " + expected + " but got " + actual);
}
function shouldThrow(f, expected, message) {
let error = null;
try {
f();
} catch (e) {
error = e;
}
if (!(error instanceof expected)) throw new Error(message + ": expected a " + expected.name + " but got " + error);
}

const accessors = $vm.createBufferAccessors();
class Buffer extends Uint8Array {}
Object.assign(Buffer.prototype, accessors);

const buf = new Buffer(64);
const dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);

function writeBigInt64LE(b, v, o) {
return b.writeBigInt64LE(v, o);
}
noInline(writeBigInt64LE);
function writeBigInt64BE(b, v, o) {
return b.writeBigInt64BE(v, o);
}
noInline(writeBigInt64BE);
function writeBigUInt64LE(b, v, o) {
return b.writeBigUInt64LE(v, o);
}
noInline(writeBigUInt64LE);
function writeBigUInt64BE(b, v, o) {
return b.writeBigUInt64BE(v, o);
}
noInline(writeBigUInt64BE);

const values = [0n, 1n, -1n, 42n, -42n, 2n ** 31n, -(2n ** 31n), 2n ** 32n + 7n, 2n ** 63n - 1n, -(2n ** 63n)];
for (let i = 0; i < testLoopCount * 2; ++i) {
const o = (i & 7) * 8;
const v = values[i % values.length];
shouldBe(writeBigInt64LE(buf, v, o), o + 8, "writeBigInt64LE result");
shouldBe(dv.getBigInt64(o, true), v, "writeBigInt64LE store");
shouldBe(writeBigInt64BE(buf, v, o), o + 8, "writeBigInt64BE result");
shouldBe(dv.getBigInt64(o, false), v, "writeBigInt64BE store");
if (v >= 0n) {
shouldBe(writeBigUInt64LE(buf, v, o), o + 8, "writeBigUInt64LE result");
shouldBe(dv.getBigUint64(o, true), v, "writeBigUInt64LE store");
shouldBe(writeBigUInt64BE(buf, v, o), o + 8, "writeBigUInt64BE result");
shouldBe(dv.getBigUint64(o, false), v, "writeBigUInt64BE store");
}
}

for (let i = 0; i < testLoopCount * 2; ++i) {
shouldBe(writeBigUInt64LE(buf, 2n ** 64n - 1n, 0), 8, "unsigned max");
shouldBe(dv.getBigUint64(0, true), 2n ** 64n - 1n, "unsigned max store");
shouldThrow(() => writeBigUInt64LE(buf, -1n, 0), RangeError, "unsigned negative");
shouldThrow(() => writeBigUInt64LE(buf, 2n ** 64n, 0), RangeError, "unsigned too big");
shouldThrow(() => writeBigInt64LE(buf, 2n ** 63n, 0), RangeError, "signed too big");
shouldThrow(() => writeBigInt64LE(buf, -(2n ** 63n) - 1n, 0), RangeError, "signed too small");
shouldThrow(() => writeBigInt64LE(buf, 2n ** 100n, 0), RangeError, "way too big");
shouldThrow(() => writeBigInt64LE(buf, 5, 0), TypeError, "a number is not a BigInt");
shouldThrow(() => writeBigInt64LE(buf, 0n, 57), RangeError, "out of bounds");
shouldBe(dv.getBigInt64(0, true), -1n, "the failed writes stored nothing");
}
47 changes: 47 additions & 0 deletions JSTests/stress/buffer-accessor-jit-byteoffset.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//@ requireOptions("--useDollarVM=1")

let ab;
try {
ab = new ArrayBuffer(4 * 2 ** 30);
} catch (e) {
quit();
}
Object.assign(Uint8Array.prototype, $vm.createBufferAccessors());

function shouldBe(actual, expected, message) {
if (actual !== expected)
throw new Error(message + ": expected " + expected + " but got " + actual);
}
function shouldThrow(f, expected, message) {
let error = null;
try {
f();
} catch (e) {
error = e;
}
if (!(error instanceof expected))
throw new Error(message + ": expected a " + expected.name + " but got " + error);
}

const tailOffset = 4 * 2 ** 30 - 64;
const tail = new Uint8Array(ab, tailOffset, 64);
const wide = new Uint8Array(ab, 2 ** 31);
const raw = new DataView(ab);

function readAt(v, o) { return v.readInt32LE(o); }
function writeAt(v, x, o) { return v.writeInt32LE(x, o); }
noInline(readAt);
noInline(writeAt);

const iterations = testLoopCount * 30;
for (let i = 0; i < iterations; ++i) {
shouldBe(writeAt(tail, i, 8), 12, "write into the ~4GB byteOffset view");
shouldBe(readAt(tail, 8), i, "read the ~4GB byteOffset view back");
shouldBe(writeAt(wide, ~i, wide.length - 4), wide.length, "write at the top of the 2GB byteOffset view");
shouldBe(readAt(wide, wide.length - 4), ~i, "read at the top of the 2GB byteOffset view");
}
shouldBe(raw.getInt32(tailOffset + 8, true), iterations - 1, "the store landed at byteOffset + offset in the raw buffer");
shouldBe(raw.getInt32(2 ** 31 + wide.length - 4, true), ~(iterations - 1), "the store landed at the 2GB byteOffset");
shouldThrow(() => readAt(tail, 61), RangeError, "straddling the end of the small view");
shouldThrow(() => writeAt(wide, 0, wide.length - 3), RangeError, "straddling the end of the wide view");
shouldBe(numberOfDFGCompiles(readAt) <= 4, true, "the huge byteOffset does not cause recompiles");
73 changes: 73 additions & 0 deletions JSTests/stress/buffer-accessor-jit-detach.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//@ requireOptions("--useDollarVM=1")

// Detached, shrunk, regrown and out-of-bounds receivers at call sites that are already JIT-compiled,
// including detaching / resizing from inside the value argument's valueOf: every access must land on
// the live length (throw) and never touch the old storage.
const accessors = $vm.createBufferAccessors();
class Buffer extends Uint8Array {}
Object.assign(Buffer.prototype, accessors);
function expectThrow(f, what) { try { f(); } catch (e) { return; } throw new Error("expected throw: " + what); }

function rd(b, o) { return b.readInt32LE(o); }
function rd8(b, o) { return b.readDoubleBE(o); }
function rdb(b, o) { return b.readBigUInt64LE(o); }
function wr(b, v, o) { return b.writeUInt32LE(v, o); }
function wr8(b, v, o) { return b.writeInt8(v, o); }
function wrd(b, v, o) { return b.writeDoubleLE(v, o); }
function wrb(b, v, o) { return b.writeBigInt64LE(v, o); }
for (const f of [rd, rd8, rdb, wr, wr8, wrd, wrb]) noInline(f);

function warm(b) {
for (let i = 0; i < testLoopCount; i++) { rd(b, i & 31); rd8(b, i & 31); rdb(b, i & 31); wr(b, i, i & 31); wr8(b, i & 127, i & 31); wrd(b, i, i & 31); wrb(b, 1n, i & 31); }
}

// 1. detach after tier-up, same call sites
{
const b = new Buffer(64); warm(b);
transferArrayBuffer(b.buffer);
for (let i = 0; i < 100; i++) {
expectThrow(() => rd(b, 0), "rd detached"); expectThrow(() => rd8(b, 0), "rd8"); expectThrow(() => rdb(b, 0), "rdb");
expectThrow(() => wr(b, 1, 0), "wr"); expectThrow(() => wr8(b, 1, 0), "wr8"); expectThrow(() => wrd(b, 1, 0), "wrd"); expectThrow(() => wrb(b, 1n, 0), "wrb");
}
}
// 2. detach from inside value.valueOf while the call site is hot
{
const b = new Buffer(64); warm(b);
const evil = { valueOf() { transferArrayBuffer(b.buffer); return 7; } };
expectThrow(() => wr(b, evil, 0), "wr valueOf-detach");
const b2 = new Buffer(64); warm(b2);
const evil2 = { valueOf() { transferArrayBuffer(b2.buffer); return 7; } };
expectThrow(() => wr8(b2, evil2, 60), "wr8 valueOf-detach");
expectThrow(() => wrd(b2, 1, 0), "after");
}
// 3. resizable: shrink from inside valueOf, and shrink between hot calls
{
const rab = new ArrayBuffer(64, { maxByteLength: 4096 });
const b = new Buffer(rab); warm(b); // length-tracking view
rab.resize(8);
expectThrow(() => rd(b, 5), "rd past shrunk end"); rd(b, 4);
expectThrow(() => wrd(b, 1, 1), "wrd past shrunk end"); wr8(b, 5, 7); expectThrow(() => wr8(b, 5, 8), "wr8 at shrunk length");
const evil = { valueOf() { rab.resize(2); return 1; } };
expectThrow(() => wr(b, evil, 0), "wr valueOf-shrink");
rab.resize(0);
expectThrow(() => rd(b, 0), "rd zero-length"); expectThrow(() => wr8(b, 1, 0), "wr8 zero-length");
rab.resize(4096); if (wr(b, 0xdeadbeef, 4092) !== 4096 || rd(b, 4092) !== (0xdeadbeef | 0)) throw new Error("regrow");
// fixed-length view on a resizable buffer that shrinks below the view -> view goes out of bounds (length 0)
const fixed = new Buffer(rab, 16, 64); warm(fixed);
rab.resize(20);
expectThrow(() => rd(fixed, 0), "fixed view OOB after shrink"); expectThrow(() => wr8(fixed, 1, 0), "fixed view OOB write");
}
// 4. growable shared, length-tracking
{
const gsab = new SharedArrayBuffer(16, { maxByteLength: 256 });
const b = new Buffer(gsab); warm(new Buffer(64));
expectThrow(() => rd(b, 13), "gsab oob");
gsab.grow(256); if (wr(b, 123, 252) !== 256 || rd(b, 252) !== 123) throw new Error("gsab grown");
}
// 5. polymorphic site: hot on Buffer, then detached plain Uint8Array / other receivers
{
const b = new Buffer(64); warm(b);
const u = new Uint8Array(16); transferArrayBuffer(u.buffer);
for (let i = 0; i < testLoopCount / 10; i++) { expectThrow(() => rd(u, 0), "plain detached"); rd(b, i & 31); }
}

Loading
Loading