// Test that wasm atomic operations implement correct mutual exclusion. // // We have several agents that attempt to hammer on a shared location with rmw // operations in such a way that failed atomicity will lead to an incorrect // result. Each agent attempts to clear or set specific bits in a shared datum.
// 1 for a little bit, 2 for a lot, 3 to quit before running tests const DEBUG = 0;
// The longer we run, the better, really, but we don't want to time out. const ITERATIONS = 100000;
// If you change NUMWORKERS you must also change the tables for INIT, VAL, and // RESULT for all the operations, below, by adding or removing bits. const NUMWORKERS = 2; const NUMAGENTS = NUMWORKERS + 1;
// Need at least one thread per agent.
if (!wasmThreadsEnabled() || helperThreadCount() < NUMWORKERS) { if (DEBUG > 0)
print("Threads not supported");
quit(0);
}
// Unless there are enough actual cores the spinning threads will not interact // in the desired way (we'll be waiting on preemption to advance), and this // makes the test pointless and also will usually make it time out. So bail out // if we can't have one core per agent.
if (getCoreCount() < NUMAGENTS) { if (DEBUG > 0)
print("Fake or feeble hardware");
quit(0);
}
// Most of the simulators have poor support for mutual exclusion and are anyway // too slow; avoid intermittent failures and timeouts.
if (getBuildConfiguration("arm-simulator") || getBuildConfiguration("arm64-simulator") ||
getBuildConfiguration("mips32-simulator") || getBuildConfiguration("mips64-simulator"))
{ if (DEBUG > 0)
print("Atomicity test disabled on simulator");
quit(0);
}
//////////////////////////////////////////////////////////////////////// // // Coordination code for bootstrapping workers - use spawn() to create a worker, // send() to send an item to a worker. send() will send to anyone, so only one // worker should be receiving at a time. spawn() will block until the worker is // running; send() will block until the message has been received.
var COORD_BUSY = 0; var COORD_NUMLOC = 1;
var coord = new Int32Array(new SharedArrayBuffer(COORD_NUMLOC*4));
function spawn(text) {
text = ` var _coord = new Int32Array(getSharedObject());
Atomics.store(_coord, ${COORD_BUSY}, 0); function receive() { while (!Atomics.load(_coord, ${COORD_BUSY}))
;
let x = getSharedObject();
Atomics.store(_coord, ${COORD_BUSY}, 0); return x;
}
` + text;
setSharedObject(coord.buffer);
Atomics.store(coord, COORD_BUSY, 1);
evalInWorker(text); while (Atomics.load(coord, COORD_BUSY))
;
}
///////////////////////////////////////////////////////////////////////////////// // // The "agents" comprise one master and one or more additional workers. We make // a separate module for each agent so that test values can be inlined as // constants. // // The master initially sets a shared location LOC to a value START. // // Each agent then operates atomically on LOC with an operation OP and a value // VAL. The operation OP is the same for all agents but each agent `i` has a // different VAL_i. // // To make this more interesting, the value START is distributed as many times // through the value at LOC as there is space for, and we perform several // operations back-to-back, with the VAL_i appropriately shifted. // // Each agent then spin-waits for LOC to contain a particular RESULT, which is // always (START OP VAL_0 OP VAL_1 ... VAL_k), again repeated throughout the // RESULT as appropriate. // // The process then starts over, and we repeat the process many times. If we // fail to have atomicity at any point the program will hang (LOC will never // attain the desired value) and the test should therefore time out. // // (Barriers are needed to make this all work out.) // // The general principle for the values is that each VAL should add (or clear) a // bit of the stored value. // // OP START VAL0 VAL1 VAL2 RESULT // // ADD[*] 0 1 2 4 7 // SUB 7 1 2 4 0 // AND 7 3 6 5 0 // OR 0 1 2 4 7 // XOR 0 1 2 4 7 // or start with 7 and end with 0 // CMPXCHG 0 1 2 4 7 // use nonatomic "or" to add the bit // // [*] Running the tests actually assumes that ADD works reasonably well. // // TODO - more variants we could test: // // - tests that do not drop the values of the atomic ops but accumulate them: // uses different code generation on x86/x64 // // - Xchg needs a different method, since here the atomic thing is that we read // the "previous value" and set the next value atomically. How can we observe // that that fails? If we run three agents, which all set the value to X, // X+1, ..., X+n, with the initial value being (say) X-1, each can record the // value it observed in a table, and we should be able to predict the counts // in that table once postprocessed. eg, the counts should all be the same. // If atomicity fails then a value is read twice when it shouldn't be, and // some other value is not read at all, and the counts will be off. // // - the different rmw operations can usually be combined so that we can test // the atomicity of operations that may be implemented differently. // // - the same tests, with test values as variables instead of constants.
function makeModule(id) {
let isMaster = id == 0;
let VALSHIFT = NUMAGENTS; // 1 bit per agent
function makeLoop(bits, name, op, loc, initial, val, expected) { // Exclude high bit to avoid messing with the sign.
let NUMVALS32 = Math.floor(31/VALSHIFT);
let NUMVALS = bits == 64 ? 2 * NUMVALS32 : Math.floor(Math.min(bits,31)/VALSHIFT);
let BARRIER = "(i32.const 0)";
let barrier = `
;; Barrier
(local.set $barrierValue (i32.add (local.get $barrierValue) (i32.const ${NUMAGENTS})))
(drop (i32.atomic.rmw.add ${BARRIER} (i32.const 1)))
(loop $c1
(if (i32.lt_s (i32.atomic.load ${BARRIER}) (local.get $barrierValue))
(then (br $c1))))
;; End barrier
`;
// Distribute a value `v` across a word NUMVALs times
function distribute(v) { if (bits <= 32) return'0x' + dist32(v); return'0x' + dist32(v) + dist32(v);
}
function dist32(v) {
let n = 0; for (let i=0; i < Math.min(NUMVALS, NUMVALS32); i++)
n = n | (v << (i*VALSHIFT));
assertEq(n >= 0, true); return (n + 0x100000000).toString(16).substring(1);
}
// Position a value `v` at position `pos` in a word
${(() => {
let s = `;; Do\n`; for (let i=0; i < NUMVALS; i++) {
let bitval = `(${prefix}.const ${format(val, i)})` // The load must be atomic though it would be better if it were relaxed, // we would avoid fences in that case. if (op.match(/cmpxchg/)) {
s += `(loop $doit
(local.set $tmp (${prefix}.atomic.load${width}${view} ${loc}))
(br_if $doit (i32.eqz
(${prefix}.eq
(local.get $tmp)
(${op} ${loc} (local.get $tmp) (${prefix}.or (local.get $tmp) ${bitval}))))))
`;
} else {
s += `(drop (${op} ${loc} ${bitval}))
`;
}
} return s
})()}
(loop $wait_done
(br_if $wait_done (${prefix}.ne (${prefix}.atomic.load${width}${view} ${loc}) (${prefix}.const ${distribute(expected)}))))
${barrier}
(local.set $n (i32.sub (local.get $n) (i32.const 1)))
(br $outer)))))
(local.get $barrierValue))`;
}
function makeModule2(id) {
let text = makeModule(id); if (DEBUG > 1)
print(text); returnnew WebAssembly.Module(wasmTextToBinary(text));
}
var mods = [];
mods.push(makeModule2(0)); for ( let i=0; i < NUMWORKERS; i++ )
mods.push(makeModule2(i+1)); if (DEBUG > 2)
quit(0); var mem = new WebAssembly.Memory({initial: 1, maximum: 1, shared: true});
function startWorkers() { for ( let i=0; i < NUMWORKERS; i++ ) {
spawn(` var mem = receive(); var mod = receive(); function pr(n) { if (${DEBUG}) print(n); } var ins = new WebAssembly.Instance(mod, {"":{memory: mem, print:pr}}); if (${DEBUG} > 0)
print("Running ${i}");
ins.exports.test();
`);
send(mem);
send(mods[i+1]);
}
}
//////////////////////////////////////////////////////////////////////// // // Main thread code
startWorkers(); function pr(n) { if (DEBUG) print(n); } var ins = new WebAssembly.Instance(mods[0], {"":{memory: mem, print:pr}}); if (DEBUG > 0)
print("Running master");
ins.exports.test();
Messung V0.5
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet)
¤
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.