-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move
emscripten_atomic_wait_async
to atomic.h and add core testing (#…
…20404) This API was orininally added for wasm workers but is not wasm worker specific. Followup to #20381.
- Loading branch information
Showing
22 changed files
with
397 additions
and
302 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
/** | ||
* @license | ||
* Copyright 2023 The Emscripten Authors | ||
* SPDX-License-Identifier: MIT | ||
*/ | ||
|
||
assert(SHARED_MEMORY); | ||
|
||
addToLibrary({ | ||
// Chrome 87 (and hence Edge 87) shipped Atomics.waitAsync: | ||
// https://www.chromestatus.com/feature/6243382101803008 | ||
// However its implementation is faulty: | ||
// https://bugs.chromium.org/p/chromium/issues/detail?id=1167541 | ||
// Firefox Nightly 86.0a1 (2021-01-15) does not yet have it: | ||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1467846 | ||
// And at the time of writing, no other browser has it either. | ||
#if MIN_EDGE_VERSION < 91 || MIN_CHROME_VERSION < 91 || MIN_SAFARI_VERSION != TARGET_NOT_SUPPORTED || MIN_FIREFOX_VERSION != TARGET_NOT_SUPPORTED || ENVIRONMENT_MAY_BE_NODE | ||
// Partially polyfill Atomics.waitAsync() if not available in the browser. | ||
// Also polyfill for old Chrome-based browsers, where Atomics.waitAsync is | ||
// broken until Chrome 91, see: | ||
// https://bugs.chromium.org/p/chromium/issues/detail?id=1167541 | ||
// https://github.com/tc39/proposal-atomics-wait-async/blob/master/PROPOSAL.md | ||
// This polyfill performs polling with setTimeout() to observe a change in the | ||
// target memory location. | ||
$polyfillWaitAsync__postset: `if (!Atomics.waitAsync || (typeof navigator !== 'undefined' && navigator.userAgent && jstoi_q((navigator.userAgent.match(/Chrom(e|ium)\\/([0-9]+)\\./)||[])[2]) < 91)) { | ||
let __Atomics_waitAsyncAddresses = [/*[i32a, index, value, maxWaitMilliseconds, promiseResolve]*/]; | ||
function __Atomics_pollWaitAsyncAddresses() { | ||
let now = performance.now(); | ||
let l = __Atomics_waitAsyncAddresses.length; | ||
for (let i = 0; i < l; ++i) { | ||
let a = __Atomics_waitAsyncAddresses[i]; | ||
let expired = (now > a[3]); | ||
let awoken = (Atomics.load(a[0], a[1]) != a[2]); | ||
if (expired || awoken) { | ||
__Atomics_waitAsyncAddresses[i--] = __Atomics_waitAsyncAddresses[--l]; | ||
__Atomics_waitAsyncAddresses.length = l; | ||
a[4](awoken ? 'ok': 'timed-out'); | ||
} | ||
} | ||
if (l) { | ||
// If we still have addresses to wait, loop the timeout handler to continue polling. | ||
setTimeout(__Atomics_pollWaitAsyncAddresses, 10); | ||
} | ||
} | ||
#if ASSERTIONS && WASM_WORKERS | ||
if (!ENVIRONMENT_IS_WASM_WORKER) err('Current environment does not support Atomics.waitAsync(): polyfilling it, but this is going to be suboptimal.'); | ||
#endif | ||
/** | ||
* @param {number=} maxWaitMilliseconds | ||
*/ | ||
Atomics.waitAsync = (i32a, index, value, maxWaitMilliseconds) => { | ||
let val = Atomics.load(i32a, index); | ||
if (val != value) return { async: false, value: 'not-equal' }; | ||
if (maxWaitMilliseconds <= 0) return { async: false, value: 'timed-out' }; | ||
maxWaitMilliseconds = performance.now() + (maxWaitMilliseconds || Infinity); | ||
let promiseResolve; | ||
let promise = new Promise((resolve) => { promiseResolve = resolve; }); | ||
if (!__Atomics_waitAsyncAddresses[0]) setTimeout(__Atomics_pollWaitAsyncAddresses, 10); | ||
__Atomics_waitAsyncAddresses.push([i32a, index, value, maxWaitMilliseconds, promiseResolve]); | ||
return { async: true, value: promise }; | ||
}; | ||
}`, | ||
$polyfillWaitAsync__deps: ['$jstoi_q'], | ||
#endif | ||
|
||
$polyfillWaitAsync__internal: true, | ||
$polyfillWaitAsync: () => { | ||
// nop, used for its postset to ensure `Atomics.waitAsync()` polyfill is | ||
// included exactly once and only included when needed. | ||
// Any function using Atomics.waitAsync should depend on this. | ||
}, | ||
|
||
$atomicWaitStates__internal: true, | ||
$atomicWaitStates: ['ok', 'not-equal', 'timed-out'], | ||
$liveAtomicWaitAsyncs: {}, | ||
$liveAtomicWaitAsyncs__internal: true, | ||
$liveAtomicWaitAsyncCounter: 0, | ||
$liveAtomicWaitAsyncCounter__internal: true, | ||
|
||
emscripten_atomic_wait_async__deps: ['$atomicWaitStates', '$liveAtomicWaitAsyncs', '$liveAtomicWaitAsyncCounter', '$polyfillWaitAsync', '$callUserCallback'], | ||
emscripten_atomic_wait_async: (addr, val, asyncWaitFinished, userData, maxWaitMilliseconds) => { | ||
let wait = Atomics.waitAsync(HEAP32, {{{ getHeapOffset('addr', 'i32') }}}, val, maxWaitMilliseconds); | ||
if (!wait.async) return atomicWaitStates.indexOf(wait.value); | ||
// Increment waitAsync generation counter, account for wraparound in case | ||
// application does huge amounts of waitAsyncs per second (not sure if | ||
// possible?) | ||
// Valid counterrange: 0...2^31-1 | ||
let counter = liveAtomicWaitAsyncCounter; | ||
liveAtomicWaitAsyncCounter = Math.max(0, (liveAtomicWaitAsyncCounter+1)|0); | ||
liveAtomicWaitAsyncs[counter] = addr; | ||
{{{ runtimeKeepalivePush() }}} | ||
wait.value.then((value) => { | ||
if (liveAtomicWaitAsyncs[counter]) { | ||
{{{ runtimeKeepalivePop() }}} | ||
delete liveAtomicWaitAsyncs[counter]; | ||
callUserCallback(() => {{{ makeDynCall('vpiip', 'asyncWaitFinished') }}}(addr, val, atomicWaitStates.indexOf(value), userData)); | ||
} | ||
}); | ||
return -counter; | ||
}, | ||
|
||
emscripten_atomic_cancel_wait_async__deps: ['$liveAtomicWaitAsyncs'], | ||
emscripten_atomic_cancel_wait_async: (waitToken) => { | ||
#if ASSERTIONS | ||
if (waitToken == {{{ cDefs.ATOMICS_WAIT_NOT_EQUAL }}}) { | ||
warnOnce('Attempted to call emscripten_atomic_cancel_wait_async() with a value ATOMICS_WAIT_NOT_EQUAL (1) that is not a valid wait token! Check success in return value from call to emscripten_atomic_wait_async()'); | ||
} else if (waitToken == {{{ cDefs.ATOMICS_WAIT_TIMED_OUT }}}) { | ||
warnOnce('Attempted to call emscripten_atomic_cancel_wait_async() with a value ATOMICS_WAIT_TIMED_OUT (2) that is not a valid wait token! Check success in return value from call to emscripten_atomic_wait_async()'); | ||
} else if (waitToken > 0) { | ||
warnOnce(`Attempted to call emscripten_atomic_cancel_wait_async() with an invalid wait token value ${waitToken}`); | ||
} | ||
#endif | ||
var address = liveAtomicWaitAsyncs[waitToken]; | ||
if (address) { | ||
// Notify the waitAsync waiters on the memory location, so that JavaScript | ||
// garbage collection can occur. | ||
// See https://github.com/WebAssembly/threads/issues/176 | ||
// This has the unfortunate effect of causing spurious wakeup of all other | ||
// waiters at the address (which causes a small performance loss). | ||
Atomics.notify(HEAP32, {{{ getHeapOffset('address', 'i32') }}}); | ||
delete liveAtomicWaitAsyncs[waitToken]; | ||
{{{ runtimeKeepalivePop() }}} | ||
return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; | ||
} | ||
// This waitToken does not exist. | ||
return {{{ cDefs.EMSCRIPTEN_RESULT_INVALID_PARAM }}}; | ||
}, | ||
|
||
emscripten_atomic_cancel_all_wait_asyncs__deps: ['$liveAtomicWaitAsyncs'], | ||
emscripten_atomic_cancel_all_wait_asyncs: () => { | ||
let waitAsyncs = Object.values(liveAtomicWaitAsyncs); | ||
waitAsyncs.forEach((address) => { | ||
Atomics.notify(HEAP32, {{{ getHeapOffset('address', 'i32') }}}); | ||
}); | ||
liveAtomicWaitAsyncs = {}; | ||
return waitAsyncs.length; | ||
}, | ||
|
||
emscripten_atomic_cancel_all_wait_asyncs_at_address__deps: ['$liveAtomicWaitAsyncs'], | ||
emscripten_atomic_cancel_all_wait_asyncs_at_address: (address) => { | ||
let numCancelled = 0; | ||
Object.keys(liveAtomicWaitAsyncs).forEach((waitToken) => { | ||
if (liveAtomicWaitAsyncs[waitToken] == address) { | ||
Atomics.notify(HEAP32, {{{ getHeapOffset('address', 'i32') }}}); | ||
delete liveAtomicWaitAsyncs[waitToken]; | ||
numCancelled++; | ||
} | ||
}); | ||
return numCancelled; | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.