diff --git a/emcc.py b/emcc.py index d83fbf9f49a9a..d37c0193bfa51 100755 --- a/emcc.py +++ b/emcc.py @@ -1294,6 +1294,7 @@ def is_supported_link_flag(f): if shared.Settings.STACK_OVERFLOW_CHECK: if shared.Settings.MINIMAL_RUNTIME: shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$abortStackOverflow'] + shared.Settings.EXPORTED_RUNTIME_METHODS += ['writeStackCookie', 'checkStackCookie'] else: shared.Settings.EXPORTED_RUNTIME_METHODS += ['writeStackCookie', 'checkStackCookie', 'abortStackOverflow'] @@ -1528,16 +1529,17 @@ def is_supported_link_flag(f): ] if shared.Settings.USE_PTHREADS: - shared.Settings.EXPORTED_RUNTIME_METHODS += ['establishStackSpace'] - # memalign is used to ensure allocated thread stacks are aligned. - shared.Settings.EXPORTED_FUNCTIONS += ['_memalign'] + shared.Settings.EXPORTED_FUNCTIONS += ['_memalign', '_malloc'] # dynCall_ii is used to call pthread entry points in worker.js (as # metadce does not consider worker.js, which is external, we must # consider it a user export, i.e., one which can never be removed). shared.Building.user_requested_exports += ['dynCall_ii'] + if shared.Settings.MINIMAL_RUNTIME: + shared.Building.user_requested_exports += ['exit'] + if shared.Settings.PROXY_TO_PTHREAD: shared.Settings.EXPORTED_FUNCTIONS += ['_proxy_main'] @@ -1546,15 +1548,22 @@ def include_and_export(name): shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$' + name] shared.Settings.EXPORTED_FUNCTIONS += [name] - include_and_export('establishStackSpaceInJsModule') - include_and_export('getNoExitRuntime') + include_and_export('establishStackSpace') + if not shared.Settings.MINIMAL_RUNTIME: + # noExitRuntime does not apply to MINIMAL_RUNTIME. + include_and_export('getNoExitRuntime') 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'] + shared.Settings.EXPORTED_RUNTIME_METHODS += ['PThread'] + + # To keep code size to minimum, MINIMAL_RUNTIME does not utilize the global ExitStatus + # object, only regular runtime has it. + if not shared.Settings.MINIMAL_RUNTIME: + shared.Settings.EXPORTED_RUNTIME_METHODS += ['ExitStatus'] # stack check: if shared.Settings.STACK_OVERFLOW_CHECK: @@ -1921,9 +1930,6 @@ def check_human_readable_list(items): if shared.Settings.EMTERPRETIFY: exit_with_error('-s EMTERPRETIFY=1 is not supported with -s MINIMAL_RUNTIME=1') - if shared.Settings.USE_PTHREADS: - exit_with_error('-s USE_PTHREADS=1 is not yet supported with -s MINIMAL_RUNTIME=1') - if shared.Settings.PRECISE_F32 == 2: exit_with_error('-s PRECISE_F32=2 is not supported with -s MINIMAL_RUNTIME=1') @@ -3327,8 +3333,9 @@ def modularize(): logger.debug('Modularizing, assigning to var ' + shared.Settings.EXPORT_NAME) src = open(final).read() - # TODO: exports object generation for MINIMAL_RUNTIME - exports_object = '{}' if shared.Settings.MINIMAL_RUNTIME else shared.Settings.EXPORT_NAME + # TODO: exports object generation for MINIMAL_RUNTIME. As a temp measure, multithreaded MINIMAL_RUNTIME builds export like regular + # runtime does, so that worker.js can see the JS module contents. + exports_object = '{}' if shared.Settings.MINIMAL_RUNTIME and not shared.Settings.USE_PTHREADS else shared.Settings.EXPORT_NAME src = ''' function(%(EXPORT_NAME)s) { @@ -3424,8 +3431,7 @@ def module_export_name_substitution(): src = src.replace(shared.JS.module_export_name_substitution_pattern, replacement) # For Node.js and other shell environments, create an unminified Module object so that # loading external .asm.js file that assigns to Module['asm'] works even when Closure is used. - if shared.Settings.MINIMAL_RUNTIME and (shared.Settings.target_environment_may_be('node') or - shared.Settings.target_environment_may_be('shell')): + if shared.Settings.MINIMAL_RUNTIME and not shared.Settings.MODULARIZE_INSTANCE and (shared.Settings.target_environment_may_be('node') or shared.Settings.target_environment_may_be('shell')): src = 'if(typeof Module==="undefined"){var Module={};}' + src f.write(src) save_intermediate('module_export_name_substitution') diff --git a/emscripten.py b/emscripten.py index 53b14d6297368..695b08b7f71d2 100644 --- a/emscripten.py +++ b/emscripten.py @@ -943,8 +943,8 @@ def get_exported_implemented_functions(all_exported_functions, all_implemented, funcs.append('_emscripten_replace_memory') if not shared.Settings.SIDE_MODULE and not shared.Settings.MINIMAL_RUNTIME: funcs += ['stackAlloc', 'stackSave', 'stackRestore'] - if shared.Settings.USE_PTHREADS: - funcs += ['establishStackSpace'] + if shared.Settings.USE_PTHREADS: + funcs += ['asmJsEstablishStackFrame'] if shared.Settings.EMTERPRETIFY: funcs += ['emterpret'] @@ -1675,8 +1675,6 @@ def create_asm_runtime_funcs(): funcs = [] if not (shared.Settings.WASM and shared.Settings.SIDE_MODULE) and not shared.Settings.MINIMAL_RUNTIME: funcs += ['stackAlloc', 'stackSave', 'stackRestore'] - if shared.Settings.USE_PTHREADS: - funcs += ['establishStackSpace'] return funcs @@ -1887,9 +1885,13 @@ def create_runtime_funcs_asmjs(exports, metadata): } ''' % stack_check] + if shared.Settings.MINIMAL_RUNTIME: + # MINIMAL_RUNTIME moves stack functions to library. + funcs = [] + if shared.Settings.USE_PTHREADS: funcs.append(''' -function establishStackSpace(stackBase, stackMax) { +function asmJsEstablishStackFrame(stackBase, stackMax) { stackBase = stackBase|0; stackMax = stackMax|0; STACKTOP = stackBase; @@ -1899,10 +1901,6 @@ def create_runtime_funcs_asmjs(exports, metadata): } ''') - if shared.Settings.MINIMAL_RUNTIME: - # MINIMAL_RUNTIME moves stack functions to library. - funcs = [] - if shared.Settings.EMTERPRETIFY: funcs.append(''' function emterpret(pc) { // this will be replaced when the emterpreter code is generated; adding it here allows validation until then @@ -2645,7 +2643,13 @@ def create_receiving_wasm(exports, initializers): # In wasm2js exports can be directly processed at top level, i.e. # var asm = Module["asm"](asmGlobalArg, asmLibraryArg, buffer); # var _main = asm["_main"]; - receiving += ['var ' + asmjs_mangle(s) + ' = asm["' + asmjs_mangle(s) + '"];' for s in exports_that_are_not_initializers] + if shared.Settings.USE_PTHREADS and shared.Settings.MODULARIZE: + # TODO: As a temp solution, multithreaded MODULARIZEd MINIMAL_RUNTIME builds export all symbols like regular runtime does. + # Fix this by migrating worker.js code to reside inside the Module so it is in the same scope as the rest of the JS code, or + # by defining an export syntax to MINIMAL_RUNTIME that multithreaded MODULARIZEd builds can export on. + receiving += [asmjs_mangle(s) + ' = Module["' + asmjs_mangle(s) + '"] = asm["' + s + '"];' for s in exports_that_are_not_initializers] + else: + receiving += ['var ' + asmjs_mangle(s) + ' = asm["' + asmjs_mangle(s) + '"];' for s in exports_that_are_not_initializers] else: receiving += ['var ' + asmjs_mangle(s) + ' = Module["' + asmjs_mangle(s) + '"] = asm["' + s + '"];' for s in exports] else: diff --git a/src/closure-externs.js b/src/closure-externs.js index 56173c900ec9f..b41ed0ba35fc8 100644 --- a/src/closure-externs.js +++ b/src/closure-externs.js @@ -975,3 +975,15 @@ var outerWidth; var outerHeight; var event; var devicePixelRatio; + +// TODO: Use Closure's multifile support and/or migrate worker.js onmessage handler to inside the MODULARIZEd block +// to be able to remove all the variables below: + +// Variables that are present in both output runtime .js file/JS lib files, and worker.js, so cannot be minified because +// the names need to match: +/** @suppress {duplicate} */ +var threadInfoStruct; +/** @suppress {duplicate} */ +var selfThreadId; +/** @suppress {duplicate} */ +var noExitRuntime; diff --git a/src/library.js b/src/library.js index 8c89ff1170873..4887615dc0b62 100644 --- a/src/library.js +++ b/src/library.js @@ -275,6 +275,12 @@ LibraryManager.library = { _Exit__sig: 'vi', _Exit: 'exit', +#if MINIMAL_RUNTIME + $exit: function(status) { + throw 'exit(' + status + ')'; + }, +#endif + fork__deps: ['__setErrNo'], fork: function() { // pid_t fork(void); @@ -1353,17 +1359,6 @@ LibraryManager.library = { }, #endif -#if USE_PTHREADS - $establishStackSpace__asm: true, - $establishStackSpace__sig: 'vii', - $establishStackSpace: function(stackBase, stackMax) { - stackBase = stackBase|0; - stackMax = stackMax|0; - STACKTOP = stackBase; - STACK_MAX = stackMax; - }, -#endif - #if WASM_BACKEND == 0 $setThrew__asm: true, $setThrew__sig: 'vii', diff --git a/src/library_pthread.js b/src/library_pthread.js index 74e3a655bc779..4dfc203095175 100644 --- a/src/library_pthread.js +++ b/src/library_pthread.js @@ -29,7 +29,9 @@ var LibraryPThread = { _emscripten_register_main_browser_thread_id(PThread.mainThreadBlock); }, initMainThreadBlock: function() { - if (ENVIRONMENT_IS_PTHREAD) return; +#if ASSERTIONS + assert(!ENVIRONMENT_IS_PTHREAD); +#endif #if PTHREAD_POOL_SIZE var pthreadPoolSize = {{{ PTHREAD_POOL_SIZE }}}; @@ -374,8 +376,8 @@ var LibraryPThread = { #endif #if ASSERTIONS && WASM - assert(wasmMemory, 'WebAssembly memory should have been loaded by now!'); - assert(wasmModule, 'WebAssembly Module should have been loaded by now!'); + assert(wasmMemory instanceof WebAssembly.Memory, 'WebAssembly memory should have been loaded by now!'); + assert(wasmModule instanceof WebAssembly.Module, 'WebAssembly Module should have been loaded by now!'); #endif // Ask the new worker to load up the Emscripten-compiled page. This is a heavy operation. @@ -400,17 +402,23 @@ var LibraryPThread = { 'buffer': HEAPU8.buffer, 'asmJsUrlOrBlob': Module["asmJsUrlOrBlob"], #endif +#if !MINIMAL_RUNTIME 'DYNAMIC_BASE': DYNAMIC_BASE, +#endif 'DYNAMICTOP_PTR': DYNAMICTOP_PTR }); }, // Creates a new web Worker and places it in the unused worker pool to wait for its use. allocateUnusedWorker: function() { +#if MINIMAL_RUNTIME + var pthreadMainJs = Module['worker']; +#else // Allow HTML module to configure the location where the 'worker.js' file will be loaded from, // via Module.locateFile() function. If not specified, then the default URL 'worker.js' relative // to the main html file is loaded. var pthreadMainJs = locateFile('{{{ PTHREAD_WORKER_FILE }}}'); +#endif #if PTHREADS_DEBUG out('Allocating a new web worker from ' + pthreadMainJs); #endif @@ -550,7 +558,7 @@ var LibraryPThread = { return navigator['hardwareConcurrency']; }, - {{{ USE_LSAN || USE_ASAN ? 'emscripten_builtin_' : '' }}}pthread_create__deps: ['_spawn_thread', 'pthread_getschedparam', 'pthread_self', 'memalign'], + {{{ USE_LSAN || USE_ASAN ? 'emscripten_builtin_' : '' }}}pthread_create__deps: ['_spawn_thread', 'pthread_getschedparam', 'pthread_self', 'memalign', '$resetPrototype'], {{{ USE_LSAN || USE_ASAN ? 'emscripten_builtin_' : '' }}}pthread_create: function(pthread_ptr, attr, start_routine, arg) { if (typeof SharedArrayBuffer === 'undefined') { err('Current environment does not support SharedArrayBuffer, pthreads are not available!'); @@ -769,17 +777,30 @@ var LibraryPThread = { if (canceled == 2) throw 'Canceled!'; }, +#if MINIMAL_RUNTIME + emscripten_check_blocking_allowed__deps: ['$warnOnce'], +#endif emscripten_check_blocking_allowed: function() { -#if ASSERTIONS - assert(ENVIRONMENT_IS_WEB); - warnOnce('Blocking on the main thread is very dangerous, see https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread'); +#if ASSERTIONS || IN_TEST_HARNESS || !MINIMAL_RUNTIME || !ALLOW_BLOCKING_ON_MAIN_THREAD +#if ENVIRONMENT_MAY_BE_NODE + if (ENVIRONMENT_IS_NODE) return; #endif + + if (ENVIRONMENT_IS_PTHREAD) return; // Blocking in a pthread is fine. + + warnOnce('Blocking on the main thread is very dangerous, see https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread'); #if !ALLOW_BLOCKING_ON_MAIN_THREAD abort('Blocking on the main thread is not allowed by default. See https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread'); +#endif + #endif }, - _emscripten_do_pthread_join__deps: ['_cleanup_thread', '_pthread_testcancel_js', 'emscripten_main_thread_process_queued_calls', 'emscripten_futex_wait', 'emscripten_check_blocking_allowed'], + _emscripten_do_pthread_join__deps: ['_cleanup_thread', '_pthread_testcancel_js', 'emscripten_main_thread_process_queued_calls', 'emscripten_futex_wait', +#if ASSERTIONS || IN_TEST_HARNESS || !MINIMAL_RUNTIME || !ALLOW_BLOCKING_ON_MAIN_THREAD + 'emscripten_check_blocking_allowed' +#endif + ], _emscripten_do_pthread_join: function(thread, status, block) { if (!thread) { err('pthread_join attempted on a null thread pointer!'); @@ -805,9 +826,11 @@ var LibraryPThread = { return ERRNO_CODES.EINVAL; // The thread is already detached, can no longer join it! } - if (block && ENVIRONMENT_IS_WEB) { +#if ASSERTIONS || IN_TEST_HARNESS || !MINIMAL_RUNTIME || !ALLOW_BLOCKING_ON_MAIN_THREAD + if (block) { _emscripten_check_blocking_allowed(); } +#endif for (;;) { var threadStatus = Atomics.load(HEAPU32, (thread + {{{ C_STRUCTS.pthread.threadStatus }}} ) >> 2); @@ -1057,9 +1080,11 @@ var LibraryPThread = { pthread_cleanup_push: function(routine, arg) { if (PThread.exitHandlers === null) { PThread.exitHandlers = []; +#if EXIT_RUNTIME if (!ENVIRONMENT_IS_PTHREAD) { __ATEXIT__.push(function() { PThread.runExitHandlers(); }); } +#endif } PThread.exitHandlers.push(function() { {{{ makeDynCall('vi') }}}(routine, arg) }); }, @@ -1297,7 +1322,7 @@ var LibraryPThread = { return func.apply(null, _emscripten_receive_on_main_thread_js_callArgs); }, - $establishStackSpaceInJsModule: function(stackTop, stackMax) { + $establishStackSpace: function(stackTop, stackMax) { STACK_BASE = STACKTOP = stackTop; STACK_MAX = stackMax; @@ -1318,8 +1343,14 @@ var LibraryPThread = { #if STACK_OVERFLOW_CHECK writeStackCookie(); #endif - // Call inside asm.js/wasm module to set up the stack frame for this pthread in asm.js/wasm module scope - establishStackSpace(stackTop, stackMax); + +#if WASM_BACKEND + // Call inside wasm module to set up the stack frame for this pthread in asm.js/wasm module scope + stackRestore(stackTop); +#else + // In old asm.js backend, use a dedicated function to establish the stack frame. + asmJsEstablishStackFrame(stackTop, stackMax); +#endif }, // allow pthreads to check if noExitRuntime from worker.js @@ -1327,6 +1358,19 @@ var LibraryPThread = { return noExitRuntime; }, + // When using postMessage to send an object, it is processed by the structured clone algorithm. + // The prototype, and hence methods, on that object is then lost. This function adds back the lost prototype. + // This does not work with nested objects that has prototypes, but it suffices for WasmSourceMap and WasmOffsetConverter. + $resetPrototype: function(constructor, attrs) { + var object = Object.create(constructor.prototype); + for (var key in attrs) { + if (attrs.hasOwnProperty(key)) { + object[key] = attrs[key]; + } + } + return object; + }, + // This function is called internally to notify target thread ID that it has messages it needs to // process in its message queue inside the Wasm heap. As a helper, the caller must also pass the // ID of the main browser thread to this function, to avoid needlessly ping-ponging between JS and diff --git a/src/library_syscall.js b/src/library_syscall.js index afaa6518742d7..52f174451e720 100644 --- a/src/library_syscall.js +++ b/src/library_syscall.js @@ -1153,6 +1153,10 @@ var SyscallsLibrary = { } #endif // SYSCALLS_REQUIRE_FILESYSTEM }, + +#if MINIMAL_RUNTIME + __syscall252__deps: ['$exit'], +#endif __syscall252: function(which, varargs) { // exit_group var status = SYSCALLS.get(); exit(status); diff --git a/src/memoryprofiler.js b/src/memoryprofiler.js index 6b02884f778b3..b50bfcd06921b 100644 --- a/src/memoryprofiler.js +++ b/src/memoryprofiler.js @@ -1,3 +1,5 @@ +#if MEMORYPROFILER + // Copyright 2015 The Emscripten Authors. All rights reserved. // Emscripten is available under two separate licenses, the MIT license and the // University of Illinois/NCSA Open Source License. Both these licenses can be @@ -597,3 +599,5 @@ var emscriptenMemoryProfiler = { function memoryprofiler_add_hooks() { emscriptenMemoryProfiler.initialize(); } if (typeof Module !== 'undefined' && typeof document !== 'undefined' && typeof window !== 'undefined' && typeof process === 'undefined') emscriptenMemoryProfiler.initialize(); + +#endif diff --git a/src/minimal_runtime_worker_externs.js b/src/minimal_runtime_worker_externs.js new file mode 100644 index 0000000000000..37bfaa3ee5336 --- /dev/null +++ b/src/minimal_runtime_worker_externs.js @@ -0,0 +1,6 @@ +// These externs are needed for MINIMAL_RUNTIME + USE_PTHREADS + !MODULARIZE +// This file should go away in the future when worker.js is refactored to live inside the JS module. + +var ENVIRONMENT_IS_PTHREAD; +/** @suppress {duplicate} */ +var wasmMemory; diff --git a/src/modules.js b/src/modules.js index f7a35003263ff..e09e3104fd883 100644 --- a/src/modules.js +++ b/src/modules.js @@ -87,6 +87,10 @@ var LibraryManager = { libraries.push('library_browser.js'); } + if (USE_PTHREADS) { // TODO: Currently WebGL proxying makes pthreads library depend on WebGL. + libraries.push('library_webgl.js'); + } + if (FILESYSTEM) { // Core filesystem libraries (always linked against, unless -s FILESYSTEM=0 is specified) libraries = libraries.concat([ @@ -154,7 +158,10 @@ var LibraryManager = { if (BOOTSTRAPPING_STRUCT_INFO) libraries = ['library_bootstrap_structInfo.js', 'library_formatString.js']; - // TODO: deduplicate libraries (not needed for correctness, but avoids unnecessary work) + // Deduplicate libraries to avoid processing any library file multiple times + libraries = libraries.filter(function(item, pos) { + return libraries.indexOf(item) == pos; + }); // Save the list for has() queries later. this.libraries = libraries; @@ -455,9 +462,6 @@ function exportRuntime() { 'allocateUTF8OnStack' ]); - if (USE_PTHREADS) { - runtimeElements.push('establishStackSpace'); - } } if (STACK_OVERFLOW_CHECK) { @@ -472,10 +476,13 @@ function exportRuntime() { // 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). - var threadExports = ['PThread', 'ExitStatus', '_pthread_self']; + var threadExports = ['PThread', '_pthread_self']; if (WASM) { threadExports.push('wasmMemory'); } + if (!MINIMAL_RUNTIME) { + threadExports.push('ExitStatus'); + } threadExports.forEach(function(x) { EXPORTED_RUNTIME_METHODS_SET[x] = 1; diff --git a/src/parseTools.js b/src/parseTools.js index 9c9ead903e57f..de2c8cb11281d 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -1658,3 +1658,19 @@ function buildStringArray(array) { return '[]'; } } + +// Generates access to a JS imports scope variable in pthreads worker.js. In MODULARIZE mode these flow into the imports object for the Module. +// In non-MODULARIZE mode, we can directly access the variables in global scope. +function makeAsmImportsAccessInPthread(variable) { + if (!MINIMAL_RUNTIME) { + // Regular runtime uses the name "Module" for both imports and exports. + return "Module['" + variable + "']"; + } + if (MODULARIZE) { + // MINIMAL_RUNTIME uses 'imports' as the name for the imports object in MODULARIZE builds. + return "imports['" + variable + "']"; + } else { + // In non-MODULARIZE builds, can access the imports from global scope. + return variable; + } +} diff --git a/src/postamble_minimal.js b/src/postamble_minimal.js index c004eff1c6f41..2b4023264b579 100644 --- a/src/postamble_minimal.js +++ b/src/postamble_minimal.js @@ -92,6 +92,25 @@ var imports = { var asm; #endif +#if USE_PTHREADS && WASM +var wasmModule; +#if PTHREAD_POOL_SIZE +function loadWasmModuleToWorkers() { +#if PTHREAD_POOL_DELAY_LOAD + PThread.unusedWorkers.forEach(PThread.loadWasmModuleToWorker); +#else + var numWorkersToLoad = PThread.unusedWorkers.length; + PThread.unusedWorkers.forEach(function(w) { PThread.loadWasmModuleToWorker(w, function() { + // PTHREAD_POOL_DELAY_LOAD==0: we wanted to synchronously wait until the Worker pool + // has loaded up. If all Workers have finished loading up the Wasm Module, proceed with main() + if (!--numWorkersToLoad) ready(); + })}); +#endif +} +#endif + +#endif + #if DECLARE_ASM_MODULE_EXPORTS /*** ASM_MODULE_EXPORTS_DECLARES ***/ #endif @@ -121,6 +140,11 @@ if (!Module['wasm']) throw 'Must load WebAssembly Module in to variable Module.w WebAssembly.instantiate(Module['wasm'], imports).then(function(output) { #endif +#if USE_PTHREADS + // Export Wasm module for pthread creation to access. + wasmModule = output.module || Module['wasm']; +#endif + #if !(LibraryManager.has('library_exports.js') && (WASM || WASM_BACKEND)) // If not using the emscripten_get_exported_function() API, keep the 'asm' exports // variable in local scope to this instantiate function. (otherwise access it without @@ -132,29 +156,65 @@ WebAssembly.instantiate(Module['wasm'], imports).then(function(output) { // output object will have an output.instance and output.module objects. But if Module['wasm'] // is an already compiled WebAssembly module, then output is the WebAssembly instance itself. // Depending on the build mode, Module['wasm'] can mean a different thing. -#if MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION || MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION +#if MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION || MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION || USE_PTHREADS // https://caniuse.com/#feat=wasm and https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiateStreaming // Firefox 52 added Wasm support, but only Firefox 58 added compileStreaming & instantiateStreaming. // Chrome 57 added Wasm support, but only Chrome 61 added compileStreaming & instantiateStreaming. // Node.js and Safari do not support compileStreaming or instantiateStreaming. -#if MIN_FIREFOX_VERSION < 58 || MIN_CHROME_VERSION < 61 || ENVIRONMENT_MAY_BE_NODE || MIN_SAFARI_VERSION != TARGET_NOT_SUPPORTED - asm = output.instance ? output.instance.exports : output.exports; +#if MIN_FIREFOX_VERSION < 58 || MIN_CHROME_VERSION < 61 || ENVIRONMENT_MAY_BE_NODE || MIN_SAFARI_VERSION != TARGET_NOT_SUPPORTED || USE_PTHREADS + // In pthreads, Module['wasm'] is an already compiled WebAssembly.Module. In that case, 'output' is a WebAssembly.Instance. + // In main thread, Module['wasm'] is either a typed array or a fetch stream. In that case, 'output.instance' is the WebAssembly.Instance. + asm = (output.instance || output).exports; #else asm = output.exports; #endif #else asm = output.instance.exports; #endif + #if USE_OFFSET_CONVERTER - wasmOffsetConverter = new WasmOffsetConverter(Module['wasm'], output.module); + wasmOffsetConverter = +#if USE_PTHREADS + ENVIRONMENT_IS_PTHREAD ? resetPrototype(WasmOffsetConverter, wasmOffsetData) : #endif + new WasmOffsetConverter(Module['wasm'], output.module); +#endif + #if !DECLARE_ASM_MODULE_EXPORTS exportAsmFunctions(asm); #else /*** ASM_MODULE_EXPORTS ***/ #endif + +#if USE_PTHREADS + //Export needed variables that worker.js needs to Module. +#if WASM_BACKEND + Module['_emscripten_tls_init'] = _emscripten_tls_init; +#endif + Module['HEAPU32'] = HEAPU32; + Module['dynCall_ii'] = dynCall_ii; + Module['__register_pthread_ptr'] = __register_pthread_ptr; + Module['_pthread_self'] = _pthread_self; +#endif + initRuntime(asm); +#if USE_PTHREADS && PTHREAD_POOL_SIZE + if (!ENVIRONMENT_IS_PTHREAD) loadWasmModuleToWorkers(); +#if !PTHREAD_POOL_DELAY_LOAD + else +#endif + ready(); +#else ready(); +#endif + +#if USE_PTHREADS + // This Worker is now ready to host pthreads, tell the main thread we can proceed. + if (ENVIRONMENT_IS_PTHREAD) { + postMessage({ 'cmd': 'loaded' }); + } +#endif + }) #if ASSERTIONS .catch(function(error) { @@ -167,8 +227,18 @@ WebAssembly.instantiate(Module['wasm'], imports).then(function(output) { // Initialize asm.js (synchronous) initRuntime(asm); + +#if USE_PTHREADS && PTHREAD_POOL_SIZE +if (!ENVIRONMENT_IS_PTHREAD) loadWasmModuleToWorkers(); +#if !PTHREAD_POOL_DELAY_LOAD +else +#endif + ready(); +#else ready(); +#endif #endif {{GLOBAL_VARS}} + diff --git a/src/preamble.js b/src/preamble.js index b93e5ff114a53..a7414cae92515 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -373,7 +373,7 @@ 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 establishStackSpaceInJsModule later. + // catch bugs, then set the real values in establishStackSpace later. #if ASSERTIONS || STACK_OVERFLOW_CHECK >= 2 STACK_MAX = STACKTOP = STACK_MAX = 0x7FFFFFFF; #endif @@ -777,9 +777,7 @@ function lookupSymbol(ptr) { // for a pointer, print out all symbols that resolv var memoryInitializer = null; -#if MEMORYPROFILER #include "memoryprofiler.js" -#endif #if ASSERTIONS && !('$FS' in addedLibraryItems) && !ASMFS // show errors on likely calls to FS when it was not included diff --git a/src/preamble_minimal.js b/src/preamble_minimal.js index 2b2f5b4ac53df..82b6448d9f5a0 100644 --- a/src/preamble_minimal.js +++ b/src/preamble_minimal.js @@ -12,7 +12,11 @@ function assert(condition, text) { #endif function abort(what) { +#if ASSERTIONS + throw new Error(what); +#else throw what; +#endif } var tempRet0 = 0; @@ -50,12 +54,16 @@ Module['wasm'] = base64Decode('{{{ getQuoted("WASM_BINARY_DATA") }}}'); #include "runtime_sab_polyfill.js" #if USE_PTHREADS +var STATIC_BASE = {{{ GLOBAL_BASE }}}; + if (!ENVIRONMENT_IS_PTHREAD) { #endif var GLOBAL_BASE = {{{ GLOBAL_BASE }}}, TOTAL_STACK = {{{ TOTAL_STACK }}}, +#if !USE_PTHREADS STATIC_BASE = {{{ GLOBAL_BASE }}}, +#endif STACK_BASE = {{{ getQuoted('STACK_BASE') }}}, STACKTOP = STACK_BASE, STACK_MAX = {{{ getQuoted('STACK_MAX') }}} @@ -82,6 +90,15 @@ var wasmMemory = new WebAssembly.Memory({ #endif }); +var buffer = wasmMemory.buffer; + +#if USE_PTHREADS +} +#if ASSERTIONS +assert(buffer instanceof SharedArrayBuffer, 'requested a shared WebAssembly.Memory but the returned buffer is not a SharedArrayBuffer, indicating that while the browser has SharedArrayBuffer it does not have WebAssembly threads support - you may need to set a flag'); +#endif +#endif + var wasmTable = new WebAssembly.Table({ 'initial': {{{ getQuoted('WASM_TABLE_SIZE') }}}, #if !ALLOW_TABLE_GROWTH @@ -94,12 +111,6 @@ var wasmTable = new WebAssembly.Table({ 'element': 'anyfunc' }); -var buffer = wasmMemory.buffer; - -#if USE_PTHREADS && ASSERTIONS -assert(buffer instanceof SharedArrayBuffer, 'requested a shared WebAssembly.Memory but the returned buffer is not a SharedArrayBuffer, indicating that while the browser has SharedArrayBuffer it does not have WebAssembly threads support - you may need to set a flag'); -#endif - #else #if USE_PTHREADS @@ -116,14 +127,20 @@ var buffer = new ArrayBuffer({{{ TOTAL_MEMORY }}}); #if ASSERTIONS var WASM_PAGE_SIZE = 65536; -assert(STACK_BASE % 16 === 0, 'stack must start aligned'); -assert(({{{ getQuoted('DYNAMIC_BASE') }}}) % 16 === 0, 'heap must start aligned'); +#if USE_PTHREADS +if (!ENVIRONMENT_IS_PTHREAD) { +#endif +assert(STACK_BASE % 16 === 0, 'stack must start aligned to 16 bytes, STACK_BASE==' + STACK_BASE); +assert(({{{ getQuoted('DYNAMIC_BASE') }}}) % 16 === 0, 'heap must start aligned to 16 bytes, DYNAMIC_BASE==' + {{{ getQuoted('DYNAMIC_BASE') }}}); assert({{{ TOTAL_MEMORY }}} >= TOTAL_STACK, 'TOTAL_MEMORY should be larger than TOTAL_STACK, was ' + {{{ TOTAL_MEMORY }}} + '! (TOTAL_STACK=' + TOTAL_STACK + ')'); assert({{{ TOTAL_MEMORY }}} % WASM_PAGE_SIZE === 0); #if WASM_MEM_MAX != -1 assert({{{ WASM_MEM_MAX }}} % WASM_PAGE_SIZE == 0); #endif assert(buffer.byteLength === {{{ TOTAL_MEMORY }}}); +#if USE_PTHREADS +} +#endif #endif // ASSERTIONS #if ALLOW_MEMORY_GROWTH @@ -156,11 +173,17 @@ var HEAPF32 = new Float32Array(buffer); var HEAPF64 = new Float64Array(buffer); #endif +#if USE_PTHREADS && ((MEM_INIT_METHOD == 1 && !MEM_INIT_IN_WASM && !SINGLE_FILE) || (SINGLE_FILE && !WASM && !WASM_BACKEND) || USES_DYNAMIC_ALLOC) +if (!ENVIRONMENT_IS_PTHREAD) { +#endif + #if MEM_INIT_METHOD == 1 && !MEM_INIT_IN_WASM && !SINGLE_FILE + #if ASSERTIONS if (!Module['mem']) throw 'Must load memory initializer as an ArrayBuffer in to variable Module.mem before adding compiled output .js script to the DOM'; #endif HEAPU8.set(new Uint8Array(Module['mem']), GLOBAL_BASE); + #endif #if SINGLE_FILE && !WASM && !WASM_BACKEND @@ -169,7 +192,11 @@ HEAPU8.set(base64Decode('{{{ getQuoted("BASE64_MEMORY_INITIALIZER") }}}'), GLOBA #endif #if USES_DYNAMIC_ALLOC -HEAP32[DYNAMICTOP_PTR>>2] = {{{ getQuoted('DYNAMIC_BASE') }}}; + HEAP32[DYNAMICTOP_PTR>>2] = {{{ getQuoted('DYNAMIC_BASE') }}}; +#endif + +#if USE_PTHREADS && ((MEM_INIT_METHOD == 1 && !MEM_INIT_IN_WASM && !SINGLE_FILE) || (SINGLE_FILE && !WASM && !WASM_BACKEND) || USES_DYNAMIC_ALLOC) +} #endif #include "runtime_stack_check.js" @@ -224,8 +251,8 @@ var runtimeExited = false; var memoryInitializer = null; -#if MEMORYPROFILER #include "memoryprofiler.js" -#endif + +#include "runtime_debug.js" // === Body === diff --git a/src/runtime_debug.js b/src/runtime_debug.js new file mode 100644 index 0000000000000..9456ed23b7b68 --- /dev/null +++ b/src/runtime_debug.js @@ -0,0 +1,51 @@ +#if RUNTIME_DEBUG +var runtimeDebug = true; // Switch to false at runtime to disable logging at the right times + +var printObjectList = []; + +function prettyPrint(arg) { + if (typeof arg == 'undefined') return '!UNDEFINED!'; + if (typeof arg == 'boolean') arg = arg + 0; + if (!arg) return arg; + var index = printObjectList.indexOf(arg); + if (index >= 0) return '<' + arg + '|' + index + '>'; + if (arg.toString() == '[object HTMLImageElement]') { + return arg + '\n\n'; + } + if (arg.byteLength) { + return '{' + Array.prototype.slice.call(arg, 0, Math.min(arg.length, 400)) + '}'; // Useful for correct arrays, less so for compiled arrays, see the code below for that + var buf = new ArrayBuffer(32); + var i8buf = new Int8Array(buf); + var i16buf = new Int16Array(buf); + var f32buf = new Float32Array(buf); + switch(arg.toString()) { + case '[object Uint8Array]': + i8buf.set(arg.subarray(0, 32)); + break; + case '[object Float32Array]': + f32buf.set(arg.subarray(0, 5)); + break; + case '[object Uint16Array]': + i16buf.set(arg.subarray(0, 16)); + break; + default: + alert('unknown array for debugging: ' + arg); + throw 'see alert'; + } + var ret = '{' + arg.byteLength + ':\n'; + var arr = Array.prototype.slice.call(i8buf); + ret += 'i8:' + arr.toString().replace(/,/g, ',') + '\n'; + arr = Array.prototype.slice.call(f32buf, 0, 8); + ret += 'f32:' + arr.toString().replace(/,/g, ',') + '}'; + return ret; + } + if (typeof arg == 'object') { + printObjectList.push(arg); + return '<' + arg + '|' + (printObjectList.length-1) + '>'; + } + if (typeof arg == 'number') { + if (arg > 0) return '0x' + arg.toString(16) + ' (' + arg + ')'; + } + return arg; +} +#endif diff --git a/src/settings_internal.js b/src/settings_internal.js index c6c977480428c..aa7fb9f651ca6 100644 --- a/src/settings_internal.js +++ b/src/settings_internal.js @@ -169,4 +169,4 @@ var TARGET_NOT_SUPPORTED = 0x7FFFFFFF; // Wasm backend does not apply C name mangling (== prefix with an underscore) to // the following functions. (it also does not mangle any function that starts with // string "dynCall_") -var WASM_FUNCTIONS_THAT_ARE_NOT_NAME_MANGLED = ['setTempRet0', 'getTempRet0', 'stackAlloc', 'stackSave', 'stackRestore', 'establishStackSpace', '__growWasmMemory', '__heap_base', '__data_end']; +var WASM_FUNCTIONS_THAT_ARE_NOT_NAME_MANGLED = ['setTempRet0', 'getTempRet0', 'stackAlloc', 'stackSave', 'stackRestore', '__growWasmMemory', '__heap_base', '__data_end']; diff --git a/src/shell_minimal.js b/src/shell_minimal.js index 1c9935181455d..aacf8912d7fe8 100644 --- a/src/shell_minimal.js +++ b/src/shell_minimal.js @@ -21,6 +21,18 @@ var ENVIRONMENT_IS_NODE = typeof process === 'object'; var ENVIRONMENT_IS_SHELL = typeof read === 'function'; #endif +#if ASSERTIONS +#if !ENVIRONMENT_MAY_BE_NODE && !ENVIRONMENT_MAY_BE_SHELL +var ENVIRONMENT_IS_WEB = true +#else +#if ENVIRONMENT && ENVIRONMENT.indexOf(',') < 0 +var ENVIRONMENT_IS_WEB = {{{ ENVIRONMENT === 'web' }}}; +#else +var ENVIRONMENT_IS_WEB = !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_SHELL; +#endif +#endif +#endif + #if ASSERTIONS && ENVIRONMENT_MAY_BE_NODE && ENVIRONMENT_MAY_BE_SHELL if (ENVIRONMENT_IS_NODE && ENVIRONMENT_IS_SHELL) { throw 'unclear environment'; @@ -117,7 +129,13 @@ function err(text) { // compilation is ready. In that callback, call the function run() to start // the program. function ready() { - run(); +#if USE_PTHREADS + if (!ENVIRONMENT_IS_PTHREAD) { +#endif + run(); +#if USE_PTHREADS + } +#endif } // --pre-jses are emitted after the Module integration code, so that they can @@ -127,22 +145,18 @@ function ready() { #if USE_PTHREADS -#if !MODULARIZE +#if !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 // before the page load. In non-MODULARIZE modes generate it here. var _scriptDir = (typeof document !== 'undefined' && document.currentScript) ? document.currentScript.src : undefined; #endif -var ENVIRONMENT_IS_PTHREAD; -if (!ENVIRONMENT_IS_PTHREAD) ENVIRONMENT_IS_PTHREAD = false; // ENVIRONMENT_IS_PTHREAD=true will have been preset in pthread-main.js. Make it false in the main runtime thread. +// MINIMAL_RUNTIME does not support --proxy-to-worker option, so Worker and Pthread environments +// coincide. +var ENVIRONMENT_IS_WORKER = ENVIRONMENT_IS_PTHREAD = typeof importScripts === 'function'; -if (typeof ENVIRONMENT_IS_PTHREAD === 'undefined') { - // ENVIRONMENT_IS_PTHREAD=true will have been preset in pthread-main.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; -} #if MODULARIZE -else { +if (ENVIRONMENT_IS_WORKER) { var buffer = {{{EXPORT_NAME}}}.buffer; var STATICTOP = {{{EXPORT_NAME}}}.STATICTOP; var DYNAMICTOP_PTR = {{{EXPORT_NAME}}}.DYNAMICTOP_PTR; diff --git a/src/shell_minimal_runtime.html b/src/shell_minimal_runtime.html index a42604bc73a31..9c6f0c8e8a1d1 100644 --- a/src/shell_minimal_runtime.html +++ b/src/shell_minimal_runtime.html @@ -2,8 +2,12 @@