Skip to content

Commit a6e89c5

Browse files
committed
Add support for MODULARIZE with USE_PTHREADS
1 parent 3187922 commit a6e89c5

File tree

11 files changed

+196
-61
lines changed

11 files changed

+196
-61
lines changed

emcc.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1199,11 +1199,6 @@ def check(input_file):
11991199
exit_with_error('USE_PTHREADS=2 is not longer supported')
12001200
if shared.Settings.ALLOW_MEMORY_GROWTH:
12011201
exit_with_error('Memory growth is not yet supported with pthreads')
1202-
if shared.Settings.MODULARIZE:
1203-
# currently worker.js uses the global namespace, so it's setting of
1204-
# ENVIRONMENT_IS_PTHREAD is not picked up, in addition to all the other
1205-
# modifications it performs.
1206-
exit_with_error('MODULARIZE is not yet supported with pthreads')
12071202
# UTF8Decoder.decode doesn't work with a view of a SharedArrayBuffer
12081203
shared.Settings.TEXTDECODER = 0
12091204
options.js_libraries.append(shared.path_from_root('src', 'library_pthread.js'))
@@ -1230,7 +1225,20 @@ def check(input_file):
12301225
'FS_unlink',
12311226
'getMemory',
12321227
'addRunDependency',
1233-
'removeRunDependency',
1228+
'removeRunDependency'
1229+
]
1230+
1231+
if shared.Settings.STACK_OVERFLOW_CHECK:
1232+
shared.Settings.EXPORTED_RUNTIME_METHODS += [
1233+
'writeStackCookie',
1234+
'checkStackCookie'
1235+
]
1236+
1237+
if shared.Settings.USE_PTHREADS:
1238+
shared.Settings.EXPORTED_RUNTIME_METHODS += [
1239+
'PThread',
1240+
'ExitStatus',
1241+
'establishStackSpaceInModule'
12341242
]
12351243

12361244
if shared.Settings.USE_PTHREADS:
@@ -1935,7 +1943,7 @@ def repl(m):
19351943

