Skip to content

Commit a30cc15

Browse files
committed
Add new emscripten_thread_can_block function and use it in emscripten_futex_wait. NFC
This new function returns true only on the main browser thread and not on the main node runtime thread (unlike `emscripten_is_main_browser_thread` which is an historical misnomer). Using this new native function along with the intrisic `__builtin_wasm_memory_atomic_wait32` we can now implement `emscripten_futex_wait` in native code and only call out to JS for the non-block/busy-looping case. As a followup we could also move the non-block/busy-looping code to the native side but doing it this way keep this change small and focused.
1 parent de2028d commit a30cc15

9 files changed

+174
-123
lines changed

src/library_pthread.js

Lines changed: 94 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,7 @@ var LibraryPThread = {
598598
// Pass the thread address to the native code where they stored in wasm
599599
// globals which act as a form of TLS. Global constructors trying
600600
// to access this value will read the wrong value, but that is UB anyway.
601-
__emscripten_thread_init(tb, /*isMainBrowserThread=*/!ENVIRONMENT_IS_WORKER, /*isMainRuntimeThread=*/1);
601+
__emscripten_thread_init(tb, /*isMainBrowserThread=*/!ENVIRONMENT_IS_WORKER, /*isMainRuntimeThread=*/1, /*canBlock=*/!ENVIRONMENT_IS_WEB);
602602
#if ASSERTIONS
603603
PThread.mainRuntimeThread = true;
604604
// Verify that this native symbol used by futex_wait/wake is exported correctly.
@@ -843,134 +843,112 @@ var LibraryPThread = {
843843
},
844844

845845
// Returns 0 on success, or one of the values -ETIMEDOUT, -EWOULDBLOCK or -EINVAL on error.
846-
emscripten_futex_wait__deps: ['emscripten_main_thread_process_queued_calls'],
847-
emscripten_futex_wait: function(addr, val, timeout) {
848-
if (addr <= 0 || addr > HEAP8.length || addr&3 != 0) return -{{{ cDefine('EINVAL') }}};
849-
// We can do a normal blocking wait anywhere but on the main browser thread.
850-
if (!ENVIRONMENT_IS_WEB) {
851-
#if PTHREADS_PROFILING
852-
PThread.setThreadStatusConditional(_pthread_self(), {{{ cDefine('EM_THREAD_STATUS_RUNNING') }}}, {{{ cDefine('EM_THREAD_STATUS_WAITFUTEX') }}});
853-
#endif
854-
var ret = Atomics.wait(HEAP32, addr >> 2, val, timeout);
855-
#if PTHREADS_PROFILING
856-
PThread.setThreadStatusConditional(_pthread_self(), {{{ cDefine('EM_THREAD_STATUS_WAITFUTEX') }}}, {{{ cDefine('EM_THREAD_STATUS_RUNNING') }}});
857-
#endif
858-
if (ret === 'timed-out') return -{{{ cDefine('ETIMEDOUT') }}};
859-
if (ret === 'not-equal') return -{{{ cDefine('EWOULDBLOCK') }}};
860-
if (ret === 'ok') return 0;
861-
throw 'Atomics.wait returned an unexpected value ' + ret;
862-
} else {
863-
// First, check if the value is correct for us to wait on.
864-
if (Atomics.load(HEAP32, addr >> 2) != val) {
865-
return -{{{ cDefine('EWOULDBLOCK') }}};
866-
}
867-
868-
// Atomics.wait is not available in the main browser thread, so simulate it via busy spinning.
869-
var tNow = performance.now();
870-
var tEnd = tNow + timeout;
871-
872-
#if PTHREADS_PROFILING
873-
PThread.setThreadStatusConditional(_pthread_self(), {{{ cDefine('EM_THREAD_STATUS_RUNNING') }}}, {{{ cDefine('EM_THREAD_STATUS_WAITFUTEX') }}});
874-
#endif
875-
// Register globally which address the main thread is simulating to be
876-
// waiting on. When zero, the main thread is not waiting on anything, and on
877-
// nonzero, the contents of the address pointed by __emscripten_main_thread_futex
878-
// tell which address the main thread is simulating its wait on.
879-
// We need to be careful of recursion here: If we wait on a futex, and
880-
// then call _emscripten_main_thread_process_queued_calls() below, that
881-
// will call code that takes the proxying mutex - which can once more
882-
// reach this code in a nested call. To avoid interference between the
883-
// two (there is just a single __emscripten_main_thread_futex at a time), unmark
884-
// ourselves before calling the potentially-recursive call. See below for
885-
// how we handle the case of our futex being notified during the time in
886-
// between when we are not set as the value of __emscripten_main_thread_futex.
887-
var lastAddr = Atomics.exchange(HEAP32, __emscripten_main_thread_futex >> 2, addr);
846+
_emscripten_futex_wait_non_blocking__deps: ['emscripten_main_thread_process_queued_calls'],
847+
_emscripten_futex_wait_non_blocking: function(addr, val, timeout) {
888848
#if ASSERTIONS
889-
// We must not have already been waiting.
890-
assert(lastAddr == 0);
891-
#endif
892-
893-
while (1) {
894-
// Check for a timeout.
895-
tNow = performance.now();
896-
if (tNow > tEnd) {
897-
#if PTHREADS_PROFILING
898-
PThread.setThreadStatusConditional(_pthread_self(), {{{ cDefine('EM_THREAD_STATUS_RUNNING') }}}, {{{ cDefine('EM_THREAD_STATUS_WAITFUTEX') }}});
849+
// Should only be called from the main web thread where atomics.wait is not allowed.
850+
assert(ENVIRONMENT_IS_WEB);
851+
#endif
852+
853+
// Atomics.wait is not available in the main browser thread, so simulate it via busy spinning.
854+
var tNow = performance.now();
855+
var tEnd = tNow + timeout;
856+
857+
// Register globally which address the main thread is simulating to be
858+
// waiting on. When zero, the main thread is not waiting on anything, and on
859+
// nonzero, the contents of the address pointed by __emscripten_main_thread_futex
860+
// tell which address the main thread is simulating its wait on.
861+
// We need to be careful of recursion here: If we wait on a futex, and
862+
// then call _emscripten_main_thread_process_queued_calls() below, that
863+
// will call code that takes the proxying mutex - which can once more
864+
// reach this code in a nested call. To avoid interference between the
865+
// two (there is just a single __emscripten_main_thread_futex at a time), unmark
866+
// ourselves before calling the potentially-recursive call. See below for
867+
// how we handle the case of our futex being notified during the time in
868+
// between when we are not set as the value of __emscripten_main_thread_futex.
869+
#if ASSERTIONS
870+
assert(__emscripten_main_thread_futex > 0);
899871
#endif
900-
// We timed out, so stop marking ourselves as waiting.
901-
lastAddr = Atomics.exchange(HEAP32, __emscripten_main_thread_futex >> 2, 0);
872+
var lastAddr = Atomics.exchange(HEAP32, __emscripten_main_thread_futex >> 2, addr);
902873
#if ASSERTIONS
903-
// The current value must have been our address which we set, or
904-
// in a race it was set to 0 which means another thread just allowed
905-
// us to run, but (tragically) that happened just a bit too late.
906-
assert(lastAddr == addr || lastAddr == 0);
874+
// We must not have already been waiting.
875+
assert(lastAddr == 0);
907876
#endif
908-
return -{{{ cDefine('ETIMEDOUT') }}};
909-
}
910-
// We are performing a blocking loop here, so we must handle proxied
911-
// events from pthreads, to avoid deadlocks.
912-
// Note that we have to do so carefully, as we may take a lock while
913-
// doing so, which can recurse into this function; stop marking
914-
// ourselves as waiting while we do so.
877+
878+
while (1) {
879+
// Check for a timeout.
880+
tNow = performance.now();
881+
if (tNow > tEnd) {
882+
// We timed out, so stop marking ourselves as waiting.
915883
lastAddr = Atomics.exchange(HEAP32, __emscripten_main_thread_futex >> 2, 0);
916884
#if ASSERTIONS
885+
// The current value must have been our address which we set, or
886+
// in a race it was set to 0 which means another thread just allowed
887+
// us to run, but (tragically) that happened just a bit too late.
917888
assert(lastAddr == addr || lastAddr == 0);
918889
#endif
919-
if (lastAddr == 0) {
920-
// We were told to stop waiting, so stop.
921-
break;
922-
}
923-
_emscripten_main_thread_process_queued_calls();
924-
925-
// Check the value, as if we were starting the futex all over again.
926-
// This handles the following case:
927-
//
928-
// * wait on futex A
929-
// * recurse into emscripten_main_thread_process_queued_calls(),
930-
// which waits on futex B. that sets the __emscripten_main_thread_futex address to
931-
// futex B, and there is no longer any mention of futex A.
932-
// * a worker is done with futex A. it checks __emscripten_main_thread_futex but does
933-
// not see A, so it does nothing special for the main thread.
934-
// * a worker is done with futex B. it flips mainThreadMutex from B
935-
// to 0, ending the wait on futex B.
936-
// * we return to the wait on futex A. __emscripten_main_thread_futex is 0, but that
937-
// is because of futex B being done - we can't tell from
938-
// __emscripten_main_thread_futex whether A is done or not. therefore, check the
939-
// memory value of the futex.
940-
//
941-
// That case motivates the design here. Given that, checking the memory
942-
// address is also necessary for other reasons: we unset and re-set our
943-
// address in __emscripten_main_thread_futex around calls to
944-
// emscripten_main_thread_process_queued_calls(), and a worker could
945-
// attempt to wake us up right before/after such times.
946-
//
947-
// Note that checking the memory value of the futex is valid to do: we
948-
// could easily have been delayed (relative to the worker holding on
949-
// to futex A), which means we could be starting all of our work at the
950-
// later time when there is no need to block. The only "odd" thing is
951-
// that we may have caused side effects in that "delay" time. But the
952-
// only side effects we can have are to call
953-
// emscripten_main_thread_process_queued_calls(). That is always ok to
954-
// do on the main thread (it's why it is ok for us to call it in the
955-
// middle of this function, and elsewhere). So if we check the value
956-
// here and return, it's the same is if what happened on the main thread
957-
// was the same as calling emscripten_main_thread_process_queued_calls()
958-
// a few times times before calling emscripten_futex_wait().
959-
if (Atomics.load(HEAP32, addr >> 2) != val) {
960-
return -{{{ cDefine('EWOULDBLOCK') }}};
961-
}
962-
963-
// Mark us as waiting once more, and continue the loop.
964-
lastAddr = Atomics.exchange(HEAP32, __emscripten_main_thread_futex >> 2, addr);
890+
return -{{{ cDefine('ETIMEDOUT') }}};
891+
}
892+
// We are performing a blocking loop here, so we must handle proxied
893+
// events from pthreads, to avoid deadlocks.
894+
// Note that we have to do so carefully, as we may take a lock while
895+
// doing so, which can recurse into this function; stop marking
896+
// ourselves as waiting while we do so.
897+
lastAddr = Atomics.exchange(HEAP32, __emscripten_main_thread_futex >> 2, 0);
965898
#if ASSERTIONS
966-
assert(lastAddr == 0);
899+
assert(lastAddr == addr || lastAddr == 0);
967900
#endif
901+
if (lastAddr == 0) {
902+
// We were told to stop waiting, so stop.
903+
break;
968904
}
969-
#if PTHREADS_PROFILING
970-
PThread.setThreadStatusConditional(_pthread_self(), {{{ cDefine('EM_THREAD_STATUS_RUNNING') }}}, {{{ cDefine('EM_THREAD_STATUS_WAITFUTEX') }}});
905+
_emscripten_main_thread_process_queued_calls();
906+
907+
// Check the value, as if we were starting the futex all over again.
908+
// This handles the following case:
909+
//
910+
// * wait on futex A
911+
// * recurse into emscripten_main_thread_process_queued_calls(),
912+
// which waits on futex B. that sets the __emscripten_main_thread_futex address to
913+
// futex B, and there is no longer any mention of futex A.
914+
// * a worker is done with futex A. it checks __emscripten_main_thread_futex but does
915+
// not see A, so it does nothing special for the main thread.
916+
// * a worker is done with futex B. it flips mainThreadMutex from B
917+
// to 0, ending the wait on futex B.
918+
// * we return to the wait on futex A. __emscripten_main_thread_futex is 0, but that
919+
// is because of futex B being done - we can't tell from
920+
// __emscripten_main_thread_futex whether A is done or not. therefore, check the
921+
// memory value of the futex.
922+
//
923+
// That case motivates the design here. Given that, checking the memory
924+
// address is also necessary for other reasons: we unset and re-set our
925+
// address in __emscripten_main_thread_futex around calls to
926+
// emscripten_main_thread_process_queued_calls(), and a worker could
927+
// attempt to wake us up right before/after such times.
928+
//
929+
// Note that checking the memory value of the futex is valid to do: we
930+
// could easily have been delayed (relative to the worker holding on
931+
// to futex A), which means we could be starting all of our work at the
932+
// later time when there is no need to block. The only "odd" thing is
933+
// that we may have caused side effects in that "delay" time. But the
934+
// only side effects we can have are to call
935+
// emscripten_main_thread_process_queued_calls(). That is always ok to
936+
// do on the main thread (it's why it is ok for us to call it in the
937+
// middle of this function, and elsewhere). So if we check the value
938+
// here and return, it's the same is if what happened on the main thread
939+
// was the same as calling emscripten_main_thread_process_queued_calls()
940+
// a few times times before calling emscripten_futex_wait().
941+
if (Atomics.load(HEAP32, addr >> 2) != val) {
942+
return -{{{ cDefine('EWOULDBLOCK') }}};
943+
}
944+
945+
// Mark us as waiting once more, and continue the loop.
946+
lastAddr = Atomics.exchange(HEAP32, __emscripten_main_thread_futex >> 2, addr);
947+
#if ASSERTIONS
948+
assert(lastAddr == 0);
971949
#endif
972-
return 0;
973950
}
951+
return 0;
974952
},
975953

976954
// Returns the number of threads (>= 0) woken up, or the value -EINVAL on error.

src/worker.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ self.onmessage = function(e) {
188188
Module['__performance_now_clock_drift'] = performance.now() - e.data.time;
189189

190190
// Pass the thread address inside the asm.js scope to store it for fast access that avoids the need for a FFI out.
191-
Module['__emscripten_thread_init'](e.data.threadInfoStruct, /*isMainBrowserThread=*/0, /*isMainRuntimeThread=*/0);
191+
Module['__emscripten_thread_init'](e.data.threadInfoStruct, /*isMainBrowserThread=*/0, /*isMainRuntimeThread=*/0, /*canBlock=*/1);
192192

193193
#if ASSERTIONS
194194
assert(e.data.threadInfoStruct);

system/include/emscripten/threading.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,9 +296,17 @@ int emscripten_dispatch_to_thread_async_(pthread_t target_thread,
296296
// Returns 1 if the current thread is the thread that hosts the Emscripten runtime.
297297
int emscripten_is_main_runtime_thread(void);
298298

299-
// Returns 1 if the current thread is the main browser thread.
299+
// Returns 1 if the current thread is the main browser thread. For historical
300+
// reasons this functions also returns 1 on the main node thread.
301+
// Use emscripten_thread_can_block() instead to determine if the current
302+
// thread can block.
300303
int emscripten_is_main_browser_thread(void);
301304

305+
// Returns 1 if the current thread can use atomics.wait or other forms of
306+
// synchronous blocking. This will return 0 on the main web browser thread
307+
// and 1 everywhere else, including on the main node thread.
308+
int emscripten_thread_can_block(void);
309+
302310
// A temporary workaround to issue
303311
// https://github.com/emscripten-core/emscripten/issues/3495:
304312
// Call this in the body of all lock-free atomic (cas) loops that the main
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2021 The Emscripten Authors. All rights reserved.
3+
* Emscripten is available under two separate licenses, the MIT license and the
4+
* University of Illinois/NCSA Open Source License. Both these licenses can be
5+
* found in the LICENSE file.
6+
*/
7+
#include <assert.h>
8+
#include <errno.h>
9+
#include <math.h>
10+
#include <emscripten/threading.h>
11+
12+
int _emscripten_futex_wait_non_blocking(volatile void *addr, uint32_t val, double max_wait_ms);
13+
14+
int emscripten_futex_wait(volatile void *addr, uint32_t val, double max_wait_ms) {
15+
if ((((intptr_t)addr)&3) != 0) {
16+
return -EINVAL;
17+
}
18+
19+
int ret;
20+
emscripten_conditional_set_current_thread_status(EM_THREAD_STATUS_RUNNING, EM_THREAD_STATUS_WAITFUTEX);
21+
22+
// For threads that cannot block (i.e. the main browser thread) we can't use
23+
// __builtin_wasm_memory_atomic_wait32 so we call out the JS function that
24+
// will busy wait.
25+
if (!emscripten_thread_can_block()) {
26+
ret = _emscripten_futex_wait_non_blocking(addr, val, max_wait_ms);
27+
emscripten_conditional_set_current_thread_status(EM_THREAD_STATUS_WAITFUTEX, EM_THREAD_STATUS_RUNNING);
28+
return ret;
29+
}
30+
31+
// -1 (or any negative number) means wait indefinitely.
32+
int64_t max_wait_ns = -1;
33+
if (max_wait_ms != INFINITY) {
34+
max_wait_ms = (int64_t)(max_wait_ms*1000*1000);
35+
}
36+
ret = __builtin_wasm_memory_atomic_wait32((int*)addr, val, max_wait_ns);
37+
emscripten_conditional_set_current_thread_status(EM_THREAD_STATUS_WAITFUTEX, EM_THREAD_STATUS_RUNNING);
38+
39+
// memory.atomic.wait32 returns:
40+
// 0 => "ok", woken by another agent.
41+
// 1 => "not-equal", loaded value != expected value
42+
// 2 => "timed-out", the timeout expired
43+
if (ret == 1) {
44+
return -EWOULDBLOCK;
45+
}
46+
if (ret == 2) {
47+
return -ETIMEDOUT;
48+
}
49+
assert(ret == 0);
50+
return 0;
51+
}

system/lib/pthread/emscripten_thread_state.S

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
#endif
66

77
.globaltype __tls_base, PTR
8-
.globaltype thread_ptr, PTR
98

9+
.globaltype thread_ptr, PTR
1010
thread_ptr:
1111

1212
.globaltype is_main_thread, i32
@@ -15,6 +15,9 @@ is_main_thread:
1515
.globaltype is_runtime_thread, i32
1616
is_runtime_thread:
1717

18+
.globaltype can_block, i32
19+
can_block:
20+
1821
.globl __get_tp
1922
__get_tp:
2023
.functype __get_tp () -> (PTR)
@@ -23,13 +26,15 @@ __get_tp:
2326

2427
.globl _emscripten_thread_init
2528
_emscripten_thread_init:
26-
.functype _emscripten_thread_init (PTR, i32, i32) -> ()
29+
.functype _emscripten_thread_init (PTR, i32, i32, i32) -> ()
2730
local.get 0
2831
global.set thread_ptr
2932
local.get 1
3033
global.set is_main_thread
3134
local.get 2
3235
global.set is_runtime_thread
36+
local.get 3
37+
global.set can_block
3338
end_function
3439

3540
# Accessor for `__tls_base` symbol which is a wasm global an not directly
@@ -53,3 +58,10 @@ emscripten_is_main_browser_thread:
5358
.functype emscripten_is_main_browser_thread () -> (i32)
5459
global.get is_main_thread
5560
end_function
61+
62+
# Semantically the same as testing "!ENVIRONMENT_IS_WEB" in JS
63+
.globl emscripten_thread_can_block
64+
emscripten_thread_can_block:
65+
.functype emscripten_thread_can_block () -> (i32)
66+
global.get can_block
67+
end_function

system/lib/pthread/pthread_create.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
// See musl's pthread_create.c
2424

2525
extern int __pthread_create_js(struct pthread *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
26-
extern void _emscripten_thread_init(int, int, int);
26+
extern void _emscripten_thread_init(int, int, int, int);
2727
extern int _emscripten_default_pthread_stack_size();
2828
extern void __pthread_detached_exit();
2929
extern void* _emscripten_tls_base();
@@ -225,7 +225,7 @@ void _emscripten_thread_exit(void* result) {
225225
self->tsd = NULL;
226226

227227
// Not hosting a pthread anymore in this worker set __pthread_self to NULL
228-
_emscripten_thread_init(0, 0, 0);
228+
_emscripten_thread_init(0, 0, 0, 1);
229229

230230
/* This atomic potentially competes with a concurrent pthread_detach
231231
* call; the loser is responsible for freeing thread resources. */

tests/other/metadce/minimal_main_Oz_USE_PTHREADS_PROXY_TO_PTHREAD.funcs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ $em_queued_call_malloc
3232
$emscripten_async_run_in_main_thread
3333
$emscripten_current_thread_process_queued_calls
3434
$emscripten_dispatch_to_thread_
35+
$emscripten_futex_wait
3536
$emscripten_main_thread_process_queued_calls
3637
$emscripten_proxy_main
3738
$emscripten_run_in_main_runtime_thread_js
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
16699
1+
16817

0 commit comments

Comments
 (0)