From 0763610e93a464943833a0ccb498bf193d2f6eb0 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Oct 2026 01:11:37 -0500 Subject: [PATCH] Version loops to a typed array's length on every typed array they index A loop such as `for (let i = 0; i < out.length; i++) out[i] = x[i] * y[i]` keeps its integer induction, and the version guard now also receives the loop's counted form, so it covers the accesses of `x` and `y` at the counter. Before, only counted loops (`i < n`) passed a counter to the guard, and loops to a length kept one bounds check per access of every other typed array, which kept LLVM from vectorizing them. Adds the typed-array-kernels runtime workload: element-wise loops over Float64Array, Float32Array, Uint8Array and Int32Array, and RGBA to gray. --- benchmarks/runtime/README.md | 1 + .../workloads/typed-array-kernels.json | 6 + .../runtime/workloads/typed-array-kernels.ts | 92 +++++++++++ .../backend/llvm/bytes-loop-versions.test.ts | 41 +++++ .../src/backend/llvm/emit-statements.ts | 21 ++- ...2-typed-array-length-loop-versions.ts.json | 6 + ...3-typed-array-length-loop-bindings.ts.json | 6 + .../6992-typed-array-length-loop-versions.ts | 141 +++++++++++++++++ .../6993-typed-array-length-loop-bindings.ts | 146 ++++++++++++++++++ ...2-typed-array-length-loop-versions.ts.json | 1 + ...3-typed-array-length-loop-bindings.ts.json | 1 + 11 files changed, 458 insertions(+), 4 deletions(-) create mode 100644 benchmarks/runtime/workloads/typed-array-kernels.json create mode 100644 benchmarks/runtime/workloads/typed-array-kernels.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6992-typed-array-length-loop-versions.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6993-typed-array-length-loop-bindings.ts.json create mode 100644 tests/corpus/6992-typed-array-length-loop-versions.ts create mode 100644 tests/corpus/6993-typed-array-length-loop-bindings.ts create mode 100644 tests/harness/corpus-costs/6992-typed-array-length-loop-versions.ts.json create mode 100644 tests/harness/corpus-costs/6993-typed-array-length-loop-bindings.ts.json diff --git a/benchmarks/runtime/README.md b/benchmarks/runtime/README.md index d736a4ed4..1acdc9ce7 100644 --- a/benchmarks/runtime/README.md +++ b/benchmarks/runtime/README.md @@ -26,6 +26,7 @@ Application-shaped workloads for measuring the speed of compiled executables. Ea | `region-builds` | cpu | a watch-mode build loop over `LifetimeRegion`: each build binds a program in a new region and releases the previous one, which a cache keeps referenced for two builds | | `template-render` | cpu | template literals, escaping, `map`/`join` | | `thread-slots`, `thread-slots-cache` | cpu | worker threads reading one published type graph from an `InternTable` and keeping per-thread state for each type they visit: a `ThreadSlots` family, or a direct-mapped cache by id in front of a `Map` by id (the `-cache` entry); under Node.js both are per thread | +| `typed-array-kernels` | cpu | element-wise loops that run to one typed array's length and index others at the same position: `Float64Array` axpy, `Float32Array` blend, `Uint8Array` mask, wrapping `Int32Array` add, RGBA to gray | | `typed-array-scan`, `typed-array-scan-workers` | cpu | element-by-element scan loops over `Uint8Array`, `Uint16Array`, `Int32Array` and `Float64Array`; the second entry is a worker-enabled executable (it can start a worker), so each access also checks for shared storage | | `unicode-lexer` | cpu | a hand-written lexer over a 2.7M-unit bundled module whose comments and strings are in several languages: `charCodeAt` scans, line starts | | `validate-errors` | cpu | throw/catch on a fraction of inputs, small call chains | diff --git a/benchmarks/runtime/workloads/typed-array-kernels.json b/benchmarks/runtime/workloads/typed-array-kernels.json new file mode 100644 index 000000000..fff290679 --- /dev/null +++ b/benchmarks/runtime/workloads/typed-array-kernels.json @@ -0,0 +1,6 @@ +{ + "entry": "workloads/typed-array-kernels.ts", + "args": [ + "1" + ] +} diff --git a/benchmarks/runtime/workloads/typed-array-kernels.ts b/benchmarks/runtime/workloads/typed-array-kernels.ts new file mode 100644 index 000000000..87c227bbc --- /dev/null +++ b/benchmarks/runtime/workloads/typed-array-kernels.ts @@ -0,0 +1,92 @@ +// Element-wise loops over several typed arrays: each loop runs to one +// array's length and reads or writes the others at the same index. + +function axpy(a: number, x: Float64Array, y: Float64Array, out: Float64Array): void { + for (let i = 0; i < out.length; i++) out[i] = a * x[i]! + y[i]!; +} + +function blend(t: number, a: Float32Array, b: Float32Array, out: Float32Array): void { + for (let i = 0; i < out.length; i++) out[i] = a[i]! * (1 - t) + b[i]! * t; +} + +function mask(data: Uint8Array, key: Uint8Array, out: Uint8Array): void { + for (let i = 0; i < out.length; i++) out[i] = data[i]! ^ key[i]!; +} + +function addWrap(a: Int32Array, b: Int32Array, out: Int32Array): void { + for (let i = 0; i < out.length; i++) out[i] = (a[i]! + b[i]!) | 0; +} + +function gray(rgba: Uint8Array, out: Uint8Array): void { + for (let i = 0; i < out.length; i++) { + const j = i * 4; + out[i] = (rgba[j]! * 77 + rgba[j + 1]! * 150 + rgba[j + 2]! * 29) >> 8; + } +} + +const scale = Number(process.argv[2] ?? "1"); +const n = 1 << 16; +const x = new Float64Array(n); +const y = new Float64Array(n); +const d = new Float64Array(n); +const fa = new Float32Array(n); +const fb = new Float32Array(n); +const fo = new Float32Array(n); +const data = new Uint8Array(n); +const key = new Uint8Array(n); +const masked = new Uint8Array(n); +const ia = new Int32Array(n); +const ib = new Int32Array(n); +const io = new Int32Array(n); +const rgba = new Uint8Array(n * 4); +const luma = new Uint8Array(n); +let state = 2463534242; +const next = (): number => { + state ^= state << 13; + state ^= state >>> 17; + state ^= state << 5; + return state >>> 0; +}; +for (let i = 0; i < n; i++) { + x[i] = (next() % 20000) / 100 - 100; + y[i] = (next() % 20000) / 100; + fa[i] = (next() % 1000) / 7; + fb[i] = (next() % 1000) / 9; + data[i] = next() & 255; + key[i] = next() & 255; + ia[i] = next() | 0; + ib[i] = next() | 0; +} +for (let i = 0; i < rgba.length; i++) rgba[i] = next() & 255; + +const rounds = Math.round(1200 * scale); +let f64Sum = 0; +let f32Sum = 0; +let byteSum = 0; +let intSum = 0; +let lumaSum = 0; +for (let r = 0; r < rounds; r++) { + // Change one input element per round so no round repeats the last. + const at = (r * 7919) & (n - 1); + x[at] = r; + fa[at] = r; + data[at] = r & 255; + ia[at] = r * 65537; + rgba[at * 4] = r & 255; + axpy(1.5, x, y, d); + blend((r & 15) / 16, fa, fb, fo); + mask(data, key, masked); + addWrap(ia, ib, io); + gray(rgba, luma); + const probe = (r * 104729) & (n - 1); + f64Sum += d[probe]!; + f32Sum += fo[probe]!; + byteSum += masked[probe]!; + intSum = (intSum + io[probe]!) | 0; + lumaSum += luma[probe]!; +} +console.log(`float64 ${f64Sum.toFixed(3)}`); +console.log(`float32 ${f32Sum.toFixed(3)}`); +console.log(`uint8 ${byteSum}`); +console.log(`int32 ${intSum}`); +console.log(`gray ${lumaSum}`); diff --git a/packages/compiler/src/backend/llvm/bytes-loop-versions.test.ts b/packages/compiler/src/backend/llvm/bytes-loop-versions.test.ts index ddcfd667d..ea568bc3f 100644 --- a/packages/compiler/src/backend/llvm/bytes-loop-versions.test.ts +++ b/packages/compiler/src/backend/llvm/bytes-loop-versions.test.ts @@ -209,6 +209,47 @@ test("emitted loops version the body and tag typed-array memory", () => { expect(elements).not.toBe(header); }); +test("loops to a typed array's length version the other typed arrays they index", () => { + const length = (receiver: IrExpr): IrExpr => ({ + kind: "bytesIntrinsic", + method: "length", + receiver, + args: [], + type: F64, + loc, + }); + const emit = (body: IrStmt[]): string => { + const mod: IrModule = { + irVersion: 17, + sourceFile: loc.file, + entry: "__main", + functions: [ + { name: "__main", params: [], returnType: VOID, locals: [], body: [], loc }, + fn(body), + ], + }; + return /^define internal [^\n]*@sc_bf_kernel[^]*?^}/m.exec(emitLlvmModule(mod))![0]; + }; + // for (let i = 0; i < out.length; i++) out[i] = src[i] * 2 + const scaled = set(ref("out", f64s), ref("i"), bin("*", get(ref("src", f64s), ref("i")), num(2))); + const body = emit([counted([scaled], length(ref("out", f64s)))]); + const fast = /^bytes\.version\.fast\d+:$[^]*?^bytes\.version\.done\d+:$/m.exec(body)![0]; + // The loop keeps its integer induction, and the fast copy reads `src` + // unchecked. + expect(body).toContain("integer induction i"); + expect(fast).not.toContain("bytes.index"); + expect(body.slice(0, body.indexOf(fast))).toContain("bytes.index.invalid"); + // A body that rebinds `src` keeps one checked loop. + const rebound = emit([ + counted( + [scaled, { kind: "assign", localId: "src", value: ref("out", f64s), loc }], + length(ref("out", f64s)), + ), + ]); + expect(rebound).not.toContain("bytes.version.fast"); + expect(rebound).toContain("bytes.index.invalid"); +}); + test("Float16Array accesses version in elements and share the element tag", () => { const f16s: IrType = { kind: "bytes", elem: "f16" }; const withHalves = (body: IrStmt[]): IrFunction => { diff --git a/packages/compiler/src/backend/llvm/emit-statements.ts b/packages/compiler/src/backend/llvm/emit-statements.ts index d1dc6369b..433029651 100644 --- a/packages/compiler/src/backend/llvm/emit-statements.ts +++ b/packages/compiler/src/backend/llvm/emit-statements.ts @@ -945,12 +945,25 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void { B.startBlock(done); break; } - const version = + const versionable = emitter.bytesLoopVersionsEnabled && emitter.bytesLoopVersioning.size < MAX_BYTES_LOOP_VERSION_DEPTH && - !emitter.bytesLoopVersioning.has(s) - ? emitter.matchBytesLoopVersion(s, countedLoop) - : null; + !emitter.bytesLoopVersioning.has(s); + // A loop to a typed array's or an array's length keeps its integer + // induction. Its counted form only gives the version guard the + // counter's iteration space, so the guard also covers accesses of + // other typed arrays at the counter, such as `x[i]` and `y[i]` in + // `for (let i = 0; i < out.length; i++) out[i] = x[i] * y[i]`. + const versionCounted = + versionable && !countedLoop && integerLoop && emitter.countedLoopsEnabled + ? matchIntegerCountedForLoop( + s, + emitter.numericLocals, + emitter.integerRanges, + emitter.unaliasedArrays, + ) + : countedLoop; + const version = versionable ? emitter.matchBytesLoopVersion(s, versionCounted) : null; if (version) { const fast = B.newLabel("bytes.version.fast"), slow = B.newLabel("bytes.version.slow"), diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6992-typed-array-length-loop-versions.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6992-typed-array-length-loop-versions.ts.json new file mode 100644 index 000000000..2d7025954 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6992-typed-array-length-loop-versions.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6992-typed-array-length-loop-versions.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6993-typed-array-length-loop-bindings.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6993-typed-array-length-loop-bindings.ts.json new file mode 100644 index 000000000..43b9c5f09 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6993-typed-array-length-loop-bindings.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6993-typed-array-length-loop-bindings.ts" + ], + "diags": [] +} diff --git a/tests/corpus/6992-typed-array-length-loop-versions.ts b/tests/corpus/6992-typed-array-length-loop-versions.ts new file mode 100644 index 000000000..eb08f7145 --- /dev/null +++ b/tests/corpus/6992-typed-array-length-loop-versions.ts @@ -0,0 +1,141 @@ +// Loops bounded by one typed array's length that index other typed arrays: +// equal and different lengths, offsets, mixed element types and views that +// share a buffer. Out-of-bounds reads give undefined (NaN in arithmetic) and +// out-of-bounds writes are ignored, whichever array is shorter. + +function mulInto(x: Float32Array, y: Float32Array, out: Float32Array): void { + for (let i = 0; i < out.length; i++) out[i] = x[i]! * y[i]!; +} + +function axpy(a: number, x: Float64Array, y: Float64Array, out: Float64Array): void { + for (let i = 0; i < out.length; i++) out[i] = a * x[i]! + y[i]!; +} + +function xorInto(a: Uint8Array, b: Uint8Array, out: Uint8Array): void { + for (let i = 0; i < out.length; i++) out[i] = a[i]! ^ b[i]!; +} + +function addWrap(a: Int32Array, b: Int32Array, out: Int32Array): void { + for (let i = 0; i < out.length; i++) out[i] = (a[i]! + b[i]!) | 0; +} + +function narrow(src: Float64Array, i8: Int8Array, u16: Uint16Array, u32: Uint32Array): void { + for (let i = 0; i < src.length; i++) { + i8[i] = src[i]!; + u16[i] = src[i]!; + u32[i] = src[i]!; + } +} + +function neighbors(a: Int32Array, out: Int32Array): void { + for (let i = 0; i < a.length; i++) out[i] = a[i + 1]! - a[i]!; +} + +function previous(a: Float64Array, out: Float64Array): void { + for (let i = 0; i < out.length; i++) out[i] = a[i - 1]!; +} + +function gray(px: Uint8Array, out: Uint8Array): void { + for (let i = 0; i < out.length; i++) { + const j = i * 4; + out[i] = (px[j]! * 77 + px[j + 1]! * 150 + px[j + 2]! * 29) >> 8; + } +} + +function sumPairs(a: Float64Array, b: Float64Array): number { + let s = 0; + for (let i = 0; i < a.length; i++) s += a[i]! * b[i]!; + return s; +} + +function shiftUp(view: Uint8Array, from: Uint8Array): void { + for (let i = 0; i < view.length; i++) view[i] = from[i]! + 1; +} + +const n = 37; +const fx = new Float32Array(n); +const fy = new Float32Array(n); +const dx = new Float64Array(n); +const dy = new Float64Array(n); +const ba = new Uint8Array(n); +const bb = new Uint8Array(n); +const ia = new Int32Array(n); +const ib = new Int32Array(n); +for (let i = 0; i < n; i++) { + fx[i] = i / 3; + fy[i] = 0.1 * i - 1.7; + dx[i] = i * 1.25 - 9; + dy[i] = (i * 7) % 11; + ba[i] = (i * 29) & 255; + bb[i] = (i * 113 + 7) & 255; + ia[i] = i * 0x3fffffff; + ib[i] = i * -0x2ffffff1; +} + +const fo = new Float32Array(n); +mulInto(fx, fy, fo); +console.log("f32", fo.join(",")); +const shortF = new Float32Array(5); +mulInto(fx, fy.subarray(0, 3), shortF); +console.log("f32 short input", shortF.join(",")); +const longF = new Float32Array(n + 4); +mulInto(fx, fy, longF); +console.log("f32 long output", longF.subarray(n - 2).join(",")); + +const dout = new Float64Array(n); +axpy(-2.5, dx, dy, dout); +console.log("axpy", dout.join(",")); +axpy(0.5, dx.subarray(10), dy, dout); +console.log("axpy short x", dout.subarray(20).join(",")); + +const bo = new Uint8Array(n); +xorInto(ba, bb, bo); +console.log("xor", bo.join(",")); +const io = new Int32Array(n); +addWrap(ia, ib, io); +console.log("i32", io.join(",")); + +const src = new Float64Array([0, -1, 1.5, -128.5, 255.7, 256, 65535.9, -65537, 4294967296.5, NaN, Infinity, -0]); +const i8 = new Int8Array(src.length); +const u16 = new Uint16Array(src.length - 2); +const u32 = new Uint32Array(src.length + 1); +narrow(src, i8, u16, u32); +console.log("narrow", i8.join(","), "|", u16.join(","), "|", u32.join(",")); + +const diff = new Int32Array(n); +neighbors(ia, diff); +console.log("neighbors", diff.subarray(n - 3).join(",")); +const before = new Float64Array(4); +previous(dx, before); +console.log("previous", before.join(",")); + +const px = new Uint8Array(n * 4); +for (let i = 0; i < px.length; i++) px[i] = (i * 53) & 255; +const g = new Uint8Array(n); +gray(px, g); +console.log("gray", g.join(",")); +const wide = new Uint8Array(n + 3); +gray(px, wide); +console.log("gray past input", wide.subarray(n - 1).join(",")); + +console.log("dot", sumPairs(dx, dy), sumPairs(dx, dy.subarray(0, 30))); + +// Views of one buffer: writes through one are visible to reads through the +// other on later iterations. +const buffer = new Uint8Array(24); +for (let i = 0; i < buffer.length; i++) buffer[i] = (i * 37) & 255; +shiftUp(buffer.subarray(1), buffer); +console.log("overlap forward", buffer.join(",")); +for (let i = 0; i < buffer.length; i++) buffer[i] = (i * 37) & 255; +shiftUp(buffer, buffer.subarray(1)); +console.log("overlap backward", buffer.join(",")); +const shared = new ArrayBuffer(64); +const asFloat = new Float32Array(shared); +const asBytes = new Uint8Array(shared, 4, 16); +for (let i = 0; i < asFloat.length; i++) asFloat[i] = i + 0.5; +const fromBytes = new Float32Array(16); +for (let i = 0; i < fromBytes.length; i++) { + asBytes[i] = i * 3; + fromBytes[i] = asFloat[i]!; +} +console.log("mixed views", fromBytes.join(","), asBytes.join(",")); diff --git a/tests/corpus/6993-typed-array-length-loop-bindings.ts b/tests/corpus/6993-typed-array-length-loop-bindings.ts new file mode 100644 index 000000000..e4b242b28 --- /dev/null +++ b/tests/corpus/6993-typed-array-length-loop-bindings.ts @@ -0,0 +1,146 @@ +// Loops bounded by a typed array's length whose bindings change while they +// run, that leave early, or that run at module level, in generators and in +// async functions. Each keeps the reads, writes and bounds the source names +// on every iteration. + +function swapBound(a: Float64Array, b: Float64Array, out: Float64Array): number { + let n = 0; + for (let i = 0; i < a.length; i++) { + out[i] = a[i]! + b[i]!; + n++; + if (i === 2) a = b; + } + return n; +} + +function swapOperand(a: Int16Array, b: Int16Array, out: Int16Array): void { + for (let i = 0; i < out.length; i++) { + out[i] = a[i]! * 3; + if (i === 3) a = b; + } +} + +function closureSwap(a: Uint16Array, b: Uint16Array, out: Uint16Array): void { + const swap = (): void => { + out = b; + }; + for (let i = 0; i < out.length; i++) { + out[i] = a[i]! + 1000; + if (i === 1) swap(); + } +} + +function firstAbove(values: Float32Array, limits: Float32Array): number { + for (let i = 0; i < values.length; i++) { + if (values[i]! > limits[i]!) return i; + } + return -1; +} + +function copyUntilZero(src: Uint8Array, out: Uint8Array): number { + let copied = 0; + for (let i = 0; i < src.length; i++) { + if (src[i] === 0) break; + out[i] = src[i]!; + copied++; + } + return copied; +} + +function checkedScale(src: Int32Array, out: Int32Array): void { + for (let i = 0; i < src.length; i++) { + if (src[i]! < 0) throw new RangeError(`negative at ${i}: ${src[i]}`); + out[i] = src[i]! * 2; + } +} + +function fromList(values: number[], out: Float64Array): void { + for (let i = 0; i < values.length; i++) { + out[i] = values[i]! / 2; + if (values.length < 6) values.push(i * 10); + } +} + +function rows(table: Uint32Array[], totals: Uint32Array): void { + for (let r = 0; r < table.length; r++) { + const row = table[r]!; + let sum = 0; + for (let c = 0; c < row.length; c++) sum += row[c]! * (c + 1); + totals[r] = sum; + } +} + +function* pairs(a: Int8Array, b: Int8Array): Generator { + for (let i = 0; i < a.length; i++) yield a[i]! * b[i]!; +} + +async function addLater(a: Float64Array, b: Float64Array, out: Float64Array): Promise { + for (let i = 0; i < out.length; i++) { + out[i] = a[i]! + b[i]!; + if (i % 2 === 0) await null; + } +} + +const a = new Float64Array([1, 2, 3, 4, 5, 6]); +const b = new Float64Array([10, 20, 30]); +const sum = new Float64Array(8); +console.log("swap bound", swapBound(a, b, sum), sum.join(",")); + +const s16 = new Int16Array([1, -2, 3, -4, 5, -6, 7]); +const t16 = new Int16Array([100, 200, 300, 400, 500, 600]); +const o16 = new Int16Array(7); +swapOperand(s16, t16, o16); +console.log("swap operand", o16.join(",")); + +const u = new Uint16Array([5, 6, 7, 8]); +const other = new Uint16Array(2); +const first = new Uint16Array(4); +closureSwap(u, other, first); +console.log("closure swap", first.join(","), other.join(",")); + +const values = new Float32Array([0.5, 1.5, 2.5, 3.5]); +console.log("first above", firstAbove(values, new Float32Array([1, 1, 1, 1]))); +console.log("first above short", firstAbove(values, new Float32Array([9]))); +console.log("first above none", firstAbove(values, new Float32Array([9, 9, 9, 9]))); + +const text = new Uint8Array([72, 105, 33, 0, 88]); +const copy = new Uint8Array(2); +console.log("copy", copyUntilZero(text, copy), copy.join(",")); + +const ints = new Int32Array([4, 8, -15, 16]); +const doubled = new Int32Array(4); +try { + checkedScale(ints, doubled); +} catch (error) { + console.log((error as Error).name, (error as Error).message, doubled.join(",")); +} + +const list = [3, 5]; +const halves = new Float64Array(5); +fromList(list, halves); +console.log("from list", list.join(","), halves.join(",")); + +const table = [new Uint32Array([1, 2, 3]), new Uint32Array([]), new Uint32Array([4294967295, 1])]; +const totals = new Uint32Array(4); +rows(table, totals); +console.log("rows", totals.join(",")); + +console.log("pairs", [...pairs(new Int8Array([1, -2, 3, 127]), new Int8Array([4, 5, -6]))].join(",")); + +const left = new Float64Array([0.25, 0.5, 0.75, 1]); +const right = new Float64Array([1, 2, 3, 4, 5]); +const later = new Float64Array(5); +const pending = addLater(left, right, later); +console.log("before await", later.join(",")); + +// Module-level bindings, including one a loop body reassigns. +let source = new Uint8Array([9, 8, 7, 6, 5]); +const replacement = new Uint8Array([1, 2]); +const target = new Uint8Array(5); +for (let i = 0; i < source.length; i++) { + target[i] = source[i]! + 100; + if (i === 1) source = replacement; +} +console.log("module", target.join(","), source.join(",")); + +pending.then(() => console.log("after await", later.join(","))); diff --git a/tests/harness/corpus-costs/6992-typed-array-length-loop-versions.ts.json b/tests/harness/corpus-costs/6992-typed-array-length-loop-versions.ts.json new file mode 100644 index 000000000..26208456e --- /dev/null +++ b/tests/harness/corpus-costs/6992-typed-array-length-loop-versions.ts.json @@ -0,0 +1 @@ +{ "plain": 3800, "san": 5600 } diff --git a/tests/harness/corpus-costs/6993-typed-array-length-loop-bindings.ts.json b/tests/harness/corpus-costs/6993-typed-array-length-loop-bindings.ts.json new file mode 100644 index 000000000..a6e281d1e --- /dev/null +++ b/tests/harness/corpus-costs/6993-typed-array-length-loop-bindings.ts.json @@ -0,0 +1 @@ +{ "plain": 2700, "san": 4000 }