Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
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 < 2e4; ++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 < 2e4; ++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");
}
125 changes: 125 additions & 0 deletions JSTests/stress/buffer-accessor-jit-exits.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
//@ requireOptions("--useDollarVM=1")

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

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

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

function readInt32LE(b, o) {
return b.readInt32LE(o);
}
noInline(readInt32LE);
function readUInt8(b, o) {
return b.readUInt8(o);
}
noInline(readUInt8);
for (let i = 0; i < 1e4; ++i) {
dv.setInt32(12, i, true);
shouldBe(readInt32LE(buf, 12), i, "last valid offset");
shouldThrow(() => readInt32LE(buf, 13), RangeError, "one past the last valid offset");
shouldThrow(() => readInt32LE(buf, 16), RangeError, "offset === length");
shouldThrow(() => readInt32LE(buf, -1), RangeError, "negative offset");
shouldThrow(() => readInt32LE(buf, 1.5), RangeError, "fractional offset");
shouldThrow(() => readInt32LE(buf, NaN), RangeError, "NaN offset");
shouldThrow(() => readInt32LE(buf, Infinity), RangeError, "Infinity offset");
shouldThrow(() => readInt32LE(buf, "0"), RangeError, "string offset");
shouldBe(readInt32LE(buf, 4.0), dv.getInt32(4, true), "integral double offset");
shouldBe(readUInt8(buf, 15), buf[15], "last byte");
shouldThrow(() => readUInt8(buf, 16), RangeError, "one-byte read one past the end");
}

function writeInt8(b, v, o) {
return b.writeInt8(v, o);
}
noInline(writeInt8);
function writeUInt16BE(b, v, o) {
return b.writeUInt16BE(v, o);
}
noInline(writeUInt16BE);
function writeUInt32LE(b, v, o) {
return b.writeUInt32LE(v, o);
}
noInline(writeUInt32LE);
for (let i = 0; i < 1e4; ++i) {
shouldBe(writeInt8(buf, 127, 3), 4, "writeInt8 max");
shouldBe(dv.getInt8(3), 127, "writeInt8 max store");
shouldBe(writeInt8(buf, -128, 3), 4, "writeInt8 min");
shouldBe(dv.getInt8(3), -128, "writeInt8 min store");
shouldThrow(() => writeInt8(buf, 128, 3), RangeError, "writeInt8 too big");
shouldThrow(() => writeInt8(buf, -129, 3), RangeError, "writeInt8 too small");
shouldBe(dv.getInt8(3), -128, "a throwing writeInt8 stores nothing");
shouldThrow(() => writeUInt16BE(buf, -1, 2), RangeError, "writeUInt16BE negative");
shouldThrow(() => writeUInt16BE(buf, 65536, 2), RangeError, "writeUInt16BE too big");
shouldBe(writeUInt16BE(buf, 65535, 2), 4, "writeUInt16BE max");
shouldBe(dv.getUint16(2, false), 65535, "writeUInt16BE max store");
shouldThrow(() => writeUInt32LE(buf, -1, 4), RangeError, "writeUInt32LE negative");
shouldThrow(() => writeUInt32LE(buf, 4294967296, 4), RangeError, "writeUInt32LE too big");
shouldThrow(() => writeUInt32LE(buf, 4294967295, 14), RangeError, "writeUInt32LE out of bounds");
shouldBe(writeUInt32LE(buf, 4294967295, 4), 8, "writeUInt32LE max");
shouldBe(dv.getUint32(4, true), 4294967295, "writeUInt32LE max store");
}

{
const floats = new Float64Array(4);
const dataView = new DataView(new ArrayBuffer(8));
const otherView = new Uint32Array(4);
function readOnAnything(b, o) {
return accessors.readInt32LE.call(b, o);
}
noInline(readOnAnything);
for (let i = 0; i < 1e4; ++i) {
floats[0] = i;
shouldBe(readOnAnything(buf, 0), dv.getInt32(0, true), "Buffer receiver");
shouldBe(readOnAnything(floats, 0), new DataView(floats.buffer).getInt32(0, true), "Float64Array receiver");
shouldBe(readOnAnything(dataView, 4), 0, "DataView receiver");
shouldBe(readOnAnything(otherView, 12), 0, "Uint32Array receiver (byte semantics)");
shouldThrow(() => readOnAnything({}, 0), TypeError, "plain object receiver");
shouldThrow(() => readOnAnything(null, 0), TypeError, "null receiver");
}
}

