blob: 3c0b68085426aa921dae524e7b2e525ecfa6bf82 [file] [edit]
// Tests CSE and strength reduction for WasmArrayLength B3 opcode under OMG compilation.
// Exercises:
// 1. Duplicate array.len on the same array (CSE eliminates redundant length load)
// 2. array.len survives array.set (length is immutable, not clobbered by stores)
// 3. Loop with array.len as bound + array.get (length CSE across loop iterations)
// 4. Loop with array.len as bound + array.set + array.get (length CSE survives stores)
// 5. array.len on a freshly-allocated array (de-trapping: WasmArrayNew is non-null)
// 6. array.len on two different arrays (must NOT be CSE'd)
//
// Approach: allocate arrays once, then call the wasm functions N=100 000 times
// (enough to reach the OMG tier, default threshold: 50 000).
const N = 100000;
function makeInstance(bytes) {
return new WebAssembly.Instance(new WebAssembly.Module(new Uint8Array(bytes).buffer));
}
// ── WasmArrayLength CSE module ──────────────────────────────────────────────
/*
(module
(type $arr (array (mut i32)))
;; 1. Duplicate array.len — CSE should eliminate the second load
(func (export "dupLen") (param $a (ref null 0)) (result i32)
(i32.add (array.len (local.get $a)) (array.len (local.get $a))))
;; 2. array.len before and after array.set — length is immutable
(func (export "lenAfterSet") (param $a (ref null 0)) (param $val i32) (result i32)
(local $before i32)
(local.set $before (array.len (local.get $a)))
(array.set 0 (local.get $a) (i32.const 0) (local.get $val))
(i32.add (local.get $before) (array.len (local.get $a))))
;; 3. Loop summing elements — array.len used as bound should be CSE'd
(func (export "loopSum") (param $a (ref null 0)) (result i32)
(local $i i32) (local $sum i32)
(local.set $i (i32.const 0)) (local.set $sum (i32.const 0))
(block $break (loop $loop
(br_if $break (i32.ge_u (local.get $i) (array.len (local.get $a))))
(local.set $sum (i32.add (local.get $sum) (array.get 0 (local.get $a) (local.get $i))))
(local.set $i (i32.add (local.get $i) (i32.const 1)))
(br $loop)))
(local.get $sum))
;; 4. Loop incrementing elements — array.len bound survives array.set clobber
(func (export "loopInc") (param $a (ref null 0)) (result i32)
(local $i i32) (local $sum i32)
;; first loop: increment each element by 1
(local.set $i (i32.const 0))
(block $break (loop $loop
(br_if $break (i32.ge_u (local.get $i) (array.len (local.get $a))))
(array.set 0 (local.get $a) (local.get $i)
(i32.add (array.get 0 (local.get $a) (local.get $i)) (i32.const 1)))
(local.set $i (i32.add (local.get $i) (i32.const 1)))
(br $loop)))
;; second loop: sum all elements (also uses array.len)
(local.set $i (i32.const 0)) (local.set $sum (i32.const 0))
(block $break2 (loop $loop2
(br_if $break2 (i32.ge_u (local.get $i) (array.len (local.get $a))))
(local.set $sum (i32.add (local.get $sum) (array.get 0 (local.get $a) (local.get $i))))
(local.set $i (i32.add (local.get $i) (i32.const 1)))
(br $loop2)))
(local.get $sum))
;; 5. array.len on a freshly-allocated array (de-trapping optimization)
(func (export "lenOfNew") (param $size i32) (result i32)
(array.len (array.new 0 (i32.const 0) (local.get $size))))
;; 6. array.len on two different arrays — must NOT be CSE'd
(func (export "lenTwoArrays") (param $a (ref null 0)) (param $b (ref null 0)) (result i32)
(i32.add (array.len (local.get $a)) (array.len (local.get $b))))
;; Helpers
(func (export "alloc") (param $size i32) (result (ref null 0))
(array.new 0 (i32.const 0) (local.get $size)))
(func (export "set") (param $a (ref null 0)) (param $idx i32) (param $val i32)
(array.set 0 (local.get $a) (local.get $idx) (local.get $val)))
)
*/
const LEN_CSE = [0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0xab, 0x80, 0x80, 0x80, 0x00, 0x07, 0x5e, 0x7f, 0x01, 0x60, 0x01, 0x63, 0x00, 0x01, 0x7f, 0x60, 0x02, 0x63, 0x00, 0x7f, 0x01, 0x7f, 0x60, 0x01, 0x7f, 0x01, 0x7f, 0x60, 0x02, 0x63, 0x00, 0x63, 0x00, 0x01, 0x7f, 0x60, 0x01, 0x7f, 0x01, 0x63, 0x00, 0x60, 0x03, 0x63, 0x00, 0x7f, 0x7f, 0x00, 0x03, 0x89, 0x80, 0x80, 0x80, 0x00, 0x08, 0x01, 0x02, 0x01, 0x01, 0x03, 0x04, 0x05, 0x06, 0x07, 0xd4, 0x80, 0x80, 0x80, 0x00, 0x08, 0x06, 0x64, 0x75, 0x70, 0x4c, 0x65, 0x6e, 0x00, 0x00, 0x0b, 0x6c, 0x65, 0x6e, 0x41, 0x66, 0x74, 0x65, 0x72, 0x53, 0x65, 0x74, 0x00, 0x01, 0x07, 0x6c, 0x6f, 0x6f, 0x70, 0x53, 0x75, 0x6d, 0x00, 0x02, 0x07, 0x6c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x63, 0x00, 0x03, 0x08, 0x6c, 0x65, 0x6e, 0x4f, 0x66, 0x4e, 0x65, 0x77, 0x00, 0x04, 0x0c, 0x6c, 0x65, 0x6e, 0x54, 0x77, 0x6f, 0x41, 0x72, 0x72, 0x61, 0x79, 0x73, 0x00, 0x05, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x00, 0x06, 0x03, 0x73, 0x65, 0x74, 0x00, 0x07, 0x0a, 0x89, 0x82, 0x80, 0x80, 0x00, 0x08, 0x8b, 0x80, 0x80, 0x80, 0x00, 0x00, 0x20, 0x00, 0xfb, 0x0f, 0x20, 0x00, 0xfb, 0x0f, 0x6a, 0x0b, 0x9a, 0x80, 0x80, 0x80, 0x00, 0x01, 0x01, 0x7f, 0x20, 0x00, 0xfb, 0x0f, 0x21, 0x02, 0x20, 0x00, 0x41, 0x00, 0x20, 0x01, 0xfb, 0x0e, 0x00, 0x20, 0x02, 0x20, 0x00, 0xfb, 0x0f, 0x6a, 0x0b, 0xb2, 0x80, 0x80, 0x80, 0x00, 0x01, 0x02, 0x7f, 0x41, 0x00, 0x21, 0x01, 0x41, 0x00, 0x21, 0x02, 0x02, 0x40, 0x03, 0x40, 0x20, 0x01, 0x20, 0x00, 0xfb, 0x0f, 0x4f, 0x0d, 0x01, 0x20, 0x02, 0x20, 0x00, 0x20, 0x01, 0xfb, 0x0b, 0x00, 0x6a, 0x21, 0x02, 0x20, 0x01, 0x41, 0x01, 0x6a, 0x21, 0x01, 0x0c, 0x00, 0x0b, 0x0b, 0x20, 0x02, 0x0b, 0xdf, 0x80, 0x80, 0x80, 0x00, 0x01, 0x02, 0x7f, 0x41, 0x00, 0x21, 0x01, 0x02, 0x40, 0x03, 0x40, 0x20, 0x01, 0x20, 0x00, 0xfb, 0x0f, 0x4f, 0x0d, 0x01, 0x20, 0x00, 0x20, 0x01, 0x20, 0x00, 0x20, 0x01, 0xfb, 0x0b, 0x00, 0x41, 0x01, 0x6a, 0xfb, 0x0e, 0x00, 0x20, 0x01, 0x41, 0x01, 0x6a, 0x21, 0x01, 0x0c, 0x00, 0x0b, 0x0b, 0x41, 0x00, 0x21, 0x01, 0x41, 0x00, 0x21, 0x02, 0x02, 0x40, 0x03, 0x40, 0x20, 0x01, 0x20, 0x00, 0xfb, 0x0f, 0x4f, 0x0d, 0x01, 0x20, 0x02, 0x20, 0x00, 0x20, 0x01, 0xfb, 0x0b, 0x00, 0x6a, 0x21, 0x02, 0x20, 0x01, 0x41, 0x01, 0x6a, 0x21, 0x01, 0x0c, 0x00, 0x0b, 0x0b, 0x20, 0x02, 0x0b, 0x8b, 0x80, 0x80, 0x80, 0x00, 0x00, 0x41, 0x00, 0x20, 0x00, 0xfb, 0x06, 0x00, 0xfb, 0x0f, 0x0b, 0x8b, 0x80, 0x80, 0x80, 0x00, 0x00, 0x20, 0x00, 0xfb, 0x0f, 0x20, 0x01, 0xfb, 0x0f, 0x6a, 0x0b, 0x89, 0x80, 0x80, 0x80, 0x00, 0x00, 0x41, 0x00, 0x20, 0x00, 0xfb, 0x06, 0x00, 0x0b, 0x8b, 0x80, 0x80, 0x80, 0x00, 0x00, 0x20, 0x00, 0x20, 0x01, 0x20, 0x02, 0xfb, 0x0e, 0x00, 0x0b];
const { dupLen, lenAfterSet, loopSum, loopInc, lenOfNew, lenTwoArrays, alloc, set } = makeInstance(LEN_CSE).exports;
// ─── 1. Duplicate array.len ─────────────────────────────────────────────────
{
const arr = alloc(10);
for (let i = 0; i < N; i++) {
const r = dupLen(arr);
if (r !== 20)
throw new Error(`dupLen(10): expected 20, got ${r}`);
}
const arr2 = alloc(7);
for (let i = 0; i < N; i++) {
const r = dupLen(arr2);
if (r !== 14)
throw new Error(`dupLen(7): expected 14, got ${r}`);
}
}
// ─── 2. array.len survives array.set (immutable length) ─────────────────────
{
const arr = alloc(8);
for (let i = 0; i < N; i++) {
const r = lenAfterSet(arr, i % 1000);
if (r !== 16)
throw new Error(`lenAfterSet(8): expected 16, got ${r}`);
}
}
// ─── 3. Loop with array.len as bound + array.get ────────────────────────────
{
const SIZE = 16;
const arr = alloc(SIZE);
// Fill: arr[i] = i + 1
for (let i = 0; i < SIZE; i++)
set(arr, i, i + 1);
const expected = SIZE * (SIZE + 1) / 2; // sum of 1..SIZE = 136
for (let i = 0; i < N; i++) {
const r = loopSum(arr);
if (r !== expected)
throw new Error(`loopSum(16): expected ${expected}, got ${r}`);
}
}
// ─── 4. Loop with array.len bound + array.set + array.get ───────────────────
{
const SIZE = 8;
const arr = alloc(SIZE);
// Fill: arr[i] = 0 (already zeroed by alloc)
// loopInc increments each element by 1 then sums.
// After call k: arr[i] = k, sum = k * SIZE
for (let i = 0; i < N; i++) {
const r = loopInc(arr);
const expected = (i + 1) * SIZE;
if (r !== expected)
throw new Error(`loopInc iteration ${i}: expected ${expected}, got ${r}`);
}
}
// ─── 5. array.len on freshly-allocated array (de-trapping) ──────────────────
{
for (let i = 0; i < N; i++) {
const sz = (i % 100) + 1;
const r = lenOfNew(sz);
if (r !== sz)
throw new Error(`lenOfNew(${sz}): expected ${sz}, got ${r}`);
}
}
// ─── 6. array.len on two different arrays — must NOT be CSE'd ───────────────
{
const a = alloc(5);
const b = alloc(11);
for (let i = 0; i < N; i++) {
const r = lenTwoArrays(a, b);
if (r !== 16)
throw new Error(`lenTwoArrays(5, 11): expected 16, got ${r}`);
}
// Swap arguments to verify independence
for (let i = 0; i < N; i++) {
const r = lenTwoArrays(b, a);
if (r !== 16)
throw new Error(`lenTwoArrays(11, 5): expected 16, got ${r}`);
}
}