Navigation Menu

Skip to content

Commit

Permalink
[JSC] Add support for Atomics.waitAsync
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=241414
rdar://94655073

Reviewed by Yusuke Suzuki.

Atomics.waitAsync() waits asynchronously on a shared array buffer and
returns an object { async: bool, value: Promise }. Compare to Atomics.wait(),
waitAsync is non-blocking and usable on the main thread.

TC39 Spec: https://tc39.es/proposal-atomics-wait-async/
TC39 Proposal: https://github.com/tc39/proposal-atomics-wait-async
MDN Web Doc: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/waitAsync

* JSTests/stress/settimeout-starvation.js: Added.
(let.promise.new.Promise):
(wait):
* JSTests/test262/config.yaml:
* Source/JavaScriptCore/jsc.cpp:
(JSC_DEFINE_HOST_FUNCTION):
* Source/JavaScriptCore/runtime/AtomicsObject.cpp:
(JSC::atomicsWaitImpl):
(JSC::JSC_DEFINE_HOST_FUNCTION):
* Source/JavaScriptCore/runtime/Intrinsic.cpp:
(JSC::intrinsicName):
* Source/JavaScriptCore/runtime/Intrinsic.h:
* Source/JavaScriptCore/runtime/JSArrayBufferView.h:
(JSC::JSArrayBufferView::hasArrayBuffer const):
* Source/JavaScriptCore/runtime/JSGlobalObject.h:
* Source/JavaScriptCore/runtime/SimpleTypedArrayController.cpp:
(JSC::SimpleTypedArrayController::isAtomicsWaitAsyncAllowedOnCurrentThread):
* Source/JavaScriptCore/runtime/SimpleTypedArrayController.h:
* Source/JavaScriptCore/runtime/TypedArrayController.h:
* Source/JavaScriptCore/runtime/VM.cpp:
(JSC::VM::~VM):
* Source/JavaScriptCore/runtime/WaiterListsManager.h: Added.
(JSC::Waiter::Waiter):
(JSC::Waiter::isAsync const):
(JSC::Waiter::getVM const):
(JSC::Waiter::getPromise const):
(JSC::Waiter::getTicket const):
(JSC::Waiter::dump const):
(JSC::WaiterList::enqueue):
(JSC::WaiterList::dequeue):
(JSC::WaiterList::takeFirst):
(JSC::WaiterList::isEmpty):
(JSC::WaiterList::dump const):
(JSC::WaiterListsManager::singleton):
(JSC::WaiterListsManager::addWaiter):
(JSC::WaiterListsManager::notifyWaiter):
(JSC::WaiterListsManager::timeoutAsyncWaiter):
(JSC::WaiterListsManager::unregister):
(JSC::WaiterListsManager::dump const):
(JSC::WaiterListsManager::RegisteredVMs::add):
(JSC::WaiterListsManager::RegisteredVMs::remove):
(JSC::WaiterListsManager::RegisteredVMs::contains):
(JSC::WaiterListsManager::RegisteredVMs::dump const):
(JSC::WaiterListsManager::notifyWaiterImpl):
(JSC::WaiterListsManager::add):
(JSC::WaiterListsManager::find):
* Source/WebCore/bindings/js/WebCoreTypedArrayController.cpp:
(WebCore::WebCoreTypedArrayController::isAtomicsWaitAsyncAllowedOnCurrentThread):
* Source/WebCore/bindings/js/WebCoreTypedArrayController.h:

Canonical link: https://commits.webkit.org/257061@main
  • Loading branch information