{
const detached = new Buffer(16);
function readDetached(b) {
return b.readUInt16LE(0);
}
noInline(readDetached);
for (let i = 0; i < 1e3; ++i) shouldBe(readDetached(detached), 0, "before detach");
transferArrayBuffer(detached.buffer);
for (let i = 0; i < 1e3; ++i) shouldThrow(() => readDetached(detached), RangeError, "after detach");
}

{
let calls = 0;
const value = {
valueOf() {
calls++;
return 5;
},
};
function writeWithBadOffset(b, o) {
return b.writeInt32LE(value, o);
}
noInline(writeWithBadOffset);
for (let i = 0; i < 1e3; ++i) {
shouldBe(writeWithBadOffset(buf, 0), 4, "good offset");
shouldThrow(() => writeWithBadOffset(buf, 100), RangeError, "bad offset");
}
shouldBe(calls, 2000, "valueOf calls");
}
90 changes: 90 additions & 0 deletions JSTests/stress/buffer-accessor-jit-resizable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//@ 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);

function readUInt16LE(b, o) {
return b.readUInt16LE(o);
}
noInline(readUInt16LE);
function writeUInt16LE(b, v, o) {
return b.writeUInt16LE(v, o);
}
noInline(writeUInt16LE);

{
const fixed = new Buffer(64);
for (let i = 0; i < 1e4; ++i) {
shouldBe(writeUInt16LE(fixed, i & 0xffff, i & 62), (i & 62) + 2, "fixed write");
shouldBe(readUInt16LE(fixed, i & 62), i & 0xffff, "fixed read");
}
}

