Source code
Revision control
Copy as Markdown
Other Tools
// |jit-test| skip-if: !wasmMemoryControlEnabled(); test-also=--setpref=wasm_memory64=true; test-also=--setpref=wasm_memory64=false
// This tests memory.discard and WebAssembly.Memory.discard() by placing data
// (the alphabet) halfway across a page boundary, then discarding the first
// page. The first half of the alphabet should be zeroed; the second should
// not. The memory should remain readable and writable.
//
// The ultimate goal is to release physical pages of memory back to the
// operating system, but we can't really observe memory metrics here. Oh well.
function initModule(discardOffset, discardLen, discardViaJS, shared, memType = 'i32') {
const memProps = shared ? '4 4 shared' : '4'; // 4 pages
const text = `(module
(memory (export "memory") ${memType} ${memProps})
(data "abcdefghijklmnopqrstuvwxyz")
(func (export "init")
;; splat alphabet halfway across the 3/4 page boundary.
;; 196595 = (65536 * 3) - (26 / 2)
(memory.init 0 (${memType}.const 196595) (i32.const 0) (i32.const 26))
)
(func (export "letter") (param i32) (result i32)
(i32.load8_u (${memType}.add (${memType}.const 196595) ${memType == 'i64' ? '(i64.extend_i32_u (local.get 0))' : '(local.get 0)'}))
)
(func (export "discard")
(memory.discard (${memType}.const ${discardOffset}) (${memType}.const ${discardLen}))
)
)`;
const exp = wasmEvalText(text).exports;
return [exp, discardViaJS ? () => exp.memory.discard(discardOffset, discardLen) : exp.discard];
}
function checkRegion(exp, min, max, expectLetters) {
for (let i = min; i < max; i++) {
const c = "a".charCodeAt(0) + i;
const expected = expectLetters ? c : 0;
assertEq(exp.letter(i), expected, `checking position of letter ${String.fromCharCode(c)}`);
}
}
function checkFirstHalf(exp, expectLetters) { return checkRegion(exp, 0, 13, expectLetters) }
function checkSecondHalf(exp, expectLetters) { return checkRegion(exp, 13, 26, expectLetters) }
function checkWholeAlphabet(exp, expectLetters) { return checkRegion(exp, 0, 26, expectLetters) }
function testAll(func) {
func(false, false, 'i32');
func(false, true, 'i32');
func(true, false, 'i32');
func(true, true, 'i32');
if (wasmMemory64Enabled()) {
func(false, false, 'i64');
func(false, true, 'i64');
func(true, false, 'i64');
func(true, true, 'i64');
}
}
testAll(function testHappyPath(discardViaJS, shared, memType) {
// Only page 3 of memory, half the alphabet
const [exp, discard] = initModule(65536 * 2, 65536, discardViaJS, shared, memType);
// All zero to start
checkWholeAlphabet(exp, false);
// Then all alphabet
exp.init();
checkWholeAlphabet(exp, true);
// Discarding the first page clears the first half of the alphabet
discard();
checkFirstHalf(exp, false);
checkSecondHalf(exp, true);
// We should be able to write back to a discarded region of memory
exp.init();
checkWholeAlphabet(exp, true);
// ...and then discard again
discard();
checkFirstHalf(exp, false);
checkSecondHalf(exp, true);
});
testAll(function testZeroLen(discardViaJS, shared) {
// Discard zero bytes
const [exp, discard] = initModule(PageSizeInBytes * 2, 0, discardViaJS, shared);
// Init the stuff
exp.init();
checkWholeAlphabet(exp, true);
// Discarding zero bytes should be valid...
discard();
// ...but should have no effect.
checkWholeAlphabet(exp, true);
});
testAll(function testWithGrow(discardViaJS, shared, memType) {
if (shared) {
return; // shared memories cannot grow
}
// Only page 3 of memory, half the alphabet. There is no max size on the
// memory, so it will be subject to moving grows in 32-bit mode.
const [exp, discard] = initModule(65536 * 2, 65536, discardViaJS, false, memType);
// Start with the whole alphabet
exp.init();
checkWholeAlphabet(exp, true);
// Discarding the first page clears the first half of the alphabet
discard();
checkFirstHalf(exp, false);
checkSecondHalf(exp, true);
// Oops we just grew by a preposterous amount, time to move
exp.memory.grow(memType === "i64" ? 200n : 200);
// The memory should still be readable
checkFirstHalf(exp, false);
checkSecondHalf(exp, true);
// We should be able to write back to a discarded region of memory
exp.init();
checkWholeAlphabet(exp, true);
// ...and then discard again
discard();
checkFirstHalf(exp, false);
checkSecondHalf(exp, true);
});
testAll(function testOOB(discardViaJS, shared) {
// Discard two pages where there is only one
const [exp, discard] = initModule(PageSizeInBytes * 3, PageSizeInBytes * 2, discardViaJS, shared);
exp.init();
checkWholeAlphabet(exp, true);
assertErrorMessage(() => discard(), WebAssembly.RuntimeError, /out of bounds/);
// Ensure nothing was discarded
checkWholeAlphabet(exp, true);
});
testAll(function testOOB2(discardViaJS, shared) {
// Discard two pages starting near the end of 32-bit address space
// (would trigger an overflow in 32-bit world)
const [exp, discard] = initModule(2 ** 32 - PageSizeInBytes, PageSizeInBytes * 2, discardViaJS, shared);
exp.init();
checkWholeAlphabet(exp, true);
assertErrorMessage(() => discard(), WebAssembly.RuntimeError, /out of bounds/);
// Ensure nothing was discarded
checkWholeAlphabet(exp, true);
});
testAll(function testOOB3(discardViaJS, shared) {
// Discard nearly an entire 32-bit address space's worth of pages. Very exciting!
const [exp, discard] = initModule(0, 2 ** 32 - PageSizeInBytes, discardViaJS, shared);
exp.init();
checkWholeAlphabet(exp, true);
assertErrorMessage(() => discard(), WebAssembly.RuntimeError, /out of bounds/);
// Ensure nothing was discarded
checkWholeAlphabet(exp, true);
});
(function testOOB4() {
// Discard an entire 32-bit address space's worth of pages. JS can do this
// even to 32-bit memories because it can handle numbers bigger
// than 2^32 - 1.
const [exp, _] = initModule(0, 0, false); // pass zero to allow the wasm module to validate
const discard = () => exp.memory.discard(0, 2 ** 32);
exp.init();
checkWholeAlphabet(exp, true);
assertErrorMessage(() => discard(), WebAssembly.RuntimeError, /out of bounds/);
// Ensure nothing was discarded
checkWholeAlphabet(exp, true);
})();
if (wasmMemory64Enabled()) {
(function testOverflow() {
// Discard UINT64_MAX - (2 pages), starting from page 4. This overflows but puts both start and end in bounds.
// This cannot be done with a JS discard because JS can't actually represent big enough integers.
// The big ol' number here is 2^64 - (65536 * 2)
const [exp, discard] = initModule(65536 * 3, `18_446_744_073_709_420_544`, false, false, 'i64');
// Init the stuff
exp.init();
checkWholeAlphabet(exp, true);
// Discarding should not be valid when it goes out of bounds
assertErrorMessage(() => discard(), WebAssembly.RuntimeError, /out of bounds/);
// Ensure nothing was discarded
checkWholeAlphabet(exp, true);
})();
}
testAll(function testMisalignedStart(discardViaJS, shared) {
// Discard only the first half of the alphabet (this misaligns the start)
const [exp, discard] = initModule(PageSizeInBytes * 3 - 13, 13, discardViaJS, shared);
exp.init();
checkWholeAlphabet(exp, true);
assertErrorMessage(() => discard(), WebAssembly.RuntimeError, /unaligned/);
// Ensure nothing was discarded
checkWholeAlphabet(exp, true);
});
testAll(function testMisalignedEnd(discardViaJS, shared) {
// Discard only the second half of the alphabet (this misaligns the end)
const [exp, discard] = initModule(PageSizeInBytes * 3, 13, discardViaJS, shared);
exp.init();
checkWholeAlphabet(exp, true);
assertErrorMessage(() => discard(), WebAssembly.RuntimeError, /unaligned/);
// Ensure nothing was discarded
checkWholeAlphabet(exp, true);
});