Yijia Huang committed Nov 28, 2022
1 parent c8bde1b commit 7e3fb31
Show file tree
Hide file tree
Showing 32 changed files with 1,283 additions and 50 deletions.
13 changes: 13 additions & 0 deletions JSTests/stress/SharedArrayBuffer.js
Expand Up @@ -85,6 +85,8 @@ for (bad of [void 0, null, false, true, 1, 0.5, Symbol(), {}, "hello", dv, u8ca,
for (bad of [void 0, null, false, true, 1, 0.5, Symbol(), {}, "hello", dv, i8a, i16a, u8a, u8ca, u16a, u32a, f32a, f64a]) {
shouldFail(() => Atomics.notify(bad, 0, 0), TypeError);
shouldFail(() => Atomics.wait(bad, 0, 0), TypeError);
shouldFail(() => Atomics.waitAsync(bad, 0, 0), TypeError);
shouldFail(() => waiterListSize(bad, 0), TypeError);
}

for (idx of [-1, -1000000000000, 10000, 10000000000000]) {
Expand All @@ -101,6 +103,8 @@ for (idx of [-1, -1000000000000, 10000, 10000000000000]) {
}
shouldFail(() => Atomics.notify(i32a, idx, 0), RangeError);
shouldFail(() => Atomics.wait(i32a, idx, 0), RangeError);
shouldFail(() => Atomics.waitAsync(i32a, idx, 0), RangeError);
shouldFail(() => waiterListSize(i32a, idx), RangeError);
}

for (idx of ["hello"]) {
Expand All @@ -117,6 +121,7 @@ for (idx of ["hello"]) {
}
shouldSucceed(() => Atomics.notify(i32a, idx, 0));
shouldSucceed(() => Atomics.wait(i32a, idx, 0, 1));
shouldSucceed(() => Atomics.waitAsync(i32a, idx, 0, 1));
}

function runAtomic(array, index, init, name, args, expectedResult, expectedOutcome)
Expand Down Expand Up @@ -146,9 +151,17 @@ i32a[0] = 0;
var result = Atomics.wait(i32a, 0, 1);
if (result != "not-equal")
throw "Error: bad result from Atomics.wait: " + result;

var result = Atomics.waitAsync(i32a, 0, 1);
if (result.value != "not-equal")
throw "Error: bad result from Atomics.waitAsync: " + result;

for (timeout of [0, 1, 10]) {
var result = Atomics.wait(i32a, 0, 0, timeout);
if (result != "timed-out")
throw "Error: bad result from Atomics.wait: " + result;
}

var result = Atomics.waitAsync(i32a, 0, 0, 0);
if (result.value != "timed-out")
throw "Error: bad result from Atomics.waitAsync: " + result;
4 changes: 4 additions & 0 deletions JSTests/stress/bigint-atomics-fail.js
Expand Up @@ -20,6 +20,10 @@ shouldThrow(() => {
Atomics.wait(u64a, 0, 0n);
}, `TypeError: Typed array argument must be an Int32Array or BigInt64Array.`);

shouldThrow(() => {
Atomics.waitAsync(u64a, 0, 0n);
}, `TypeError: Typed array argument must be an Int32Array or BigInt64Array.`);

shouldThrow(() => {
Atomics.load(new Float64Array(8), 0);
}, `TypeError: Typed array argument must be an Int8Array, Int16Array, Int32Array, Uint8Array, Uint16Array, Uint32Array, BigInt64Array, or BigUint64Array.`);
13 changes: 13 additions & 0 deletions JSTests/stress/settimeout-starvation.js
@@ -0,0 +1,13 @@
let promise = new Promise((resolve, _) => {
setTimeout(() => { resolve("timed-out") }, 1);
});
let outcome = null;

(function wait() {
if (outcome === "timed-out") {
return;
}
setTimeout(wait, 0);
})();

promise.then(result => { outcome = result; });
14 changes: 14 additions & 0 deletions JSTests/stress/shared-array-buffer-bigint.js
Expand Up @@ -22,6 +22,8 @@ function shouldSucceed(f)
for (bad of [bu64a]) {
shouldFail(() => Atomics.notify(bad, 0, 0n), TypeError);
shouldFail(() => Atomics.wait(bad, 0, 0n), TypeError);
shouldFail(() => Atomics.waitAsync(bad, 0, 0n), TypeError);
shouldFail(() => waiterListSize(bad, 0), TypeError);
}

for (idx of [-1, -1000000000000, 10000, 10000000000000]) {
Expand All @@ -38,6 +40,8 @@ for (idx of [-1, -1000000000000, 10000, 10000000000000]) {
}
shouldFail(() => Atomics.notify(bi64a, idx, 0n), RangeError);
shouldFail(() => Atomics.wait(bi64a, idx, 0n), RangeError);
shouldFail(() => Atomics.waitAsync(bi64a, idx, 0n), RangeError);
shouldFail(() => waiterListSize(bi64a, idx), RangeError);
}

for (idx of ["hello"]) {
Expand All @@ -54,6 +58,7 @@ for (idx of ["hello"]) {
}
shouldSucceed(() => Atomics.notify(bi64a, idx, 0));
shouldSucceed(() => Atomics.wait(bi64a, idx, 0n, 1));
shouldSucceed(() => Atomics.waitAsync(bi64a, idx, 0n, 1));
}

function runAtomic(array, index, init, name, args, expectedResult, expectedOutcome)
Expand Down Expand Up @@ -83,8 +88,17 @@ bi64a[0] = 0n;
var result = Atomics.wait(bi64a, 0, 1n);
if (result != "not-equal")
throw "Error: bad result from Atomics.wait: " + result;

var result = Atomics.waitAsync(bi64a, 0, 1n);
if (result.value != "not-equal")
throw "Error: bad result from Atomics.waitAsync: " + result;

for (timeout of [0, 1, 10]) {
var result = Atomics.wait(bi64a, 0, 0n, timeout);
if (result != "timed-out")
throw "Error: bad result from Atomics.wait: " + result;
}

var result = Atomics.waitAsync(bi64a, 0, 0n, 0);
if (result.value != "timed-out")
throw "Error: bad result from Atomics.waitAsync: " + result;
75 changes: 75 additions & 0 deletions JSTests/stress/waitasync-notify-multi-workers.js
@@ -0,0 +1,75 @@
var sab = new SharedArrayBuffer(3 * 4);
var i32a = new Int32Array(sab);

var numWorkers = 0;
function startWorker(code) {
numWorkers++;
$.agent.start(code);
}

const WAIT_INDEX = 0;
const READY_INDEX_A = 1;
const READY_INDEX_B = 2;
const NOTIFY_COUNT = 2;

startWorker(`
$.agent.receiveBroadcast(async (sab) => {
var i32a = new Int32Array(sab);
let p = Atomics.waitAsync(i32a, ${WAIT_INDEX}, 0, undefined).value;
Atomics.store(i32a, ${READY_INDEX_A}, 1);
Atomics.notify(i32a, ${READY_INDEX_A});
let res = await p;
if (res !== 'ok')
throw new Error("A resolve: " + res);
$.agent.report("done");
});
`);

startWorker(`
$.agent.receiveBroadcast(async (sab) => {
var i32a = new Int32Array(sab);
let p = Atomics.waitAsync(i32a, ${WAIT_INDEX}, 0, undefined).value;
Atomics.store(i32a, ${READY_INDEX_B}, 1);
Atomics.notify(i32a, ${READY_INDEX_B});
let res = await p;
if (res !== 'ok')
throw new Error("B resolve: " + res);
$.agent.report("done");
});
`);

startWorker(`
$.agent.receiveBroadcast((sab) => {
var i32a = new Int32Array(sab);
Atomics.wait(i32a, ${READY_INDEX_A}, 0);
Atomics.wait(i32a, ${READY_INDEX_B}, 0);
let res = Atomics.notify(i32a, ${WAIT_INDEX}, ${NOTIFY_COUNT});
if (res !== 2)
throw new Error("C notified workers: " + res);
$.agent.report("done");
});
`);

$.agent.broadcast(sab);

for (; ;) {
var report = waitForReport();
if (report == "done") {
if (!--numWorkers) {
print("All workers done!");
break;
}
} else
print("report: " + report);
}
57 changes: 57 additions & 0 deletions JSTests/stress/waitasync-promise-timeout-finite-gc.js
@@ -0,0 +1,57 @@
var sab = new SharedArrayBuffer(3 * 4);
var i32a = new Int32Array(sab);

var numWorkers = 0;
function startWorker(code) {
numWorkers++;
$.agent.start(code);
}

const WAIT_INDEX = 0;
const TOTAL_WAITER_COUNT = 1;

startWorker(`
$.agent.receiveBroadcast((sab) => {
(function() {
var i32a = new Int32Array(sab);
Atomics.waitAsync(i32a, ${WAIT_INDEX}, 0, 100).value.then((value) => {
$.agent.report("value " + value);
$.agent.report("done");
},
() => {
$.agent.report("error");
});
})();
gc();
});
`);

$.agent.broadcast(sab);

let waiters = 0;
do {
waiters = waiterListSize(i32a, WAIT_INDEX);
} while (waiters != TOTAL_WAITER_COUNT);

if (Atomics.notify(i32a, WAIT_INDEX, 1) != TOTAL_WAITER_COUNT)
throw new Error(`Atomics.notify should return ${TOTAL_WAITER_COUNT}.`);

for (; ;) {
var report = waitForReport();
if (report == "done") {
if (!--numWorkers) {
print("All workers done!");
break;
}
} else if (report.startsWith('value')) {
if (report != 'value ok')
throw new Error("The promise should be resolved with ok");
} else
print("report: " + report);
}

if (waiterListSize(i32a, WAIT_INDEX) != 0)
throw new Error("The WaiterList should be empty.");
57 changes: 57 additions & 0 deletions JSTests/stress/waitasync-promise-timeout-infinity-gc.js
@@ -0,0 +1,57 @@
var sab = new SharedArrayBuffer(3 * 4);
var i32a = new Int32Array(sab);

var numWorkers = 0;
function startWorker(code) {
numWorkers++;
$.agent.start(code);
}

const WAIT_INDEX = 0;
const TOTAL_WAITER_COUNT = 1;

startWorker(`
$.agent.receiveBroadcast((sab) => {
(function() {
var i32a = new Int32Array(sab);
Atomics.waitAsync(i32a, ${WAIT_INDEX}, 0).value.then((value) => {
$.agent.report("value " + value);
$.agent.report("done");
},
() => {
$.agent.report("error");
});
})();
gc();
});
`);

$.agent.broadcast(sab);

let waiters = 0;
do {
waiters = waiterListSize(i32a, WAIT_INDEX);
} while (waiters != TOTAL_WAITER_COUNT);

if (Atomics.notify(i32a, WAIT_INDEX, 1) != TOTAL_WAITER_COUNT)
throw new Error(`Atomics.notify should return ${TOTAL_WAITER_COUNT}.`);

for (; ;) {
var report = waitForReport();
if (report == "done") {
if (!--numWorkers) {
print("All workers done!");
break;
}
} else if (report.startsWith('value')) {
if (report != 'value ok')
throw new Error("The promise should be resolved with ok");
} else
print("report: " + report);
}

if (waiterListSize(i32a, WAIT_INDEX) != 0)
throw new Error("The WaiterList should be empty.");
48 changes: 48 additions & 0 deletions JSTests/stress/waitasync-timeout-finite-gc.js
@@ -0,0 +1,48 @@
var sab = new SharedArrayBuffer(3 * 4);
var i32a = new Int32Array(sab);

var numWorkers = 0;
function startWorker(code) {
numWorkers++;
$.agent.start(code);
}

const WAIT_INDEX = 0;

startWorker(`
$.agent.receiveBroadcast((unused_sab) => {
(function() {
var sab = new SharedArrayBuffer(8 * 4);
var i32a = new Int32Array(sab);
Atomics.waitAsync(i32a, ${WAIT_INDEX}, 0, 1).value.then((value) => {
$.agent.report("value " + value);
$.agent.report("done");
},
() => {
$.agent.report("error");
});
})();
gc();
});
`);

$.agent.broadcast(sab);

for (; ;) {
var report = waitForReport();
if (report == "done") {
if (!--numWorkers) {
print("All workers done!");
break;
}
} else if (report.startsWith('value')) {
if (report != 'value timed-out')
throw new Error("The promise should be resolved with timed-out");
} else
print("report: " + report);
}

if (waiterListSize(i32a, WAIT_INDEX) != 0)
throw new Error("The WaiterList should be empty.");

0 comments on commit 7e3fb31

Please sign in to comment.