From fb013bd45e4babf8b8f3f7ca02cf5420f7b71c11 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 19:32:18 -0800 Subject: [PATCH 1/3] test(ts): pick lang cases by two bytes, so none past 255 is silently skipped Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/caller.ts | 210 ++++++++++++++------------- ts/fixtures/lang/expected/harness.ts | 32 ++-- ts/fixtures/lang/src/harness.rs | 27 ++-- ts/tests/differential.ts | 15 +- 4 files changed, 157 insertions(+), 127 deletions(-) diff --git a/ts/fixtures/lang/caller.ts b/ts/fixtures/lang/caller.ts index 62e123d3b..86e35e7f1 100644 --- a/ts/fixtures/lang/caller.ts +++ b/ts/fixtures/lang/caller.ts @@ -4,9 +4,18 @@ // `harness::entry`, from the output directory. import * as Fault from "./fmt.Fault.ts"; -import { cases as named, run as harness, op } from "./harness.ts"; +import { cases as named, run as harness, op as pick } from "./harness.ts"; import * as rt from "./rt.ts"; +/** The first two bytes, which pick the case named `name`. */ +function op(name: string): number[] { + const at = pick(name); + return [at >> 8, at & 0xff]; +} + +/** The bytes that pick each case, through `op`, which panics for one past what two bytes pick, and a `Fault`'s first byte. */ +export const ops: Uint8Array[] = [...named().map(([name]) => Uint8Array.from(op(name))), Uint8Array.of(0xff)]; + export function run(input: rt.ByteSlice): rt.ByteVec { if (rt.byteSliceFirst(input) !== 0xff) return harness(input); const fault = Fault.new(rt.byteSliceRange(input, 1)); @@ -25,7 +34,7 @@ export function run(input: rt.ByteSlice): rt.ByteVec { } /** `shapes::loops` with each first byte that one of its cases of `!` takes, with one more byte and without. */ -const loops = [0xf1, 0xf3, 0xf4, 0xf5, 0xf6, 0xf9, 0xfa].flatMap((b) => [Uint8Array.of(op("shapes::loops"), 0, b), Uint8Array.of(op("shapes::loops"), 0, b, 1)]); +const loops = [0xf1, 0xf3, 0xf4, 0xf5, 0xf6, 0xf9, 0xfa].flatMap((b) => [Uint8Array.of(...op("shapes::loops"), 0, b), Uint8Array.of(...op("shapes::loops"), 0, b, 1)]); /** A `Duration`'s bytes as `durations` takes them: its seconds, then its nanoseconds. */ function duration(secs: bigint, nanos: number): number[] { @@ -46,9 +55,9 @@ const factors = [0, 1, 3, 1000, 9_007_199, 1_000_000_000, 0xffffffff].map((n) => const durationsOp = op("durations::durations"); const durations = [ ...[0n, 1n, 1n << 32n, (1n << 53n) - 1n, 1n << 53n, (1n << 64n) - 2n, (1n << 64n) - 1n].flatMap((secs) => - [0, 1, 999_999_999, 1_000_000_000, 0xffffffff].flatMap((nanos) => [0, 1].map((kind) => Uint8Array.of(durationsOp, kind, ...duration(secs, nanos)))), + [0, 1, 999_999_999, 1_000_000_000, 0xffffffff].flatMap((nanos) => [0, 1].map((kind) => Uint8Array.of(...durationsOp, kind, ...duration(secs, nanos)))), ), - ...edges.flatMap((a) => edges.flatMap((b) => factors.flatMap((n) => [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13].map((kind) => Uint8Array.of(durationsOp, kind, ...a, ...b, ...n))))), + ...edges.flatMap((a) => edges.flatMap((b) => factors.flatMap((n) => [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13].map((kind) => Uint8Array.of(...durationsOp, kind, ...a, ...b, ...n))))), // Each side of where minutes, hours, and a `u128` of nanoseconds pass `u64::MAX` seconds. ...[2n ** 64n / 60n, 2n ** 64n / 3600n].flatMap((units) => [units, units + 1n].map((n) => wide(n << 64n))), ...[(7n << 64n) + 999_999_999n, 2n ** 64n * 1_000_000_000n - 1n, 2n ** 64n * 1_000_000_000n, 2n ** 128n - 1n].map((n) => wide(n)), @@ -56,11 +65,11 @@ const durations = [ /** `durations`' larger units, of a `u128` whose high half is also its `u64`. */ function wide(n: bigint): Uint8Array { - const bytes = new Uint8Array(18); - bytes.set([durationsOp, 12]); + const bytes = new Uint8Array(19); + bytes.set([...durationsOp, 12]); const view = new DataView(bytes.buffer); - view.setBigUint64(2, n >> 64n); - view.setBigUint64(10, n & 0xffff_ffff_ffff_ffffn); + view.setBigUint64(3, n >> 64n); + view.setBigUint64(11, n & 0xffff_ffff_ffff_ffffn); return bytes; } @@ -88,17 +97,17 @@ const doubles: (number | bigint)[] = [ /** Integers whose `f64` rounds, as `floats`' kind 2 takes them: 16 bytes of a `u128`. */ const wides = [0n, 1n, (1n << 53n) + 1n, (1n << 53n) + 3n, (1n << 54n) + 2n, (1n << 63n) + 1025n, (1n << 64n) - 1n, (1n << 64n) - 1025n, (1n << 64n) + (1n << 11n) + 1n, (1n << 127n) + 1n, (1n << 128n) - 1n, (1n << 128n) - (1n << 74n), -(1n << 100n) - 1n]; const floatsOp = op("floats::floats"); -/** How many cases the first byte picks among, modulo. */ +/** How many cases the first two bytes pick among, modulo. */ const count = named().length; const floats = [ - ...Array.from({ length: 10 }, (_, kind) => doubles.flatMap((a) => doubles.map((b) => Uint8Array.of(floatsOp, kind, ...double(a), ...double(b), 1)))).flat(), + ...Array.from({ length: 10 }, (_, kind) => doubles.flatMap((a) => doubles.map((b) => Uint8Array.of(...floatsOp, kind, ...double(a), ...double(b), 1)))).flat(), // `from_secs_f64`, `mul_f64`, and `div_f64` panicking, and `Duration::MAX.mul_f64`. - ...[18, 28, 38].flatMap((kind) => doubles.map((a) => Uint8Array.of(floatsOp, kind, ...double(a), ...double(a), 3))), + ...[18, 28, 38].flatMap((kind) => doubles.map((a) => Uint8Array.of(...floatsOp, kind, ...double(a), ...double(a), 3))), ...wides.map((v) => { const bytes = new Uint8Array(16); new DataView(bytes.buffer).setBigUint64(0, BigInt.asUintN(64, v >> 64n)); new DataView(bytes.buffer).setBigUint64(8, BigInt.asUintN(64, v)); - return Uint8Array.of(floatsOp, 2, ...bytes); + return Uint8Array.of(...floatsOp, 2, ...bytes); }), ]; @@ -115,11 +124,11 @@ const formatted: (number | bigint)[] = [ ]; const formatsOp = op("floats::formats"); const formats = [ - ...formatted.flatMap((a) => Array.from({ length: 22 }, (_, p) => Uint8Array.of(formatsOp, 0, ...double(a), p))), - ...formatted.flatMap((a) => [1, 3, 4].map((kind) => Uint8Array.of(formatsOp, kind, ...double(a), 0))), + ...formatted.flatMap((a) => Array.from({ length: 22 }, (_, p) => Uint8Array.of(...formatsOp, 0, ...double(a), p))), + ...formatted.flatMap((a) => [1, 3, 4].map((kind) => Uint8Array.of(...formatsOp, kind, ...double(a), 0))), // Seeds for `parses`' decimals, a signaling NaN's bits among them, which a seed keeps. - ...doubles.map((a) => Uint8Array.of(op("floats::parses"), 2, ...double(a))), - ...formatted.flatMap((a) => [[1, 2], [2, 1], [NaN, 1], [1, NaN], [-0, 0], [0, -0]].map(([b, c]) => Uint8Array.of(formatsOp, 2, ...double(a), ...double(b), ...double(c)))), + ...doubles.map((a) => Uint8Array.of(...op("floats::parses"), 2, ...double(a))), + ...formatted.flatMap((a) => [[1, 2], [2, 1], [NaN, 1], [1, NaN], [-0, 0], [0, -0]].map(([b, c]) => Uint8Array.of(...formatsOp, 2, ...double(a), ...double(b), ...double(c)))), ]; /** @@ -135,7 +144,7 @@ const parsed = [ "9007199254740993", "9007199254740993.0000000000000000001", "9007199254740992.9999999999999999999", "123456789012345678901234567890", "0.1000000000000000055511151231257827021181583404541015625", "1e23", "8.98846567431158e307", `0.${"0".repeat(400)}1e400`, `1${"0".repeat(800)}e-800`, `${"9".repeat(800)}e-1100`, "1e65535", `1e-65536`, `0.${"0".repeat(70000)}1e70000`, -].map((text) => Uint8Array.of(op("floats::parses"), 0, ...new TextEncoder().encode(text))); +].map((text) => Uint8Array.of(...op("floats::parses"), 0, ...new TextEncoder().encode(text))); /** A single's bits, big-endian, as `singles` takes them. */ function single(x: number | bigint): number[] { @@ -158,16 +167,16 @@ const singlesValues: (number | bigint)[] = [ ]; const singlesOp = op("singles::singles"); const singlesCases = [ - ...Array.from({ length: 10 }, (_, kind) => singlesValues.flatMap((a) => singlesValues.map((b) => Uint8Array.of(singlesOp, kind, ...single(a), ...single(b), 1)))).flat(), + ...Array.from({ length: 10 }, (_, kind) => singlesValues.flatMap((a) => singlesValues.map((b) => Uint8Array.of(...singlesOp, kind, ...single(a), ...single(b), 1)))).flat(), // `from_secs_f32`, `mul_f32`, and `div_f32` panicking, and `Duration::MAX.mul_f32`. - ...[18, 28, 38].flatMap((kind) => singlesValues.map((a) => Uint8Array.of(singlesOp, kind, ...single(a), ...single(a), 3))), + ...[18, 28, 38].flatMap((kind) => singlesValues.map((a) => Uint8Array.of(...singlesOp, kind, ...single(a), ...single(a), 3))), // Integers whose `f32` rounds once where through an `f64` it would round twice: past // 2^53, a bit just past halfway between two singles. ...wides.concat([(1n << 60n) + (1n << 36n) + 1n, (1n << 64n) - (1n << 39n) - 1n, (1n << 127n) + (1n << 103n) + 1n, (1n << 24n) + 1n]).map((v) => { const bytes = new Uint8Array(16); new DataView(bytes.buffer).setBigUint64(0, BigInt.asUintN(64, v >> 64n)); new DataView(bytes.buffer).setBigUint64(8, BigInt.asUintN(64, v)); - return Uint8Array.of(singlesOp, 2, ...bytes); + return Uint8Array.of(...singlesOp, 2, ...bytes); }), ]; @@ -179,10 +188,10 @@ const singlesFormatted: (number | bigint)[] = [ const singlesFormatsOp = op("singles::formats"); const singlesParsesOp = op("singles::parses"); const singlesFormats = [ - ...singlesFormatted.flatMap((a) => Array.from({ length: 12 }, (_, p) => Uint8Array.of(singlesFormatsOp, 0, ...single(a), p))), - ...singlesFormatted.flatMap((a) => [1, 3, 4].map((kind) => Uint8Array.of(singlesFormatsOp, kind, ...single(a), 0))), - ...singlesFormatted.flatMap((a) => [[1, 2], [2, 1], [NaN, 1], [1, NaN], [-0, 0], [0, -0]].map(([b, c]) => Uint8Array.of(singlesFormatsOp, 2, ...single(a), ...single(b), ...single(c)))), - ...singlesValues.map((a) => Uint8Array.of(singlesParsesOp, 2, ...single(a))), + ...singlesFormatted.flatMap((a) => Array.from({ length: 12 }, (_, p) => Uint8Array.of(...singlesFormatsOp, 0, ...single(a), p))), + ...singlesFormatted.flatMap((a) => [1, 3, 4].map((kind) => Uint8Array.of(...singlesFormatsOp, kind, ...single(a), 0))), + ...singlesFormatted.flatMap((a) => [[1, 2], [2, 1], [NaN, 1], [1, NaN], [-0, 0], [0, -0]].map(([b, c]) => Uint8Array.of(...singlesFormatsOp, 2, ...single(a), ...single(b), ...single(c)))), + ...singlesValues.map((a) => Uint8Array.of(...singlesParsesOp, 2, ...single(a))), ]; /** @@ -197,7 +206,7 @@ const singlesParsed = [ "16777217", "16777217.000000000001", "16777218.99999999999", "16777219", "1.00000005960464477539062499", "1.000000059604644775390625", "1.00000005960464477539062501", "1.0000000596046448", "0.1", "1e10", "1234567e-10", "12345678e-10", "9007199254740993", `1${"0".repeat(60)}e-60`, `0.${"0".repeat(60)}1e61`, "1e65535", "1e-65536", -].map((text) => Uint8Array.of(singlesParsesOp, 0, ...new TextEncoder().encode(text))); +].map((text) => Uint8Array.of(...singlesParsesOp, 0, ...new TextEncoder().encode(text))); /** * `mul_adds`' operands at each width: signed zeros, a product that underflows to either @@ -206,7 +215,7 @@ const singlesParsed = [ * product of half `c`'s last place, which only its error moves off a tie; then * seeds for its sweeps. */ -const mulAdds = (max: number, tiny: number, eps: number, op: number, bytes: (x: number) => number[]) => [ +const mulAdds = (max: number, tiny: number, eps: number, op: number[], bytes: (x: number) => number[]) => [ ...[ [0, 1, -0], [-0, 1, -0], [0, -1, 0], [-0, -0, -0], [-0, 0, -0], [1, -1, 1], [-1, 1, 1], [tiny, -(2 ** -30), 0], [tiny, 2 ** -30, -0], [-tiny, 2 ** -30, -0], [tiny, 0.5, 0], [tiny, 0.5, -0], [-tiny, 0.5, 0], @@ -216,18 +225,21 @@ const mulAdds = (max: number, tiny: number, eps: number, op: number, bytes: (x: [max, 2, -max], [max, -2, max], [max, 1 + eps, -max], [max, 1 + eps, 0], [max * 0.5, 2, max * eps * 0.25], [1 + 2 ** -27, 1 - 2 ** -27, -1], [1 + eps, 1 + eps, -1], [1 + eps, 1 - eps, -1], [0.1, 10, -1], [3, 1 / 3, -1], [1 + eps, 1 + eps, -(1 + 2 * eps)], [1 + 2 ** -12, 1 + 2 ** -12, tiny], [1 + 2 ** -12, 1 + 2 ** -12, -tiny], [1 + eps, 1 + eps / 2, 0], [eps / 2 + eps * eps / 2, 1 - eps / 2, 1], [eps / 2 + eps * eps / 2, 1 - eps / 2, -1], - ].map((xs) => Uint8Array.of(op, 0, ...xs.flatMap(bytes))), - ...Array.from({ length: 400 }, (_, i) => Uint8Array.of(op, 1, ...double(BigInt(i + 1) * 0x9e3779b97f4a7c15n & 0xffff_ffff_ffff_ffffn))), + ].map((xs) => Uint8Array.of(...op, 0, ...xs.flatMap(bytes))), + ...Array.from({ length: 400 }, (_, i) => Uint8Array.of(...op, 1, ...double(BigInt(i + 1) * 0x9e3779b97f4a7c15n & 0xffff_ffff_ffff_ffffn))), ]; const floatsMulAdds = mulAdds(Number.MAX_VALUE, 5e-324, 2 ** -52, op("floats::mul_adds"), double); const singlesMulAdds = mulAdds(3.4028234663852886e38, 2 ** -149, 2 ** -23, op("singles::mul_adds"), single); +const [floatsAt, singlesAt] = [pick("floats::floats"), pick("singles::singles")]; + /** How many ULPs from native Rust's each float of `floats`' and `singles`' kind 7 may be, which Rust leaves to libm. */ const ULPS = 2n; export function within(input: Uint8Array, native: Uint8Array, translated: Uint8Array): boolean { - const width = input[0] % count === floatsOp ? 8 : input[0] % count === singlesOp ? 4 : 0; - if (width === 0 || input[1] % 10 !== 7 || native.length !== translated.length) return false; + const at = ((input[0] << 8) | input[1]) % count; + const width = at === floatsAt ? 8 : at === singlesAt ? 4 : 0; + if (width === 0 || input[2] % 10 !== 7 || native.length !== translated.length) return false; const [a, b] = [native, translated].map((v) => new DataView(v.buffer, v.byteOffset, v.length)); const read = (v: DataView, i: number) => (width === 8 ? v.getBigInt64(i) : BigInt(v.getInt32(i))); // Bits as a line through zero, so the distance between two is how many floats apart they are. @@ -283,37 +295,37 @@ function ints(): Uint8Array[] { const v = values[(i * 13 + 5) % values.length]; const shift = shifts[i++ % shifts.length]; for (let kind = 0; kind < 31; kind++) { - for (const o of [u64, i64]) cases.push(Uint8Array.of(o, kind, ...be(x), ...be(y), shift)); - for (const o of [u128, i128]) cases.push(Uint8Array.of(o, kind, ...be(x), ...be(y), ...be(u), ...be(v), shift)); + for (const o of [u64, i64]) cases.push(Uint8Array.of(...o, kind, ...be(x), ...be(y), shift)); + for (const o of [u128, i128]) cases.push(Uint8Array.of(...o, kind, ...be(x), ...be(y), ...be(u), ...be(v), shift)); } // `clamp` between two values of the sweep, whose third operand follows the shift's place. - for (const o of [u64, i64]) cases.push(Uint8Array.of(o, 26, ...be(x), ...be(y), ...be(u))); - for (const o of [u128, i128]) cases.push(Uint8Array.of(o, 26, ...be(x), ...be(y), ...be(u), ...be(v), ...be(y), ...be(x))); + for (const o of [u64, i64]) cases.push(Uint8Array.of(...o, 26, ...be(x), ...be(y), ...be(u))); + for (const o of [u128, i128]) cases.push(Uint8Array.of(...o, 26, ...be(x), ...be(y), ...be(u), ...be(v), ...be(y), ...be(x))); // Negating an `i64` and an `i128`. - cases.push(Uint8Array.of(neg, 3, ...be(x)), Uint8Array.of(neg, 4, ...be(x), ...be(y))); + cases.push(Uint8Array.of(...neg, 3, ...be(x)), Uint8Array.of(...neg, 4, ...be(x), ...be(y))); // `x * y`, `checked_mul`, `saturating_mul`, and `wrapping_mul` at 128 bits, extended in // each type's sign: a product of two numbers, past 2^53, multiplies their halves. const signs = (v: bigint) => (v >> 63n === 1n ? 0xffffffffffffffffn : 0n); for (const kind of [2, 13, 24, 25]) { - cases.push(Uint8Array.of(u128, kind, ...be(0n), ...be(x), ...be(0n), ...be(y))); - cases.push(Uint8Array.of(i128, kind, ...be(signs(x)), ...be(x), ...be(signs(y)), ...be(y))); + cases.push(Uint8Array.of(...u128, kind, ...be(0n), ...be(x), ...be(0n), ...be(y))); + cases.push(Uint8Array.of(...i128, kind, ...be(signs(x)), ...be(x), ...be(signs(y)), ...be(y))); } // `x:y / d`, `x:y % d`, `div_ceil`, and `is_multiple_of` at 128 bits by a divisor // below 2^32 in magnitude, which divides limbs by a number, extended in each type's // sign: -1 overflows `i128::MIN`. for (const d of [3n, 1_000_000n, 0xffffffffn, -1n, -0xffffffffn]) { for (const kind of [3, 4, 27, 28]) { - for (const o of [u128, i128]) cases.push(Uint8Array.of(o, kind, ...be(x), ...be(y), ...be(signs(d & 0xffffffffffffffffn)), ...be(d & 0xffffffffffffffffn))); + for (const o of [u128, i128]) cases.push(Uint8Array.of(...o, kind, ...be(x), ...be(y), ...be(signs(d & 0xffffffffffffffffn)), ...be(d & 0xffffffffffffffffn))); } } } // Conversions, and halves joined back. - cases.push(Uint8Array.of(convert, 0, ...be(x), ...be(x))); + cases.push(Uint8Array.of(...convert, 0, ...be(x), ...be(x))); for (const y of [0n, 1n, 1n << 32n, 1n << 63n, 0xffffffffffffffffn]) { for (const from of [3, 4, 9, 10]) { // A cast to every type, and every format spec. - for (let to = 0; to < 12; to++) cases.push(Uint8Array.of(cast, from, to, ...be(x), ...be(y))); - cases.push(Uint8Array.of(fmtInts, from, ...be(x), ...be(y))); + for (let to = 0; to < 12; to++) cases.push(Uint8Array.of(...cast, from, to, ...be(x), ...be(y))); + cases.push(Uint8Array.of(...fmtInts, from, ...be(x), ...be(y))); } } } @@ -336,14 +348,14 @@ const utf8 = [ [0xe2, 0x82], [0xc2], [0x80, 0xf8, 0xff], -].map((bytes) => Uint8Array.of(op("utf8::decodes"), 0, ...bytes)); +].map((bytes) => Uint8Array.of(...op("utf8::decodes"), 0, ...bytes)); /** * `dyns::dyns` too short to run, with a `Tri` of each variant, at the ends of each side and * count, and boxing each kind of shape. */ const dyns = [[], [1, 2], [3, 0, 7], [255, 1, 255, 0, 1, 2, 3, 4, 5], [0, 3, 2, 9, 255, 6, 128]].map((bytes) => - Uint8Array.of(op("dyns::dyns"), 0, ...bytes), + Uint8Array.of(...op("dyns::dyns"), 0, ...bytes), ); /** @@ -374,21 +386,21 @@ const cellScripts = [ [12, 7, 12, 4], [], ]; -const cells = Array.from({ length: 256 }, (_, kind) => cellScripts.map((script) => Uint8Array.of(cellsOp, kind, ...script))).flat(); +const cells = Array.from({ length: 256 }, (_, kind) => cellScripts.map((script) => Uint8Array.of(...cellsOp, kind, ...script))).flat(); /** * `dyns::shared` too short to run, and with `Arc`s of each type, the last count of each at * zero and at the top of its range. */ const shared = [[], [1], [0, 0], [255, 255, 0, 1, 2, 3, 4, 5], [7, 0xff, 255, 254, 253]].map((bytes) => - Uint8Array.of(op("dyns::shared"), 0, ...bytes), + Uint8Array.of(...op("dyns::shared"), 0, ...bytes), ); /** * `locks` scripts: writes through guards and reads, `get_mut`, and a lock matched. */ const locks = [[0, 1, 5, 6, 2, 7, 3, 8, 3, 0, 1], [4, 0, 4, 1], [2, 9, 2, 10, 4, 1], [3, 3, 4, 0]].map( - (bytes) => Uint8Array.of(op("locks::locks"), 0, ...bytes), + (bytes) => Uint8Array.of(...op("locks::locks"), 0, ...bytes), ); /** @@ -401,7 +413,7 @@ const guards = [ [0x08, 0xe1, 0xf1], [0x08, 0xe6], [0xf6], -].map((bytes) => Uint8Array.of(op("borrows::guards"), 0, ...bytes)); +].map((bytes) => Uint8Array.of(...op("borrows::guards"), 0, ...bytes)); /** * `borrows::maps` scripts: each operation through mapped, filtered, and split guards, with @@ -414,11 +426,11 @@ const maps = [ [0x0a, 0xd6], [0xe6], [0x02, 0xf6], -].map((bytes) => Uint8Array.of(op("borrows::maps"), 0, ...bytes)); +].map((bytes) => Uint8Array.of(...op("borrows::maps"), 0, ...bytes)); /** `locks::mapped` scripts: writes and reads through mapped and filtered guards. */ const mappedLocks = [[0x04, 0x01, 0x02, 0x86, 0x05, 0x0a, 0x03, 0x02, 0x80, 0x01, 0x03]].map((bytes) => - Uint8Array.of(op("locks::mapped"), 0, ...bytes), + Uint8Array.of(...op("locks::mapped"), 0, ...bytes), ); /** @@ -426,7 +438,7 @@ const mappedLocks = [[0x04, 0x01, 0x02, 0x86, 0x05, 0x0a, 0x03, 0x02, 0x80, 0x01 * panics, and a read-write lock read or written again while a guard, mapped or not, holds * it, which aborts but for a read under a read. */ -const relocks = Array.from({ length: 16 }, (_, pick) => Uint8Array.of(op("locks::relock"), 0, pick)); +const relocks = Array.from({ length: 16 }, (_, pick) => Uint8Array.of(...op("locks::relock"), 0, pick)); /** * `borrows::tries` scripts: each operation through tried guards, given back beside each @@ -438,19 +450,19 @@ const tries = [ [0xd3], [0xe3], [0x00, 0xf3], -].map((bytes) => Uint8Array.of(op("borrows::tries"), 0, ...bytes)); +].map((bytes) => Uint8Array.of(...op("borrows::tries"), 0, ...bytes)); /** `locks::mutex_mapped` scripts: writes and reads through mapped and filtered mutex guards. */ const mutexMaps = [[0x04, 0x01, 0x02, 0x86, 0xf5, 0x0a, 0x03, 0x02, 0xe1, 0x80, 0x07, 0x02]].map((bytes) => - Uint8Array.of(op("locks::mutex_mapped"), 0, ...bytes), + Uint8Array.of(...op("locks::mutex_mapped"), 0, ...bytes), ); /** `locks::printed` scripts: each lock's guards printed, empty and not. */ -const printed = [[], [0, 1, 25, 200]].map((bytes) => Uint8Array.of(op("locks::printed"), 0, ...bytes)); +const printed = [[], [0, 1, 25, 200]].map((bytes) => Uint8Array.of(...op("locks::printed"), 0, ...bytes)); /** `locks::downgraded` scripts: write guards downgraded, each lock's released in either order, and whole byte arrays mapped. */ const downgrades = [[0x00, 0x01, 0x11, 0x02, 0xf2, 0xe2, 0x03, 0x04, 0x13, 0x05]].map((bytes) => - Uint8Array.of(op("locks::downgraded"), 0, ...bytes), + Uint8Array.of(...op("locks::downgraded"), 0, ...bytes), ); /** @@ -458,18 +470,18 @@ const downgrades = [[0x00, 0x01, 0x11, 0x02, 0xf2, 0xe2, 0x03, 0x04, 0x13, 0x05] * borrow again while a leak holds the cell, which panics. */ const leaks = [[0x00, 0x01, 0x02, 0xf1, 0xf2, 0x04, 0x05, 0x03, 0x06], [0xe3], [0x00, 0xf3]].map((bytes) => - Uint8Array.of(op("borrows::leaks"), 0, ...bytes), + Uint8Array.of(...op("borrows::leaks"), 0, ...bytes), ); /** `locks::rwlocks` scripts: `get_mut` and `into_inner` among writes and reads. */ -const rwlocks = [[6, 7, 0, 6, 2, 14, 15, 1]].map((bytes) => Uint8Array.of(op("locks::rwlocks"), 0, ...bytes)); +const rwlocks = [[6, 7, 0, 6, 2, 14, 15, 1]].map((bytes) => Uint8Array.of(...op("locks::rwlocks"), 0, ...bytes)); /** * `statics::onces` scripts: a `Once` forced, called, and forced again, which drops the * function it never runs, each static's, each `call_once` and `call_once_force` run again * from within itself, and crossbeam-epoch's `OnceLock` of each kind. */ -const onces = [[1, 0, 1, 7], [0, 6, 1, 2, 3, 4, 5, 11], [2, 14, 8], [9], [3, 15], [4, 17, 23, 29, 4]].map((bytes) => Uint8Array.of(op("statics::onces"), 0, ...bytes)); +const onces = [[1, 0, 1, 7], [0, 6, 1, 2, 3, 4, 5, 11], [2, 14, 8], [9], [3, 15], [4, 17, 23, 29, 4]].map((bytes) => Uint8Array.of(...op("statics::onces"), 0, ...bytes)); /** * `bounds` with each kind of bound at each end, over every pair of values around and past @@ -479,7 +491,7 @@ const boundsOp = op("bounds::bounds"); const bounds = Array.from({ length: 6 * 9 * 36 }, (_, n) => { const [kind, ends, at] = [n % 6, Math.floor(n / 6) % 9, Math.floor(n / 54)]; const [a, b] = [at % 6, Math.floor(at / 6)]; - return [Uint8Array.of(boundsOp, kind, ends % 3, Math.floor(ends / 3), a, b, 1, 3, 2, 5), Uint8Array.of(boundsOp, kind, ends % 3, Math.floor(ends / 3), a, b)]; + return [Uint8Array.of(...boundsOp, kind, ends % 3, Math.floor(ends / 3), a, b, 1, 3, 2, 5), Uint8Array.of(...boundsOp, kind, ends % 3, Math.floor(ends / 3), a, b)]; }).flat(); /** @@ -491,14 +503,14 @@ const unsafeCells = [ [4, 12, 4, 5, 253, 7, 255, 0], [3, 251, 6, 3, 2, 2, 254], [], -].map((bytes) => Uint8Array.of(op("pointers::cells"), 0, ...bytes)); +].map((bytes) => Uint8Array.of(...op("pointers::cells"), 0, ...bytes)); /** * `strs::edits` of each edit on `aé\u{800}😀`, a char of each UTF-8 width, at every byte * offset into it and up to two past its end, each made a raw offset by its byte's bits. */ const editsOp = op("strs::edits"); -const edits = Array.from({ length: 14 * 16 * 16 }, (_, i) => Uint8Array.of(editsOp, 4, 2, 7, 9, 14, i >> 8, (i >> 4) & 15, i & 15)); +const edits = Array.from({ length: 14 * 16 * 16 }, (_, i) => Uint8Array.of(...editsOp, 4, 2, 7, 9, 14, i >> 8, (i >> 4) & 15, i & 15)); /** * `subslices` of each kind over zero to four items, at every pair of bounds up to one past @@ -508,7 +520,7 @@ const subslicesOp = op("slices::subslices"); const subslices = [0, 1, 2, 3, 4].flatMap((n) => Array.from({ length: 8 * (n + 2) ** 2 }, (_, i) => { const [kind, a, b] = [i % 8, Math.floor(i / 8) % (n + 2), Math.floor(i / 8 / (n + 2))]; - return Uint8Array.of(subslicesOp, kind, a, b, ...[0x31, 0xc2, 0x07, 0xfe].slice(0, n)); + return Uint8Array.of(...subslicesOp, kind, a, b, ...[0x31, 0xc2, 0x07, 0xfe].slice(0, n)); }), ); @@ -517,7 +529,7 @@ const subslices = [0, 1, 2, 3, 4].flatMap((n) => * `None`, `None` before `Some`, `nth` past the end, only `None`s, keys all tied, and * `find` stopping early. */ -const consumers = [[], [6], [5], [1, 2], [7, 2], [1, 3, 5], [2, 6, 10, 14], [4, 3, 0x90, 0x92, 5]].map((bytes) => Uint8Array.of(op("nested::nested"), 3, ...bytes)); +const consumers = [[], [6], [5], [1, 2], [7, 2], [1, 3, 5], [2, 6, 10, 14], [4, 3, 0x90, 0x92, 5]].map((bytes) => Uint8Array.of(...op("nested::nested"), 3, ...bytes)); /** * `nested::closures`, whose bytes `pick` each `None` (0 mod 3), `Some(None)` (1), or @@ -525,14 +537,14 @@ const consumers = [[], [6], [5], [1, 2], [7, 2], [1, 3, 5], [2, 6, 10, 14], [4, * a `None` and after it, a seed of each, and a `successors` whose size hint reserves past * `isize::MAX`. */ -const closures = [[], [3], [0, 3], [1], [4, 7, 1], [1, 2, 0, 4, 5], [4, 0, 1, 3, 5], [2, 1, 4, 3, 8], [5, 4], [0, 1, 2], [0xf0], [0xf1]].map((bytes) => Uint8Array.of(op("nested::nested"), 5, ...bytes)); +const closures = [[], [3], [0, 3], [1], [4, 7, 1], [1, 2, 0, 4, 5], [4, 0, 1, 3, 5], [2, 1, 4, 3, 8], [5, 4], [0, 1, 2], [0xf0], [0xf1]].map((bytes) => Uint8Array.of(...op("nested::nested"), 5, ...bytes)); /** * `nested::elements`, whose first byte is also the index and whose odd bytes are each a * `None` element: of no elements, one of each, only `None`s, and an index in range and * past the end of each mix. */ -const elements = [[], [1], [2], [1, 3], [3, 1, 5], [0, 1, 2], [2, 6, 1, 8], [5, 4, 3, 0x10, 7, 0x40], [7, 0x22, 9, 0x22, 1, 0x80, 3, 6]].map((bytes) => Uint8Array.of(op("nested::nested"), 6, ...bytes)); +const elements = [[], [1], [2], [1, 3], [3, 1, 5], [0, 1, 2], [2, 6, 1, 8], [5, 4, 3, 0x10, 7, 0x40], [7, 0x22, 9, 0x22, 1, 0x80, 3, 6]].map((bytes) => Uint8Array.of(...op("nested::nested"), 6, ...bytes)); /** * `ranges` of each kind, and each kind of `Bound` at the end, over zero to four items, at @@ -543,7 +555,7 @@ const ranges = [0, 1, 2, 3, 4].flatMap((n) => { const ends = [...Array(n + 2).keys(), 0xff]; return Array.from({ length: 24 * ends.length ** 2 }, (_, i) => { const [kind, a, b] = [i % 24, ends[Math.floor(i / 24) % ends.length], ends[Math.floor(i / 24 / ends.length)]]; - return Uint8Array.of(rangesOp, kind, a, b, ...[0x31, 0xc2, 0x07, 0xfe].slice(0, n)); + return Uint8Array.of(...rangesOp, kind, a, b, ...[0x31, 0xc2, 0x07, 0xfe].slice(0, n)); }); }); @@ -551,14 +563,14 @@ const ranges = [0, 1, 2, 3, 4].flatMap((n) => { * `iters::reducers` of no items, one, one repeated, which `next_if_eq` takes, and keys * that all tie. */ -const reducers = [[], [9], [9, 9], [8, 4, 12, 0x40, 0x80]].map((bytes) => Uint8Array.of(op("iters::reducers"), 0, ...bytes)); +const reducers = [[], [9], [9, 9], [8, 4, 12, 0x40, 0x80]].map((bytes) => Uint8Array.of(...op("iters::reducers"), 0, ...bytes)); /** * `iters::peeked_others` of no items, one, a run that matches the first throughout, and * items that each match one comparison and miss the next. */ const peekedOthers = [[], [3], [4, 4, 4, 4], [0, 1, 2, 3, 4, 5, 6], [1, 0, 0, 2, 5, 9]].map((bytes) => - Uint8Array.of(op("iters::peeked_others"), 0, ...bytes), + Uint8Array.of(...op("iters::peeked_others"), 0, ...bytes), ); /** @@ -569,7 +581,7 @@ const editsSlicesOp = op("slices::edits"); const sliceEdits = [0, 1, 2, 3, 4].flatMap((n) => Array.from({ length: 8 * (n + 2) ** 3 }, (_, i) => { const [kind, a, b, c] = [i % 8, Math.floor(i / 8) % (n + 2), Math.floor(i / 8 / (n + 2)) % (n + 2), Math.floor(i / 8 / (n + 2) ** 2)]; - return Uint8Array.of(editsSlicesOp, kind, a, b, c, ...[0x31, 0xc2, 0x07, 0xfe].slice(0, n)); + return Uint8Array.of(...editsSlicesOp, kind, a, b, c, ...[0x31, 0xc2, 0x07, 0xfe].slice(0, n)); }), ); @@ -581,15 +593,15 @@ const sliceEdits = [0, 1, 2, 3, 4].flatMap((n) => const drainsOp = op("strs::drains"); const drains = Array.from({ length: 32 * 13 * 13 }, (_, i) => { const [form, a, b] = [i % 32, Math.floor(i / 32) % 13, Math.floor(i / 416)]; - return Uint8Array.of(drainsOp, 4, 2, 7, 9, 14, form, a, b, [0, 0x0a, 0x1f][(form + a + b) % 3]); + return Uint8Array.of(...drainsOp, 4, 2, 7, 9, 14, form, a, b, [0, 0x0a, 0x1f][(form + a + b) % 3]); }); /** `strs::iters` of each kind, on no text, text of each UTF-8 width, and parts between `/`s, empty ones too. */ -const iters = Array.from({ length: 12 }, (_, kind) => [[], [2, 7, 9, 14], [0, 2, 0, 0, 7, 9], [14, 0]].map((data) => Uint8Array.of(op("strs::iters"), kind, ...data))).flat(); +const iters = Array.from({ length: 12 }, (_, kind) => [[], [2, 7, 9, 14], [0, 2, 0, 0, 7, 9], [14, 0]].map((data) => Uint8Array.of(...op("strs::iters"), kind, ...data))).flat(); /** `strs::hinted` of each adaptor and each pair of range lengths, each way, on `aé` alone. */ const hintedOp = op("strs::hinted"); -const hinted = Array.from({ length: 10 * 7 * 21 }, (_, i) => Uint8Array.of(hintedOp, i % 10, Math.floor(i / 10) % 7, Math.floor(i / 70))); +const hinted = Array.from({ length: 10 * 7 * 21 }, (_, i) => Uint8Array.of(...hintedOp, i % 10, Math.floor(i / 10) % 7, Math.floor(i / 70))); /** * `hints::hints` of each iterator, each pair of lengths, and each of its arms, read three @@ -599,13 +611,13 @@ const hintsOp = op("hints::hints"); const hints = Array.from({ length: 24 * 8 * 8 * 8 * 2 }, (_, i) => { const [kind, n, m, arm, back] = [i % 24, Math.floor(i / 24) % 8, Math.floor(i / 192) % 8, Math.floor(i / 1536) % 8, Math.floor(i / 12288)]; const steps = (arm << 5) | ((back ? 0b101 : 0b010) << 2) | 3; - return Uint8Array.of(hintsOp, kind, n, m, steps, ...Array.from({ length: n }, (_, b) => b * 37)); + return Uint8Array.of(...hintsOp, kind, n, m, steps, ...Array.from({ length: n }, (_, b) => b * 37)); }); /** `hints::reserves` of each collection and element type, each shape, length, and limit on what it reads. */ const reservesOp = op("hints::reserves"); const reserves = Array.from({ length: 13 * 6 * 5 * 4 * 5 }, (_, i) => - Uint8Array.of(reservesOp, i % 78, Math.floor(i / 78) % 5, Math.floor(i / 390) % 4, Math.floor(i / 1560)), + Uint8Array.of(...reservesOp, i % 78, Math.floor(i / 78) % 5, Math.floor(i / 390) % 4, Math.floor(i / 1560)), ); /** @@ -616,21 +628,21 @@ const reserves = Array.from({ length: 13 * 6 * 5 * 4 * 5 }, (_, i) => */ const setOpsOp = op("collections::set_ops"); const setOps = [ - ...[15, 16, 17, 18, 19].flatMap((kind) => [0x20, 0x21, 0x25, 0x3e, 0x3f].map((single) => Uint8Array.of(setOpsOp, kind, single, ...Array.from({ length: 30 }, (_, i) => 0x41 + i)))), + ...[15, 16, 17, 18, 19].flatMap((kind) => [0x20, 0x21, 0x25, 0x3e, 0x3f].map((single) => Uint8Array.of(...setOpsOp, kind, single, ...Array.from({ length: 30 }, (_, i) => 0x41 + i)))), ...[ [0x01, 0x02, 0x13, 0x21, 0x32, 0x44], [0x01, 0x02, 0x13, 0x21, 0x32, 0x44, 0x55], [0x01, 0x02, 0x13, 0x55, 0x21, 0x32, 0x44], - ].map((data) => Uint8Array.of(setOpsOp, 3, ...data)), + ].map((data) => Uint8Array.of(...setOpsOp, 3, ...data)), ]; /** `zeros::zeros` of each case, on up to three bytes. */ const zerosOp = op("zeros::zeros"); -const zeros = Array.from({ length: 7 * 4 }, (_, i) => Uint8Array.of(zerosOp, i % 7, ...Array.from({ length: Math.floor(i / 7) }, (_, b) => b))); +const zeros = Array.from({ length: 7 * 4 }, (_, i) => Uint8Array.of(...zerosOp, i % 7, ...Array.from({ length: Math.floor(i / 7) }, (_, b) => b))); /** `zeros::writes` of each case, on up to three bytes: the last past the slice it swaps in. */ const writesOp = op("zeros::writes"); -const writes = Array.from({ length: 5 * 4 }, (_, i) => Uint8Array.of(writesOp, i % 5, ...Array.from({ length: Math.floor(i / 5) }, (_, b) => b))); +const writes = Array.from({ length: 5 * 4 }, (_, i) => Uint8Array.of(...writesOp, i % 5, ...Array.from({ length: Math.floor(i / 5) }, (_, b) => b))); /** * `collections::unlike_eqs` of every pair of positions where `eq` and `ne` disagree and each @@ -639,16 +651,16 @@ const writes = Array.from({ length: 5 * 4 }, (_, i) => Uint8Array.of(writesOp, i */ const unlikeEqsOp = op("collections::unlike_eqs"); const unlikeEqs = [[], [5], [1, 2], [0, 7, 2, 9], [3, 3, 3, 3, 3, 3, 3, 3, 3]].flatMap((bytes) => - Array.from({ length: 128 }, (_, kind) => Uint8Array.of(unlikeEqsOp, kind, ...bytes)), + Array.from({ length: 128 }, (_, kind) => Uint8Array.of(...unlikeEqsOp, kind, ...bytes)), ); /** `collections::own_nes` of the same positions, lengths, and items as `unlike_eqs`. */ const ownNesOp = op("collections::own_nes"); -const ownNes = unlikeEqs.map((input) => Uint8Array.of(ownNesOp, ...input.subarray(1))); +const ownNes = unlikeEqs.map((input) => Uint8Array.of(...ownNesOp, ...input.subarray(2))); /** `collections::unlike_compares` of the same positions, lengths, and items as `unlike_eqs`. */ const unlikeComparesOp = op("collections::unlike_compares"); -const unlikeCompares = unlikeEqs.map((input) => Uint8Array.of(unlikeComparesOp, ...input.subarray(1))); +const unlikeCompares = unlikeEqs.map((input) => Uint8Array.of(...unlikeComparesOp, ...input.subarray(2))); /** * `iters::range_froms` of each case, each way its zips read the range, from each distance @@ -660,12 +672,12 @@ const rangeFroms = Array.from({ length: 16 * 4 * 4 * 10 }, (_, i) => { const kind = (i % 16) | ((Math.floor(i / 16) % 4) << 6); const a = [0, 1, 3, 250][Math.floor(i / 64) % 4]; const data = Array.from({ length: Math.floor(i / 256) }, (_, j) => (j === 0 ? a : (j * 37) & 0xff)); - return Uint8Array.of(rangeFromsOp, kind, ...data); + return Uint8Array.of(...rangeFromsOp, kind, ...data); }); /** `iters::range_methods` of each type's table, at each pair of its first eight values. */ const rangeMethodsOp = op("iters::range_methods"); -const rangeMethods = Array.from({ length: 19 * 64 }, (_, i) => Uint8Array.of(rangeMethodsOp, i % 19, Math.floor(i / 19) % 8, Math.floor(i / 152))); +const rangeMethods = Array.from({ length: 19 * 64 }, (_, i) => Uint8Array.of(...rangeMethodsOp, i % 19, Math.floor(i / 19) % 8, Math.floor(i / 152))); /** * `iters::compares` of each comparison, shape, and pair, over no items, one, a run up to @@ -673,7 +685,7 @@ const rangeMethods = Array.from({ length: 19 * 64 }, (_, i) => Uint8Array.of(ran */ const comparesOp = op("iters::compares"); const compares = [[], [5], [1, 2, 3], [250, 251, 252, 253, 254, 255], [0xff, 3, 0xff], [0, 1, 2, 3, 4, 5], [7, 7, 7, 7, 7, 7, 7, 7, 7]].flatMap((data) => - Array.from({ length: 256 }, (_, kind) => Uint8Array.of(comparesOp, kind, ...data)), + Array.from({ length: 256 }, (_, kind) => Uint8Array.of(...comparesOp, kind, ...data)), ); /** @@ -681,7 +693,7 @@ const compares = [[], [5], [1, 2, 3], [250, 251, 252, 253, 254, 255], [0xff, 3, * 4, and 5 agree and disagree. */ const refOrdersOp = op("collections::ref_orders"); -const refOrders = [[], [5], [3, 3, 3], [1, 2], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], [7, 2, 9, 4, 4, 11, 15, 6], [12, 6, 0, 30, 60]].map((data) => Uint8Array.of(refOrdersOp, 0, ...data)); +const refOrders = [[], [5], [3, 3, 3], [1, 2], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], [7, 2, 9, 4, 4, 11, 15, 6], [12, 6, 0, 30, 60]].map((data) => Uint8Array.of(...refOrdersOp, 0, ...data)); /** * `separated::separated` of every form and order of reads, over data whose bytes its bounds @@ -689,7 +701,7 @@ const refOrders = [[], [5], [3, 3, 3], [1, 2], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 */ const separatedOp = op("separated::separated"); const separated = [[], [0], [0x90], [0, 0, 0], [5, 0x90, 0, 0x7f, 0, 0xff, 3], [0x80, 1, 2, 0], [0xff, 0x7f, 0x80, 0x81]].flatMap((data) => - Array.from({ length: 256 }, (_, kind) => Uint8Array.of(separatedOp, kind, ...data)), + Array.from({ length: 256 }, (_, kind) => Uint8Array.of(...separatedOp, kind, ...data)), ); /** @@ -715,7 +727,7 @@ function wraps(): Uint8Array[] { for (const b of edges) { const c = edges[(i * 5 + 3) % edges.length]; const [low, high] = shifts[i++ % shifts.length]; - for (let kind = 0; kind < 24; kind++) cases.push(Uint8Array.of(o, kind, ...be(a), ...be(b), ...be(c), low, high)); + for (let kind = 0; kind < 24; kind++) cases.push(Uint8Array.of(...o, kind, ...be(a), ...be(b), ...be(c), low, high)); } } } @@ -743,7 +755,7 @@ function saturates(): Uint8Array[] { for (const a of edges) { for (const b of edges) { const c = edges[(i++ * 5 + 3) % edges.length]; - for (let kind = 0; kind < 24; kind++) cases.push(Uint8Array.of(o, kind, ...be(a), ...be(b), ...be(c))); + for (let kind = 0; kind < 24; kind++) cases.push(Uint8Array.of(...o, kind, ...be(a), ...be(b), ...be(c))); } } } @@ -781,9 +793,9 @@ function methods(): Uint8Array[] { for (let kind = 0; kind < 42; kind++) { for (const a of edges) { if (exponents.has(kind)) { - for (const n of shifts) cases.push(Uint8Array.of(o, kind, ...be(a), ...be(others[i++ % others.length]), ...u32(n))); + for (const n of shifts) cases.push(Uint8Array.of(...o, kind, ...be(a), ...be(others[i++ % others.length]), ...u32(n))); } else { - for (const b of others) cases.push(Uint8Array.of(o, kind, ...be(a), ...be(b), ...u32(shifts[i++ % shifts.length]))); + for (const b of others) cases.push(Uint8Array.of(...o, kind, ...be(a), ...be(b), ...u32(shifts[i++ % shifts.length]))); } } } @@ -800,15 +812,15 @@ function methods(): Uint8Array[] { const asciiText = [0x20, 0x09, 0x0b, 0x48, 0x7a, 0x22, 0x5c, 0x27, 0x7f, 0x80, 0xff, 0x0a, 0x0c, 0x0d, 0x20]; const asciiCases = [ ...["ascii::bytes", "ascii::slices"].flatMap((name) => - Array.from({ length: 4 * (asciiText.length + 1) }, (_, i) => Uint8Array.of(op(name), i % 4, ...asciiText.slice(0, Math.floor(i / 4)))), + Array.from({ length: 4 * (asciiText.length + 1) }, (_, i) => Uint8Array.of(...op(name), i % 4, ...asciiText.slice(0, Math.floor(i / 4)))), ), // Text of every char `ascii.rs` tries, each once. - ...[0, 1, 2, 3].map((kind) => Uint8Array.of(op("ascii::slices"), kind, ...Array.from({ length: 41 }, (_, i) => i))), - Uint8Array.of(op("ascii::chars"), 0, ...[0xd7ff, 0xd800, 0xdfff, 0xe000, 0x1f600, 0x10ffff, 0x110000].flatMap((n) => [n >>> 24, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff])), + ...[0, 1, 2, 3].map((kind) => Uint8Array.of(...op("ascii::slices"), kind, ...Array.from({ length: 41 }, (_, i) => i))), + Uint8Array.of(...op("ascii::chars"), 0, ...[0xd7ff, 0xd800, 0xdfff, 0xe000, 0x1f600, 0x10ffff, 0x110000].flatMap((n) => [n >>> 24, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff])), // `parts` of each kind at every pair of byte offsets up to two past the end of its // 14 bytes of text, and of no text. - ...Array.from({ length: 8 * 17 * 17 }, (_, i) => Uint8Array.of(op("ascii::parts"), i % 8, Math.floor(i / 8) % 17, Math.floor(i / 8 / 17))), - ...Array.from({ length: 8 * 3 * 3 }, (_, i) => Uint8Array.of(op("ascii::parts"), 0x10 | (i % 8), Math.floor(i / 8) % 3, Math.floor(i / 24))), + ...Array.from({ length: 8 * 17 * 17 }, (_, i) => Uint8Array.of(...op("ascii::parts"), i % 8, Math.floor(i / 8) % 17, Math.floor(i / 8 / 17))), + ...Array.from({ length: 8 * 3 * 3 }, (_, i) => Uint8Array.of(...op("ascii::parts"), 0x10 | (i % 8), Math.floor(i / 8) % 3, Math.floor(i / 24))), ]; /** @@ -820,7 +832,7 @@ const getsAt = (n: number) => [...Array.from({ length: n + 3 }, (_, i) => i), 0x const gets = [0, 0x10].flatMap((text) => { const at = getsAt(text === 0 ? 13 : 0); return Array.from({ length: 16 * at.length * at.length }, (_, i) => - Uint8Array.of(getsOp, text | (i % 16), at[Math.floor(i / 16) % at.length], at[Math.floor(i / 16 / at.length)]), + Uint8Array.of(...getsOp, text | (i % 16), at[Math.floor(i / 16) % at.length], at[Math.floor(i / 16 / at.length)]), ); }); @@ -835,16 +847,16 @@ const capacityCases = [ ...[["capacity::vecs", 25], ["capacity::deques", 21], ["capacity::heaps", 13]].flatMap(([name, steps]) => Array.from({ length: 5 * 2 * (steps as number) * 4 }, (_, i) => { const [kind, first, step, arg] = [i % 5, (Math.floor(i / 5) % 2) * 0x11, Math.floor(i / 10) % (steps as number), [3, 0xc1, 0xe3, 0x16][Math.floor(i / (10 * (steps as number)))]]; - return Uint8Array.of(op(name as string), kind, first, ...[0, 1, 0, 2, 0, 3, 0, 4, 0, 5].map((b, j) => (j % 2 === 0 ? b : b * 41)), step, arg, step, arg ^ 1); + return Uint8Array.of(...op(name as string), kind, first, ...[0, 1, 0, 2, 0, 3, 0, 4, 0, 5].map((b, j) => (j % 2 === 0 ? b : b * 41)), step, arg, step, arg ^ 1); }), ), ...[["capacity::vecs", 25], ["capacity::deques", 21], ["capacity::heaps", 13]].flatMap(([name, steps]) => - Array.from({ length: 5 * 2 * (steps as number) }, (_, i) => Uint8Array.of(op(name as string), i % 5, 0, 0, 1, 3 + (Math.floor(i / 5) % 2), 3, Math.floor(i / 10), 0)), + Array.from({ length: 5 * 2 * (steps as number) }, (_, i) => Uint8Array.of(...op(name as string), i % 5, 0, 0, 1, 3 + (Math.floor(i / 5) % 2), 3, Math.floor(i / 10), 0)), ), ...["capacity::vecs", "capacity::deques", "capacity::heaps"].flatMap((name) => - Array.from({ length: 5 * 2 }, (_, i) => Uint8Array.of(op(name), i % 5, Math.floor(i / 5) * 0x11, ...Array.from({ length: 40 }, (_, p) => [0, p]).flat())), + Array.from({ length: 5 * 2 }, (_, i) => Uint8Array.of(...op(name), i % 5, Math.floor(i / 5) * 0x11, ...Array.from({ length: 40 }, (_, p) => [0, p]).flat())), ), - ...Array.from({ length: 8 * 6 }, (_, i) => Uint8Array.of(op("capacity::collects"), i % 8, [0, 1, 2, 5, 9, 23][Math.floor(i / 8)], 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)), + ...Array.from({ length: 8 * 6 }, (_, i) => Uint8Array.of(...op("capacity::collects"), i % 8, [0, 1, 2, 5, 9, 23][Math.floor(i / 8)], 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)), ]; export const cases: Uint8Array[] = [...loops, ...durations, ...ints(), ...utf8, ...floats, ...formats, ...parsed, ...singlesCases, ...singlesFormats, ...singlesParsed, ...dyns, ...cells, ...shared, ...locks, ...guards, ...maps, ...mappedLocks, ...relocks, ...tries, ...mutexMaps, ...printed, ...downgrades, ...leaks, ...rwlocks, ...onces, ...bounds, ...unsafeCells, ...edits, ...subslices, ...consumers, ...closures, ...elements, ...ranges, ...reducers, ...sliceEdits, ...drains, ...iters, ...hinted, ...peekedOthers, ...hints, ...reserves, ...setOps, ...zeros, ...writes, ...floatsMulAdds, ...singlesMulAdds, ...unlikeEqs, ...ownNes, ...rangeFroms, ...compares, ...unlikeCompares, ...rangeMethods, ...wraps(), ...refOrders, ...saturates(), ...methods(), ...separated, ...asciiCases, ...gets, ...capacityCases]; diff --git a/ts/fixtures/lang/expected/harness.ts b/ts/fixtures/lang/expected/harness.ts index 8c9c5641c..2a9d520f8 100644 --- a/ts/fixtures/lang/expected/harness.ts +++ b/ts/fixtures/lang/expected/harness.ts @@ -64,8 +64,8 @@ import { u8s as wraps_u8s, u16s as wraps_u16s, u32s as wraps_u32s, u64s as wraps import { zeros, impls, keys as zeros_keys, unread, writes as zeros_writes } from "./zeros.ts"; /** - * Each case's name and what it runs. The first byte picks one modulo their count, so a - * case added at the end moves no other. + * Each case's name and what it runs. The first two bytes, big-endian, pick one modulo + * their count, so a case added at the end moves no other. */ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined][] { return [["ints::u8_op", u8Op as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["ints::u16_op", u16Op as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["ints::u32_op", u32Op as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["ints::u64_op", u64Op as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["ints::u128_op", u128Op as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["ints::usize_op", usizeOp as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["ints::i8_op", i8Op as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["ints::i16_op", i16Op as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["ints::i32_op", i32Op as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["ints::i64_op", i64Op as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["ints::i128_op", i128Op as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["ints::isize_op", isizeOp as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["ints::neg", neg as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["ints::cast", cast as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["ints::convert", (_2100: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): undefined => { @@ -284,24 +284,34 @@ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) /** Run one case. */ export function run(input: rt.ByteSlice): rt.ByteVec { const out: rt.ByteVec = new rt.ByteVec(); - if (!(input.length >= 2)) { + if (!(input.length >= 3)) { return out; } const cases_: [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined][] = cases(); - cases_[rt.idx(cases_, rt.usizeRem(input.data[input.start] as rt.usize, cases_.length))][1](input.data[input.start + 1], new rt.ByteSlice(input.data, input.start + 2, input.length - 2), out); + cases_[rt.idx(cases_, rt.usizeRem(((input.data[input.start] << 8) | input.data[input.start + 1]) as rt.usize, cases_.length))][1](input.data[input.start + 2], new rt.ByteSlice(input.data, input.start + 3, input.length - 3), out); return out; } -/** The first byte that picks the case named `name`, which `caller.ts` targets by. */ -export function op(name: string): rt.u8 { - const _2: rt.usize | undefined = rt.iterPosition(rt.iterSlice(cases()), (_22: [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]): boolean => { - return _22[0] === name; +/** + * The first two bytes, as one big-endian number, that pick the case named `name`, which + * `caller.ts` targets by. + */ +export function op(name: string): rt.u16 { + const _11: rt.usize | undefined = rt.iterPosition(rt.iterSlice(cases()), (_2: [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]): boolean => { + return _2[0] === name; }); - if (_2 === undefined) { + if (_11 !== undefined) { + const _20: rt.Result = rt.u16TryFrom(_11); + if (_20.ok) { + if ((_20.value >> 8) !== 255) { + return _20.value; + } + } + const args4: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPad(f, name)]; + rt.panicFmt(["case ", args4[0], " is past what two bytes pick"]); + } else { const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPad(f, name)]; rt.panicFmt(["no case ", args2[0]]); - } else { - return _2 & 0xff; } } diff --git a/ts/fixtures/lang/src/harness.rs b/ts/fixtures/lang/src/harness.rs index 1fa621a15..2e7781ec8 100644 --- a/ts/fixtures/lang/src/harness.rs +++ b/ts/fixtures/lang/src/harness.rs @@ -1,4 +1,4 @@ -//! The entry point both sides of the differential test run. The first byte picks a +//! The entry point both sides of the differential test run. The first two bytes pick a //! case and the rest is its input. use crate::{ @@ -18,11 +18,11 @@ pub fn entry(input: &[u8]) -> Vec { } } -/// What a case runs on the second byte and the rest. +/// What a case runs on the third byte and the rest. type Case = fn(u8, &[u8], &mut Vec); -/// Each case's name and what it runs. The first byte picks one modulo their count, so a -/// case added at the end moves no other. +/// Each case's name and what it runs. The first two bytes, big-endian, pick one modulo +/// their count, so a case added at the end moves no other. fn cases() -> Vec<(&'static str, Case)> { vec![ ("ints::u8_op", ints::u8_op), @@ -292,20 +292,25 @@ fn cases() -> Vec<(&'static str, Case)> { /// Run one case. pub fn run(input: &[u8]) -> Vec { let mut out = Vec::new(); - let [op, kind, data @ ..] = input else { + let [high, low, kind, data @ ..] = input else { return out; }; let cases = cases(); - let (_, case) = cases[usize::from(*op) % cases.len()]; + let (_, case) = cases[usize::from(u16::from_be_bytes([*high, *low])) % cases.len()]; case(*kind, data, &mut out); out } -/// The first byte that picks the case named `name`, which `caller.ts` targets by. -pub fn op(name: &str) -> u8 { - match cases().iter().position(|&(n, _)| n == name) { - Some(i) => i as u8, - None => panic!("no case {name}"), +/// The first two bytes, as one big-endian number, that pick the case named `name`, which +/// `caller.ts` targets by. +pub fn op(name: &str) -> u16 { + let Some(i) = cases().iter().position(|&(n, _)| n == name) else { + panic!("no case {name}"); + }; + // A first byte of 0xff is `entry`'s, which prints a `Fault` instead. + match u16::try_from(i) { + Ok(op) if op >> 8 != 0xff => op, + _ => panic!("case {name} is past what two bytes pick"), } } diff --git a/ts/tests/differential.ts b/ts/tests/differential.ts index 82eca6e9d..d777483c1 100644 --- a/ts/tests/differential.ts +++ b/ts/tests/differential.ts @@ -5,6 +5,7 @@ // // A harness, or a hand-written caller wrapping one, exports `run(input: rt.ByteSlice): // Vec`, and may export `cases`, inputs it names, which run with the generated ones, +// `ops`, the bytes that pick each of its cases, if not the first byte alone, // and `within(input, native, translated)`, which accepts outputs that differ only where // Rust leaves the result to the platform, as libm's are. // The wasm build exports `input`, `run`, and `output` around the crate's entry, which @@ -80,20 +81,22 @@ function display(outcome: Outcome): string { } const cases: Uint8Array[] = [new Uint8Array(0)]; +const ops: Uint8Array[] = harness.ops ?? Array.from({ length: 256 }, (_, op) => Uint8Array.of(op)); +if (new Set(ops.map((op) => Buffer.from(op).toString("hex"))).size !== ops.length) throw new Error("two ops are the same bytes, so one of them never runs"); // Every op with every short tail, so each path's edges are covered exhaustively. -for (let op = 0; op < 256; op++) { - cases.push(Uint8Array.of(op)); - for (let b = 0; b < 256; b += 17) cases.push(Uint8Array.of(op, b)); +for (const op of ops) { + cases.push(op); + for (let b = 0; b < 256; b += 17) cases.push(Uint8Array.of(...op, b)); } -// Full-width values through every op: each u64 boundary 2^k - 1, 2^k, 2^k + 1. -for (let op = 0; op < 8; op++) { +// Full-width values through the first ops: each u64 boundary 2^k - 1, 2^k, 2^k + 1. +for (const op of ops.slice(0, 8)) { for (let k = 0n; k <= 64n; k++) { for (const delta of [-1n, 0n, 1n]) { const value = ((1n << k) + delta) & 0xffffffffffffffffn; const bytes = new Uint8Array(8); new DataView(bytes.buffer).setBigUint64(0, value); for (const selector of [0, 1, 2, 3, 4, 5, 6]) { - cases.push(Uint8Array.of(op, selector, ...bytes, ...bytes, ...bytes)); + cases.push(Uint8Array.of(...op, selector, ...bytes, ...bytes, ...bytes)); } } } From be1976822fab220f21192567e1009fda2d3ce8f9 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 19:34:38 -0800 Subject: [PATCH 2/3] fix(ts): leave a Fault's input out of the float tolerance Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/caller.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ts/fixtures/lang/caller.ts b/ts/fixtures/lang/caller.ts index 86e35e7f1..113a64c31 100644 --- a/ts/fixtures/lang/caller.ts +++ b/ts/fixtures/lang/caller.ts @@ -237,6 +237,8 @@ const [floatsAt, singlesAt] = [pick("floats::floats"), pick("singles::singles")] const ULPS = 2n; export function within(input: Uint8Array, native: Uint8Array, translated: Uint8Array): boolean { + // A first byte of 0xff prints a `Fault`, and picks no case. + if (input[0] === 0xff) return false; const at = ((input[0] << 8) | input[1]) % count; const width = at === floatsAt ? 8 : at === singlesAt ? 4 : 0; if (width === 0 || input[2] % 10 !== 7 || native.length !== translated.length) return false; From 7e814171ea0a54b55ec901b6eef998db01600b6a Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 20:05:35 -0800 Subject: [PATCH 3/3] fix(ts): hold a byte vector's capacity as a heap or deque takes it Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/caller.ts | 5 ++++- ts/runtime/rt.ts | 7 +++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/ts/fixtures/lang/caller.ts b/ts/fixtures/lang/caller.ts index 113a64c31..5e12e45c1 100644 --- a/ts/fixtures/lang/caller.ts +++ b/ts/fixtures/lang/caller.ts @@ -843,7 +843,9 @@ const gets = [0, 0x10].flatMap((text) => { * few, past what it may hold, and within eight of `usize::MAX`, once it grew past its * least capacity, made by `new` and by `with_capacity`; each step after a `reserve` or * `reserve_exact` that needed no room, whose floor it keeps; a run of pushes that doubles - * it from each; and each way `collects` makes one, of each length it tries. + * it from each; a heap that a `reserve` gave room past its length, remade from its `Vec` + * and cleared, which keeps that floor; and each way `collects` makes one, of each length + * it tries. */ const capacityCases = [ ...[["capacity::vecs", 25], ["capacity::deques", 21], ["capacity::heaps", 13]].flatMap(([name, steps]) => @@ -858,6 +860,7 @@ const capacityCases = [ ...["capacity::vecs", "capacity::deques", "capacity::heaps"].flatMap((name) => Array.from({ length: 5 * 2 }, (_, i) => Uint8Array.of(...op(name), i % 5, Math.floor(i / 5) * 0x11, ...Array.from({ length: 40 }, (_, p) => [0, p]).flat())), ), + ...Array.from({ length: 5 }, (_, kind) => Uint8Array.of(...op("capacity::heaps"), kind, 0, 8, 15, 3, 0, 12, 0, 7, 0)), ...Array.from({ length: 8 * 6 }, (_, i) => Uint8Array.of(...op("capacity::collects"), i % 8, [0, 1, 2, 5, 9, 23][Math.floor(i / 8)], 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)), ]; diff --git a/ts/runtime/rt.ts b/ts/runtime/rt.ts index 438eb76d6..812c7d983 100644 --- a/ts/runtime/rt.ts +++ b/ts/runtime/rt.ts @@ -1357,10 +1357,13 @@ function realloc(v: ByteVec, cap: number): void { v.data = data; } -/** The bytes of `v` as a `Vec`, with its capacity, as a deque or heap takes it. */ +/** + * The bytes of `v` as a `Vec`, with its capacity, as a deque or heap takes it: + * held, as `reserve` may have asked for it, though its length gives it now. + */ export function byteVecToVec(v: ByteVec): number[] { const out = Array.from(byteVecView(v)); - capSet(out, out.length, v.data.length, 1); + capHold(out, v.data.length, 1); return out; }