memory-discard.js (8027B)
1 // |jit-test| skip-if: !wasmMemoryControlEnabled() 2 3 // This tests memory.discard and WebAssembly.Memory.discard() by placing data 4 // (the alphabet) halfway across a page boundary, then discarding the first 5 // page. The first half of the alphabet should be zeroed; the second should 6 // not. The memory should remain readable and writable. 7 // 8 // The ultimate goal is to release physical pages of memory back to the 9 // operating system, but we can't really observe memory metrics here. Oh well. 10 11 function initModule(discardOffset, discardLen, discardViaJS, shared, memType = 'i32') { 12 const memProps = shared ? '4 4 shared' : '4'; // 4 pages 13 const text = `(module 14 (memory (export "memory") ${memType} ${memProps}) 15 (data "abcdefghijklmnopqrstuvwxyz") 16 (func (export "init") 17 ;; splat alphabet halfway across the 3/4 page boundary. 18 ;; 196595 = (65536 * 3) - (26 / 2) 19 (memory.init 0 (${memType}.const 196595) (i32.const 0) (i32.const 26)) 20 ) 21 (func (export "letter") (param i32) (result i32) 22 (i32.load8_u (${memType}.add (${memType}.const 196595) ${memType == 'i64' ? '(i64.extend_i32_u (local.get 0))' : '(local.get 0)'})) 23 ) 24 (func (export "discard") 25 (memory.discard (${memType}.const ${discardOffset}) (${memType}.const ${discardLen})) 26 ) 27 )`; 28 const exp = wasmEvalText(text).exports; 29 30 return [exp, discardViaJS ? () => exp.memory.discard(discardOffset, discardLen) : exp.discard]; 31 } 32 33 function checkRegion(exp, min, max, expectLetters) { 34 for (let i = min; i < max; i++) { 35 const c = "a".charCodeAt(0) + i; 36 const expected = expectLetters ? c : 0; 37 assertEq(exp.letter(i), expected, `checking position of letter ${String.fromCharCode(c)}`); 38 } 39 } 40 function checkFirstHalf(exp, expectLetters) { return checkRegion(exp, 0, 13, expectLetters) } 41 function checkSecondHalf(exp, expectLetters) { return checkRegion(exp, 13, 26, expectLetters) } 42 function checkWholeAlphabet(exp, expectLetters) { return checkRegion(exp, 0, 26, expectLetters) } 43 44 function testAll(func) { 45 func(false, false, 'i32'); 46 func(false, true, 'i32'); 47 func(true, false, 'i32'); 48 func(true, true, 'i32'); 49 func(false, false, 'i64'); 50 func(false, true, 'i64'); 51 func(true, false, 'i64'); 52 func(true, true, 'i64'); 53 } 54 55 testAll(function testHappyPath(discardViaJS, shared, memType) { 56 // Only page 3 of memory, half the alphabet 57 const [exp, discard] = initModule(65536 * 2, 65536, discardViaJS, shared, memType); 58 59 // All zero to start 60 checkWholeAlphabet(exp, false); 61 62 // Then all alphabet 63 exp.init(); 64 checkWholeAlphabet(exp, true); 65 66 // Discarding the first page clears the first half of the alphabet 67 discard(); 68 checkFirstHalf(exp, false); 69 checkSecondHalf(exp, true); 70 71 // We should be able to write back to a discarded region of memory 72 exp.init(); 73 checkWholeAlphabet(exp, true); 74 75 // ...and then discard again 76 discard(); 77 checkFirstHalf(exp, false); 78 checkSecondHalf(exp, true); 79 }); 80 81 testAll(function testZeroLen(discardViaJS, shared) { 82 // Discard zero bytes 83 const [exp, discard] = initModule(PageSizeInBytes * 2, 0, discardViaJS, shared); 84 85 // Init the stuff 86 exp.init(); 87 checkWholeAlphabet(exp, true); 88 89 // Discarding zero bytes should be valid... 90 discard(); 91 92 // ...but should have no effect. 93 checkWholeAlphabet(exp, true); 94 }); 95 96 testAll(function testWithGrow(discardViaJS, shared, memType) { 97 if (shared) { 98 return; // shared memories cannot grow 99 } 100 101 // Only page 3 of memory, half the alphabet. There is no max size on the 102 // memory, so it will be subject to moving grows in 32-bit mode. 103 const [exp, discard] = initModule(65536 * 2, 65536, discardViaJS, false, memType); 104 105 // Start with the whole alphabet 106 exp.init(); 107 checkWholeAlphabet(exp, true); 108 109 // Discarding the first page clears the first half of the alphabet 110 discard(); 111 checkFirstHalf(exp, false); 112 checkSecondHalf(exp, true); 113 114 // Oops we just grew by a preposterous amount, time to move 115 exp.memory.grow(memType === "i64" ? 200n : 200); 116 117 // The memory should still be readable 118 checkFirstHalf(exp, false); 119 checkSecondHalf(exp, true); 120 121 // We should be able to write back to a discarded region of memory 122 exp.init(); 123 checkWholeAlphabet(exp, true); 124 125 // ...and then discard again 126 discard(); 127 checkFirstHalf(exp, false); 128 checkSecondHalf(exp, true); 129 }); 130 131 testAll(function testOOB(discardViaJS, shared) { 132 // Discard two pages where there is only one 133 const [exp, discard] = initModule(PageSizeInBytes * 3, PageSizeInBytes * 2, discardViaJS, shared); 134 135 exp.init(); 136 checkWholeAlphabet(exp, true); 137 assertErrorMessage(() => discard(), WebAssembly.RuntimeError, /out of bounds/); 138 139 // Ensure nothing was discarded 140 checkWholeAlphabet(exp, true); 141 }); 142 143 testAll(function testOOB2(discardViaJS, shared) { 144 // Discard two pages starting near the end of 32-bit address space 145 // (would trigger an overflow in 32-bit world) 146 const [exp, discard] = initModule(2 ** 32 - PageSizeInBytes, PageSizeInBytes * 2, discardViaJS, shared); 147 148 exp.init(); 149 checkWholeAlphabet(exp, true); 150 assertErrorMessage(() => discard(), WebAssembly.RuntimeError, /out of bounds/); 151 152 // Ensure nothing was discarded 153 checkWholeAlphabet(exp, true); 154 }); 155 156 testAll(function testOOB3(discardViaJS, shared) { 157 // Discard nearly an entire 32-bit address space's worth of pages. Very exciting! 158 const [exp, discard] = initModule(0, 2 ** 32 - PageSizeInBytes, discardViaJS, shared); 159 160 exp.init(); 161 checkWholeAlphabet(exp, true); 162 assertErrorMessage(() => discard(), WebAssembly.RuntimeError, /out of bounds/); 163 164 // Ensure nothing was discarded 165 checkWholeAlphabet(exp, true); 166 }); 167 168 (function testOOB4() { 169 // Discard an entire 32-bit address space's worth of pages. JS can do this 170 // even to 32-bit memories because it can handle numbers bigger 171 // than 2^32 - 1. 172 const [exp, _] = initModule(0, 0, false); // pass zero to allow the wasm module to validate 173 const discard = () => exp.memory.discard(0, 2 ** 32); 174 175 exp.init(); 176 checkWholeAlphabet(exp, true); 177 assertErrorMessage(() => discard(), WebAssembly.RuntimeError, /out of bounds/); 178 179 // Ensure nothing was discarded 180 checkWholeAlphabet(exp, true); 181 })(); 182 183 (function testOverflow() { 184 // Discard UINT64_MAX - (2 pages), starting from page 4. This overflows but puts both start and end in bounds. 185 // This cannot be done with a JS discard because JS can't actually represent big enough integers. 186 187 // The big ol' number here is 2^64 - (65536 * 2) 188 const [exp, discard] = initModule(65536 * 3, `18_446_744_073_709_420_544`, false, false, 'i64'); 189 190 // Init the stuff 191 exp.init(); 192 checkWholeAlphabet(exp, true); 193 194 // Discarding should not be valid when it goes out of bounds 195 assertErrorMessage(() => discard(), WebAssembly.RuntimeError, /out of bounds/); 196 197 // Ensure nothing was discarded 198 checkWholeAlphabet(exp, true); 199 })(); 200 201 testAll(function testMisalignedStart(discardViaJS, shared) { 202 // Discard only the first half of the alphabet (this misaligns the start) 203 const [exp, discard] = initModule(PageSizeInBytes * 3 - 13, 13, discardViaJS, shared); 204 205 exp.init(); 206 checkWholeAlphabet(exp, true); 207 assertErrorMessage(() => discard(), WebAssembly.RuntimeError, /unaligned/); 208 209 // Ensure nothing was discarded 210 checkWholeAlphabet(exp, true); 211 }); 212 213 testAll(function testMisalignedEnd(discardViaJS, shared) { 214 // Discard only the second half of the alphabet (this misaligns the end) 215 const [exp, discard] = initModule(PageSizeInBytes * 3, 13, discardViaJS, shared); 216 217 exp.init(); 218 checkWholeAlphabet(exp, true); 219 assertErrorMessage(() => discard(), WebAssembly.RuntimeError, /unaligned/); 220 221 // Ensure nothing was discarded 222 checkWholeAlphabet(exp, true); 223 });