Skip to content

Commit d666646

Browse files
committed
Make USE_PTHREADS work with MODULARIZE mode.
pthread-main.js was touching globals directly, which aren't exposed as globals when the Module object is encapsulated into a function constructor with MODULARIZE and EXPORT_NAME options. Modified pthread-main.js and library_pthread.js (and a little modification to shell.js and jsifier.js) to use accessors for a couple things, and to inject the initialized stuff for pthread workers via options on the Module object instead of directly. emcc.py is changed to support checking document.currentScript.src at load time, outside the module wrapper constructor, and passes the value in to the module via Module.currentScriptUrl; this is overriding the document.currentScript.src check in shell.js. Shouldn't break existing non-modular code, but does require that pthread-main.js be updated when recompiling (as should happen normally). Fixes #5009
1 parent e6e11c0 commit d666646

File tree

6 files changed

+76
-31
lines changed

6 files changed

+76
-31
lines changed

emcc.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1986,14 +1986,20 @@ def do_minify(): # minifies the code. this is also when we do certain optimizati
19861986
src = open(final).read()
19871987
final = final + '.modular.js'
19881988
f = open(final, 'w')
1989-
f.write('var ' + shared.Settings.EXPORT_NAME + ' = function(' + shared.Settings.EXPORT_NAME + ') {\n')
1989+
f.write('var ' + shared.Settings.EXPORT_NAME + '\n')
1990+
f.write('(function() {\n')
1991+
f.write(' var scriptSrc;\n')
1992+
f.write(' if (typeof document !== \'undefined\' && document.currentScript) scriptSrc = document.currentScript.src;\n')
1993+
f.write(shared.Settings.EXPORT_NAME + ' = function(' + shared.Settings.EXPORT_NAME + ') {\n')
19901994
f.write(' ' + shared.Settings.EXPORT_NAME + ' = ' + shared.Settings.EXPORT_NAME + ' || {};\n')
1995+
f.write(' if (!' + shared.Settings.EXPORT_NAME + '.currentScriptUrl) ' + shared.Settings.EXPORT_NAME + '.currentScriptUrl = scriptSrc;\n')
19911996
f.write(' var Module = ' + shared.Settings.EXPORT_NAME + ';\n') # included code may refer to Module (e.g. from file packager), so alias it
19921997
f.write('\n')
19931998
f.write(src)
19941999
f.write('\n')
19952000
f.write(' return ' + shared.Settings.EXPORT_NAME + ';\n')
19962001
f.write('};\n')
2002+
f.write('})();\n');
19972003
f.close()
19982004
src = None
19992005

