Skip to content

Commit 1d2df94

Browse files
committed
Merge remote-tracking branch 'origin/main' into audio_worklets
2 parents 605319e + 44b2c2a commit 1d2df94

35 files changed

+548
-168
lines changed

emcc.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1653,7 +1653,13 @@ def setup_pthreads(target):
16531653
]
16541654

16551655
if settings.MAIN_MODULE:
1656-
settings.REQUIRED_EXPORTS += ['_emscripten_thread_sync_code', '__dl_seterr']
1656+
settings.REQUIRED_EXPORTS += [
1657+
'_emscripten_dlsync_self',
1658+
'_emscripten_dlsync_self_async',
1659+
'_emscripten_proxy_dlsync',
1660+
'_emscripten_proxy_dlsync_async',
1661+
'__dl_seterr',
1662+
]
16571663

16581664
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [
16591665
'$exitOnMainThread',

site/source/docs/compiling/Dynamic-Linking.rst

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -206,12 +206,11 @@ the stack trace (in an unminified build); building with
206206
Limitations
207207
-----------
208208

209-
- Chromium does not support compiling >4kB WASM on the main thread, and
210-
that includes side modules; you can use ``--use-preload-plugins`` (in
211-
``emcc`` or ``file_packager.py``) to make Emscripten compile them on
212-
startup
213-
`[doc] <https://emscripten.org/docs/porting/files/packaging_files.html#preloading-files>`__
214-
`[discuss] <https://groups.google.com/forum/#!topic/emscripten-discuss/cE3hUV3fDSw>`__.
209+
- Chromium does not support compiling >4kB WASM on the main thread, and that
210+
includes side modules; you can use ``--use-preload-plugins`` (in ``emcc`` or
211+
``file_packager.py``) to make Emscripten compile them on startup
212+
`[doc] <https://emscripten.org/docs/porting/files/packaging_files.html#preloading-files>`__
213+
`[discuss] <https://groups.google.com/forum/#!topic/emscripten-discuss/cE3hUV3fDSw>`__.
215214
- ``EM_ASM`` code defined within side modules depends on ``eval`` support are
216215
is therefore incompatible with ``-sDYNAMIC_EXECUTION=0``.
217216
- ``EM_JS`` functions defined in side modules are not yet supported.
@@ -220,10 +219,26 @@ Limitations
220219
Pthreads support
221220
----------------
222221

223-
Dynamic linking + pthreads is is still experimental. While you can link with
224-
``MAIN_MODULE`` and ``-pthread`` emscripten will produce a warning by default
225-
when you do this.
226-
227-
While load-time dynamic linking should largely work and does not have any major
228-
known issues, runtime dynamic linking (with ``dlopen()``) has limited support
229-
when used with pthreads.
222+
Dynamic linking + pthreads is is still experimental. As such, linking with both
223+
``MAIN_MODULE`` and ``-pthread`` will produce a warning.
224+
225+
While load-time dynamic linking works without any complications, runtime dynamic
226+
linking via ``dlopen``/``dlsym`` can require some extra consideration. The
227+
reason for this is that keeping the indirection function pointer table in sync
228+
between threads has to be done by emscripten library code. Each time a new
229+
library is loaded or a new symbol is requested via ``dlsym``, table slots can be
230+
added and these changes need to be mirrored on every thread in the process.
231+
232+
Changes to the table are protected by a mutex, and before any thread returns
233+
from ``dlopen`` or ``dlsym`` it will wait until all other threads are sync. In
234+
order to make this synchronization as seamless as possible, we hook into the
235+
low level primitives of `emscripten_futex_wait` and `emscirpten_yield`.
236+
237+
For most use cases all this happens under hood and no special action is needed.
238+
However, there there is one class of application that currently may require
239+
modification. If your applications busy waits, or directly uses the
240+
``atomic.waitXX`` instructions (or the clang
241+
``__builtin_wasm_memory_atomic_waitXX`` builtins) you maybe need to switch it
242+
to use ``emscripten_futex_wait`` or order avoid deadlocks. If you don't use
243+
``emscripten_futex_wait`` while you block, you could potentially block other
244+
threads that are calling ``dlopen`` and/or ``dlsym``.

src/library_promise.js

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,25 @@ mergeInto(LibraryManager.library, {
1212
$getPromise: function(id) {
1313
return promiseMap.get(id).promise;
1414
},
15-
emscripten_promise_create__deps: ['$promiseMap'],
16-
emscripten_promise_create__sig: 'p',
17-
emscripten_promise_create: function() {
15+
16+
$makePromise__deps: ['$promiseMap'],
17+
$makePromise: function() {
1818
var promiseInfo = {};
1919
promiseInfo.promise = new Promise((resolve, reject) => {
2020
promiseInfo.reject = reject;
2121
promiseInfo.resolve = resolve;
2222
});
23-
var id = promiseMap.allocate(promiseInfo);
23+
promiseInfo.id = promiseMap.allocate(promiseInfo);
2424
#if RUNTIME_DEBUG
25-
dbg('emscripten_promise_create: ' + id);
25+
dbg('makePromise: ' + promiseInfo.id);
2626
#endif
27-
return id;
27+
return promiseInfo;
28+
},
29+
30+
emscripten_promise_create__deps: ['$makePromise'],
31+
emscripten_promise_create__sig: 'p',
32+
emscripten_promise_create: function() {
33+
return makePromise().id;
2834
},
2935

3036
emscripten_promise_destroy__deps: ['$promiseMap'],
@@ -42,7 +48,7 @@ mergeInto(LibraryManager.library, {
4248
emscripten_promise_resolve__sig: 'vpip',
4349
emscripten_promise_resolve: function(id, result, value) {
4450
#if RUNTIME_DEBUG
45-
err('emscripten_promise_resolve: ' + id);
51+
dbg('emscripten_promise_resolve: ' + id);
4652
#endif
4753
var info = promiseMap.get(id);
4854
switch (result) {

src/library_pthread.js

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ var LibraryPThread = {
3131
$PThread__deps: ['_emscripten_thread_init',
3232
'$killThread',
3333
'$cancelThread', '$cleanupThread', '$zeroMemory',
34+
#if MAIN_MODULE
35+
'$markAsFinshed',
36+
#endif
3437
'$spawnThread',
3538
'_emscripten_thread_free_data',
3639
'exit',
@@ -101,6 +104,12 @@ var LibraryPThread = {
101104
while (pthreadPoolSize--) {
102105
PThread.allocateUnusedWorker();
103106
}
107+
#endif
108+
#if MAIN_MODULE
109+
PThread.outstandingPromises = {};
110+
// Finished threads are threads that have finished running but we not yet
111+
// joined.
112+
PThread.finishedThreads = new Set();
104113
#endif
105114
},
106115

@@ -273,6 +282,10 @@ var LibraryPThread = {
273282
spawnThread(d);
274283
} else if (cmd === 'cleanupThread') {
275284
cleanupThread(d['thread']);
285+
#if MAIN_MODULE
286+
} else if (cmd === 'markAsFinshed') {
287+
markAsFinshed(d['thread']);
288+
#endif
276289
} else if (cmd === 'killThread') {
277290
killThread(d['thread']);
278291
} else if (cmd === 'cancelThread') {
@@ -544,11 +557,20 @@ var LibraryPThread = {
544557
},
545558

546559
$cleanupThread: function(pthread_ptr) {
560+
#if PTHREADS_DEBUG
561+
dbg('cleanupThread: ' + ptrToString(pthread_ptr))
562+
#endif
547563
#if ASSERTIONS
548564
assert(!ENVIRONMENT_IS_PTHREAD, 'Internal Error! cleanupThread() can only ever be called from main application thread!');
549565
assert(pthread_ptr, 'Internal Error! Null pthread_ptr in cleanupThread!');
550566
#endif
551567
var worker = PThread.pthreads[pthread_ptr];
568+
#if MAIN_MODULE
569+
PThread.finishedThreads.delete(pthread_ptr);
570+
if (pthread_ptr in PThread.outstandingPromises) {
571+
PThread.outstandingPromises[pthread_ptr].resolve();
572+
}
573+
#endif
552574
assert(worker);
553575
PThread.returnWorkerToPool(worker);
554576
},
@@ -1052,7 +1074,7 @@ var LibraryPThread = {
10521074
// Before we call the thread entry point, make sure any shared libraries
10531075
// have been loaded on this there. Otherwise our table migth be not be
10541076
// in sync and might not contain the function pointer `ptr` at all.
1055-
__emscripten_thread_sync_code();
1077+
__emscripten_dlsync_self();
10561078
#endif
10571079
// pthread entry points are always of signature 'void *ThreadMain(void *arg)'
10581080
// Native codebases sometimes spawn threads with other thread entry point
@@ -1081,6 +1103,99 @@ var LibraryPThread = {
10811103
#endif
10821104
},
10831105

1106+
#if MAIN_MODULE
1107+
_emscripten_thread_exit_joinable: function(thread) {
1108+
// Called when a thread exits and is joinable. We mark these threads
1109+
// as finished, which means that are in state where are no longer actually
1110+
// runnning, but remain around waiting to be joined. In this state they
1111+
// cannot run any more proxied work.
1112+
if (!ENVIRONMENT_IS_PTHREAD) markAsFinshed(thread);
1113+
else postMessage({ 'cmd': 'markAsFinshed', 'thread': thread });
1114+
},
1115+
1116+
$markAsFinshed: function(pthread_ptr) {
1117+
#if PTHREADS_DEBUG
1118+
dbg('markAsFinshed: ' + ptrToString(pthread_ptr));
1119+
#endif
1120+
PThread.finishedThreads.add(pthread_ptr);
1121+
if (pthread_ptr in PThread.outstandingPromises) {
1122+
PThread.outstandingPromises[pthread_ptr].resolve();
1123+
}
1124+
},
1125+
1126+
// Asynchronous version dlsync_threads. Always run on the main thread.
1127+
// This work happens asynchronously. The `callback` is called once this work
1128+
// is completed, passing the ctx.
1129+
// TODO(sbc): Should we make a new form of __proxy attribute for JS library
1130+
// function that run asynchronously like but blocks the caller until they are
1131+
// done. Perhaps "sync_with_ctx"?
1132+
_emscripten_dlsync_threads_async__sig: 'vppp',
1133+
_emscripten_dlsync_threads_async__deps: ['_emscripten_proxy_dlsync_async', 'emscripten_promise_create', '$getPromise'],
1134+
_emscripten_dlsync_threads_async: function(caller, callback, ctx) {
1135+
#if PTHREADS_DEBUG
1136+
dbg("_emscripten_dlsync_threads_async caller=" + ptrToString(caller));
1137+
#endif
1138+
#if ASSERTIONS
1139+
assert(!ENVIRONMENT_IS_PTHREAD, 'Internal Error! _emscripten_dlsync_threads_async() can only ever be called from main thread');
1140+
#endif
1141+
1142+
const promises = [];
1143+
assert(Object.keys(PThread.outstandingPromises).length === 0);
1144+
1145+
// This first promise resolves once the main thread has loaded all modules.
1146+
var info = makePromise();
1147+
promises.push(info.promise);
1148+
__emscripten_dlsync_self_async(info.id);
1149+
1150+
1151+
// We then create a sequence of promises, one per thread, that resolve once
1152+
// each thread has performed its sync using _emscripten_proxy_dlsync.
1153+
// Any new threads that are created after this call will automaticaly be
1154+
// in sync because we call `__emscripten_dlsync_self` in
1155+
// invokeEntryPoint before the threads entry point is called.
1156+
for (const ptr of Object.keys(PThread.pthreads)) {
1157+
const pthread_ptr = Number(ptr);
1158+
if (pthread_ptr !== caller && !PThread.finishedThreads.has(pthread_ptr)) {
1159+
info = makePromise();
1160+
__emscripten_proxy_dlsync_async(pthread_ptr, info.id);
1161+
PThread.outstandingPromises[pthread_ptr] = info;
1162+
promises.push(info.promise);
1163+
}
1164+
}
1165+
1166+
#if PTHREADS_DEBUG
1167+
dbg('_emscripten_dlsync_threads_async: waiting on ' + promises.length + ' promises');
1168+
#endif
1169+
// Once all promises are resolved then we know all threads are in sync and
1170+
// we can call the callback.
1171+
Promise.all(promises).then(() => {
1172+
PThread.outstandingPromises = {};
1173+
#if PTHREADS_DEBUG
1174+
dbg('_emscripten_dlsync_threads_async done: calling callback');
1175+
#endif
1176+
{{{ makeDynCall('vp', 'callback') }}}(ctx);
1177+
});
1178+
},
1179+
1180+
// Synchronous version dlsync_threads. This is only needed for the case then
1181+
// the main thread call dlopen and in that case we have not choice but to
1182+
// synchronously block the main thread until all other threads are in sync.
1183+
// When `dlopen` is called from a worker, the worker itself is blocked but
1184+
// the operation its waiting on (on the main thread) can be async.
1185+
_emscripten_dlsync_threads__deps: ['_emscripten_proxy_dlsync'],
1186+
_emscripten_dlsync_threads: function() {
1187+
#if ASSERTIONS
1188+
assert(!ENVIRONMENT_IS_PTHREAD, 'Internal Error! _emscripten_dlsync_threads() can only ever be called from main thread');
1189+
#endif
1190+
for (const ptr of Object.keys(PThread.pthreads)) {
1191+
const pthread_ptr = Number(ptr);
1192+
if (!PThread.finishedThreads.has(pthread_ptr)) {
1193+
__emscripten_proxy_dlsync(pthread_ptr);
1194+
}
1195+
}
1196+
},
1197+
#endif // MAIN_MODULE
1198+
10841199
$executeNotifiedProxyingQueue: function(queue) {
10851200
// Set the notification state to processing.
10861201
Atomics.store(HEAP32, queue >> 2, {{{ cDefine('NOTIFICATION_RECEIVED') }}});

system/include/emscripten/threading.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -275,9 +275,6 @@ int emscripten_pthread_attr_settransferredcanvases(pthread_attr_t *a, const char
275275
// blocking is not enabled, see ALLOW_BLOCKING_ON_MAIN_THREAD.
276276
void emscripten_check_blocking_allowed(void);
277277

278-
// Experimental API for syncing loaded code between pthreads.
279-
void _emscripten_thread_sync_code();
280-
281278
#ifdef __cplusplus
282279
}
283280
#endif

0 commit comments

Comments
 (0)