test262/harness/atomicsHelper.js
Rick Waldron e4883091b9 Introduce $262.agent.safeBroadcast and migrate Atomics tests.
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/master/INTERPRETING.md, there are host implementations that assume compatibility, which must be maintained.
2018-11-20 15:17:47 -05:00

275 lines
9.9 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.
---*/
/**
* @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;
};
}
/**
*
* 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/master/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/master/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) {
$ERROR(`${Constructor.name} cannot be used as a shared typed array. (${error})`);
}
$262.agent.broadcast(typedArray.buffer);
};
/**
* 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);
};