src/jsifier.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,11 @@ function JSify(data, functionsOnly) {
347347
if (!BUILD_AS_SHARED_LIB && !SIDE_MODULE) {
348348
if (USE_PTHREADS) {
349349
print('var tempDoublePtr;\n');
350-
print('if (!ENVIRONMENT_IS_PTHREAD) tempDoublePtr = Runtime.alignMemory(allocate(12, "i8", ALLOC_STATIC), 8);\n');
350+
print('if (ENVIRONMENT_IS_PTHREAD) {\n');
351+
print(' tempDoublePtr = Module[\'tempDoublePtr\'];\n');
352+
print('} else {\n');
353+
print(' tempDoublePtr = Runtime.alignMemory(allocate(12, "i8", ALLOC_STATIC), 8);\n');
354+
print('}\n');
351355
} else {
352356
print('var tempDoublePtr = ' + makeStaticAlloc(8) + '\n');
353357
}

src/library_pthread.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
var LibraryPThread = {
2-
$PThread__postset: 'if (!ENVIRONMENT_IS_PTHREAD) PThread.initMainThreadBlock();',
2+
$PThread__postset: 'if (!ENVIRONMENT_IS_PTHREAD) { PThread.initMainThreadBlock(); } else { Module.PThread=PThread; }',
33
$PThread__deps: ['$PROCINFO', '_register_pthread_ptr', 'emscripten_main_thread_process_queued_calls'],
44
$PThread: {
55
MAIN_THREAD_ID: 1, // A special constant that identifies the main JS thread ID.
@@ -325,7 +325,9 @@ var LibraryPThread = {
325325
STATICTOP: STATICTOP,
326326
DYNAMIC_BASE: DYNAMIC_BASE,
327327
DYNAMICTOP_PTR: DYNAMICTOP_PTR,
328-
PthreadWorkerInit: PthreadWorkerInit
328+
PthreadWorkerInit: PthreadWorkerInit,
329+
modularize: {{{ MODULARIZE }}},
330+
moduleExportName: '{{{ EXPORT_NAME }}}'
329331
});
330332
PThread.unusedWorkerPool.push(worker);
331333
}
@@ -342,6 +344,25 @@ var LibraryPThread = {
342344
while(performance.now() < t) {
343345
;
344346
}
347+
},
348+
349+
registerPthreadPtr: function(pthreadPtr, isMainBrowserThread, isMainRuntimeThread) {
350+
__register_pthread_ptr(pthreadPtr, isMainBrowserThread, isMainRuntimeThread);
351+
},
352+
353+
wakeAllThreads: function() {
354+
var tb = _pthread_self();
355+
_emscripten_futex_wake(tb + {{{ C_STRUCTS.pthread.threadStatus }}}, {{{ cDefine('INT_MAX') }}});
356+
},
357+
358+
runThreadFunc: function(funcPtr, arg) {
359+
// HACK: Some code in the wild has instead signatures of form 'void *ThreadMain()', which seems to be ok in native code.
360+
// To emulate supporting both in test suites, use the following form. This is brittle!
361+
if (typeof asm['dynCall_ii'] !== 'undefined') {
362+
result = asm.dynCall_ii(funcPtr, arg); // pthread entry points are always of signature 'void *ThreadMain(void *arg)'
363+
} else {
364+
result = asm.dynCall_i(funcPtr); // as a hack, try signature 'i' as fallback.
365+
}
345366
}
346367
},
347368

src/preamble.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1150,7 +1150,11 @@ if (typeof SharedArrayBuffer === 'undefined' || typeof Atomics === 'undefined')
11501150

11511151
#if USE_PTHREADS
11521152
if (typeof SharedArrayBuffer !== 'undefined') {
1153-
if (!ENVIRONMENT_IS_PTHREAD) buffer = new SharedArrayBuffer(TOTAL_MEMORY);
1153+
if (ENVIRONMENT_IS_PTHREAD) {
1154+
buffer = Module.buffer;
1155+
} else {
1156+
buffer = new SharedArrayBuffer(TOTAL_MEMORY);
1157+
}
11541158
// Currently SharedArrayBuffer does not have a slice() operation, so polyfill it in.
11551159
// Adapted from https://github.com/ttaubert/node-arraybuffer-slice, (c) 2014 Tim Taubert <[email protected]>
11561160
// arraybuffer-slice may be freely distributed under the MIT license.

src/pthread-main.js

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
var threadInfoStruct = 0; // Info area for this thread in Emscripten HEAP (shared). If zero, this worker is not currently hosting an executing pthread.
77
var selfThreadId = 0; // The ID of this thread. 0 if not hosting a pthread.
88
var parentThreadId = 0; // The ID of the parent pthread that launched this thread.
9-
var tempDoublePtr = 0; // A temporary memory area for global float and double marshalling operations.
109

1110
// Thread-local: Each thread has its own allocated stack space.
1211
var STACK_BASE = 0;
@@ -25,7 +24,9 @@ var ENVIRONMENT_IS_PTHREAD = true;
2524

2625
// Cannot use console.log or console.error in a web worker, since that would risk a browser deadlock! https://bugzilla.mozilla.org/show_bug.cgi?id=1049091
2726
// Therefore implement custom logging facility for threads running in a worker, which queue the messages to main thread to print.
28-
var Module = {};
27+
var Module = {
28+
ENVIRONMENT: 'PTHREAD'
29+
};
2930

3031
function threadPrint() {
3132
var text = Array.prototype.slice.call(arguments).join(' ');
@@ -43,27 +44,39 @@ Module['print'] = threadPrint;
4344
Module['printErr'] = threadPrintErr;
4445
this.alert = threadAlert;
4546

47+
// If modularized, we can't reuse the module's assert() function.
48+
function assert(condition, text) {
49+
if (!condition) {
50+
abort('Assertion failed: ' + text);
51+
}
52+
}
53+
4654
this.onmessage = function(e) {
4755
if (e.data.cmd === 'load') { // Preload command that is called once per worker to parse and load the Emscripten code.
4856
// Initialize the thread-local field(s):
49-
tempDoublePtr = e.data.tempDoublePtr;
57+
Module['tempDoublePtr'] = e.data.tempDoublePtr;
5058

5159
// Initialize the global "process"-wide fields:
52-
buffer = e.data.buffer;
60+
Module['buffer'] = buffer = e.data.buffer;
5361
Module['TOTAL_MEMORY'] = TOTAL_MEMORY = e.data.TOTAL_MEMORY;
5462
STATICTOP = e.data.STATICTOP;
5563
DYNAMIC_BASE = e.data.DYNAMIC_BASE;
5664
DYNAMICTOP_PTR = e.data.DYNAMICTOP_PTR;
5765

58-
PthreadWorkerInit = e.data.PthreadWorkerInit;
66+
Module['pthreadWorkerInit'] = e.data.PthreadWorkerInit;
5967
importScripts(e.data.url);
68+
if (e.data.modularize) {
69+
// Feed input options into the modularized constructor...
70+
// 'this' is the Worker, which is also global scope.
71+
Module = new this[e.data.moduleExportName](Module);
72+
}
6073
if (typeof FS !== 'undefined') FS.createStandardStreams();
6174
postMessage({ cmd: 'loaded' });
6275
} else if (e.data.cmd === 'objectTransfer') {
63-
PThread.receiveObjectTransfer(e.data);
76+
Module.PThread.receiveObjectTransfer(e.data);
6477
} else if (e.data.cmd === 'run') { // This worker was idle, and now should start executing its pthread entry point.
6578
threadInfoStruct = e.data.threadInfoStruct;
66-
__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.
79+
Module.PThread.registerPthreadPtr(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.
6780
assert(threadInfoStruct);
6881
selfThreadId = e.data.selfThreadId;
6982
parentThreadId = e.data.parentThreadId;
@@ -75,39 +88,33 @@ this.onmessage = function(e) {
7588
STACK_MAX = STACK_BASE + e.data.stackSize;
7689
assert(STACK_BASE != 0);
7790
assert(STACK_MAX > STACK_BASE);
78-
Runtime.establishStackSpace(e.data.stackBase, e.data.stackBase + e.data.stackSize);
91+
Module.Runtime.establishStackSpace(e.data.stackBase, e.data.stackBase + e.data.stackSize);
7992
var result = 0;
8093

81-
PThread.receiveObjectTransfer(e.data);
94+
Module.PThread.receiveObjectTransfer(e.data);
8295

83-
PThread.setThreadStatus(_pthread_self(), 1/*EM_THREAD_STATUS_RUNNING*/);
96+
Module.PThread.setThreadStatus(threadInfoStruct, 1/*EM_THREAD_STATUS_RUNNING*/);
8497

8598
try {
86-
// HACK: Some code in the wild has instead signatures of form 'void *ThreadMain()', which seems to be ok in native code.
87-
// To emulate supporting both in test suites, use the following form. This is brittle!
88-
if (typeof asm['dynCall_ii'] !== 'undefined') {
89-
result = asm.dynCall_ii(e.data.start_routine, e.data.arg); // pthread entry points are always of signature 'void *ThreadMain(void *arg)'
90-
} else {
91-
result = asm.dynCall_i(e.data.start_routine); // as a hack, try signature 'i' as fallback.
92-
}
99+
Module.PThread.runThreadFunc(e.data.start_routine, e.data.arg);
93100
} catch(e) {
94101
if (e === 'Canceled!') {
95-
PThread.threadCancel();
102+
Module.PThread.threadCancel();
96103
return;
97104
} else {
98-
Atomics.store(HEAPU32, (threadInfoStruct + 4 /*{{{ C_STRUCTS.pthread.threadExitCode }}}*/ ) >> 2, -2 /*A custom entry specific to Emscripten denoting that the thread crashed.*/);
99-
Atomics.store(HEAPU32, (threadInfoStruct + 0 /*{{{ C_STRUCTS.pthread.threadStatus }}}*/ ) >> 2, 1); // Mark the thread as no longer running.
100-
_emscripten_futex_wake(threadInfoStruct + 0 /*{{{ C_STRUCTS.pthread.threadStatus }}}*/, 0x7FFFFFFF/*INT_MAX*/); // wake all threads
105+
Atomics.store(Module.HEAPU32, (threadInfoStruct + 4 /*{{{ C_STRUCTS.pthread.threadExitCode }}}*/ ) >> 2, -2 /*A custom entry specific to Emscripten denoting that the thread crashed.*/);
106+
Atomics.store(Module.HEAPU32, (threadInfoStruct + 0 /*{{{ C_STRUCTS.pthread.threadStatus }}}*/ ) >> 2, 1); // Mark the thread as no longer running.
107+
Module.PThread.wakeAllThreads();
101108
throw e;
102109
}
103110
}
104111
// The thread might have finished without calling pthread_exit(). If so, then perform the exit operation ourselves.
105112
// (This is a no-op if explicit pthread_exit() had been called prior.)
106-
if (!Module['noExitRuntime']) PThread.threadExit(result);
113+
if (!Module['noExitRuntime']) Module.PThread.threadExit(result);
107114
else console.log('pthread noExitRuntime: not quitting.');
108115
} else if (e.data.cmd === 'cancel') { // Main thread is asking for a pthread_cancel() on this thread.
109-
if (threadInfoStruct && PThread.thisThreadCancelState == 0/*PTHREAD_CANCEL_ENABLE*/) {
110-
PThread.threadCancel();
116+
if (threadInfoStruct && Module.PThread.thisThreadCancelState == 0/*PTHREAD_CANCEL_ENABLE*/) {
117+
Module.PThread.threadCancel();
111118
}
112119
} else {
113120
Module['printErr']('pthread-main.js received unknown command ' + e.data.cmd);

src/shell.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ if (Module['ENVIRONMENT']) {
5353
ENVIRONMENT_IS_NODE = true;
5454
} else if (Module['ENVIRONMENT'] === 'SHELL') {
5555
ENVIRONMENT_IS_SHELL = true;
56+
} else if (Module['ENVIRONMENT'] === 'PTHREAD') {
57+
ENVIRONMENT_IS_WORKER = true;
58+
ENVIRONMENT_IS_PTHREAD = true;
5659
} else {
5760
throw new Error('The provided Module[\'ENVIRONMENT\'] value is not valid. It must be one of: WEB|WORKER|NODE|SHELL.');
5861
}
@@ -66,9 +69,9 @@ if (Module['ENVIRONMENT']) {
6669
#if USE_PTHREADS
6770
var ENVIRONMENT_IS_PTHREAD;
6871
if (!ENVIRONMENT_IS_PTHREAD) ENVIRONMENT_IS_PTHREAD = false; // ENVIRONMENT_IS_PTHREAD=true will have been preset in pthread-main.js. Make it false in the main runtime thread.
69-
var PthreadWorkerInit; // Collects together variables that are needed at initialization time for the web workers that host pthreads.
72+
var PthreadWorkerInit = Module['pthreadWorkerInit'] || undefined; // Collects together variables that are needed at initialization time for the web workers that host pthreads.
7073
if (!ENVIRONMENT_IS_PTHREAD) PthreadWorkerInit = {};
71-
var currentScriptUrl = ENVIRONMENT_IS_WORKER ? undefined : document.currentScript.src;
74+
var currentScriptUrl = Module['currentScriptUrl'] || (ENVIRONMENT_IS_WORKER ? undefined : document.currentScript.src);
7275
#endif
7376

7477
if (ENVIRONMENT_IS_NODE) {

0 commit comments

Comments
 (0)