for (let op of ['add','and','or','sub','xor','xchg']) { // Operate with appropriately-typed value 1 on address 0
let text = (shared) => `(module (memory 1 1 ${shared})
(func (result ${type}) (${type}.atomic.rmw${width}.${op}${view} (i32.const 0) (${type}.const 1)))
(export "" (func 0)))`;
assertEq(valText(text(shared)), true);
}
}
for (let type of ['i32', 'i64']) {
let text = (shared) => `(module (memory 1 1 ${shared})
(func (result i32) (memory.atomic.wait${type.slice(1)} (i32.const 0) (${type}.const 1) (i64.const -1)))
(export "" (func 0)))`;
assertEq(valText(text(shared)), true);
}
function Uint64Array(arg) {
let buffer = arg; if (typeof arg == "number")
buffer = new ArrayBuffer(arg*8); this.buf = buffer; this.elem = new Uint32Array(buffer);
}
Uint64Array.BYTES_PER_ELEMENT = 8;
Uint8Array.prototype.read = function (n) { returnthis[n] }
Uint16Array.prototype.read = function (n) { returnthis[n] }
Uint32Array.prototype.read = function (n) { returnthis[n] }
Uint64Array.prototype.read = function (n) { returnnew I64(this.elem[n*2+1], this.elem[n*2]);
}
Uint8Array.prototype.write = function (n,v) { this[n] = v }
Uint16Array.prototype.write = function (n,v) { this[n] = v }
Uint32Array.prototype.write = function (n,v) { this[n] = v}
Uint64Array.prototype.write = function (n,v) { if (typeof v == "number") { // Note, this chops v if v is too large this.elem[n*2] = v; this.elem[n*2+1] = 0;
} else { this.elem[n*2] = v.low; this.elem[n*2+1] = v.high;
}
}
// Widen a one-byte value to a k-byte value where k is TA's width. // Complementation leads to better error checking, probably.
function widen(TA, value, complement = true) {
let n = value;
let s = ""; for ( let i=0; i < Math.min(TA.BYTES_PER_ELEMENT, 4); i++ ) {
let v = (256 + n).toString(16);
s = s + v.substring(v.length-2); if (complement)
n = ~n;
} if (TA.BYTES_PER_ELEMENT == 8)
s = s + s;
s = "0x" + s;
n = value;
let num = 0; for ( let i=0; i < Math.min(TA.BYTES_PER_ELEMENT, 4); i++ ) {
num = (num << 8) | (n & 255); if (complement)
n = ~n;
}
num = num >>> 0;
if (TA.BYTES_PER_ELEMENT == 8) { return [s, new I64(num, num)];
} else { return [s, num];
}
}
// Atomic RMW ops are sometimes used for effect, sometimes for their value, and // in SpiderMonkey code generation differs for the two cases, so we need to test // both. Also, there may be different paths for constant addresses/operands and // variable ditto, so test as many combinations as possible.
for ( let shared of ['shared',''] ) {
let RMWOperation = {
loadStoreModule(type, width, view, address, operand) {
let bin = wasmTextToBinary(
`(module
(memory (import"""memory") 1 1 ${shared})
(func (export "st") (param i32)
(${type}.atomic.store${width} ${address} ${operand}))
(func $ld (param i32) (result ${type})
(${type}.atomic.load${width}${view} ${address}))
(func (export "ld") (param i32) (result i32)
(${type}.eq (call $ld (local.get 0)) ${operand})))`);
let mod = new WebAssembly.Module(bin);
let mem = new WebAssembly.Memory({initial: 1, maximum: 1, shared});
let ins = new WebAssembly.Instance(mod, {"": {memory: mem}}); return [mem, ins.exports.ld, ins.exports.st];
},
opModuleEffect(type, width, view, address, op, operand, ignored) {
let bin = wasmTextToBinary(
`(module
(memory (import"""memory") 1 1 ${shared})
(func (export "f") (param i32) (result i32)
(drop (${type}.atomic.rmw${width}.${op}${view} ${address} ${operand}))
(i32.const 1)))`);
let mod = new WebAssembly.Module(bin);
let mem = new WebAssembly.Memory({initial: 1, maximum: 1, shared});
let ins = new WebAssembly.Instance(mod, {"": {memory: mem}}); return [mem, ins.exports.f];
},
opModuleReturned(type, width, view, address, op, operand, expected) {
let bin = wasmTextToBinary(
`(module
(memory (import"""memory") 1 1 ${shared})
(func $_f (param i32) (result ${type})
(${type}.atomic.rmw${width}.${op}${view} ${address} ${operand}))
(func (export "f") (param i32) (result i32)
(${type}.eq (call $_f (local.get 0)) (${type}.const ${expected}))))`);
let mod = new WebAssembly.Module(bin);
let mem = new WebAssembly.Memory({initial: 1, maximum: 1, shared});
let ins = new WebAssembly.Instance(mod, {"": {memory: mem}}); return [mem, ins.exports.f];
},
cmpxchgModuleEffect(type, width, view, address, operand1, operand2, ignored) {
let bin = wasmTextToBinary(
`(module
(memory (import"""memory") 1 1 ${shared})
(func (export "f") (param i32)
(drop (${type}.atomic.rmw${width}.cmpxchg${view} ${address} ${operand1} ${operand2}))))`);
let mod = new WebAssembly.Module(bin);
let mem = new WebAssembly.Memory({initial: 1, maximum: 1, shared});
let ins = new WebAssembly.Instance(mod, {"": {memory: mem}}); return [mem, ins.exports.f];
},
cmpxchgModuleReturned(type, width, view, address, operand1, operand2, expected) {
let bin = wasmTextToBinary(
`(module
(memory (import"""memory") 1 1 ${shared})
(func $_f (param i32) (result ${type})
(${type}.atomic.rmw${width}.cmpxchg${view} ${address} ${operand1} ${operand2}))
(func (export "f") (param i32) (result i32)
(${type}.eq (call $_f (local.get 0)) (${type}.const ${expected}))))`);
let mod = new WebAssembly.Module(bin);
let mem = new WebAssembly.Memory({initial: 1, maximum: 1, shared});
let ins = new WebAssembly.Instance(mod, {"": {memory: mem}}); return [mem, ins.exports.f];
},
assertZero(array, LOC) { for ( let i=0 ; i < 100 ; i++ ) { if (i != LOC)
assertNum(array.read(i), 0);
}
},
run() { const LOC = 13; // The cell we operate on const OPD1 = 37; // Sometimes we'll put an operand here const OPD2 = 42; // Sometimes we'll put another operand here
for ( let [type, variations] of
[["i32", [[Uint8Array,"8", "_u"], [Uint16Array,"16", "_u"], [Uint32Array,"",""]]],
["i64", [[Uint8Array,"8","_u"], [Uint16Array,"16","_u"], [Uint32Array,"32","_u"], [Uint64Array,"",""]]]] )
{ for ( let [TA, width, view] of variations )
{ for ( let addr of [`(i32.const ${LOC * TA.BYTES_PER_ELEMENT})`,
`(local.get 0)`] )
{ for ( let [initial, operand] of [[0x12, 0x37]] )
{
let [opd_str, opd_num] = widen(TA, operand); for ( let rhs of [`(${type}.const ${opd_str})`,
`(${type}.load${width}${view} (i32.const ${OPD1 * TA.BYTES_PER_ELEMENT}))`] )
{
let [mem, ld, st] = this.loadStoreModule(type, width, view, addr, rhs);
let array = new TA(mem.buffer);
array.write(OPD1, opd_num);
array.write(LOC, initial);
st(LOC * TA.BYTES_PER_ELEMENT);
let res = ld(LOC * TA.BYTES_PER_ELEMENT);
assertEq(res, 1);
assertNum(array.read(LOC), opd_num);
array.write(OPD1, 0); this.assertZero(array, LOC);
}
}
for ( let [op, initial, operand, expected] of [["add", 37, 5, 42],
["sub", 42, 5, 37],
["and", 0x45, 0x13, 0x01],
["or", 0x45, 0x13, 0x57],
["xor", 0x45, 0x13, 0x56],
["xchg", 0x45, 0x13, 0x13]] )
{
let complement = op == "xchg";
let [ini_str, ini_num] = widen(TA, initial, complement);
let [opd_str, opd_num] = widen(TA, operand, complement);
let [exp_str, exp_num] = widen(TA, expected, complement); for ( let rhs of [`(${type}.const ${opd_str})`,
`(${type}.load${width}${view} (i32.const ${OPD1 * TA.BYTES_PER_ELEMENT}))`] )
{ for ( let [generateIt, checkIt] of [["opModuleEffect", false], ["opModuleReturned", true]] )
{
let [mem, f] = this[generateIt](type, width, view, addr, op, rhs, ini_str);
let array = new TA(mem.buffer);
array.write(OPD1, opd_num);
array.write(LOC, ini_num);
let res = f(LOC * TA.BYTES_PER_ELEMENT); if (checkIt)
assertEq(res, 1);
assertNum(array.read(LOC), exp_num);
array.write(OPD1, 0); this.assertZero(array, LOC);
}
}
}
for ( let [initial, operand1, operand2, expected] of [[33, 33, 44, 44], [33, 44, 55, 33]] )
{
let [ini_str, ini_num] = widen(TA, initial);
let [opd1_str, opd1_num] = widen(TA, operand1);
let [opd2_str, opd2_num] = widen(TA, operand2);
let [exp_str, exp_num] = widen(TA, expected); for ( let op1 of [`(${type}.const ${opd1_str})`,
`(${type}.load${width}${view} (i32.const ${OPD1 * TA.BYTES_PER_ELEMENT}))`] )
{ for ( let op2 of [`(${type}.const ${opd2_str})`,
`(${type}.load${width}${view} (i32.const ${OPD2 * TA.BYTES_PER_ELEMENT}))`] )
{ for ( let [generateIt, checkIt] of [["cmpxchgModuleEffect", false], ["cmpxchgModuleReturned", true]] )
{
let [mem, f] = this[generateIt](type, width, view, addr, op1, op2, ini_str);
let array = new TA(mem.buffer);
array.write(OPD1, opd1_num);
array.write(OPD2, opd2_num);
array.write(LOC, ini_num);
let res = f(LOC * TA.BYTES_PER_ELEMENT); if (checkIt)
assertEq(res, 1);
assertNum(array.read(13), exp_num);
array.write(OPD1, 0);
array.write(OPD2, 0); this.assertZero(array, LOC);
}
}
}
}
}
}
}
}
};
RMWOperation.run();
}
// Test bounds and alignment checking on atomic ops
run() { for ( let [type, variations] of [["i32", [["8","_u", 1], ["16","_u", 2], ["","", 4]]],
["i64", [["8","_u",1], ["16","_u",2], ["32","_u",4], ["","",8]]]] )
{ for ( let [width,view,size] of variations )
{ // Aligned but out-of-bounds
let addrs = [[65536, 0, oob], [65536*2, 0, oob], [65532, 4, oob],
[65533, 3, oob], [65534, 2, oob], [65535, 1, oob]]; if (type == "i64")
addrs.push([65536-8, 8, oob]);
// In-bounds but unaligned for ( let i=1 ; i < size ; i++ )
addrs.push([65520, i, unaligned]);
// Both out-of-bounds and unaligned. The spec leaves it unspecified // whether we see the OOB message or the unaligned message (they are // both "traps"). In Firefox, the unaligned check comes first. for ( let i=1 ; i < size ; i++ )
addrs.push([65536, i, unaligned]);
// GC to prevent TSan builds from running out of memory.
gc();
// For 'notify', we check bounds and alignment before returning 0 in the case of // non-shared memory, so both shared and non-shared memories must be checked.
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.