diff --git a/ChangeLog.md b/ChangeLog.md index c39559e4d4fbd..bced35e6272b3 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -20,6 +20,9 @@ See docs/process.md for more on how version tagging works. 4.0.8 (in development) ---------------------- +- Programs built with `-sWASM_WORKERS` no longer generate a separate `.ww.js` + file. This is similar to the change that was already made for pthreads in + #21701. This saves on complexity, code size and network requests (#24163) 4.0.7 - 04/15/25 ---------------- diff --git a/site/source/docs/tools_reference/settings_reference.rst b/site/source/docs/tools_reference/settings_reference.rst index 3de33688fa1d3..7d55efc6aea0a 100644 --- a/site/source/docs/tools_reference/settings_reference.rst +++ b/site/source/docs/tools_reference/settings_reference.rst @@ -2507,13 +2507,9 @@ Default value: false WASM_WORKERS ============ -If 1, enables support for Wasm Workers. Wasm Workers enable applications +Enables support for Wasm Workers. Wasm Workers enable applications to create threads using a lightweight web-specific API that builds on top -of Wasm SharedArrayBuffer + Atomics API. When enabled, a new build output -file a.ww.js will be generated to bootstrap the Wasm Worker JS contexts. -If 2, enables support for Wasm Workers, but without using a separate a.ww.js -file on the side. This can simplify deployment of builds, but will have a -downside that the generated build will no longer be csp-eval compliant. +of Wasm SharedArrayBuffer + Atomics API. [compile+link] - affects user code at compile and system libraries at link. Default value: 0 diff --git a/src/audio_worklet.js b/src/audio_worklet.js index 7f19d6404a25b..397250cc972d8 100644 --- a/src/audio_worklet.js +++ b/src/audio_worklet.js @@ -140,6 +140,9 @@ function createWasmAudioWorkletProcessor(audioParams) { class BootstrapMessages extends AudioWorkletProcessor { constructor(arg) { super(); + // Audio worklets need to show up as wasm workers. This is the way we signal + // that. + globalThis.name = 'em-ww'; // Initialize the global Emscripten Module object that contains e.g. the // Wasm Module and Memory objects. After this we are ready to load in the // main application JS script, which the main thread will addModule() diff --git a/src/lib/libwasm_worker.js b/src/lib/libwasm_worker.js index cf127361f3930..c77a1ba5c0395 100644 --- a/src/lib/libwasm_worker.js +++ b/src/lib/libwasm_worker.js @@ -4,15 +4,6 @@ * SPDX-License-Identifier: MIT */ -#if WASM_WORKERS == 2 -// Helpers for _wasmWorkerBlobUrl used in WASM_WORKERS == 2 mode -{{{ - const captureModuleArg = () => MODULARIZE ? '' : 'self.Module=d;'; - const instantiateModule = () => MODULARIZE ? `${EXPORT_NAME}(d);` : ''; - const instantiateWasm = () => MINIMAL_RUNTIME ? '' : 'd[`instantiateWasm`]=(i,r)=>{var n=new WebAssembly.Instance(d[`wasm`],i);return r(n,d[`wasm`]);};'; -}}} -#endif - #if WASM_WORKERS #if !SHARED_MEMORY @@ -37,18 +28,28 @@ {{{ const workerSupportsFutexWait = () => AUDIO_WORKLET ? "typeof AudioWorkletGlobalScope === 'undefined'" : '1'; const wasmWorkerJs = ` -#if WASM_WORKERS == 2 - _wasmWorkerBlobUrl -#elif MINIMAL_RUNTIME +#if MINIMAL_RUNTIME #if ENVIRONMENT_MAY_BE_NODE - Module['$wb'] || './${WASM_WORKER_FILE}' + Module['js'] || './${TARGET_JS_NAME}' #else - Module['$wb'] + Module['js'] #endif #else - locateFile('${WASM_WORKER_FILE}') + locateFile('${TARGET_JS_NAME}') #endif `; + const wasmWorkerOptions = `{ +#if ENVIRONMENT_MAY_BE_NODE + // This is the way that we signal to the node worker that it is hosting + // a wasm worker. + 'workerData': 'em-ww', +#endif +#if ENVIRONMENT_MAY_BE_WEB || ENVIRONMENT_MAY_BE_WORKER + // This is the way that we signal to the Web Worker that it is hosting + // a pthread. + 'name': 'em-ww', +#endif +}`; }}} #endif // ~WASM_WORKERS @@ -92,9 +93,13 @@ addToLibrary({ $_wasmWorkerInitializeRuntime: () => { let m = Module; #if ASSERTIONS + assert(m && m['$ww']); assert(m['sb'] % 16 == 0); assert(m['sz'] % 16 == 0); #endif +#if RUNTIME_DEBUG + dbg("wasmWorkerInitializeRuntime $ww:", m['$ww']); +#endif #if !MINIMAL_RUNTIME && isSymbolNeeded('$noExitRuntime') // Wasm workers basically never exit their runtime @@ -149,20 +154,9 @@ addToLibrary({ #endif }, -#if WASM_WORKERS == 2 - // In WASM_WORKERS == 2 build mode, we create the Wasm Worker global scope - // script from a string bundled in the main application JS file. This - // simplifies the number of deployed JS files with the app, but has a downside - // that the generated build output will no longer be csp-eval compliant. - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#unsafe_eval_expressions - $_wasmWorkerBlobUrl: "URL.createObjectURL(new Blob(['onmessage=function(d){onmessage=null;d=d.data;{{{ captureModuleArg() }}}{{{ instantiateWasm() }}}importScripts(d.js);{{{ instantiateModule() }}}d.wasm=d.mem=d.js=0;}'],{type:'application/javascript'}))", -#endif _emscripten_create_wasm_worker__deps: [ '$_wasmWorkers', '$_wasmWorkersID', '$_wasmWorkerAppendToQueue', '$_wasmWorkerRunPostMessage', -#if WASM_WORKERS == 2 - '$_wasmWorkerBlobUrl', -#endif #if ASSERTIONS 'emscripten_has_threading_support', #endif @@ -174,7 +168,7 @@ if (ENVIRONMENT_IS_WASM_WORKER && !ENVIRONMENT_IS_AUDIO_WORKLET #endif ) { - _wasmWorkers[0] = this; + _wasmWorkers[0] = globalThis; addEventListener("message", _wasmWorkerAppendToQueue); }`, _emscripten_create_wasm_worker: (stackLowestAddress, stackSize) => { @@ -191,10 +185,10 @@ if (ENVIRONMENT_IS_WASM_WORKER var p = trustedTypes.createPolicy( 'emscripten#workerPolicy1', { createScriptURL: (ignored) => {{{ wasmWorkerJs }}}} ); - worker = _wasmWorkers[_wasmWorkersID] = new Worker(p.createScriptURL('ignored')); + worker = _wasmWorkers[_wasmWorkersID] = new Worker(p.createScriptURL('ignored'), {{{ wasmWorkerOptions }}}); } else #endif - worker = _wasmWorkers[_wasmWorkersID] = new Worker({{{ wasmWorkerJs }}}); + worker = _wasmWorkers[_wasmWorkersID] = new Worker({{{ wasmWorkerJs }}}, {{{ wasmWorkerOptions }}}); // Craft the Module object for the Wasm Worker scope: worker.postMessage({ // Signal with a non-zero value that this Worker will be a Wasm Worker, @@ -202,17 +196,10 @@ if (ENVIRONMENT_IS_WASM_WORKER '$ww': _wasmWorkersID, #if MINIMAL_RUNTIME 'wasm': Module['wasm'], -#if ENVIRONMENT_MAY_BE_NODE - 'js': Module['js'] || './{{{ TARGET_JS_NAME }}}', -#else - 'js': Module['js'], -#endif - 'mem': wasmMemory, #else 'wasm': wasmModule, - 'js': Module['mainScriptUrlOrBlob'] || _scriptName, - 'wasmMemory': wasmMemory, #endif + 'mem': wasmMemory, 'sb': stackLowestAddress, // sb = stack bottom (lowest stack address, SP points at this when stack is full) 'sz': stackSize, // sz = stack size }); diff --git a/src/parseTools.mjs b/src/parseTools.mjs index 16f9a96bcfc45..393e4de557fb4 100644 --- a/src/parseTools.mjs +++ b/src/parseTools.mjs @@ -1081,10 +1081,6 @@ function getPerformanceNow() { } } -function implicitSelf() { - return ENVIRONMENT.includes('node') ? 'self.' : ''; -} - function ENVIRONMENT_IS_MAIN_THREAD() { return `(!${ENVIRONMENT_IS_WORKER_THREAD()})`; } @@ -1143,7 +1139,6 @@ addToCompileTimeContext({ getPerformanceNow, getUnsharedTextDecoderView, hasExportedSymbol, - implicitSelf, isSymbolNeeded, makeDynCall, makeEval, diff --git a/src/postamble_minimal.js b/src/postamble_minimal.js index 428d21ed5e906..12f89e5ac9a29 100644 --- a/src/postamble_minimal.js +++ b/src/postamble_minimal.js @@ -95,7 +95,7 @@ var wasmExports; var wasmModule; #endif -#if PTHREADS +#if PTHREADS || WASM_WORKERS function loadModule() { assignWasmImports(); #endif @@ -255,12 +255,12 @@ WebAssembly.instantiate(Module['wasm'], imports).then((output) => { #endif // ASSERTIONS || WASM == 2 ); -#if PTHREADS +#if PTHREADS || WASM_WORKERS } -if (!ENVIRONMENT_IS_PTHREAD) { - // When running in a pthread we delay module loading untill we have - // received the module via postMessage - loadModule(); -} +// When running in a background thread we delay module loading until we have +#if AUDIO_WORKLET +if (ENVIRONMENT_IS_AUDIO_WORKLET) loadModule(); +#endif +{{{ runIfMainThread('loadModule();') }}} #endif diff --git a/src/preamble.js b/src/preamble.js index 63d8bed8d9c41..766e90f90157f 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -248,9 +248,7 @@ function exitRuntime() { #if STACK_OVERFLOW_CHECK checkStackCookie(); #endif -#if PTHREADS - if (ENVIRONMENT_IS_PTHREAD) return; // PThreads reuse the runtime from the main thread. -#endif + {{{ runIfWorkerThread('return;') }}} // PThreads reuse the runtime from the main thread. #if !STANDALONE_WASM ___funcs_on_exit(); // Native atexit() functions #endif @@ -266,9 +264,7 @@ function postRun() { #if STACK_OVERFLOW_CHECK checkStackCookie(); #endif -#if PTHREADS - if (ENVIRONMENT_IS_PTHREAD) return; // PThreads reuse the runtime from the main thread. -#endif + {{{ runIfWorkerThread('return;') }}} // PThreads reuse the runtime from the main thread. #if expectToReceiveOnModule('postRun') if (Module['postRun']) { @@ -823,7 +819,7 @@ async function instantiateAsync(binary, binaryFile, imports) { #if !WASM_ESM_INTEGRATION function getWasmImports() { -#if PTHREADS +#if PTHREADS || WASM_WORKERS assignWasmImports(); #endif #if ASYNCIFY && (ASSERTIONS || ASYNCIFY == 2) @@ -1008,8 +1004,8 @@ function getWasmImports() { } #endif -#if PTHREADS - if (ENVIRONMENT_IS_PTHREAD) { +#if PTHREADS || WASM_WORKERS + if ({{{ ENVIRONMENT_IS_WORKER_THREAD() }}}) { return new Promise((resolve) => { wasmModuleReceived = (module) => { // Instantiate from the module posted from the main thread. diff --git a/src/runtime_debug.js b/src/runtime_debug.js index c3736d42892c7..71961a96a7470 100644 --- a/src/runtime_debug.js +++ b/src/runtime_debug.js @@ -193,7 +193,7 @@ var runtimeDebug = true; // Switch to false at runtime to disable logging at the // Used by XXXXX_DEBUG settings to output debug messages. function dbg(...args) { if (!runtimeDebug && typeof runtimeDebug != 'undefined') return; -#if ENVIRONMENT_MAY_BE_NODE && PTHREADS +#if ENVIRONMENT_MAY_BE_NODE && (PTHREADS || WASM_WORKERS) // Avoid using the console for debugging in multi-threaded node applications // See https://github.com/emscripten-core/emscripten/issues/14804 if (ENVIRONMENT_IS_NODE) { diff --git a/src/runtime_init_memory.js b/src/runtime_init_memory.js index 2834e516dd847..73a7f91432fe9 100644 --- a/src/runtime_init_memory.js +++ b/src/runtime_init_memory.js @@ -12,9 +12,10 @@ // check for full engine support (use string 'subarray' to avoid closure compiler confusion) function initMemory() { -#if PTHREADS - if (ENVIRONMENT_IS_PTHREAD) return; -#endif // PTHREADS +#if AUDIO_WORKLET + if (!ENVIRONMENT_IS_AUDIO_WORKLET) +#endif + {{{ runIfWorkerThread('return') }}} #if expectToReceiveOnModule('wasmMemory') if (Module['wasmMemory']) { diff --git a/src/runtime_pthread.js b/src/runtime_pthread.js index 8a9f4adb708f2..6132b4aa9c4af 100644 --- a/src/runtime_pthread.js +++ b/src/runtime_pthread.js @@ -21,25 +21,6 @@ var sharedModules = {}; #endif if (ENVIRONMENT_IS_PTHREAD) { -#if !MINIMAL_RUNTIME - var wasmModuleReceived; -#endif - -#if ENVIRONMENT_MAY_BE_NODE - // Node.js support - if (ENVIRONMENT_IS_NODE) { - // Create as web-worker-like an environment as we can. - - var parentPort = worker_threads['parentPort']; - parentPort.on('message', (msg) => onmessage({ data: msg })); - - Object.assign(globalThis, { - self: global, - postMessage: (msg) => parentPort.postMessage(msg), - }); - } -#endif // ENVIRONMENT_MAY_BE_NODE - // Thread-local guard variable for one-time init of the JS state var initializedJS = false; diff --git a/src/runtime_shared.js b/src/runtime_shared.js index 16bc957c994a6..b9ed3b11f0f8d 100644 --- a/src/runtime_shared.js +++ b/src/runtime_shared.js @@ -21,10 +21,32 @@ #include "runtime_asan.js" #endif +#if PTHREADS || WASM_WORKERS +#if !MINIMAL_RUNTIME +var wasmModuleReceived; +#endif + +#if ENVIRONMENT_MAY_BE_NODE +if (ENVIRONMENT_IS_NODE && {{{ ENVIRONMENT_IS_WORKER_THREAD() }}}) { + // Create as web-worker-like an environment as we can. + var parentPort = worker_threads['parentPort']; + parentPort.on('message', (msg) => global.onmessage?.({ data: msg })); + Object.assign(globalThis, { + self: global, + postMessage: (msg) => parentPort['postMessage'](msg), + }); +} +#endif // ENVIRONMENT_MAY_BE_NODE +#endif + #if PTHREADS #include "runtime_pthread.js" #endif +#if WASM_WORKERS +#include "wasm_worker.js" +#endif + #if LOAD_SOURCE_MAP var wasmSourceMap; #include "source_map_support.js" diff --git a/src/settings.js b/src/settings.js index e95eb350a465b..1dfa11ed0b326 100644 --- a/src/settings.js +++ b/src/settings.js @@ -1637,13 +1637,9 @@ var USE_SQLITE3 = false; // [compile+link] - affects user code at compile and system libraries at link. var SHARED_MEMORY = false; -// If 1, enables support for Wasm Workers. Wasm Workers enable applications +// Enables support for Wasm Workers. Wasm Workers enable applications // to create threads using a lightweight web-specific API that builds on top -// of Wasm SharedArrayBuffer + Atomics API. When enabled, a new build output -// file a.ww.js will be generated to bootstrap the Wasm Worker JS contexts. -// If 2, enables support for Wasm Workers, but without using a separate a.ww.js -// file on the side. This can simplify deployment of builds, but will have a -// downside that the generated build will no longer be csp-eval compliant. +// of Wasm SharedArrayBuffer + Atomics API. // [compile+link] - affects user code at compile and system libraries at link. var WASM_WORKERS = 0; diff --git a/src/settings_internal.js b/src/settings_internal.js index 44dfca3f5a2ad..55853239d3208 100644 --- a/src/settings_internal.js +++ b/src/settings_internal.js @@ -134,9 +134,6 @@ var USER_EXPORTS = []; // name of the file containing wasm binary, if relevant var WASM_BINARY_FILE = ''; -// name of the file containing the Wasm Worker *.ww.js, if relevant -var WASM_WORKER_FILE = ''; - // Base URL the source mapfile, if relevant var SOURCE_MAP_BASE = ''; diff --git a/src/shell.js b/src/shell.js index a30d4278b059d..d928d1cb2dacf 100644 --- a/src/shell.js +++ b/src/shell.js @@ -102,6 +102,12 @@ if (ENVIRONMENT_IS_PTHREAD) { #endif #endif +#if WASM_WORKERS +// The way we signal to a worker that it is hosting a pthread is to construct +// it with a specific name. +var ENVIRONMENT_IS_WASM_WORKER = globalThis.name == 'em-ww'; +#endif + #if ENVIRONMENT_MAY_BE_NODE if (ENVIRONMENT_IS_NODE) { #if EXPORT_ES6 @@ -121,14 +127,13 @@ if (ENVIRONMENT_IS_NODE) { // is hosting a pthread. ENVIRONMENT_IS_PTHREAD = ENVIRONMENT_IS_WORKER && worker_threads['workerData'] == 'em-pthread' #endif // PTHREADS +#if WASM_WORKERS + ENVIRONMENT_IS_WASM_WORKER = ENVIRONMENT_IS_WORKER && worker_threads['workerData'] == 'em-ww' +#endif #endif // PTHREADS || WASM_WORKERS } #endif // ENVIRONMENT_MAY_BE_NODE -#if WASM_WORKERS -var ENVIRONMENT_IS_WASM_WORKER = !!Module['$ww']; -#endif - // --pre-jses are emitted after the Module integration code, so that they can // refer to Module (if they choose; they can also define Module) {{{ preJS() }}} diff --git a/src/shell_minimal.js b/src/shell_minimal.js index a34167d431cba..fdaf115c6adc3 100644 --- a/src/shell_minimal.js +++ b/src/shell_minimal.js @@ -69,8 +69,23 @@ var ENVIRONMENT_IS_WEB = !ENVIRONMENT_IS_NODE; #endif #endif // ASSERTIONS || PTHREADS +#if ENVIRONMENT_MAY_BE_NODE && (PTHREADS || WASM_WORKERS) +if (ENVIRONMENT_IS_NODE) { + var worker_threads = require('worker_threads'); + global.Worker = worker_threads.Worker; +} +#endif + #if WASM_WORKERS -var ENVIRONMENT_IS_WASM_WORKER = !!Module['$ww']; +var ENVIRONMENT_IS_WASM_WORKER = globalThis.name == 'em-ww'; + +#if ENVIRONMENT_MAY_BE_NODE +if (ENVIRONMENT_IS_NODE) { + // The way we signal to a worker that it is hosting a pthread is to construct + // it with a specific name. + ENVIRONMENT_IS_WASM_WORKER = worker_threads['workerData'] == 'em-ww' +} +#endif #endif #if ASSERTIONS && ENVIRONMENT_MAY_BE_NODE && ENVIRONMENT_MAY_BE_SHELL @@ -139,8 +154,6 @@ var _scriptName = typeof document != 'undefined' ? document.currentScript?.src : #if ENVIRONMENT_MAY_BE_NODE if (ENVIRONMENT_IS_NODE) { - var worker_threads = require('worker_threads'); - global.Worker = worker_threads.Worker; ENVIRONMENT_IS_WORKER = !worker_threads.isMainThread; // Under node we set `workerData` to `em-pthread` to signal that the worker // is hosting a pthread. diff --git a/src/wasm_worker.js b/src/wasm_worker.js index b08b60877ead4..1c6a88a7fcf54 100644 --- a/src/wasm_worker.js +++ b/src/wasm_worker.js @@ -1,21 +1,15 @@ -// N.B. The contents of this file are duplicated in src/library_wasm_worker.js -// in variable "_wasmWorkerBlobUrl" (where the contents are pre-minified) If -// doing any changes to this file, be sure to update the contents there too. - -'use strict'; +#if AUDIO_WORKLET +if (ENVIRONMENT_IS_WASM_WORKER && !ENVIRONMENT_IS_AUDIO_WORKLET) { +#else +if (ENVIRONMENT_IS_WASM_WORKER) { +#endif +#if RUNTIME_DEBUG + dbg('wasm worker starting ...'); +#endif #if ENVIRONMENT_MAY_BE_NODE // Node.js support -var ENVIRONMENT_IS_NODE = typeof process == 'object' && typeof process.versions == 'object' && typeof process.versions.node == 'string' && process.type != 'renderer'; if (ENVIRONMENT_IS_NODE) { - // Create as web-worker-like an environment as we can. - - var nodeWorkerThreads = require('worker_threads'); - - var parentPort = nodeWorkerThreads.parentPort; - - parentPort.on('message', (msg) => global.onmessage?.({ data: msg })); - // Weak map of handle functions to their wrapper. Used to implement // addEventListener/removeEventListener. var wrappedHandlers = new WeakMap(); @@ -28,52 +22,39 @@ if (ENVIRONMENT_IS_NODE) { return f; } - var fs = require('fs'); - var vm = require('vm'); - - Object.assign(global, { - self: global, - require, -#if !EXPORT_ES6 - // `vm.runInThisContext` global scope lacks `__filename` and `__dirname` - __filename, - __dirname, -#endif - Worker: nodeWorkerThreads.Worker, - importScripts: (f) => vm.runInThisContext(fs.readFileSync(f, 'utf8'), {filename: f}), - postMessage: (msg) => parentPort.postMessage(msg), - performance: global.performance || { now: Date.now }, - addEventListener: (name, handler) => parentPort.on(name, wrapMsgHandler(handler)), - removeEventListener: (name, handler) => parentPort.off(name, wrapMsgHandler(handler)), + Object.assign(globalThis, { + addEventListener: (name, handler) => parentPort['on'](name, wrapMsgHandler(handler)), + removeEventListener: (name, handler) => parentPort['off'](name, wrapMsgHandler(handler)), }); } #endif // ENVIRONMENT_MAY_BE_NODE -{{{ implicitSelf() }}}onmessage = function(d) { +onmessage = (d) => { // The first message sent to the Worker is always the bootstrap message. // Drop this message listener, it served its purpose of bootstrapping // the Wasm Module load, and is no longer needed. Let user code register // any desired message handlers from now on. - {{{ implicitSelf() }}}onmessage = null; - d = d.data; -#if !MODULARIZE - self.{{{ EXPORT_NAME }}} = d; -#endif -#if !MINIMAL_RUNTIME - d['instantiateWasm'] = (info, receiveInstance) => { var instance = new WebAssembly.Instance(d['wasm'], info); return receiveInstance(instance, d['wasm']); } + /** @suppress {checkTypes} */ + onmessage = null; +#if RUNTIME_DEBUG + dbg('wasm worker initial onmessage'); #endif -#if TRUSTED_TYPES - // Use Trusted Types compatible wrappers. - if (typeof trustedTypes != 'undefined' && trustedTypes.createPolicy) { - var p = trustedTypes.createPolicy('emscripten#scriptPolicy1', { createScriptURL: (ignored) => d.js }); - importScripts(p.createScriptURL('ignored')); - } else + d = d.data; +#if MINIMAL_RUNTIME + Module ||= {}; #endif - importScripts(d.js); -#if MODULARIZE - {{{ EXPORT_NAME }}}(d); + /** @suppress {checkTypes} */ + Object.assign(Module, d); + wasmMemory = d['mem']; + updateMemoryViews(); +#if MINIMAL_RUNTIME + loadModule() +#else + wasmModuleReceived(d['wasm']); #endif // Drop now unneeded references to from the Module object in this Worker, // these are not needed anymore. - d.wasm = d.mem = d.js = 0; + d['wasm'] = d['mem'] = 0; +} + } diff --git a/test/code_size/hello_wasm_worker_wasm.js b/test/code_size/hello_wasm_worker_wasm.js index 37071d3d1c82d..48ef493cac20b 100644 --- a/test/code_size/hello_wasm_worker_wasm.js +++ b/test/code_size/hello_wasm_worker_wasm.js @@ -1,50 +1,75 @@ -var b = Module, c = !!b.$ww, e = b.mem || new WebAssembly.Memory({ +var b = Module, c = "em-ww" == globalThis.name, e, k, w, x, y; + +c && (onmessage = a => { + onmessage = null; + a = a.data; + b ||= {}; + Object.assign(b, a); + e = a.mem; + f(); + g(); + a.wasm = a.mem = 0; +}); + +function f() {} + +c || (e = b.mem || new WebAssembly.Memory({ initial: 256, maximum: 256, shared: !0 -}), f = e.buffer, g = [], h, k = a => { +}), f()); + +var h = [], m = a => { a = a.data; let d = a._wsc; - d && h.get(d)(...a.x); -}, l = a => { - g.push(a); -}, n = {}, p = 1, q, r; - -c && (n[0] = this, addEventListener("message", l)); - -WebAssembly.instantiate(b.wasm, { - a: { - b: (a, d) => { - let m = n[p] = new Worker(b.$wb); - m.postMessage({ - $ww: p, - wasm: b.wasm, - js: b.js, - mem: e, - sb: a, - sz: d - }); - m.onmessage = k; - return p++; - }, - c: () => !1, - d: (a, d) => { - n[a].postMessage({ - _wsc: d, - x: [] - }); - }, - e: function() { - console.log("Hello from wasm worker!"); - }, + d && k.get(d)(...a.x); +}, n = a => { + h.push(a); +}, p = {}, q = 1, r = (a, d) => { + let l = p[q] = new Worker(b.js, { + name: "em-ww" + }); + l.postMessage({ + $ww: q, + wasm: b.wasm, + mem: e, + sb: a, + sz: d + }); + l.onmessage = m; + return q++; +}, t = () => !1, u = (a, d) => { + p[a].postMessage({ + _wsc: d, + x: [] + }); +}; + +c && (p[0] = globalThis, addEventListener("message", n)); + +function v() { + console.log("Hello from wasm worker!"); +} + +function g() { + w = { + b: r, + c: t, + d: u, + e: v, a: e - } -}).then((a => { - a = a.instance.exports; - q = a.g; - r = a.i; - h = a.h; - c ? (a = b, r(a.sb, a.sz), removeEventListener("message", l), g = g.forEach(k), - addEventListener("message", k)) : a.f(); - c || q(); -})); \ No newline at end of file + }; + WebAssembly.instantiate(b.wasm, { + a: w + }).then((a => { + a = a.instance.exports; + x = a.g; + y = a.i; + k = a.h; + c ? (a = b, y(a.sb, a.sz), removeEventListener("message", n), h = h.forEach(m), + addEventListener("message", m)) : a.f(); + c || x(); + })); +} + +c || g(); \ No newline at end of file diff --git a/test/code_size/hello_wasm_worker_wasm.json b/test/code_size/hello_wasm_worker_wasm.json index e8f273675b42a..130b44ce1ee91 100644 --- a/test/code_size/hello_wasm_worker_wasm.json +++ b/test/code_size/hello_wasm_worker_wasm.json @@ -1,12 +1,10 @@ { - "a.html": 618, - "a.html.gz": 384, - "a.js": 667, - "a.js.gz": 457, - "a.ww.js": 115, - "a.ww.js.gz": 127, + "a.html": 519, + "a.html.gz": 364, + "a.js": 856, + "a.js.gz": 548, "a.wasm": 1881, "a.wasm.gz": 1069, - "total": 3281, - "total_gz": 2037 + "total": 3256, + "total_gz": 1981 } diff --git a/test/modularize_post_js.js b/test/modularize_post_js.js index 18ad0c15b8d6e..73dc884a486cd 100644 --- a/test/modularize_post_js.js +++ b/test/modularize_post_js.js @@ -7,4 +7,8 @@ // Avoid instantiating the module on pthreads. if (!isPthread) #endif +#if WASM_WORKERS +// Avoid instantiating the module on pthreads. +if (!isWW) +#endif {{{ EXPORT_NAME }}}(); diff --git a/test/other/codesize/test_codesize_minimal_pthreads.gzsize b/test/other/codesize/test_codesize_minimal_pthreads.gzsize index c79ca50a799a1..42daeff1ec5e7 100644 --- a/test/other/codesize/test_codesize_minimal_pthreads.gzsize +++ b/test/other/codesize/test_codesize_minimal_pthreads.gzsize @@ -1 +1 @@ -3983 +3988 diff --git a/test/other/codesize/test_codesize_minimal_pthreads.jssize b/test/other/codesize/test_codesize_minimal_pthreads.jssize index 6098d2d252fd2..894177be76d04 100644 --- a/test/other/codesize/test_codesize_minimal_pthreads.jssize +++ b/test/other/codesize/test_codesize_minimal_pthreads.jssize @@ -1 +1 @@ -8257 +8264 diff --git a/test/other/test_unoptimized_code_size.js.size b/test/other/test_unoptimized_code_size.js.size index bf4cfbe8fa457..e5f530b3994cf 100644 --- a/test/other/test_unoptimized_code_size.js.size +++ b/test/other/test_unoptimized_code_size.js.size @@ -1 +1 @@ -53547 +53602 diff --git a/test/other/test_unoptimized_code_size_no_asserts.js.size b/test/other/test_unoptimized_code_size_no_asserts.js.size index 6068c3460c88c..3059e1329cf71 100644 --- a/test/other/test_unoptimized_code_size_no_asserts.js.size +++ b/test/other/test_unoptimized_code_size_no_asserts.js.size @@ -1 +1 @@ -26961 +27016 diff --git a/test/other/test_unoptimized_code_size_strict.js.size b/test/other/test_unoptimized_code_size_strict.js.size index 488dac4df77a7..40ec1162cfc7b 100644 --- a/test/other/test_unoptimized_code_size_strict.js.size +++ b/test/other/test_unoptimized_code_size_strict.js.size @@ -1 +1 @@ -51597 +51652 diff --git a/test/test_browser.py b/test/test_browser.py index ea89013ef51b9..5f49489c53a34 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -5115,7 +5115,7 @@ def test_system(self): # Tests the hello_wasm_worker.c documentation example code. @also_with_minimal_runtime def test_wasm_worker_hello(self): - self.btest_exit('wasm_worker/hello_wasm_worker.c', emcc_args=['-sWASM_WORKERS']) + self.btest_exit('wasm_worker/hello_wasm_worker.c', emcc_args=['-sWASM_WORKERS', '-sENVIRONMENT=web,worker']) def test_wasm_worker_hello_minimal_runtime_2(self): self.btest_exit('wasm_worker/hello_wasm_worker.c', emcc_args=['-sWASM_WORKERS', '-sMINIMAL_RUNTIME=2']) diff --git a/test/test_other.py b/test/test_other.py index 767789eb075c8..bfa0cd34c430f 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -11794,8 +11794,6 @@ def print_percent(actual, expected): return ' ({:+.2f}%)'.format((actual - expected) * 100.0 / expected) outputs = ['a.html', 'a.js'] - if '-sWASM_WORKERS' in sources: - outputs += ['a.ww.js'] args = smallest_code_size_args[:] diff --git a/tools/emscripten.py b/tools/emscripten.py index 6ec4cba7454e8..47496bf2c42ab 100644 --- a/tools/emscripten.py +++ b/tools/emscripten.py @@ -942,7 +942,7 @@ def install_wrapper(sym): # With assertions enabled we create a wrapper that are calls get routed through, for # the lifetime of the program. wrapper += f"createExportWrapper('{name}', {nargs});" - elif (settings.WASM_ASYNC_COMPILATION and not can_use_await()) or settings.PTHREADS: + elif (settings.WASM_ASYNC_COMPILATION and not can_use_await()) or settings.PTHREADS or settings.WASM_WORKERS: # With WASM_ASYNC_COMPILATION wrapper will replace the global var and Module var on # first use. args = [f'a{i}' for i in range(nargs)] @@ -1010,7 +1010,7 @@ def create_module(receiving, metadata, global_exports, library_symbols): module = [] sending = create_sending(metadata, library_symbols) - if settings.PTHREADS: + if settings.PTHREADS or settings.WASM_WORKERS: sending = textwrap.indent(sending, ' ').strip() module.append('''\ var wasmImports; diff --git a/tools/link.py b/tools/link.py index 0fa6443d07100..854091edb2881 100644 --- a/tools/link.py +++ b/tools/link.py @@ -1405,9 +1405,6 @@ def limit_incoming_module_api(): if settings.WASM_WORKERS: settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$_wasmWorkerInitializeRuntime'] - # set location of Wasm Worker bootstrap JS file - if settings.WASM_WORKERS == 1: - settings.WASM_WORKER_FILE = unsuffixed(os.path.basename(target)) + '.ww.js' add_system_js_lib('libwasm_worker.js') # Set min browser versions based on certain settings such as WASM_BIGINT, @@ -2176,10 +2173,6 @@ def phase_final_emitting(options, target, js_target, wasm_target): target_dir = os.path.dirname(os.path.abspath(target)) - # Deploy the Wasm Worker bootstrap file as an output file (*.ww.js) - if settings.WASM_WORKERS == 1: - create_worker_file('src/wasm_worker.js', target_dir, settings.WASM_WORKER_FILE, options) - # Deploy the Audio Worklet module bootstrap file (*.aw.js) if settings.AUDIO_WORKLET: audio_worklet_file = unsuffixed_basename(js_target) + '.aw.js' @@ -2444,6 +2437,15 @@ def node_pthread_detection(): return "require('worker_threads').workerData === 'em-pthread'\n" +def node_ww_detection(): + # Under node we detect that we are running in a wasm worker by checking the + # workerData property. + if settings.EXPORT_ES6: + return "(await import('worker_threads')).workerData === 'em-ww';\n" + else: + return "require('worker_threads').workerData === 'em-ww'\n" + + def modularize(): global final_js logger.debug(f'Modularizing, assigning to var {settings.EXPORT_NAME}') @@ -2469,7 +2471,7 @@ def modularize(): 'generated_js': generated_js } - if settings.MINIMAL_RUNTIME and not settings.PTHREADS: + if settings.MINIMAL_RUNTIME and not settings.PTHREADS and not settings.WASM_WORKERS: # Single threaded MINIMAL_RUNTIME programs do not need access to # document.currentScript, so a simple export declaration is enough. src = f'var {settings.EXPORT_NAME} = {wrapper_function};' @@ -2536,6 +2538,26 @@ def modularize(): else: src += 'isPthread && %s();\n' % settings.EXPORT_NAME + if settings.WASM_WORKERS: + # Same as above for for WASM_WORKERS + # Normally this detection is done when the module is itself run but + # when running in MODULARIZE mode we need use this to know if we should + # run the module constructor on startup (true only for pthreads). + if settings.ENVIRONMENT_MAY_BE_WEB or settings.ENVIRONMENT_MAY_BE_WORKER: + src += "var isWW = globalThis.self?.name == 'em-ww';\n" + # In order to support both web and node we also need to detect node here. + if settings.ENVIRONMENT_MAY_BE_NODE: + if not settings.PTHREADS: + src += "var isNode = typeof globalThis.process?.versions?.node == 'string';\n" + src += f'if (isNode) isWW = {node_ww_detection()}\n' + elif settings.ENVIRONMENT_MAY_BE_NODE: + src += f'var isWW = {node_ww_detection()}\n' + src += '// When running as a wasm worker, construct a new instance on startup\n' + if settings.MODULARIZE == 'instance': + src += 'isWW && init();\n' + else: + src += 'isWW && %s();\n' % settings.EXPORT_NAME + final_js += '.modular.js' write_file(final_js, src) shared.get_temp_files().note(final_js) diff --git a/tools/minimal_runtime_shell.py b/tools/minimal_runtime_shell.py index 5e07bab0a1be3..1c1a29fe24ae5 100644 --- a/tools/minimal_runtime_shell.py +++ b/tools/minimal_runtime_shell.py @@ -63,17 +63,8 @@ def generate_minimal_runtime_load_statement(target_basename): files_to_load += [download_wasm] # Download wasm_worker file - if settings.WASM_WORKERS: - if settings.MODULARIZE: - if settings.WASM_WORKERS == 1: # '$wb': Wasm Worker Blob - modularize_imports += ["$wb: URL.createObjectURL(new Blob([r[%d]], { type: 'application/javascript' }))" % len(files_to_load)] - modularize_imports += ['js: js'] - else: - if settings.WASM_WORKERS == 1: - then_statements += ["%s.$wb = URL.createObjectURL(new Blob([r[%d]], { type: 'application/javascript' }));" % (settings.EXPORT_NAME, len(files_to_load))] - - if download_wasm and settings.WASM_WORKERS == 1: - files_to_load += [f"binary('{settings.WASM_WORKER_FILE}')"] + if settings.WASM_WORKERS and settings.MODULARIZE: + modularize_imports += ['js: js'] # Download Wasm2JS code if target browser does not support WebAssembly if settings.WASM == 2: