// 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.getReport - $262.agent.safeBroadcast - $262.agent.tryYield - $262.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; }; } /** * * 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); };