diff --git a/emcc.py b/emcc.py index a95ff9088448c..6eb91d0a495d3 100755 --- a/emcc.py +++ b/emcc.py @@ -1082,6 +1082,9 @@ def check(input_file): if shared.Settings.MODULARIZE_INSTANCE: shared.Settings.MODULARIZE = 1 + if shared.Settings.MODULARIZE: + assert not options.proxy_to_worker, '-s MODULARIZE=1 and -s MODULARIZE_INSTANCE=1 are not compatible with --proxy-to-worker (if you want to run in a worker with -s MODULARIZE=1, you likely want to do the worker side setup manually)' + if shared.Settings.EMULATE_FUNCTION_POINTER_CASTS: shared.Settings.ALIASING_FUNCTION_POINTERS = 0 @@ -1187,11 +1190,6 @@ def check(input_file): exit_with_error('USE_PTHREADS=2 is not longer supported') if shared.Settings.ALLOW_MEMORY_GROWTH: exit_with_error('Memory growth is not yet supported with pthreads') - if shared.Settings.MODULARIZE: - # currently worker.js uses the global namespace, so it's setting of - # ENVIRONMENT_IS_PTHREAD is not picked up, in addition to all the other - # modifications it performs. - exit_with_error('MODULARIZE is not yet supported with pthreads') # UTF8Decoder.decode doesn't work with a view of a SharedArrayBuffer shared.Settings.TEXTDECODER = 0 options.js_libraries.append(shared.path_from_root('src', 'library_pthread.js')) @@ -1225,6 +1223,21 @@ def check(input_file): ] if shared.Settings.USE_PTHREADS: + if shared.Settings.MODULARIZE: + # MODULARIZE+USE_PTHREADS mode requires extra exports out to Module so that worker.js + # can access them: + + # general threading variables: + shared.Settings.EXPORTED_RUNTIME_METHODS += ['PThread', 'ExitStatus'] + + # pthread stack setup: + shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$establishStackSpaceInJsModule'] + shared.Settings.EXPORTED_FUNCTIONS += ['establishStackSpaceInJsModule'] + + # stack check: + if shared.Settings.STACK_OVERFLOW_CHECK: + shared.Settings.EXPORTED_RUNTIME_METHODS += ['writeStackCookie', 'checkStackCookie'] + if shared.Settings.LINKABLE: exit_with_error('-s LINKABLE=1 is not supported with -s USE_PTHREADS>0!') if shared.Settings.SIDE_MODULE: @@ -1994,8 +2007,9 @@ def repl(m): if shared.Settings.USE_PTHREADS: target_dir = os.path.dirname(os.path.abspath(target)) - shutil.copyfile(shared.path_from_root('src', 'worker.js'), - os.path.join(target_dir, shared.Settings.PTHREAD_WORKER_FILE)) + worker_output = os.path.join(target_dir, shared.Settings.PTHREAD_WORKER_FILE) + with open(worker_output, 'w') as f: + f.write(shared.read_and_preprocess(shared.path_from_root('src', 'worker.js'), expand_macros=True)) # Generate the fetch-worker.js script for multithreaded emscripten_fetch() support if targeting pthreads. if shared.Settings.FETCH and shared.Settings.USE_PTHREADS: diff --git a/src/library_pthread.js b/src/library_pthread.js index ec4e83fc0396c..d824340f790cc 100644 --- a/src/library_pthread.js +++ b/src/library_pthread.js @@ -356,12 +356,13 @@ var LibraryPThread = { // 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'] || currentScriptUrl, + urlOrBlob: Module['mainScriptUrlOrBlob'] || _scriptDir, #if WASM wasmMemory: wasmMemory, wasmModule: wasmModule, #else buffer: HEAPU8.buffer, + asmJsUrlOrBlob: Module["asmJsUrlOrBlob"], #endif tempDoublePtr: tempDoublePtr, TOTAL_MEMORY: TOTAL_MEMORY, @@ -1181,6 +1182,13 @@ var LibraryPThread = { #endif return func.apply(null, callArgs); }, + +#if MODULARIZE + $establishStackSpaceInJsModule: function(stackBase, stackMax) { + STACK_BASE = STACKTOP = stackBase; + STACK_MAX = stackMax; + }, +#endif }; autoAddDeps(LibraryPThread, '$PThread'); diff --git a/src/modules.js b/src/modules.js index d8f8d79dd6711..b385f528b0fec 100644 --- a/src/modules.js +++ b/src/modules.js @@ -454,9 +454,24 @@ function exportRuntime() { 'getTempRet0', 'setTempRet0', ]; + if (!MINIMAL_RUNTIME) { runtimeElements.push('Pointer_stringify'); } + + 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 (SUPPORT_BASE64_EMBEDDING) { runtimeElements.push('intArrayFromBase64'); runtimeElements.push('tryParseAsDataURI'); diff --git a/src/parseTools.js b/src/parseTools.js index 1c22209f733b4..e074afbee73df 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -1477,6 +1477,37 @@ function makeStaticString(string) { return '(stringToUTF8("' + string + '", ' + ptr + ', ' + len + '), ' + ptr + ')'; } +// 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 1820d41c38c31..eb9129884f40a 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -358,14 +358,6 @@ assert(DYNAMIC_BASE % 16 === 0, 'heap must start aligned'); } #endif -#if USE_PTHREADS -if (ENVIRONMENT_IS_PTHREAD) { -#if SEPARATE_ASM != 0 - importScripts('{{{ SEPARATE_ASM }}}'); // load the separated-out asm.js -#endif -} -#endif - #if EMTERPRETIFY function abortStackOverflowEmterpreter() { abort("Emterpreter stack overflow! Decrease the recursion level or increase EMT_STACK_MAX in tools/emterpretify.py (current value " + EMT_STACK_MAX + ")."); @@ -1028,7 +1020,13 @@ function createWasm(env) { Module['asm'] = function(global, env, providedBuffer) { // memory was already allocated (so js could use the buffer) - env['memory'] = wasmMemory; + env['memory'] = wasmMemory +#if MODULARIZE && USE_PTHREADS + // Pthreads assign wasmMemory in their worker startup. In MODULARIZE mode, they cannot assign inside the + // Module scope, so lookup via Module as well. + || Module['wasmMemory'] +#endif + ; // import table env['table'] = wasmTable = new WebAssembly.Table({ 'initial': {{{ getQuoted('WASM_TABLE_SIZE') }}}, diff --git a/src/shell.js b/src/shell.js index fe07d20b9f980..4448dcfe31302 100644 --- a/src/shell.js +++ b/src/shell.js @@ -76,17 +76,13 @@ if (Module['ENVIRONMENT']) { } #endif -// Three configurations we can be running in: -// 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 -var ENVIRONMENT_IS_PTHREAD; -if (!ENVIRONMENT_IS_PTHREAD) ENVIRONMENT_IS_PTHREAD = false; // ENVIRONMENT_IS_PTHREAD=true will have been preset in worker.js. Make it false in the main runtime thread. -var PthreadWorkerInit; // Collects together variables that are needed at initialization time for the web workers that host pthreads. -if (!ENVIRONMENT_IS_PTHREAD) PthreadWorkerInit = {}; -var currentScriptUrl = (typeof document !== 'undefined' && document.currentScript) ? document.currentScript.src : undefined; -#endif // USE_PTHREADS +#include "shell_pthreads.js" + +#if USE_PTHREADS && !MODULARIZE +// 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 +// before the page load. In non-MODULARIZE modes generate it here. +var _scriptDir = (typeof document !== 'undefined' && document.currentScript) ? document.currentScript.src : undefined; +#endif // `/` should be present at the end if `scriptDirectory` is not empty var scriptDirectory = ''; diff --git a/src/shell_pthreads.js b/src/shell_pthreads.js new file mode 100644 index 0000000000000..e031a7dce9cec --- /dev/null +++ b/src/shell_pthreads.js @@ -0,0 +1,28 @@ +// Three configurations we can be running in: +// 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 + +if (typeof ENVIRONMENT_IS_PTHREAD === 'undefined') { + // ENVIRONMENT_IS_PTHREAD=true will have been preset in worker.js. Make it false in the main runtime thread. + // N.B. this line needs to appear without 'var' keyword to avoid 'var hoisting' from occurring. (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var) + ENVIRONMENT_IS_PTHREAD = false; + var PthreadWorkerInit = {}; // Collects together variables that are needed at initialization time for the web workers that host pthreads. +} +#if MODULARIZE +else { + // Grab imports from the pthread to local scope. + var buffer = {{{EXPORT_NAME}}}.buffer; + var tempDoublePtr = {{{EXPORT_NAME}}}.tempDoublePtr; + var TOTAL_MEMORY = {{{EXPORT_NAME}}}.TOTAL_MEMORY; + var STATICTOP = {{{EXPORT_NAME}}}.STATICTOP; + var DYNAMIC_BASE = {{{EXPORT_NAME}}}.DYNAMIC_BASE; + var DYNAMICTOP_PTR = {{{EXPORT_NAME}}}.DYNAMICTOP_PTR; + var 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 72601f7b3e529..e67afe3236ab2 100644 --- a/src/worker.js +++ b/src/worker.js @@ -25,6 +25,7 @@ var TOTAL_MEMORY = 0; var DYNAMIC_BASE = 0; var ENVIRONMENT_IS_PTHREAD = true; +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 @@ -36,13 +37,19 @@ var __performance_now_clock_drift = 0; // Therefore implement custom logging facility for threads running in a worker, which queue the messages to main thread to print. var Module = {}; +#if ASSERTIONS +function assert(condition, text) { + if (!condition) abort('Assertion failed: ' + text); +} +#endif + // When error objects propagate from Web Worker to main thread, they lose helpful call stack and thread ID information, so print out errors early here, // before that happens. this.addEventListener('error', function(e) { if (e.message.indexOf('SimulateInfiniteLoop') != -1) return e.preventDefault(); var errorSource = ' in ' + e.filename + ':' + e.lineno + ':' + e.colno; - console.error('Pthread ' + selfThreadId + ' uncaught exception' + (e.filename || e.lineno || e.colno ? errorSource : '') + ': ' + e.message + '. Error object:'); + console.error('Pthread ' + selfThreadId + ' uncaught exception' + (e.filename || e.lineno || e.colno ? errorSource : "") + ': ' + e.message + '. Error object:'); console.error(e.error); }); @@ -63,7 +70,7 @@ out = threadPrint; err = threadPrintErr; this.alert = threadAlert; -// #if WASM +#if WASM Module['instantiateWasm'] = function(info, receiveInstance) { // Instantiate from the module posted from the main thread. // We can just use sync instantiation in the worker. @@ -73,35 +80,57 @@ Module['instantiateWasm'] = function(info, receiveInstance) { receiveInstance(instance); // The second 'module' parameter is intentionally null here, we don't need to keep a ref to the Module object from here. return instance.exports; } -//#endif +#endif var wasmModule; +var wasmMemory; this.onmessage = function(e) { try { if (e.data.cmd === 'load') { // Preload command that is called once per worker to parse and load the Emscripten code. // Initialize the thread-local field(s): - tempDoublePtr = e.data.tempDoublePtr; + {{{ makeAsmGlobalAccessInPthread('tempDoublePtr') }}} = e.data.tempDoublePtr; // Initialize the global "process"-wide fields: - Module['TOTAL_MEMORY'] = TOTAL_MEMORY = e.data.TOTAL_MEMORY; - DYNAMIC_BASE = e.data.DYNAMIC_BASE; - DYNAMICTOP_PTR = e.data.DYNAMICTOP_PTR; - - -//#if WASM - if (e.data.wasmModule) { - // Module and memory were sent from main thread - wasmModule = e.data.wasmModule; - wasmMemory = e.data.wasmMemory; - buffer = wasmMemory.buffer; + {{{ makeAsmExportAndGlobalAssignTargetInPthread('TOTAL_MEMORY') }}} = e.data.TOTAL_MEMORY; + {{{ makeAsmExportAndGlobalAssignTargetInPthread('DYNAMIC_BASE') }}} = e.data.DYNAMIC_BASE; + {{{ makeAsmExportAndGlobalAssignTargetInPthread('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; + {{{ makeAsmExportAndGlobalAssignTargetInPthread('buffer') }}} = {{{ makeAsmGlobalAccessInPthread('wasmMemory') }}}.buffer; +#else + {{{ makeAsmExportAndGlobalAssignTargetInPthread('buffer') }}} = e.data.buffer; + +#if SEPARATE_ASM + // load the separated-out asm.js + e.data.asmJsUrlOrBlob = e.data.asmJsUrlOrBlob || '{{{ SEPARATE_ASM }}}'; + if (typeof e.data.asmJsUrlOrBlob === 'string') { + importScripts(e.data.asmJsUrlOrBlob); } else { -//#else - buffer = e.data.buffer; + var objectUrl = URL.createObjectURL(e.data.asmJsUrlOrBlob); + importScripts(objectUrl); + URL.revokeObjectURL(objectUrl); } -//#endif +#endif + +#endif + + {{{ makeAsmExportAndGlobalAssignTargetInPthread('PthreadWorkerInit') }}} = e.data.PthreadWorkerInit; - PthreadWorkerInit = e.data.PthreadWorkerInit; if (typeof e.data.urlOrBlob === 'string') { importScripts(e.data.urlOrBlob); } else { @@ -109,35 +138,47 @@ this.onmessage = function(e) { importScripts(objectUrl); URL.revokeObjectURL(objectUrl); } -//#if !ASMFS + +#if MODULARIZE + Module = {{{ EXPORT_NAME }}}(Module); + PThread = Module['PThread']; + HEAPU32 = Module['HEAPU32']; +#endif + +#if !ASMFS if (typeof FS !== 'undefined' && typeof FS.createStandardStreams === 'function') FS.createStandardStreams(); -//#endif +#endif postMessage({ cmd: 'loaded' }); } else if (e.data.cmd === 'objectTransfer') { PThread.receiveObjectTransfer(e.data); } 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; - __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. - assert(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. selfThreadId = e.data.selfThreadId; parentThreadId = e.data.parentThreadId; + // Establish the stack frame for this thread in global scope + {{{ makeAsmExportAndGlobalAssignTargetInPthread('STACK_BASE') }}} = {{{ makeAsmExportAndGlobalAssignTargetInPthread('STACKTOP') }}} = e.data.stackBase; + {{{ makeAsmExportAndGlobalAssignTargetInPthread('STACK_MAX') }}} = STACK_BASE + e.data.stackSize; +#if ASSERTIONS + assert(threadInfoStruct); assert(selfThreadId); assert(parentThreadId); - // TODO: Emscripten runtime has these variables twice(!), once outside the asm.js module, and a second time inside the asm.js module. - // Review why that is? Can those get out of sync? - STACK_BASE = STACKTOP = e.data.stackBase; - STACK_MAX = STACK_BASE + e.data.stackSize; assert(STACK_BASE != 0); assert(STACK_MAX > STACK_BASE); +#endif + // Call inside asm.js/wasm module to set up the stack frame for this pthread in asm.js/wasm module scope Module['establishStackSpace'](e.data.stackBase, e.data.stackBase + e.data.stackSize); - var result = 0; -//#if STACK_OVERFLOW_CHECK - if (typeof writeStackCookie === 'function') writeStackCookie(); -//#endif +#if MODULARIZE + // Also call inside JS module to set up the stack frame for this pthread in JS module scope + Module['establishStackSpaceInJsModule'](e.data.stackBase, e.data.stackBase + e.data.stackSize); +#endif +#if STACK_OVERFLOW_CHECK + {{{ makeAsmGlobalAccessInPthread('writeStackCookie') }}}(); +#endif PThread.receiveObjectTransfer(e.data); - PThread.setThreadStatus(_pthread_self(), 1/*EM_THREAD_STATUS_RUNNING*/); + PThread.setThreadStatus({{{ makeAsmGlobalAccessInPthread('_pthread_self') }}}(), 1/*EM_THREAD_STATUS_RUNNING*/); try { // pthread entry points are always of signature 'void *ThreadMain(void *arg)' @@ -147,11 +188,11 @@ this.onmessage = function(e) { // enable that to work. If you find the following line to crash, either change the signature // to "proper" void *ThreadMain(void *arg) form, or try linking with the Emscripten linker // flag -s EMULATE_FUNCTION_POINTER_CASTS=1 to add in emulation for this x86 ABI extension. - result = Module['dynCall_ii'](e.data.start_routine, e.data.arg); + var result = Module['dynCall_ii'](e.data.start_routine, e.data.arg); -//#if STACK_OVERFLOW_CHECK - if (typeof checkStackCookie === 'function') checkStackCookie(); -//#endif +#if STACK_OVERFLOW_CHECK + {{{ makeAsmGlobalAccessInPthread('checkStackCookie') }}}(); +#endif } catch(e) { if (e === 'Canceled!') { @@ -160,10 +201,10 @@ this.onmessage = function(e) { } else if (e === 'SimulateInfiniteLoop') { return; } else { - Atomics.store(HEAPU32, (threadInfoStruct + 4 /*{{{ C_STRUCTS.pthread.threadExitCode }}}*/ ) >> 2, (e instanceof 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. - _emscripten_futex_wake(threadInfoStruct + 0 /*{{{ C_STRUCTS.pthread.threadStatus }}}*/, 0x7FFFFFFF/*INT_MAX*/); // Wake all threads waiting on this thread to finish. - if (!(e instanceof ExitStatus)) throw e; + 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 + 0 /*C_STRUCTS.pthread.threadStatus*/ ) >> 2, 1); // Mark the thread as no longer running. + {{{ 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; } } // The thread might have finished without calling pthread_exit(). If so, then perform the exit operation ourselves. @@ -177,7 +218,7 @@ this.onmessage = function(e) { // no-op } else if (e.data.cmd === 'processThreadQueue') { if (threadInfoStruct) { // If this thread is actually running? - _emscripten_current_thread_process_queued_calls(); + {{{ makeAsmGlobalAccessInPthread('_emscripten_current_thread_process_queued_calls') }}}(); } } else { err('worker.js received unknown command ' + e.data.cmd); diff --git a/tests/shell_that_launches_modularize.html b/tests/shell_that_launches_modularize.html new file mode 100644 index 0000000000000..41affbd86b55a --- /dev/null +++ b/tests/shell_that_launches_modularize.html @@ -0,0 +1,154 @@ + + + + + + Emscripten-Generated Code + + + +
+
emscripten
+
Downloading...
+
+ +
+
+ +
+
+
+ Resize canvas + Lock/hide mouse pointer +     + +
+ +
+ +
+ + {{{ SCRIPT }}} + + + diff --git a/tests/test_browser.py b/tests/test_browser.py index df28b6532929d..705491e974424 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -3569,7 +3569,8 @@ def test_pthread_proxy_to_pthread(self): # Test that a pthread can spawn another pthread of its own. @requires_threads def test_pthread_create_pthread(self): - self.btest(path_from_root('tests', 'pthread', 'test_pthread_create_pthread.cpp'), expected='1', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=2']) + for modularize in [[], ['-s', 'MODULARIZE=1', '-s', 'EXPORT_NAME=MyModule', '--shell-file', path_from_root('tests', 'shell_that_launches_modularize.html')]]: + self.btest(path_from_root('tests', 'pthread', 'test_pthread_create_pthread.cpp'), expected='1', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=2'] + modularize) # Test another case of pthreads spawning pthreads, but this time the callers immediately join on the threads they created. @requires_threads @@ -3680,7 +3681,8 @@ def test_pthread_supported(self): @no_wasm_backend('asm.js') @requires_threads def test_pthread_separate_asm_pthreads(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', '--separate-asm', '--profiling']) + for modularize in [[], ['-s', 'MODULARIZE=1', '-s', 'EXPORT_NAME=MyModule', '--shell-file', path_from_root('tests', 'shell_that_launches_modularize.html')]]: + 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', '--separate-asm', '--profiling'] + modularize) # Test the operation of Module.pthreadMainPrefixURL variable @requires_threads @@ -3772,7 +3774,7 @@ def test_pthread_call_async_on_main_thread(self): @requires_threads def test_pthread_global_data_initialization(self): for mem_init_mode in [[], ['--memory-init-file', '0'], ['--memory-init-file', '1'], ['-s', 'MEM_INIT_METHOD=2', '-s', 'WASM=0']]: - for args in [[], ['-O3']]: + for args in [['-s', 'MODULARIZE=1', '-s', 'EXPORT_NAME=MyModule', '--shell-file', path_from_root('tests', 'shell_that_launches_modularize.html')], ['-O3']]: self.btest(path_from_root('tests', 'pthread', 'test_pthread_global_data_initialization.c'), expected='20', args=args + mem_init_mode + ['-s', 'USE_PTHREADS=1', '-s', 'PROXY_TO_PTHREAD=1', '-s', 'PTHREAD_POOL_SIZE=1']) @requires_threads @@ -4266,8 +4268,10 @@ def test_emscripten_animate_canvas_element_size(self): # Tests the absolute minimum pthread-enabled application. @requires_threads - def test_hello_thread(self): - self.btest(path_from_root('tests', 'pthread', 'hello_thread.c'), expected='1', args=['-s', 'USE_PTHREADS=1']) + def test_pthread_hello_thread(self): + for opts in [[], ['-O3']]: + for modularize in [[], ['-s', 'MODULARIZE=1', '-s', 'EXPORT_NAME=MyModule', '--shell-file', path_from_root('tests', 'shell_that_launches_modularize.html')]]: + self.btest(path_from_root('tests', 'pthread', 'hello_thread.c'), expected='1', args=['-s', 'USE_PTHREADS=1'] + modularize + opts) # Tests that it is possible to load the main .js file of the application manually via a Blob URL, and still use pthreads. @requires_threads diff --git a/tools/preprocessor.js b/tools/preprocessor.js index d1e279b201610..1a6c8de0784fb 100644 --- a/tools/preprocessor.js +++ b/tools/preprocessor.js @@ -132,7 +132,7 @@ set = function() {} var settings_file = arguments_[0]; var shell_file = arguments_[1]; -var process_macros = arguments_.length >= 3 && arguments_[2]; +var process_macros = arguments_.indexOf('--expandMacros') >= 0; load(settings_file); load('parseTools.js'); diff --git a/tools/shared.py b/tools/shared.py index 4bfbee03d1243..56b723411b939 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -3204,7 +3204,7 @@ def clang_preprocess(filename): return run_process([CLANG_CC, '-DFETCH_DEBUG=1', '-E', '-P', '-C', '-x', 'c', filename], check=True, stdout=subprocess.PIPE).stdout -def read_and_preprocess(filename): +def read_and_preprocess(filename, expand_macros=False): temp_dir = get_emscripten_temp_dir() # Create a settings file with the current settings to pass to the JS preprocessor # Note: Settings.serialize returns an array of -s options i.e. ['-s', '', '-s', '', ...] @@ -3222,6 +3222,8 @@ def read_and_preprocess(filename): path = None stdout = os.path.join(temp_dir, 'stdout') args = [settings_file, file] + if expand_macros: + args += ['--expandMacros'] run_js(path_from_root('tools/preprocessor.js'), NODE_JS, args, True, stdout=open(stdout, 'w'), cwd=path) out = open(stdout, 'r').read()