{
const rab = new ArrayBuffer(16, { maxByteLength: 64 });
const tracking = new Buffer(rab);
shouldBe(tracking.length, 16, "tracking length");
for (let i = 0; i < 1e4; ++i) {
shouldBe(writeUInt16LE(tracking, i & 0xffff, 14), 16, "tracking write at the end");
shouldBe(readUInt16LE(tracking, 14), i & 0xffff, "tracking read at the end");
shouldThrow(() => readUInt16LE(tracking, 15), RangeError, "tracking read straddling the end");
shouldThrow(() => readUInt16LE(tracking, 16), RangeError, "tracking read past the end");
}
rab.resize(64);
shouldBe(tracking.length, 64, "grown tracking length");
for (let i = 0; i < 1e4; ++i) {
shouldBe(writeUInt16LE(tracking, i & 0xffff, 62), 64, "write near the grown end");
shouldBe(readUInt16LE(tracking, 62), i & 0xffff, "read near the grown end");
}
rab.resize(8);
shouldBe(tracking.length, 8, "shrunk tracking length");
for (let i = 0; i < 1e3; ++i) {
shouldBe(readUInt16LE(tracking, 6), 0, "read near the shrunk end (never written)");
shouldThrow(() => readUInt16LE(tracking, 7), RangeError, "read straddling the shrunk end");
shouldThrow(() => writeUInt16LE(tracking, 0, 62), RangeError, "write past the shrunk end");
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

{
const rab = new ArrayBuffer(32, { maxByteLength: 64 });
const fixed = new Buffer(rab, 8, 16);
shouldBe(fixed.length, 16, "fixed-length view length");
for (let i = 0; i < 1e4; ++i) {
shouldBe(writeUInt16LE(fixed, i & 0xffff, 14), 16, "fixed-length view write");
shouldBe(readUInt16LE(fixed, 14), i & 0xffff, "fixed-length view read");
}
rab.resize(16);
for (let i = 0; i < 1e3; ++i) {
shouldThrow(() => readUInt16LE(fixed, 0), RangeError, "out-of-bounds view read");
shouldThrow(() => writeUInt16LE(fixed, 0, 0), RangeError, "out-of-bounds view write");
}
}

{
const gsab = new SharedArrayBuffer(16, { maxByteLength: 64 });
const shared = new Buffer(gsab);
for (let i = 0; i < 1e4; ++i) {
shouldBe(writeUInt16LE(shared, i & 0xffff, 14), 16, "shared write at the end");
shouldBe(readUInt16LE(shared, 14), i & 0xffff, "shared read at the end");
shouldThrow(() => readUInt16LE(shared, 15), RangeError, "shared read past the end");
}
gsab.grow(64);
for (let i = 0; i < 1e4; ++i) {
shouldBe(writeUInt16LE(shared, i & 0xffff, 62), 64, "write near the grown shared end");
shouldBe(readUInt16LE(shared, 62), i & 0xffff, "read near the grown shared end");
}
}
113 changes: 113 additions & 0 deletions JSTests/stress/buffer-accessor-jit-varwidth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
//@ 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);
}

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

const buf = new Buffer(64);
const dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
for (let i = 0; i < buf.length; ++i) buf[i] = (i * 71 + 5) & 0xff;

function readIntLE(b, o, l) {
return b.readIntLE(o, l);
}
function readIntBE(b, o, l) {
return b.readIntBE(o, l);
}
function readUIntLE(b, o, l) {
return b.readUIntLE(o, l);
}
function readUIntBE(b, o, l) {
return b.readUIntBE(o, l);
}
function writeIntLE(b, v, o, l) {
return b.writeIntLE(v, o, l);
}
function writeUIntBE(b, v, o, l) {
return b.writeUIntBE(v, o, l);
}
function readInt32ConstLE(b, o) {
return b.readIntLE(o, 4);
}
function readUInt16ConstBE(b, o) {
return b.readUIntBE(o, 2);
}
function readUInt24ConstLE(b, o) {
return b.readUIntLE(o, 3);
}
function writeInt8Const(b, v, o) {
return b.writeIntLE(v, o, 1);
}
for (const f of [
readIntLE,
readIntBE,
readUIntLE,
readUIntBE,
writeIntLE,
writeUIntBE,
readInt32ConstLE,
readUInt16ConstBE,
readUInt24ConstLE,
writeInt8Const,
])
noInline(f);

const uint = (o, l, le) => {
let value = 0;
for (let i = 0; i < l; ++i) value = le ? value + buf[o + i] * 2 ** (8 * i) : value * 256 + buf[o + i];
return value;
};
const sint = (o, l, le) => {
const value = uint(o, l, le);
return value >= 2 ** (8 * l - 1) ? value - 2 ** (8 * l) : value;
};

for (let i = 0; i < 1e4; ++i) {
const o = i & 15;
for (const l of [1, 2, 3, 4, 5, 6]) {
shouldBe(readIntLE(buf, o, l), sint(o, l, true), "readIntLE " + l);
shouldBe(readIntBE(buf, o, l), sint(o, l, false), "readIntBE " + l);
shouldBe(readUIntLE(buf, o, l), uint(o, l, true), "readUIntLE " + l);
shouldBe(readUIntBE(buf, o, l), uint(o, l, false), "readUIntBE " + l);
}
shouldBe(readInt32ConstLE(buf, o), dv.getInt32(o, true), "readIntLE const 4");
shouldBe(readUInt16ConstBE(buf, o), dv.getUint16(o, false), "readUIntBE const 2");
shouldBe(readUInt24ConstLE(buf, o), uint(o, 3, true), "readUIntLE const 3");
}

const scratch = new Buffer(64);
const scratchDV = new DataView(scratch.buffer);
for (let i = 0; i < 1e4; ++i) {
const o = i & 15;
shouldBe(writeIntLE(scratch, -(i & 0x7fff), o, 4), o + 4, "writeIntLE 4 result");
shouldBe(scratchDV.getInt32(o, true), -(i & 0x7fff), "writeIntLE 4 store");
shouldBe(writeUIntBE(scratch, i & 0xffffff, o, 3), o + 3, "writeUIntBE 3 result");
shouldBe(scratch[o] * 65536 + scratch[o + 1] * 256 + scratch[o + 2], i & 0xffffff, "writeUIntBE 3 store");
shouldBe(writeInt8Const(scratch, (i & 0xff) - 128, o), o + 1, "writeIntLE const 1 result");
shouldBe(scratchDV.getInt8(o), (i & 0xff) - 128, "writeIntLE const 1 store");
}

for (let i = 0; i < 3e3; ++i) {
shouldThrow(() => readIntLE(buf, 0, 7), RangeError, "byteLength 7");
shouldThrow(() => readIntLE(buf, 0, 0), RangeError, "byteLength 0");
shouldThrow(() => readInt32ConstLE(buf, undefined), TypeError, "undefined offset");
shouldThrow(() => readInt32ConstLE(buf, 61), RangeError, "out of bounds");
shouldThrow(() => writeInt8Const(scratch, 128, 0), RangeError, "value out of range");
shouldThrow(() => writeUIntBE(scratch, 2 ** 24, 0, 3), RangeError, "3-byte value out of range");
shouldThrow(() => writeIntLE(scratch, NaN, 0, 4), RangeError, "NaN value");
shouldThrow(() => writeIntLE(scratch, Infinity, 0, 4), RangeError, "Infinity value");
shouldThrow(() => writeIntLE(scratch, -Infinity, 0, 4), RangeError, "-Infinity value");
shouldThrow(() => writeInt8Const(scratch, NaN, 0), RangeError, "NaN value, constant width");
}
Loading
Loading