Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/src/lib/libatomic.js
6171 views
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 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_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
#if ENVIRONMENT_MAY_BE_NODE
26
// Under Deno Atomics.waitAsync currently broken: https://github.com/denoland/deno/issues/14786
27
$waitAsyncPolyfilled: '=(!Atomics.waitAsync || globalThis.Deno || (globalThis.navigator?.userAgent && Number((navigator.userAgent.match(/Chrom(e|ium)\\/([0-9]+)\\./)||[])[2]) < 91));',
28
#else
29
$waitAsyncPolyfilled: '=(!Atomics.waitAsync || (globalThis.navigator?.userAgent && Number((navigator.userAgent.match(/Chrom(e|ium)\\/([0-9]+)\\./)||[])[2]) < 91));',
30
#endif
31
$polyfillWaitAsync__deps: ['$waitAsyncPolyfilled'],
32
$polyfillWaitAsync__postset: `if (waitAsyncPolyfilled) {
33
let __Atomics_waitAsyncAddresses = [/*[i32a, index, value, maxWaitMilliseconds, promiseResolve]*/];
34
function __Atomics_pollWaitAsyncAddresses() {
35
let now = performance.now();
36
let l = __Atomics_waitAsyncAddresses.length;
37
for (let i = 0; i < l; ++i) {
38
let a = __Atomics_waitAsyncAddresses[i];
39
let expired = (now > a[3]);
40
let awoken = (Atomics.load(a[0], a[1]) != a[2]);
41
if (expired || awoken) {
42
__Atomics_waitAsyncAddresses[i--] = __Atomics_waitAsyncAddresses[--l];
43
__Atomics_waitAsyncAddresses.length = l;
44
a[4](awoken ? 'ok': 'timed-out');
45
}
46
}
47
if (l) {
48
// If we still have addresses to wait, loop the timeout handler to continue polling.
49
setTimeout(__Atomics_pollWaitAsyncAddresses, 10);
50
}
51
}
52
#if ASSERTIONS && WASM_WORKERS
53
if (!ENVIRONMENT_IS_WASM_WORKER) err('Current environment does not support Atomics.waitAsync(): polyfilling it, but this is going to be suboptimal.');
54
#endif
55
/**
56
* @param {number=} maxWaitMilliseconds
57
*/
58
Atomics.waitAsync = (i32a, index, value, maxWaitMilliseconds) => {
59
let val = Atomics.load(i32a, index);
60
if (val != value) return { async: false, value: 'not-equal' };
61
if (maxWaitMilliseconds <= 0) return { async: false, value: 'timed-out' };
62
maxWaitMilliseconds = performance.now() + (maxWaitMilliseconds || Infinity);
63
let promiseResolve;
64
let promise = new Promise((resolve) => { promiseResolve = resolve; });
65
if (!__Atomics_waitAsyncAddresses[0]) setTimeout(__Atomics_pollWaitAsyncAddresses, 10);
66
__Atomics_waitAsyncAddresses.push([i32a, index, value, maxWaitMilliseconds, promiseResolve]);
67
return { async: true, value: promise };
68
};
69
}`,
70
#else
71
$waitAsyncPolyfilled: false,
72
#endif
73
74
$polyfillWaitAsync__internal: true,
75
$polyfillWaitAsync: () => {
76
// nop, used for its postset to ensure `Atomics.waitAsync()` polyfill is
77
// included exactly once and only included when needed.
78
// Any function using Atomics.waitAsync should depend on this.
79
},
80
81
$atomicWaitStates__internal: true,
82
$atomicWaitStates: ['ok', 'not-equal', 'timed-out'],
83
$liveAtomicWaitAsyncs: {},
84
$liveAtomicWaitAsyncs__internal: true,
85
$liveAtomicWaitAsyncCounter: 0,
86
$liveAtomicWaitAsyncCounter__internal: true,
87
88
emscripten_atomic_wait_async__deps: ['$atomicWaitStates', '$liveAtomicWaitAsyncs', '$liveAtomicWaitAsyncCounter', '$polyfillWaitAsync', '$callUserCallback'],
89
emscripten_atomic_wait_async: (addr, val, asyncWaitFinished, userData, maxWaitMilliseconds) => {
90
let wait = Atomics.waitAsync(HEAP32, {{{ getHeapOffset('addr', 'i32') }}}, val, maxWaitMilliseconds);
91
if (!wait.async) return atomicWaitStates.indexOf(wait.value);
92
// Increment waitAsync generation counter, account for wraparound in case
93
// application does huge amounts of waitAsyncs per second (not sure if
94
// possible?)
95
// Valid counter range: 0...2^31-1
96
let counter = liveAtomicWaitAsyncCounter;
97
liveAtomicWaitAsyncCounter = Math.max(0, (liveAtomicWaitAsyncCounter+1)|0);
98
liveAtomicWaitAsyncs[counter] = addr;
99
{{{ runtimeKeepalivePush() }}}
100
wait.value.then((value) => {
101
if (liveAtomicWaitAsyncs[counter]) {
102
{{{ runtimeKeepalivePop() }}}
103
delete liveAtomicWaitAsyncs[counter];
104
callUserCallback(() => {{{ makeDynCall('vpiip', 'asyncWaitFinished') }}}(addr, val, atomicWaitStates.indexOf(value), userData));
105
}
106
});
107
return -counter;
108
},
109
110
emscripten_atomic_cancel_wait_async__deps: ['$liveAtomicWaitAsyncs'],
111
emscripten_atomic_cancel_wait_async: (waitToken) => {
112
#if ASSERTIONS
113
if (waitToken == {{{ cDefs.ATOMICS_WAIT_NOT_EQUAL }}}) {
114
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()');
115
} else if (waitToken == {{{ cDefs.ATOMICS_WAIT_TIMED_OUT }}}) {
116
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()');
117
} else if (waitToken > 0) {
118
warnOnce(`Attempted to call emscripten_atomic_cancel_wait_async() with an invalid wait token value ${waitToken}`);
119
}
120
#endif
121
var address = liveAtomicWaitAsyncs[waitToken];
122
if (address) {
123
// Notify the waitAsync waiters on the memory location, so that JavaScript
124
// garbage collection can occur.
125
// See https://github.com/WebAssembly/threads/issues/176
126
// This has the unfortunate effect of causing spurious wakeup of all other
127
// waiters at the address (which causes a small performance loss).
128
Atomics.notify(HEAP32, {{{ getHeapOffset('address', 'i32') }}});
129
delete liveAtomicWaitAsyncs[waitToken];
130
{{{ runtimeKeepalivePop() }}}
131
return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}};
132
}
133
// This waitToken does not exist.
134
return {{{ cDefs.EMSCRIPTEN_RESULT_INVALID_PARAM }}};
135
},
136
137
emscripten_atomic_cancel_all_wait_asyncs__deps: ['$liveAtomicWaitAsyncs'],
138
emscripten_atomic_cancel_all_wait_asyncs: () => {
139
let waitAsyncs = Object.values(liveAtomicWaitAsyncs);
140
for (var address of waitAsyncs) {
141
Atomics.notify(HEAP32, {{{ getHeapOffset('address', 'i32') }}});
142
}
143
liveAtomicWaitAsyncs = {};
144
return waitAsyncs.length;
145
},
146
147
emscripten_atomic_cancel_all_wait_asyncs_at_address__deps: ['$liveAtomicWaitAsyncs'],
148
emscripten_atomic_cancel_all_wait_asyncs_at_address: (address) => {
149
let numCancelled = 0;
150
for (var [waitToken, waitAddress] of Object.entries(liveAtomicWaitAsyncs)) {
151
if (waitAddress == address) {
152
Atomics.notify(HEAP32, {{{ getHeapOffset('address', 'i32') }}});
153
delete liveAtomicWaitAsyncs[waitToken];
154
numCancelled++;
155
}
156
}
157
return numCancelled;
158
},
159
160
emscripten_has_threading_support: () => !!globalThis.SharedArrayBuffer,
161
162
emscripten_num_logical_cores: () =>
163
#if ENVIRONMENT_MAY_BE_NODE
164
ENVIRONMENT_IS_NODE ? require('node:os').cpus().length :
165
#endif
166
navigator['hardwareConcurrency'],
167
});
168
169