Skip to content

Commit d24e5b7

Browse files
committed
Add support for MODULARIZE with USE_PTHREADS
1 parent dcf6dff commit d24e5b7

File tree

11 files changed

+223
-73
lines changed

11 files changed

+223
-73
lines changed

emcc.py

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,6 +1081,15 @@ def check(input_file):
10811081
if shared.Settings.MODULARIZE_INSTANCE:
10821082
shared.Settings.MODULARIZE = 1
10831083

1084+
if shared.Settings.MODULARIZE:
1085+
assert not options.proxy_to_worker, '-s MODULARIZE=1 and -s MODULARIZE_INSTANCE=1 are not compatible with --proxy-to-worker (if you want to run in a worker with -s MODULARIZE=1, you likely want to do the worker side setup manually)'
1086+
1087+
if not shared.Settings.EXPORT_NAME:
1088+
# If we are building directly to .html with -s MODULARIZE=1 (but not -s MODULARIZE_INSTANCE=1), default to exporting
1089+
# Emscripten code under function EmscriptenCode() so that it does not collide with the Module object. (When not modularizing,
1090+
# or if building with MODULARIZE_INSTANCE, or if building to .js manually, there is no conflict, so default to 'Module' in those cases)
1091+
shared.Settings.EXPORT_NAME = 'EmscriptenCode' if final_suffix == '.html' and shared.Settings.MODULARIZE and not shared.Settings.MODULARIZE_INSTANCE else 'Module'
1092+
10841093
if shared.Settings.EMULATE_FUNCTION_POINTER_CASTS:
10851094
shared.Settings.ALIASING_FUNCTION_POINTERS = 0
10861095

@@ -1186,11 +1195,6 @@ def check(input_file):
11861195
exit_with_error('USE_PTHREADS=2 is not longer supported')
11871196
if shared.Settings.ALLOW_MEMORY_GROWTH:
11881197
exit_with_error('Memory growth is not yet supported with pthreads')
1189-
if shared.Settings.MODULARIZE:
1190-
# currently worker.js uses the global namespace, so it's setting of
1191-
# ENVIRONMENT_IS_PTHREAD is not picked up, in addition to all the other
1192-
# modifications it performs.
1193-
exit_with_error('MODULARIZE is not yet supported with pthreads')
11941198
# UTF8Decoder.decode doesn't work with a view of a SharedArrayBuffer
11951199
shared.Settings.TEXTDECODER = 0
11961200
options.js_libraries.append(shared.path_from_root('src', 'library_pthread.js'))
@@ -1225,6 +1229,21 @@ def check(input_file):
12251229
]
12261230

12271231
if shared.Settings.USE_PTHREADS:
1232+
if shared.Settings.MODULARIZE:
1233+
# MODULARIZE+USE_PTHREADS mode requires extra exports out to Module so that worker.js
1234+
# can access them:
1235+
1236+
# general threading variables:
1237+
shared.Settings.EXPORTED_RUNTIME_METHODS += ['PThread', 'ExitStatus']
1238+
1239+
# pthread stack setup:
1240+
shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$establishStackSpaceInJsModule']
1241+
shared.Settings.EXPORTED_FUNCTIONS += ['establishStackSpaceInJsModule']
1242+
1243+
# stack check:
1244+
if shared.Settings.STACK_OVERFLOW_CHECK:
1245+
shared.Settings.EXPORTED_RUNTIME_METHODS += ['writeStackCookie', 'checkStackCookie']
1246+
12281247
if shared.Settings.LINKABLE:
12291248
exit_with_error('-s LINKABLE=1 is not supported with -s USE_PTHREADS>0!')
12301249
if shared.Settings.SIDE_MODULE:
@@ -1960,8 +1979,9 @@ def repl(m):
19601979

