|
| 1 | +/** |
| 2 | + * @license |
| 3 | + * Copyright 2023 The Emscripten Authors |
| 4 | + * SPDX-License-Identifier: MIT |
| 5 | + */ |
| 6 | + |
| 7 | +assert(SHARED_MEMORY); |
| 8 | + |
| 9 | +addToLibrary({ |
| 10 | +// Chrome 87 (and hence Edge 87) shipped Atomics.waitAsync: |
| 11 | +// https://www.chromestatus.com/feature/6243382101803008 |
| 12 | +// However its implementation is faulty: |
| 13 | +// https://bugs.chromium.org/p/chromium/issues/detail?id=1167541 |
| 14 | +// Firefox Nightly 86.0a1 (2021-01-15) does not yet have it: |
| 15 | +// https://bugzilla.mozilla.org/show_bug.cgi?id=1467846 |
| 16 | +// And at the time of writing, no other browser has it either. |
| 17 | +#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 |
| 18 | + // Partially polyfill Atomics.waitAsync() if not available in the browser. |
| 19 | + // Also polyfill for old Chrome-based browsers, where Atomics.waitAsync is |
| 20 | + // broken until Chrome 91, see: |
| 21 | + // https://bugs.chromium.org/p/chromium/issues/detail?id=1167541 |
| 22 | + // https://github.com/tc39/proposal-atomics-wait-async/blob/master/PROPOSAL.md |
| 23 | + // This polyfill performs polling with setTimeout() to observe a change in the |
| 24 | + // target memory location. |
| 25 | + $polyfillWaitAsync__postset: `if (!Atomics.waitAsync || (typeof navigator !== 'undefined' && navigator.userAgent && jstoi_q((navigator.userAgent.match(/Chrom(e|ium)\\/([0-9]+)\\./)||[])[2]) < 91)) { |
| 26 | + let __Atomics_waitAsyncAddresses = [/*[i32a, index, value, maxWaitMilliseconds, promiseResolve]*/]; |
| 27 | + function __Atomics_pollWaitAsyncAddresses() { |
| 28 | + let now = performance.now(); |
| 29 | + let l = __Atomics_waitAsyncAddresses.length; |
| 30 | + for (let i = 0; i < l; ++i) { |
| 31 | + let a = __Atomics_waitAsyncAddresses[i]; |
| 32 | + let expired = (now > a[3]); |
| 33 | + let awoken = (Atomics.load(a[0], a[1]) != a[2]); |
| 34 | + if (expired || awoken) { |
| 35 | + __Atomics_waitAsyncAddresses[i--] = __Atomics_waitAsyncAddresses[--l]; |
| 36 | + __Atomics_waitAsyncAddresses.length = l; |
| 37 | + a[4](awoken ? 'ok': 'timed-out'); |
| 38 | + } |
| 39 | + } |
| 40 | + if (l) { |
| 41 | + // If we still have addresses to wait, loop the timeout handler to continue polling. |
| 42 | + setTimeout(__Atomics_pollWaitAsyncAddresses, 10); |
| 43 | + } |
| 44 | + } |
| 45 | + #if ASSERTIONS && WASM_WORKERS |
| 46 | + if (!ENVIRONMENT_IS_WASM_WORKER) err('Current environment does not support Atomics.waitAsync(): polyfilling it, but this is going to be suboptimal.'); |
| 47 | + #endif |
| 48 | + /** |
| 49 | + * @param {number=} maxWaitMilliseconds |
| 50 | + */ |
| 51 | + Atomics.waitAsync = (i32a, index, value, maxWaitMilliseconds) => { |
| 52 | + let val = Atomics.load(i32a, index); |
| 53 | + if (val != value) return { async: false, value: 'not-equal' }; |
| 54 | + if (maxWaitMilliseconds <= 0) return { async: false, value: 'timed-out' }; |
| 55 | + maxWaitMilliseconds = performance.now() + (maxWaitMilliseconds || Infinity); |
| 56 | + let promiseResolve; |
| 57 | + let promise = new Promise((resolve) => { promiseResolve = resolve; }); |
| 58 | + if (!__Atomics_waitAsyncAddresses[0]) setTimeout(__Atomics_pollWaitAsyncAddresses, 10); |
| 59 | + __Atomics_waitAsyncAddresses.push([i32a, index, value, maxWaitMilliseconds, promiseResolve]); |
| 60 | + return { async: true, value: promise }; |
| 61 | + }; |
| 62 | +}`, |
| 63 | + $polyfillWaitAsync__deps: ['$jstoi_q'], |
| 64 | +#endif |
| 65 | + |
| 66 | + $polyfillWaitAsync__internal: true, |
| 67 | + $polyfillWaitAsync: () => { |
| 68 | + // nop, used for its postset to ensure `Atomics.waitAsync()` polyfill is |
| 69 | + // included exactly once and only included when needed. |
| 70 | + // Any function using Atomics.waitAsync should depend on this. |
| 71 | + }, |
| 72 | + |
| 73 | + $atomicWaitStates__internal: true, |
| 74 | + $atomicWaitStates: ['ok', 'not-equal', 'timed-out'], |
| 75 | + $liveAtomicWaitAsyncs: {}, |
| 76 | + $liveAtomicWaitAsyncs__internal: true, |
| 77 | + $liveAtomicWaitAsyncCounter: 0, |
| 78 | + $liveAtomicWaitAsyncCounter__internal: true, |
| 79 | + |
| 80 | + emscripten_atomic_wait_async__deps: ['$atomicWaitStates', '$liveAtomicWaitAsyncs', '$liveAtomicWaitAsyncCounter', '$polyfillWaitAsync', '$callUserCallback'], |
| 81 | + emscripten_atomic_wait_async: (addr, val, asyncWaitFinished, userData, maxWaitMilliseconds) => { |
| 82 | + let wait = Atomics.waitAsync(HEAP32, {{{ getHeapOffset('addr', 'i32') }}}, val, maxWaitMilliseconds); |
| 83 | + if (!wait.async) return atomicWaitStates.indexOf(wait.value); |
| 84 | + // Increment waitAsync generation counter, account for wraparound in case |
| 85 | + // application does huge amounts of waitAsyncs per second (not sure if |
| 86 | + // possible?) |
| 87 | + // Valid counterrange: 0...2^31-1 |
| 88 | + let counter = liveAtomicWaitAsyncCounter; |
| 89 | + liveAtomicWaitAsyncCounter = Math.max(0, (liveAtomicWaitAsyncCounter+1)|0); |
| 90 | + liveAtomicWaitAsyncs[counter] = addr; |
| 91 | + {{{ runtimeKeepalivePush() }}} |
| 92 | + wait.value.then((value) => { |
| 93 | + if (liveAtomicWaitAsyncs[counter]) { |
| 94 | + {{{ runtimeKeepalivePop() }}} |
| 95 | + delete liveAtomicWaitAsyncs[counter]; |
| 96 | + callUserCallback(() => {{{ makeDynCall('vpiip', 'asyncWaitFinished') }}}(addr, val, atomicWaitStates.indexOf(value), userData)); |
| 97 | + } |
| 98 | + }); |
| 99 | + return -counter; |
| 100 | + }, |
| 101 | + |
| 102 | + emscripten_atomic_cancel_wait_async__deps: ['$liveAtomicWaitAsyncs'], |
| 103 | + emscripten_atomic_cancel_wait_async: (waitToken) => { |
| 104 | +#if ASSERTIONS |
| 105 | + if (waitToken == {{{ cDefs.ATOMICS_WAIT_NOT_EQUAL }}}) { |
| 106 | + 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()'); |
| 107 | + } else if (waitToken == {{{ cDefs.ATOMICS_WAIT_TIMED_OUT }}}) { |
| 108 | + 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()'); |
| 109 | + } else if (waitToken > 0) { |
| 110 | + warnOnce(`Attempted to call emscripten_atomic_cancel_wait_async() with an invalid wait token value ${waitToken}`); |
| 111 | + } |
| 112 | +#endif |
| 113 | + var address = liveAtomicWaitAsyncs[waitToken]; |
| 114 | + if (address) { |
| 115 | + // Notify the waitAsync waiters on the memory location, so that JavaScript |
| 116 | + // garbage collection can occur. |
| 117 | + // See https://github.com/WebAssembly/threads/issues/176 |
| 118 | + // This has the unfortunate effect of causing spurious wakeup of all other |
| 119 | + // waiters at the address (which causes a small performance loss). |
| 120 | + Atomics.notify(HEAP32, {{{ getHeapOffset('address', 'i32') }}}); |
| 121 | + delete liveAtomicWaitAsyncs[waitToken]; |
| 122 | + {{{ runtimeKeepalivePop() }}} |
| 123 | + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; |
| 124 | + } |
| 125 | + // This waitToken does not exist. |
| 126 | + return {{{ cDefs.EMSCRIPTEN_RESULT_INVALID_PARAM }}}; |
| 127 | + }, |
| 128 | + |
| 129 | + emscripten_atomic_cancel_all_wait_asyncs__deps: ['$liveAtomicWaitAsyncs'], |
| 130 | + emscripten_atomic_cancel_all_wait_asyncs: () => { |
| 131 | + let waitAsyncs = Object.values(liveAtomicWaitAsyncs); |
| 132 | + waitAsyncs.forEach((address) => { |
| 133 | + Atomics.notify(HEAP32, {{{ getHeapOffset('address', 'i32') }}}); |
| 134 | + }); |
| 135 | + liveAtomicWaitAsyncs = {}; |
| 136 | + return waitAsyncs.length; |
| 137 | + }, |
| 138 | + |
| 139 | + emscripten_atomic_cancel_all_wait_asyncs_at_address__deps: ['$liveAtomicWaitAsyncs'], |
| 140 | + emscripten_atomic_cancel_all_wait_asyncs_at_address: (address) => { |
| 141 | + let numCancelled = 0; |
| 142 | + Object.keys(liveAtomicWaitAsyncs).forEach((waitToken) => { |
| 143 | + if (liveAtomicWaitAsyncs[waitToken] == address) { |
| 144 | + Atomics.notify(HEAP32, {{{ getHeapOffset('address', 'i32') }}}); |
| 145 | + delete liveAtomicWaitAsyncs[waitToken]; |
| 146 | + numCancelled++; |
| 147 | + } |
| 148 | + }); |
| 149 | + return numCancelled; |
| 150 | + }, |
| 151 | +}); |
0 commit comments