mirror of https://github.com/tc39/test262.git
325 lines
11 KiB
JavaScript
325 lines
11 KiB
JavaScript
// Copyright (C) 2017 Mozilla Corporation. All rights reserved.
|
|
// This code is governed by the BSD license found in the LICENSE file.
|
|
/*---
|
|
description: >
|
|
Collection of functions used to interact with Atomics.* operations across agent boundaries.
|
|
defines:
|
|
- $262.agent.getReportAsync
|
|
- $262.agent.getReport
|
|
- $262.agent.safeBroadcastAsync
|
|
- $262.agent.safeBroadcast
|
|
- $262.agent.setTimeout
|
|
- $262.agent.tryYield
|
|
- $262.agent.trySleep
|
|
---*/
|
|
|
|
/**
|
|
* @return {String} A report sent from an agent.
|
|
*/
|
|
{
|
|
// This is only necessary because the original
|
|
// $262.agent.getReport API was insufficient.
|
|
//
|
|
// All runtimes currently have their own
|
|
// $262.agent.getReport which is wrong, so we
|
|
// will pave over it with a corrected version.
|
|
//
|
|
// Binding $262.agent is necessary to prevent
|
|
// breaking SpiderMonkey's $262.agent.getReport
|
|
let getReport = $262.agent.getReport.bind($262.agent);
|
|
|
|
$262.agent.getReport = function() {
|
|
var r;
|
|
while ((r = getReport()) == null) {
|
|
$262.agent.sleep(1);
|
|
}
|
|
return r;
|
|
};
|
|
|
|
if (this.setTimeout === undefined) {
|
|
(function(that) {
|
|
that.setTimeout = function(callback, delay) {
|
|
let p = Promise.resolve();
|
|
let start = Date.now();
|
|
let end = start + delay;
|
|
function check() {
|
|
if ((end - Date.now()) > 0) {
|
|
p.then(check);
|
|
}
|
|
else {
|
|
callback();
|
|
}
|
|
}
|
|
p.then(check);
|
|
}
|
|
})(this);
|
|
}
|
|
|
|
$262.agent.setTimeout = setTimeout;
|
|
|
|
$262.agent.getReportAsync = function() {
|
|
return new Promise(function(resolve) {
|
|
(function loop() {
|
|
let result = getReport();
|
|
if (!result) {
|
|
setTimeout(loop, 1000);
|
|
} else {
|
|
resolve(result);
|
|
}
|
|
})();
|
|
});
|
|
};
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Share a given Int32Array or BigInt64Array to all running agents. Ensure that the
|
|
* provided TypedArray is a "shared typed array".
|
|
*
|
|
* NOTE: Migrating all tests to this API is necessary to prevent tests from hanging
|
|
* indefinitely when a SAB is sent to a worker but the code in the worker attempts to
|
|
* create a non-sharable TypedArray (something that is not Int32Array or BigInt64Array).
|
|
* When that scenario occurs, an exception is thrown and the agent worker can no
|
|
* longer communicate with any other threads that control the SAB. If the main
|
|
* thread happens to be spinning in the $262.agent.waitUntil() while loop, it will never
|
|
* meet its termination condition and the test will hang indefinitely.
|
|
*
|
|
* Because we've defined $262.agent.broadcast(SAB) in
|
|
* https://github.com/tc39/test262/blob/HEAD/INTERPRETING.md, there are host implementations
|
|
* that assume compatibility, which must be maintained.
|
|
*
|
|
*
|
|
* $262.agent.safeBroadcast(TA) should not be included in
|
|
* https://github.com/tc39/test262/blob/HEAD/INTERPRETING.md
|
|
*
|
|
*
|
|
* @param {(Int32Array|BigInt64Array)} typedArray An Int32Array or BigInt64Array with a SharedArrayBuffer
|
|
*/
|
|
$262.agent.safeBroadcast = function(typedArray) {
|
|
let Constructor = Object.getPrototypeOf(typedArray).constructor;
|
|
let temp = new Constructor(
|
|
new SharedArrayBuffer(Constructor.BYTES_PER_ELEMENT)
|
|
);
|
|
try {
|
|
// This will never actually wait, but that's fine because we only
|
|
// want to ensure that this typedArray CAN be waited on and is shareable.
|
|
Atomics.wait(temp, 0, Constructor === Int32Array ? 1 : BigInt(1));
|
|
} catch (error) {
|
|
throw new Test262Error(`${Constructor.name} cannot be used as a shared typed array. (${error})`);
|
|
}
|
|
|
|
$262.agent.broadcast(typedArray.buffer);
|
|
};
|
|
|
|
$262.agent.safeBroadcastAsync = async function(ta, index, expected) {
|
|
await $262.agent.broadcast(ta.buffer);
|
|
await $262.agent.waitUntil(ta, index, expected);
|
|
await $262.agent.tryYield();
|
|
return await Atomics.load(ta, index);
|
|
};
|
|
|
|
|
|
/**
|
|
* With a given Int32Array or BigInt64Array, wait until the expected number of agents have
|
|
* reported themselves by calling:
|
|
*
|
|
* Atomics.add(typedArray, index, 1);
|
|
*
|
|
* @param {(Int32Array|BigInt64Array)} typedArray An Int32Array or BigInt64Array with a SharedArrayBuffer
|
|
* @param {number} index The index of which all agents will report.
|
|
* @param {number} expected The number of agents that are expected to report as active.
|
|
*/
|
|
$262.agent.waitUntil = function(typedArray, index, expected) {
|
|
|
|
var agents = 0;
|
|
while ((agents = Atomics.load(typedArray, index)) !== expected) {
|
|
/* nothing */
|
|
}
|
|
assert.sameValue(agents, expected, "Reporting number of 'agents' equals the value of 'expected'");
|
|
};
|
|
|
|
/**
|
|
* Timeout values used throughout the Atomics tests. All timeouts are specified in milliseconds.
|
|
*
|
|
* @property {number} yield Used for `$262.agent.tryYield`. Must not be used in other functions.
|
|
* @property {number} small Used when agents will always timeout and `Atomics.wake` is not part
|
|
* of the test semantics. Must be larger than `$262.agent.timeouts.yield`.
|
|
* @property {number} long Used when some agents may timeout and `Atomics.wake` is called on some
|
|
* agents. The agents are required to wait and this needs to be observable
|
|
* by the main thread.
|
|
* @property {number} huge Used when `Atomics.wake` is called on all waiting agents. The waiting
|
|
* must not timeout. The agents are required to wait and this needs to be
|
|
* observable by the main thread. All waiting agents must be woken by the
|
|
* main thread.
|
|
*
|
|
* Usage for `$262.agent.timeouts.small`:
|
|
* const WAIT_INDEX = 0;
|
|
* const RUNNING = 1;
|
|
* const TIMEOUT = $262.agent.timeouts.small;
|
|
* const i32a = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 2));
|
|
*
|
|
* $262.agent.start(`
|
|
* $262.agent.receiveBroadcast(function(sab) {
|
|
* const i32a = new Int32Array(sab);
|
|
* Atomics.add(i32a, ${RUNNING}, 1);
|
|
*
|
|
* $262.agent.report(Atomics.wait(i32a, ${WAIT_INDEX}, 0, ${TIMEOUT}));
|
|
*
|
|
* $262.agent.leaving();
|
|
* });
|
|
* `);
|
|
* $262.agent.safeBroadcast(i32a.buffer);
|
|
*
|
|
* // Wait until the agent was started and then try to yield control to increase
|
|
* // the likelihood the agent has called `Atomics.wait` and is now waiting.
|
|
* $262.agent.waitUntil(i32a, RUNNING, 1);
|
|
* $262.agent.tryYield();
|
|
*
|
|
* // The agent is expected to time out.
|
|
* assert.sameValue($262.agent.getReport(), "timed-out");
|
|
*
|
|
*
|
|
* Usage for `$262.agent.timeouts.long`:
|
|
* const WAIT_INDEX = 0;
|
|
* const RUNNING = 1;
|
|
* const NUMAGENT = 2;
|
|
* const TIMEOUT = $262.agent.timeouts.long;
|
|
* const i32a = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 2));
|
|
*
|
|
* for (let i = 0; i < NUMAGENT; i++) {
|
|
* $262.agent.start(`
|
|
* $262.agent.receiveBroadcast(function(sab) {
|
|
* const i32a = new Int32Array(sab);
|
|
* Atomics.add(i32a, ${RUNNING}, 1);
|
|
*
|
|
* $262.agent.report(Atomics.wait(i32a, ${WAIT_INDEX}, 0, ${TIMEOUT}));
|
|
*
|
|
* $262.agent.leaving();
|
|
* });
|
|
* `);
|
|
* }
|
|
* $262.agent.safeBroadcast(i32a.buffer);
|
|
*
|
|
* // Wait until the agents were started and then try to yield control to increase
|
|
* // the likelihood the agents have called `Atomics.wait` and are now waiting.
|
|
* $262.agent.waitUntil(i32a, RUNNING, NUMAGENT);
|
|
* $262.agent.tryYield();
|
|
*
|
|
* // Wake exactly one agent.
|
|
* assert.sameValue(Atomics.wake(i32a, WAIT_INDEX, 1), 1);
|
|
*
|
|
* // When it doesn't matter how many agents were woken at once, a while loop
|
|
* // can be used to make the test more resilient against intermittent failures
|
|
* // in case even though `tryYield` was called, the agents haven't started to
|
|
* // wait.
|
|
* //
|
|
* // // Repeat until exactly one agent was woken.
|
|
* // var woken = 0;
|
|
* // while ((woken = Atomics.wake(i32a, WAIT_INDEX, 1)) !== 0) ;
|
|
* // assert.sameValue(woken, 1);
|
|
*
|
|
* // One agent was woken and the other one timed out.
|
|
* const reports = [$262.agent.getReport(), $262.agent.getReport()];
|
|
* assert(reports.includes("ok"));
|
|
* assert(reports.includes("timed-out"));
|
|
*
|
|
*
|
|
* Usage for `$262.agent.timeouts.huge`:
|
|
* const WAIT_INDEX = 0;
|
|
* const RUNNING = 1;
|
|
* const NUMAGENT = 2;
|
|
* const TIMEOUT = $262.agent.timeouts.huge;
|
|
* const i32a = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 2));
|
|
*
|
|
* for (let i = 0; i < NUMAGENT; i++) {
|
|
* $262.agent.start(`
|
|
* $262.agent.receiveBroadcast(function(sab) {
|
|
* const i32a = new Int32Array(sab);
|
|
* Atomics.add(i32a, ${RUNNING}, 1);
|
|
*
|
|
* $262.agent.report(Atomics.wait(i32a, ${WAIT_INDEX}, 0, ${TIMEOUT}));
|
|
*
|
|
* $262.agent.leaving();
|
|
* });
|
|
* `);
|
|
* }
|
|
* $262.agent.safeBroadcast(i32a.buffer);
|
|
*
|
|
* // Wait until the agents were started and then try to yield control to increase
|
|
* // the likelihood the agents have called `Atomics.wait` and are now waiting.
|
|
* $262.agent.waitUntil(i32a, RUNNING, NUMAGENT);
|
|
* $262.agent.tryYield();
|
|
*
|
|
* // Wake all agents.
|
|
* assert.sameValue(Atomics.wake(i32a, WAIT_INDEX), NUMAGENT);
|
|
*
|
|
* // When it doesn't matter how many agents were woken at once, a while loop
|
|
* // can be used to make the test more resilient against intermittent failures
|
|
* // in case even though `tryYield` was called, the agents haven't started to
|
|
* // wait.
|
|
* //
|
|
* // // Repeat until all agents were woken.
|
|
* // for (var wokenCount = 0; wokenCount < NUMAGENT; ) {
|
|
* // var woken = 0;
|
|
* // while ((woken = Atomics.wake(i32a, WAIT_INDEX)) !== 0) ;
|
|
* // // Maybe perform an action on the woken agents here.
|
|
* // wokenCount += woken;
|
|
* // }
|
|
*
|
|
* // All agents were woken and none timeout.
|
|
* for (var i = 0; i < NUMAGENT; i++) {
|
|
* assert($262.agent.getReport(), "ok");
|
|
* }
|
|
*/
|
|
$262.agent.timeouts = {
|
|
yield: 100,
|
|
small: 200,
|
|
long: 1000,
|
|
huge: 10000,
|
|
};
|
|
|
|
/**
|
|
* Try to yield control to the agent threads.
|
|
*
|
|
* Usage:
|
|
* const VALUE = 0;
|
|
* const RUNNING = 1;
|
|
* const i32a = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 2));
|
|
*
|
|
* $262.agent.start(`
|
|
* $262.agent.receiveBroadcast(function(sab) {
|
|
* const i32a = new Int32Array(sab);
|
|
* Atomics.add(i32a, ${RUNNING}, 1);
|
|
*
|
|
* Atomics.store(i32a, ${VALUE}, 1);
|
|
*
|
|
* $262.agent.leaving();
|
|
* });
|
|
* `);
|
|
* $262.agent.safeBroadcast(i32a.buffer);
|
|
*
|
|
* // Wait until agent was started and then try to yield control.
|
|
* $262.agent.waitUntil(i32a, RUNNING, 1);
|
|
* $262.agent.tryYield();
|
|
*
|
|
* // Note: This result is not guaranteed, but should hold in practice most of the time.
|
|
* assert.sameValue(Atomics.load(i32a, VALUE), 1);
|
|
*
|
|
* The default implementation simply waits for `$262.agent.timeouts.yield` milliseconds.
|
|
*/
|
|
$262.agent.tryYield = function() {
|
|
$262.agent.sleep($262.agent.timeouts.yield);
|
|
};
|
|
|
|
/**
|
|
* Try to sleep the current agent for the given amount of milliseconds. It is acceptable,
|
|
* but not encouraged, to ignore this sleep request and directly continue execution.
|
|
*
|
|
* The default implementation calls `$262.agent.sleep(ms)`.
|
|
*
|
|
* @param {number} ms Time to sleep in milliseconds.
|
|
*/
|
|
$262.agent.trySleep = function(ms) {
|
|
$262.agent.sleep(ms);
|
|
};
|