19611980
if shared.Settings.USE_PTHREADS:
19621981
target_dir = os.path.dirname(os.path.abspath(target))
1963-
shutil.copyfile(shared.path_from_root('src', 'worker.js'),
1964-
os.path.join(target_dir, shared.Settings.PTHREAD_WORKER_FILE))
1982+
worker_output = os.path.join(target_dir, shared.Settings.PTHREAD_WORKER_FILE)
1983+
with open(worker_output, 'w') as f:
1984+
f.write(shared.read_and_preprocess(shared.path_from_root('src', 'worker.js'), expand_macros=True))
19651985

19661986
# Generate the fetch-worker.js script for multithreaded emscripten_fetch() support if targeting pthreads.
19671987
if shared.Settings.FETCH and shared.Settings.USE_PTHREADS:
@@ -2942,18 +2962,28 @@ def un_src(self):
29422962
"""Use this if you want to modify the script and need it to be inline."""
29432963
if self.src is None:
29442964
return
2965+
run_module = ''
2966+
if shared.Settings.MODULARIZE and not shared.Settings.MODULARIZE_INSTANCE:
2967+
run_module = 'script.onload = function() { %s(Module); };' % shared.Settings.EXPORT_NAME
2968+
29452969
self.inline = '''
29462970
var script = document.createElement('script');
29472971
script.src = "%s";
2972+
%s
29482973
document.body.appendChild(script);
2949-
''' % self.src
2974+
''' % (self.src, run_module)
29502975
self.src = None
29512976

29522977
def replacement(self):
2978+
onload = ''
2979+
if shared.Settings.MODULARIZE and not shared.Settings.MODULARIZE_INSTANCE:
2980+
assert shared.Settings.EXPORT_NAME != 'Module', 'EXPORT_NAME cannot be called "Module"!'
2981+
onload = 'onload="%s(Module)"' % shared.Settings.EXPORT_NAME
2982+
29532983
"""Returns the script tag to replace the {{{ SCRIPT }}} tag in the target"""
29542984
assert (self.src or self.inline) and not (self.src and self.inline)
29552985
if self.src:
2956-
return '<script async type="text/javascript" src="%s"></script>' % quote(self.src)
2986+
return '<script async type="text/javascript" src="%s" %s></script>' % (quote(self.src), onload)
29572987
else:
29582988
return '<script>\n%s\n</script>' % self.inline
29592989

src/library_pthread.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,12 +356,13 @@ var LibraryPThread = {
356356
// it could load up the same file. In that case, developer must either deliver the Blob
357357
// object in Module['mainScriptUrlOrBlob'], or a URL to it, so that pthread Workers can
358358
// independently load up the same main application file.
359-
urlOrBlob: Module['mainScriptUrlOrBlob'] || currentScriptUrl,
359+
urlOrBlob: Module['mainScriptUrlOrBlob'] || _scriptDir,
360360
#if WASM
361361
wasmMemory: wasmMemory,
362362
wasmModule: wasmModule,
363363
#else
364364
buffer: HEAPU8.buffer,
365+
asmJsUrlOrBlob: Module["asmJsUrlOrBlob"],
365366
#endif
366367
tempDoublePtr: tempDoublePtr,
367368
TOTAL_MEMORY: TOTAL_MEMORY,
@@ -1181,6 +1182,13 @@ var LibraryPThread = {
11811182
#endif
11821183
return func.apply(null, callArgs);
11831184
},
1185+
1186+
#if MODULARIZE
1187+
$establishStackSpaceInJsModule: function(stackBase, stackMax) {
1188+
STACK_BASE = STACKTOP = stackBase;
1189+
STACK_MAX = stackMax;
1190+
},
1191+
#endif
11841192
};
11851193

11861194
autoAddDeps(LibraryPThread, '$PThread');

src/modules.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,18 @@ function exportRuntime() {
426426
'getTempRet0',
427427
'setTempRet0',
428428
];
429+
if (MODULARIZE) {
430+
// In MODULARIZE=1 mode, the following functions need to be exported out to Module for worker.js to access.
431+
if (STACK_OVERFLOW_CHECK) {
432+
runtimeElements.push('writeStackCookie');
433+
runtimeElements.push('checkStackCookie');
434+
runtimeElements.push('abortStackOverflow');
435+
}
436+
if (USE_PTHREADS) {
437+
runtimeElements.push('PThread');
438+
runtimeElements.push('ExitStatus');
439+
}
440+
}
429441
if (SUPPORT_BASE64_EMBEDDING) {
430442
runtimeElements.push('intArrayFromBase64');
431443
runtimeElements.push('tryParseAsDataURI');

src/parseTools.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1477,6 +1477,34 @@ function makeStaticString(string) {
14771477
return '(stringToUTF8("' + string + '", ' + ptr + ', ' + len + '), ' + ptr + ')';
14781478
}
14791479

1480+
// Generates access to module exports variable in pthreads worker.js
1481+
function makeAsmExportAccessInPthread(variable) {
1482+
if (MODULARIZE) {
1483+
return "Module['" + variable + "']" // 'Module' is defined in worker.js local scope, so not EXPORT_NAME in this case.
1484+
} else {
1485+
return EXPORT_NAME + "['" + variable + "']"
1486+
}
1487+
}
1488+
1489+
// Generates access to a global scope variable in pthreads worker.js
1490+
function makeAsmGlobalAccessInPthread(variable) {
1491+
if (MODULARIZE) {
1492+
return "Module['" + variable + "']" // 'Module' is defined in worker.js local scope, so not EXPORT_NAME in this case.
1493+
} else {
1494+
return variable
1495+
}
1496+
}
1497+
1498+
// Generates access to both global scope variable and exported Module variable, e.g. "Module['foo'] = foo" or just plain "foo" depending on if we are MODULARIZEing.
1499+
// Used the be able to initialize both variables at the same time.
1500+
function makeAsmExportAndGlobalAccessInPthread(variable) {
1501+
if (MODULARIZE) {
1502+
return "Module['" + variable + "'] = " + variable // 'Module' is defined in worker.js local scope, so not EXPORT_NAME in this case.
1503+
} else {
1504+
return variable
1505+
}
1506+
}
1507+
14801508
// Some things, like the dynamic and stack bases, will be computed later and
14811509
// applied. Return them as {{{ STR }}} for that replacing later.
14821510

src/preamble.js

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -389,14 +389,6 @@ assert(DYNAMIC_BASE % 16 === 0, 'heap must start aligned');
389389
}
390390
#endif
391391

392-
#if USE_PTHREADS
393-
if (ENVIRONMENT_IS_PTHREAD) {
394-
#if SEPARATE_ASM != 0
395-
importScripts('{{{ SEPARATE_ASM }}}'); // load the separated-out asm.js
396-
#endif
397-
}
398-
#endif
399-
400392
#if EMTERPRETIFY
401393
function abortStackOverflowEmterpreter() {
402394
abort("Emterpreter stack overflow! Decrease the recursion level or increase EMT_STACK_MAX in tools/emterpretify.py (current value " + EMT_STACK_MAX + ").");
@@ -1059,7 +1051,13 @@ function createWasm(env) {
10591051

10601052
Module['asm'] = function(global, env, providedBuffer) {
10611053
// memory was already allocated (so js could use the buffer)
1062-
env['memory'] = wasmMemory;
1054+
env['memory'] = wasmMemory
1055+
#if MODULARIZE && USE_PTHREADS
1056+
// Pthreads assign wasmMemory in their worker startup. In MODULARIZE mode, they cannot assign inside the
1057+
// Module scope, so lookup via Module as well.
1058+
|| Module['wasmMemory']
1059+
#endif
1060+
;
10631061
// import table
10641062
env['table'] = wasmTable = new WebAssembly.Table({
10651063
'initial': {{{ getQuoted('WASM_TABLE_SIZE') }}},

src/settings.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -936,7 +936,10 @@ var EXPLICIT_ZEXT = 0;
936936

937937
// Global variable to export the module as for environments without a
938938
// standardized module loading system (e.g. the browser and SM shell).
939-
var EXPORT_NAME = 'Module';
939+
// Default EXPORT_NAME is 'Module', but if building directly to .html
940+
// with -s MODULARIZE=1 but not with -s MODULARIZE_INSTANCE=1, then default EXPORT_NAME
941+
// is 'EmscriptenCode' to not conflict with the default provided Module object.
942+
var EXPORT_NAME = '';
940943

941944
// When set to 0, we do not emit eval() and new Function(), which disables some functionality
942945
// (causing runtime errors if attempted to be used), but allows the emitted code to be

src/shell.js

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,34 @@ if (Module['ENVIRONMENT']) {
8181
// 2) We could be the application main() thread proxied to worker. (with Emscripten -s PROXY_TO_WORKER=1) (ENVIRONMENT_IS_WORKER == true, ENVIRONMENT_IS_PTHREAD == false)
8282
// 3) We could be an application pthread running in a worker. (ENVIRONMENT_IS_WORKER == true and ENVIRONMENT_IS_PTHREAD == true)
8383
#if USE_PTHREADS
84-
var ENVIRONMENT_IS_PTHREAD;
85-
if (!ENVIRONMENT_IS_PTHREAD) ENVIRONMENT_IS_PTHREAD = false; // ENVIRONMENT_IS_PTHREAD=true will have been preset in worker.js. Make it false in the main runtime thread.
86-
var PthreadWorkerInit; // Collects together variables that are needed at initialization time for the web workers that host pthreads.
87-
if (!ENVIRONMENT_IS_PTHREAD) PthreadWorkerInit = {};
88-
var currentScriptUrl = (typeof document !== 'undefined' && document.currentScript) ? document.currentScript.src : undefined;
84+
85+
if (typeof ENVIRONMENT_IS_PTHREAD === 'undefined') {
86+
// ENVIRONMENT_IS_PTHREAD=true will have been preset in worker.js. Make it false in the main runtime thread.
87+
// 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)
88+
ENVIRONMENT_IS_PTHREAD = false;
89+
var PthreadWorkerInit = {}; // Collects together variables that are needed at initialization time for the web workers that host pthreads.
90+
}
91+
#if MODULARIZE
92+
else {
93+
// Grab imports from the pthread to local scope.
94+
var buffer = {{{EXPORT_NAME}}}.buffer;
95+
var tempDoublePtr = {{{EXPORT_NAME}}}.tempDoublePtr;
96+
var TOTAL_MEMORY = {{{EXPORT_NAME}}}.TOTAL_MEMORY;
97+
var STATICTOP = {{{EXPORT_NAME}}}.STATICTOP;
98+
var DYNAMIC_BASE = {{{EXPORT_NAME}}}.DYNAMIC_BASE;
99+
var DYNAMICTOP_PTR = {{{EXPORT_NAME}}}.DYNAMICTOP_PTR;
100+
var PthreadWorkerInit = {{{EXPORT_NAME}}}.PthreadWorkerInit;
101+
// Note that not all runtime fields are imported above. Values for STACK_BASE, STACKTOP and STACK_MAX are not yet known at worker.js load time.
102+
// These will be filled in at pthread startup time (the 'run' message for a pthread - pthread start establishes the stack frame)
103+
}
104+
#endif
105+
106+
#if !MODULARIZE
107+
// 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
108+
// before the page load. In non-MODULARIZE modes generate it here.
109+
var _scriptDir = (typeof document !== 'undefined' && document.currentScript) ? document.currentScript.src : undefined;
110+
#endif
111+
89112
#endif // USE_PTHREADS
90113

91114
// `/` should be present at the end if `scriptDirectory` is not empty

0 commit comments

Comments
 (0)