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);