tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

atomic.js (20582B)


      1 // |jit-test| skip-if: !wasmThreadsEnabled()
      2 
      3 const oob = /index out of bounds/;
      4 const unaligned = /unaligned memory access/;
      5 const RuntimeError = WebAssembly.RuntimeError;
      6 
      7 function valText(text) {
      8    return WebAssembly.validate(wasmTextToBinary(text));
      9 }
     10 
     11 function assertNum(a, b) {
     12    if (typeof a == "number" && typeof b == "number")
     13 assertEq(a, b);
     14    else if (typeof a == "number") {
     15 assertEq(a, b.low);
     16 assertEq(0, b.high);
     17    } else if (typeof b == "number") {
     18 assertEq(a.low, b);
     19 assertEq(a.high, 0);
     20    } else {
     21 assertEq(a.high, b.high);
     22 assertEq(a.low, b.low);
     23    }
     24 }
     25 
     26 // Check that the output of wasmTextToBinary verifies correctly.
     27 
     28 for ( let shared of ['shared', ''] ) {
     29    for (let [type,width,view] of [['i32','8', '_u'],['i32','16','_u'],['i32','',''],['i64','8','_u'],['i64','16','_u'],['i64','32','_u'],['i64','','']]) {
     30        {
     31     let text = (shared) => `(module (memory 1 1 ${shared})
     32 			 (func (result ${type}) (${type}.atomic.load${width}${view} (i32.const 0)))
     33 			 (export "" (func 0)))`;
     34     assertEq(valText(text(shared)), true);
     35        }
     36 
     37        {
     38     let text = (shared) => `(module (memory 1 1 ${shared})
     39 			 (func (${type}.atomic.store${width} (i32.const 0) (${type}.const 1)))
     40 			 (export "" (func 0)))`;
     41     assertEq(valText(text(shared)), true);
     42        }
     43 
     44        {
     45     let text = (shared) => `(module (memory 1 1 ${shared})
     46 			 (func (result ${type})
     47 			  (${type}.atomic.rmw${width}.cmpxchg${view} (i32.const 0) (${type}.const 1) (${type}.const 2)))
     48 			 (export "" (func 0)))`;
     49     assertEq(valText(text(shared)), true);
     50        }
     51 
     52        for (let op of ['add','and','or','sub','xor','xchg']) {
     53     // Operate with appropriately-typed value 1 on address 0
     54     let text = (shared) => `(module (memory 1 1 ${shared})
     55 			 (func (result ${type}) (${type}.atomic.rmw${width}.${op}${view} (i32.const 0) (${type}.const 1)))
     56 			 (export "" (func 0)))`;
     57 
     58     assertEq(valText(text(shared)), true);
     59        }
     60    }
     61 
     62    for (let type of ['i32', 'i64']) {
     63        let text = (shared) => `(module (memory 1 1 ${shared})
     64 		     (func (result i32) (memory.atomic.wait${type.slice(1)} (i32.const 0) (${type}.const 1) (i64.const -1)))
     65 		     (export "" (func 0)))`;
     66        assertEq(valText(text(shared)), true);
     67    }
     68 
     69    let text = (shared) => `(module (memory 1 1 ${shared})
     70      (func (result i32) \(memory.atomic.notify (i32.const 0) (i32.const 1)))
     71      (export "" (func 0)))`;
     72    assertEq(valText(text(shared)), true);
     73 
     74    // Required explicit alignment for WAIT is the size of the datum
     75 
     76    for (let [type,align,good] of [['i32',1,false],['i32',2,false],['i32',4,true],['i32',8,false],
     77 		           ['i64',1,false],['i64',2,false],['i64',4,false],['i64',8,true]])
     78    {
     79        let text = `(module (memory 1 1 shared)
     80 	 (func (result i32) (memory.atomic.wait${type.slice(1)} align=${align} (i32.const 0) (${type}.const 1) (i64.const -1)))
     81 	 (export "" (func 0)))`;
     82        assertEq(valText(text), good);
     83    }
     84 
     85    // Required explicit alignment for NOTIFY is 4
     86 
     87    for (let align of [1, 2, 4, 8]) {
     88        let text = `(module (memory 1 1 shared)
     89 	 (func (result i32) \(memory.atomic.notify align=${align} (i32.const 0) (i32.const 1)))
     90 	 (export "" (func 0)))`;
     91        assertEq(valText(text), align == 4);
     92    }
     93 }
     94 
     95 // Test that atomic operations work.
     96 
     97 function I64(hi, lo) {
     98    this.high = hi;
     99    this.low = lo;
    100 }
    101 I64.prototype.toString = function () {
    102    return "(" + this.high.toString(16) + " " + this.low.toString(16) + ")";
    103 }
    104 
    105 function Uint64Array(arg) {
    106    let buffer = arg;
    107    if (typeof arg == "number")
    108 buffer = new ArrayBuffer(arg*8);
    109    this.buf = buffer;
    110    this.elem = new Uint32Array(buffer);
    111 }
    112 
    113 Uint64Array.BYTES_PER_ELEMENT = 8;
    114 
    115 Uint8Array.prototype.read = function (n) { return this[n] }
    116 Uint16Array.prototype.read = function (n) { return this[n] }
    117 Uint32Array.prototype.read = function (n) { return this[n] }
    118 Uint64Array.prototype.read = function (n) {
    119    return new I64(this.elem[n*2+1], this.elem[n*2]);
    120 }
    121 
    122 Uint8Array.prototype.write = function (n,v) { this[n] = v }
    123 Uint16Array.prototype.write = function (n,v) { this[n] = v }
    124 Uint32Array.prototype.write = function (n,v) { this[n] = v}
    125 Uint64Array.prototype.write = function (n,v) {
    126    if (typeof v == "number") {
    127 // Note, this chops v if v is too large
    128 this.elem[n*2] = v;
    129 this.elem[n*2+1] = 0;
    130    } else {
    131 this.elem[n*2] = v.low;
    132 this.elem[n*2+1] = v.high;
    133    }
    134 }
    135 
    136 // Widen a one-byte value to a k-byte value where k is TA's width.
    137 // Complementation leads to better error checking, probably.
    138 
    139 function widen(TA, value, complement = true) {
    140    let n = value;
    141    let s = "";
    142    for ( let i=0; i < Math.min(TA.BYTES_PER_ELEMENT, 4); i++ ) {
    143 let v = (256 + n).toString(16);
    144 s = s + v.substring(v.length-2);
    145 if (complement)
    146     n = ~n;
    147    }
    148    if (TA.BYTES_PER_ELEMENT == 8)
    149 s = s + s;
    150    s = "0x" + s;
    151 
    152    n = value;
    153    let num = 0;
    154    for ( let i=0; i < Math.min(TA.BYTES_PER_ELEMENT, 4); i++ ) {
    155 num = (num << 8) | (n & 255);
    156 if (complement)
    157     n = ~n;
    158    }
    159    num = num >>> 0;
    160 
    161    if (TA.BYTES_PER_ELEMENT == 8) {
    162 return [s, new I64(num, num)];
    163    } else {
    164 return [s, num];
    165    }
    166 }
    167 
    168 // Atomic RMW ops are sometimes used for effect, sometimes for their value, and
    169 // in SpiderMonkey code generation differs for the two cases, so we need to test
    170 // both.  Also, there may be different paths for constant addresses/operands and
    171 // variable ditto, so test as many combinations as possible.
    172 
    173 for ( let shared of ['shared',''] ) {
    174    let RMWOperation = {
    175        loadStoreModule(type, width, view, address, operand) {
    176     let bin = wasmTextToBinary(
    177         `(module
    178       (memory (import "" "memory") 1 1 ${shared})
    179       (func (export "st") (param i32)
    180        (${type}.atomic.store${width} ${address} ${operand}))
    181       (func $ld (param i32) (result ${type})
    182        (${type}.atomic.load${width}${view} ${address}))
    183       (func (export "ld") (param i32) (result i32)
    184        (${type}.eq (call $ld (local.get 0)) ${operand})))`);
    185     let mod = new WebAssembly.Module(bin);
    186     let mem = new WebAssembly.Memory({initial: 1, maximum: 1, shared});
    187     let ins = new WebAssembly.Instance(mod, {"": {memory: mem}});
    188     return [mem, ins.exports.ld, ins.exports.st];
    189        },
    190 
    191        opModuleEffect(type, width, view, address, op, operand, ignored) {
    192     let bin = wasmTextToBinary(
    193         `(module
    194       (memory (import "" "memory") 1 1 ${shared})
    195       (func (export "f") (param i32) (result i32)
    196        (drop (${type}.atomic.rmw${width}.${op}${view} ${address} ${operand}))
    197        (i32.const 1)))`);
    198     let mod = new WebAssembly.Module(bin);
    199     let mem = new WebAssembly.Memory({initial: 1, maximum: 1, shared});
    200     let ins = new WebAssembly.Instance(mod, {"": {memory: mem}});
    201     return [mem, ins.exports.f];
    202        },
    203 
    204        opModuleReturned(type, width, view, address, op, operand, expected) {
    205     let bin = wasmTextToBinary(
    206         `(module
    207       (memory (import "" "memory") 1 1 ${shared})
    208       (func $_f (param i32) (result ${type})
    209        (${type}.atomic.rmw${width}.${op}${view} ${address} ${operand}))
    210       (func (export "f") (param i32) (result i32)
    211        (${type}.eq (call $_f (local.get 0)) (${type}.const ${expected}))))`);
    212     let mod = new WebAssembly.Module(bin);
    213     let mem = new WebAssembly.Memory({initial: 1, maximum: 1, shared});
    214     let ins = new WebAssembly.Instance(mod, {"": {memory: mem}});
    215     return [mem, ins.exports.f];
    216        },
    217 
    218        cmpxchgModuleEffect(type, width, view, address, operand1, operand2, ignored) {
    219     let bin = wasmTextToBinary(
    220         `(module
    221       (memory (import "" "memory") 1 1 ${shared})
    222       (func (export "f") (param i32)
    223        (drop (${type}.atomic.rmw${width}.cmpxchg${view} ${address} ${operand1} ${operand2}))))`);
    224     let mod = new WebAssembly.Module(bin);
    225     let mem = new WebAssembly.Memory({initial: 1, maximum: 1, shared});
    226     let ins = new WebAssembly.Instance(mod, {"": {memory: mem}});
    227     return [mem, ins.exports.f];
    228        },
    229 
    230        cmpxchgModuleReturned(type, width, view, address, operand1, operand2, expected) {
    231     let bin = wasmTextToBinary(
    232         `(module
    233       (memory (import "" "memory") 1 1 ${shared})
    234       (func $_f (param i32) (result ${type})
    235        (${type}.atomic.rmw${width}.cmpxchg${view} ${address} ${operand1} ${operand2}))
    236       (func (export "f") (param i32) (result i32)
    237        (${type}.eq (call $_f (local.get 0)) (${type}.const ${expected}))))`);
    238     let mod = new WebAssembly.Module(bin);
    239     let mem = new WebAssembly.Memory({initial: 1, maximum: 1, shared});
    240     let ins = new WebAssembly.Instance(mod, {"": {memory: mem}});
    241     return [mem, ins.exports.f];
    242        },
    243 
    244        assertZero(array, LOC) {
    245     for ( let i=0 ; i < 100 ; i++ ) {
    246         if (i != LOC)
    247 	    assertNum(array.read(i), 0);
    248     }
    249        },
    250 
    251        run() {
    252     const LOC = 13;		// The cell we operate on
    253     const OPD1 = 37;	// Sometimes we'll put an operand here
    254     const OPD2 = 42;	// Sometimes we'll put another operand here
    255 
    256     for ( let [type, variations] of
    257           [["i32", [[Uint8Array,"8", "_u"], [Uint16Array,"16", "_u"], [Uint32Array,"",""]]],
    258 	   ["i64", [[Uint8Array,"8","_u"], [Uint16Array,"16","_u"], [Uint32Array,"32","_u"], [Uint64Array,"",""]]]] )
    259     {
    260         for ( let [TA, width, view] of variations )
    261         {
    262 	    for ( let addr of [`(i32.const ${LOC * TA.BYTES_PER_ELEMENT})`,
    263 			       `(local.get 0)`] )
    264 	    {
    265 	        for ( let [initial, operand] of [[0x12, 0x37]] )
    266 	        {
    267 		    let [opd_str, opd_num] = widen(TA, operand);
    268 		    for ( let rhs of [`(${type}.const ${opd_str})`,
    269 				      `(${type}.load${width}${view} (i32.const ${OPD1 * TA.BYTES_PER_ELEMENT}))`] )
    270 		    {
    271 		        let [mem, ld, st] = this.loadStoreModule(type, width, view, addr, rhs);
    272 		        let array = new TA(mem.buffer);
    273 		        array.write(OPD1, opd_num);
    274 		        array.write(LOC, initial);
    275 		        st(LOC * TA.BYTES_PER_ELEMENT);
    276 		        let res = ld(LOC * TA.BYTES_PER_ELEMENT);
    277 		        assertEq(res, 1);
    278 		        assertNum(array.read(LOC), opd_num);
    279 		        array.write(OPD1, 0);
    280 		        this.assertZero(array, LOC);
    281 		    }
    282 	        }
    283 
    284 	        for ( let [op, initial, operand, expected] of [["add", 37, 5, 42],
    285 							       ["sub", 42, 5, 37],
    286 							       ["and", 0x45, 0x13, 0x01],
    287 							       ["or",  0x45, 0x13, 0x57],
    288 							       ["xor", 0x45, 0x13, 0x56],
    289 							       ["xchg", 0x45, 0x13, 0x13]] )
    290 	        {
    291 		    let complement = op == "xchg";
    292 		    let [ini_str, ini_num] = widen(TA, initial, complement);
    293 		    let [opd_str, opd_num] = widen(TA, operand, complement);
    294 		    let [exp_str, exp_num] = widen(TA, expected, complement);
    295 		    for ( let rhs of [`(${type}.const ${opd_str})`,
    296 				      `(${type}.load${width}${view} (i32.const ${OPD1 * TA.BYTES_PER_ELEMENT}))`] )
    297 		    {
    298 		        for ( let [generateIt, checkIt] of [["opModuleEffect", false], ["opModuleReturned", true]] )
    299 		        {
    300 			    let [mem, f] = this[generateIt](type, width, view, addr, op, rhs, ini_str);
    301 			    let array = new TA(mem.buffer);
    302 			    array.write(OPD1, opd_num);
    303 			    array.write(LOC, ini_num);
    304 			    let res = f(LOC * TA.BYTES_PER_ELEMENT);
    305 			    if (checkIt)
    306 			        assertEq(res, 1);
    307 			    assertNum(array.read(LOC), exp_num);
    308 			    array.write(OPD1, 0);
    309 			    this.assertZero(array, LOC);
    310 		        }
    311 		    }
    312 	        }
    313 
    314 	        for ( let [initial, operand1, operand2, expected] of [[33, 33, 44, 44], [33, 44, 55, 33]] )
    315 	        {
    316 		    let [ini_str, ini_num] = widen(TA, initial);
    317 		    let [opd1_str, opd1_num] = widen(TA, operand1);
    318 		    let [opd2_str, opd2_num] = widen(TA, operand2);
    319 		    let [exp_str, exp_num] = widen(TA, expected);
    320 		    for ( let op1 of [`(${type}.const ${opd1_str})`,
    321 				      `(${type}.load${width}${view} (i32.const ${OPD1 * TA.BYTES_PER_ELEMENT}))`] )
    322 		    {
    323 		        for ( let op2 of [`(${type}.const ${opd2_str})`,
    324 				          `(${type}.load${width}${view} (i32.const ${OPD2 * TA.BYTES_PER_ELEMENT}))`] )
    325 		        {
    326 			    for ( let [generateIt, checkIt] of [["cmpxchgModuleEffect", false], ["cmpxchgModuleReturned", true]] )
    327 			    {
    328 			        let [mem, f] = this[generateIt](type, width, view, addr, op1, op2, ini_str);
    329 			        let array = new TA(mem.buffer);
    330 			        array.write(OPD1, opd1_num);
    331 			        array.write(OPD2, opd2_num);
    332 			        array.write(LOC, ini_num);
    333 			        let res = f(LOC * TA.BYTES_PER_ELEMENT);
    334 			        if (checkIt)
    335 				    assertEq(res, 1);
    336 			        assertNum(array.read(13), exp_num);
    337 			        array.write(OPD1, 0);
    338 			        array.write(OPD2, 0);
    339 			        this.assertZero(array, LOC);
    340 			    }
    341 		        }
    342 		    }
    343 	        }
    344 	    }
    345         }
    346     }
    347        }
    348    };
    349 
    350    RMWOperation.run();
    351 }
    352 
    353 // Test bounds and alignment checking on atomic ops
    354 
    355 for ( let shared of ['shared',''] ) {
    356    var BoundsAndAlignment = {
    357        loadModule(type, view, width, offset) {
    358     return wasmEvalText(
    359         `(module
    360       (memory 1 1 ${shared})
    361       (func $0 (param i32) (result ${type})
    362        (${type}.atomic.load${width}${view} offset=${offset} (local.get 0)))
    363       (func (export "f") (param i32)
    364        (drop (call $0 (local.get 0)))))
    365     `).exports.f;
    366        },
    367 
    368        loadModuleIgnored(type, view, width, offset) {
    369     return wasmEvalText(
    370         `(module
    371       (memory 1 1 ${shared})
    372       (func (export "f") (param i32)
    373        (drop (${type}.atomic.load${width}${view} offset=${offset} (local.get 0)))))
    374     `).exports.f;
    375        },
    376 
    377        storeModule(type, view, width, offset) {
    378     return wasmEvalText(
    379         `(module
    380       (memory 1 1 ${shared})
    381       (func (export "f") (param i32)
    382        (${type}.atomic.store${width} offset=${offset} (local.get 0) (${type}.const 37))))
    383     `).exports.f;
    384        },
    385 
    386        opModule(type, view, width, offset, op) {
    387     return wasmEvalText(
    388         `(module
    389       (memory 1 1 ${shared})
    390       (func $0 (param i32) (result ${type})
    391        (${type}.atomic.rmw${width}.${op}${view} offset=${offset} (local.get 0) (${type}.const 37)))
    392       (func (export "f") (param i32)
    393        (drop (call $0 (local.get 0)))))
    394     `).exports.f;
    395        },
    396 
    397        opModuleForEffect(type, view, width, offset, op) {
    398     return wasmEvalText(
    399         `(module
    400       (memory 1 1 ${shared})
    401       (func (export "f") (param i32)
    402        (drop (${type}.atomic.rmw${width}.${op}${view} offset=${offset} (local.get 0) (${type}.const 37)))))
    403     `).exports.f;
    404        },
    405 
    406        cmpxchgModule(type, view, width, offset) {
    407     return wasmEvalText(
    408         `(module
    409       (memory 1 1 ${shared})
    410       (func $0 (param i32) (result ${type})
    411        (${type}.atomic.rmw${width}.cmpxchg${view} offset=${offset} (local.get 0) (${type}.const 37) (${type}.const 42)))
    412       (func (export "f") (param i32)
    413        (drop (call $0 (local.get 0)))))
    414     `).exports.f;
    415        },
    416 
    417        run() {
    418     for ( let [type, variations] of [["i32", [["8","_u", 1], ["16","_u", 2], ["","", 4]]],
    419 				     ["i64", [["8","_u",1], ["16","_u",2], ["32","_u",4], ["","",8]]]] )
    420     {
    421         for ( let [width,view,size] of variations )
    422         {
    423 	    // Aligned but out-of-bounds
    424 	    let addrs = [[65536, 0, oob], [65536*2, 0, oob], [65532, 4, oob],
    425 		         [65533, 3, oob], [65534, 2, oob], [65535, 1, oob]];
    426                    if (type == "i64")
    427                        addrs.push([65536-8, 8, oob]);
    428 
    429 	    // In-bounds but unaligned
    430 	    for ( let i=1 ; i < size ; i++ )
    431 	        addrs.push([65520, i, unaligned]);
    432 
    433 	    // Both out-of-bounds and unaligned.  The spec leaves it unspecified
    434 	    // whether we see the OOB message or the unaligned message (they are
    435 	    // both "traps").  In Firefox, the unaligned check comes first.
    436 	    for ( let i=1 ; i < size ; i++ )
    437 	        addrs.push([65536, i, unaligned]);
    438 
    439 	    // GC to prevent TSan builds from running out of memory.
    440 	    gc();
    441 
    442 	    for ( let [ base, offset, re ] of addrs )
    443 	    {
    444 	        assertErrorMessage(() => this.loadModule(type, view, width, offset)(base), RuntimeError, re);
    445 	        assertErrorMessage(() => this.loadModuleIgnored(type, view, width, offset)(base), RuntimeError, re);
    446 	        assertErrorMessage(() => this.storeModule(type, view, width, offset)(base), RuntimeError, re);
    447 	        for ( let op of [ "add", "sub", "and", "or", "xor", "xchg" ]) {
    448 		    assertErrorMessage(() => this.opModule(type, view, width, offset, op)(base), RuntimeError, re);
    449 		    assertErrorMessage(() => this.opModuleForEffect(type, view, width, offset, op)(base), RuntimeError, re);
    450 	        }
    451 	        assertErrorMessage(() => this.cmpxchgModule(type, view, width, offset)(base), RuntimeError, re);
    452 	    }
    453         }
    454     }
    455        }
    456    }
    457 
    458    BoundsAndAlignment.run();
    459 }
    460 
    461 // Bounds and alignment checks on wait and notify
    462 
    463 // For 'wait', we check bounds and alignment after sharedness, so the memory
    464 // must be shared always.
    465 
    466 assertErrorMessage(() => wasmEvalText(`(module (memory 1 1 shared)
    467 				(func (param i32) (result i32)
    468 				 (memory.atomic.wait32 (local.get 0) (i32.const 1) (i64.const -1)))
    469 				(export "" (func 0)))`).exports[""](65536),
    470 	   RuntimeError, oob);
    471 
    472 assertErrorMessage(() => wasmEvalText(`(module (memory 1 1 shared)
    473 				(func (param i32) (result i32)
    474 				 (memory.atomic.wait64 (local.get 0) (i64.const 1) (i64.const -1)))
    475 				(export "" (func 0)))`).exports[""](65536),
    476 	   RuntimeError, oob);
    477 
    478 assertErrorMessage(() => wasmEvalText(`(module (memory 1 1 shared)
    479 				(func (param i32) (result i32)
    480 				 (memory.atomic.wait32 (local.get 0) (i32.const 1) (i64.const -1)))
    481 				(export "" (func 0)))`).exports[""](65501),
    482 	   RuntimeError, unaligned);
    483 
    484 assertErrorMessage(() => wasmEvalText(`(module (memory 1 1 shared)
    485 				(func (param i32) (result i32)
    486 				 (memory.atomic.wait64 (local.get 0) (i64.const 1) (i64.const -1)))
    487 				(export "" (func 0)))`).exports[""](65501),
    488 	   RuntimeError, unaligned);
    489 
    490 // For 'notify', we check bounds and alignment before returning 0 in the case of
    491 // non-shared memory, so both shared and non-shared memories must be checked.
    492 
    493 for ( let shared of ['shared',''] ) {
    494    assertErrorMessage(() => wasmEvalText(`(module (memory 1 1 ${shared})
    495 				(func (param i32) (result i32)
    496 				 \(memory.atomic.notify (local.get 0) (i32.const 1)))
    497 				(export "" (func 0)))`).exports[""](65536),
    498 	   RuntimeError, oob);
    499 
    500    // Minimum run-time alignment for NOTIFY is 4
    501    for (let addr of [1,2,3,5,6,7]) {
    502        assertErrorMessage(() => wasmEvalText(`(module (memory 1 1 ${shared})
    503 				    (func (export "f") (param i32) (result i32)
    504 				     \(memory.atomic.notify (local.get 0) (i32.const 1))))`).exports.f(addr),
    505 	           RuntimeError, unaligned);
    506    }
    507 }
    508 
    509 // Sharedness check for wait
    510 
    511 assertErrorMessage(() => wasmEvalText(`(module (memory 1 1)
    512 				(func (param i32) (result i32)
    513 				 (memory.atomic.wait32 (local.get 0) (i32.const 1) (i64.const -1)))
    514 				(export "" (func 0)))`).exports[""](0),
    515 	   RuntimeError, /atomic wait on non-shared memory/);
    516 
    517 // Ensure that notify works on non-shared memories and returns zero.
    518 
    519 assertEq(wasmEvalText(`
    520 (module (memory 1 1)
    521  (func (export "f") (param i32) (result i32)
    522    \(memory.atomic.notify (local.get 0) (i32.const 1))))
    523 `).exports.f(256), 0);
    524 
    525 // Ensure alias analysis works even if atomic and non-atomic accesses are
    526 // mixed.
    527 assertErrorMessage(() => wasmEvalText(`(module
    528  (memory 0 1 shared)
    529  (func (export "main")
    530    i32.const 1
    531    i32.const 2816
    532    i32.atomic.rmw16.xchg_u align=2
    533    i32.load16_s offset=83 align=1
    534    drop
    535  )
    536 )`).exports.main(), RuntimeError, unaligned);
    537 
    538 // Make sure we can handle wait and notify without memory
    539 
    540 var nomem = /memory index out of range/;
    541 
    542 assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(`
    543 (module
    544  (func (result i32)
    545    \(memory.atomic.notify (i32.const 0) (i32.const 1))))`)),
    546                   WebAssembly.CompileError,
    547                   nomem);
    548 
    549 assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(`
    550 (module
    551  (func (result i32)
    552    (memory.atomic.wait32 (i32.const 0) (i32.const 1) (i64.const -1))))`)),
    553                   WebAssembly.CompileError,
    554                   nomem);
    555 
    556 assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(`
    557 (module
    558  (func (result i32)
    559    (memory.atomic.wait64 (i32.const 0) (i64.const 1) (i64.const -1))))`)),
    560                   WebAssembly.CompileError,
    561                   nomem);