diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /js/src/jit-test/tests/wasm/memory.js | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/jit-test/tests/wasm/memory.js')
-rw-r--r-- | js/src/jit-test/tests/wasm/memory.js | 472 |
1 files changed, 472 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/wasm/memory.js b/js/src/jit-test/tests/wasm/memory.js new file mode 100644 index 0000000000..8363ee4ccb --- /dev/null +++ b/js/src/jit-test/tests/wasm/memory.js @@ -0,0 +1,472 @@ +const RuntimeError = WebAssembly.RuntimeError; + +function loadModuleSrc(type, ext, offset, align, drop = false) { + let maybeResult = drop ? '' : `(result ${type})`; + let maybeDrop = drop ? 'drop' : ''; + return `(module + (memory 1) + (data (i32.const 0) "\\00\\01\\02\\03\\04\\05\\06\\07\\08\\09\\0a\\0b\\0c\\0d\\0e\\0f") + (data (i32.const 16) "\\f0\\f1\\f2\\f3\\f4\\f5\\f6\\f7\\f8\\f9\\fa\\fb\\fc\\fd\\fe\\ff") + (func $load (param i32) ${maybeResult} + (${type}.load${ext} + offset=${offset} + ${align != 0 ? 'align=' + align : ''} + (local.get 0) + ) + ${maybeDrop} + ) (export "" (func 0)))`; +} +function loadModule(type, ext, offset, align, drop = false) { + return wasmEvalText(loadModuleSrc(type, ext, offset, align, drop)).exports[""]; +} + +function storeModuleSrc(type, ext, offset, align) { + var load_ext = ext === '' ? '' : ext + '_s'; + return `(module + (memory 1) + (data (i32.const 0) "\\00\\01\\02\\03\\04\\05\\06\\07\\08\\09\\0a\\0b\\0c\\0d\\0e\\0f") + (data (i32.const 16) "\\f0\\f1\\f2\\f3\\f4\\f5\\f6\\f7\\f8\\f9\\fa\\fb\\fc\\fd\\fe\\ff") + (func $store (param i32) (param ${type}) + (${type}.store${ext} + offset=${offset} + ${align != 0 ? 'align=' + align : ''} + (local.get 0) + (local.get 1) + ) + ) (export "store" (func 0)) + (func $load (param i32) (result ${type}) + (${type}.load${load_ext} + offset=${offset} + ${align != 0 ? 'align=' + align : ''} + (local.get 0) + ) + ) (export "load" (func 1)))`; +} +function storeModule(type, ext, offset, align) { + return wasmEvalText(storeModuleSrc(type, ext, offset, align)).exports; +} + +function storeModuleCstSrc(type, ext, offset, align, value) { + var load_ext = ext === '' ? '' : ext + '_s'; + return `(module + (memory 1) + (data (i32.const 0) "\\00\\01\\02\\03\\04\\05\\06\\07\\08\\09\\0a\\0b\\0c\\0d\\0e\\0f") + (data (i32.const 16) "\\f0\\f1\\f2\\f3\\f4\\f5\\f6\\f7\\f8\\f9\\fa\\fb\\fc\\fd\\fe\\ff") + (func $store (param i32) + (${type}.store${ext} + offset=${offset} + ${align != 0 ? 'align=' + align : ''} + (local.get 0) + (${type}.const ${value}) + ) + ) (export "store" (func 0)) + (func $load (param i32) (result ${type}) + (${type}.load${load_ext} + offset=${offset} + ${align != 0 ? 'align=' + align : ''} + (local.get 0) + ) + ) (export "load" (func 1)))`; +} +function storeModuleCst(type, ext, offset, align, value) { + return wasmEvalText(storeModuleCstSrc(type, ext, offset, align, value)).exports; +} + +function testLoad(type, ext, base, offset, align, expect) { + if (type === 'i64') { + wasmAssert(loadModuleSrc(type, ext, offset, align), [{ + type, + func: '$load', + expected: expect, + args: [`i32.const ${base}`] + }]); + } + else { + assertEq(loadModule(type, ext, offset, align)(base), expect); + } +} + +function testLoadOOB(type, ext, base, offset, align) { + assertErrorMessage(() => loadModule(type, ext, offset, align, /*drop*/ true)(base), RuntimeError, /index out of bounds/); +} + +function testStore(type, ext, base, offset, align, value) { + if (type === 'i64') { + wasmAssert(storeModuleSrc(type, ext, offset, align), [ + {type, func: '$store', args: [`i32.const ${base}`, `i64.const ${value}`]}, + {type, func: '$load', args: [`i32.const ${base}`], expected: value}, + ]); + wasmAssert(storeModuleCstSrc(type, ext, offset, align, value), [ + {type, func: '$store', args: [`i32.const ${base}`]}, + {type, func: '$load', args: [`i32.const ${base}`], expected: value}, + ]); + } else { + let module = storeModule(type, ext, offset, align); + let moduleCst = storeModuleCst(type, ext, offset, align, value); + module.store(base, value); + assertEq(module.load(base), value); + moduleCst.store(base); + assertEq(moduleCst.load(base), value); + } +} + +function testStoreOOB(type, ext, base, offset, align, value) { + if (type === 'i64') { + assertErrorMessage(() => wasmAssert( + storeModuleSrc(type, ext, offset, align), + [{type, func: '$store', args: [`i32.const ${base}`, `i64.const ${value}`]}] + ), RuntimeError, /index out of bounds/); + } else { + assertErrorMessage(() => storeModule(type, ext, offset, align).store(base, value), RuntimeError, /index out of bounds/); + } +} + +function badLoadModule(type, ext) { + wasmFailValidateText( `(module (func (param i32) (${type}.load${ext} (local.get 0))) (export "" (func 0)))`, /(can't touch memory)|(unknown memory 0)/); +} + +function badStoreModule(type, ext) { + wasmFailValidateText(`(module (func (param i32) (${type}.store${ext} (local.get 0) (${type}.const 0))) (export "" (func 0)))`, /(can't touch memory)|(unknown memory 0)/); +} + +// Can't touch memory. +for (let [type, ext] of [ + ['i32', ''], + ['i32', '8_s'], + ['i32', '8_u'], + ['i32', '16_s'], + ['i32', '16_u'], + ['i64', ''], + ['i64', '8_s'], + ['i64', '8_u'], + ['i64', '16_s'], + ['i64', '16_u'], + ['i64', '32_s'], + ['i64', '32_u'], + ['f32', ''], + ['f64', ''], +]) +{ + badLoadModule(type, ext); +} + +for (let [type, ext] of [ + ['i32', ''], + ['i32', '8'], + ['i32', '16'], + ['i64', ''], + ['i64', '8'], + ['i64', '16'], + ['i64', '32'], + ['f32', ''], + ['f64', ''], +]) +{ + badStoreModule(type, ext); +} + +assertEq(getJitCompilerOptions()['wasm.fold-offsets'], 1); + +for (var foldOffsets = 0; foldOffsets <= 1; foldOffsets++) { + setJitCompilerOption('wasm.fold-offsets', foldOffsets | 0); + + testLoad('i32', '', 0, 0, 0, 0x03020100); + testLoad('i32', '', 1, 0, 1, 0x04030201); + testLoad('i32', '', 0, 4, 0, 0x07060504); + testLoad('i32', '', 1, 3, 4, 0x07060504); + testLoad('f32', '', 0, 0, 0, 3.820471434542632e-37); + testLoad('f32', '', 1, 0, 1, 1.539989614439558e-36); + testLoad('f32', '', 0, 4, 0, 1.0082513512365273e-34); + testLoad('f32', '', 1, 3, 4, 1.0082513512365273e-34); + testLoad('f64', '', 0, 0, 0, 7.949928895127363e-275); + testLoad('f64', '', 1, 0, 1, 5.447603722011605e-270); + testLoad('f64', '', 0, 8, 0, 3.6919162048650923e-236); + testLoad('f64', '', 1, 7, 8, 3.6919162048650923e-236); + + testLoad('i32', '8_s', 16, 0, 0, -0x10); + testLoad('i32', '8_u', 16, 0, 0, 0xf0); + testLoad('i32', '16_s', 16, 0, 0, -0xe10); + testLoad('i32', '16_u', 16, 0, 0, 0xf1f0); + + testStore('i32', '', 0, 0, 0, -0x3f3e2c2c); + testStore('i32', '', 1, 0, 1, -0x3f3e2c2c); + testStore('i32', '', 0, 1, 1, -0x3f3e2c2c); + testStore('i32', '', 1, 1, 4, -0x3f3e2c2c); + + testStore('f32', '', 0, 0, 0, 0.01234566979110241); + testStore('f32', '', 1, 0, 1, 0.01234566979110241); + testStore('f32', '', 0, 4, 0, 0.01234566979110241); + testStore('f32', '', 1, 3, 4, 0.01234566979110241); + testStore('f64', '', 0, 0, 0, 0.89012345); + testStore('f64', '', 1, 0, 1, 0.89012345); + testStore('f64', '', 0, 8, 0, 0.89012345); + testStore('f64', '', 1, 7, 8, 0.89012345); + + testStore('i32', '8', 0, 0, 0, 0x23); + testStore('i32', '16', 0, 0, 0, 0x2345); + + wasmFailValidateText('(module (memory 2 1))', /maximum length 1 is less than initial length 2/); + + // Test bounds checks and edge cases. + + for (let align of [0,1,2,4]) { + + for (let offset of [0, 1, 2, 3, 4, 8, 16, 41, 0xfff8]) { + // Accesses of 1 byte. + let lastValidIndex = 0x10000 - 1 - offset; + + if (align < 2) { + testLoad('i32', '8_s', lastValidIndex, offset, align, 0); + testLoadOOB('i32', '8_s', lastValidIndex + 1, offset, align); + + testLoad('i32', '8_u', lastValidIndex, offset, align, 0); + testLoadOOB('i32', '8_u', lastValidIndex + 1, offset, align); + + testStore('i32', '8', lastValidIndex, offset, align, -42); + testStoreOOB('i32', '8', lastValidIndex + 1, offset, align, -42); + } + + // Accesses of 2 bytes. + lastValidIndex = 0x10000 - 2 - offset; + + if (align < 4) { + testLoad('i32', '16_s', lastValidIndex, offset, align, 0); + testLoadOOB('i32', '16_s', lastValidIndex + 1, offset, align); + + testLoad('i32', '16_u', lastValidIndex, offset, align, 0); + testLoadOOB('i32', '16_u', lastValidIndex + 1, offset, align); + + testStore('i32', '16', lastValidIndex, offset, align, -32768); + testStoreOOB('i32', '16', lastValidIndex + 1, offset, align, -32768); + } + + // Accesses of 4 bytes. + lastValidIndex = 0x10000 - 4 - offset; + + testLoad('i32', '', lastValidIndex, offset, align, 0); + testLoadOOB('i32', '', lastValidIndex + 1, offset, align); + + testLoad('f32', '', lastValidIndex, offset, align, 0); + testLoadOOB('f32', '', lastValidIndex + 1, offset, align); + + testStore('i32', '', lastValidIndex, offset, align, 1337); + testStoreOOB('i32', '', lastValidIndex + 1, offset, align, 1337); + + testStore('f32', '', lastValidIndex, offset, align, Math.fround(13.37)); + testStoreOOB('f32', '', lastValidIndex + 1, offset, align, Math.fround(13.37)); + + // Accesses of 8 bytes. + lastValidIndex = 0x10000 - 8 - offset; + + testLoad('f64', '', lastValidIndex, offset, align, 0); + testLoadOOB('f64', '', lastValidIndex + 1, offset, align); + + testStore('f64', '', lastValidIndex, offset, align, 1.23456789); + testStoreOOB('f64', '', lastValidIndex + 1, offset, align, 1.23456789); + } + + // Ensure wrapping doesn't apply. + offset = 0x7fffffff; // maximum allowed offset that doesn't always throw. + for (let index of [0, 1, 2, 3, 0x7fffffff, 0x80000000, 0x80000001]) { + if (align < 2) { + testLoadOOB('i32', '8_s', index, offset, align); + } + if (align < 4) { + testLoadOOB('i32', '16_s', index, offset, align); + } + testLoadOOB('i32', '', index, offset, align); + testLoadOOB('f32', '', index, offset, align); + testLoadOOB('f64', '', index, offset, align); + } + + // Ensure out of bounds when the offset is greater than the immediate range. + index = 0; + for (let offset of [0x80000000, 0xfffffffe, 0xffffffff]) { + testLoadOOB('i32', '8_s', index, offset, 1); + testLoadOOB('i32', '16_s', index, offset, 1); + testLoadOOB('i32', '16_s', index, offset, 2); + testLoadOOB('i32', '', index, offset, 1); + testLoadOOB('i32', '', index, offset, 4); + testLoadOOB('f32', '', index, offset, 1); + testLoadOOB('f32', '', index, offset, 4); + testLoadOOB('f64', '', index, offset, 1); + testLoadOOB('f64', '', index, offset, 8); + } + + wasmFailValidateText('(module (memory 1) (func (f64.store offset=0 (i32.const 0) (i32.const 0))))', mismatchError("i32", "f64")); + wasmFailValidateText('(module (memory 1) (func (f64.store offset=0 (i32.const 0) (f32.const 0))))', mismatchError("f32", "f64")); + + wasmFailValidateText('(module (memory 1) (func (f32.store offset=0 (i32.const 0) (i32.const 0))))', mismatchError("i32", "f32")); + wasmFailValidateText('(module (memory 1) (func (f32.store offset=0 (i32.const 0) (f64.const 0))))', mismatchError("f64", "f32")); + + wasmFailValidateText('(module (memory 1) (func (i32.store offset=0 (i32.const 0) (f32.const 0))))', mismatchError("f32", "i32")); + wasmFailValidateText('(module (memory 1) (func (i32.store offset=0 (i32.const 0) (f64.const 0))))', mismatchError("f64", "i32")); + + // Test high number of registers. + function testRegisters() { + assertEq(wasmEvalText( + `(module + (memory 1) + (data (i32.const 0) "\\00\\01\\02\\03\\04\\05\\06\\07\\08\\09\\0a\\0b\\0c\\0d\\0e\\0f") + (data (i32.const 16) "\\f0\\f1\\f2\\f3\\f4\\f5\\f6\\f7\\f8\\f9\\fa\\fb\\fc\\fd\\fe\\ff") + (func (param i32) (result i32) (local i32 i32 i32 i32 f32 f64) + (local.set 1 (i32.load8_s offset=4 (local.get 0))) + (local.set 2 (i32.load16_s (local.get 1))) + (i32.store8 offset=4 (local.get 0) (local.get 1)) + (local.set 3 (i32.load16_u (local.get 2))) + (i32.store16 (local.get 1) (local.get 2)) + (local.set 4 (i32.load (local.get 2))) + (i32.store (local.get 1) (local.get 2)) + (local.set 5 (f32.load (local.get 4))) + (f32.store (local.get 4) (local.get 5)) + (local.set 6 (f64.load (local.get 4))) + (f64.store (local.get 4) (local.get 6)) + (i32.add + (i32.add + (local.get 0) + (local.get 1) + ) + (i32.add + (i32.add + (local.get 2) + (local.get 3) + ) + (i32.add + (local.get 4) + (i32.reinterpret/f32 (local.get 5)) + ) + ) + ) + ) (export "" (func 0)))` + ).exports[""](1), 50464523); + } + + testRegisters(); + + testLoad('i64', '', 0, 0, 0, '0x0706050403020100'); + testLoad('i64', '', 1, 0, 0, '0x0807060504030201'); + testLoad('i64', '', 0, 1, 0, '0x0807060504030201'); + testLoad('i64', '', 1, 1, 4, '0x0908070605040302'); + + testLoad('i64', '8_s', 16, 0, 0, -0x10); + testLoad('i64', '8_u', 16, 0, 0, 0xf0); + testLoad('i64', '16_s', 16, 0, 0, -0xe10); + testLoad('i64', '16_u', 16, 0, 0, 0xf1f0); + testLoad('i64', '32_s', 16, 0, 0, 0xf3f2f1f0 | 0); + testLoad('i64', '32_u', 16, 0, 0, '0xf3f2f1f0'); + + testStore('i64', '', 0, 0, 0, '0xc0c1d3d4e6e7090a'); + testStore('i64', '', 1, 0, 0, '0xc0c1d3d4e6e7090a'); + testStore('i64', '', 0, 1, 0, '0xc0c1d3d4e6e7090a'); + testStore('i64', '', 1, 1, 4, '0xc0c1d3d4e6e7090a'); + testStore('i64', '8', 0, 0, 0, 0x23); + testStore('i64', '16', 0, 0, 0, 0x23); + testStore('i64', '32', 0, 0, 0, 0x23); + + for (let offset of [0, 1, 2, 3, 4, 8, 16, 41, 0xfff8]) { + // Accesses of 1 byte. + let lastValidIndex = 0x10000 - 1 - offset; + + if (align < 2) { + testLoad('i64', '8_s', lastValidIndex, offset, align, 0); + testLoadOOB('i64', '8_s', lastValidIndex + 1, offset, align); + + testLoad('i64', '8_u', lastValidIndex, offset, align, 0); + testLoadOOB('i64', '8_u', lastValidIndex + 1, offset, align); + + testStore('i64', '8', lastValidIndex, offset, align, -42); + testStoreOOB('i64', '8', lastValidIndex + 1, offset, align, -42); + } + + // Accesses of 2 bytes. + lastValidIndex = 0x10000 - 2 - offset; + + if (align < 4) { + testLoad('i64', '16_s', lastValidIndex, offset, align, 0); + testLoadOOB('i64', '16_s', lastValidIndex + 1, offset, align); + + testLoad('i64', '16_u', lastValidIndex, offset, align, 0); + testLoadOOB('i64', '16_u', lastValidIndex + 1, offset, align); + + testStore('i64', '16', lastValidIndex, offset, align, -32768); + testStoreOOB('i64', '16', lastValidIndex + 1, offset, align, -32768); + } + + // Accesses of 4 bytes. + lastValidIndex = 0x10000 - 4 - offset; + + testLoad('i64', '32_s', lastValidIndex, offset, align, 0); + testLoadOOB('i64', '32_s', lastValidIndex + 1, offset, align); + + testLoad('i64', '32_u', lastValidIndex, offset, align, 0); + testLoadOOB('i64', '32_u', lastValidIndex + 1, offset, align); + + testStore('i64', '32', lastValidIndex, offset, align, 0xf1231337 | 0); + testStoreOOB('i64', '32', lastValidIndex + 1, offset, align, 0xf1231337 | 0); + + // Accesses of 8 bytes. + lastValidIndex = 0x10000 - 8 - offset; + + testLoad('i64', '', lastValidIndex, offset, align, 0); + testLoadOOB('i64', '', lastValidIndex + 1, offset, align); + + testStore('i64', '', lastValidIndex, offset, align, '0x1234567887654321'); + testStoreOOB('i64', '', lastValidIndex + 1, offset, align, '0x1234567887654321'); + } + } +} + +setJitCompilerOption('wasm.fold-offsets', 1); + +// Test active segments with a memory index. + +{ + function makeIt(flag, memindex) { + return new Uint8Array([0x00, 0x61, 0x73, 0x6d, + 0x01, 0x00, 0x00, 0x00, + 0x05, // Memory section + 0x03, // Section size + 0x01, // One memory + 0x00, // Unshared, min only + 0x01, // Min + 0x0b, // Data section + 0x0a, // Section size + 0x01, // One data segment + flag, // Flag should be 2, or > 2 if invalid + memindex, // Memory index should be 0, or > 0 if invalid + 0x41, // Init expr: i32.const + 0x00, // Init expr: zero (payload) + 0x0b, // Init expr: end + 0x03, // Three bytes, because why not? + 0x01, + 0x02, + 0x03]); + } + + // Should succeed because this is what an active segment with index looks like + new WebAssembly.Module(makeIt(0x02, 0x00)); + + // Should fail because the kind is unknown + assertErrorMessage(() => new WebAssembly.Module(makeIt(0x03, 0x00)), + WebAssembly.CompileError, + /invalid data initializer-kind/); + + // Should fail because the memory index is bad + assertErrorMessage(() => new WebAssembly.Module(makeIt(0x02, 0x01)), + WebAssembly.CompileError, + /memory index must be zero/); +} + +// Misc syntax for data. + +// When memory index is present it must be zero, and the offset must be present too; +// but it's OK for there to be neither +new WebAssembly.Module(wasmTextToBinary(`(module (memory 1) (data 0 (i32.const 0) ""))`)); +new WebAssembly.Module(wasmTextToBinary(`(module (memory 1) (data 0 (offset (i32.const 0)) ""))`)); +new WebAssembly.Module(wasmTextToBinary(`(module (memory 1) (data ""))`)); +assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(`(module (memory 1) (data 0 ""))`)), + SyntaxError, + /wasm text error/); +assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(`(module (memory 1) (data 1 (i32.const 0) ""))`)), + WebAssembly.CompileError, + /memory index must be zero/); |