Commit 14bfcf7c authored by Ben L. Titzer's avatar Ben L. Titzer Committed by Commit Bot

[mjsunit/wasm] Reuse WebAssembly.Memory objects in stress test

In the atomics stress, the search for sequential sequences creates
lots of new WebAssembly.Memory objects. This memory pressure is not
central to this test, so reuse the same memory to make them less
flaky.

R=mstarzinger@chromium.org

Change-Id: I8d135e7b82d572cb1df38f37a4e2f6393f6b2e05
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1697247Reviewed-by: 's avatarMichael Starzinger <mstarzinger@chromium.org>
Commit-Queue: Ben Titzer <titzer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#62644}
parent 98bc64d3
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
// Note that results of this test are flaky by design. While the test is // Note that results of this test are flaky by design. While the test is
// deterministic with a fixed seed, bugs may introduce non-determinism. // deterministic with a fixed seed, bugs may introduce non-determinism.
load("test/mjsunit/wasm/wasm-module-builder.js"); load('test/mjsunit/wasm/wasm-module-builder.js');
const kDebug = false; const kDebug = false;
...@@ -22,319 +22,287 @@ const kFirstOpcodeWithoutOutput = 3; ...@@ -22,319 +22,287 @@ const kFirstOpcodeWithoutOutput = 3;
const kLastOpcodeWithoutOutput = 5; const kLastOpcodeWithoutOutput = 5;
const opCodes = [ const opCodes = [
kExprI32AtomicLoad, kExprI32AtomicLoad, kExprI32AtomicLoad8U, kExprI32AtomicLoad16U,
kExprI32AtomicLoad8U, kExprI32AtomicStore, kExprI32AtomicStore8U, kExprI32AtomicStore16U,
kExprI32AtomicLoad16U, kExprI32AtomicAdd, kExprI32AtomicAdd8U, kExprI32AtomicAdd16U,
kExprI32AtomicStore, kExprI32AtomicSub, kExprI32AtomicSub8U, kExprI32AtomicSub16U,
kExprI32AtomicStore8U, kExprI32AtomicAnd, kExprI32AtomicAnd8U, kExprI32AtomicAnd16U,
kExprI32AtomicStore16U, kExprI32AtomicOr, kExprI32AtomicOr8U, kExprI32AtomicOr16U,
kExprI32AtomicAdd, kExprI32AtomicXor, kExprI32AtomicXor8U, kExprI32AtomicXor16U,
kExprI32AtomicAdd8U, kExprI32AtomicExchange, kExprI32AtomicExchange8U, kExprI32AtomicExchange16U
kExprI32AtomicAdd16U,
kExprI32AtomicSub,
kExprI32AtomicSub8U,
kExprI32AtomicSub16U,
kExprI32AtomicAnd,
kExprI32AtomicAnd8U,
kExprI32AtomicAnd16U,
kExprI32AtomicOr,
kExprI32AtomicOr8U,
kExprI32AtomicOr16U,
kExprI32AtomicXor,
kExprI32AtomicXor8U,
kExprI32AtomicXor16U,
kExprI32AtomicExchange,
kExprI32AtomicExchange8U,
kExprI32AtomicExchange16U
]; ];
const opCodeNames = [ const opCodeNames = [
"kExprI32AtomicLoad", 'kExprI32AtomicLoad', 'kExprI32AtomicLoad8U',
"kExprI32AtomicLoad8U", 'kExprI32AtomicLoad16U', 'kExprI32AtomicStore',
"kExprI32AtomicLoad16U", 'kExprI32AtomicStore8U', 'kExprI32AtomicStore16U',
"kExprI32AtomicStore", 'kExprI32AtomicAdd', 'kExprI32AtomicAdd8U',
"kExprI32AtomicStore8U", 'kExprI32AtomicAdd16U', 'kExprI32AtomicSub',
"kExprI32AtomicStore16U", 'kExprI32AtomicSub8U', 'kExprI32AtomicSub16U',
"kExprI32AtomicAdd", 'kExprI32AtomicAnd', 'kExprI32AtomicAnd8U',
"kExprI32AtomicAdd8U", 'kExprI32AtomicAnd16U', 'kExprI32AtomicOr',
"kExprI32AtomicAdd16U", 'kExprI32AtomicOr8U', 'kExprI32AtomicOr16U',
"kExprI32AtomicSub", 'kExprI32AtomicXor', 'kExprI32AtomicXor8U',
"kExprI32AtomicSub8U", 'kExprI32AtomicXor16U', 'kExprI32AtomicExchange',
"kExprI32AtomicSub16U", 'kExprI32AtomicExchange8U', 'kExprI32AtomicExchange16U'
"kExprI32AtomicAnd",
"kExprI32AtomicAnd8U",
"kExprI32AtomicAnd16U",
"kExprI32AtomicOr",
"kExprI32AtomicOr8U",
"kExprI32AtomicOr16U",
"kExprI32AtomicXor",
"kExprI32AtomicXor8U",
"kExprI32AtomicXor16U",
"kExprI32AtomicExchange",
"kExprI32AtomicExchange8U",
"kExprI32AtomicExchange16U"
]; ];
class Operation { let kMaxMemPages = 10;
constructor(opcode, input, offset) { let gSharedMemory =
this.opcode = opcode != undefined ? opcode : Operation.nextOpcode(); new WebAssembly.Memory({initial: 1, maximum: kMaxMemPages, shared: true});
this.size = Operation.opcodeToSize(this.opcode); let gSharedMemoryView = new Int32Array(gSharedMemory.buffer);
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() { let gPrivateMemory =
if (!Operation.__instance) { new WebAssembly.Memory({initial: 1, maximum: kMaxMemPages, shared: true});
return {}; let gPrivateMemoryView = new Int32Array(gPrivateMemory.buffer);
}
return Operation.__instance.exports;
}
static get memory() { class Operation {
return Operation.exports.mem; constructor(opcode, input, offset) {
} this.opcode = opcode != undefined ? opcode : Operation.nextOpcode();
this.size = Operation.opcodeToSize(this.opcode);
static set instance(instance) { this.input = input != undefined ? input : Operation.inputForSize(this.size);
Operation.__instance = instance; this.offset =
} offset != undefined ? offset : Operation.offsetForSize(this.size);
}
compute(state) {
let evalFun = Operation.exports[this.key]; static nextOpcode() {
if (!evalFun) { let random = Math.random();
let builder = Operation.builder; return Math.floor(random * opCodes.length);
let body = [ }
// Load address of low 32 bits.
kExprI32Const, 0, static opcodeToSize(opcode) {
// Load expected value. // Instructions are ordered in 32, 8, 16 bits size
kExprGetLocal, 0, return [32, 8, 16][opcode % 3];
kExprI32StoreMem, 2, 0, }
// Load address of high 32 bits.
kExprI32Const, 4, static opcodeToAlignment(opcode) {
// Load expected value. // Instructions are ordered in 32, 8, 16 bits size
kExprGetLocal, 1, return [2, 0, 1][opcode % 3];
kExprI32StoreMem, 2, 0, }
// Load address of where our window starts.
kExprI32Const, 0, static inputForSize(size) {
// Load input if there is one. let random = Math.random();
...(this.hasInput ? [kExprGetLocal, 2] : []), // Avoid 32 bit overflow for integer here :(
// Perform operation. return Math.floor(random * (1 << (size - 1)) * 2);
kAtomicPrefix, ...this.wasmOpcode, }
// Drop output if it had any.
...(this.hasOutput ? [kExprDrop] : []), static offsetForSize(size) {
// Load resulting value. // Pick an offset in bytes between 0 and 7.
kExprI32Const, 0, let offset = Math.floor(Math.random() * 8);
kExprI32LoadMem, 2, 0, // Make sure the offset matches the required alignment by masking out the
// Return. // lower bits.
kExprReturn let size_in_bytes = size / 8;
] let mask = ~(size_in_bytes - 1);
builder.addFunction(this.key, kSig_i_iii) return offset & mask;
.addBody(body) }
.exportAs(this.key);
// Instantiate module, get function exports. get wasmOpcode() {
let module = new WebAssembly.Module(builder.toBuffer()); // [opcode, alignment, offset]
Operation.instance = new WebAssembly.Instance(module); return [
evalFun = Operation.exports[this.key]; opCodes[this.opcode], Operation.opcodeToAlignment(this.opcode),
} this.offset
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() + get hasInput() {
" -> " + ta[1] + ":" + ta[0]); return this.opcode >= kFirstOpcodeWithInput;
} }
if (result != ta[0]) throw "!";
return { get hasOutput() {
low: ta[0], return this.opcode < kFirstOpcodeWithoutOutput ||
high: ta[1] this.opcode > kLastOpcodeWithoutOutput;
}; }
}
truncateResultBits(low, high) {
toString() { // Shift the lower part. For offsets greater four it drops out of the
return opCodeNames[this.opcode] + "[+" + this.offset + "] " + this.input; // 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
get key() { // shifted for [4..7].
return this.opcode + "-" + this.offset; 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.addImportedMemory('m', 'imported_mem', 0, kMaxMemPages, 'shared');
Operation.__builder = builder;
}
return Operation.__builder;
}
static get exports() {
if (!Operation.__instance) {
return {};
}
return Operation.__instance.exports;
}
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, {m: {imported_mem: gPrivateMemory}});
evalFun = Operation.exports[this.key];
}
let result = evalFun(state.low, state.high, this.input);
let ta = gPrivateMemoryView;
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 { class State {
constructor(low, high, indices, count) { constructor(low, high, indices, count) {
this.low = low; this.low = low;
this.high = high; this.high = high;
this.indices = indices; this.indices = indices;
this.count = count; this.count = count;
} }
isFinal() { isFinal() {
return (this.count == kNumberOfWorker * kSequenceLength); return (this.count == kNumberOfWorker * kSequenceLength);
} }
toString() { toString() {
return this.high + ":" + this.low + " @ " + this.indices; return this.high + ':' + this.low + ' @ ' + this.indices;
} }
} }
function makeSequenceOfOperations(size) { function makeSequenceOfOperations(size) {
let result = new Array(size); let result = new Array(size);
for (let i = 0; i < size; i++) { for (let i = 0; i < size; i++) {
result[i] = new Operation(); result[i] = new Operation();
} }
return result; return result;
} }
function toSLeb128(val) { function toSLeb128(val) {
let result = []; let result = [];
while (true) { while (true) {
let v = val & 0x7f; let v = val & 0x7f;
val = val >> 7; val = val >> 7;
let msbIsSet = (v & 0x40) || false; let msbIsSet = (v & 0x40) || false;
if (((val == 0) && !msbIsSet) || ((val == -1) && msbIsSet)) { if (((val == 0) && !msbIsSet) || ((val == -1) && msbIsSet)) {
result.push(v); result.push(v);
break; break;
} }
result.push(v | 0x80); result.push(v | 0x80);
} }
return result; return result;
} }
function generateFunctionBodyForSequence(sequence) { function generateFunctionBodyForSequence(sequence) {
// We expect the int32* to perform ops on as arg 0 and // We expect the int32* to perform ops on as arg 0 and
// the int32* for our value log as arg1. Argument 2 gives // the int32* for our value log as arg1. Argument 2 gives
// an int32* we use to count down spinning workers. // an int32* we use to count down spinning workers.
let body = []; let body = [];
// Initially, we spin until all workers start running. // Initially, we spin until all workers start running.
if (!kDebug) { if (!kDebug) {
body.push( body.push(
// Decrement the wait count. // Decrement the wait count.
kExprGetLocal, 2, kExprGetLocal, 2, kExprI32Const, 1, kAtomicPrefix, kExprI32AtomicSub, 2,
kExprI32Const, 1, 0,
kAtomicPrefix, kExprI32AtomicSub, 2, 0, // Spin until zero.
// Spin until zero. kExprLoop, kWasmStmt, kExprGetLocal, 2, kAtomicPrefix,
kExprLoop, kWasmStmt, kExprI32AtomicLoad, 2, 0, kExprI32Const, 0, kExprI32GtU, kExprBrIf, 0,
kExprGetLocal, 2, kExprEnd);
kAtomicPrefix, kExprI32AtomicLoad, 2, 0, }
kExprI32Const, 0, for (let operation of sequence) {
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( body.push(
// Pre-load address of results sequence pointer for later.
kExprGetLocal, 1, kExprGetLocal, 1,
kExprReturn); // Load address where atomic pointers are stored.
return body; 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) { function getSequence(start, end) {
return new Int32Array(memory.buffer, start, (end - start) / Int32Array.BYTES_PER_ELEMENT); return new Int32Array(
gSharedMemory.buffer, start,
(end - start) / Int32Array.BYTES_PER_ELEMENT);
} }
function spawnWorkers() { function spawnWorkers() {
let workers = []; let workers = [];
for (let i = 0; i < kNumberOfWorker; i++) { for (let i = 0; i < kNumberOfWorker; i++) {
let worker = new Worker( let worker = new Worker(
`onmessage = function(msg) { `onmessage = function(msg) {
if (msg.module) { if (msg.module) {
let module = msg.module; let module = msg.module;
let mem = msg.mem; let mem = msg.mem;
...@@ -348,206 +316,190 @@ function spawnWorkers() { ...@@ -348,206 +316,190 @@ function spawnWorkers() {
let result = instance.exports["worker" + index](address, sequence, spin); let result = instance.exports["worker" + index](address, sequence, spin);
postMessage({index: index, sequence: sequence, result: result}); postMessage({index: index, sequence: sequence, result: result});
} }
}`, {type: 'string'} }`,
); {type: 'string'});
workers.push(worker); workers.push(worker);
} }
return workers; return workers;
} }
function instantiateModuleInWorkers(workers) { function instantiateModuleInWorkers(workers) {
for (let worker of workers) { for (let worker of workers) {
worker.postMessage({ worker.postMessage({module: module, mem: gSharedMemory});
module: module, let msg = worker.getMessage();
mem: memory if (!msg.instantiated) throw 'Worker failed to instantiate';
}); }
let msg = worker.getMessage();
if (!msg.instantiated) throw "Worker failed to instantiate";
}
} }
function executeSequenceInWorkers(workers) { function executeSequenceInWorkers(workers) {
for (i = 0; i < workers.length; i++) { for (i = 0; i < workers.length; i++) {
let worker = workers[i]; let worker = workers[i];
worker.postMessage({ worker.postMessage({
index: i, index: i,
address: 0, address: 0,
spin: 16, spin: 16,
sequence: 32 + ((kSequenceLength * 4) + 32) * i sequence: 32 + ((kSequenceLength * 4) + 32) * i
}); });
// In debug mode, keep execution sequential. // In debug mode, keep execution sequential.
if (kDebug) { if (kDebug) {
let msg = worker.getMessage(); let msg = worker.getMessage();
results[msg.index] = getSequence(msg.sequence, msg.result); results[msg.index] = getSequence(msg.sequence, msg.result);
} }
} }
} }
function selectMatchingWorkers(state) { function selectMatchingWorkers(state) {
let matching = []; let matching = [];
let indices = state.indices; let indices = state.indices;
for (let i = 0; i < indices.length; i++) { for (let i = 0; i < indices.length; i++) {
let index = indices[i]; let index = indices[i];
if (index >= kSequenceLength) continue; if (index >= kSequenceLength) continue;
// We need to project the expected value to the number of bits this // We need to project the expected value to the number of bits this
// operation will read at runtime. // operation will read at runtime.
let expected = sequences[i][index].truncateResultBits(state.low, state.high); let expected =
let hasOutput = sequences[i][index].hasOutput; sequences[i][index].truncateResultBits(state.low, state.high);
if (!hasOutput || (results[i][index] == expected)) { let hasOutput = sequences[i][index].hasOutput;
matching.push(i); if (!hasOutput || (results[i][index] == expected)) {
} matching.push(i);
} }
return matching; }
return matching;
} }
function computeNextState(state, advanceIdx) { function computeNextState(state, advanceIdx) {
let newIndices = state.indices.slice(); let newIndices = state.indices.slice();
let sequence = sequences[advanceIdx]; let sequence = sequences[advanceIdx];
let operation = sequence[state.indices[advanceIdx]]; let operation = sequence[state.indices[advanceIdx]];
newIndices[advanceIdx]++; newIndices[advanceIdx]++;
let { let {low, high} = operation.compute(state);
low, return new State(low, high, newIndices, state.count + 1);
high
} = operation.compute(state);
return new State(low, high, newIndices, state.count + 1);
} }
function findSequentialOrdering() { function findSequentialOrdering() {
let startIndices = new Array(results.length); let startIndices = new Array(results.length);
let steps = 0; let steps = 0;
startIndices.fill(0); startIndices.fill(0);
let matchingStates = [new State(0, 0, startIndices, 0)]; let matchingStates = [new State(0, 0, startIndices, 0)];
while (matchingStates.length > 0) { while (matchingStates.length > 0) {
let current = matchingStates.pop(); let current = matchingStates.pop();
if (kDebug) { if (kDebug) {
print(current); print(current);
} }
let matchingResults = selectMatchingWorkers(current); let matchingResults = selectMatchingWorkers(current);
if (matchingResults.length == 0) { if (matchingResults.length == 0) {
continue; continue;
} }
for (let match of matchingResults) { for (let match of matchingResults) {
let newState = computeNextState(current, match); let newState = computeNextState(current, match);
if (newState.isFinal()) { if (newState.isFinal()) {
return true; return true;
} }
matchingStates.push(newState); matchingStates.push(newState);
} }
if (steps++ > kNumberOfSteps) { if (steps++ > kNumberOfSteps) {
print("Search timed out, aborting..."); print('Search timed out, aborting...');
return true; return true;
} }
} }
// We have no options left. // We have no options left.
return false; return false;
} }
// Helpful for debugging failed tests. // Helpful for debugging failed tests.
function loadSequencesFromStrings(inputs) { function loadSequencesFromStrings(inputs) {
let reverseOpcodes = {}; let reverseOpcodes = {};
for (let i = 0; i < opCodeNames.length; i++) { for (let i = 0; i < opCodeNames.length; i++) {
reverseOpcodes[opCodeNames[i]] = i; reverseOpcodes[opCodeNames[i]] = i;
} }
let sequences = []; let sequences = [];
let parseRE = /([a-zA-Z0-9]*)\[\+([0-9])\] ([\-0-9]*)/; let parseRE = /([a-zA-Z0-9]*)\[\+([0-9])\] ([\-0-9]*)/;
for (let input of inputs) { for (let input of inputs) {
let parts = input.split(","); let parts = input.split(',');
let sequence = []; let sequence = [];
for (let part of parts) { for (let part of parts) {
let parsed = parseRE.exec(part); let parsed = parseRE.exec(part);
sequence.push(new Operation(reverseOpcodes[parsed[1]], parsed[3], sequence.push(
parsed[2] | 0)); new Operation(reverseOpcodes[parsed[1]], parsed[3], parsed[2] | 0));
} }
sequences.push(sequence); sequences.push(sequence);
} }
return sequences; return sequences;
} }
// Helpful for debugging failed tests. // Helpful for debugging failed tests.
function loadResultsFromStrings(inputs) { function loadResultsFromStrings(inputs) {
let results = []; let results = [];
for (let input of inputs) { for (let input of inputs) {
let parts = input.split(","); let parts = input.split(',');
let result = []; let result = [];
for (let number of parts) { for (let number of parts) {
result.push(number | 0); result.push(number | 0);
}
results.push(result);
} }
return results; 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 sequences = [];
let results = []; let results = [];
let builder = new WasmModuleBuilder(); let builder = new WasmModuleBuilder();
builder.addImportedMemory("m", "imported_mem", 0, maxSize, "shared"); builder.addImportedMemory('m', 'imported_mem', 0, kMaxMemPages, 'shared');
for (let i = 0; i < kNumberOfWorker; i++) { for (let i = 0; i < kNumberOfWorker; i++) {
sequences[i] = makeSequenceOfOperations(kSequenceLength); sequences[i] = makeSequenceOfOperations(kSequenceLength);
builder.addFunction("worker" + i, kSig_i_iii) builder.addFunction('worker' + i, kSig_i_iii)
.addBody(generateFunctionBodyForSequence(sequences[i])) .addBody(generateFunctionBodyForSequence(sequences[i]))
.exportAs("worker" + i); .exportAs('worker' + i);
} }
// Instantiate module, get function exports. // Instantiate module, get function exports.
let module = new WebAssembly.Module(builder.toBuffer()); let module = new WebAssembly.Module(builder.toBuffer());
let instance = new WebAssembly.Instance(module, { let instance =
m: { new WebAssembly.Instance(module, {m: {imported_mem: gSharedMemory}});
imported_mem: memory
}
});
// Spawn off the workers and run the sequences. // Spawn off the workers and run the sequences.
let workers = spawnWorkers(); let workers = spawnWorkers();
// Set spin count. // Set spin count.
memory_view[4] = kNumberOfWorker; gSharedMemoryView[4] = kNumberOfWorker;
instantiateModuleInWorkers(workers); instantiateModuleInWorkers(workers);
executeSequenceInWorkers(workers); executeSequenceInWorkers(workers);
if (!kDebug) { if (!kDebug) {
// Collect results, d8 style. // Collect results, d8 style.
for (let worker of workers) { for (let worker of workers) {
let msg = worker.getMessage(); let msg = worker.getMessage();
results[msg.index] = getSequence(msg.sequence, msg.result); results[msg.index] = getSequence(msg.sequence, msg.result);
} }
} }
// Terminate all workers. // Terminate all workers.
for (let worker of workers) { for (let worker of workers) {
worker.terminate(); worker.terminate();
} }
// In debug mode, print sequences and results. // In debug mode, print sequences and results.
if (kDebug) { if (kDebug) {
for (let result of results) { for (let result of results) {
print(result); print(result);
} }
for (let sequence of sequences) { for (let sequence of sequences) {
print(sequence); print(sequence);
} }
} }
// Try to reconstruct a sequential ordering. // Try to reconstruct a sequential ordering.
let passed = findSequentialOrdering(); let passed = findSequentialOrdering();
if (passed) { if (passed) {
print("PASS"); print('PASS');
} else { } else {
for (let i = 0; i < kNumberOfWorker; i++) { for (let i = 0; i < kNumberOfWorker; i++) {
print("Worker " + i); print('Worker ' + i);
print(sequences[i]); print(sequences[i]);
print(results[i]); print(results[i]);
} }
print("FAIL"); print('FAIL');
quit(-1); quit(-1);
} }
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
// Note that results of this test are flaky by design. While the test is // Note that results of this test are flaky by design. While the test is
// deterministic with a fixed seed, bugs may introduce non-determinism. // deterministic with a fixed seed, bugs may introduce non-determinism.
load("test/mjsunit/wasm/wasm-module-builder.js"); load('test/mjsunit/wasm/wasm-module-builder.js');
const kDebug = false; const kDebug = false;
...@@ -22,358 +22,328 @@ const kFirstOpcodeWithoutOutput = 4; ...@@ -22,358 +22,328 @@ const kFirstOpcodeWithoutOutput = 4;
const kLastOpcodeWithoutOutput = 7; const kLastOpcodeWithoutOutput = 7;
const opCodes = [ const opCodes = [
kExprI64AtomicLoad, kExprI64AtomicLoad, kExprI64AtomicLoad8U, kExprI64AtomicLoad16U,
kExprI64AtomicLoad8U, kExprI64AtomicLoad32U, kExprI64AtomicStore, kExprI64AtomicStore8U,
kExprI64AtomicLoad16U, kExprI64AtomicStore16U, kExprI64AtomicStore32U, kExprI64AtomicAdd,
kExprI64AtomicLoad32U, kExprI64AtomicAdd8U, kExprI64AtomicAdd16U, kExprI64AtomicAdd32U,
kExprI64AtomicStore, kExprI64AtomicSub, kExprI64AtomicSub8U, kExprI64AtomicSub16U,
kExprI64AtomicStore8U, kExprI64AtomicSub32U, kExprI64AtomicAnd, kExprI64AtomicAnd8U,
kExprI64AtomicStore16U, kExprI64AtomicAnd16U, kExprI64AtomicAnd32U, kExprI64AtomicOr,
kExprI64AtomicStore32U, kExprI64AtomicOr8U, kExprI64AtomicOr16U, kExprI64AtomicOr32U,
kExprI64AtomicAdd, kExprI64AtomicXor, kExprI64AtomicXor8U, kExprI64AtomicXor16U,
kExprI64AtomicAdd8U, kExprI64AtomicXor32U, kExprI64AtomicExchange, kExprI64AtomicExchange8U,
kExprI64AtomicAdd16U, kExprI64AtomicExchange16U, kExprI64AtomicExchange32U
kExprI64AtomicAdd32U,
kExprI64AtomicSub,
kExprI64AtomicSub8U,
kExprI64AtomicSub16U,
kExprI64AtomicSub32U,
kExprI64AtomicAnd,
kExprI64AtomicAnd8U,
kExprI64AtomicAnd16U,
kExprI64AtomicAnd32U,
kExprI64AtomicOr,
kExprI64AtomicOr8U,
kExprI64AtomicOr16U,
kExprI64AtomicOr32U,
kExprI64AtomicXor,
kExprI64AtomicXor8U,
kExprI64AtomicXor16U,
kExprI64AtomicXor32U,
kExprI64AtomicExchange,
kExprI64AtomicExchange8U,
kExprI64AtomicExchange16U,
kExprI64AtomicExchange32U
]; ];
const opCodeNames = [ const opCodeNames = [
"kExprI64AtomicLoad", 'kExprI64AtomicLoad', 'kExprI64AtomicLoad8U',
"kExprI64AtomicLoad8U", 'kExprI64AtomicLoad16U', 'kExprI64AtomicLoad32U',
"kExprI64AtomicLoad16U", 'kExprI64AtomicStore', 'kExprI64AtomicStore8U',
"kExprI64AtomicLoad32U", 'kExprI64AtomicStore16U', 'kExprI64AtomicStore32U',
"kExprI64AtomicStore", 'kExprI64AtomicAdd', 'kExprI64AtomicAdd8U',
"kExprI64AtomicStore8U", 'kExprI64AtomicAdd16U', 'kExprI64AtomicAdd32U',
"kExprI64AtomicStore16U", 'kExprI64AtomicSub', 'kExprI64AtomicSub8U',
"kExprI64AtomicStore32U", 'kExprI64AtomicSub16U', 'kExprI64AtomicSub32U',
"kExprI64AtomicAdd", 'kExprI64AtomicAnd', 'kExprI64AtomicAnd8U',
"kExprI64AtomicAdd8U", 'kExprI64AtomicAnd16U', 'kExprI64AtomicAnd32U',
"kExprI64AtomicAdd16U", 'kExprI64AtomicOr', 'kExprI64AtomicOr8U',
"kExprI64AtomicAdd32U", 'kExprI64AtomicOr16U', 'kExprI64AtomicOr32U',
"kExprI64AtomicSub", 'kExprI64AtomicXor', 'kExprI64AtomicXor8U',
"kExprI64AtomicSub8U", 'kExprI64AtomicXor16U', 'kExprI64AtomicXor32U',
"kExprI64AtomicSub16U", 'kExprI64AtomicExchange', 'kExprI64AtomicExchange8U',
"kExprI64AtomicSub32U", 'kExprI64AtomicExchange16U', 'kExprI64AtomicExchange32U'
"kExprI64AtomicAnd",
"kExprI64AtomicAnd8U",
"kExprI64AtomicAnd16U",
"kExprI64AtomicAnd32U",
"kExprI64AtomicOr",
"kExprI64AtomicOr8U",
"kExprI64AtomicOr16U",
"kExprI64AtomicOr32U",
"kExprI64AtomicXor",
"kExprI64AtomicXor8U",
"kExprI64AtomicXor16U",
"kExprI64AtomicXor32U",
"kExprI64AtomicExchange",
"kExprI64AtomicExchange8U",
"kExprI64AtomicExchange16U",
"kExprI64AtomicExchange32U"
]; ];
const kMaxInt32 = (1 << 31) * 2; let kMaxMemPages = 10;
let gSharedMemory =
class Operation { new WebAssembly.Memory({initial: 1, maximum: kMaxMemPages, shared: true});
constructor(opcode, low_input, high_input, offset) { let gSharedMemoryView = new Int32Array(gSharedMemory.buffer);
this.opcode = opcode != undefined ? opcode : Operation.nextOpcode();
this.size = Operation.opcodeToSize(this.opcode);
if (low_input == undefined) {
[low_input, high_input] = Operation.inputForSize(this.size);
}
this.low_input = low_input;
this.high_input = high_input;
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 64, 8, 16, 32 bits size
return [64, 8, 16, 32][opcode % 4];
}
static opcodeToAlignment(opcode) {
// Instructions are ordered in 64, 8, 16, 32 bits size
return [3, 0, 1, 2][opcode % 4];
}
static inputForSize(size) {
if (size <= 32) {
let random = Math.random();
// Avoid 32 bit overflow for integer here :(
return [Math.floor(random * (1 << (size - 1)) * 2), 0];
}
return [Math.floor(Math.random() * kMaxInt32), Math.floor(Math.random() *
kMaxInt32)];
}
static offsetForSize(size) {
// Pick an offset in bytes between 0 and 8.
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() { let gPrivateMemory =
return this.opcode >= kFirstOpcodeWithInput; new WebAssembly.Memory({initial: 1, maximum: kMaxMemPages, shared: true});
} let gPrivateMemoryView = new Int32Array(gPrivateMemory.buffer);
get hasOutput() {
return this.opcode < kFirstOpcodeWithoutOutput || this.opcode >
kLastOpcodeWithoutOutput;
}
truncateResultBits(low, high) {
if (this.size == 64) return [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, 0];
case 16:
return [value & 0xFFFF, 0];
case 32:
return [value, 0];
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, 3,
kExprI64UConvertI32,
kExprI64Const, 32,
kExprI64Shl,
kExprGetLocal, 2,
kExprI64UConvertI32,
kExprI64Ior
] : []),
// Perform operation.
kAtomicPrefix, ...this.wasmOpcode,
// Drop output if it had any.
...(this.hasOutput ? [kExprDrop] : []),
// Return.
kExprReturn
]
builder.addFunction(this.key, kSig_v_iiii)
.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];
}
evalFun(state.low, state.high, this.low_input, this.high_input);
let ta = new Int32Array(Operation.memory.buffer);
if (kDebug) {
print(state.high + ":" + state.low + " " + this.toString() +
" -> " + ta[1] + ":" + ta[0]);
}
return {
low: ta[0],
high: ta[1]
};
}
toString() { const kMaxInt32 = (1 << 31) * 2;
return opCodeNames[this.opcode] + "[+" + this.offset + "] " + this.high_input +
":" + this.low_input;
}
get key() { class Operation {
return this.opcode + "-" + this.offset; constructor(opcode, low_input, high_input, offset) {
} this.opcode = opcode != undefined ? opcode : Operation.nextOpcode();
this.size = Operation.opcodeToSize(this.opcode);
if (low_input == undefined) {
[low_input, high_input] = Operation.inputForSize(this.size);
}
this.low_input = low_input;
this.high_input = high_input;
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 64, 8, 16, 32 bits size
return [64, 8, 16, 32][opcode % 4];
}
static opcodeToAlignment(opcode) {
// Instructions are ordered in 64, 8, 16, 32 bits size
return [3, 0, 1, 2][opcode % 4];
}
static inputForSize(size) {
if (size <= 32) {
let random = Math.random();
// Avoid 32 bit overflow for integer here :(
return [Math.floor(random * (1 << (size - 1)) * 2), 0];
}
return [
Math.floor(Math.random() * kMaxInt32),
Math.floor(Math.random() * kMaxInt32)
];
}
static offsetForSize(size) {
// Pick an offset in bytes between 0 and 8.
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) {
if (this.size == 64)
return [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, 0];
case 16:
return [value & 0xFFFF, 0];
case 32:
return [value, 0];
default:
throw 'Unexpected size: ' + this.size;
}
}
static get builder() {
if (!Operation.__builder) {
let builder = new WasmModuleBuilder();
builder.addImportedMemory('m', 'imported_mem', 0, kMaxMemPages, 'shared');
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, 3, kExprI64UConvertI32, kExprI64Const, 32,
kExprI64Shl, kExprGetLocal, 2, kExprI64UConvertI32,
kExprI64Ior
] :
[]),
// Perform operation.
kAtomicPrefix, ...this.wasmOpcode,
// Drop output if it had any.
...(this.hasOutput ? [kExprDrop] : []),
// Return.
kExprReturn
]
builder.addFunction(this.key, kSig_v_iiii)
.addBody(body)
.exportAs(this.key);
// Instantiate module, get function exports.
let module = new WebAssembly.Module(builder.toBuffer());
Operation.instance =
new WebAssembly.Instance(module, {m: {imported_mem: gPrivateMemory}});
evalFun = Operation.exports[this.key];
}
evalFun(state.low, state.high, this.low_input, this.high_input);
let ta = gPrivateMemoryView;
if (kDebug) {
print(
state.high + ':' + state.low + ' ' + this.toString() + ' -> ' +
ta[1] + ':' + ta[0]);
}
return {low: ta[0], high: ta[1]};
}
toString() {
return opCodeNames[this.opcode] + '[+' + this.offset + '] ' +
this.high_input + ':' + this.low_input;
}
get key() {
return this.opcode + '-' + this.offset;
}
} }
class State { class State {
constructor(low, high, indices, count) { constructor(low, high, indices, count) {
this.low = low; this.low = low;
this.high = high; this.high = high;
this.indices = indices; this.indices = indices;
this.count = count; this.count = count;
} }
isFinal() { isFinal() {
return (this.count == kNumberOfWorker * kSequenceLength); return (this.count == kNumberOfWorker * kSequenceLength);
} }
toString() { toString() {
return this.high + ":" + this.low + " @ " + this.indices; return this.high + ':' + this.low + ' @ ' + this.indices;
} }
} }
function makeSequenceOfOperations(size) { function makeSequenceOfOperations(size) {
let result = new Array(size); let result = new Array(size);
for (let i = 0; i < size; i++) { for (let i = 0; i < size; i++) {
result[i] = new Operation(); result[i] = new Operation();
} }
return result; return result;
} }
function toSLeb128(low, high) { function toSLeb128(low, high) {
let result = []; let result = [];
while (true) { while (true) {
let v = low & 0x7f; let v = low & 0x7f;
// For low, fill up with zeros, high will add extra bits. // For low, fill up with zeros, high will add extra bits.
low = low >>> 7; low = low >>> 7;
if (high != 0) { if (high != 0) {
let shiftIn = high << (32 - 7); let shiftIn = high << (32 - 7);
low = low | shiftIn; low = low | shiftIn;
// For high, fill up with ones, so that we keep trailing one. // For high, fill up with ones, so that we keep trailing one.
high = high >> 7; high = high >> 7;
} }
let msbIsSet = (v & 0x40) || false; let msbIsSet = (v & 0x40) || false;
if (((low == 0) && (high == 0) && !msbIsSet) || ((low == -1) && (high == if (((low == 0) && (high == 0) && !msbIsSet) ||
-1) && msbIsSet)) { ((low == -1) && (high == -1) && msbIsSet)) {
result.push(v); result.push(v);
break; break;
} }
result.push(v | 0x80); result.push(v | 0x80);
} }
return result; return result;
} }
function generateFunctionBodyForSequence(sequence) { function generateFunctionBodyForSequence(sequence) {
// We expect the int64* to perform ops on as arg 0 and // We expect the int64* to perform ops on as arg 0 and
// the int64* for our value log as arg1. Argument 2 gives // the int64* for our value log as arg1. Argument 2 gives
// an int32* we use to count down spinning workers. // an int32* we use to count down spinning workers.
let body = []; let body = [];
// Initially, we spin until all workers start running. // Initially, we spin until all workers start running.
if (!kDebug) { 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 ? [kExprI64Const, ...toSLeb128(operation
.low_input, operation.high_input)] : []),
// Perform operation
kAtomicPrefix, ...operation.wasmOpcode,
// Generate fake output in needed.
...(operation.hasOutput ? [] : [kExprI64Const, 0]),
// Store read intermediate to sequence.
kExprI64StoreMem, 3, 0,
// Increment result sequence pointer.
kExprGetLocal, 1,
kExprI32Const, 8,
kExprI32Add,
kExprSetLocal, 1
);
}
// Return end of sequence index.
body.push( 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, kExprGetLocal, 1,
kExprReturn); // Load address where atomic pointers are stored.
return body; kExprGetLocal, 0,
// Load the second argument if it had any.
...(operation.hasInput ?
[
kExprI64Const,
...toSLeb128(operation.low_input, operation.high_input)
] :
[]),
// Perform operation
kAtomicPrefix, ...operation.wasmOpcode,
// Generate fake output in needed.
...(operation.hasOutput ? [] : [kExprI64Const, 0]),
// Store read intermediate to sequence.
kExprI64StoreMem, 3, 0,
// Increment result sequence pointer.
kExprGetLocal, 1, kExprI32Const, 8, kExprI32Add, kExprSetLocal, 1);
}
// Return end of sequence index.
body.push(kExprGetLocal, 1, kExprReturn);
return body;
} }
function getSequence(start, end) { function getSequence(start, end) {
return new Int32Array(memory.buffer, start, (end - start) / Int32Array.BYTES_PER_ELEMENT); return new Int32Array(
gSharedMemory.buffer, start,
(end - start) / Int32Array.BYTES_PER_ELEMENT);
} }
function spawnWorkers() { function spawnWorkers() {
let workers = []; let workers = [];
for (let i = 0; i < kNumberOfWorker; i++) { for (let i = 0; i < kNumberOfWorker; i++) {
let worker = new Worker( let worker = new Worker(
`onmessage = function(msg) { `onmessage = function(msg) {
if (msg.module) { if (msg.module) {
let module = msg.module; let module = msg.module;
let mem = msg.mem; let mem = msg.mem;
...@@ -387,209 +357,193 @@ function spawnWorkers() { ...@@ -387,209 +357,193 @@ function spawnWorkers() {
let result = instance.exports["worker" + index](address, sequence, spin); let result = instance.exports["worker" + index](address, sequence, spin);
postMessage({index: index, sequence: sequence, result: result}); postMessage({index: index, sequence: sequence, result: result});
} }
}`, {type: 'string'} }`,
); {type: 'string'});
workers.push(worker); workers.push(worker);
} }
return workers; return workers;
} }
function instantiateModuleInWorkers(workers) { function instantiateModuleInWorkers(workers) {
for (let worker of workers) { for (let worker of workers) {
worker.postMessage({ worker.postMessage({module: module, mem: gSharedMemory});
module: module, let msg = worker.getMessage();
mem: memory if (!msg.instantiated) throw 'Worker failed to instantiate';
}); }
let msg = worker.getMessage();
if (!msg.instantiated) throw "Worker failed to instantiate";
}
} }
function executeSequenceInWorkers(workers) { function executeSequenceInWorkers(workers) {
for (i = 0; i < workers.length; i++) { for (i = 0; i < workers.length; i++) {
let worker = workers[i]; let worker = workers[i];
worker.postMessage({ worker.postMessage({
index: i, index: i,
address: 0, address: 0,
spin: 16, spin: 16,
sequence: 32 + ((kSequenceLength * 8) + 32) * i sequence: 32 + ((kSequenceLength * 8) + 32) * i
}); });
// In debug mode, keep execution sequential. // In debug mode, keep execution sequential.
if (kDebug) { if (kDebug) {
let msg = worker.getMessage(); let msg = worker.getMessage();
results[msg.index] = getSequence(msg.sequence, msg.result); results[msg.index] = getSequence(msg.sequence, msg.result);
} }
} }
} }
function selectMatchingWorkers(state) { function selectMatchingWorkers(state) {
let matching = []; let matching = [];
let indices = state.indices; let indices = state.indices;
for (let i = 0; i < indices.length; i++) { for (let i = 0; i < indices.length; i++) {
let index = indices[i]; let index = indices[i];
if (index >= kSequenceLength) continue; if (index >= kSequenceLength) continue;
// We need to project the expected value to the number of bits this // We need to project the expected value to the number of bits this
// operation will read at runtime. // operation will read at runtime.
let [expected_low, expected_high] = sequences[i][index].truncateResultBits( let [expected_low, expected_high] =
state.low, state.high); sequences[i][index].truncateResultBits(state.low, state.high);
let hasOutput = sequences[i][index].hasOutput; let hasOutput = sequences[i][index].hasOutput;
if (!hasOutput || ((results[i][index * 2] == expected_low) && (results[ if (!hasOutput ||
i][index * 2 + 1] == expected_high))) { ((results[i][index * 2] == expected_low) &&
matching.push(i); (results[i][index * 2 + 1] == expected_high))) {
} matching.push(i);
} }
return matching; }
return matching;
} }
function computeNextState(state, advanceIdx) { function computeNextState(state, advanceIdx) {
let newIndices = state.indices.slice(); let newIndices = state.indices.slice();
let sequence = sequences[advanceIdx]; let sequence = sequences[advanceIdx];
let operation = sequence[state.indices[advanceIdx]]; let operation = sequence[state.indices[advanceIdx]];
newIndices[advanceIdx]++; newIndices[advanceIdx]++;
let { let {low, high} = operation.compute(state);
low,
high return new State(low, high, newIndices, state.count + 1);
} = operation.compute(state);
return new State(low, high, newIndices, state.count + 1);
} }
function findSequentialOrdering() { function findSequentialOrdering() {
let startIndices = new Array(results.length); let startIndices = new Array(results.length);
let steps = 0; let steps = 0;
startIndices.fill(0); startIndices.fill(0);
let matchingStates = [new State(0, 0, startIndices, 0)]; let matchingStates = [new State(0, 0, startIndices, 0)];
while (matchingStates.length > 0) { while (matchingStates.length > 0) {
let current = matchingStates.pop(); let current = matchingStates.pop();
if (kDebug) { if (kDebug) {
print(current); print(current);
} }
let matchingResults = selectMatchingWorkers(current); let matchingResults = selectMatchingWorkers(current);
if (matchingResults.length == 0) { if (matchingResults.length == 0) {
continue; continue;
} }
for (let match of matchingResults) { for (let match of matchingResults) {
let newState = computeNextState(current, match); let newState = computeNextState(current, match);
if (newState.isFinal()) { if (newState.isFinal()) {
return true; return true;
} }
matchingStates.push(newState); matchingStates.push(newState);
} }
if (steps++ > kNumberOfSteps) { if (steps++ > kNumberOfSteps) {
print("Search timed out, aborting..."); print('Search timed out, aborting...');
return true; return true;
} }
} }
// We have no options left. // We have no options left.
return false; return false;
} }
// Helpful for debugging failed tests. // Helpful for debugging failed tests.
function loadSequencesFromStrings(inputs) { function loadSequencesFromStrings(inputs) {
let reverseOpcodes = {}; let reverseOpcodes = {};
for (let i = 0; i < opCodeNames.length; i++) { for (let i = 0; i < opCodeNames.length; i++) {
reverseOpcodes[opCodeNames[i]] = i; reverseOpcodes[opCodeNames[i]] = i;
} }
let sequences = []; let sequences = [];
let parseRE = /([a-zA-Z0-9]*)\[\+([0-9])\] ([\-0-9]*)/; let parseRE = /([a-zA-Z0-9]*)\[\+([0-9])\] ([\-0-9]*)/;
for (let input of inputs) { for (let input of inputs) {
let parts = input.split(","); let parts = input.split(',');
let sequence = []; let sequence = [];
for (let part of parts) { for (let part of parts) {
let parsed = parseRE.exec(part); let parsed = parseRE.exec(part);
sequence.push(new Operation(reverseOpcodes[parsed[1]], parsed[3], sequence.push(
parsed[2] | 0)); new Operation(reverseOpcodes[parsed[1]], parsed[3], parsed[2] | 0));
} }
sequences.push(sequence); sequences.push(sequence);
} }
return sequences; return sequences;
} }
// Helpful for debugging failed tests. // Helpful for debugging failed tests.
function loadResultsFromStrings(inputs) { function loadResultsFromStrings(inputs) {
let results = []; let results = [];
for (let input of inputs) { for (let input of inputs) {
let parts = input.split(","); let parts = input.split(',');
let result = []; let result = [];
for (let number of parts) { for (let number of parts) {
result.push(number | 0); result.push(number | 0);
}
results.push(result);
} }
return results; 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 sequences = [];
let results = []; let results = [];
let builder = new WasmModuleBuilder(); let builder = new WasmModuleBuilder();
builder.addImportedMemory("m", "imported_mem", 0, maxSize, "shared"); builder.addImportedMemory('m', 'imported_mem', 0, kMaxMemPages, 'shared');
for (let i = 0; i < kNumberOfWorker; i++) { for (let i = 0; i < kNumberOfWorker; i++) {
sequences[i] = makeSequenceOfOperations(kSequenceLength); sequences[i] = makeSequenceOfOperations(kSequenceLength);
builder.addFunction("worker" + i, kSig_i_iii) builder.addFunction('worker' + i, kSig_i_iii)
.addBody(generateFunctionBodyForSequence(sequences[i])) .addBody(generateFunctionBodyForSequence(sequences[i]))
.exportAs("worker" + i); .exportAs('worker' + i);
} }
// Instantiate module, get function exports. // Instantiate module, get function exports.
let module = new WebAssembly.Module(builder.toBuffer()); let module = new WebAssembly.Module(builder.toBuffer());
let instance = new WebAssembly.Instance(module, { let instance =
m: { new WebAssembly.Instance(module, {m: {imported_mem: gSharedMemory}});
imported_mem: memory
}
});
// Spawn off the workers and run the sequences. // Spawn off the workers and run the sequences.
let workers = spawnWorkers(); let workers = spawnWorkers();
// Set spin count. // Set spin count.
memory_view[4] = kNumberOfWorker; gSharedMemoryView[4] = kNumberOfWorker;
instantiateModuleInWorkers(workers); instantiateModuleInWorkers(workers);
executeSequenceInWorkers(workers); executeSequenceInWorkers(workers);
if (!kDebug) { if (!kDebug) {
// Collect results, d8 style. // Collect results, d8 style.
for (let worker of workers) { for (let worker of workers) {
let msg = worker.getMessage(); let msg = worker.getMessage();
results[msg.index] = getSequence(msg.sequence, msg.result); results[msg.index] = getSequence(msg.sequence, msg.result);
} }
} }
// Terminate all workers. // Terminate all workers.
for (let worker of workers) { for (let worker of workers) {
worker.terminate(); worker.terminate();
} }
// In debug mode, print sequences and results. // In debug mode, print sequences and results.
if (kDebug) { if (kDebug) {
for (let result of results) { for (let result of results) {
print(result); print(result);
} }
for (let sequence of sequences) { for (let sequence of sequences) {
print(sequence); print(sequence);
} }
} }
// Try to reconstruct a sequential ordering. // Try to reconstruct a sequential ordering.
let passed = findSequentialOrdering(); let passed = findSequentialOrdering();
if (passed) { if (passed) {
print("PASS"); print('PASS');
} else { } else {
for (let i = 0; i < kNumberOfWorker; i++) { for (let i = 0; i < kNumberOfWorker; i++) {
print("Worker " + i); print('Worker ' + i);
print(sequences[i]); print(sequences[i]);
print(results[i]); print(results[i]);
} }
print("FAIL"); print('FAIL');
quit(-1); quit(-1);
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment