diff --git a/emscripten-version.txt b/emscripten-version.txt index c8d10bd20fa3f..417d11289d72c 100644 --- a/emscripten-version.txt +++ b/emscripten-version.txt @@ -1 +1 @@ -"1.38.14" +"1.38.15" diff --git a/src/library_pthread.js b/src/library_pthread.js index 570aa4166567f..aef6132088618 100644 --- a/src/library_pthread.js +++ b/src/library_pthread.js @@ -171,7 +171,7 @@ var LibraryPThread = { } if (transferList.length > 0) { postMessage({ - targetThread: parentThreadId, + targetThread: _emscripten_main_browser_thread_id(), cmd: 'objectTransfer', offscreenCanvases: offscreenCanvases, moduleCanvasId: Module['canvas'].id, // moduleCanvasId specifies which canvas is denoted via the "#canvas" shorthand. @@ -269,12 +269,13 @@ var LibraryPThread = { (function(worker) { worker.onmessage = function(e) { var d = e.data; + // Sometimes we need to backproxy events to the calling thread (e.g. HTML5 DOM events handlers such as emscripten_set_mousemove_callback()), so keep track in a globally accessible variable about the thread that initiated the proxying. + if (worker.pthread) PThread.currentProxiedOperationCallerThread = worker.pthread.threadInfoStruct; // TODO: Move the proxied call mechanism into a queue inside heap. if (d.proxiedCall) { var returnValue; var funcTable = (d.func >= 0) ? proxiedFunctionTable : ASM_CONSTS; var funcIdx = (d.func >= 0) ? d.func : (-1 - d.func); - PThread.currentProxiedOperationCallerThread = worker.pthread.threadInfoStruct; // Sometimes we need to backproxy events to the calling thread (e.g. HTML5 DOM events handlers such as emscripten_set_mousemove_callback()), so keep track in a globally accessible variable about the thread that initiated the proxying. switch(d.proxiedCall & 31) { case 1: returnValue = funcTable[funcIdx](); break; case 2: returnValue = funcTable[funcIdx](d.p0); break; @@ -302,6 +303,7 @@ var LibraryPThread = { Atomics.store(HEAP32, waitAddress >> 2, 1); Atomics.wake(HEAP32, waitAddress >> 2, 1); } + PThread.currentProxiedOperationCallerThread = undefined; return; } @@ -313,6 +315,7 @@ var LibraryPThread = { } else { console.error('Internal error! Worker sent a message "' + d.cmd + '" to target pthread ' + d.targetThread + ', but that thread no longer exists!'); } + PThread.currentProxiedOperationCallerThread = undefined; return; } @@ -345,7 +348,11 @@ var LibraryPThread = { } else if (d.cmd === 'alert') { alert('Thread ' + d.threadId + ': ' + d.text); } else if (d.cmd === 'exit') { - // currently no-op + // Thread is exiting, no-op here + } else if (d.cmd === 'exitProcess') { + // A pthread has requested to exit the whole application process (runtime). + Module['noExitRuntime'] = false; + exit(d.returnCode); } else if (d.cmd === 'cancelDone') { PThread.freeThreadData(worker.pthread); worker.pthread = undefined; // Detach the worker from the pthread object, and return it to the worker pool as an unused worker. @@ -359,6 +366,7 @@ var LibraryPThread = { } else { err("worker sent an unknown command " + d.cmd); } + PThread.currentProxiedOperationCallerThread = undefined; }; worker.onerror = function(e) { @@ -621,7 +629,10 @@ var LibraryPThread = { if (inheritSched) { var prevSchedPolicy = {{{ makeGetValue('attr', 20/*_a_policy*/, 'i32') }}}; var prevSchedPrio = {{{ makeGetValue('attr', 24/*_a_prio*/, 'i32') }}}; - _pthread_getschedparam(_pthread_self(), attr + 20, attr + 24); + // If we are inheriting the scheduling properties from the parent thread, we need to identify the parent thread properly - this function call may + // be getting proxied, in which case _pthread_self() will point to the thread performing the proxying, not the thread that initiated the call. + var parentThreadPtr = PThread.currentProxiedOperationCallerThread ? PThread.currentProxiedOperationCallerThread : _pthread_self(); + _pthread_getschedparam(parentThreadPtr, attr + 20, attr + 24); schedPolicy = {{{ makeGetValue('attr', 20/*_a_policy*/, 'i32') }}}; schedPrio = {{{ makeGetValue('attr', 24/*_a_prio*/, 'i32') }}}; {{{ makeSetValue('attr', 20/*_a_policy*/, 'prevSchedPolicy', 'i32') }}}; diff --git a/src/library_pthread_stub.js b/src/library_pthread_stub.js index 1ea1199b350b7..af2c3b3aa5e9a 100644 --- a/src/library_pthread_stub.js +++ b/src/library_pthread_stub.js @@ -218,6 +218,9 @@ var LibraryPThreadStub = { sem_trywait: function() {}, sem_destroy: function() {}, + emscripten_main_browser_thread_id__deps: ['pthread_self'], + emscripten_main_browser_thread_id: function() { return _pthread_self(); }, + // When pthreads is not enabled, we can't use the Atomics futex api to do proper sleeps, so simulate a busy spin wait loop instead. usleep: function(useconds) { // int usleep(useconds_t useconds); diff --git a/src/preamble.js b/src/preamble.js index 5c17968354caa..f978e5eadaaf8 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -1600,6 +1600,7 @@ function ensureInitRuntime() { #if USE_PTHREADS // Pass the thread address inside the asm.js scope to store it for fast access that avoids the need for a FFI out. __register_pthread_ptr(PThread.mainThreadBlock, /*isMainBrowserThread=*/!ENVIRONMENT_IS_WORKER, /*isMainRuntimeThread=*/1); + _emscripten_register_main_browser_thread_id(PThread.mainThreadBlock); #endif callRuntimeCallbacks(__ATINIT__); } diff --git a/src/struct_info.json b/src/struct_info.json index c3db819795b68..2719ead440975 100644 --- a/src/struct_info.json +++ b/src/struct_info.json @@ -1236,7 +1236,10 @@ "EM_HTML5_SHORT_STRING_LEN_BYTES", "EM_HTML5_MEDIUM_STRING_LEN_BYTES", - "EM_HTML5_LONG_STRING_LEN_BYTES" + "EM_HTML5_LONG_STRING_LEN_BYTES", + + "EM_CALLBACK_THREAD_CONTEXT_MAIN_BROWSER_THREAD", + "EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD" ], "structs": { "EmscriptenKeyboardEvent": [ @@ -1491,7 +1494,15 @@ "EM_THREAD_STATUS_SLEEPING", "EM_THREAD_STATUS_WAITFUTEX", "EM_THREAD_STATUS_WAITPROXY", - "EM_THREAD_STATUS_FINISHED" + "EM_THREAD_STATUS_FINISHED", + "EM_FUNC_SIG_V", + "EM_FUNC_SIG_VI", + "EM_FUNC_SIG_VII", + "EM_FUNC_SIG_VIII", + "EM_FUNC_SIG_I", + "EM_FUNC_SIG_II", + "EM_FUNC_SIG_III", + "EM_FUNC_SIG_IIII" ] }, { diff --git a/system/include/emscripten/html5.h b/system/include/emscripten/html5.h index c63a1ba1c97de..388d24d655617 100644 --- a/system/include/emscripten/html5.h +++ b/system/include/emscripten/html5.h @@ -8,6 +8,18 @@ #ifndef __emscripten_events_h__ #define __emscripten_events_h__ +#ifdef __cplusplus +#if !defined(__DEFINED_pthread_t) +typedef unsigned long pthread_t; +#define __DEFINED_pthread_t +#endif +#else +#if !defined(__DEFINED_pthread_t) +typedef struct __pthread * pthread_t; +#define __DEFINED_pthread_t +#endif +#endif + #ifdef __cplusplus extern "C" { #endif @@ -452,6 +464,9 @@ extern EMSCRIPTEN_RESULT emscripten_get_canvas_element_size(const char *target, extern EMSCRIPTEN_RESULT emscripten_set_element_css_size(const char *target, double width, double height); extern EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height); +#define EM_CALLBACK_THREAD_CONTEXT_MAIN_BROWSER_THREAD ((pthread_t)0x1) +#define EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD ((pthread_t)0x2) + #ifdef __cplusplus } // ~extern "C" #endif diff --git a/system/include/emscripten/threading.h b/system/include/emscripten/threading.h index 23d3312763ca7..9235fc4ecc401 100644 --- a/system/include/emscripten/threading.h +++ b/system/include/emscripten/threading.h @@ -93,6 +93,7 @@ int emscripten_futex_wake(volatile void/*uint32_t*/ *addr, int count); typedef union em_variant_val { int i; + int64_t i64; float f; double d; void *vp; @@ -108,6 +109,9 @@ typedef struct em_queued_call em_variant_val args[EM_QUEUED_CALL_MAX_ARGS]; em_variant_val returnValue; + // An optional pointer to a secondary data block that should be free()d when this queued call is freed. + void *satelliteData; + // If true, the caller has "detached" itself from this call // object and the Emscripten main runtime thread should free up // this em_queued_call object after it has been executed. If @@ -124,25 +128,94 @@ void *emscripten_sync_run_in_main_thread_7(int function, void *arg1, void *arg2, typedef void (*em_func_v)(void); typedef void (*em_func_vi)(int); +typedef void (*em_func_vf)(float); typedef void (*em_func_vii)(int, int); +typedef void (*em_func_vif)(int, float); +typedef void (*em_func_vff)(float, float); typedef void (*em_func_viii)(int, int, int); +typedef void (*em_func_viif)(int, int, float); +typedef void (*em_func_viff)(int, float, float); +typedef void (*em_func_vfff)(float, float, float); +typedef void (*em_func_viiii)(int, int, int, int); +typedef void (*em_func_vifff)(int, float, float, float); +typedef void (*em_func_vffff)(float, float, float, float); +typedef void (*em_func_viiiii)(int, int, int, int, int); +typedef void (*em_func_viffff)(int, float, float, float, float); +typedef void (*em_func_viiiiii)(int, int, int, int, int, int); +typedef void (*em_func_viiiiiii)(int, int, int, int, int, int, int); +typedef void (*em_func_viiiiiiii)(int, int, int, int, int, int, int, int); +typedef void (*em_func_viiiiiiiii)(int, int, int, int, int, int, int, int, int); typedef int (*em_func_i)(void); typedef int (*em_func_ii)(int); typedef int (*em_func_iii)(int, int); typedef int (*em_func_iiii)(int, int, int); -#define EM_FUNC_SIGNATURE int - -#define EM_FUNC_SIG_V 1024 -#define EM_FUNC_SIG_VI (1024 + 1) -#define EM_FUNC_SIG_VII (1024 + 2) -#define EM_FUNC_SIG_VIII (1024 + 3) -#define EM_FUNC_SIG_I 2048 -#define EM_FUNC_SIG_II (2048 + 1) -#define EM_FUNC_SIG_III (2048 + 2) -#define EM_FUNC_SIG_IIII (2048 + 3) - -#define EM_FUNC_SIG_NUM_FUNC_ARGUMENTS(x) ((x) & 1023) +// Encode function signatures into a single uint32_t integer. +// N.B. This encoding scheme is internal to the implementation, and can change in the future. Do not depend on the +// exact numbers in this scheme. +#define EM_FUNC_SIGNATURE unsigned int + +// The encoding scheme is as follows: +// - highest three bits identify the type of the return value +#define EM_FUNC_SIG_RETURN_VALUE_MASK (0x7U << 29) + +#define EM_FUNC_SIG_RETURN_VALUE_V 0 +#define EM_FUNC_SIG_RETURN_VALUE_I (0x1U << 29) +#define EM_FUNC_SIG_RETURN_VALUE_I64 (0x2U << 29) +#define EM_FUNC_SIG_RETURN_VALUE_F (0x3U << 29) +#define EM_FUNC_SIG_RETURN_VALUE_D (0x4U << 29) + +// - next highest four bits specify the number of input parameters to the function (allowed values are 0-12, inclusively) +#define EM_FUNC_SIG_NUM_PARAMETERS_SHIFT 25 +#define EM_FUNC_SIG_NUM_PARAMETERS_MASK (0xFU << EM_FUNC_SIG_NUM_PARAMETERS_SHIFT) +#define EM_FUNC_SIG_WITH_N_PARAMETERS(x) (((EM_FUNC_SIGNATURE)(x)) << EM_FUNC_SIG_NUM_PARAMETERS_SHIFT) + +// - starting from the lowest bits upwards, each pair of two subsequent bits specifies the type of an input parameter. +// That is, bits 1:0 encode the type of the first input, bits 3:2 encode the type of the second input, and so on. +#define EM_FUNC_SIG_ARGUMENTS_TYPE_MASK (~(EM_FUNC_SIG_RETURN_VALUE_MASK | EM_FUNC_SIG_NUM_PARAMETERS_MASK)) +#define EM_FUNC_SIG_ARGUMENT_TYPE_SIZE_MASK 0x3U +#define EM_FUNC_SIG_ARGUMENT_TYPE_SIZE_SHIFT 2 + +#define EM_FUNC_SIG_PARAM_I 0 +#define EM_FUNC_SIG_PARAM_I64 0x1U +#define EM_FUNC_SIG_PARAM_F 0x2U +#define EM_FUNC_SIG_PARAM_D 0x3U +#define EM_FUNC_SIG_SET_PARAM(i, type) ((EM_FUNC_SIGNATURE)(type) << (2*i)) + +#define EM_FUNC_SIG_V (EM_FUNC_SIG_RETURN_VALUE_V | EM_FUNC_SIG_WITH_N_PARAMETERS(0)) +#define EM_FUNC_SIG_VI (EM_FUNC_SIG_RETURN_VALUE_V | EM_FUNC_SIG_WITH_N_PARAMETERS(1) | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_I)) +#define EM_FUNC_SIG_VF (EM_FUNC_SIG_RETURN_VALUE_V | EM_FUNC_SIG_WITH_N_PARAMETERS(1) | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_F)) +#define EM_FUNC_SIG_VII (EM_FUNC_SIG_RETURN_VALUE_V | EM_FUNC_SIG_WITH_N_PARAMETERS(2) | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(1, EM_FUNC_SIG_PARAM_I)) +#define EM_FUNC_SIG_VIF (EM_FUNC_SIG_RETURN_VALUE_V | EM_FUNC_SIG_WITH_N_PARAMETERS(2) | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(1, EM_FUNC_SIG_PARAM_F)) +#define EM_FUNC_SIG_VFF (EM_FUNC_SIG_RETURN_VALUE_V | EM_FUNC_SIG_WITH_N_PARAMETERS(2) | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_F) | EM_FUNC_SIG_SET_PARAM(1, EM_FUNC_SIG_PARAM_F)) +#define EM_FUNC_SIG_VIII (EM_FUNC_SIG_RETURN_VALUE_V | EM_FUNC_SIG_WITH_N_PARAMETERS(3) | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(1, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(2, EM_FUNC_SIG_PARAM_I)) +#define EM_FUNC_SIG_VIIF (EM_FUNC_SIG_RETURN_VALUE_V | EM_FUNC_SIG_WITH_N_PARAMETERS(3) | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(1, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(2, EM_FUNC_SIG_PARAM_F)) +#define EM_FUNC_SIG_VIFF (EM_FUNC_SIG_RETURN_VALUE_V | EM_FUNC_SIG_WITH_N_PARAMETERS(3) | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(1, EM_FUNC_SIG_PARAM_F) | EM_FUNC_SIG_SET_PARAM(2, EM_FUNC_SIG_PARAM_F)) +#define EM_FUNC_SIG_VFFF (EM_FUNC_SIG_RETURN_VALUE_V | EM_FUNC_SIG_WITH_N_PARAMETERS(3) | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_F) | EM_FUNC_SIG_SET_PARAM(1, EM_FUNC_SIG_PARAM_F) | EM_FUNC_SIG_SET_PARAM(2, EM_FUNC_SIG_PARAM_F)) +#define EM_FUNC_SIG_VIIII (EM_FUNC_SIG_RETURN_VALUE_V | EM_FUNC_SIG_WITH_N_PARAMETERS(4) | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(1, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(2, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(3, EM_FUNC_SIG_PARAM_I)) +#define EM_FUNC_SIG_VIIFI (EM_FUNC_SIG_RETURN_VALUE_V | EM_FUNC_SIG_WITH_N_PARAMETERS(4) | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(1, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(2, EM_FUNC_SIG_PARAM_F) | EM_FUNC_SIG_SET_PARAM(3, EM_FUNC_SIG_PARAM_I)) +#define EM_FUNC_SIG_VIFFF (EM_FUNC_SIG_RETURN_VALUE_V | EM_FUNC_SIG_WITH_N_PARAMETERS(4) | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(1, EM_FUNC_SIG_PARAM_F) | EM_FUNC_SIG_SET_PARAM(2, EM_FUNC_SIG_PARAM_F) | EM_FUNC_SIG_SET_PARAM(3, EM_FUNC_SIG_PARAM_F)) +#define EM_FUNC_SIG_VFFFF (EM_FUNC_SIG_RETURN_VALUE_V | EM_FUNC_SIG_WITH_N_PARAMETERS(4) | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_F) | EM_FUNC_SIG_SET_PARAM(1, EM_FUNC_SIG_PARAM_F) | EM_FUNC_SIG_SET_PARAM(2, EM_FUNC_SIG_PARAM_F) | EM_FUNC_SIG_SET_PARAM(3, EM_FUNC_SIG_PARAM_F)) +#define EM_FUNC_SIG_VIIIII (EM_FUNC_SIG_RETURN_VALUE_V | EM_FUNC_SIG_WITH_N_PARAMETERS(5) | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(1, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(2, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(3, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(4, EM_FUNC_SIG_PARAM_I)) +#define EM_FUNC_SIG_VIFFFF (EM_FUNC_SIG_RETURN_VALUE_V | EM_FUNC_SIG_WITH_N_PARAMETERS(5) | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(1, EM_FUNC_SIG_PARAM_F) | EM_FUNC_SIG_SET_PARAM(2, EM_FUNC_SIG_PARAM_F) | EM_FUNC_SIG_SET_PARAM(3, EM_FUNC_SIG_PARAM_F) | EM_FUNC_SIG_SET_PARAM(4, EM_FUNC_SIG_PARAM_F)) +#define EM_FUNC_SIG_VIIIIII (EM_FUNC_SIG_RETURN_VALUE_V | EM_FUNC_SIG_WITH_N_PARAMETERS(6) | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(1, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(2, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(3, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(4, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(5, EM_FUNC_SIG_PARAM_I)) +#define EM_FUNC_SIG_VIIIIIII (EM_FUNC_SIG_RETURN_VALUE_V | EM_FUNC_SIG_WITH_N_PARAMETERS(7) | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(1, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(2, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(3, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(4, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(5, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(6, EM_FUNC_SIG_PARAM_I)) +#define EM_FUNC_SIG_VIIIIIIII (EM_FUNC_SIG_RETURN_VALUE_V | EM_FUNC_SIG_WITH_N_PARAMETERS(8) | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(1, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(2, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(3, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(4, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(5, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(6, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(7, EM_FUNC_SIG_PARAM_I)) +#define EM_FUNC_SIG_VIIIIIIIII (EM_FUNC_SIG_RETURN_VALUE_V | EM_FUNC_SIG_WITH_N_PARAMETERS(9) | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(1, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(2, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(3, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(4, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(5, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(6, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(7, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(8, EM_FUNC_SIG_PARAM_I)) +#define EM_FUNC_SIG_VIIIIIIIIII (EM_FUNC_SIG_RETURN_VALUE_V | EM_FUNC_SIG_WITH_N_PARAMETERS(10) | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(1, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(2, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(3, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(4, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(5, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(6, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(7, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(8, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(9, EM_FUNC_SIG_PARAM_I)) +#define EM_FUNC_SIG_VIIIIIIIIIII (EM_FUNC_SIG_RETURN_VALUE_V | EM_FUNC_SIG_WITH_N_PARAMETERS(11) | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(1, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(2, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(3, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(4, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(5, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(6, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(7, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(8, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(9, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(10, EM_FUNC_SIG_PARAM_I)) +#define EM_FUNC_SIG_I (EM_FUNC_SIG_RETURN_VALUE_I | EM_FUNC_SIG_WITH_N_PARAMETERS(0)) +#define EM_FUNC_SIG_II (EM_FUNC_SIG_RETURN_VALUE_I | EM_FUNC_SIG_WITH_N_PARAMETERS(1) | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_I)) +#define EM_FUNC_SIG_III (EM_FUNC_SIG_RETURN_VALUE_I | EM_FUNC_SIG_WITH_N_PARAMETERS(2) | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(1, EM_FUNC_SIG_PARAM_I)) +#define EM_FUNC_SIG_IIII (EM_FUNC_SIG_RETURN_VALUE_I | EM_FUNC_SIG_WITH_N_PARAMETERS(3) | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(1, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(2, EM_FUNC_SIG_PARAM_I)) +#define EM_FUNC_SIG_IIIII (EM_FUNC_SIG_RETURN_VALUE_I | EM_FUNC_SIG_WITH_N_PARAMETERS(4) | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(1, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(2, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(3, EM_FUNC_SIG_PARAM_I)) +#define EM_FUNC_SIG_IIIIII (EM_FUNC_SIG_RETURN_VALUE_I | EM_FUNC_SIG_WITH_N_PARAMETERS(5) | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(1, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(2, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(3, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(4, EM_FUNC_SIG_PARAM_I)) +#define EM_FUNC_SIG_IIIIIII (EM_FUNC_SIG_RETURN_VALUE_I | EM_FUNC_SIG_WITH_N_PARAMETERS(6) | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(1, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(2, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(3, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(4, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(5, EM_FUNC_SIG_PARAM_I)) +#define EM_FUNC_SIG_IIIIIIII (EM_FUNC_SIG_RETURN_VALUE_I | EM_FUNC_SIG_WITH_N_PARAMETERS(7) | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(1, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(2, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(3, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(4, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(5, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(6, EM_FUNC_SIG_PARAM_I)) +#define EM_FUNC_SIG_IIIIIIIII (EM_FUNC_SIG_RETURN_VALUE_I | EM_FUNC_SIG_WITH_N_PARAMETERS(8) | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(1, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(2, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(3, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(4, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(5, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(6, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(7, EM_FUNC_SIG_PARAM_I)) +#define EM_FUNC_SIG_IIIIIIIIII (EM_FUNC_SIG_RETURN_VALUE_I | EM_FUNC_SIG_WITH_N_PARAMETERS(9) | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(1, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(2, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(3, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(4, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(5, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(6, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(7, EM_FUNC_SIG_PARAM_I) | EM_FUNC_SIG_SET_PARAM(8, EM_FUNC_SIG_PARAM_I)) + +#define EM_FUNC_SIG_NUM_FUNC_ARGUMENTS(x) ((((EM_FUNC_SIGNATURE)x) & EM_FUNC_SIG_NUM_PARAMETERS_MASK) >> EM_FUNC_SIG_NUM_PARAMETERS_SHIFT) // Runs the given function synchronously on the main Emscripten runtime thread. // If this thread is the main thread, the operation is immediately performed, and the result is returned. @@ -177,6 +250,11 @@ EMSCRIPTEN_RESULT emscripten_wait_for_call_i(em_queued_call *call, double timeou void emscripten_async_waitable_close(em_queued_call *call); +// Queues the given function call to be performed on the specified thread. +void emscripten_async_queue_on_thread_(pthread_t target_thread, EM_FUNC_SIGNATURE sig, void *func_ptr, void *satellite, ...); + +#define emscripten_async_queue_on_thread(target_thread, sig, func_ptr, satellite, ...) emscripten_async_queue_on_thread_((target_thread), (sig), (void*)(func_ptr), (satellite),##__VA_ARGS__) + // Returns 1 if the current thread is the thread that hosts the Emscripten runtime. int emscripten_is_main_runtime_thread(void); @@ -189,6 +267,10 @@ int emscripten_is_main_browser_thread(void); // that are considered cancellation points. void emscripten_main_thread_process_queued_calls(void); +void emscripten_current_thread_process_queued_calls(void); + +pthread_t emscripten_main_browser_thread_id(void); + // Direct syscall access, second argument is a varargs pointer. used in proxying int emscripten_syscall(int, void*); diff --git a/system/lib/pthread/library_pthread.c b/system/lib/pthread/library_pthread.c index 805f3104cf1b9..8fe9888ee4fa4 100644 --- a/system/lib/pthread/library_pthread.c +++ b/system/lib/pthread/library_pthread.c @@ -169,11 +169,13 @@ static em_queued_call *em_queued_call_malloc() { call->operationDone = 0; call->functionPtr = 0; + call->satelliteData = 0; } return call; } static void em_queued_call_free(em_queued_call *call) { + if (call) free(call->satelliteData); free(call); } @@ -190,8 +192,23 @@ static void _do_call(em_queued_call *q) case EM_PROXIED_SYSCALL: q->returnValue.i = emscripten_syscall(q->args[0].i, q->args[1].vp); break; case EM_FUNC_SIG_V: ((em_func_v)q->functionPtr)(); break; case EM_FUNC_SIG_VI: ((em_func_vi)q->functionPtr)(q->args[0].i); break; + case EM_FUNC_SIG_VF: ((em_func_vf)q->functionPtr)(q->args[0].f); break; case EM_FUNC_SIG_VII: ((em_func_vii)q->functionPtr)(q->args[0].i, q->args[1].i); break; + case EM_FUNC_SIG_VIF: ((em_func_vif)q->functionPtr)(q->args[0].i, q->args[1].f); break; + case EM_FUNC_SIG_VFF: ((em_func_vff)q->functionPtr)(q->args[0].f, q->args[1].f); break; case EM_FUNC_SIG_VIII: ((em_func_viii)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].i); break; + case EM_FUNC_SIG_VIIF: ((em_func_viif)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].f); break; + case EM_FUNC_SIG_VIFF: ((em_func_viff)q->functionPtr)(q->args[0].i, q->args[1].f, q->args[2].f); break; + case EM_FUNC_SIG_VFFF: ((em_func_vfff)q->functionPtr)(q->args[0].f, q->args[1].f, q->args[2].f); break; + case EM_FUNC_SIG_VIIII: ((em_func_viiii)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].i, q->args[3].i); break; + case EM_FUNC_SIG_VIFFF: ((em_func_vifff)q->functionPtr)(q->args[0].i, q->args[1].f, q->args[2].f, q->args[3].f); break; + case EM_FUNC_SIG_VFFFF: ((em_func_vffff)q->functionPtr)(q->args[0].f, q->args[1].f, q->args[2].f, q->args[3].f); break; + case EM_FUNC_SIG_VIIIII: ((em_func_viiiii)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].i, q->args[3].i, q->args[4].i); break; + case EM_FUNC_SIG_VIFFFF: ((em_func_viffff)q->functionPtr)(q->args[0].i, q->args[1].f, q->args[2].f, q->args[3].f, q->args[4].f); break; + case EM_FUNC_SIG_VIIIIII: ((em_func_viiiiii)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].i, q->args[3].i, q->args[4].i, q->args[5].i); break; + case EM_FUNC_SIG_VIIIIIII: ((em_func_viiiiiii)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].i, q->args[3].i, q->args[4].i, q->args[5].i, q->args[6].i); break; + case EM_FUNC_SIG_VIIIIIIII: ((em_func_viiiiiiii)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].i, q->args[3].i, q->args[4].i, q->args[5].i, q->args[6].i, q->args[7].i); break; + case EM_FUNC_SIG_VIIIIIIIII: ((em_func_viiiiiiiii)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].i, q->args[3].i, q->args[4].i, q->args[5].i, q->args[6].i, q->args[7].i, q->args[8].i); break; case EM_FUNC_SIG_I: q->returnValue.i = ((em_func_i)q->functionPtr)(); break; case EM_FUNC_SIG_II: q->returnValue.i = ((em_func_ii)q->functionPtr)(q->args[0].i); break; case EM_FUNC_SIG_III: q->returnValue.i = ((em_func_iii)q->functionPtr)(q->args[0].i, q->args[1].i); break; @@ -211,10 +228,52 @@ static void _do_call(em_queued_call *q) } #define CALL_QUEUE_SIZE 128 -static em_queued_call **call_queue = 0; -static volatile int call_queue_head = 0; // Shared data synchronized by call_queue_lock. -static volatile int call_queue_tail = 0; + +typedef struct CallQueue +{ + void *target_thread; + em_queued_call **call_queue; + int call_queue_head; // Shared data synchronized by call_queue_lock. + int call_queue_tail; + struct CallQueue *next; +} CallQueue; + +// Currently global to the queue, but this can be improved to be per-queue specific. (TODO: with lockfree list operations on callQueue_head, or removing the list by moving this data to pthread_t) static pthread_mutex_t call_queue_lock = PTHREAD_MUTEX_INITIALIZER; +static CallQueue *callQueue_head = 0; + +static CallQueue *GetQueue(void *target) // Not thread safe, call while having call_queue_lock obtained. +{ + assert(target); + CallQueue *q = callQueue_head; + while(q && q->target_thread != target) + q = q->next; + return q; +} + +static CallQueue *GetOrAllocateQueue(void *target) // Not thread safe, call while having call_queue_lock obtained. +{ + CallQueue *q = GetQueue(target); + if (q) return q; + + q = (CallQueue *)malloc(sizeof(CallQueue)); + q->target_thread = target; + q->call_queue = 0; + q->call_queue_head = 0; + q->call_queue_tail = 0; + q->next = 0; + if (callQueue_head) + { + CallQueue *last = callQueue_head; + while(last->next) last = last->next; + last->next = q; + } + else + { + callQueue_head = q; + } + return q; +} EMSCRIPTEN_RESULT emscripten_wait_for_call_v(em_queued_call *call, double timeoutMSecs) { @@ -243,45 +302,115 @@ EMSCRIPTEN_RESULT emscripten_wait_for_call_i(em_queued_call *call, double timeou return res; } -void EMSCRIPTEN_KEEPALIVE emscripten_async_run_in_main_thread(em_queued_call *call) +static pthread_t main_browser_thread_id_ = 0; + +void EMSCRIPTEN_KEEPALIVE emscripten_register_main_browser_thread_id(pthread_t main_browser_thread_id) +{ + main_browser_thread_id_ = main_browser_thread_id; +} + +pthread_t EMSCRIPTEN_KEEPALIVE emscripten_main_browser_thread_id() +{ + return main_browser_thread_id_; +} + +static void EMSCRIPTEN_KEEPALIVE emscripten_async_queue_call_on_thread(pthread_t target_thread, em_queued_call *call) { assert(call); - // If we are the main Emscripten runtime thread, we can just call the operation directly. - if (emscripten_is_main_runtime_thread()) { + +// #if PTHREADS_DEBUG // TODO: Create a debug version of pthreads library +// EM_ASM_INT({dump('thread ' + _pthread_self() + ' (ENVIRONMENT_IS_WORKER: ' + ENVIRONMENT_IS_WORKER + '), queueing call of function enum=' + $0 + '/ptr=' + $1 + ' on thread ' + $2 + '\n' + new Error().stack)}, call->functionEnum, call->functionPtr, target_thread); +// #endif + + // Can't be a null pointer here, but can't be EM_CALLBACK_THREAD_CONTEXT_MAIN_BROWSER_THREAD either. + assert(target_thread); + if (target_thread == EM_CALLBACK_THREAD_CONTEXT_MAIN_BROWSER_THREAD) target_thread = emscripten_main_browser_thread_id(); + + // If we are the target recipient of this message, we can just call the operation directly. + if (target_thread == EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD || target_thread == pthread_self()) { _do_call(call); return; } // Add the operation to the call queue of the main runtime thread. pthread_mutex_lock(&call_queue_lock); - if (!call_queue) call_queue = malloc(sizeof(em_queued_call*) * CALL_QUEUE_SIZE); // Shared data synchronized by call_queue_lock. + CallQueue *q = GetOrAllocateQueue(target_thread); + if (!q->call_queue) q->call_queue = malloc(sizeof(em_queued_call*) * CALL_QUEUE_SIZE); // Shared data synchronized by call_queue_lock. - int head = emscripten_atomic_load_u32((void*)&call_queue_head); - int tail = emscripten_atomic_load_u32((void*)&call_queue_tail); + int head = emscripten_atomic_load_u32((void*)&q->call_queue_head); + int tail = emscripten_atomic_load_u32((void*)&q->call_queue_tail); int new_tail = (tail + 1) % CALL_QUEUE_SIZE; while(new_tail == head) { // Queue is full? pthread_mutex_unlock(&call_queue_lock); - emscripten_futex_wait((void*)&call_queue_head, head, INFINITY); - pthread_mutex_lock(&call_queue_lock); - head = emscripten_atomic_load_u32((void*)&call_queue_head); - tail = emscripten_atomic_load_u32((void*)&call_queue_tail); - new_tail = (tail + 1) % CALL_QUEUE_SIZE; + + // If queue of the main browser thread is full, then we wait. (never drop messages for the main browser thread) + if (target_thread == emscripten_main_browser_thread_id()) + { + emscripten_futex_wait((void*)&q->call_queue_head, head, INFINITY); + pthread_mutex_lock(&call_queue_lock); + head = emscripten_atomic_load_u32((void*)&q->call_queue_head); + tail = emscripten_atomic_load_u32((void*)&q->call_queue_tail); + new_tail = (tail + 1) % CALL_QUEUE_SIZE; + } + else + { + // For the queues of other threads, just drop the message. +// #if DEBUG TODO: a debug build of pthreads library? +// EM_ASM(console.error('Pthread queue overflowed, dropping queued message to thread. ' + new Error().stack)); +// #endif + em_queued_call_free(call); + return; + } } - call_queue[tail] = call; + q->call_queue[tail] = call; // If the call queue was empty, the main runtime thread is likely idle in the browser event loop, // so send a message to it to ensure that it wakes up to start processing the command we have posted. if (head == tail) { - EM_ASM(postMessage({ cmd: 'processQueuedMainThreadWork' })); + if (target_thread == emscripten_main_browser_thread_id()) + { + EM_ASM(postMessage({ cmd: 'processQueuedMainThreadWork' })); + } + else + { + int success = EM_ASM_INT({ + if (!ENVIRONMENT_IS_PTHREAD) { + if (!PThread.pthreads[$0] || !PThread.pthreads[$0].worker) { +// #if DEBUG +// Module.printErr('Cannot send message to thread with ID ' + $0 + ', unknown thread ID!'); +// #endif + return 0; + } + PThread.pthreads[$0].worker.postMessage({ cmd: 'processThreadQueue' }); + } else { + postMessage({ targetThread: $0, cmd: 'processThreadQueue' }); + } + return 1; + }, target_thread); + + // Failed to dispatch the thread, delete the crafted message. + if (!success) { + em_queued_call_free(call); + pthread_mutex_unlock(&call_queue_lock); + return; + } + + // TODO: Need to postMessage() to a specific target Worker that is hosting target_thread.... + } } - emscripten_atomic_store_u32((void*)&call_queue_tail, new_tail); + emscripten_atomic_store_u32((void*)&q->call_queue_tail, new_tail); pthread_mutex_unlock(&call_queue_lock); } +void EMSCRIPTEN_KEEPALIVE emscripten_async_run_in_main_thread(em_queued_call *call) +{ + emscripten_async_queue_call_on_thread(emscripten_main_browser_thread_id(), call); +} + void EMSCRIPTEN_KEEPALIVE emscripten_sync_run_in_main_thread(em_queued_call *call) { emscripten_async_run_in_main_thread(call); @@ -405,39 +534,61 @@ void * EMSCRIPTEN_KEEPALIVE emscripten_sync_run_in_main_thread_7(int function, v return q.returnValue.vp; } -static int bool_inside_nested_process_queued_calls = 0; - -void EMSCRIPTEN_KEEPALIVE emscripten_main_thread_process_queued_calls() +void EMSCRIPTEN_KEEPALIVE emscripten_current_thread_process_queued_calls() { - assert(emscripten_is_main_runtime_thread() && "emscripten_main_thread_process_queued_calls must be called from the main thread!"); - if (!emscripten_is_main_runtime_thread()) return; +// #if PTHREADS_DEBUG == 2 +// EM_ASM(console.error('thread ' + _pthread_self() + ': emscripten_current_thread_process_queued_calls(), ' + new Error().stack)); +// #endif + + // TODO: Under certain conditions we may want to have a nesting guard also for pthreads (and it will certainly be cleaner that way), but + // we don't yet have TLS variables outside pthread_set/getspecific, so convert this to TLS after TLS is implemented. + static int bool_main_thread_inside_nested_process_queued_calls = 0; + + if (emscripten_is_main_browser_thread()) + { + // It is possible that when processing a queued call, the call flow leads back to calling this function in a nested fashion! + // Therefore this scenario must explicitly be detected, and processing the queue must be avoided if we are nesting, or otherwise + // the same queued calls would be processed again and again. + if (bool_main_thread_inside_nested_process_queued_calls) return; + // This must be before pthread_mutex_lock(), since pthread_mutex_lock() can call back to this function. + bool_main_thread_inside_nested_process_queued_calls = 1; + } - // It is possible that when processing a queued call, the call flow leads back to calling this function in a nested fashion! - // Therefore this scenario must explicitly be detected, and processing the queue must be avoided if we are nesting, or otherwise - // the same queued calls would be processed again and again. - if (bool_inside_nested_process_queued_calls) return; - // This must be before pthread_mutex_lock(), since pthread_mutex_lock() can call back to this function. - bool_inside_nested_process_queued_calls = 1; pthread_mutex_lock(&call_queue_lock); - int head = emscripten_atomic_load_u32((void*)&call_queue_head); - int tail = emscripten_atomic_load_u32((void*)&call_queue_tail); + CallQueue *q = GetQueue(pthread_self()); + if (!q) + { + pthread_mutex_unlock(&call_queue_lock); + if (emscripten_is_main_browser_thread()) bool_main_thread_inside_nested_process_queued_calls = 0; + return; + } + + int head = emscripten_atomic_load_u32((void*)&q->call_queue_head); + int tail = emscripten_atomic_load_u32((void*)&q->call_queue_tail); while (head != tail) { // Assume that the call is heavy, so unlock access to the call queue while it is being performed. pthread_mutex_unlock(&call_queue_lock); - _do_call(call_queue[head]); + _do_call(q->call_queue[head]); pthread_mutex_lock(&call_queue_lock); head = (head + 1) % CALL_QUEUE_SIZE; - emscripten_atomic_store_u32((void*)&call_queue_head, head); - tail = emscripten_atomic_load_u32((void*)&call_queue_tail); + emscripten_atomic_store_u32((void*)&q->call_queue_head, head); + tail = emscripten_atomic_load_u32((void*)&q->call_queue_tail); } pthread_mutex_unlock(&call_queue_lock); // If the queue was full and we had waiters pending to get to put data to queue, wake them up. - emscripten_futex_wake((void*)&call_queue_head, 0x7FFFFFFF); + emscripten_futex_wake((void*)&q->call_queue_head, 0x7FFFFFFF); - bool_inside_nested_process_queued_calls = 0; + if (emscripten_is_main_browser_thread()) bool_main_thread_inside_nested_process_queued_calls = 0; +} + +void EMSCRIPTEN_KEEPALIVE emscripten_main_thread_process_queued_calls() +{ + if (!emscripten_is_main_browser_thread()) return; + + emscripten_current_thread_process_queued_calls(); } int emscripten_sync_run_in_main_runtime_thread_(EM_FUNC_SIGNATURE sig, void *func_ptr, ...) @@ -445,10 +596,20 @@ int emscripten_sync_run_in_main_runtime_thread_(EM_FUNC_SIGNATURE sig, void *fun int numArguments = EM_FUNC_SIG_NUM_FUNC_ARGUMENTS(sig); em_queued_call q = { sig, func_ptr }; + EM_FUNC_SIGNATURE argumentsType = sig & EM_FUNC_SIG_ARGUMENTS_TYPE_MASK; va_list args; va_start(args, func_ptr); for(int i = 0; i < numArguments; ++i) - q.args[i].i = va_arg(args, int); + { + switch((argumentsType & EM_FUNC_SIG_ARGUMENT_TYPE_SIZE_MASK)) + { + case EM_FUNC_SIG_PARAM_I: q.args[i].i = va_arg(args, int); break; + case EM_FUNC_SIG_PARAM_I64: q.args[i].i64 = va_arg(args, int64_t); break; + case EM_FUNC_SIG_PARAM_F: q.args[i].f = (float)va_arg(args, double); break; + case EM_FUNC_SIG_PARAM_D: q.args[i].d = va_arg(args, double); break; + } + argumentsType >>= EM_FUNC_SIG_ARGUMENT_TYPE_SIZE_SHIFT; + } va_end(args); emscripten_sync_run_in_main_thread(&q); return q.returnValue.i; @@ -462,10 +623,20 @@ void emscripten_async_run_in_main_runtime_thread_(EM_FUNC_SIGNATURE sig, void *f q->functionEnum = sig; q->functionPtr = func_ptr; + EM_FUNC_SIGNATURE argumentsType = sig & EM_FUNC_SIG_ARGUMENTS_TYPE_MASK; va_list args; va_start(args, func_ptr); for(int i = 0; i < numArguments; ++i) - q->args[i].i = va_arg(args, int); + { + switch((argumentsType & EM_FUNC_SIG_ARGUMENT_TYPE_SIZE_MASK)) + { + case EM_FUNC_SIG_PARAM_I: q->args[i].i = va_arg(args, int); break; + case EM_FUNC_SIG_PARAM_I64: q->args[i].i64 = va_arg(args, int64_t); break; + case EM_FUNC_SIG_PARAM_F: q->args[i].f = (float)va_arg(args, double); break; + case EM_FUNC_SIG_PARAM_D: q->args[i].d = va_arg(args, double); break; + } + argumentsType >>= EM_FUNC_SIG_ARGUMENT_TYPE_SIZE_SHIFT; + } va_end(args); // 'async' runs are fire and forget, where the caller detaches itself from the call object after returning here, // and it is the callee's responsibility to free up the memory after the call has been performed. @@ -481,10 +652,20 @@ em_queued_call *emscripten_async_waitable_run_in_main_runtime_thread_(EM_FUNC_SI q->functionEnum = sig; q->functionPtr = func_ptr; + EM_FUNC_SIGNATURE argumentsType = sig & EM_FUNC_SIG_ARGUMENTS_TYPE_MASK; va_list args; va_start(args, func_ptr); for(int i = 0; i < numArguments; ++i) - q->args[i].i = va_arg(args, int); + { + switch((argumentsType & EM_FUNC_SIG_ARGUMENT_TYPE_SIZE_MASK)) + { + case EM_FUNC_SIG_PARAM_I: q->args[i].i = va_arg(args, int); break; + case EM_FUNC_SIG_PARAM_I64: q->args[i].i64 = va_arg(args, int64_t); break; + case EM_FUNC_SIG_PARAM_F: q->args[i].f = (float)va_arg(args, double); break; + case EM_FUNC_SIG_PARAM_D: q->args[i].d = va_arg(args, double); break; + } + argumentsType >>= EM_FUNC_SIG_ARGUMENT_TYPE_SIZE_SHIFT; + } va_end(args); // 'async waitable' runs are waited on by the caller, so the call object needs to remain alive for the caller to // access it after the operation is done. The caller is responsible in cleaning up the object after done. @@ -493,6 +674,38 @@ em_queued_call *emscripten_async_waitable_run_in_main_runtime_thread_(EM_FUNC_SI return q; } +void EMSCRIPTEN_KEEPALIVE emscripten_async_queue_on_thread_(pthread_t targetThread, EM_FUNC_SIGNATURE sig, void *func_ptr, void *satellite, ...) +{ + int numArguments = EM_FUNC_SIG_NUM_FUNC_ARGUMENTS(sig); + em_queued_call *q = em_queued_call_malloc(); + assert(q); + if (!q) return; + q->functionEnum = sig; + q->functionPtr = func_ptr; + q->satelliteData = satellite; + + EM_FUNC_SIGNATURE argumentsType = sig & EM_FUNC_SIG_ARGUMENTS_TYPE_MASK; + va_list args; + va_start(args, satellite); + for(int i = 0; i < numArguments; ++i) + { + switch((argumentsType & EM_FUNC_SIG_ARGUMENT_TYPE_SIZE_MASK)) + { + case EM_FUNC_SIG_PARAM_I: q->args[i].i = va_arg(args, int); break; + case EM_FUNC_SIG_PARAM_I64: q->args[i].i64 = va_arg(args, int64_t); break; + case EM_FUNC_SIG_PARAM_F: q->args[i].f = (float)va_arg(args, double); break; + case EM_FUNC_SIG_PARAM_D: q->args[i].d = va_arg(args, double); break; + } + argumentsType >>= EM_FUNC_SIG_ARGUMENT_TYPE_SIZE_SHIFT; + } + va_end(args); + + // 'async' runs are fire and forget, where the caller detaches itself from the call object after returning here, + // and it is the callee's responsibility to free up the memory after the call has been performed. + q->calleeDelete = 1; + emscripten_async_queue_call_on_thread(targetThread, q); +} + int llvm_memory_barrier() { emscripten_atomic_fence(); @@ -518,7 +731,7 @@ void *__emscripten_thread_main(void *param) return (void*)__call_main(args->argc, args->argv); } -static volatile main_args _main_arguments; +static main_args _main_arguments; // TODO: Create a separate library of this to be able to drop EMSCRIPTEN_KEEPALIVE from this definition. int EMSCRIPTEN_KEEPALIVE proxy_main(int argc, char **argv) diff --git a/tests/pthread/test_pthread_run_script.cpp b/tests/pthread/test_pthread_run_script.cpp new file mode 100644 index 0000000000000..83bde387b5b0b --- /dev/null +++ b/tests/pthread/test_pthread_run_script.cpp @@ -0,0 +1,72 @@ +#include +#include +#include +#include +#include +#include + +extern "C" +{ +void EMSCRIPTEN_KEEPALIVE FinishTest(int result) +{ + printf("Test finished, result: %d\n", result); +#ifdef REPORT_RESULT + REPORT_RESULT(result); +#endif +} +} + +void TestAsyncRunScript() +{ + // 5. Test emscripten_async_run_script() runs in a pthread. +#if __EMSCRIPTEN_PTHREADS__ + emscripten_async_run_script("Module['_FinishTest'](ENVIRONMENT_IS_PTHREAD && ENVIRONMENT_IS_WORKER);", 1); +#else + emscripten_async_run_script("Module['_FinishTest'](!ENVIRONMENT_IS_WORKER);", 1); +#endif +} + +void AsyncScriptLoaded() +{ + printf("async script load succeeded!\n"); + TestAsyncRunScript(); +} + +void AsyncScriptFailed() +{ + printf("async script load failed!\n"); + TestAsyncRunScript(); +} + +int main() { + + // 1. Test that emscripten_run_script() works in a pthread, and it gets executed in the web worker and not on the main thread. +#if __EMSCRIPTEN_PTHREADS__ + emscripten_run_script("Module['ranScript'] = ENVIRONMENT_IS_PTHREAD && ENVIRONMENT_IS_WORKER;"); +#else + emscripten_run_script("Module['ranScript'] = true;"); +#endif + + // 2. Test that emscripten_run_script_int() works in a pthread and it gets executed in the web worker and not on the main thread. +#if __EMSCRIPTEN_PTHREADS__ + int result = emscripten_run_script_int("Module['ranScript'] && ENVIRONMENT_IS_PTHREAD && ENVIRONMENT_IS_WORKER;"); +#else + int result = emscripten_run_script_int("Module['ranScript'];"); +#endif + printf("Module['ranScript']=%d\n", result); + assert(result); + + // 3. Test emscripten_run_script_string() runs in a pthread. +#if __EMSCRIPTEN_PTHREADS__ + char *data = emscripten_run_script_string("ENVIRONMENT_IS_PTHREAD && ENVIRONMENT_IS_WORKER ? 'in pthread' : 'not in pthread';"); + printf("%s\n", data); + assert(!strcmp(data, "in pthread")); +#else + char *data = emscripten_run_script_string("ENVIRONMENT_IS_WORKER ? 'in worker' : 'not in worker';"); + printf("%s\n", data); + assert(!strcmp(data, "not in worker")); +#endif + + // 4. Test emscripten_async_load_script() runs in a pthread. + emscripten_async_load_script("foo.js", AsyncScriptLoaded, AsyncScriptFailed); +} diff --git a/tests/test_browser.py b/tests/test_browser.py index 9e48b213c6345..7162099cafa4e 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -3948,6 +3948,11 @@ def test_pthread_locale(self): def test_emscripten_set_canvas_element_size(self): self.btest('emscripten_set_canvas_element_size.c', expected='1') + # Tests that emscripten_run_script() variants of functions work in pthreads. + def test_pthread_run_script(self): + for args in [[], ['-s', 'USE_PTHREADS=1', '-s', 'PROXY_TO_PTHREAD=1']]: + self.btest(path_from_root('tests', 'pthread', 'test_pthread_run_script.cpp'), expected='1', args=['-O3', '--separate-asm'] + args, timeout=30) + # Tests the absolute minimum pthread-enabled application. def test_hello_thread(self): self.btest(path_from_root('tests', 'pthread', 'hello_thread.c'), expected='1', args=['-s', 'USE_PTHREADS=1']) diff --git a/tests/test_other.py b/tests/test_other.py index 3b14995f2c987..3c1f586906008 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -8245,7 +8245,7 @@ def test(filename, expectations): 0, [], ['tempDoublePtr', 'waka'], 8, 0, 0, 0), # noqa; totally empty! # but we don't metadce with linkable code! other modules may want it (['-O3', '-s', 'MAIN_MODULE=1'], - 1488, ['invoke_v'], ['waka'], 469663, 149, 1443, None), # noqa; don't compare the # of functions in a main module, which changes a lot + 1489, ['invoke_v'], ['waka'], 469663, 149, 1443, None), # noqa; don't compare the # of functions in a main module, which changes a lot ]) # noqa print('test on a minimal pure computational thing') diff --git a/tools/gen_struct_info.py b/tools/gen_struct_info.py index bffdbfecc8afe..1f1274bdea036 100755 --- a/tools/gen_struct_info.py +++ b/tools/gen_struct_info.py @@ -393,7 +393,7 @@ def inspect_code(headers, cpp_opts, structs, defines): # Compile the program. show('Compiling generated code...') # -Oz optimizes enough to avoid warnings on code size/num locals - cmd = [shared.PYTHON, shared.EMCC] + cpp_opts + ['-o', js_file[1], src_file[1], '-s', 'BOOTSTRAPPING_STRUCT_INFO=1', '-s', 'WARN_ON_UNDEFINED_SYMBOLS=0', '-Oz', '--js-opts', '0', '--memory-init-file', '0', '-s', 'SINGLE_FILE=1', '-s', 'WASM=0'] + cmd = [shared.PYTHON, shared.EMCC] + cpp_opts + ['-o', js_file[1], src_file[1], '-s', 'BOOTSTRAPPING_STRUCT_INFO=1', '-s', 'WARN_ON_UNDEFINED_SYMBOLS=0', '-Oz', '--js-opts', '0', '--memory-init-file', '0', '-s', 'SINGLE_FILE=1', '-s', 'WASM=0', '-Wno-format'] if shared.Settings.WASM_OBJECT_FILES: cmd += ['-s', 'WASM_OBJECT_FILES=1']