19361944
if shared.Settings.USE_PTHREADS:
19371945
target_dir = os.path.dirname(os.path.abspath(target))
1938-
shutil.copyfile(shared.path_from_root('src', 'worker.js'),
1946+
shared.run_c_preprocessor_on_file(shared.path_from_root('src', 'worker.js'),
19391947
os.path.join(target_dir, shared.Settings.PTHREAD_WORKER_FILE))
19401948

19411949
# Generate the fetch-worker.js script for multithreaded emscripten_fetch() support if targeting pthreads.
@@ -2464,7 +2472,7 @@ def emit_js_source_maps(target, js_transform_tempfiles):
24642472
def separate_asm_js(final, asm_target):
24652473
"""Separate out the asm.js code, if asked. Or, if necessary for another option"""
24662474
logger.debug('separating asm')
2467-
shared.check_call([shared.PYTHON, shared.path_from_root('tools', 'separate_asm.py'), final, asm_target, final])
2475+
shared.check_call([shared.PYTHON, shared.path_from_root('tools', 'separate_asm.py'), final, asm_target, final, shared.Settings.ASM_MODULE_NAME])
24682476

24692477
# extra only-my-code logic
24702478
if shared.Settings.ONLY_MY_CODE:

src/library_pthread.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,7 @@ var LibraryPThread = {
397397
wasmModule: Module['wasmModule'],
398398
#else
399399
buffer: HEAPU8.buffer,
400+
asmJsUrlOrBlob: Module["asmJsUrlOrBlob"],
400401
#endif
401402
tempDoublePtr: tempDoublePtr,
402403
TOTAL_MEMORY: TOTAL_MEMORY,
@@ -1177,3 +1178,8 @@ var LibraryPThread = {
11771178

11781179
autoAddDeps(LibraryPThread, '$PThread');
11791180
mergeInto(LibraryManager.library, LibraryPThread);
1181+
1182+
if (USE_PTHREADS) {
1183+
DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.push('emscripten_futex_wake');
1184+
DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.push('emscripten_futex_wait');
1185+
}

src/modules.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,18 @@ function exportRuntime() {
417417
'print',
418418
'printErr',
419419
];
420+
if (STACK_OVERFLOW_CHECK) {
421+
runtimeElements.push('writeStackCookie');
422+
runtimeElements.push('checkStackCookie');
423+
runtimeElements.push('abortStackOverflow');
424+
}
425+
if (USE_PTHREADS) {
426+
runtimeElements.push('PThread');
427+
runtimeElements.push('ExitStatus');
428+
if (MODULARIZE) {
429+
runtimeElements.push('establishStackSpaceInModule');
430+
}
431+
}
420432
if (SUPPORT_BASE64_EMBEDDING) {
421433
runtimeElements.push('intArrayFromBase64');
422434
runtimeElements.push('tryParseAsDataURI');

src/postamble.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,9 @@ if (!ENVIRONMENT_IS_PTHREAD) // EXIT_RUNTIME=0 only applies to default behavior
464464

465465
#if USE_PTHREADS
466466
if (!ENVIRONMENT_IS_PTHREAD) run();
467+
468+
Module._emscripten_futex_wake = _emscripten_futex_wake;
469+
467470
#else
468471
run();
469472
#endif

src/preamble.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -921,9 +921,6 @@ if (!ENVIRONMENT_IS_PTHREAD) { // Pthreads have already initialized these variab
921921
#if USE_PTHREADS
922922
if (ENVIRONMENT_IS_PTHREAD) {
923923
staticSealed = true; // The static memory area has been initialized already in the main thread, pthreads skip this.
924-
#if SEPARATE_ASM != 0
925-
importScripts('{{{ SEPARATE_ASM }}}'); // load the separated-out asm.js
926-
#endif
927924
}
928925
#endif
929926

@@ -948,6 +945,13 @@ function abortStackOverflow(allocSize) {
948945
}
949946
#endif
950947

948+
#if USE_PTHREADS && MODULARIZE
949+
function establishStackSpaceInModule(stackBase, stackMax) {
950+
STACK_BASE = STACKTOP = stackBase;
951+
STACK_MAX = stackMax;
952+
}
953+
#endif
954+
951955
#if EMTERPRETIFY
952956
function abortStackOverflowEmterpreter() {
953957
abort("Emterpreter stack overflow! Decrease the recursion level or increase EMT_STACK_MAX in tools/emterpretify.py (current value " + EMT_STACK_MAX + ").");

src/settings.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -829,6 +829,12 @@ var MODULARIZE = 0;
829829
// (since you arean't creating the instance yourself).
830830
var MODULARIZE_INSTANCE = 0;
831831

832+
// If we separate out asm.js with the --separate-asm option,
833+
// this is the name of the variable where the generated asm.js
834+
// Module is assigned to. This name can either be a property
835+
// of Module, or a freestanding variable name, like "var asmJsModule".
836+
var ASM_MODULE_NAME = 'Module["asm"]';
837+
832838
// Export using an ES6 Module export rather than a UMD export. MODULARIZE must
833839
// be enabled for ES6 exports.
834840
var EXPORT_ES6 = 0;

src/shell.js

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,26 @@ 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 pthread-main.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+
} else {
91+
var buffer = {{{EXPORT_NAME}}}.buffer;
92+
var tempDoublePtr = {{{EXPORT_NAME}}}.tempDoublePtr;
93+
var TOTAL_MEMORY = {{{EXPORT_NAME}}}.TOTAL_MEMORY;
94+
var STATICTOP = {{{EXPORT_NAME}}}.STATICTOP;
95+
var DYNAMIC_BASE = {{{EXPORT_NAME}}}.DYNAMIC_BASE;
96+
var DYNAMICTOP_PTR = {{{EXPORT_NAME}}}.DYNAMICTOP_PTR;
97+
var PthreadWorkerInit = {{{EXPORT_NAME}}}.PthreadWorkerInit;
98+
var STACK_BASE = {{{EXPORT_NAME}}}.STACK_BASE;
99+
var STACKTOP = {{{EXPORT_NAME}}}.STACKTOP;
100+
var STACK_MAX = {{{EXPORT_NAME}}}.STACK_MAX;
101+
}
102+
103+
var currentScriptUrl = typeof _scriptDir !== 'undefined' ? _scriptDir : ((typeof document !== 'undefined' && document.currentScript) ? document.currentScript.src : undefined);
89104
#endif // USE_PTHREADS
90105

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

src/worker.js

Lines changed: 71 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ var __performance_now_clock_drift = 0;
3838
// Therefore implement custom logging facility for threads running in a worker, which queue the messages to main thread to print.
3939
var Module = {};
4040

41+
function assert(condition, text) {
42+
if (!condition) abort('Assertion failed: ' + text);
43+
}
44+
4145
// When error objects propagate from Web Worker to main thread, they lose helpful call stack and thread ID information, so print out errors early here,
4246
// before that happens.
4347
this.addEventListener('error', function(e) {
@@ -65,7 +69,7 @@ out = threadPrint;
6569
err = threadPrintErr;
6670
this.alert = threadAlert;
6771

68-
// #if WASM
72+
#if WASM
6973
Module['instantiateWasm'] = function(info, receiveInstance) {
7074
// Instantiate from the module posted from the main thread.
7175
// We can just use sync instantiation in the worker.
@@ -75,71 +79,101 @@ Module['instantiateWasm'] = function(info, receiveInstance) {
7579
receiveInstance(instance); // The second 'module' parameter is intentionally null here, we don't need to keep a ref to the Module object from here.
7680
return instance.exports;
7781
}
78-
//#endif
82+
#endif
7983

8084
this.onmessage = function(e) {
8185
try {
8286
if (e.data.cmd === 'load') { // Preload command that is called once per worker to parse and load the Emscripten code.
8387
// Initialize the thread-local field(s):
84-
tempDoublePtr = e.data.tempDoublePtr;
88+
Module['tempDoublePtr'] = e.data.tempDoublePtr;
8589

8690
// Initialize the global "process"-wide fields:
87-
Module['TOTAL_MEMORY'] = TOTAL_MEMORY = e.data.TOTAL_MEMORY;
88-
STATICTOP = e.data.STATICTOP;
89-
DYNAMIC_BASE = e.data.DYNAMIC_BASE;
90-
DYNAMICTOP_PTR = e.data.DYNAMICTOP_PTR;
91-
92-
93-
//#if WASM
94-
if (e.data.wasmModule) {
95-
// Module and memory were sent from main thread
96-
Module['wasmModule'] = e.data.wasmModule;
97-
Module['wasmMemory'] = e.data.wasmMemory;
98-
buffer = Module['wasmMemory'].buffer;
91+
TOTAL_MEMORY = Module['TOTAL_MEMORY'] = TOTAL_MEMORY = e.data.TOTAL_MEMORY;
92+
STATICTOP = Module['STATICTOP'] = e.data.STATICTOP;
93+
DYNAMIC_BASE = Module['DYNAMIC_BASE'] = e.data.DYNAMIC_BASE;
94+
DYNAMICTOP_PTR = Module['DYNAMICTOP_PTR'] = e.data.DYNAMICTOP_PTR;
95+
96+
#if WASM
97+
// The Wasm module will have import fields for STACKTOP and STACK_MAX. At 'load' stage of Worker startup, we are just
98+
// spawning this Web Worker to act as a host for future created pthreads, i.e. we do not have a pthread to start up here yet.
99+
// (A single Worker can also host multiple pthreads throughout its lifetime, shutting down a pthread will not shut down its hosting Worker,
100+
// but the Worker is reused for later spawned pthreads). The 'run' stage below will actually start running a pthread.
101+
// The stack space for a pthread is allocated and deallocated when a pthread is actually run, not yet at Worker 'load' stage.
102+
// However, the WebAssembly module we are loading up here has import fields for STACKTOP and STACK_MAX, which it needs to get filled in
103+
// immediately at Wasm Module instantiation time. The values of these will not get used until pthread is actually running some code, so
104+
// we'll proceed to set up temporary invalid values for these fields for import purposes. Then whenever a pthread is launched at 'run' stage
105+
// below, these values are rewritten to establish proper stack area for the particular pthread.
106+
Module['STACK_MAX'] = Module['STACKTOP'] = 0x7FFFFFFF;
107+
108+
// Module and memory were sent from main thread
109+
Module['wasmModule'] = e.data.wasmModule;
110+
Module['wasmMemory'] = e.data.wasmMemory;
111+
buffer = Module['buffer'] = Module['wasmMemory'].buffer;
112+
#else
113+
buffer = Module['buffer'] = e.data.buffer;
114+
115+
#if SEPARATE_ASM != 0
116+
// load the separated-out asm.js
117+
e.data.asmJsUrlOrBlob = e.data.asmJsUrlOrBlob || '{{{ SEPARATE_ASM }}}';
118+
if (typeof e.data.asmJsUrlOrBlob === 'string') {
119+
importScripts(e.data.asmJsUrlOrBlob);
99120
} else {
100-
//#else
101-
buffer = e.data.buffer;
121+
var objectUrl = URL.createObjectURL(e.data.asmJsUrlOrBlob);
122+
importScripts(objectUrl);
123+
URL.revokeObjectURL(objectUrl);
102124
}
103-
//#endif
125+
#endif
126+
127+
#endif
104128

105-
PthreadWorkerInit = e.data.PthreadWorkerInit;
129+
Module['PthreadWorkerInit'] = e.data.PthreadWorkerInit;
106130
if (typeof e.data.urlOrBlob === 'string') {
107131
importScripts(e.data.urlOrBlob);
108132
} else {
109133
var objectUrl = URL.createObjectURL(e.data.urlOrBlob);
110134
importScripts(objectUrl);
111135
URL.revokeObjectURL(objectUrl);
112136
}
113-
//#if !ASMFS
137+
138+
#if MODULARIZE
139+
Module = {{{EXPORT_NAME}}}(Module);
140+
PThread = Module.PThread;
141+
HEAPU32 = Module.HEAPU32;
142+
#endif
143+
144+
#if !ASMFS
114145
if (typeof FS !== 'undefined' && typeof FS.createStandardStreams === 'function') FS.createStandardStreams();
115-
//#endif
146+
#endif
116147
postMessage({ cmd: 'loaded' });
117148
} else if (e.data.cmd === 'objectTransfer') {
118149
PThread.receiveObjectTransfer(e.data);
119150
} else if (e.data.cmd === 'run') { // This worker was idle, and now should start executing its pthread entry point.
120151
__performance_now_clock_drift = performance.now() - e.data.time; // Sync up to the clock of the main thread.
121152
threadInfoStruct = e.data.threadInfoStruct;
122-
__register_pthread_ptr(threadInfoStruct, /*isMainBrowserThread=*/0, /*isMainRuntimeThread=*/0); // Pass the thread address inside the asm.js scope to store it for fast access that avoids the need for a FFI out.
153+
Module.__register_pthread_ptr(threadInfoStruct, /*isMainBrowserThread=*/0, /*isMainRuntimeThread=*/0); // Pass the thread address inside the asm.js scope to store it for fast access that avoids the need for a FFI out.
123154
assert(threadInfoStruct);
124155
selfThreadId = e.data.selfThreadId;
125156
parentThreadId = e.data.parentThreadId;
126157
assert(selfThreadId);
127158
assert(parentThreadId);
128159
// TODO: Emscripten runtime has these variables twice(!), once outside the asm.js module, and a second time inside the asm.js module.
129160
// Review why that is? Can those get out of sync?
130-
STACK_BASE = STACKTOP = e.data.stackBase;
131-
STACK_MAX = STACK_BASE + e.data.stackSize;
161+
STACK_BASE = STACKTOP = Module['STACK_BASE'] = Module['STACKTOP'] = e.data.stackBase;
162+
STACK_MAX = Module['STACK_MAX'] = STACK_BASE + e.data.stackSize;
132163
assert(STACK_BASE != 0);
133164
assert(STACK_MAX > STACK_BASE);
134165
Module['establishStackSpace'](e.data.stackBase, e.data.stackBase + e.data.stackSize);
135-
var result = 0;
136-
//#if STACK_OVERFLOW_CHECK
137-
if (typeof writeStackCookie === 'function') writeStackCookie();
138-
//#endif
166+
#if MODULARIZE
167+
Module['establishStackSpaceInModule'](e.data.stackBase, e.data.stackBase + e.data.stackSize);
168+
#endif
169+
#if STACK_OVERFLOW_CHECK
170+
Module.writeStackCookie();
171+
#endif
139172

140173
PThread.receiveObjectTransfer(e.data);
141-
PThread.setThreadStatus(_pthread_self(), 1/*EM_THREAD_STATUS_RUNNING*/);
174+
PThread.setThreadStatus(Module._pthread_self(), 1/*EM_THREAD_STATUS_RUNNING*/);
142175

176+
var result = 0;
143177
try {
144178
// pthread entry points are always of signature 'void *ThreadMain(void *arg)'
145179
// Native codebases sometimes spawn threads with other thread entry point signatures,
@@ -150,9 +184,9 @@ this.onmessage = function(e) {
150184
// flag -s EMULATE_FUNCTION_POINTER_CASTS=1 to add in emulation for this x86 ABI extension.
151185
result = Module['dynCall_ii'](e.data.start_routine, e.data.arg);
152186

153-
//#if STACK_OVERFLOW_CHECK
154-
if (typeof checkStackCookie === 'function') checkStackCookie();
155-
//#endif
187+
#if STACK_OVERFLOW_CHECK
188+
Module.checkStackCookie();
189+
#endif
156190

157191
} catch(e) {
158192
if (e === 'Canceled!') {
@@ -161,10 +195,10 @@ this.onmessage = function(e) {
161195
} else if (e === 'SimulateInfiniteLoop') {
162196
return;
163197
} else {
164-
Atomics.store(HEAPU32, (threadInfoStruct + 4 /*{{{ C_STRUCTS.pthread.threadExitCode }}}*/ ) >> 2, (e instanceof ExitStatus) ? e.status : -2 /*A custom entry specific to Emscripten denoting that the thread crashed.*/);
165-
Atomics.store(HEAPU32, (threadInfoStruct + 0 /*{{{ C_STRUCTS.pthread.threadStatus }}}*/ ) >> 2, 1); // Mark the thread as no longer running.
166-
_emscripten_futex_wake(threadInfoStruct + 0 /*{{{ C_STRUCTS.pthread.threadStatus }}}*/, 0x7FFFFFFF/*INT_MAX*/); // Wake all threads waiting on this thread to finish.
167-
if (!(e instanceof ExitStatus)) throw e;
198+
Atomics.store(HEAPU32, (threadInfoStruct + 4 /*C_STRUCTS.pthread.threadExitCode*/ ) >> 2, (e instanceof Module.ExitStatus) ? e.status : -2 /*A custom entry specific to Emscripten denoting that the thread crashed.*/);
199+
Atomics.store(HEAPU32, (threadInfoStruct + 0 /*C_STRUCTS.pthread.threadStatus*/ ) >> 2, 1); // Mark the thread as no longer running.
200+
Module._emscripten_futex_wake(threadInfoStruct + 0 /*C_STRUCTS.pthread.threadStatus*/, 0x7FFFFFFF/*INT_MAX*/); // Wake all threads waiting on this thread to finish.
201+
if (!(e instanceof Module.ExitStatus)) throw e;
168202
}
169203
}
170204
// The thread might have finished without calling pthread_exit(). If so, then perform the exit operation ourselves.
@@ -178,7 +212,7 @@ this.onmessage = function(e) {
178212
// no-op
179213
} else if (e.data.cmd === 'processThreadQueue') {
180214
if (threadInfoStruct) { // If this thread is actually running?
181-
_emscripten_current_thread_process_queued_calls();
215+
Module['_emscripten_current_thread_process_queued_calls']();
182216
}
183217
} else {
184218
err('worker.js received unknown command ' + e.data.cmd);

tools/preprocessor.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,12 @@ set = function() {}
132132

133133
var settings_file = arguments_[0];
134134
var shell_file = arguments_[1];
135+
var process_macros = arguments_.length >= 3 && arguments_[2];
135136

136137
load(settings_file);
137138
load('parseTools.js');
138139

139140
var from_html = read(shell_file);
140-
var to_html = preprocess(from_html);
141+
var to_html = process_macros ? processMacros(preprocess(from_html)) : preprocess(from_html);
141142

142143
print(to_html);

tools/separate_asm.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,19 @@
1818
infile = sys.argv[1]
1919
asmfile = sys.argv[2]
2020
otherfile = sys.argv[3]
21+
asm_module_name = sys.argv[4]
22+
if asm_module_name.startswith('var ') or asm_module_name.startswith('let '):
23+
asm_module_name_without_var = asm_module_name[4:]
24+
else:
25+
asm_module_name_without_var = asm_module_name
26+
2127

2228
everything = open(infile).read()
2329
module = asm_module.AsmModule(infile).asm_js
2430

2531
module = module[module.find('=')+1:] # strip the initial "var asm =" bit, leave just the raw module as a function
2632
if 'var Module' in everything:
27-
everything = everything.replace(module, 'Module["asm"]')
33+
everything = everything.replace(module, asm_module_name_without_var)
2834
else:
2935
# closure compiler removes |var Module|, we need to find the closured name
3036
# seek a pattern like (e.ENVIRONMENT), which is in the shell.js if-cascade for the ENVIRONMENT override
@@ -37,7 +43,7 @@
3743
everything = everything.replace(module, closured_name + '["asm"]')
3844

3945
o = open(asmfile, 'w')
40-
o.write('Module["asm"] = ')
46+
o.write(asm_module_name + ' = ')
4147
o.write(module)
4248
o.write(';')
4349
o.close()

0 commit comments

Comments
 (0)