// |jit-test| heavy; allow-oom // Basic tests around creating and linking memories with i64 indices // test the validity of different i64 memory types in validation, compilation, // and the JS-API. function memoryTypeModuleText(shared, initial, max) { return `(module (memory i64 ${initial} ${max ?? ''} ${shared ? `shared` : ''}))`; } function memoryTypeDescriptor(shared, initial, max) { return { address: 'i64', initial, maximum: max, shared, }; } function validAndInstantiableMemoryType(shared, initial, max) { wasmValidateText(memoryTypeModuleText(shared, initial, max)); wasmEvalText(memoryTypeModuleText(shared, initial, max)); new WebAssembly.Memory(memoryTypeDescriptor(shared, initial, max)); } function validButNotInstantiableMemoryType(shared, initial, max, errorMessage) { wasmValidateText(memoryTypeModuleText(shared, initial, max)); assertErrorMessage(() => wasmEvalText(memoryTypeModuleText(shared, initial, max)), WebAssembly.RuntimeError, errorMessage); assertErrorMessage(() => new WebAssembly.Memory(memoryTypeDescriptor(shared, initial, max)), WebAssembly.RuntimeError, errorMessage); } function invalidMemoryType(shared, initial, max, compileMessage, jsMessage) { wasmFailValidateText(memoryTypeModuleText(shared, initial, max), compileMessage); assertErrorMessage(() => wasmEvalText(memoryTypeModuleText(shared, initial, max)), WebAssembly.CompileError, compileMessage); assertErrorMessage(() => new WebAssembly.Memory(memoryTypeDescriptor(shared, initial, max)), Error, jsMessage); } function tableTypeModuleText(element, initial, max) { return `(module (table i64 ${initial} ${max ?? ''} ${element}) )`; } function tableTypeDescriptor(element, initial, max) { return { address: 'i64', element, initial, maximum: max, }; } function validAndInstantiableTableType(element, initial, max) { wasmValidateText(tableTypeModuleText(element, initial, max)); wasmEvalText(tableTypeModuleText(element, initial, max)); new WebAssembly.Table(tableTypeDescriptor(element, initial, max)); } function validButNotInstantiableTableType(element, initial, max, errorMessage) { wasmValidateText(tableTypeModuleText(element, initial, max)); assertErrorMessage(() => wasmEvalText(tableTypeModuleText(element, initial, max)), WebAssembly.RuntimeError, errorMessage); assertErrorMessage(() => new WebAssembly.Table(tableTypeDescriptor(element, initial, max)), WebAssembly.RuntimeError, errorMessage); } function invalidTableType(element, initial, max, compileMessage, jsMessage) { wasmFailValidateText(tableTypeModuleText(element, initial, max), compileMessage); assertErrorMessage(() => wasmEvalText(tableTypeModuleText(element, initial, max)), WebAssembly.CompileError, compileMessage); assertErrorMessage(() => new WebAssembly.Table(tableTypeDescriptor(element, initial, max)), Error, jsMessage); } // valid to define a memory with i64 validAndInstantiableMemoryType(false, 0n); // valid to define max with i64 validAndInstantiableMemoryType(false, 0n, 1n); // invalid for min to be greater than max with i64 invalidMemoryType(false, 2n, 1n, /minimum must not be greater than maximum/, /initial Memory size cannot be greater than maximum/); // valid to define shared memory with max with i64 validAndInstantiableMemoryType(true, 1n, 2n); // invalid to define shared memory without max with i64 invalidMemoryType(true, 1n, undefined, /maximum length required for shared memory/, /maximum is not specified/); // valid to define a table with i64 validAndInstantiableTableType('funcref', 0n); // valid to define table max with i64 validAndInstantiableTableType('funcref', 0n, 1n); // invalid for table min to be greater than max with i64 invalidTableType('funcref', 2n, 1n, /minimum must not be greater than maximum/, /initial Table size cannot be greater than maximum/); // test the validation limits of memory64 memories validButNotInstantiableMemoryType(false, MaxMemory64PagesValidation, undefined, /too many memory pages/); validButNotInstantiableMemoryType(false, MaxMemory64PagesValidation, MaxMemory64PagesValidation, /too many memory pages/); validAndInstantiableMemoryType(false, 0n, MaxMemory64PagesValidation); invalidMemoryType(false, 0n, MaxMemory64PagesValidation + 1n, /maximum memory size too big/, /bad Memory maximum/); validAndInstantiableMemoryType(true, 0n, MaxMemory64PagesValidation); invalidMemoryType(true, 0n, MaxMemory64PagesValidation + 1n, /maximum memory size too big/, /bad Memory maximum/); // test the validation limits of memory64 tables validButNotInstantiableTableType('funcref', MaxTable64ElemsValidation, undefined, /too many table elements/); validButNotInstantiableTableType('funcref', MaxTable64ElemsValidation, MaxTable64ElemsValidation, /too many table elements/); validAndInstantiableTableType('funcref', 0n, MaxTable64ElemsValidation); // cannot create oversize table via either text or binary format since the full u64 range is valid assertErrorMessage(() => new WebAssembly.Table(tableTypeDescriptor('funcref', 0n, MaxTable64ElemsValidation + 1n)), TypeError, /bad Table maximum/); // further validation of memory64 descriptor params assertErrorMessage(() => new WebAssembly.Memory(memoryTypeDescriptor(false, 0)), TypeError, /can't convert 0 to BigInt/); assertErrorMessage(() => new WebAssembly.Memory(memoryTypeDescriptor(false, -1n)), TypeError, /bad Memory initial size/); assertErrorMessage(() => new WebAssembly.Memory(memoryTypeDescriptor(false, 2n**64n)), TypeError, /bad Memory initial size/); assertErrorMessage(() => new WebAssembly.Memory(memoryTypeDescriptor(false, 0n, 1)), TypeError, /can't convert 1 to BigInt/); assertErrorMessage(() => new WebAssembly.Memory(memoryTypeDescriptor(false, 0n, -1n)), TypeError, /bad Memory maximum size/); assertErrorMessage(() => new WebAssembly.Memory(memoryTypeDescriptor(false, 0n, 2n**64n)), TypeError, /bad Memory maximum size/); assertErrorMessage(() => new WebAssembly.Table(tableTypeDescriptor('funcref', 0)), TypeError, /can't convert 0 to BigInt/); assertErrorMessage(() => new WebAssembly.Table(tableTypeDescriptor('funcref', -1n)), TypeError, /bad Table initial size/); assertErrorMessage(() => new WebAssembly.Table(tableTypeDescriptor('funcref', 2n**64n)), TypeError, /bad Table initial size/); assertErrorMessage(() => new WebAssembly.Table(tableTypeDescriptor('funcref', 0n, 1)), TypeError, /can't convert 1 to BigInt/); assertErrorMessage(() => new WebAssembly.Table(tableTypeDescriptor('funcref', 0n, -1n)), TypeError, /bad Table maximum size/); assertErrorMessage(() => new WebAssembly.Table(tableTypeDescriptor('funcref', 0n, 2n**64n)), TypeError, /bad Table maximum size/); // test that linking requires address types to be equal function testLinkMemory(importedAddressType, importAddressType) { let imported = new WebAssembly.Memory({ address: importedAddressType, initial: importedAddressType === 'i64' ? 0n : 0, }); let testModule = `(module (memory (import "" "imported") ${importAddressType} 0))`; if (importedAddressType === importAddressType) { wasmEvalText(testModule, {"": {imported}}); } else { assertErrorMessage(() => wasmEvalText(testModule, {"": {imported}}), WebAssembly.LinkError, /address type/); } } function testLinkTable(importedAddressType, importAddressType) { const imported = new WebAssembly.Table({ element: 'funcref', address: importedAddressType, initial: importedAddressType === 'i64' ? 0n : 0, }); const testModule = `(module (table (import "" "imported") ${importAddressType} 0 funcref))`; if (importedAddressType === importAddressType) { wasmEvalText(testModule, {"": {imported}}); } else { assertErrorMessage(() => wasmEvalText(testModule, {"": {imported}}), WebAssembly.LinkError, /address type/); } } var types = [ ['i64', 'i64'], ['i32', 'i32'], ['i64', 'i32'], ['i32', 'i64'] ]; for ( let [a,b] of types ) { testLinkMemory(a, b); testLinkTable(a, b); } // Active data segments use the address type for the init expression for ( let [memType, exprType] of types ) { const moduleText = ` (module (memory ${memType} 1) (data (${exprType}.const 0) "abcde"))`; if (memType == exprType) { wasmEvalText(moduleText); } else { wasmFailValidateText(moduleText, new RegExp(`expression has type ${exprType} but expected ${memType}`)); } } // Active element segments use the address type for the init expression for ( let [tableType, exprType] of types ) { const moduleText = ` (module (table ${tableType} 1 funcref) (elem (table 0) (offset ${exprType}.const 0) funcref (ref.null func)))`; if (tableType == exprType) { wasmEvalText(moduleText); } else { wasmFailValidateText(moduleText, new RegExp(`expression has type ${exprType} but expected ${tableType}`)); } } // Validate instructions using 32/64-bit pointers in 32/64-bit memories. var validOffsets = {i32: ['', 'offset=0x10000000'], i64: ['', 'offset=0x10000000', 'offset=0x200000000']} // Basic load/store for (let [memType, ptrType] of types ) { for (let offs of validOffsets[memType]) { assertEq(WebAssembly.validate(wasmTextToBinary(` (module (memory ${memType} 1) (func (param $p ${ptrType}) (param $i i32) (param $l i64) (param $f f32) (param $d f64) (drop (i32.add (i32.const 1) (i32.load8_s ${offs} (local.get $p)))) (drop (i32.add (i32.const 1) (i32.load8_u ${offs} (local.get $p)))) (drop (i32.add (i32.const 1) (i32.load16_s ${offs} (local.get $p)))) (drop (i32.add (i32.const 1) (i32.load16_u ${offs} (local.get $p)))) (drop (i32.add (i32.const 1) (i32.load ${offs} (local.get $p)))) (i32.store8 ${offs} (local.get $p) (local.get $i)) (i32.store16 ${offs} (local.get $p) (local.get $i)) (i32.store ${offs} (local.get $p) (local.get $i)) (drop (i64.add (i64.const 1) (i64.load8_s ${offs} (local.get $p)))) (drop (i64.add (i64.const 1) (i64.load8_u ${offs} (local.get $p)))) (drop (i64.add (i64.const 1) (i64.load16_s ${offs} (local.get $p)))) (drop (i64.add (i64.const 1) (i64.load16_u ${offs} (local.get $p)))) (drop (i64.add (i64.const 1) (i64.load32_s ${offs} (local.get $p)))) (drop (i64.add (i64.const 1) (i64.load32_u ${offs} (local.get $p)))) (drop (i64.add (i64.const 1) (i64.load ${offs} (local.get $p)))) (i64.store8 ${offs} (local.get $p) (local.get $l)) (i64.store16 ${offs} (local.get $p) (local.get $l)) (i64.store32 ${offs} (local.get $p) (local.get $l)) (i64.store ${offs} (local.get $p) (local.get $l)) (drop (f32.add (f32.const 1) (f32.load ${offs} (local.get $p)))) (f32.store ${offs} (local.get $p) (local.get $f)) (drop (f64.add (f64.const 1) (f64.load ${offs} (local.get $p)))) (f64.store ${offs} (local.get $p) (local.get $d)) ))`)), memType == ptrType); } } // Basic table get/set for (const [tableType, ptrType] of types) { function mod(ins) { return `(module (table ${tableType} 1 funcref) (func (param $p ${ptrType}) ${ins} ) )`; } if (tableType === ptrType) { wasmValidateText(mod(`(drop (table.get (${ptrType}.const 0)))`)); wasmValidateText(mod(`(table.set (${ptrType}.const 0) (ref.null func))`)); } else { wasmFailValidateText( mod(`(drop (table.get (${ptrType}.const 0)))`), new RegExp(`expression has type ${ptrType} but expected ${tableType}`), ); wasmFailValidateText( mod(`(table.set (${ptrType}.const 0) (ref.null func))`), new RegExp(`expression has type ${ptrType} but expected ${tableType}`), ); } } // Bulk memory operations for (const [memType, ptrType] of types) { function mod(ins) { return `(module (memory ${memType} 1) (data $seg "0123456789abcdef") (func (param $p ${ptrType}) ${ins} ) )`; } if (memType === ptrType) { wasmValidateText(mod(`(drop (${ptrType}.add (${ptrType}.const 1) (memory.size)))`)); wasmValidateText(mod(`(drop (${ptrType}.add (${ptrType}.const 1) (memory.grow (${ptrType}.const 1))))`)); wasmValidateText(mod(`(memory.copy (local.get $p) (${ptrType}.const 0) (${ptrType}.const 628))`)); wasmValidateText(mod(`(memory.fill (local.get $p) (i32.const 37) (${ptrType}.const 1024))`)); wasmValidateText(mod(`(memory.init $seg (local.get $p) (i32.const 3) (i32.const 5))`)); } else { wasmFailValidateText( mod(`(drop (${ptrType}.add (${ptrType}.const 1) (memory.size)))`), new RegExp(`expression has type ${memType} but expected ${ptrType}`), ); wasmFailValidateText( mod(`(drop (memory.grow (${ptrType}.const 1)))`), new RegExp(`expression has type ${ptrType} but expected ${memType}`), ); wasmFailValidateText( mod(`(drop (${ptrType}.add (${ptrType}.const 1) (memory.grow (${memType}.const 1))))`), new RegExp(`expression has type ${memType} but expected ${ptrType}`), ); wasmFailValidateText( mod(`(memory.copy (local.get $p) (${ptrType}.const 0) (${ptrType}.const 628))`), new RegExp(`expression has type ${ptrType} but expected ${memType}`), ); wasmFailValidateText( mod(`(memory.fill (local.get $p) (i32.const 37) (${ptrType}.const 1024))`), new RegExp(`expression has type ${ptrType} but expected ${memType}`), ); wasmFailValidateText( mod(`(memory.init $seg (local.get $p) (i32.const 3) (i32.const 5))`), new RegExp(`expression has type ${ptrType} but expected ${memType}`), ); } } // Bulk table operations for (const [tableType, ptrType] of types) { function mod(ins) { return `(module (table ${tableType} 1 funcref) (elem $seg funcref (ref.null func)) (func (param $p ${ptrType}) ${ins} ) )`; } if (tableType === ptrType) { wasmValidateText(mod(`(drop (${ptrType}.add (${ptrType}.const 1) (table.size)))`)); wasmValidateText(mod(`(drop (${ptrType}.add (${ptrType}.const 1) (table.grow (ref.null func) (${ptrType}.const 1))))`)); wasmValidateText(mod(`(table.copy (local.get $p) (${ptrType}.const 0) (${ptrType}.const 628))`)); wasmValidateText(mod(`(table.fill (local.get $p) (ref.null func) (${ptrType}.const 1024))`)); wasmValidateText(mod(`(table.init $seg (local.get $p) (i32.const 3) (i32.const 5))`)); } else { wasmFailValidateText( mod(`(drop (${ptrType}.add (${ptrType}.const 1) (table.size)))`), new RegExp(`expression has type ${tableType} but expected ${ptrType}`), ); wasmFailValidateText( mod(`(drop (table.grow (ref.null func) (${ptrType}.const 1)))`), new RegExp(`expression has type ${ptrType} but expected ${tableType}`), ); wasmFailValidateText( mod(`(drop (${ptrType}.add (${ptrType}.const 1) (table.grow (ref.null func) (${tableType}.const 1))))`), new RegExp(`expression has type ${tableType} but expected ${ptrType}`), ); wasmFailValidateText( mod(`(table.copy (local.get $p) (${ptrType}.const 0) (${ptrType}.const 628))`), new RegExp(`expression has type ${ptrType} but expected ${tableType}`), ); wasmFailValidateText( mod(`(table.fill (local.get $p) (ref.null func) (${ptrType}.const 1024))`), new RegExp(`expression has type ${ptrType} but expected ${tableType}`), ); wasmFailValidateText( mod(`(table.init $seg (local.get $p) (i32.const 3) (i32.const 5))`), new RegExp(`expression has type ${ptrType} but expected ${tableType}`), ); } } // SIMD if (wasmSimdEnabled()) { for (let [memType, ptrType] of types ) { for (let offs of validOffsets[memType]) { assertEq(WebAssembly.validate(wasmTextToBinary(` (module (memory ${memType} 1) (func (param $p ${ptrType}) (param $v v128) (param $w v128) (drop (i8x16.add (local.get $w) (v128.load ${offs} (local.get $p)))) (v128.store ${offs} (local.get $p) (local.get $v)) (drop (i8x16.add (local.get $w) (v128.load8_splat ${offs} (local.get $p)))) (drop (i8x16.add (local.get $w) (v128.load16_splat ${offs} (local.get $p)))) (drop (i8x16.add (local.get $w) (v128.load32_splat ${offs} (local.get $p)))) (drop (i8x16.add (local.get $w) (v128.load64_splat ${offs} (local.get $p)))) (drop (i8x16.add (local.get $w) (v128.load32_zero ${offs} (local.get $p)))) (drop (i8x16.add (local.get $w) (v128.load64_zero ${offs} (local.get $p)))) (drop (i8x16.add (local.get $w) (v128.load8_lane ${offs} 1 (local.get $p) (local.get $v)))) (drop (i8x16.add (local.get $w) (v128.load16_lane ${offs} 1 (local.get $p) (local.get $v)))) (drop (i8x16.add (local.get $w) (v128.load32_lane ${offs} 1 (local.get $p) (local.get $v)))) (drop (i8x16.add (local.get $w) (v128.load64_lane ${offs} 1 (local.get $p) (local.get $v)))) (v128.store8_lane ${offs} 1 (local.get $p) (local.get $v)) (v128.store16_lane ${offs} 1 (local.get $p) (local.get $v)) (v128.store32_lane ${offs} 1 (local.get $p) (local.get $v)) (v128.store64_lane ${offs} 1 (local.get $p) (local.get $v)) (drop (i8x16.add (local.get $w) (v128.load8x8_s ${offs} (local.get $p)))) (drop (i8x16.add (local.get $w) (v128.load8x8_u ${offs} (local.get $p)))) (drop (i8x16.add (local.get $w) (v128.load16x4_s ${offs} (local.get $p)))) (drop (i8x16.add (local.get $w) (v128.load16x4_u ${offs} (local.get $p)))) (drop (i8x16.add (local.get $w) (v128.load32x2_s ${offs} (local.get $p)))) (drop (i8x16.add (local.get $w) (v128.load32x2_u ${offs} (local.get $p)))) ))`)), memType == ptrType); } } } // Threads if (wasmThreadsEnabled()) { for (let [memType, ptrType] of types ) { for (let offs of validOffsets[memType]) { assertEq(WebAssembly.validate(wasmTextToBinary(` (module (memory ${memType} 1 100 shared) (func (param $p ${ptrType}) (param $i i32) (param $l i64) (drop (i32.add (i32.const 1) (memory.atomic.wait32 ${offs} (local.get $p) (i32.const 0) (i64.const 37)))) (drop (i32.add (i32.const 1) (memory.atomic.wait64 ${offs} (local.get $p) (i64.const 0) (i64.const 37)))) (drop (i32.add (i32.const 1) (memory.atomic.notify ${offs} (local.get $p) (i32.const 1)))) ))`)), memType == ptrType); for (let [ty,size,sx] of [['i32','','','',],['i32','8','_u'],['i32','16','_u'], ['i64','',''],['i64','8','_u'],['i64','16','_u'],['i64','32','_u']]) { assertEq(WebAssembly.validate(wasmTextToBinary(` (module (memory ${memType} 1 100 shared) (func (param $p ${ptrType}) (param $vi32 i32) (param $vi64 i64) (drop (${ty}.add (${ty}.const 1) (${ty}.atomic.load${size}${sx} ${offs} (local.get $p)))) (${ty}.atomic.store${size} ${offs} (local.get $p) (local.get $v${ty})) (drop (${ty}.add (${ty}.const 1) (${ty}.atomic.rmw${size}.add${sx} ${offs} (local.get $p) (local.get $v${ty})))) (drop (${ty}.add (${ty}.const 1) (${ty}.atomic.rmw${size}.sub${sx} ${offs} (local.get $p) (local.get $v${ty})))) (drop (${ty}.add (${ty}.const 1) (${ty}.atomic.rmw${size}.and${sx} ${offs} (local.get $p) (local.get $v${ty})))) (drop (${ty}.add (${ty}.const 1) (${ty}.atomic.rmw${size}.or${sx} ${offs} (local.get $p) (local.get $v${ty})))) (drop (${ty}.add (${ty}.const 1) (${ty}.atomic.rmw${size}.xor${sx} ${offs} (local.get $p) (local.get $v${ty})))) (drop (${ty}.add (${ty}.const 1) (${ty}.atomic.rmw${size}.xchg${sx} ${offs} (local.get $p) (local.get $v${ty})))) (drop (${ty}.add (${ty}.const 1) (${ty}.atomic.rmw${size}.cmpxchg${sx} ${offs} (local.get $p) (local.get $v${ty}) (${ty}.const 37)))) ))`)), memType == ptrType); } } } } // Cursorily check that invalid offsets are rejected. wasmFailValidateText(` (module (memory i32 1) (func (param $p i32) (drop (i32.load offset=0x100000000 (local.get $p)))))`, /offset too large for memory type/); /////////////////////////////////////////////////////////////////////////////// // // EXECUTION // Smoketest: Can we actually allocate a memory larger than 4GB? if (getBuildConfiguration("pointer-byte-size") == 8) { try { new WebAssembly.Memory({address:"i64", initial:BigInt(65536 * 1.5), maximum:BigInt(65536 * 2)}); } catch (e) { // OOM is OK. if (!(e instanceof WebAssembly.RuntimeError) || !String(e).match(/too many memory pages/)) { throw e; } } } // JS-API if (WebAssembly.Function) { const m64 = new WebAssembly.Memory({ address: "i64", initial:1n }); assertEq(m64.type().address, "i64"); const m32 = new WebAssembly.Memory({ initial:1 }); assertEq(m32.type().address, "i32"); const t64 = new WebAssembly.Table({ address: "i64", element: "funcref", initial: 1n }); assertEq(t64.type().address, "i64"); const t32 = new WebAssembly.Table({ initial: 1, element: "funcref" }); assertEq(t32.type().address, "i32"); const ins = wasmEvalText(`(module (memory (export "mem") i64 1 0x100000000) (table (export "table") i64 1 0x1000 funcref) )`); assertEq(ins.exports.mem.type().address, "i64"); assertEq(ins.exports.mem.type().minimum, 1n); assertEq(ins.exports.mem.type().maximum, 0x100000000n); assertEq(ins.exports.table.type().address, "i64"); assertEq(ins.exports.table.type().minimum, 1n); assertEq(ins.exports.table.type().maximum, 0x1000n); } // Instructions const SMALL = 64; // < offsetguard everywhere const BIG = 131072; // > offsetguard on 32-bit const HUGE = 2147483656; // > offsetguard on 64-bit const VAST = 0x112001300; // > 4GB function makeTest(LOC, INITIAL, MAXIMUM, SHARED) { const v128Prefix = ` (func $stash (param v128) (v128.store (i64.const 0) (local.get 0))) (func $unstash (result v128) (v128.load (i64.const 0))) `; const readV128Code = ` (func (export "readv128@0") (param $p i64) (call $stash (v128.load (local.get $p)))) (func (export "readv128@small") (param $p i64) (call $stash (v128.load offset=${SMALL} (local.get $p)))) (func (export "readv128@big") (param $p i64) (call $stash (v128.load offset=${BIG} (local.get $p)))) (func (export "readv128@huge") (param $p i64) (call $stash (v128.load offset=${HUGE} (local.get $p)))) (func (export "readv128/const@0") (call $stash (v128.load (i64.const ${LOC})))) (func (export "readv128/const@small") (call $stash (v128.load offset=${SMALL} (i64.const ${LOC})))) (func (export "readv128/const@big") (call $stash (v128.load offset=${BIG} (i64.const ${LOC})))) (func (export "v128.load_splat@small") (param $p i64) (call $stash (v128.load32_splat offset=${SMALL} (local.get $p)))) (func (export "v128.load_zero@small") (param $p i64) (call $stash (v128.load32_zero offset=${SMALL} (local.get $p)))) (func (export "v128.load_extend@small") (param $p i64) (call $stash (v128.load32x2_u offset=${SMALL} (local.get $p)))) (func (export "v128.load_lane@small") (param $p i64) (call $stash (v128.load32_lane offset=${SMALL} 2 (local.get $p) (call $unstash)))) `; const writeV128Code = ` (func (export "writev128@0") (param $p i64) (v128.store (local.get $p) (call $unstash))) (func (export "writev128@small") (param $p i64) (v128.store offset=${SMALL} (local.get $p) (call $unstash))) (func (export "writev128@big") (param $p i64) (v128.store offset=${BIG} (local.get $p) (call $unstash))) (func (export "writev128@huge") (param $p i64) (v128.store offset=${HUGE} (local.get $p) (call $unstash))) (func (export "writev128/const@0") (v128.store (i64.const ${LOC}) (call $unstash))) (func (export "writev128/const@small") (v128.store offset=${SMALL} (i64.const ${LOC}) (call $unstash))) (func (export "writev128/const@big") (v128.store offset=${BIG} (i64.const ${LOC}) (call $unstash))) (func (export "v128.store_lane@small") (param $p i64) (v128.store32_lane offset=${SMALL} 2 (local.get $p) (call $unstash))) `; const ins = wasmEvalText(` (module (memory (export "mem") i64 ${INITIAL} ${MAXIMUM} ${SHARED}) ;; About the test cases: there are various optimizations in the engine ;; for different shapes of a pointer+offset. Constant pointers are ;; resolved early; large offsets are folded early using explicit code ;; with an overflow check (but "large" depends on 32-bit vs 64-bit); ;; wait/notify fold offsets early regardless; zero offsets lead to ;; tighter code with variable pointers; and don't get me started on ;; alignment checks. These test cases are not exhaustive but aim ;; to test at least some things. ;; TODO: more sizes for all operations, though this is not critical ;; TODO: sign extending loads, again not critical ${wasmSimdEnabled() ? v128Prefix : ""} ;; Read i32 (func (export "readi32@0") (param $p i64) (result i32) (i32.load (local.get $p))) (func (export "readi32@small") (param $p i64) (result i32) (i32.load offset=${SMALL} (local.get $p))) (func (export "readi32@big") (param $p i64) (result i32) (i32.load offset=${BIG} (local.get $p))) (func (export "readi32@huge") (param $p i64) (result i32) (i32.load offset=${HUGE} (local.get $p))) (func (export "readi32@vast") (param $p i64) (result i32) (i32.load offset=${VAST} (local.get $p))) (func (export "readi32/const@0") (result i32) (i32.load (i64.const ${LOC}))) (func (export "readi32/const@small") (result i32) (i32.load offset=${SMALL} (i64.const ${LOC}))) (func (export "readi32/const@big") (result i32) (i32.load offset=${BIG} (i64.const ${LOC}))) (func (export "readi32/const@vast") (result i32) (i32.load offset=${VAST} (i64.const ${LOC}))) ;; Read i64 (func (export "readi64@0") (param $p i64) (result i64) (i64.load (local.get $p))) (func (export "readi64@small") (param $p i64) (result i64) (i64.load offset=${SMALL} (local.get $p))) (func (export "readi64@big") (param $p i64) (result i64) (i64.load offset=${BIG} (local.get $p))) (func (export "readi64@huge") (param $p i64) (result i64) (i64.load offset=${HUGE} (local.get $p))) (func (export "readi64@vast") (param $p i64) (result i64) (i64.load offset=${VAST} (local.get $p))) (func (export "readi64/const@0") (result i64) (i64.load (i64.const ${LOC}))) (func (export "readi64/const@small") (result i64) (i64.load offset=${SMALL} (i64.const ${LOC}))) (func (export "readi64/const@big") (result i64) (i64.load offset=${BIG} (i64.const ${LOC}))) (func (export "readi64/const@vast") (result i64) (i64.load offset=${VAST} (i64.const ${LOC}))) ;; Read v128 ${wasmSimdEnabled() ? readV128Code : ""} ;; write i32 (func (export "writei32@0") (param $p i64) (param $v i32) (i32.store (local.get $p) (local.get $v))) (func (export "writei32@small") (param $p i64) (param $v i32) (i32.store offset=${SMALL} (local.get $p) (local.get $v))) (func (export "writei32@big") (param $p i64) (param $v i32) (i32.store offset=${BIG} (local.get $p) (local.get $v))) (func (export "writei32@huge") (param $p i64) (param $v i32) (i32.store offset=${HUGE} (local.get $p) (local.get $v))) (func (export "writei32@vast") (param $p i64) (param $v i32) (i32.store offset=${VAST} (local.get $p) (local.get $v))) (func (export "writei32/const@0") (param $v i32) (i32.store (i64.const ${LOC}) (local.get $v))) (func (export "writei32/const@small") (param $v i32) (i32.store offset=${SMALL} (i64.const ${LOC}) (local.get $v))) (func (export "writei32/const@big") (param $v i32) (i32.store offset=${BIG} (i64.const ${LOC}) (local.get $v))) (func (export "writei32/const@vast") (param $v i32) (i32.store offset=${VAST} (i64.const ${LOC}) (local.get $v))) ;; write i64 (func (export "writei64@0") (param $p i64) (param $v i64) (i64.store (local.get $p) (local.get $v))) (func (export "writei64@small") (param $p i64) (param $v i64) (i64.store offset=${SMALL} (local.get $p) (local.get $v))) (func (export "writei64@big") (param $p i64) (param $v i64) (i64.store offset=${BIG} (local.get $p) (local.get $v))) (func (export "writei64@huge") (param $p i64) (param $v i64) (i64.store offset=${HUGE} (local.get $p) (local.get $v))) (func (export "writei64@vast") (param $p i64) (param $v i64) (i64.store offset=${VAST} (local.get $p) (local.get $v))) (func (export "writei64/const@0") (param $v i64) (i64.store (i64.const ${LOC}) (local.get $v))) (func (export "writei64/const@small") (param $v i64) (i64.store offset=${SMALL} (i64.const ${LOC}) (local.get $v))) (func (export "writei64/const@big") (param $v i64) (i64.store offset=${BIG} (i64.const ${LOC}) (local.get $v))) (func (export "writei64/const@vast") (param $v i64) (i64.store offset=${VAST} (i64.const ${LOC}) (local.get $v))) ;; Read v128 ${wasmSimdEnabled() ? writeV128Code : ""} ;; Atomic read i32 (func (export "areadi32@0") (param $p i64) (result i32) (i32.atomic.load (local.get $p))) (func (export "areadi32@small") (param $p i64) (result i32) (i32.atomic.load offset=${SMALL} (local.get $p))) (func (export "areadi32@big") (param $p i64) (result i32) (i32.atomic.load offset=${BIG} (local.get $p))) (func (export "areadi32@huge") (param $p i64) (result i32) (i32.atomic.load offset=${HUGE} (local.get $p))) (func (export "areadi32@vast") (param $p i64) (result i32) (i32.atomic.load offset=${VAST} (local.get $p))) (func (export "areadi32/const@0") (result i32) (i32.atomic.load (i64.const ${LOC}))) (func (export "areadi32/const@small") (result i32) (i32.atomic.load offset=${SMALL} (i64.const ${LOC}))) (func (export "areadi32/const@big") (result i32) (i32.atomic.load offset=${BIG} (i64.const ${LOC}))) (func (export "areadi32/const@vast") (result i32) (i32.atomic.load offset=${VAST} (i64.const ${LOC}))) ;; Atomic read i64 (func (export "areadi64@0") (param $p i64) (result i64) (i64.atomic.load (local.get $p))) (func (export "areadi64@small") (param $p i64) (result i64) (i64.atomic.load offset=${SMALL} (local.get $p))) (func (export "areadi64@big") (param $p i64) (result i64) (i64.atomic.load offset=${BIG} (local.get $p))) (func (export "areadi64@huge") (param $p i64) (result i64) (i64.atomic.load offset=${HUGE} (local.get $p))) (func (export "areadi64@vast") (param $p i64) (result i64) (i64.atomic.load offset=${VAST} (local.get $p))) (func (export "areadi64/const@0") (result i64) (i64.atomic.load (i64.const ${LOC}))) (func (export "areadi64/const@small") (result i64) (i64.atomic.load offset=${SMALL} (i64.const ${LOC}))) (func (export "areadi64/const@big") (result i64) (i64.atomic.load offset=${BIG} (i64.const ${LOC}))) (func (export "areadi64/const@vast") (result i64) (i64.atomic.load offset=${VAST} (i64.const ${LOC}))) ;; Atomic write i32 (func (export "awritei32@0") (param $p i64) (param $v i32) (i32.atomic.store (local.get $p) (local.get $v))) (func (export "awritei32@small") (param $p i64) (param $v i32) (i32.atomic.store offset=${SMALL} (local.get $p) (local.get $v))) (func (export "awritei32@big") (param $p i64) (param $v i32) (i32.atomic.store offset=${BIG} (local.get $p) (local.get $v))) (func (export "awritei32@huge") (param $p i64) (param $v i32) (i32.atomic.store offset=${HUGE} (local.get $p) (local.get $v))) (func (export "awritei32@vast") (param $p i64) (param $v i32) (i32.atomic.store offset=${VAST} (local.get $p) (local.get $v))) (func (export "awritei32/const@0") (param $v i32) (i32.atomic.store (i64.const ${LOC}) (local.get $v))) (func (export "awritei32/const@small") (param $v i32) (i32.atomic.store offset=${SMALL} (i64.const ${LOC}) (local.get $v))) (func (export "awritei32/const@big") (param $v i32) (i32.atomic.store offset=${BIG} (i64.const ${LOC}) (local.get $v))) (func (export "awritei32/const@vast") (param $v i32) (i32.atomic.store offset=${VAST} (i64.const ${LOC}) (local.get $v))) ;; Atomic write i64 (func (export "awritei64@0") (param $p i64) (param $v i64) (i64.atomic.store (local.get $p) (local.get $v))) (func (export "awritei64@small") (param $p i64) (param $v i64) (i64.atomic.store offset=${SMALL} (local.get $p) (local.get $v))) (func (export "awritei64@big") (param $p i64) (param $v i64) (i64.atomic.store offset=${BIG} (local.get $p) (local.get $v))) (func (export "awritei64@huge") (param $p i64) (param $v i64) (i64.atomic.store offset=${HUGE} (local.get $p) (local.get $v))) (func (export "awritei64@vast") (param $p i64) (param $v i64) (i64.atomic.store offset=${VAST} (local.get $p) (local.get $v))) (func (export "awritei64/const@0") (param $v i64) (i64.atomic.store (i64.const ${LOC}) (local.get $v))) (func (export "awritei64/const@small") (param $v i64) (i64.atomic.store offset=${SMALL} (i64.const ${LOC}) (local.get $v))) (func (export "awritei64/const@big") (param $v i64) (i64.atomic.store offset=${BIG} (i64.const ${LOC}) (local.get $v))) (func (export "awritei64/const@vast") (param $v i64) (i64.atomic.store offset=${VAST} (i64.const ${LOC}) (local.get $v))) ;; xchg i32 (func (export "xchgi32@0") (param $p i64) (param $v i32) (result i32) (i32.atomic.rmw.xchg (local.get $p) (local.get $v))) (func (export "xchgi32@small") (param $p i64) (param $v i32) (result i32) (i32.atomic.rmw.xchg offset=${SMALL} (local.get $p) (local.get $v))) (func (export "xchgi32@big") (param $p i64) (param $v i32) (result i32) (i32.atomic.rmw.xchg offset=${BIG} (local.get $p) (local.get $v))) (func (export "xchgi32@huge") (param $p i64) (param $v i32) (result i32) (i32.atomic.rmw.xchg offset=${HUGE} (local.get $p) (local.get $v))) (func (export "xchgi32/const@0") (param $v i32) (result i32) (i32.atomic.rmw.xchg (i64.const ${LOC}) (local.get $v))) (func (export "xchgi32/const@small") (param $v i32) (result i32) (i32.atomic.rmw.xchg offset=${SMALL} (i64.const ${LOC}) (local.get $v))) (func (export "xchgi32/const@big") (param $v i32) (result i32) (i32.atomic.rmw.xchg offset=${BIG} (i64.const ${LOC}) (local.get $v))) ;; xchg i64 (func (export "xchgi64@0") (param $p i64) (param $v i64) (result i64) (i64.atomic.rmw.xchg (local.get $p) (local.get $v))) (func (export "xchgi64@small") (param $p i64) (param $v i64) (result i64) (i64.atomic.rmw.xchg offset=${SMALL} (local.get $p) (local.get $v))) (func (export "xchgi64@big") (param $p i64) (param $v i64) (result i64) (i64.atomic.rmw.xchg offset=${BIG} (local.get $p) (local.get $v))) (func (export "xchgi64@huge") (param $p i64) (param $v i64) (result i64) (i64.atomic.rmw.xchg offset=${HUGE} (local.get $p) (local.get $v))) (func (export "xchgi64/const@0") (param $v i64) (result i64) (i64.atomic.rmw.xchg (i64.const ${LOC}) (local.get $v))) (func (export "xchgi64/const@small") (param $v i64) (result i64) (i64.atomic.rmw.xchg offset=${SMALL} (i64.const ${LOC}) (local.get $v))) (func (export "xchgi64/const@big") (param $v i64) (result i64) (i64.atomic.rmw.xchg offset=${BIG} (i64.const ${LOC}) (local.get $v))) ;; add i32 (func (export "addi32@0") (param $p i64) (param $v i32) (result i32) (i32.atomic.rmw.add (local.get $p) (local.get $v))) (func (export "addi32@small") (param $p i64) (param $v i32) (result i32) (i32.atomic.rmw.add offset=${SMALL} (local.get $p) (local.get $v))) (func (export "addi32@big") (param $p i64) (param $v i32) (result i32) (i32.atomic.rmw.add offset=${BIG} (local.get $p) (local.get $v))) (func (export "addi32@huge") (param $p i64) (param $v i32) (result i32) (i32.atomic.rmw.add offset=${HUGE} (local.get $p) (local.get $v))) (func (export "addi32/const@0") (param $v i32) (result i32) (i32.atomic.rmw.add (i64.const ${LOC}) (local.get $v))) (func (export "addi32/const@small") (param $v i32) (result i32) (i32.atomic.rmw.add offset=${SMALL} (i64.const ${LOC}) (local.get $v))) (func (export "addi32/const@big") (param $v i32) (result i32) (i32.atomic.rmw.add offset=${BIG} (i64.const ${LOC}) (local.get $v))) ;; add i64 (func (export "addi64@0") (param $p i64) (param $v i64) (result i64) (i64.atomic.rmw.add (local.get $p) (local.get $v))) (func (export "addi64@small") (param $p i64) (param $v i64) (result i64) (i64.atomic.rmw.add offset=${SMALL} (local.get $p) (local.get $v))) (func (export "addi64@big") (param $p i64) (param $v i64) (result i64) (i64.atomic.rmw.add offset=${BIG} (local.get $p) (local.get $v))) (func (export "addi64@huge") (param $p i64) (param $v i64) (result i64) (i64.atomic.rmw.add offset=${HUGE} (local.get $p) (local.get $v))) (func (export "addi64/const@0") (param $v i64) (result i64) (i64.atomic.rmw.add (i64.const ${LOC}) (local.get $v))) (func (export "addi64/const@small") (param $v i64) (result i64) (i64.atomic.rmw.add offset=${SMALL} (i64.const ${LOC}) (local.get $v))) (func (export "addi64/const@big") (param $v i64) (result i64) (i64.atomic.rmw.add offset=${BIG} (i64.const ${LOC}) (local.get $v))) ;; sub i32 (func (export "subi32@0") (param $p i64) (param $v i32) (result i32) (i32.atomic.rmw.sub (local.get $p) (local.get $v))) (func (export "subi32@small") (param $p i64) (param $v i32) (result i32) (i32.atomic.rmw.sub offset=${SMALL} (local.get $p) (local.get $v))) (func (export "subi32@big") (param $p i64) (param $v i32) (result i32) (i32.atomic.rmw.sub offset=${BIG} (local.get $p) (local.get $v))) (func (export "subi32@huge") (param $p i64) (param $v i32) (result i32) (i32.atomic.rmw.sub offset=${HUGE} (local.get $p) (local.get $v))) (func (export "subi32/const@0") (param $v i32) (result i32) (i32.atomic.rmw.sub (i64.const ${LOC}) (local.get $v))) (func (export "subi32/const@small") (param $v i32) (result i32) (i32.atomic.rmw.sub offset=${SMALL} (i64.const ${LOC}) (local.get $v))) (func (export "subi32/const@big") (param $v i32) (result i32) (i32.atomic.rmw.sub offset=${BIG} (i64.const ${LOC}) (local.get $v))) ;; sub i64 (func (export "subi64@0") (param $p i64) (param $v i64) (result i64) (i64.atomic.rmw.sub (local.get $p) (local.get $v))) (func (export "subi64@small") (param $p i64) (param $v i64) (result i64) (i64.atomic.rmw.sub offset=${SMALL} (local.get $p) (local.get $v))) (func (export "subi64@big") (param $p i64) (param $v i64) (result i64) (i64.atomic.rmw.sub offset=${BIG} (local.get $p) (local.get $v))) (func (export "subi64@huge") (param $p i64) (param $v i64) (result i64) (i64.atomic.rmw.sub offset=${HUGE} (local.get $p) (local.get $v))) (func (export "subi64/const@0") (param $v i64) (result i64) (i64.atomic.rmw.sub (i64.const ${LOC}) (local.get $v))) (func (export "subi64/const@small") (param $v i64) (result i64) (i64.atomic.rmw.sub offset=${SMALL} (i64.const ${LOC}) (local.get $v))) (func (export "subi64/const@big") (param $v i64) (result i64) (i64.atomic.rmw.sub offset=${BIG} (i64.const ${LOC}) (local.get $v))) ;; and i32 (func (export "andi32@0") (param $p i64) (param $v i32) (result i32) (i32.atomic.rmw.and (local.get $p) (local.get $v))) (func (export "andi32@small") (param $p i64) (param $v i32) (result i32) (i32.atomic.rmw.and offset=${SMALL} (local.get $p) (local.get $v))) (func (export "andi32@big") (param $p i64) (param $v i32) (result i32) (i32.atomic.rmw.and offset=${BIG} (local.get $p) (local.get $v))) (func (export "andi32@huge") (param $p i64) (param $v i32) (result i32) (i32.atomic.rmw.and offset=${HUGE} (local.get $p) (local.get $v))) (func (export "andi32/const@0") (param $v i32) (result i32) (i32.atomic.rmw.and (i64.const ${LOC}) (local.get $v))) (func (export "andi32/const@small") (param $v i32) (result i32) (i32.atomic.rmw.and offset=${SMALL} (i64.const ${LOC}) (local.get $v))) (func (export "andi32/const@big") (param $v i32) (result i32) (i32.atomic.rmw.and offset=${BIG} (i64.const ${LOC}) (local.get $v))) ;; and i64 (func (export "andi64@0") (param $p i64) (param $v i64) (result i64) (i64.atomic.rmw.and (local.get $p) (local.get $v))) (func (export "andi64@small") (param $p i64) (param $v i64) (result i64) (i64.atomic.rmw.and offset=${SMALL} (local.get $p) (local.get $v))) (func (export "andi64@big") (param $p i64) (param $v i64) (result i64) (i64.atomic.rmw.and offset=${BIG} (local.get $p) (local.get $v))) (func (export "andi64@huge") (param $p i64) (param $v i64) (result i64) (i64.atomic.rmw.and offset=${HUGE} (local.get $p) (local.get $v))) (func (export "andi64/const@0") (param $v i64) (result i64) (i64.atomic.rmw.and (i64.const ${LOC}) (local.get $v))) (func (export "andi64/const@small") (param $v i64) (result i64) (i64.atomic.rmw.and offset=${SMALL} (i64.const ${LOC}) (local.get $v))) (func (export "andi64/const@big") (param $v i64) (result i64) (i64.atomic.rmw.and offset=${BIG} (i64.const ${LOC}) (local.get $v))) ;; or i32 (func (export "ori32@0") (param $p i64) (param $v i32) (result i32) (i32.atomic.rmw.or (local.get $p) (local.get $v))) (func (export "ori32@small") (param $p i64) (param $v i32) (result i32) (i32.atomic.rmw.or offset=${SMALL} (local.get $p) (local.get $v))) (func (export "ori32@big") (param $p i64) (param $v i32) (result i32) (i32.atomic.rmw.or offset=${BIG} (local.get $p) (local.get $v))) (func (export "ori32@huge") (param $p i64) (param $v i32) (result i32) (i32.atomic.rmw.or offset=${HUGE} (local.get $p) (local.get $v))) (func (export "ori32/const@0") (param $v i32) (result i32) (i32.atomic.rmw.or (i64.const ${LOC}) (local.get $v))) (func (export "ori32/const@small") (param $v i32) (result i32) (i32.atomic.rmw.or offset=${SMALL} (i64.const ${LOC}) (local.get $v))) (func (export "ori32/const@big") (param $v i32) (result i32) (i32.atomic.rmw.or offset=${BIG} (i64.const ${LOC}) (local.get $v))) ;; or i64 (func (export "ori64@0") (param $p i64) (param $v i64) (result i64) (i64.atomic.rmw.or (local.get $p) (local.get $v))) (func (export "ori64@small") (param $p i64) (param $v i64) (result i64) (i64.atomic.rmw.or offset=${SMALL} (local.get $p) (local.get $v))) (func (export "ori64@big") (param $p i64) (param $v i64) (result i64) (i64.atomic.rmw.or offset=${BIG} (local.get $p) (local.get $v))) (func (export "ori64@huge") (param $p i64) (param $v i64) (result i64) (i64.atomic.rmw.or offset=${HUGE} (local.get $p) (local.get $v))) (func (export "ori64/const@0") (param $v i64) (result i64) (i64.atomic.rmw.or (i64.const ${LOC}) (local.get $v))) (func (export "ori64/const@small") (param $v i64) (result i64) (i64.atomic.rmw.or offset=${SMALL} (i64.const ${LOC}) (local.get $v))) (func (export "ori64/const@big") (param $v i64) (result i64) (i64.atomic.rmw.or offset=${BIG} (i64.const ${LOC}) (local.get $v))) ;; xor i32 (func (export "xori32@0") (param $p i64) (param $v i32) (result i32) (i32.atomic.rmw.xor (local.get $p) (local.get $v))) (func (export "xori32@small") (param $p i64) (param $v i32) (result i32) (i32.atomic.rmw.xor offset=${SMALL} (local.get $p) (local.get $v))) (func (export "xori32@big") (param $p i64) (param $v i32) (result i32) (i32.atomic.rmw.xor offset=${BIG} (local.get $p) (local.get $v))) (func (export "xori32@huge") (param $p i64) (param $v i32) (result i32) (i32.atomic.rmw.xor offset=${HUGE} (local.get $p) (local.get $v))) (func (export "xori32/const@0") (param $v i32) (result i32) (i32.atomic.rmw.xor (i64.const ${LOC}) (local.get $v))) (func (export "xori32/const@small") (param $v i32) (result i32) (i32.atomic.rmw.xor offset=${SMALL} (i64.const ${LOC}) (local.get $v))) (func (export "xori32/const@big") (param $v i32) (result i32) (i32.atomic.rmw.xor offset=${BIG} (i64.const ${LOC}) (local.get $v))) ;; xor i64 (func (export "xori64@0") (param $p i64) (param $v i64) (result i64) (i64.atomic.rmw.xor (local.get $p) (local.get $v))) (func (export "xori64@small") (param $p i64) (param $v i64) (result i64) (i64.atomic.rmw.xor offset=${SMALL} (local.get $p) (local.get $v))) (func (export "xori64@big") (param $p i64) (param $v i64) (result i64) (i64.atomic.rmw.xor offset=${BIG} (local.get $p) (local.get $v))) (func (export "xori64@huge") (param $p i64) (param $v i64) (result i64) (i64.atomic.rmw.xor offset=${HUGE} (local.get $p) (local.get $v))) (func (export "xori64/const@0") (param $v i64) (result i64) (i64.atomic.rmw.xor (i64.const ${LOC}) (local.get $v))) (func (export "xori64/const@small") (param $v i64) (result i64) (i64.atomic.rmw.xor offset=${SMALL} (i64.const ${LOC}) (local.get $v))) (func (export "xori64/const@big") (param $v i64) (result i64) (i64.atomic.rmw.xor offset=${BIG} (i64.const ${LOC}) (local.get $v))) ;; cmpxchg i32 (func (export "cmpxchgi32@0") (param $p i64) (param $expect i32) (param $new i32) (result i32) (i32.atomic.rmw.cmpxchg (local.get $p) (local.get $expect) (local.get $new))) (func (export "cmpxchgi32@small") (param $p i64) (param $expect i32) (param $new i32) (result i32) (i32.atomic.rmw.cmpxchg offset=${SMALL} (local.get $p) (local.get $expect) (local.get $new))) (func (export "cmpxchgi32@big") (param $p i64) (param $expect i32) (param $new i32) (result i32) (i32.atomic.rmw.cmpxchg offset=${BIG} (local.get $p) (local.get $expect) (local.get $new))) (func (export "cmpxchgi32@huge") (param $p i64) (param $expect i32) (param $new i32) (result i32) (i32.atomic.rmw.cmpxchg offset=${HUGE} (local.get $p) (local.get $expect) (local.get $new))) (func (export "cmpxchgi32/const@0") (param $expect i32) (param $new i32) (result i32) (i32.atomic.rmw.cmpxchg (i64.const ${LOC}) (local.get $expect) (local.get $new))) (func (export "cmpxchgi32/const@small") (param $expect i32) (param $new i32) (result i32) (i32.atomic.rmw.cmpxchg offset=${SMALL} (i64.const ${LOC}) (local.get $expect) (local.get $new))) (func (export "cmpxchgi32/const@big") (param $expect i32) (param $new i32) (result i32) (i32.atomic.rmw.cmpxchg offset=${BIG} (i64.const ${LOC}) (local.get $expect) (local.get $new))) ;; cmpxchg i64 (func (export "cmpxchgi64@0") (param $p i64) (param $expect i64) (param $new i64) (result i64) (i64.atomic.rmw.cmpxchg (local.get $p) (local.get $expect) (local.get $new))) (func (export "cmpxchgi64@small") (param $p i64) (param $expect i64) (param $new i64) (result i64) (i64.atomic.rmw.cmpxchg offset=${SMALL} (local.get $p) (local.get $expect) (local.get $new))) (func (export "cmpxchgi64@big") (param $p i64) (param $expect i64) (param $new i64) (result i64) (i64.atomic.rmw.cmpxchg offset=${BIG} (local.get $p) (local.get $expect) (local.get $new))) (func (export "cmpxchgi64@huge") (param $p i64) (param $expect i64) (param $new i64) (result i64) (i64.atomic.rmw.cmpxchg offset=${HUGE} (local.get $p) (local.get $expect) (local.get $new))) (func (export "cmpxchgi64/const@0") (param $expect i64) (param $new i64) (result i64) (i64.atomic.rmw.cmpxchg (i64.const ${LOC}) (local.get $expect) (local.get $new))) (func (export "cmpxchgi64/const@small") (param $expect i64) (param $new i64) (result i64) (i64.atomic.rmw.cmpxchg offset=${SMALL} (i64.const ${LOC}) (local.get $expect) (local.get $new))) (func (export "cmpxchgi64/const@big") (param $expect i64) (param $new i64) (result i64) (i64.atomic.rmw.cmpxchg offset=${BIG} (i64.const ${LOC}) (local.get $expect) (local.get $new))) ;; wait (func (export "waiti32@small") (param $p i64) (result i32) (memory.atomic.wait32 offset=${SMALL} (local.get $p) (i32.const 1) (i64.const 0))) (func (export "waiti32@huge") (param $p i64) (result i32) (memory.atomic.wait32 offset=${HUGE} (local.get $p) (i32.const 1) (i64.const 0))) (func (export "waiti64@small") (param $p i64) (result i32) (memory.atomic.wait64 offset=${SMALL} (local.get $p) (i64.const 1) (i64.const 0))) (func (export "waiti64@huge") (param $p i64) (result i32) (memory.atomic.wait64 offset=${HUGE} (local.get $p) (i64.const 1) (i64.const 0))) ;; wake (func (export "wake@0") (param $p i64) (result i32) (memory.atomic.notify (local.get $p) (i32.const 1))) (func (export "wake@small") (param $p i64) (result i32) (memory.atomic.notify offset=${SMALL} (local.get $p) (i32.const 1))) (func (export "wake@big") (param $p i64) (result i32) (memory.atomic.notify offset=${BIG} (local.get $p) (i32.const 1))) (func (export "wake@huge") (param $p i64) (result i32) (memory.atomic.notify offset=${HUGE} (local.get $p) (i32.const 1))) ) `); return ins; } function i32Random() { // Limit this to small positive numbers to keep life simple. for (;;) { let r = (Math.random() * 0x3FFF_FFFF) | 0; if (r != 0) return r; } } function i64Random() { return (BigInt(i32Random()) << 32n) | BigInt(i32Random()); } function Random(sz) { if (sz == 4) return i32Random(); return i64Random(); } function Random2(sz) { return [Random(sz), Random(sz)]; } function Random4(sz) { return [Random(sz), Random(sz), Random(sz), Random(sz)]; } function Zero(sz) { if (sz == 4) return 0; return 0n; } function testRead(ins, mem, LOC, prefix) { let r = 0; let SZ = mem.BYTES_PER_ELEMENT; let len = mem.length * SZ; let NM = prefix + "readi" + (SZ * 8); // Read in-bounds r = Random(SZ); mem[LOC / SZ] = r; assertEq(ins.exports[NM + "@0"](BigInt(LOC)), r); assertEq(ins.exports[NM + "/const@0"](), r); mem[(len / SZ) - 1] = Zero(SZ); assertEq(ins.exports[NM + "@0"](BigInt(len - SZ)), Zero(SZ)); // Just barely in-bounds r = Random(SZ); mem[(LOC + SMALL) / SZ] = r; assertEq(ins.exports[NM + "@small"](BigInt(LOC)), r); assertEq(ins.exports[NM + "/const@small"](), r); if (len >= LOC + BIG + SZ) { r = Random(SZ); mem[(LOC + BIG) / SZ] = r; assertEq(ins.exports[NM + "@big"](BigInt(LOC)), r); assertEq(ins.exports[NM + "/const@big"](), r); } if (len >= LOC + VAST + SZ) { r = Random(SZ); mem[(LOC + VAST) / SZ] = r; assertEq(ins.exports[NM + "@vast"](BigInt(LOC)), r); assertEq(ins.exports[NM + "/const@vast"](), r); } // Read out-of-bounds assertErrorMessage(() => ins.exports[NM + "@0"](BigInt(len)), WebAssembly.RuntimeError, /out of bounds/); assertErrorMessage(() => ins.exports[NM + "@0"](BigInt(len-(SZ-1))), WebAssembly.RuntimeError, prefix == "" ? /out of bounds/ : /unaligned memory access/); // This is OOB if we consider the whole pointer as we must, but if we // mistakenly only look at the low bits then it's in-bounds. if (len < 0x1_0000_0000) { assertErrorMessage(() => ins.exports[NM + "@0"](0x1_0000_0000n), WebAssembly.RuntimeError, /out of bounds/); } if (len < HUGE) { assertErrorMessage(() => ins.exports[NM + "@huge"](0n), WebAssembly.RuntimeError, /out of bounds/); } if (len < VAST) { assertErrorMessage(() => ins.exports[NM + "@vast"](0n), WebAssembly.RuntimeError, /out of bounds/); } } function testReadV128(ins, mem, LOC) { let r = 0; let SZ = mem.BYTES_PER_ELEMENT; let len = mem.length * SZ; let NM = "readv128"; assertEq(SZ, 4); // Read in-bounds r = Random4(4); mem.set(r, LOC / SZ); ins.exports[NM + "@0"](BigInt(LOC)) assertSame(mem.slice(0, 4), r); ins.exports[NM + "/const@0"](); assertSame(mem.slice(0, 4), r); r = new Int32Array([0,0,0,0]); mem.set(r, (len / SZ) - 4); ins.exports[NM + "@0"](BigInt(len - (SZ * 4))); // Just barely in-bounds assertSame(mem.slice(0, 4), r); r = Random4(SZ); mem.set(r, (LOC + SMALL) / SZ); ins.exports[NM + "@small"](BigInt(LOC)) assertSame(mem.slice(0, 4), r); ins.exports[NM + "/const@small"](); assertSame(mem.slice(0, 4), r); if (len >= LOC + BIG + SZ * 4) { r = Random4(SZ); mem.set(r, (LOC + BIG) / SZ); ins.exports[NM + "@big"](BigInt(LOC)); assertSame(mem.slice(0, 4), r); ins.exports[NM + "/const@big"](); assertSame(mem.slice(0, 4), r); } // Read out-of-bounds assertErrorMessage(() => ins.exports[NM + "@0"](BigInt(len)), WebAssembly.RuntimeError, /out of bounds/); assertErrorMessage(() => ins.exports[NM + "@0"](BigInt(len-((SZ*4)-1))), WebAssembly.RuntimeError, /out of bounds/); // This is OOB if we consider the whole pointer as we must, but if we // mistakenly only look at the low bits then it's in-bounds. if (len < 0x1_0000_0000) { assertErrorMessage(() => ins.exports[NM + "@0"](0x1_0000_0000n), WebAssembly.RuntimeError, /out of bounds/); } if (len < HUGE) { assertErrorMessage(() => ins.exports[NM + "@huge"](0n), WebAssembly.RuntimeError, /out of bounds/); } // Superficial testing of other load operations r = i32Random() mem[(LOC + SMALL) / SZ] = r; ins.exports["v128.load_splat@small"](BigInt(LOC)); assertSame(mem.slice(0, 4), [r, r, r, r]); r = i32Random() mem[(LOC + SMALL) / SZ] = r; ins.exports["v128.load_zero@small"](BigInt(LOC)); assertSame(mem.slice(0, 4), [r, 0, 0, 0]); r = Random2(SZ) mem.set(r, (LOC + SMALL) / SZ); ins.exports["v128.load_extend@small"](BigInt(LOC)); assertSame(mem.slice(0, 4), [r[0], 0, r[1], 0]); r = Random4(SZ) mem.set(r, 0); let s = i32Random() mem[(LOC + SMALL) / SZ] = s; ins.exports["v128.load_lane@small"](BigInt(LOC)); assertSame(mem.slice(0, 4), [r[0], r[1], s, r[3]]); } function testWrite(ins, mem, LOC, prefix) { let r = 0; let SZ = mem.BYTES_PER_ELEMENT; let len = mem.length * SZ; let WNM = prefix + "writei" + (SZ * 8); let RNM = prefix + "readi" + (SZ * 8); // Write in-bounds r = Random(SZ); ins.exports[WNM + "@0"](BigInt(LOC), r); assertEq(ins.exports[RNM + "@0"](BigInt(LOC)), r); r = Random(SZ); ins.exports[WNM + "@small"](BigInt(LOC), r); assertEq(ins.exports[RNM + "@small"](BigInt(LOC)), r); if (len >= LOC + BIG + SZ) { r = Random(SZ); ins.exports[WNM + "@big"](BigInt(LOC), r); assertEq(ins.exports[RNM + "@big"](BigInt(LOC)), r); } if (len >= LOC + VAST + SZ) { r = Random(SZ); ins.exports[WNM + "@vast"](BigInt(LOC), r); assertEq(ins.exports[RNM + "@vast"](BigInt(LOC)), r); } r = Random(SZ); ins.exports[WNM + "@0"](BigInt(len - SZ), r); // Just barely in-bounds assertEq(ins.exports[RNM + "@0"](BigInt(len - SZ)), r); // Write out-of-bounds assertErrorMessage(() => ins.exports[WNM + "@0"](BigInt(len), Random(SZ)), WebAssembly.RuntimeError, /out of bounds/); assertErrorMessage(() => ins.exports[WNM + "@0"](BigInt(len - (SZ - 1)), Random(SZ)), WebAssembly.RuntimeError, prefix == "" ? /out of bounds/ : /unaligned memory access/); if (len < 0x1_0000_0000) { let xs = ins.exports[RNM + "@0"](0n); assertErrorMessage(() => ins.exports[WNM + "@0"](0x1_0000_0000n, Random(SZ)), WebAssembly.RuntimeError, /out of bounds/); // Check for scribbling assertEq(ins.exports[RNM + "@0"](0n), xs); } if (len < HUGE) { assertErrorMessage(() => ins.exports[WNM + "@huge"](0n, Random(SZ)), WebAssembly.RuntimeError, /out of bounds/); } if (len < VAST) { assertErrorMessage(() => ins.exports[WNM + "@vast"](0n, Random(SZ)), WebAssembly.RuntimeError, /out of bounds/); } } function testWriteV128(ins, mem, LOC) { let r = 0; let p = 0; let SZ = mem.BYTES_PER_ELEMENT; let len = mem.length * SZ; let WNM = "writev128"; let RNM = "readv128"; assertEq(SZ, 4); // Write in-bounds r = Random4(SZ); mem.set(r, 0); p = LOC / SZ; ins.exports[WNM + "@0"](BigInt(LOC)); assertSame(mem.slice(p, p + 4), r); r = Random4(SZ); mem.set(r, 0); p = (LOC + SMALL) / SZ; ins.exports[WNM + "@small"](BigInt(LOC)); assertSame(mem.slice(p, p + 4), r); if (len >= LOC + BIG + SZ) { r = Random4(SZ); mem.set(r, 0); p = (LOC + BIG) / SZ; ins.exports[WNM + "@big"](BigInt(LOC)); assertSame(mem.slice(p, p + 4), r); } r = Random4(SZ); mem.set(r, 0); p = (len - (SZ * 4)) / SZ; ins.exports[WNM + "@0"](BigInt(len - (SZ * 4))); // Just barely in-bounds assertSame(mem.slice(p, p + 4), r); // Write out-of-bounds assertErrorMessage(() => ins.exports[WNM + "@0"](BigInt(len)), WebAssembly.RuntimeError, /out of bounds/); assertErrorMessage(() => ins.exports[WNM + "@0"](BigInt(len - ((SZ * 4) - 1))), WebAssembly.RuntimeError, /out of bounds/); if (len < HUGE) { assertErrorMessage(() => ins.exports[WNM + "@huge"](0n), WebAssembly.RuntimeError, /out of bounds/); } // Superficial testing of other store operations r = Random4(SZ); mem.set(r, 0); ins.exports["v128.store_lane@small"](BigInt(LOC)); assertEq(mem[(LOC + SMALL) / SZ], r[2]); } function testAtomicRMW(ins, mem, LOC, op, fn) { let r = 0, s = 0; let SZ = mem.BYTES_PER_ELEMENT; let len = mem.length * SZ; let NM = op + "i" + (SZ * 8); [r,s] = Random2(SZ); mem[LOC / SZ] = r; assertEq(ins.exports[NM + "@0"](BigInt(LOC), s), r); assertEq(mem[LOC / SZ], fn(r, s)); [r,s] = Random2(SZ); mem[(LOC + SMALL) / SZ] = r; assertEq(ins.exports[NM + "@small"](BigInt(LOC), s), r); assertEq(mem[(LOC + SMALL) / SZ], fn(r, s)); if (len >= LOC + BIG + SZ) { [r,s] = Random2(SZ); mem[(LOC + BIG) / SZ] = r; assertEq(ins.exports[NM + "@big"](BigInt(LOC), s), r); assertEq(mem[(LOC + BIG) / SZ], fn(r, s)); } assertErrorMessage(() => ins.exports[NM + "@0"](BigInt(len), Zero(SZ)), WebAssembly.RuntimeError, /out of bounds/); assertErrorMessage(() => ins.exports[NM + "@0"](BigInt(len - (SZ - 1)), Zero(SZ)), WebAssembly.RuntimeError, /unaligned memory access/); if (len < HUGE) { assertErrorMessage(() => ins.exports[NM + "@huge"](0n, Zero(SZ)), WebAssembly.RuntimeError, /out of bounds/); } } function testAtomicCmpxchg(ins, mem, LOC) { let r = 0, s = 0; let SZ = mem.BYTES_PER_ELEMENT; let len = mem.length * SZ; let NM = "cmpxchgi" + (SZ * 8); [r,s] = Random2(SZ); mem[LOC / SZ] = r; assertEq(ins.exports[NM + "@0"](BigInt(LOC), Zero(SZ), s), r); assertEq(ins.exports[NM + "@0"](BigInt(LOC), r, s), r); assertEq(mem[LOC / SZ], s); [r,s] = Random2(SZ); mem[(LOC + SMALL) / SZ] = r; assertEq(ins.exports[NM + "@0"](BigInt(LOC + SMALL), Zero(SZ), s), r); assertEq(ins.exports[NM + "@0"](BigInt(LOC + SMALL), r, s), r); assertEq(mem[(LOC + SMALL) / SZ], s); if (len >= LOC + BIG + SZ) { [r,s] = Random2(SZ); mem[(LOC + BIG) / SZ] = r; assertEq(ins.exports[NM + "@0"](BigInt(LOC + BIG), Zero(SZ), s), r); assertEq(ins.exports[NM + "@0"](BigInt(LOC + BIG), r, s), r); assertEq(mem[(LOC + BIG) / SZ], s); } assertErrorMessage(() => ins.exports[NM + "@0"](BigInt(len), Zero(SZ), Zero(SZ)), WebAssembly.RuntimeError, /out of bounds/); assertErrorMessage(() => ins.exports[NM + "@0"](BigInt(len - (SZ - 1)), Zero(SZ), Zero(SZ)), WebAssembly.RuntimeError, /unaligned memory access/); if (len < HUGE) { assertErrorMessage(() => ins.exports[NM + "@huge"](0n, Zero(SZ), Zero(SZ)), WebAssembly.RuntimeError, /out of bounds/); } } function testAtomicWake(ins, mem, LOC) { let SZ = mem.BYTES_PER_ELEMENT; let len = mem.length * SZ; assertEq(ins.exports["wake@0"](BigInt(LOC)), 0); assertEq(ins.exports["wake@small"](BigInt(LOC)), 0); if (len >= LOC + BIG + SZ) { assertEq(ins.exports["wake@big"](BigInt(LOC)), 0); } assertErrorMessage(() => ins.exports["wake@0"](BigInt(len)), WebAssembly.RuntimeError, /out of bounds/); assertErrorMessage(() => ins.exports["wake@0"](BigInt(len - (SZ - 1))), WebAssembly.RuntimeError, /unaligned memory access/); if (len < HUGE) { assertErrorMessage(() => ins.exports["wake@huge"](BigInt(LOC)), WebAssembly.RuntimeError, /out of bounds/); } } // Sometimes we start memory at zero to disable certain bounds checking // optimizations. Other times we start memory at something beyond most of // our references to enable those optimizations. let configs = [[40, 0, 3], [40, 3, '']]; // On 64-bit systems, test beyond 2GB and also beyond 4GB if (getBuildConfiguration("pointer-byte-size") == 8) { configs.push([Math.pow(2, 31) + 40, 32771, '']); configs.push([Math.pow(2, 32) + 40, 65539, '']); configs.push([Math.pow(2, 31) + 40, 32771, 32773]); configs.push([Math.pow(2, 32) + 40, 65539, 65541]); } for ( let shared of ['','shared'] ) { for (let [LOC, start, max] of configs) { if (shared != '' && max == '') { continue; } const ins = makeTest(LOC, start, max, shared); if (max != '') { // This can OOM legitimately; let it. let res = Number(ins.exports.mem.grow(BigInt(max - start))); if (res == -1) { print("SPURIOUS OOM"); continue; } assertEq(res, start); } const mem32 = new Int32Array(ins.exports.mem.buffer); const mem64 = new BigInt64Array(ins.exports.mem.buffer); for ( let m of [mem32, mem64] ) { testRead(ins, m, LOC, ""); testWrite(ins, m, LOC, ""); testRead(ins, m, LOC, "a"); testWrite(ins, m, LOC, "a"); testAtomicRMW(ins, m, LOC, "add", (r,s) => r+s); testAtomicRMW(ins, m, LOC, "sub", (r,s) => r-s); testAtomicRMW(ins, m, LOC, "and", (r,s) => r&s); testAtomicRMW(ins, m, LOC, "or", (r,s) => r|s); testAtomicRMW(ins, m, LOC, "xor", (r,s) => r^s); testAtomicRMW(ins, m, LOC, "xchg", (r,s) => s); testAtomicCmpxchg(ins, m, LOC); testAtomicWake(ins, m, LOC); } if (wasmSimdEnabled()) { testReadV128(ins, mem32, LOC); testWriteV128(ins, mem32, LOC); } } } // Bulk memory operations function makeModule(initial, maximum, shared) { return ` (module (memory (export "mem") i64 ${initial} ${maximum} ${shared}) (data $seg "0123456789") (func (export "size") (result i64) memory.size) (func (export "grow") (param $delta i64) (result i64) (memory.grow (local.get $delta))) (func (export "copy") (param $to i64) (param $from i64) (param $len i64) (memory.copy (local.get $to) (local.get $from) (local.get $len))) (func (export "fill") (param $to i64) (param $val i32) (param $len i64) (memory.fill (local.get $to) (local.get $val) (local.get $len))) (func (export "init") (param $to i64) (param $src i32) (param $count i32) (memory.init $seg (local.get $to) (local.get $src) (local.get $count))) )`; } for ( let shared of ['','shared'] ) { let ins = wasmEvalText(makeModule(1, 3, shared)); assertEq(ins.exports.size(), 1n); // OOM with very low probability will result in test failure assertEq(ins.exports.grow(2n), 1n); assertEq(ins.exports.size(), 3n); // OOM with very low probability will result in test failure assertEq(ins.exports.grow(1n), -1n); assertEq(ins.exports.size(), 3n); // More than max pages assertEq(ins.exports.grow(100000n), -1n); assertEq(ins.exports.size(), 3n); // More than 2^48 pages assertEq(ins.exports.grow(0x1_0000_0000_0000n), -1n); assertEq(ins.exports.size(), 3n); // More than 2^48 pages - interpreted as unsigned assertEq(ins.exports.grow(-1n), -1n); assertEq(ins.exports.size(), 3n); var mem = new Uint8Array(ins.exports.mem.buffer); var val = [1,2,3,4,5]; mem.set(val, 20); ins.exports.copy(40n, 20n, 5n); assertSame(val, mem.slice(40, 45)); ins.exports.fill(39n, 37, 8n); assertSame(iota(8).map(_ => 37), mem.slice(39, 47)); ins.exports.init(128n, 1, 5); assertSame(iota(5).map(x => x+49), mem.slice(128, 133)); } if (getBuildConfiguration("pointer-byte-size") == 8) { for ( let shared of ['','shared'] ) { let limit = wasmMaxMemoryPages('i64'); let initial = 65537; let maximum = limit + 1; let pagesize = 65536n; let ins = wasmEvalText(makeModule(initial, maximum, shared)); assertEq(ins.exports.size(), BigInt(initial)); // This can OOM legitimately; let it. { let res = ins.exports.grow(2n); if (res == -1) { print("SPURIOUS OOM"); continue; } assertEq(res, BigInt(initial)); } assertEq(ins.exports.size(), BigInt(initial + 2)); // This must fail assertEq(ins.exports.grow(BigInt(limit - (initial + 2) + 1)), -1n); assertEq(ins.exports.size(), BigInt(initial + 2)); // This can OOM legitimately; let it. { let res = ins.exports.grow(BigInt(limit - (initial + 2))); if (res == -1) { print("SPURIOUS OOM"); continue; } assertEq(res, BigInt(initial + 2)); } assertEq(ins.exports.size(), BigInt(limit)); let mem = new Uint8Array(ins.exports.mem.buffer); let copyval = [1,2,3,4,5]; let source = 20n; let target = BigInt(initial) * pagesize + 40n; let oobTarget = BigInt(limit) * pagesize - 1n; // Copy from memory below 4GB to memory beyond 4GB mem.set(copyval, Number(source)); ins.exports.copy(target, source, BigInt(copyval.length)); assertSame(copyval, mem.slice(Number(target), Number(target) + copyval.length)) // Try to copy out of bounds // src and target are both in bounds but len brings it oob assertErrorMessage(() => ins.exports.copy(oobTarget, source, BigInt(copyval.length)), WebAssembly.RuntimeError, /out of bounds/); assertEq(mem[Number(oobTarget-1n)], 0); assertErrorMessage(() => ins.exports.copy(-1n, source, BigInt(copyval.length)), WebAssembly.RuntimeError, /out of bounds/); assertEq(mem[Number(oobTarget-1n)], 0); // Fill above 4GB ins.exports.fill(target, 37, 30n); assertSame(iota(30).map(_ => 37), mem.slice(Number(target), Number(target) + 30)); // Try to fill out of bounds assertErrorMessage(() => ins.exports.fill(oobTarget, 37, 2n), WebAssembly.RuntimeError, /out of bounds/); assertEq(mem[Number(oobTarget-1n)], 0); // Init above 4GB ins.exports.init(target, 1, 5); assertSame(iota(5).map(x => x+49), mem.slice(Number(target), Number(target)+5)); // Try to init out of bounds assertErrorMessage(() => ins.exports.init(oobTarget, 1, 5), WebAssembly.RuntimeError, /out of bounds/); assertEq(mem[Number(oobTarget-1n)], 0); } } //////////////////////////////////// // Table64 execution // get / set const table64Tests = [ { elem: "anyref", jsval: "haha you cannot represent me", wasmval: `(struct.new_default $s)`, }, ]; if (WebAssembly.Function) { table64Tests.push({ elem: "funcref", jsval: new WebAssembly.Function({ parameters: ["i32"], results: [] }, () => {}), wasmval: `(ref.func 0)`, }); } for (const test of table64Tests) { const externalTable = new WebAssembly.Table({ address: "i64", element: test.elem, initial: 2n }); externalTable.set(0n, test.jsval); externalTable.set(1n, null); const { internalTable, extIsNull, swapExt, getInternal, getExternal, setInternal, setExternal, } = wasmEvalText(`(module (import "" "externalTable" (table $ext i64 2 ${test.elem})) (table $int (export "internalTable") i64 2 ${test.elem}) (elem declare func 0) (type $s (struct)) (func $start (table.set $int (i64.const 0) ${test.wasmval}) ) (start $start) (func (export "extIsNull") (param $i i64) (result i32) (ref.is_null (table.get $ext (local.get $i))) ) (func (export "swapExt") (local $tmp ${test.elem}) (local.set $tmp (table.get $ext (i64.const 0))) (table.set $ext (i64.const 0) (table.get $ext (i64.const 1))) (table.set $ext (i64.const 1) (local.get $tmp)) ) (func (export "getInternal") (param i64) (result ${test.elem}) (table.get $int (local.get 0)) ) (func (export "getExternal") (param i64) (result ${test.elem}) (table.get $ext (local.get 0)) ) (func (export "setInternal") (param i64 ${test.elem}) (table.set $int (local.get 0) (local.get 1)) ) (func (export "setExternal") (param i64 ${test.elem}) (table.set $ext (local.get 0) (local.get 1)) ) )`, { "": { externalTable } }).exports; assertEq(internalTable.get(0n) === null, false); assertEq(internalTable.get(1n) === null, true); assertEq(extIsNull(0n), 0); assertEq(extIsNull(1n), 1); swapExt(); const tmp = internalTable.get(0n); internalTable.set(0n, internalTable.get(1n)); internalTable.set(1n, tmp); assertEq(internalTable.get(0n) === null, true); assertEq(internalTable.get(1n) === null, false); assertEq(extIsNull(0n), 1); assertEq(extIsNull(1n), 0); // Test bounds checks const indexes = [ [-1n, false, TypeError], [2n, false, RangeError], [BigInt(0 + (MaxUint32 + 1)), false, RangeError], [BigInt(1 + (MaxUint32 + 1)), false, RangeError], [BigInt(2 + (MaxUint32 + 1)), false, RangeError], [BigInt(Number.MAX_SAFE_INTEGER), false, RangeError], [BigInt(Number.MAX_SAFE_INTEGER + 1), false, RangeError], [2n**64n - 1n, false, RangeError], [2n**64n, true, TypeError], ]; for (const [index, jsOnly, jsError] of indexes) { if (!jsOnly) { assertErrorMessage(() => getInternal(index), WebAssembly.RuntimeError, /index out of bounds/); assertErrorMessage(() => setInternal(index, null), WebAssembly.RuntimeError, /index out of bounds/); } assertErrorMessage(() => internalTable.get(index), jsError, /bad Table get address/); assertErrorMessage(() => internalTable.set(index, null), jsError, /bad Table set address/); if (!jsOnly) { assertErrorMessage(() => getExternal(index), WebAssembly.RuntimeError, /index out of bounds/); assertErrorMessage(() => setExternal(index, null), WebAssembly.RuntimeError, /index out of bounds/); } assertErrorMessage(() => externalTable.get(index), jsError, /bad Table get address/); assertErrorMessage(() => externalTable.set(index, null), jsError, /bad Table set address/); } } // call_indirect / call_ref / return_call_indirect / return_call_ref { const { callIndirect, callRef, returnCallIndirect, returnCallRef, } = wasmEvalText(`(module (table $int (export "internalTable") i64 funcref (elem (ref.func 0) (ref.null func)) ) (type $ft (func (param i32) (result i32))) (func (type $ft) (i32.add (local.get 0) (i32.const 10)) ) (func (export "callIndirect") (param i64 i32) (result i32) (call_indirect $int (type $ft) (local.get 1) (local.get 0)) ) (func (export "callRef") (param i64 i32) (result i32) (call_ref $ft (local.get 1) (ref.cast (ref null $ft) (table.get $int (local.get 0)))) ) (func (export "returnCallIndirect") (param i64 i32) (result i32) (return_call_indirect $int (type $ft) (local.get 1) (local.get 0)) ) (func (export "returnCallRef") (param i64 i32) (result i32) (return_call_ref $ft (local.get 1) (ref.cast (ref null $ft) (table.get $int (local.get 0)))) ) )`).exports; assertEq(callIndirect(0n, 1), 11); assertEq(callRef(0n, 2), 12); assertErrorMessage(() => callIndirect(1n, 1), WebAssembly.RuntimeError, /indirect call to null/); assertErrorMessage(() => callRef(1n, 2), WebAssembly.RuntimeError, /dereferencing null pointer/); assertEq(returnCallIndirect(0n, 3), 13); assertEq(returnCallRef(0n, 4), 14); assertErrorMessage(() => returnCallIndirect(1n, 3), WebAssembly.RuntimeError, /indirect call to null/); assertErrorMessage(() => returnCallRef(1n, 4), WebAssembly.RuntimeError, /dereferencing null pointer/); // Test bounds checks const indexes = [ -1, 2, 0 + (MaxUint32 + 1), 1 + (MaxUint32 + 1), 2 + (MaxUint32 + 1), Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER + 1, ]; for (const index of indexes) { assertErrorMessage(() => callIndirect(BigInt(index), 1), WebAssembly.RuntimeError, /index out of bounds/); assertErrorMessage(() => callRef(BigInt(index), 2), WebAssembly.RuntimeError, /table index out of bounds/); assertErrorMessage(() => returnCallIndirect(BigInt(index), 3), WebAssembly.RuntimeError, /index out of bounds/); assertErrorMessage(() => returnCallRef(BigInt(index), 4), WebAssembly.RuntimeError, /table index out of bounds/); } } // Bulk table operations for (const [addrType1, addrType2] of types) { const lenType = addrType1 === "i64" && addrType2 === "i64" ? "i64" : "i32"; const { f1, f2, e1, e2, callF1, callF2, getE1, getE2, initF1, initE1, copyF, copyE, growF1, growE1, growF2, growE2, sizeF1, sizeE1, sizeF2, sizeE2, fillF1WithA, fillE1WithA, } = wasmEvalText(`(module (global $ea (import "" "ea") externref) (global $eb (import "" "eb") externref) (global $ec (import "" "ec") externref) (global $ed (import "" "ed") externref) (table $f1 (export "f1") ${addrType1} 8 funcref) (table $f2 (export "f2") ${addrType2} 8 funcref) (table $e1 (export "e1") ${addrType1} 8 externref) (table $e2 (export "e2") ${addrType2} 8 externref) (elem (table $f1) (offset ${addrType1}.const 2) funcref (ref.func $a) (ref.func $b)) (elem $fcd funcref (ref.func $c) (ref.func $d)) (elem (table $e1) (offset ${addrType1}.const 2) externref (global.get $ea) (global.get $eb)) (elem $ecd externref (global.get $ec) (global.get $ed)) (type $ft (func (result i32))) (func $a (result i32) i32.const 97) ;; 'a' (func $b (result i32) i32.const 98) ;; 'b' (func $c (result i32) i32.const 99) ;; 'c' (func $d (result i32) i32.const 100) ;; 'd' (func (export "callF1") (param ${addrType1}) (result i32) (if (ref.is_null (table.get $f1 (local.get 0))) (then (return (i32.const 0)) ) ) (table.get $f1 (local.get 0)) ref.cast (ref $ft) call_ref $ft ) (func (export "callF2") (param ${addrType2}) (result i32) (if (ref.is_null (table.get $f2 (local.get 0))) (then (return (i32.const 0)) ) ) (table.get $f2 (local.get 0)) ref.cast (ref $ft) call_ref $ft ) (func (export "getE1") (param ${addrType1}) (result externref) (table.get $e1 (local.get 0)) ) (func (export "getE2") (param ${addrType2}) (result externref) (table.get $e2 (local.get 0)) ) (func (export "sizeF1") (result ${addrType1}) table.size $f1) (func (export "sizeF2") (result ${addrType2}) table.size $f2) (func (export "sizeE1") (result ${addrType1}) table.size $e1) (func (export "sizeE2") (result ${addrType2}) table.size $e2) (func (export "initF1") (param ${addrType1} i32 i32) (table.init $f1 $fcd (local.get 0) (local.get 1) (local.get 2)) ) (func (export "initE1") (param ${addrType1} i32 i32) (table.init $e1 $ecd (local.get 0) (local.get 1) (local.get 2)) ) (func (export "copyF") (param ${addrType2} ${addrType1} ${lenType}) (table.copy $f2 $f1 (local.get 0) (local.get 1) (local.get 2)) ) (func (export "copyE") (param ${addrType2} ${addrType1} ${lenType}) (table.copy $e2 $e1 (local.get 0) (local.get 1) (local.get 2)) ) (func (export "growF1") (param ${addrType1}) (result ${addrType1}) (table.grow $f1 (ref.null func) (local.get 0)) ) (func (export "growE1") (param ${addrType1}) (result ${addrType1}) (table.grow $e1 (ref.null extern) (local.get 0)) ) (func (export "growF2") (param ${addrType2}) (result ${addrType2}) (table.grow $f2 (ref.null func) (local.get 0)) ) (func (export "growE2") (param ${addrType2}) (result ${addrType2}) (table.grow $e2 (ref.null extern) (local.get 0)) ) (func (export "fillF1WithA") (param ${addrType1} ${addrType1}) (table.fill $f1 (local.get 0) (ref.func $a) (local.get 1)) ) (func (export "fillE1WithA") (param ${addrType1} ${addrType1}) (table.fill $e1 (local.get 0) (global.get $ea) (local.get 1)) ) )`, { "": { "ea": "a", "eb": "b", "ec": "c", "ed": "d", }, }).exports; function addr1(n) { return addrType1 === "i64" ? BigInt(n) : n; } function addr2(n) { return addrType2 === "i64" ? BigInt(n) : n; } function len(n) { return lenType === "i64" ? BigInt(n) : n; } function tryGrow(growFunc) { const res = growFunc(); if (res < 0) { throw new RangeError(`failed table grow inside wasm (result ${res})`); } } function testFuncTable(table, vals) { const actual = (table === 1 ? sizeF1 : sizeF2)(); const expected = table === 1 ? addr1(vals.length) : addr2(vals.length); assertEq(actual, expected, `table $f${table} had wrong size`); for (let i = 0; i < vals.length; i++) { const addr = table === 1 ? addr1(i) : addr2(i); const expected = typeof vals[i] === "string" ? vals[i].charCodeAt(0) : vals[i]; const actual = (table === 1 ? callF1 : callF2)(addr); assertEq(actual, expected, `table $e${table} index ${i}`); } } // "extern" is "extn" here to line up with "func" for aesthetic reasons. // I will not apologize for this. function testExtnTable(table, vals) { const actual = (table === 1 ? sizeE1 : sizeE2)(); const expected = table === 1 ? addr1(vals.length) : addr2(vals.length); assertEq(actual, expected, `table $e${table} had wrong size`); for (let i = 0; i < vals.length; i++) { const addr = table === 1 ? addr1(i) : addr2(i); const expected = vals[i]; const actual = (table === 1 ? getE1 : getE2)(addr); assertEq(actual, expected, `table $e${table} index ${i}`); } } testFuncTable(1, [0, 0, "a", "b", 0, 0, 0, 0]); testFuncTable(2, [0, 0, 0, 0, 0, 0, 0, 0]); testExtnTable(1, [null, null, "a", "b", null, null, null, null]); testExtnTable(2, [null, null, null, null, null, null, null, null]); initF1(addr1(4), 0, 2); initE1(addr1(4), 0, 2); testFuncTable(1, [0, 0, "a", "b", "c", "d", 0, 0]); testFuncTable(2, [0, 0, 0, 0, 0, 0, 0, 0]); testExtnTable(1, [null, null, "a", "b", "c", "d", null, null]); testExtnTable(2, [null, null, null, null, null, null, null, null]); copyF(addr2(4), addr1(2), len(4)); copyE(addr2(4), addr1(2), len(4)); testFuncTable(1, [0, 0, "a", "b", "c", "d", 0, 0]); testFuncTable(2, [0, 0, 0, 0, "a", "b", "c", "d"]); testExtnTable(1, [null, null, "a", "b", "c", "d", null, null]); testExtnTable(2, [null, null, null, null, "a", "b", "c", "d"]); tryGrow(() => growF1(addr1(4))); tryGrow(() => growF2(addr2(4))); tryGrow(() => growE1(addr1(4))); tryGrow(() => growE2(addr2(4))); testFuncTable(1, [0, 0, "a", "b", "c", "d", 0, 0, 0, 0, 0, 0]); testFuncTable(2, [0, 0, 0, 0, "a", "b", "c", "d", 0, 0, 0, 0]); testExtnTable(1, [null, null, "a", "b", "c", "d", null, null, null, null, null, null]); testExtnTable(2, [null, null, null, null, "a", "b", "c", "d", null, null, null, null]); fillF1WithA(addr1(8), addr1(4)); fillE1WithA(addr1(8), addr1(4)); testFuncTable(1, [0, 0, "a", "b", "c", "d", 0, 0, "a", "a", "a", "a"]); testFuncTable(2, [0, 0, 0, 0, "a", "b", "c", "d", 0, 0, 0, 0]); testExtnTable(1, [null, null, "a", "b", "c", "d", null, null, "a", "a", "a", "a"]); testExtnTable(2, [null, null, null, null, "a", "b", "c", "d", null, null, null, null]); // and now explode! if (addrType1 === "i64") { // JS API must use BigInt for i64 assertErrorMessage(() => f1.grow(1), TypeError, /can't convert 1 to BigInt/); assertErrorMessage(() => f1.get(0), TypeError, /can't convert 0 to BigInt/); assertErrorMessage(() => f1.set(0, null), TypeError, /can't convert 0 to BigInt/); assertEq(typeof f1.length, "bigint"); } const indexes = [ -1, 11, // length is 12, so test around the boundary 12, 13, 0 + (MaxUint32 + 1), 1 + (MaxUint32 + 1), 2 + (MaxUint32 + 1), Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER + 1, ]; if (addrType1 === "i64") { for (const i of indexes) { assertErrorMessage( () => fillF1WithA(addr1(i), addr1(2)), WebAssembly.RuntimeError, /index out of bounds/, `start index ${i}`, ); assertErrorMessage( () => fillE1WithA(addr1(i), addr1(2)), WebAssembly.RuntimeError, /index out of bounds/, `start index ${i}`, ); assertErrorMessage( () => copyF(addr2(0), addr1(i), len(2)), WebAssembly.RuntimeError, /index out of bounds/, `src index ${i}`, ); assertErrorMessage( () => copyE(addr2(0), addr1(i), len(2)), WebAssembly.RuntimeError, /index out of bounds/, `src index ${i}`, ); assertErrorMessage( () => initF1(addr1(i), 0, 2), WebAssembly.RuntimeError, /index out of bounds/, `dst index ${i}`, ); assertErrorMessage( () => initE1(addr1(i), 0, 2), WebAssembly.RuntimeError, /index out of bounds/, `dst index ${i}`, ); } } if (addrType2 === "i64") { for (const i of indexes) { assertErrorMessage( () => copyF(addr2(i), addr1(0), len(2)), WebAssembly.RuntimeError, /index out of bounds/, `dst index ${i}`, ); assertErrorMessage( () => copyE(addr2(i), addr1(0), len(2)), WebAssembly.RuntimeError, /index out of bounds/, `dst index ${i}`, ); } } const maxDelta = MaxTableElemsRuntime - 12; assertEq(maxDelta % 2, 0, "maxDelta needs to be even for this test to work"); try { // Grow the tables up to max size using both the instruction and the JS API tryGrow(() => growF1(addr1(maxDelta / 2))); tryGrow(() => growF2(addr2(maxDelta / 2))); tryGrow(() => growE1(addr1(maxDelta / 2))); tryGrow(() => growE2(addr2(maxDelta / 2))); f1.grow(addr1(maxDelta / 2), null); f2.grow(addr2(maxDelta / 2), null); e1.grow(addr1(maxDelta / 2), null); e2.grow(addr2(maxDelta / 2), null); assertEq(sizeF1(), addr1(MaxTableElemsRuntime)); assertEq(sizeF2(), addr2(MaxTableElemsRuntime)); assertEq(sizeE1(), addr1(MaxTableElemsRuntime)); assertEq(sizeE2(), addr2(MaxTableElemsRuntime)); for (const delta of indexes) { if (addrType1 === "i64") { console.log(delta); assertEq(growF1(addr1(delta)), addr1(-1), `growing by ${delta}`); assertEq(growE1(addr1(delta)), addr1(-1), `growing by ${delta}`); assertErrorMessage(() => f1.grow(addr1(delta), null), Error, /grow/); // Loose to accept either TypeError or RangeError assertErrorMessage(() => e1.grow(addr1(delta), null), Error, /grow/); } if (addrType2 === "i64") { assertEq(growF2(addr2(delta)), addr2(-1), `growing by ${delta}`); assertEq(growE2(addr2(delta)), addr2(-1), `growing by ${delta}`); assertErrorMessage(() => f2.grow(addr2(delta), null), Error, /grow/); assertErrorMessage(() => e2.grow(addr2(delta), null), Error, /grow/); } } assertEq(sizeF1(), addr1(MaxTableElemsRuntime)); assertEq(sizeF2(), addr2(MaxTableElemsRuntime)); assertEq(sizeE1(), addr1(MaxTableElemsRuntime)); assertEq(sizeE2(), addr2(MaxTableElemsRuntime)); } catch (e) { if (e instanceof RangeError) { // This can happen due to resource exhaustion on some platforms and is not worth // failing the whole test over. print("WARNING: Failed to test all cases of table grow.", e); } else { throw e; } } }