Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------------
Expand Down
8 changes: 2 additions & 6 deletions site/source/docs/tools_reference/settings_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/audio_worklet.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
61 changes: 24 additions & 37 deletions src/lib/libwasm_worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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) => {
Expand All @@ -191,28 +185,21 @@ 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,
// and not the main browser thread.
'$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
});
Expand Down
5 changes: 0 additions & 5 deletions src/parseTools.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1081,10 +1081,6 @@ function getPerformanceNow() {
}
}

function implicitSelf() {
return ENVIRONMENT.includes('node') ? 'self.' : '';
}

function ENVIRONMENT_IS_MAIN_THREAD() {
return `(!${ENVIRONMENT_IS_WORKER_THREAD()})`;
}
Expand Down Expand Up @@ -1143,7 +1139,6 @@ addToCompileTimeContext({
getPerformanceNow,
getUnsharedTextDecoderView,
hasExportedSymbol,
implicitSelf,
isSymbolNeeded,
makeDynCall,
makeEval,
Expand Down
14 changes: 7 additions & 7 deletions src/postamble_minimal.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ var wasmExports;
var wasmModule;
#endif

#if PTHREADS
#if PTHREADS || WASM_WORKERS
function loadModule() {
assignWasmImports();
#endif
Expand Down Expand Up @@ -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
14 changes: 5 additions & 9 deletions src/preamble.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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']) {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion src/runtime_debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
7 changes: 4 additions & 3 deletions src/runtime_init_memory.js
Original file line number Diff line number Diff line change
Expand Up @@ -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']) {
Expand Down
19 changes: 0 additions & 19 deletions src/runtime_pthread.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
22 changes: 22 additions & 0 deletions src/runtime_shared.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
8 changes: 2 additions & 6 deletions src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
3 changes: 0 additions & 3 deletions src/settings_internal.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '';

Expand Down
13 changes: 9 additions & 4 deletions src/shell.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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() }}}
Expand Down
Loading