diff --git a/emcc.py b/emcc.py index abf96f3d5617..f865fdf49dcf 100755 --- a/emcc.py +++ b/emcc.py @@ -1195,6 +1195,9 @@ def is_supported_link_flag(f): # These runtime methods are called from worker.js shared.Settings.EXPORTED_RUNTIME_METHODS += ['establishStackSpace', 'dynCall_ii'] + if shared.Settings.STACK_OVERFLOW_CHECK: + shared.Settings.EXPORTED_RUNTIME_METHODS += ['writeStackCookie', 'checkStackCookie', 'abortStackOverflow'] + if shared.Settings.MODULARIZE_INSTANCE: shared.Settings.MODULARIZE = 1 @@ -1361,7 +1364,10 @@ def is_supported_link_flag(f): newargs += ['-pthread'] # some pthreads code is in asm.js library functions, which are auto-exported; for the wasm backend, we must # manually export them - shared.Settings.EXPORTED_FUNCTIONS += ['_emscripten_get_global_libc', '___pthread_tsd_run_dtors', '__register_pthread_ptr', '_pthread_self', '___emscripten_pthread_data_constructor'] + shared.Settings.EXPORTED_FUNCTIONS += [ + '_emscripten_get_global_libc', '___pthread_tsd_run_dtors', + '__register_pthread_ptr', '_pthread_self', + '___emscripten_pthread_data_constructor', '_emscripten_futex_wake'] # set location of worker.js shared.Settings.PTHREAD_WORKER_FILE = unsuffixed(os.path.basename(target)) + '.worker.js' diff --git a/src/closure-externs.js b/src/closure-externs.js index eb7e25d31d77..291e02fb0834 100644 --- a/src/closure-externs.js +++ b/src/closure-externs.js @@ -48,6 +48,18 @@ Math.max = function() {}; Math.clz32 = function() {}; Math.trunc = function() {}; +/** + * Atomics + */ + +var Atomics = {}; +Atomics.compareExchange = function() {}; +Atomics.exchange = function() {}; +Atomics.wait = function() {}; +Atomics.notify = function() {}; +Atomics.load = function() {}; +Atomics.store = function() {}; + /** * SIMD.js support (not in upstream closure yet). */ diff --git a/src/library.js b/src/library.js index 71caf7daddc9..67daa5c9ba7b 100644 --- a/src/library.js +++ b/src/library.js @@ -25,8 +25,8 @@ LibraryManager.library = { // keep this low in memory, because we flatten arrays with them in them #if USE_PTHREADS - _impure_ptr: '; if (ENVIRONMENT_IS_PTHREAD) __impure_ptr = PthreadWorkerInit.__impure_ptr; else PthreadWorkerInit.__impure_ptr __impure_ptr = {{{ makeStaticAlloc(4) }}}', - __dso_handle: '; if (ENVIRONMENT_IS_PTHREAD) ___dso_handle = PthreadWorkerInit.___dso_handle; else PthreadWorkerInit.___dso_handle = ___dso_handle = {{{ makeStaticAlloc(4) }}}', + _impure_ptr: '; if (ENVIRONMENT_IS_PTHREAD) __impure_ptr = PthreadWorkerInit["__impure_ptr"]; else PthreadWorkerInit["__impure_ptr"] = {{{ makeStaticAlloc(4) }}}', + __dso_handle: '; if (ENVIRONMENT_IS_PTHREAD) ___dso_handle = PthreadWorkerInit["___dso_handle"]; else PthreadWorkerInit["___dso_handle"] = ___dso_handle = {{{ makeStaticAlloc(4) }}}', #else _impure_ptr: '{{{ makeStaticAlloc(1) }}}', __dso_handle: '{{{ makeStaticAlloc(1) }}}', @@ -1880,9 +1880,9 @@ LibraryManager.library = { // Statically allocated time struct. #if USE_PTHREADS - __tm_current: '; if (ENVIRONMENT_IS_PTHREAD) ___tm_current = PthreadWorkerInit.___tm_current; else PthreadWorkerInit.___tm_current = ___tm_current = {{{ makeStaticAlloc(C_STRUCTS.tm.__size__) }}}', - __tm_timezone: '; if (ENVIRONMENT_IS_PTHREAD) ___tm_timezone = PthreadWorkerInit.___tm_timezone; else PthreadWorkerInit.___tm_timezone = ___tm_timezone = {{{ makeStaticString("GMT") }}}', - __tm_formatted: '; if (ENVIRONMENT_IS_PTHREAD) ___tm_formatted = PthreadWorkerInit.___tm_formatted; else PthreadWorkerInit.___tm_formatted = ___tm_formatted = {{{ makeStaticAlloc(C_STRUCTS.tm.__size__) }}}', + __tm_current: '; if (ENVIRONMENT_IS_PTHREAD) ___tm_current = PthreadWorkerInit["___tm_current"]; else PthreadWorkerInit["___tm_current"] = ___tm_current = {{{ makeStaticAlloc(C_STRUCTS.tm.__size__) }}}', + __tm_timezone: '; if (ENVIRONMENT_IS_PTHREAD) ___tm_timezone = PthreadWorkerInit["___tm_timezone"]; else PthreadWorkerInit["___tm_timezone"] = ___tm_timezone = {{{ makeStaticString("GMT") }}}', + __tm_formatted: '; if (ENVIRONMENT_IS_PTHREAD) ___tm_formatted = PthreadWorkerInit["___tm_formatted"]; else PthreadWorkerInit["___tm_formatted"] = ___tm_formatted = {{{ makeStaticAlloc(C_STRUCTS.tm.__size__) }}}', #else __tm_current: '{{{ makeStaticAlloc(C_STRUCTS.tm.__size__) }}}', // Statically allocated copy of the string "GMT" for gmtime() to point to @@ -3239,8 +3239,8 @@ LibraryManager.library = { // ========================================================================== #if USE_PTHREADS - in6addr_any: '; if (ENVIRONMENT_IS_PTHREAD) _in6addr_any = PthreadWorkerInit._in6addr_any; else PthreadWorkerInit._in6addr_any = _in6addr_any = {{{ makeStaticAlloc(16) }}}', - in6addr_loopback: '; if (ENVIRONMENT_IS_PTHREAD) _in6addr_loopback = PthreadWorkerInit._in6addr_loopback; else PthreadWorkerInit._in6addr_loopback = _in6addr_loopback = {{{ makeStaticAlloc(16) }}}', + in6addr_any: '; if (ENVIRONMENT_IS_PTHREAD) _in6addr_any = PthreadWorkerInit["_in6addr_any"]; else PthreadWorkerInit["_in6addr_any"] = _in6addr_any = {{{ makeStaticAlloc(16) }}}', + in6addr_loopback: '; if (ENVIRONMENT_IS_PTHREAD) _in6addr_loopback = PthreadWorkerInit["_in6addr_loopback"]; else PthreadWorkerInit["_in6addr_loopback"] = _in6addr_loopback = {{{ makeStaticAlloc(16) }}}', #else in6addr_any: '{{{ makeStaticAlloc(16) }}}', diff --git a/src/library_fetch.js b/src/library_fetch.js index abdd288daf56..90c86088d8f6 100644 --- a/src/library_fetch.js +++ b/src/library_fetch.js @@ -8,7 +8,7 @@ var LibraryFetch = { #if USE_PTHREADS $Fetch__postset: 'if (!ENVIRONMENT_IS_PTHREAD) Fetch.staticInit();', - fetch_work_queue: '; if (ENVIRONMENT_IS_PTHREAD) _fetch_work_queue = PthreadWorkerInit._fetch_work_queue; else PthreadWorkerInit._fetch_work_queue = _fetch_work_queue = {{{ makeStaticAlloc(12) }}}', + fetch_work_queue: '; if (ENVIRONMENT_IS_PTHREAD) _fetch_work_queue = PthreadWorkerInit["_fetch_work_queue"]; else PthreadWorkerInit["_fetch_work_queue"] = _fetch_work_queue = {{{ makeStaticAlloc(12) }}}', #else $Fetch__postset: 'Fetch.staticInit();', fetch_work_queue: '{{{ makeStaticAlloc(12) }}}', diff --git a/src/library_pthread.js b/src/library_pthread.js index edb846a3a9ba..245fd06282bd 100644 --- a/src/library_pthread.js +++ b/src/library_pthread.js @@ -4,8 +4,11 @@ // found in the LICENSE file. var LibraryPThread = { - $PThread__postset: 'if (!ENVIRONMENT_IS_PTHREAD) PThread.initMainThreadBlock();', - $PThread__deps: ['$PROCINFO', '_register_pthread_ptr', 'emscripten_main_thread_process_queued_calls', '$ERRNO_CODES', 'emscripten_futex_wake'], + $PThread__postset: 'if (!ENVIRONMENT_IS_PTHREAD) PThread.initMainThreadBlock(); else PThread.initWorker();', + $PThread__deps: ['$PROCINFO', '_register_pthread_ptr', + 'emscripten_main_thread_process_queued_calls', + '$ERRNO_CODES', 'emscripten_futex_wake', '_kill_thread', + '_cancel_thread', '_cleanup_thread'], $PThread: { MAIN_THREAD_ID: 1, // A special constant that identifies the main JS thread ID. mainThreadInfo: { @@ -65,6 +68,16 @@ var LibraryPThread = { PThread.createProfilerBlock(PThread.mainThreadBlock); PThread.setThreadName(PThread.mainThreadBlock, "Browser main thread"); PThread.setThreadStatus(PThread.mainThreadBlock, {{{ cDefine('EM_THREAD_STATUS_RUNNING') }}}); +#endif + }, + initWorker: function() { +#if USE_CLOSURE_COMPILER + // worker.js is not compiled together with us, and must access certain + // things. + PThread['receiveObjectTransfer'] = PThread.receiveObjectTransfer; + PThread['setThreadStatus'] = PThread.setThreadStatus; + PThread['threadCancel'] = PThread.threadCancel; + PThread['threadExit'] = PThread.threadExit; #endif }, // Maps pthread_t to pthread info objects @@ -178,7 +191,7 @@ var LibraryPThread = { if (ENVIRONMENT_IS_PTHREAD) { // Note: in theory we would like to return any offscreen canvases back to the main thread, // but if we ever fetched a rendering context for them that would not be valid, so we don't try. - postMessage({ cmd: 'exit' }); + postMessage({ 'cmd': 'exit' }); } } }, @@ -190,7 +203,7 @@ var LibraryPThread = { _emscripten_futex_wake(threadInfoStruct + {{{ C_STRUCTS.pthread.threadStatus }}}, {{{ cDefine('INT_MAX') }}}); // wake all threads threadInfoStruct = selfThreadId = 0; // Not hosting a pthread anymore in this worker, reset the info structures to null. __register_pthread_ptr(0, 0, 0); // Unregister the thread block also inside the asm.js scope. - postMessage({ cmd: 'cancelDone' }); + postMessage({ 'cmd': 'cancelDone' }); }, terminateAllThreads: function() { @@ -305,32 +318,32 @@ var LibraryPThread = { // Ask the new worker to load up the Emscripten-compiled page. This is a heavy operation. worker.postMessage({ - cmd: 'load', + 'cmd': 'load', // If the application main .js file was loaded from a Blob, then it is not possible // to access the URL of the current script that could be passed to a Web Worker so that // it could load up the same file. In that case, developer must either deliver the Blob // object in Module['mainScriptUrlOrBlob'], or a URL to it, so that pthread Workers can // independently load up the same main application file. - urlOrBlob: Module['mainScriptUrlOrBlob'] || _scriptDir, + 'urlOrBlob': Module['mainScriptUrlOrBlob'] || _scriptDir, #if WASM - wasmMemory: wasmMemory, - wasmModule: wasmModule, + 'wasmMemory': wasmMemory, + 'wasmModule': wasmModule, #if LOAD_SOURCE_MAP - wasmSourceMap: wasmSourceMap, + 'wasmSourceMap': wasmSourceMap, #endif #if USE_OFFSET_CONVERTER - wasmOffsetConverter: wasmOffsetConverter, + 'wasmOffsetConverter': wasmOffsetConverter, #endif #else - buffer: HEAPU8.buffer, - asmJsUrlOrBlob: Module["asmJsUrlOrBlob"], + 'buffer': HEAPU8.buffer, + 'asmJsUrlOrBlob': Module["asmJsUrlOrBlob"], #endif #if !WASM_BACKEND - tempDoublePtr: tempDoublePtr, + 'tempDoublePtr': tempDoublePtr, #endif - DYNAMIC_BASE: DYNAMIC_BASE, - DYNAMICTOP_PTR: DYNAMICTOP_PTR, - PthreadWorkerInit: PthreadWorkerInit + 'DYNAMIC_BASE': DYNAMIC_BASE, + 'DYNAMICTOP_PTR': DYNAMICTOP_PTR, + 'PthreadWorkerInit': PthreadWorkerInit }); PThread.unusedWorkers.push(worker); } @@ -346,34 +359,35 @@ var LibraryPThread = { var worker = workers[i]; (function(worker) { worker.onmessage = function(e) { - var d = e.data; + var d = e['data']; + var cmd = d['cmd']; // 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; // If this message is intended to a recipient that is not the main thread, forward it to the target thread. - if (d.targetThread && d.targetThread != _pthread_self()) { + if (d['targetThread'] && d['targetThread'] != _pthread_self()) { var thread = PThread.pthreads[d.targetThread]; if (thread) { - thread.worker.postMessage(e.data, d.transferList); + thread.worker.postMessage(e.data, d['transferList']); } else { - console.error('Internal error! Worker sent a message "' + d.cmd + '" to target pthread ' + d.targetThread + ', but that thread no longer exists!'); + console.error('Internal error! Worker sent a message "' + cmd + '" to target pthread ' + d['targetThread'] + ', but that thread no longer exists!'); } PThread.currentProxiedOperationCallerThread = undefined; return; } - if (d.cmd === 'processQueuedMainThreadWork') { + if (cmd === 'processQueuedMainThreadWork') { // TODO: Must post message to main Emscripten thread in PROXY_TO_WORKER mode. _emscripten_main_thread_process_queued_calls(); - } else if (d.cmd === 'spawnThread') { + } else if (cmd === 'spawnThread') { __spawn_thread(e.data); - } else if (d.cmd === 'cleanupThread') { - __cleanup_thread(d.thread); - } else if (d.cmd === 'killThread') { - __kill_thread(d.thread); - } else if (d.cmd === 'cancelThread') { - __cancel_thread(d.thread); - } else if (d.cmd === 'loaded') { + } else if (cmd === 'cleanupThread') { + __cleanup_thread(d['thread']); + } else if (cmd === 'killThread') { + __kill_thread(d['thread']); + } else if (cmd === 'cancelThread') { + __cancel_thread(d['thread']); + } else if (cmd === 'loaded') { worker.loaded = true; // If this Worker is already pending to start running a thread, launch the thread now if (worker.runPthread) { @@ -384,34 +398,34 @@ var LibraryPThread = { if (numWorkersLoaded === numWorkers && onFinishedLoading) { onFinishedLoading(); } - } else if (d.cmd === 'print') { - out('Thread ' + d.threadId + ': ' + d.text); - } else if (d.cmd === 'printErr') { - err('Thread ' + d.threadId + ': ' + d.text); - } else if (d.cmd === 'alert') { - alert('Thread ' + d.threadId + ': ' + d.text); - } else if (d.cmd === 'exit') { + } else if (cmd === 'print') { + out('Thread ' + d['threadId'] + ': ' + d['text']); + } else if (cmd === 'printErr') { + err('Thread ' + d['threadId'] + ': ' + d['text']); + } else if (cmd === 'alert') { + alert('Thread ' + d['threadId'] + ': ' + d['text']); + } else if (cmd === 'exit') { var detached = worker.pthread && Atomics.load(HEAPU32, (worker.pthread.thread + {{{ C_STRUCTS.pthread.detached }}}) >> 2); if (detached) { PThread.returnWorkerToPool(worker); } - } else if (d.cmd === 'exitProcess') { + } else if (cmd === 'exitProcess') { // A pthread has requested to exit the whole application process (runtime). noExitRuntime = false; try { - exit(d.returnCode); + exit(d['returnCode']); } catch (e) { if (e instanceof ExitStatus) return; throw e; } - } else if (d.cmd === 'cancelDone') { + } else if (cmd === 'cancelDone') { PThread.returnWorkerToPool(worker); - } else if (d.cmd === 'objectTransfer') { + } else if (cmd === 'objectTransfer') { PThread.receiveObjectTransfer(e.data); } else if (e.data.target === 'setimmediate') { worker.postMessage(e.data); // Worker wants to postMessage() to itself to implement setImmediate() emulation. } else { - err("worker sent an unknown command " + d.cmd); + err("worker sent an unknown command " + cmd); } PThread.currentProxiedOperationCallerThread = undefined; }; @@ -483,7 +497,7 @@ var LibraryPThread = { if (ENVIRONMENT_IS_PTHREAD) throw 'Internal Error! _cancel_thread() can only ever be called from main application thread!'; if (!pthread_ptr) throw 'Internal Error! Null pthread_ptr in _cancel_thread!'; var pthread = PThread.pthreads[pthread_ptr]; - pthread.worker.postMessage({ cmd: 'cancel' }); + pthread.worker.postMessage({ 'cmd': 'cancel' }); }, _spawn_thread: function(threadParams) { @@ -537,17 +551,17 @@ var LibraryPThread = { worker.pthread = pthread; var msg = { - cmd: 'run', - start_routine: threadParams.startRoutine, - arg: threadParams.arg, - threadInfoStruct: threadParams.pthread_ptr, - selfThreadId: threadParams.pthread_ptr, // TODO: Remove this since thread ID is now the same as the thread address. - parentThreadId: threadParams.parent_pthread_ptr, - stackBase: threadParams.stackBase, - stackSize: threadParams.stackSize, + 'cmd': 'run', + 'start_routine': threadParams.startRoutine, + 'arg': threadParams.arg, + 'threadInfoStruct': threadParams.pthread_ptr, + 'selfThreadId': threadParams.pthread_ptr, // TODO: Remove this since thread ID is now the same as the thread address. + 'parentThreadId': threadParams.parent_pthread_ptr, + 'stackBase': threadParams.stackBase, + 'stackSize': threadParams.stackSize, #if OFFSCREENCANVAS_SUPPORT - moduleCanvasId: threadParams.moduleCanvasId, - offscreenCanvases: threadParams.offscreenCanvases, + 'moduleCanvasId': threadParams.moduleCanvasId, + 'offscreenCanvases': threadParams.offscreenCanvases, #endif }; worker.runPthread = function() { @@ -562,7 +576,7 @@ var LibraryPThread = { }, _num_logical_cores__deps: ['emscripten_force_num_logical_cores'], - _num_logical_cores: '; if (ENVIRONMENT_IS_PTHREAD) __num_logical_cores = PthreadWorkerInit.__num_logical_cores; else { PthreadWorkerInit.__num_logical_cores = __num_logical_cores = {{{ makeStaticAlloc(4) }}}; HEAPU32[__num_logical_cores>>2] = navigator["hardwareConcurrency"] || ' + {{{ PTHREAD_HINT_NUM_CORES }}} + '; }', + _num_logical_cores: '; if (ENVIRONMENT_IS_PTHREAD) __num_logical_cores = PthreadWorkerInit["__num_logical_cores"]; else { PthreadWorkerInit["__num_logical_cores"] = __num_logical_cores = {{{ makeStaticAlloc(4) }}}; HEAPU32[__num_logical_cores>>2] = navigator["hardwareConcurrency"] || ' + {{{ PTHREAD_HINT_NUM_CORES }}} + '; }', emscripten_has_threading_support: function() { return typeof SharedArrayBuffer !== 'undefined'; @@ -822,7 +836,7 @@ var LibraryPThread = { Atomics.store(HEAPU32, (thread + {{{ C_STRUCTS.pthread.detached }}} ) >> 2, 1); // Mark the thread as detached. if (!ENVIRONMENT_IS_PTHREAD) __cleanup_thread(thread); - else postMessage({ cmd: 'cleanupThread', thread: thread }); + else postMessage({ 'cmd': 'cleanupThread', 'thread': thread }); return 0; } // TODO HACK! Replace the _js variant with just _pthread_testcancel: @@ -854,7 +868,7 @@ var LibraryPThread = { } if (signal != 0) { if (!ENVIRONMENT_IS_PTHREAD) __kill_thread(thread); - else postMessage({ cmd: 'killThread', thread: thread}); + else postMessage({ 'cmd': 'killThread', 'thread': thread}); } return 0; }, @@ -876,7 +890,7 @@ var LibraryPThread = { } Atomics.compareExchange(HEAPU32, (thread + {{{ C_STRUCTS.pthread.threadStatus }}} ) >> 2, 0, 2); // Signal the thread that it needs to cancel itself. if (!ENVIRONMENT_IS_PTHREAD) __cancel_thread(thread); - else postMessage({ cmd: 'cancelThread', thread: thread}); + else postMessage({ 'cmd': 'cancelThread', 'thread': thread}); return 0; }, @@ -1068,7 +1082,7 @@ var LibraryPThread = { }, // Stores the memory address that the main thread is waiting on, if any. - _main_thread_futex_wait_address: '; if (ENVIRONMENT_IS_PTHREAD) __main_thread_futex_wait_address = PthreadWorkerInit.__main_thread_futex_wait_address; else PthreadWorkerInit.__main_thread_futex_wait_address = __main_thread_futex_wait_address = {{{ makeStaticAlloc(4) }}}', + _main_thread_futex_wait_address: '; if (ENVIRONMENT_IS_PTHREAD) __main_thread_futex_wait_address = PthreadWorkerInit["__main_thread_futex_wait_address"]; else PthreadWorkerInit["__main_thread_futex_wait_address"] = __main_thread_futex_wait_address = {{{ makeStaticAlloc(4) }}}', // Returns 0 on success, or one of the values -ETIMEDOUT, -EWOULDBLOCK or -EINVAL on error. emscripten_futex_wait__deps: ['_main_thread_futex_wait_address', 'emscripten_main_thread_process_queued_calls'], @@ -1159,7 +1173,7 @@ var LibraryPThread = { __call_main: function(argc, argv) { var returnCode = _main(argc, argv); - if (!noExitRuntime) postMessage({ cmd: 'exitProcess', returnCode: returnCode }); + if (!noExitRuntime) postMessage({ 'cmd': 'exitProcess', 'returnCode': returnCode }); return returnCode; }, diff --git a/src/modules.js b/src/modules.js index 979ee441d0c1..dd7b1fdb3ad7 100644 --- a/src/modules.js +++ b/src/modules.js @@ -446,17 +446,21 @@ function exportRuntime() { runtimeElements.push('warnOnce'); } - if (MODULARIZE) { - // In MODULARIZE=1 mode, the following functions need to be exported out to Module for worker.js to access. - if (STACK_OVERFLOW_CHECK) { - runtimeElements.push('writeStackCookie'); - runtimeElements.push('checkStackCookie'); - runtimeElements.push('abortStackOverflow'); - } - if (USE_PTHREADS) { - runtimeElements.push('PThread'); - runtimeElements.push('ExitStatus'); - } + if (STACK_OVERFLOW_CHECK) { + runtimeElements.push('writeStackCookie'); + runtimeElements.push('checkStackCookie'); + runtimeElements.push('abortStackOverflow'); + } + + if (USE_PTHREADS) { + // In pthreads mode, the following functions always need to be exported to + // Module for closure compiler, and also for MODULARIZE (so worker.js can + // access them). + ['PThread', 'ExitStatus', 'tempDoublePtr', 'wasmMemory', '_pthread_self', + 'ExitStatus', 'tempDoublePtr'].forEach(function(x) { + EXPORTED_RUNTIME_METHODS_SET[x] = 1; + runtimeElements.push(x); + }); } if (SUPPORT_BASE64_EMBEDDING) { diff --git a/src/parseTools.js b/src/parseTools.js index 0ded3f2a325f..e9284f0daa6f 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -1519,37 +1519,6 @@ function addAtExit(code) { } } -// Generates access to module exports variable in pthreads worker.js. Depending on whether main code is built with MODULARIZE -// or not, asm module exports need to either be accessed via a local exports object obtained from instantiating the module (in src/worker.js), or via -// the global Module exports object. -function makeAsmExportAccessInPthread(variable) { - if (MODULARIZE) { - return "Module['" + variable + "']"; // 'Module' is defined in worker.js local scope, so not EXPORT_NAME in this case. - } else { - return EXPORT_NAME + "['" + variable + "']"; - } -} - -// Generates access to a JS global scope variable in pthreads worker.js. In MODULARIZE mode the JS scope is not directly accessible, so all the relevant variables -// are exported via Module. In non-MODULARIZE mode, we can directly access the variables in global scope. -function makeAsmGlobalAccessInPthread(variable) { - if (MODULARIZE) { - return "Module['" + variable + "']"; // 'Module' is defined in worker.js local scope, so not EXPORT_NAME in this case. - } else { - return variable; - } -} - -// Generates access to both global scope variable and exported Module variable, e.g. "Module['foo'] = foo" or just plain "foo" depending on if we are MODULARIZEing. -// Used the be able to initialize both variables at the same time in scenarios where a variable exists in both global scope and in Module. -function makeAsmExportAndGlobalAssignTargetInPthread(variable) { - if (MODULARIZE) { - return "Module['" + variable + "'] = " + variable; // 'Module' is defined in worker.js local scope, so not EXPORT_NAME in this case. - } else { - return variable; - } -} - // Some things, like the dynamic and stack bases, will be computed later and // applied. Return them as {{{ STR }}} for that replacing later. diff --git a/src/preamble.js b/src/preamble.js index 4985d327762f..ec61ee660921 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -62,6 +62,15 @@ var wasmTable = new WebAssembly.Table({ #if USE_PTHREADS // For sending to workers. var wasmModule; +// Only workers actually use these field, but we refer to them from +// library_pthread (which exists on all threads) so this definition is useful +// to avoid accessing the global scope. +var threadInfoStruct = 0; +var selfThreadId = 0; +var __performance_now_clock_drift = 0; +#if WASM_BACKEND +var tempDoublePtr = 0; +#endif #endif // USE_PTHREADS //======================================== @@ -349,10 +358,6 @@ function updateGlobalBufferAndViews(buf) { Module['HEAPF64'] = HEAPF64 = new Float64Array(buf); } -#if USE_PTHREADS -if (!ENVIRONMENT_IS_PTHREAD) { // Pthreads have already initialized these variables in src/worker.js, where they were passed to the thread worker at startup time -#endif - var STATIC_BASE = {{{ GLOBAL_BASE }}}, STACK_BASE = {{{ getQuoted('STACK_BASE') }}}, STACKTOP = STACK_BASE, @@ -366,6 +371,28 @@ assert(DYNAMIC_BASE % 16 === 0, 'heap must start aligned'); #endif #if USE_PTHREADS +if (ENVIRONMENT_IS_PTHREAD) { + + // At the 'load' stage of Worker startup, we are just loading this script + // but not ready to run yet. At 'run' we receive proper values for the stack + // etc. and can launch a pthread. Set some fake values there meanwhile to + // catch bugs, then set the real values in applyStackValues later. +#if ASSERTIONS || SAFE_STACK + STACK_MAX = STACKTOP = STACK_MAX = 0x7FFFFFFF; +#endif + + Module['applyStackValues'] = function(stackBase, stackTop, stackMax) { + STACK_BASE = stackBase; + STACKTOP = stackTop; + STACK_MAX = stackMax; +#if SAFE_STACK + Module['___set_stack_limit'](STACK_MAX); +#endif + }; + + // TODO DYNAMIC_BASE = Module['DYNAMIC_BASE']; + // TODO DYNAMICTOP_PTR = Module['DYNAMICTOP_PTR']; + // TODO tempDoublePtr = Module['tempDoublePtr']; } #endif @@ -407,7 +434,7 @@ if (ENVIRONMENT_IS_WEB) { #if USE_PTHREADS if (typeof SharedArrayBuffer === 'undefined' || typeof Atomics === 'undefined') { - xhr = new XMLHttpRequest(); + var xhr = new XMLHttpRequest(); xhr.open('GET', 'http://localhost:8888/report_result?skipped:%20SharedArrayBuffer%20is%20not%20supported!'); xhr.send(); setTimeout(function() { window.close() }, 2000); diff --git a/src/runtime_init_memory.js b/src/runtime_init_memory.js index 93682b73d6d7..004a4a007c72 100644 --- a/src/runtime_init_memory.js +++ b/src/runtime_init_memory.js @@ -2,12 +2,8 @@ // memory is created in the wasm, not in JS.) #if USE_PTHREADS if (ENVIRONMENT_IS_PTHREAD) { -#if MODULARIZE && WASM - // In pthreads mode the wasmMemory and others are received in an onmessage, and that - // onmessage then loadScripts us, sending wasmMemory etc. on Module. Here we recapture - // it to a local so it can be used normally. wasmMemory = Module['wasmMemory']; -#endif + buffer = Module['buffer']; } else { #endif // USE_PTHREADS #if WASM diff --git a/src/shell.js b/src/shell.js index 9b410c490351..b8fd7a84c0c5 100644 --- a/src/shell.js +++ b/src/shell.js @@ -86,7 +86,9 @@ if (Module['ENVIRONMENT']) { } #endif +#if USE_PTHREADS #include "shell_pthreads.js" +#endif #if USE_PTHREADS && (!MODULARIZE || MODULARIZE_INSTANCE) // In MODULARIZE mode _scriptDir needs to be captured already at the very top of the page immediately when the page is parsed, so it is generated there diff --git a/src/shell_pthreads.js b/src/shell_pthreads.js index e4f9802aa9de..135a452eeac8 100644 --- a/src/shell_pthreads.js +++ b/src/shell_pthreads.js @@ -2,25 +2,20 @@ // 1) We could be the application main() thread running in the main JS UI thread. (ENVIRONMENT_IS_WORKER == false and ENVIRONMENT_IS_PTHREAD == false) // 2) We could be the application main() thread proxied to worker. (with Emscripten -s PROXY_TO_WORKER=1) (ENVIRONMENT_IS_WORKER == true, ENVIRONMENT_IS_PTHREAD == false) // 3) We could be an application pthread running in a worker. (ENVIRONMENT_IS_WORKER == true and ENVIRONMENT_IS_PTHREAD == true) -#if USE_PTHREADS // ENVIRONMENT_IS_PTHREAD=true will have been preset in worker.js. Make it false in the main runtime thread. -var ENVIRONMENT_IS_PTHREAD = Module.ENVIRONMENT_IS_PTHREAD || false; +var ENVIRONMENT_IS_PTHREAD = Module['ENVIRONMENT_IS_PTHREAD'] || false; +var PthreadWorkerInit; if (!ENVIRONMENT_IS_PTHREAD) { - var PthreadWorkerInit = {}; // Collects together variables that are needed at initialization time for the web workers that host pthreads. -} -#if MODULARIZE -else { + PthreadWorkerInit = {}; // Collects together variables that are needed at initialization time for the web workers that host pthreads. +} else { // Grab imports from the pthread to local scope. - var buffer = {{{EXPORT_NAME}}}.buffer; - var tempDoublePtr = {{{EXPORT_NAME}}}.tempDoublePtr; - var STATICTOP = {{{EXPORT_NAME}}}.STATICTOP; - var DYNAMIC_BASE = {{{EXPORT_NAME}}}.DYNAMIC_BASE; - var DYNAMICTOP_PTR = {{{EXPORT_NAME}}}.DYNAMICTOP_PTR; - var PthreadWorkerInit = {{{EXPORT_NAME}}}.PthreadWorkerInit; + buffer = {{{EXPORT_NAME}}}['buffer']; + tempDoublePtr = {{{EXPORT_NAME}}}['tempDoublePtr']; + DYNAMIC_BASE = {{{EXPORT_NAME}}}['DYNAMIC_BASE']; + DYNAMICTOP_PTR = {{{EXPORT_NAME}}}['DYNAMICTOP_PTR']; + PthreadWorkerInit = {{{EXPORT_NAME}}}['PthreadWorkerInit']; // Note that not all runtime fields are imported above. Values for STACK_BASE, STACKTOP and STACK_MAX are not yet known at worker.js load time. // These will be filled in at pthread startup time (the 'run' message for a pthread - pthread start establishes the stack frame) } -#endif -#endif diff --git a/src/worker.js b/src/worker.js index 9ca39b432d64..1a5839c246f5 100644 --- a/src/worker.js +++ b/src/worker.js @@ -15,20 +15,8 @@ var parentThreadId = 0; // The ID of the parent pthread that launched this threa var tempDoublePtr = 0; // A temporary memory area for global float and double marshalling operations. #endif -// Thread-local: Each thread has its own allocated stack space. -var STACK_BASE = 0; -var STACKTOP = 0; -var STACK_MAX = 0; - -// These are system-wide memory area parameters that are set at main runtime startup in main thread, and stay constant throughout the application. -var buffer; // All pthreads share the same Emscripten HEAP as SharedArrayBuffer with the main execution thread. -var DYNAMICTOP_PTR = 0; -var DYNAMIC_BASE = 0; - var noExitRuntime; -var PthreadWorkerInit = {}; - // performance.now() is specced to return a wallclock time in msecs since that Web Worker/main thread launched. However for pthreads this can cause // subtle problems in emscripten_get_now() as this essentially would measure time from pthread_create(), meaning that the clocks between each threads // would be wildly out of sync. Therefore sync all pthreads to the clock on the main browser thread, so that different threads see a somewhat @@ -90,9 +78,9 @@ function resetPrototype(constructor, attrs) { Module['instantiateWasm'] = function(info, receiveInstance) { // Instantiate from the module posted from the main thread. // We can just use sync instantiation in the worker. - var instance = new WebAssembly.Instance(wasmModule, info); + var instance = new WebAssembly.Instance(Module['wasmModule'], info); // We don't need the module anymore; new threads will be spawned from the main thread. - wasmModule = null; + Module['wasmModule'] = null; #if LOAD_SOURCE_MAP wasmSourceMap = resetPrototype(WasmSourceMap, wasmSourceMapData); #endif @@ -104,8 +92,6 @@ Module['instantiateWasm'] = function(info, receiveInstance) { }; #endif -var wasmModule; -var wasmMemory; #if LOAD_SOURCE_MAP var wasmSourceMapData; #endif @@ -118,37 +104,26 @@ this.onmessage = function(e) { if (e.data.cmd === 'load') { // Preload command that is called once per worker to parse and load the Emscripten code. #if !WASM_BACKEND // Initialize the thread-local field(s): - {{{ makeAsmGlobalAccessInPthread('tempDoublePtr') }}} = e.data.tempDoublePtr; + Module['tempDoublePtr'] = e.data.tempDoublePtr; #endif // Initialize the global "process"-wide fields: - {{{ makeAsmExportAndGlobalAssignTargetInPthread('DYNAMIC_BASE') }}} = e.data.DYNAMIC_BASE; - {{{ makeAsmExportAndGlobalAssignTargetInPthread('DYNAMICTOP_PTR') }}} = e.data.DYNAMICTOP_PTR; + Module['DYNAMIC_BASE'] = e.data.DYNAMIC_BASE; + Module['DYNAMICTOP_PTR'] = e.data.DYNAMICTOP_PTR; #if WASM - // The Wasm module will have import fields for STACKTOP and STACK_MAX. At 'load' stage of Worker startup, we are just - // spawning this Web Worker to act as a host for future created pthreads, i.e. we do not have a pthread to start up here yet. - // (A single Worker can also host multiple pthreads throughout its lifetime, shutting down a pthread will not shut down its hosting Worker, - // but the Worker is reused for later spawned pthreads). The 'run' stage below will actually start running a pthread. - // The stack space for a pthread is allocated and deallocated when a pthread is actually run, not yet at Worker 'load' stage. - // However, the WebAssembly module we are loading up here has import fields for STACKTOP and STACK_MAX, which it needs to get filled in - // immediately at Wasm Module instantiation time. The values of these will not get used until pthread is actually running some code, so - // we'll proceed to set up temporary invalid values for these fields for import purposes. Then whenever a pthread is launched at 'run' stage - // below, these values are rewritten to establish proper stack area for the particular pthread. - {{{ makeAsmExportAccessInPthread('STACK_MAX') }}} = {{{ makeAsmExportAccessInPthread('STACKTOP') }}} = 0x7FFFFFFF; - // Module and memory were sent from main thread - {{{ makeAsmExportAndGlobalAssignTargetInPthread('wasmModule') }}} = e.data.wasmModule; - {{{ makeAsmExportAndGlobalAssignTargetInPthread('wasmMemory') }}} = e.data.wasmMemory; + Module['wasmModule'] = e.data.wasmModule; + Module['wasmMemory'] = e.data.wasmMemory; #if LOAD_SOURCE_MAP wasmSourceMapData = e.data.wasmSourceMap; #endif #if USE_OFFSET_CONVERTER wasmOffsetData = e.data.wasmOffsetConverter; #endif - {{{ makeAsmExportAndGlobalAssignTargetInPthread('buffer') }}} = {{{ makeAsmGlobalAccessInPthread('wasmMemory') }}}.buffer; + Module['buffer'] = Module['wasmMemory'].buffer; #else - {{{ makeAsmExportAndGlobalAssignTargetInPthread('buffer') }}} = e.data.buffer; + Module['buffer'] = e.data.buffer; #if SEPARATE_ASM // load the separated-out asm.js @@ -164,7 +139,7 @@ this.onmessage = function(e) { #endif - {{{ makeAsmExportAndGlobalAssignTargetInPthread('PthreadWorkerInit') }}} = e.data.PthreadWorkerInit; + Module['PthreadWorkerInit'] = e.data.PthreadWorkerInit; Module['ENVIRONMENT_IS_PTHREAD'] = true; #if MODULARIZE && EXPORT_ES6 @@ -185,13 +160,11 @@ this.onmessage = function(e) { importScripts(objectUrl); URL.revokeObjectURL(objectUrl); } -#if MODULARIZE -#if !MODULARIZE_INSTANCE +#if MODULARIZE && !MODULARIZE_INSTANCE Module = {{{ EXPORT_NAME }}}(Module); #endif PThread = Module['PThread']; HEAPU32 = Module['HEAPU32']; -#endif #if !ASMFS if (typeof FS !== 'undefined' && typeof FS.createStandardStreams === 'function') FS.createStandardStreams(); @@ -203,7 +176,7 @@ this.onmessage = function(e) { } else if (e.data.cmd === 'run') { // This worker was idle, and now should start executing its pthread entry point. __performance_now_clock_drift = performance.now() - e.data.time; // Sync up to the clock of the main thread. threadInfoStruct = e.data.threadInfoStruct; - {{{ makeAsmGlobalAccessInPthread('__register_pthread_ptr') }}}(threadInfoStruct, /*isMainBrowserThread=*/0, /*isMainRuntimeThread=*/0); // Pass the thread address inside the asm.js scope to store it for fast access that avoids the need for a FFI out. + Module['__register_pthread_ptr'](threadInfoStruct, /*isMainBrowserThread=*/0, /*isMainRuntimeThread=*/0); // Pass the thread address inside the asm.js scope to store it for fast access that avoids the need for a FFI out. selfThreadId = e.data.selfThreadId; parentThreadId = e.data.parentThreadId; // Establish the stack frame for this thread in global scope @@ -215,14 +188,12 @@ this.onmessage = function(e) { var max = e.data.stackBase + e.data.stackSize; var top = e.data.stackBase; #endif - {{{ makeAsmExportAndGlobalAssignTargetInPthread('STACK_BASE') }}} = top; - {{{ makeAsmExportAndGlobalAssignTargetInPthread('STACKTOP') }}} = top; - {{{ makeAsmExportAndGlobalAssignTargetInPthread('STACK_MAX') }}} = max; + Module['applyStackValues'](top, top, max); #if ASSERTIONS assert(threadInfoStruct); assert(selfThreadId); assert(parentThreadId); - assert(STACK_BASE != 0); + assert(top != 0); #if WASM_BACKEND assert(max === e.data.stackBase); assert(top > max); @@ -241,15 +212,12 @@ this.onmessage = function(e) { #if WASM_BACKEND Module['_emscripten_tls_init'](); #endif -#if SAFE_STACK - Module['___set_stack_limit'](STACK_MAX); -#endif #if STACK_OVERFLOW_CHECK - {{{ makeAsmGlobalAccessInPthread('writeStackCookie') }}}(); + Module['writeStackCookie'](); #endif PThread.receiveObjectTransfer(e.data); - PThread.setThreadStatus({{{ makeAsmGlobalAccessInPthread('_pthread_self') }}}(), 1/*EM_THREAD_STATUS_RUNNING*/); + PThread.setThreadStatus(Module['_pthread_self'](), 1/*EM_THREAD_STATUS_RUNNING*/); try { // pthread entry points are always of signature 'void *ThreadMain(void *arg)' @@ -262,7 +230,7 @@ this.onmessage = function(e) { var result = Module['dynCall_ii'](e.data.start_routine, e.data.arg); #if STACK_OVERFLOW_CHECK - {{{ makeAsmGlobalAccessInPthread('checkStackCookie') }}}(); + Module['checkStackCookie'](); #endif } catch(e) { @@ -272,30 +240,30 @@ this.onmessage = function(e) { } else if (e === 'SimulateInfiniteLoop' || e === 'pthread_exit') { return; } else { - Atomics.store(HEAPU32, (threadInfoStruct + 4 /*C_STRUCTS.pthread.threadExitCode*/ ) >> 2, (e instanceof {{{ makeAsmGlobalAccessInPthread('ExitStatus') }}}) ? e.status : -2 /*A custom entry specific to Emscripten denoting that the thread crashed.*/); + Atomics.store(HEAPU32, (threadInfoStruct + 4 /*C_STRUCTS.pthread.threadExitCode*/ ) >> 2, (e instanceof Module['ExitStatus']) ? e.status : -2 /*A custom entry specific to Emscripten denoting that the thread crashed.*/); Atomics.store(HEAPU32, (threadInfoStruct + 0 /*C_STRUCTS.pthread.threadStatus*/ ) >> 2, 1); // Mark the thread as no longer running. #if ASSERTIONS - if (typeof({{{ makeAsmGlobalAccessInPthread('_emscripten_futex_wake') }}}) !== "function") { + if (typeof(Module['_emscripten_futex_wake']) !== "function") { err("Thread Initialisation failed."); throw e; } #endif - {{{ makeAsmGlobalAccessInPthread('_emscripten_futex_wake') }}}(threadInfoStruct + 0 /*C_STRUCTS.pthread.threadStatus*/, 0x7FFFFFFF/*INT_MAX*/); // Wake all threads waiting on this thread to finish. - if (!(e instanceof {{{ makeAsmGlobalAccessInPthread('ExitStatus') }}})) throw e; + Module['_emscripten_futex_wake'](threadInfoStruct + 0 /*C_STRUCTS.pthread.threadStatus*/, 0x7FFFFFFF/*INT_MAX*/); // Wake all threads waiting on this thread to finish. + if (!(e instanceof Module['ExitStatus'])) throw e; } } // The thread might have finished without calling pthread_exit(). If so, then perform the exit operation ourselves. // (This is a no-op if explicit pthread_exit() had been called prior.) if (!noExitRuntime) PThread.threadExit(result); } else if (e.data.cmd === 'cancel') { // Main thread is asking for a pthread_cancel() on this thread. - if (threadInfoStruct && PThread.thisThreadCancelState == 0/*PTHREAD_CANCEL_ENABLE*/) { + if (threadInfoStruct) { PThread.threadCancel(); } } else if (e.data.target === 'setimmediate') { // no-op } else if (e.data.cmd === 'processThreadQueue') { if (threadInfoStruct) { // If this thread is actually running? - {{{ makeAsmGlobalAccessInPthread('_emscripten_current_thread_process_queued_calls') }}}(); + Module['_emscripten_current_thread_process_queued_calls'](); } } else { err('worker.js received unknown command ' + e.data.cmd); diff --git a/tests/test_browser.py b/tests/test_browser.py index 615658e47f0d..414a504de285 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -3597,9 +3597,13 @@ def prep_no_SAB(self): ''')) # Test that the emscripten_ atomics api functions work. + @parameterized({ + 'normal': ([],), + 'closure': (['--closure', '1'],), + }) @requires_threads - def test_pthread_atomics(self): - self.btest(path_from_root('tests', 'pthread', 'test_pthread_atomics.cpp'), expected='0', args=['-s', 'TOTAL_MEMORY=64MB', '-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=8']) + def test_pthread_atomics(self, args=[]): + self.btest(path_from_root('tests', 'pthread', 'test_pthread_atomics.cpp'), expected='0', args=['-s', 'TOTAL_MEMORY=64MB', '-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=8', '-g1'] + args) # Test 64-bit atomics. @requires_threads