Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/src/lib/libatomic.js
4150 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
$polyfillWaitAsync__postset: `if (!Atomics.waitAsync || (typeof navigator != 'undefined' && navigator.userAgent && Number((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
#endif
64
65
$polyfillWaitAsync__internal: true,
66
$polyfillWaitAsync: () => {
67
// nop, used for its postset to ensure `Atomics.waitAsync()` polyfill is
68
// included exactly once and only included when needed.
69
// Any function using Atomics.waitAsync should depend on this.
70
},
71
72
$atomicWaitStates__internal: true,
73
$atomicWaitStates: ['ok', 'not-equal', 'timed-out'],
74
$liveAtomicWaitAsyncs: {},
75
$liveAtomicWaitAsyncs__internal: true,
76
$liveAtomicWaitAsyncCounter: 0,
77
$liveAtomicWaitAsyncCounter__internal: true,
78
79
emscripten_atomic_wait_async__deps: ['$atomicWaitStates', '$liveAtomicWaitAsyncs', '$liveAtomicWaitAsyncCounter', '$polyfillWaitAsync', '$callUserCallback'],
80
emscripten_atomic_wait_async: (addr, val, asyncWaitFinished, userData, maxWaitMilliseconds) => {
81
let wait = Atomics.waitAsync(HEAP32, {{{ getHeapOffset('addr', 'i32') }}}, val, maxWaitMilliseconds);
82
if (!wait.async) return atomicWaitStates.indexOf(wait.value);
83
// Increment waitAsync generation counter, account for wraparound in case
84
// application does huge amounts of waitAsyncs per second (not sure if
85
// possible?)
86
// Valid counterrange: 0...2^31-1
87
let counter = liveAtomicWaitAsyncCounter;
88
liveAtomicWaitAsyncCounter = Math.max(0, (liveAtomicWaitAsyncCounter+1)|0);
89
liveAtomicWaitAsyncs[counter] = addr;
90
{{{ runtimeKeepalivePush() }}}
91
wait.value.then((value) => {
92
if (liveAtomicWaitAsyncs[counter]) {
93
{{{ runtimeKeepalivePop() }}}
94
delete liveAtomicWaitAsyncs[counter];
95
callUserCallback(() => {{{ makeDynCall('vpiip', 'asyncWaitFinished') }}}(addr, val, atomicWaitStates.indexOf(value), userData));
96
}
97
});
98
return -counter;
99
},
100
101
emscripten_atomic_cancel_wait_async__deps: ['$liveAtomicWaitAsyncs'],
102
emscripten_atomic_cancel_wait_async: (waitToken) => {
103
#if ASSERTIONS
104
if (waitToken == {{{ cDefs.ATOMICS_WAIT_NOT_EQUAL }}}) {
105
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()');
106
} else if (waitToken == {{{ cDefs.ATOMICS_WAIT_TIMED_OUT }}}) {
107
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()');
108
} else if (waitToken > 0) {
109
warnOnce(`Attempted to call emscripten_atomic_cancel_wait_async() with an invalid wait token value ${waitToken}`);
110
}
111
#endif
112
var address = liveAtomicWaitAsyncs[waitToken];
113
if (address) {
114
// Notify the waitAsync waiters on the memory location, so that JavaScript
115
// garbage collection can occur.
116
// See https://github.com/WebAssembly/threads/issues/176
117
// This has the unfortunate effect of causing spurious wakeup of all other
118
// waiters at the address (which causes a small performance loss).
119
Atomics.notify(HEAP32, {{{ getHeapOffset('address', 'i32') }}});
120
delete liveAtomicWaitAsyncs[waitToken];
121
{{{ runtimeKeepalivePop() }}}
122
return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}};
123
}
124
// This waitToken does not exist.
125
return {{{ cDefs.EMSCRIPTEN_RESULT_INVALID_PARAM }}};
126
},
127
128
emscripten_atomic_cancel_all_wait_asyncs__deps: ['$liveAtomicWaitAsyncs'],
129
emscripten_atomic_cancel_all_wait_asyncs: () => {
130
let waitAsyncs = Object.values(liveAtomicWaitAsyncs);
131
waitAsyncs.forEach((address) => {
132
Atomics.notify(HEAP32, {{{ getHeapOffset('address', 'i32') }}});
133
});
134
liveAtomicWaitAsyncs = {};
135
return waitAsyncs.length;
136
},
137
138
emscripten_atomic_cancel_all_wait_asyncs_at_address__deps: ['$liveAtomicWaitAsyncs'],
139
emscripten_atomic_cancel_all_wait_asyncs_at_address: (address) => {
140
let numCancelled = 0;
141
Object.keys(liveAtomicWaitAsyncs).forEach((waitToken) => {
142
if (liveAtomicWaitAsyncs[waitToken] == address) {
143
Atomics.notify(HEAP32, {{{ getHeapOffset('address', 'i32') }}});
144
delete liveAtomicWaitAsyncs[waitToken];
145
numCancelled++;
146
}
147
});
148
return numCancelled;
149
},
150
151
emscripten_has_threading_support: () => typeof SharedArrayBuffer != 'undefined',
152
153
emscripten_num_logical_cores: () =>
154
#if ENVIRONMENT_MAY_BE_NODE
155
ENVIRONMENT_IS_NODE ? require('os').cpus().length :
156
#endif
157
navigator['hardwareConcurrency'],
158
});
159
160