v8/test/mjsunit/wasm/atomics-stress.js
Stephan Herhut 9c00157351 [wasm] Add stress test for I64 atomics
This test is modelled after the atomics-stress test but supports 64 bit
operands.

Bug: v8:6532
Change-Id: I313b1ade74a58201b3fa097ba5b1515754a685db
Reviewed-on: https://chromium-review.googlesource.com/c/1234414
Commit-Queue: Stephan Herhut <herhut@chromium.org>
Reviewed-by: Andreas Haas <ahaas@chromium.org>
Cr-Commit-Position: refs/heads/master@{#56812}
2018-10-19 10:51:21 +00:00

555 lines
16 KiB
JavaScript

// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --experimental-wasm-threads
// This test might time out if the search space for a sequential
// interleaving becomes to large. However, it should never fail.
// Note that results of this test are flaky by design. While the test is
// deterministic with a fixed seed, bugs may introduce non-determinism.
load("test/mjsunit/wasm/wasm-constants.js");
load("test/mjsunit/wasm/wasm-module-builder.js");
const kDebug = false;
const kSequenceLength = 256;
const kNumberOfWorker = 4;
const kNumberOfSteps = 10000000;
const kFirstOpcodeWithInput = 3;
const kFirstOpcodeWithoutOutput = 3;
const kLastOpcodeWithoutOutput = 5;
const opCodes = [
kExprI32AtomicLoad,
kExprI32AtomicLoad8U,
kExprI32AtomicLoad16U,
kExprI32AtomicStore,
kExprI32AtomicStore8U,
kExprI32AtomicStore16U,
kExprI32AtomicAdd,
kExprI32AtomicAdd8U,
kExprI32AtomicAdd16U,
kExprI32AtomicSub,
kExprI32AtomicSub8U,
kExprI32AtomicSub16U,
kExprI32AtomicAnd,
kExprI32AtomicAnd8U,
kExprI32AtomicAnd16U,
kExprI32AtomicOr,
kExprI32AtomicOr8U,
kExprI32AtomicOr16U,
kExprI32AtomicXor,
kExprI32AtomicXor8U,
kExprI32AtomicXor16U,
kExprI32AtomicExchange,
kExprI32AtomicExchange8U,
kExprI32AtomicExchange16U
];
const opCodeNames = [
"kExprI32AtomicLoad",
"kExprI32AtomicLoad8U",
"kExprI32AtomicLoad16U",
"kExprI32AtomicStore",
"kExprI32AtomicStore8U",
"kExprI32AtomicStore16U",
"kExprI32AtomicAdd",
"kExprI32AtomicAdd8U",
"kExprI32AtomicAdd16U",
"kExprI32AtomicSub",
"kExprI32AtomicSub8U",
"kExprI32AtomicSub16U",
"kExprI32AtomicAnd",
"kExprI32AtomicAnd8U",
"kExprI32AtomicAnd16U",
"kExprI32AtomicOr",
"kExprI32AtomicOr8U",
"kExprI32AtomicOr16U",
"kExprI32AtomicXor",
"kExprI32AtomicXor8U",
"kExprI32AtomicXor16U",
"kExprI32AtomicExchange",
"kExprI32AtomicExchange8U",
"kExprI32AtomicExchange16U"
];
class Operation {
constructor(opcode, input, offset) {
this.opcode = opcode != undefined ? opcode : Operation.nextOpcode();
this.size = Operation.opcodeToSize(this.opcode);
this.input = input != undefined ? input : Operation.inputForSize(
this.size);
this.offset = offset != undefined ? offset : Operation.offsetForSize(
this.size);
}
static nextOpcode() {
let random = Math.random();
return Math.floor(random * opCodes.length);
}
static opcodeToSize(opcode) {
// Instructions are ordered in 32, 8, 16 bits size
return [32, 8, 16][opcode % 3];
}
static opcodeToAlignment(opcode) {
// Instructions are ordered in 32, 8, 16 bits size
return [2, 0, 1][opcode % 3];
}
static inputForSize(size) {
let random = Math.random();
// Avoid 32 bit overflow for integer here :(
return Math.floor(random * (1 << (size - 1)) * 2);
}
static offsetForSize(size) {
// Pick an offset in bytes between 0 and 7.
let offset = Math.floor(Math.random() * 8);
// Make sure the offset matches the required alignment by masking out the lower bits.
let size_in_bytes = size / 8;
let mask = ~(size_in_bytes - 1);
return offset & mask;
}
get wasmOpcode() {
// [opcode, alignment, offset]
return [opCodes[this.opcode], Operation.opcodeToAlignment(this.opcode), this.offset];
}
get hasInput() {
return this.opcode >= kFirstOpcodeWithInput;
}
get hasOutput() {
return this.opcode < kFirstOpcodeWithoutOutput || this.opcode >
kLastOpcodeWithoutOutput;
}
truncateResultBits(low, high) {
// Shift the lower part. For offsets greater four it drops out of the visible window.
let shiftedL = this.offset >= 4 ? 0 : low >>> (this.offset * 8);
// The higher part is zero for offset 0, left shifted for [1..3] and right shifted
// for [4..7].
let shiftedH = this.offset == 0 ? 0 :
this.offset >= 4 ? high >>> (this.offset - 4) * 8 : high << ((4 -
this.offset) * 8);
let value = shiftedL | shiftedH;
switch (this.size) {
case 8:
return value & 0xFF;
case 16:
return value & 0xFFFF;
case 32:
return value;
default:
throw "Unexpected size: " + this.size;
}
}
static get builder() {
if (!Operation.__builder) {
let builder = new WasmModuleBuilder();
builder.addMemory(1, 1, 1, false);
builder.exportMemoryAs("mem");
Operation.__builder = builder;
}
return Operation.__builder;
}
static get exports() {
if (!Operation.__instance) {
return {};
}
return Operation.__instance.exports;
}
static get memory() {
return Operation.exports.mem;
}
static set instance(instance) {
Operation.__instance = instance;
}
compute(state) {
let evalFun = Operation.exports[this.key];
if (!evalFun) {
let builder = Operation.builder;
let body = [
// Load address of low 32 bits.
kExprI32Const, 0,
// Load expected value.
kExprGetLocal, 0,
kExprI32StoreMem, 2, 0,
// Load address of high 32 bits.
kExprI32Const, 4,
// Load expected value.
kExprGetLocal, 1,
kExprI32StoreMem, 2, 0,
// Load address of where our window starts.
kExprI32Const, 0,
// Load input if there is one.
...(this.hasInput ? [kExprGetLocal, 2] : []),
// Perform operation.
kAtomicPrefix, ...this.wasmOpcode,
// Drop output if it had any.
...(this.hasOutput ? [kExprDrop] : []),
// Load resulting value.
kExprI32Const, 0,
kExprI32LoadMem, 2, 0,
// Return.
kExprReturn
]
builder.addFunction(this.key, kSig_i_iii)
.addBody(body)
.exportAs(this.key);
// Instantiate module, get function exports.
let module = new WebAssembly.Module(builder.toBuffer());
Operation.instance = new WebAssembly.Instance(module);
evalFun = Operation.exports[this.key];
}
let result = evalFun(state.low, state.high, this.input);
let ta = new Int32Array(Operation.memory.buffer);
if (kDebug) {
print(state.high + ":" + state.low + " " + this.toString() +
" -> " + ta[1] + ":" + ta[0]);
}
if (result != ta[0]) throw "!";
return {
low: ta[0],
high: ta[1]
};
}
toString() {
return opCodeNames[this.opcode] + "[+" + this.offset + "] " + this.input;
}
get key() {
return this.opcode + "-" + this.offset;
}
}
class State {
constructor(low, high, indices, count) {
this.low = low;
this.high = high;
this.indices = indices;
this.count = count;
}
isFinal() {
return (this.count == kNumberOfWorker * kSequenceLength);
}
toString() {
return this.high + ":" + this.low + " @ " + this.indices;
}
}
function makeSequenceOfOperations(size) {
let result = new Array(size);
for (let i = 0; i < size; i++) {
result[i] = new Operation();
}
return result;
}
function toSLeb128(val) {
let result = [];
while (true) {
let v = val & 0x7f;
val = val >> 7;
let msbIsSet = (v & 0x40) || false;
if (((val == 0) && !msbIsSet) || ((val == -1) && msbIsSet)) {
result.push(v);
break;
}
result.push(v | 0x80);
}
return result;
}
function generateFunctionBodyForSequence(sequence) {
// We expect the int32* to perform ops on as arg 0 and
// the int32* for our value log as arg1. Argument 2 gives
// an int32* we use to count down spinning workers.
let body = [];
// Initially, we spin until all workers start running.
if (!kDebug) {
body.push(
// Decrement the wait count.
kExprGetLocal, 2,
kExprI32Const, 1,
kAtomicPrefix, kExprI32AtomicSub, 2, 0,
// Spin until zero.
kExprLoop, kWasmStmt,
kExprGetLocal, 2,
kAtomicPrefix, kExprI32AtomicLoad, 2, 0,
kExprI32Const, 0,
kExprI32GtU,
kExprBrIf, 0,
kExprEnd
);
}
for (let operation of sequence) {
body.push(
// Pre-load address of results sequence pointer for later.
kExprGetLocal, 1,
// Load address where atomic pointers are stored.
kExprGetLocal, 0,
// Load the second argument if it had any.
...(operation.hasInput ? [kExprI32Const, ...toSLeb128(operation
.input)] : []),
// Perform operation
kAtomicPrefix, ...operation.wasmOpcode,
// Generate fake output in needed.
...(operation.hasOutput ? [] : [kExprI32Const, 0]),
// Store read intermediate to sequence.
kExprI32StoreMem, 2, 0,
// Increment result sequence pointer.
kExprGetLocal, 1,
kExprI32Const, 4,
kExprI32Add,
kExprSetLocal, 1
);
}
// Return end of sequence index.
body.push(
kExprGetLocal, 1,
kExprReturn);
return body;
}
function getSequence(start, end) {
return new Int32Array(memory.buffer, start, (end - start) / Int32Array.BYTES_PER_ELEMENT);
}
function spawnWorkers() {
let workers = [];
for (let i = 0; i < kNumberOfWorker; i++) {
let worker = new Worker(
`onmessage = function(msg) {
if (msg.module) {
let module = msg.module;
let mem = msg.mem;
this.instance = new WebAssembly.Instance(module, {m: {imported_mem: mem}});
postMessage({instantiated: true});
} else {
let address = msg.address;
let sequence = msg.sequence;
let index = msg.index;
let spin = msg.spin;
let result = instance.exports["worker" + index](address, sequence, spin);
postMessage({index: index, sequence: sequence, result: result});
}
}`, {type: 'string'}
);
workers.push(worker);
}
return workers;
}
function instantiateModuleInWorkers(workers) {
for (let worker of workers) {
worker.postMessage({
module: module,
mem: memory
});
let msg = worker.getMessage();
if (!msg.instantiated) throw "Worker failed to instantiate";
}
}
function executeSequenceInWorkers(workers) {
for (i = 0; i < workers.length; i++) {
let worker = workers[i];
worker.postMessage({
index: i,
address: 0,
spin: 16,
sequence: 32 + ((kSequenceLength * 4) + 32) * i
});
// In debug mode, keep execution sequential.
if (kDebug) {
let msg = worker.getMessage();
results[msg.index] = getSequence(msg.sequence, msg.result);
}
}
}
function selectMatchingWorkers(state) {
let matching = [];
let indices = state.indices;
for (let i = 0; i < indices.length; i++) {
let index = indices[i];
if (index >= kSequenceLength) continue;
// We need to project the expected value to the number of bits this
// operation will read at runtime.
let expected = sequences[i][index].truncateResultBits(state.low, state.high);
let hasOutput = sequences[i][index].hasOutput;
if (!hasOutput || (results[i][index] == expected)) {
matching.push(i);
}
}
return matching;
}
function computeNextState(state, advanceIdx) {
let newIndices = state.indices.slice();
let sequence = sequences[advanceIdx];
let operation = sequence[state.indices[advanceIdx]];
newIndices[advanceIdx]++;
let {
low,
high
} = operation.compute(state);
return new State(low, high, newIndices, state.count + 1);
}
function findSequentialOrdering() {
let startIndices = new Array(results.length);
let steps = 0;
startIndices.fill(0);
let matchingStates = [new State(0, 0, startIndices, 0)];
while (matchingStates.length > 0) {
let current = matchingStates.pop();
if (kDebug) {
print(current);
}
let matchingResults = selectMatchingWorkers(current);
if (matchingResults.length == 0) {
continue;
}
for (let match of matchingResults) {
let newState = computeNextState(current, match);
if (newState.isFinal()) {
return true;
}
matchingStates.push(newState);
}
if (steps++ > kNumberOfSteps) {
print("Search timed out, aborting...");
return true;
}
}
// We have no options left.
return false;
}
// Helpful for debugging failed tests.
function loadSequencesFromStrings(inputs) {
let reverseOpcodes = {};
for (let i = 0; i < opCodeNames.length; i++) {
reverseOpcodes[opCodeNames[i]] = i;
}
let sequences = [];
let parseRE = /([a-zA-Z0-9]*)\[\+([0-9])\] ([\-0-9]*)/;
for (let input of inputs) {
let parts = input.split(",");
let sequence = [];
for (let part of parts) {
let parsed = parseRE.exec(part);
sequence.push(new Operation(reverseOpcodes[parsed[1]], parsed[3],
parsed[2] | 0));
}
sequences.push(sequence);
}
return sequences;
}
// Helpful for debugging failed tests.
function loadResultsFromStrings(inputs) {
let results = [];
for (let input of inputs) {
let parts = input.split(",");
let result = [];
for (let number of parts) {
result.push(number | 0);
}
results.push(result);
}
return results;
}
let maxSize = 10;
let memory = new WebAssembly.Memory({
initial: 1,
maximum: maxSize,
shared: true
});
let memory_view = new Int32Array(memory.buffer);
let sequences = [];
let results = [];
let builder = new WasmModuleBuilder();
builder.addImportedMemory("m", "imported_mem", 0, maxSize, "shared");
for (let i = 0; i < kNumberOfWorker; i++) {
sequences[i] = makeSequenceOfOperations(kSequenceLength);
builder.addFunction("worker" + i, kSig_i_iii)
.addBody(generateFunctionBodyForSequence(sequences[i]))
.exportAs("worker" + i);
}
// Instantiate module, get function exports.
let module = new WebAssembly.Module(builder.toBuffer());
let instance = new WebAssembly.Instance(module, {
m: {
imported_mem: memory
}
});
// Spawn off the workers and run the sequences.
let workers = spawnWorkers();
// Set spin count.
memory_view[4] = kNumberOfWorker;
instantiateModuleInWorkers(workers);
executeSequenceInWorkers(workers);
if (!kDebug) {
// Collect results, d8 style.
for (let worker of workers) {
let msg = worker.getMessage();
results[msg.index] = getSequence(msg.sequence, msg.result);
}
}
// Terminate all workers.
for (let worker of workers) {
worker.terminate();
}
// In debug mode, print sequences and results.
if (kDebug) {
for (let result of results) {
print(result);
}
for (let sequence of sequences) {
print(sequence);
}
}
// Try to reconstruct a sequential ordering.
let passed = findSequentialOrdering();
if (passed) {
print("PASS");
} else {
for (let i = 0; i < kNumberOfWorker; i++) {
print("Worker " + i);
print(sequences[i]);
print(results[i]);
}
print("FAIL");
quit(-1);
}