Skip to content

Commit 37e1e68

Browse files
authored
Inline pthread worker.js file into the main output file (#21701)
This method has many advantages over the previous method of generating a separate file: - Avoids separate output file simplifying deployment. - Avoids confusing laying of global scopes - Avoids exporting symbols on the Module simply for visibility within the worker file. - Avoids code duplication - Avoids the needs to importScripts call, and the node polyfill for this. - Allows optimizers such as closure and JSDCE to operate on the combined code. - `-sSINGLE_FILE` now works with pthreads - Fewer network requests - No need for locateFile logic to run on the worker to find the worker.js The test_pthread_custom_pthread_main_url test was completely removed since it was testing how worker.js was located and worker.js no longer exists. test_pthread_safe_stack depends on `onAbort` being proxied back to the main thread. In this case the `onAbort` handler is injected conditionally in `--pre-js=browser_report.js`. In the previous code this meant that the proxied version took precedence because the pthread handler override was injected first. test_pthread_asan_use_after_free_2_wasmfs depends on `printErr` not being proxied back to the main thread. It is injected unconditionally during `--pre-js`. In the previous code path this means that non-proxied version took precedence because it overrode the incoming pthread handler. Fixes: #9796
1 parent 9f19ec9 commit 37e1e68

39 files changed

+598
-645
lines changed

.eslintrc.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ ignorePatterns:
2222
- "src/closure-externs/"
2323
- "src/embind/"
2424
- "src/emrun_postjs.js"
25-
- "src/worker.js"
2625
- "src/wasm_worker.js"
2726
- "src/audio_worklet.js"
2827
- "src/wasm2js.js"

ChangeLog.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ See docs/process.md for more on how version tagging works.
2323
- Enable use of `::` to escape port option separator (#21710)
2424
- In multi-threaded builds `--extern-pre-js` and `--extern-post-js` code is
2525
now only run on the main thread, and not on each of the workers. (#21750)
26+
- Multi-threaded builds no depend on a separate `.worker.js` file. This saves
27+
on code size and network requests. In order to make this change go smoothly,
28+
without breaking build systems that expect a `worker.js`, emscripten will
29+
generate an empty `.worker.js` to give folks time to transition their
30+
deployment scripts. In `-sSTRICT` mode, this empty file will not be
31+
generated. (#21701)
2632

2733
3.1.57 - 04/10/24
2834
-----------------

site/source/docs/porting/pthreads.rst

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,6 @@ The Emscripten implementation for the pthreads API should follow the POSIX stand
146146

147147
- Pthreads + memory growth (``ALLOW_MEMORY_GROWTH``) is especially tricky, see `Wasm design issue #1271 <https://github.com/WebAssembly/design/issues/1271>`_. This currently causes JS accessing the Wasm memory to be slow - but this will likely only be noticeable if the JS does large amounts of memory reads and writes (Wasm runs at full speed, so moving work over can fix this). This also requires that your JS be aware that the HEAP* views may need to be updated - JS code embedded with ``--js-library`` etc will automatically be transformed to use the ``GROWABLE_HEAP_*`` helper functions where ``HEAP*`` are used, but external code that uses ``Module.HEAP*`` directly may encounter problems with views being smaller than memory.
148148

149-
Also note that when compiling code that uses pthreads, an additional JavaScript file ``NAME.worker.js`` is generated alongside the output .js file (where ``NAME`` is the basename of the main file being emitted). That file must be deployed with the rest of the generated code files. By default, ``NAME.worker.js`` will be loaded relative to the main HTML page URL. If it is desirable to load the file from a different location e.g. in a CDN environment, then one can define the ``Module.locateFile(filename)`` function in the main HTML ``Module`` object to return the URL of the target location of the ``NAME.worker.js`` entry point. If this function is not defined in ``Module``, then the default location relative to the main HTML file is used.
150-
151149
.. _Allocator_performance:
152150

153151
Allocator performance

src/closure-externs/minimal_runtime_worker_externs.js

Lines changed: 0 additions & 13 deletions
This file was deleted.

src/library.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ addToLibrary({
8383
#if ASSERTIONS || EXIT_RUNTIME
8484
'$keepRuntimeAlive',
8585
#endif
86+
#if PTHREADS
87+
'$exitOnMainThread',
88+
#endif
8689
#if PTHREADS_DEBUG
8790
'$runtimeKeepaliveCounter',
8891
#endif

src/library_pthread.js

Lines changed: 44 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ var LibraryPThread = {
7575
) {
7676
t = _pthread_self();
7777
}
78-
return 'w:' + (Module['workerID'] || 0) + ',t:' + ptrToString(t) + ': ';
78+
return 'w:' + workerID + ',t:' + ptrToString(t) + ': ';
7979
}
8080

8181
// Prefix all err()/dbg() messages with the calling thread ID.
@@ -127,16 +127,6 @@ var LibraryPThread = {
127127
},
128128

129129
initWorker() {
130-
#if MAYBE_CLOSURE_COMPILER
131-
// worker.js is not compiled together with us, and must access certain
132-
// things.
133-
PThread['receiveObjectTransfer'] = PThread.receiveObjectTransfer;
134-
PThread['threadInitTLS'] = PThread.threadInitTLS;
135-
#if !MINIMAL_RUNTIME
136-
PThread['setExitStatus'] = PThread.setExitStatus;
137-
#endif
138-
#endif
139-
140130
#if isSymbolNeeded('$noExitRuntime')
141131
// The default behaviour for pthreads is always to exit once they return
142132
// from their entry point (or call pthread_exit). If we set noExitRuntime
@@ -373,16 +363,6 @@ var LibraryPThread = {
373363
worker.postMessage({
374364
'cmd': 'load',
375365
'handlers': handlers,
376-
// If the application main .js file was loaded from a Blob, then it is not possible
377-
// to access the URL of the current script that could be passed to a Web Worker so that
378-
// it could load up the same file. In that case, developer must either deliver the Blob
379-
// object in Module['mainScriptUrlOrBlob'], or a URL to it, so that pthread Workers can
380-
// independently load up the same main application file.
381-
'urlOrBlob': Module['mainScriptUrlOrBlob']
382-
#if !EXPORT_ES6
383-
|| _scriptName
384-
#endif
385-
,
386366
#if WASM2JS
387367
// the polyfill WebAssembly.Memory instance has function properties,
388368
// which will fail in postMessage, so just send a custom object with the
@@ -440,34 +420,50 @@ var LibraryPThread = {
440420
// Creates a new web Worker and places it in the unused worker pool to wait for its use.
441421
allocateUnusedWorker() {
442422
var worker;
443-
#if MINIMAL_RUNTIME
444-
var pthreadMainJs = Module['worker'] || './{{{ PTHREAD_WORKER_FILE }}}';
445-
#else
423+
var workerOptions = {
424+
#if EXPORT_ES6
425+
'type': 'module',
426+
#endif
427+
#if ENVIRONMENT_MAY_BE_NODE
428+
// This is the way that we signal to the node worker that it is hosting
429+
// a pthread.
430+
'workerData': 'em-pthread',
431+
#endif
432+
#if ENVIRONMENT_MAY_BE_WEB
433+
// This is the way that we signal to the Web Worker that it is hosting
434+
// a pthread.
435+
'name': 'em-pthread',
436+
#endif
437+
};
446438
#if EXPORT_ES6 && USE_ES6_IMPORT_META
447-
// If we're using module output and there's no explicit override, use bundler-friendly pattern.
448-
if (!Module['locateFile']) {
439+
// If we're using module output, use bundler-friendly pattern.
449440
#if PTHREADS_DEBUG
450-
dbg('Allocating a new web worker from ' + new URL('{{{ PTHREAD_WORKER_FILE }}}', import.meta.url));
441+
dbg('Allocating a new web worker from ' + import.meta.url);
451442
#endif
452443
#if TRUSTED_TYPES
453-
// Use Trusted Types compatible wrappers.
454-
if (typeof trustedTypes != 'undefined' && trustedTypes.createPolicy) {
455-
var p = trustedTypes.createPolicy(
456-
'emscripten#workerPolicy1',
457-
{
458-
createScriptURL: (ignored) => new URL('{{{ PTHREAD_WORKER_FILE }}}', import.meta.url);
459-
}
460-
);
461-
worker = new Worker(p.createScriptURL('ignored'), {type: 'module'});
462-
} else
463-
#endif
464-
worker = new Worker(new URL('{{{ PTHREAD_WORKER_FILE }}}', import.meta.url), {type: 'module'});
465-
} else {
444+
// Use Trusted Types compatible wrappers.
445+
if (typeof trustedTypes != 'undefined' && trustedTypes.createPolicy) {
446+
var p = trustedTypes.createPolicy(
447+
'emscripten#workerPolicy1',
448+
{
449+
createScriptURL: (ignored) => new URL(import.meta.url);
450+
}
451+
);
452+
worker = new Worker(p.createScriptURL('ignored'), workerOptions);
453+
} else
466454
#endif
467-
// Allow HTML module to configure the location where the 'worker.js' file will be loaded from,
468-
// via Module.locateFile() function. If not specified, then the default URL 'worker.js' relative
469-
// to the main html file is loaded.
470-
var pthreadMainJs = locateFile('{{{ PTHREAD_WORKER_FILE }}}');
455+
worker = new Worker(new URL(import.meta.url), workerOptions);
456+
#else
457+
var pthreadMainJs = _scriptName;
458+
#if expectToReceiveOnModule('mainScriptUrlOrBlob')
459+
// We can't use makeModuleReceiveWithVar here since we want to also
460+
// call URL.createObjectURL on the mainScriptUrlOrBlob.
461+
if (Module['mainScriptUrlOrBlob']) {
462+
pthreadMainJs = Module['mainScriptUrlOrBlob'];
463+
if (typeof pthreadMainJs != 'string') {
464+
pthreadMainJs = URL.createObjectURL(pthreadMainJs);
465+
}
466+
}
471467
#endif
472468
#if PTHREADS_DEBUG
473469
dbg(`Allocating a new web worker from ${pthreadMainJs}`);
@@ -476,14 +472,12 @@ var LibraryPThread = {
476472
// Use Trusted Types compatible wrappers.
477473
if (typeof trustedTypes != 'undefined' && trustedTypes.createPolicy) {
478474
var p = trustedTypes.createPolicy('emscripten#workerPolicy2', { createScriptURL: (ignored) => pthreadMainJs });
479-
worker = new Worker(p.createScriptURL('ignored'){{{ EXPORT_ES6 ? ", {type: 'module'}" : '' }}});
475+
worker = new Worker(p.createScriptURL('ignored'), workerOptions);
480476
} else
481477
#endif
482-
worker = new Worker(pthreadMainJs{{{ EXPORT_ES6 ? ", {type: 'module'}" : '' }}});
483-
#if EXPORT_ES6 && USE_ES6_IMPORT_META
484-
}
485-
#endif
486-
PThread.unusedWorkers.push(worker);
478+
worker = new Worker(pthreadMainJs, workerOptions);
479+
#endif // EXPORT_ES6 && USE_ES6_IMPORT_META
480+
PThread.unusedWorkers.push(worker);
487481
},
488482

489483
getNewWorker() {

src/library_sdl.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2907,14 +2907,14 @@ var LibrarySDL = {
29072907
return SDL.setGetVolume(SDL.music, volume);
29082908
},
29092909

2910-
Mix_LoadMUS_RW__docs: '/** @param {number} a1 */',
2911-
Mix_LoadMUS_RW: 'Mix_LoadWAV_RW',
2910+
Mix_LoadMUS_RW__deps: ['Mix_LoadWAV_RW'],
2911+
Mix_LoadMUS_RW: (filename) => _Mix_LoadWAV_RW(filename, 0),
29122912

29132913
Mix_LoadMUS__deps: ['Mix_LoadMUS_RW', 'SDL_RWFromFile', 'SDL_FreeRW'],
29142914
Mix_LoadMUS__proxy: 'sync',
29152915
Mix_LoadMUS: (filename) => {
29162916
var rwops = _SDL_RWFromFile(filename, 0);
2917-
var result = _Mix_LoadMUS_RW(rwops, 0);
2917+
var result = _Mix_LoadMUS_RW(rwops);
29182918
_SDL_FreeRW(rwops);
29192919
return result;
29202920
},

src/parseTools.mjs

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -888,21 +888,6 @@ function buildStringArray(array) {
888888
}
889889
}
890890

891-
// Generates access to a JS imports scope variable in pthreads worker.js. In MODULARIZE mode these flow into the imports object for the Module.
892-
// In non-MODULARIZE mode, we can directly access the variables in global scope.
893-
function makeAsmImportsAccessInPthread(variable) {
894-
if (!MINIMAL_RUNTIME) {
895-
// Regular runtime uses the name "Module" for both imports and exports.
896-
return `Module['${variable}']`;
897-
}
898-
if (MODULARIZE) {
899-
// MINIMAL_RUNTIME uses 'imports' as the name for the imports object in MODULARIZE builds.
900-
return `imports['${variable}']`;
901-
}
902-
// In non-MODULARIZE builds, can access the imports from global scope.
903-
return `self.${variable}`;
904-
}
905-
906891
function _asmjsDemangle(symbol) {
907892
if (symbol.startsWith('dynCall_')) {
908893
return symbol;
@@ -1130,7 +1115,6 @@ addToCompileTimeContext({
11301115
hasExportedSymbol,
11311116
implicitSelf,
11321117
isSymbolNeeded,
1133-
makeAsmImportsAccessInPthread,
11341118
makeDynCall,
11351119
makeEval,
11361120
makeGetValue,

src/postamble.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,8 @@ function stackCheckInit() {
144144

145145
#if MAIN_MODULE && PTHREADS
146146
// Map of modules to be shared with new threads. This gets populated by the
147-
// main thread and shared with all new workers.
148-
var sharedModules = Module['sharedModules'] || [];
147+
// main thread and shared with all new workers via the initial `load` message.
148+
var sharedModules = {};
149149
#endif
150150

151151
#if MAIN_READS_PARAMS

src/postamble_minimal.js

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,6 @@ function initRuntime(wasmExports) {
8989

9090
// Initialize wasm (asynchronous)
9191

92-
var imports = {
93-
#if MINIFY_WASM_IMPORTED_MODULES
94-
'a': wasmImports,
95-
#else // MINIFY_WASM_IMPORTED_MODULES
96-
'env': wasmImports,
97-
'{{{ WASI_MODULE_NAME }}}': wasmImports,
98-
#endif // MINIFY_WASM_IMPORTED_MODULES
99-
};
100-
10192
// In non-fastcomp non-asm.js builds, grab wasm exports to outer scope
10293
// for emscripten_get_exported_function() to be able to access them.
10394
#if LibraryManager.has('library_exports.js')
@@ -112,6 +103,20 @@ var wasmModule;
112103
<<< WASM_MODULE_EXPORTS_DECLARES >>>
113104
#endif
114105

106+
#if PTHREADS
107+
function loadModule() {
108+
assignWasmImports();
109+
#endif
110+
111+
var imports = {
112+
#if MINIFY_WASM_IMPORTED_MODULES
113+
'a': wasmImports,
114+
#else // MINIFY_WASM_IMPORTED_MODULES
115+
'env': wasmImports,
116+
'{{{ WASI_MODULE_NAME }}}': wasmImports,
117+
#endif // MINIFY_WASM_IMPORTED_MODULES
118+
};
119+
115120
#if MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION
116121
// https://caniuse.com/#feat=wasm and https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiateStreaming
117122
// Firefox 52 added Wasm support, but only Firefox 58 added instantiateStreaming.
@@ -252,3 +257,13 @@ WebAssembly.instantiate(Module['wasm'], imports).then((output) => {
252257
}
253258
#endif // ASSERTIONS || WASM == 2
254259
);
260+
261+
#if PTHREADS
262+
}
263+
264+
if (!ENVIRONMENT_IS_PTHREAD) {
265+
// When running in a pthread we delay module loading untill we have
266+
// received the module via postMessage
267+
loadModule();
268+
}
269+
#endif

0 commit comments

Comments
 (0)