Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inline pthread worker.js file into the main output file #21701

Merged
merged 1 commit into from
Apr 16, 2024
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
1 change: 0 additions & 1 deletion .eslintrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ ignorePatterns:
- "src/closure-externs/"
- "src/embind/"
- "src/emrun_postjs.js"
- "src/worker.js"
- "src/wasm_worker.js"
- "src/audio_worklet.js"
- "src/wasm2js.js"
Expand Down
6 changes: 6 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ See docs/process.md for more on how version tagging works.
- Enable use of `::` to escape port option separator (#21710)
- In multi-threaded builds `--extern-pre-js` and `--extern-post-js` code is
now only run on the main thread, and not on each of the workers. (#21750)
- Multi-threaded builds no depend on a separate `.worker.js` file. This saves
on code size and network requests. In order to make this change go smoothly,
without breaking build systems that expect a `worker.js`, emscripten will
generate an empty `.worker.js` to give folks time to transition their
deployment scripts. In `-sSTRICT` mode, this empty file will not be
generated. (#21701)

3.1.57 - 04/10/24
-----------------
Expand Down
2 changes: 0 additions & 2 deletions site/source/docs/porting/pthreads.rst
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,6 @@ The Emscripten implementation for the pthreads API should follow the POSIX stand

- 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.

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.

sbc100 marked this conversation as resolved.
Show resolved Hide resolved
.. _Allocator_performance:

Allocator performance
Expand Down
13 changes: 0 additions & 13 deletions src/closure-externs/minimal_runtime_worker_externs.js

This file was deleted.

3 changes: 3 additions & 0 deletions src/library.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ addToLibrary({
#if ASSERTIONS || EXIT_RUNTIME
'$keepRuntimeAlive',
#endif
#if PTHREADS
'$exitOnMainThread',
#endif
#if PTHREADS_DEBUG
'$runtimeKeepaliveCounter',
#endif
Expand Down
94 changes: 44 additions & 50 deletions src/library_pthread.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ var LibraryPThread = {
) {
t = _pthread_self();
}
return 'w:' + (Module['workerID'] || 0) + ',t:' + ptrToString(t) + ': ';
return 'w:' + workerID + ',t:' + ptrToString(t) + ': ';
}

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

initWorker() {
#if MAYBE_CLOSURE_COMPILER
// worker.js is not compiled together with us, and must access certain
// things.
PThread['receiveObjectTransfer'] = PThread.receiveObjectTransfer;
PThread['threadInitTLS'] = PThread.threadInitTLS;
#if !MINIMAL_RUNTIME
PThread['setExitStatus'] = PThread.setExitStatus;
#endif
#endif

#if isSymbolNeeded('$noExitRuntime')
// The default behaviour for pthreads is always to exit once they return
// from their entry point (or call pthread_exit). If we set noExitRuntime
Expand Down Expand Up @@ -373,16 +363,6 @@ var LibraryPThread = {
worker.postMessage({
'cmd': 'load',
'handlers': handlers,
// If the application main .js file was loaded from a Blob, then it is not possible
// to access the URL of the current script that could be passed to a Web Worker so that
// it could load up the same file. In that case, developer must either deliver the Blob
// object in Module['mainScriptUrlOrBlob'], or a URL to it, so that pthread Workers can
// independently load up the same main application file.
'urlOrBlob': Module['mainScriptUrlOrBlob']
#if !EXPORT_ES6
|| _scriptName
#endif
,
#if WASM2JS
// the polyfill WebAssembly.Memory instance has function properties,
// which will fail in postMessage, so just send a custom object with the
Expand Down Expand Up @@ -440,34 +420,50 @@ var LibraryPThread = {
// Creates a new web Worker and places it in the unused worker pool to wait for its use.
allocateUnusedWorker() {
var worker;
#if MINIMAL_RUNTIME
var pthreadMainJs = Module['worker'] || './{{{ PTHREAD_WORKER_FILE }}}';
#else
var workerOptions = {
#if EXPORT_ES6
'type': 'module',
#endif
#if ENVIRONMENT_MAY_BE_NODE
// This is the way that we signal to the node worker that it is hosting
// a pthread.
'workerData': 'em-pthread',
#endif
#if ENVIRONMENT_MAY_BE_WEB
// This is the way that we signal to the Web Worker that it is hosting
// a pthread.
'name': 'em-pthread',
sbc100 marked this conversation as resolved.
Show resolved Hide resolved
#endif
};
#if EXPORT_ES6 && USE_ES6_IMPORT_META
// If we're using module output and there's no explicit override, use bundler-friendly pattern.
if (!Module['locateFile']) {
// If we're using module output, use bundler-friendly pattern.
#if PTHREADS_DEBUG
dbg('Allocating a new web worker from ' + new URL('{{{ PTHREAD_WORKER_FILE }}}', import.meta.url));
dbg('Allocating a new web worker from ' + import.meta.url);
#endif
#if TRUSTED_TYPES
// Use Trusted Types compatible wrappers.
if (typeof trustedTypes != 'undefined' && trustedTypes.createPolicy) {
var p = trustedTypes.createPolicy(
'emscripten#workerPolicy1',
{
createScriptURL: (ignored) => new URL('{{{ PTHREAD_WORKER_FILE }}}', import.meta.url);
}
);
worker = new Worker(p.createScriptURL('ignored'), {type: 'module'});
} else
#endif
worker = new Worker(new URL('{{{ PTHREAD_WORKER_FILE }}}', import.meta.url), {type: 'module'});
} else {
// Use Trusted Types compatible wrappers.
if (typeof trustedTypes != 'undefined' && trustedTypes.createPolicy) {
var p = trustedTypes.createPolicy(
'emscripten#workerPolicy1',
{
createScriptURL: (ignored) => new URL(import.meta.url);
}
);
worker = new Worker(p.createScriptURL('ignored'), workerOptions);
} else
#endif
// 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 }}}');
worker = new Worker(new URL(import.meta.url), workerOptions);
#else
var pthreadMainJs = _scriptName;
#if expectToReceiveOnModule('mainScriptUrlOrBlob')
// We can't use makeModuleReceiveWithVar here since we want to also
// call URL.createObjectURL on the mainScriptUrlOrBlob.
if (Module['mainScriptUrlOrBlob']) {
pthreadMainJs = Module['mainScriptUrlOrBlob'];
if (typeof pthreadMainJs != 'string') {
pthreadMainJs = URL.createObjectURL(pthreadMainJs);
}
}
#endif
#if PTHREADS_DEBUG
dbg(`Allocating a new web worker from ${pthreadMainJs}`);
Expand All @@ -476,14 +472,12 @@ var LibraryPThread = {
// Use Trusted Types compatible wrappers.
if (typeof trustedTypes != 'undefined' && trustedTypes.createPolicy) {
var p = trustedTypes.createPolicy('emscripten#workerPolicy2', { createScriptURL: (ignored) => pthreadMainJs });
worker = new Worker(p.createScriptURL('ignored'){{{ EXPORT_ES6 ? ", {type: 'module'}" : '' }}});
worker = new Worker(p.createScriptURL('ignored'), workerOptions);
} else
#endif
worker = new Worker(pthreadMainJs{{{ EXPORT_ES6 ? ", {type: 'module'}" : '' }}});
#if EXPORT_ES6 && USE_ES6_IMPORT_META
}
#endif
PThread.unusedWorkers.push(worker);
worker = new Worker(pthreadMainJs, workerOptions);
#endif // EXPORT_ES6 && USE_ES6_IMPORT_META
PThread.unusedWorkers.push(worker);
},

getNewWorker() {
Expand Down
6 changes: 3 additions & 3 deletions src/library_sdl.js
Original file line number Diff line number Diff line change
Expand Up @@ -2907,14 +2907,14 @@ var LibrarySDL = {
return SDL.setGetVolume(SDL.music, volume);
},

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

Mix_LoadMUS__deps: ['Mix_LoadMUS_RW', 'SDL_RWFromFile', 'SDL_FreeRW'],
Mix_LoadMUS__proxy: 'sync',
Mix_LoadMUS: (filename) => {
var rwops = _SDL_RWFromFile(filename, 0);
var result = _Mix_LoadMUS_RW(rwops, 0);
var result = _Mix_LoadMUS_RW(rwops);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is need in order to prevent a closure error when running other.test_closure_full_js_library_pthread.

building:ERROR: /tmp/emscripten_temp_2b2wdh02/hello_world.js:37694:24: WARNING - [JSC_TYPE_MISMATCH] initializing variable
found   : function(?, number): ?
required: function(number): ?
  37694|   var _Mix_LoadMUS_RW = _Mix_LoadWAV_RW;
                                 ^^^^^^^^^^^^^^^

/tmp/emscripten_temp_2b2wdh02/hello_world.js:37707:19: WARNING - [JSC_WRONG_ARGUMENT_COUNT] Function _Mix_LoadMUS_RW: called with 2 argument(s). Function requires at least 1 argument(s) and no more than 1 argument(s).
  37707|       var result = _Mix_LoadMUS_RW(rwops, 0);
                            ^^^^^^^^^^^^^^^^^^^^^^^^^

0 error(s), 2 warning(s), 67.4% typed

I have no idea at all why this change would trigger that error since it seems like a clear pre-existing error. Closure is such a mystery.

_SDL_FreeRW(rwops);
return result;
},
Expand Down
16 changes: 0 additions & 16 deletions src/parseTools.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -888,21 +888,6 @@ function buildStringArray(array) {
}
}

// 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}']`;
}
// In non-MODULARIZE builds, can access the imports from global scope.
return `self.${variable}`;
}

function _asmjsDemangle(symbol) {
if (symbol.startsWith('dynCall_')) {
return symbol;
Expand Down Expand Up @@ -1130,7 +1115,6 @@ addToCompileTimeContext({
hasExportedSymbol,
implicitSelf,
isSymbolNeeded,
makeAsmImportsAccessInPthread,
makeDynCall,
makeEval,
makeGetValue,
Expand Down
4 changes: 2 additions & 2 deletions src/postamble.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,8 @@ function stackCheckInit() {

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

#if MAIN_READS_PARAMS
Expand Down
33 changes: 24 additions & 9 deletions src/postamble_minimal.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,6 @@ function initRuntime(wasmExports) {

// Initialize wasm (asynchronous)

var imports = {
#if MINIFY_WASM_IMPORTED_MODULES
'a': wasmImports,
#else // MINIFY_WASM_IMPORTED_MODULES
'env': wasmImports,
'{{{ WASI_MODULE_NAME }}}': wasmImports,
#endif // MINIFY_WASM_IMPORTED_MODULES
};

// In non-fastcomp non-asm.js builds, grab wasm exports to outer scope
// for emscripten_get_exported_function() to be able to access them.
#if LibraryManager.has('library_exports.js')
Expand All @@ -112,6 +103,20 @@ var wasmModule;
<<< WASM_MODULE_EXPORTS_DECLARES >>>
#endif

#if PTHREADS
function loadModule() {
assignWasmImports();
#endif

var imports = {
#if MINIFY_WASM_IMPORTED_MODULES
'a': wasmImports,
#else // MINIFY_WASM_IMPORTED_MODULES
'env': wasmImports,
'{{{ WASI_MODULE_NAME }}}': wasmImports,
#endif // MINIFY_WASM_IMPORTED_MODULES
};

#if MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION
// 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 instantiateStreaming.
Expand Down Expand Up @@ -252,3 +257,13 @@ WebAssembly.instantiate(Module['wasm'], imports).then((output) => {
}
#endif // ASSERTIONS || WASM == 2
);

#if PTHREADS
}

if (!ENVIRONMENT_IS_PTHREAD) {
// When running in a pthread we delay module loading untill we have
// received the module via postMessage
loadModule();
}
#endif
51 changes: 31 additions & 20 deletions src/preamble.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
// An online HTML version (which may be of a different version of Emscripten)
// is up at http://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html

#if PTHREADS
#include "runtime_pthread.js"
#endif

#if RELOCATABLE
{{{ makeModuleReceiveWithVar('dynamicLibraries', undefined, '[]', true) }}}
#endif
Expand Down Expand Up @@ -897,11 +901,27 @@ function instantiateAsync(binary, binaryFile, imports, callback) {
}
#endif // WASM_ASYNC_COMPILATION

// Create the wasm instance.
// Receives the wasm imports, returns the exports.
function createWasm() {
function getWasmImports() {
#if PTHREADS
assignWasmImports();
#endif
#if ASYNCIFY && (ASSERTIONS || ASYNCIFY == 2)
// instrumenting imports is used in asyncify in two ways: to add assertions
// that check for proper import use, and for ASYNCIFY=2 we use them to set up
// the Promise API on the import side.
#if PTHREADS || ASYNCIFY_LAZY_LOAD_CODE
// In pthreads builds getWasmImports is called more than once but we only
// and the instrument the imports once.
if (!wasmImports.__instrumented) {
wasmImports.__instrumented = true;
Asyncify.instrumentWasmImports(wasmImports);
}
#else
Asyncify.instrumentWasmImports(wasmImports);
#endif
#endif
// prepare imports
var info = {
return {
#if MINIFY_WASM_IMPORTED_MODULES
'a': wasmImports,
#else // MINIFY_WASM_IMPORTED_MODULES
Expand All @@ -915,7 +935,13 @@ function createWasm() {
'GOT.mem': new Proxy(wasmImports, GOTHandler),
'GOT.func': new Proxy(wasmImports, GOTHandler),
#endif
};
}
}

// Create the wasm instance.
// Receives the wasm imports, returns the exports.
function createWasm() {
var info = getWasmImports();
// Load the wasm module and create an instance of using native support in the JS engine.
// handle a generated wasm instance, receiving its exports and
// performing other necessary setup
Expand Down Expand Up @@ -1047,21 +1073,6 @@ function createWasm() {
// Also pthreads and wasm workers initialize the wasm instance through this
// path.
if (Module['instantiateWasm']) {

#if USE_OFFSET_CONVERTER
#if ASSERTIONS
{{{ runIfWorkerThread("assert(Module['wasmOffsetData'], 'wasmOffsetData not found on Module object');") }}}
#endif
{{{ runIfWorkerThread("wasmOffsetConverter = resetPrototype(WasmOffsetConverter, Module['wasmOffsetData']);") }}}
#endif

#if LOAD_SOURCE_MAP
#if ASSERTIONS
{{{ runIfWorkerThread("assert(Module['wasmSourceMapData'], 'wasmSourceMapData not found on Module object');") }}}
#endif
{{{ runIfWorkerThread("wasmSourceMap = resetPrototype(WasmSourceMap, Module['wasmSourceMapData']);") }}}
#endif

try {
return Module['instantiateWasm'](info, receiveInstance);
} catch(e) {
Expand Down
Loading
Loading