Skip to content

Commit 689c6f5

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). Used in ogv.js 1.4.x for experimental multithread builds. Fixes #5009
1 parent da4f4d5 commit 689c6f5

File tree

6 files changed

+98
-58
lines changed

6 files changed

+98
-58
lines changed

emcc.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2331,14 +2331,20 @@ def modularize(final):
23312331
src = open(final).read()
23322332
final = final + '.modular.js'
23332333
f = open(final, 'w')
2334-
f.write('var ' + shared.Settings.EXPORT_NAME + ' = function(' + shared.Settings.EXPORT_NAME + ') {\n')
2334+
f.write('var ' + shared.Settings.EXPORT_NAME + '\n')
2335+
f.write('(function() {\n')
2336+
f.write(' var scriptSrc;\n')
2337+
f.write(' if (typeof document !== \'undefined\' && document.currentScript) scriptSrc = document.currentScript.src;\n')
2338+
f.write(shared.Settings.EXPORT_NAME + ' = function(' + shared.Settings.EXPORT_NAME + ') {\n')
23352339
f.write(' ' + shared.Settings.EXPORT_NAME + ' = ' + shared.Settings.EXPORT_NAME + ' || {};\n')
2340+
f.write(' if (!' + shared.Settings.EXPORT_NAME + '.currentScriptUrl) ' + shared.Settings.EXPORT_NAME + '.currentScriptUrl = scriptSrc;\n')
23362341
f.write(' var Module = ' + shared.Settings.EXPORT_NAME + ';\n') # included code may refer to Module (e.g. from file packager), so alias it
23372342
f.write('\n')
23382343
f.write(src)
23392344
f.write('\n')
23402345
f.write(' return ' + shared.Settings.EXPORT_NAME + ';\n')
23412346
f.write('};\n')
2347+
f.write('})();\n');
23422348
# Export the function if this is for Node (or similar UMD-style exporting), otherwise it is lost.
23432349
f.write('if (typeof module === "object" && module.exports) {\n')
23442350
f.write(" module['exports'] = " + shared.Settings.EXPORT_NAME + ';\n')

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: 33 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,35 @@ var LibraryPThread = {
342344
while(performance.now() < t) {
343345
;
344346
}
347+
},
348+
349+
setStackSpace: function(stackBase, stackMax) {
350+
// TODO: Emscripten runtime has these variables twice(!), once outside the asm.js module, and a second time inside the asm.js module.
351+
// Review why that is? Can those get out of sync?
352+
STACK_BASE = STACKTOP = stackBase;
353+
STACK_MAX = stackMax;
354+
assert(STACK_BASE != 0);
355+
assert(STACK_MAX > STACK_BASE);
356+
Module.Runtime.establishStackSpace(stackBase, stackMax);
357+
},
358+
359+
registerPthreadPtr: function(pthreadPtr, isMainBrowserThread, isMainRuntimeThread) {
360+
__register_pthread_ptr(pthreadPtr, isMainBrowserThread, isMainRuntimeThread);
361+
},
362+
363+
wakeAllThreads: function() {
364+
var tb = _pthread_self();
365+
_emscripten_futex_wake(tb + {{{ C_STRUCTS.pthread.threadStatus }}}, {{{ cDefine('INT_MAX') }}});
366+
},
367+
368+
runThreadFunc: function(funcPtr, arg) {
369+
// HACK: Some code in the wild has instead signatures of form 'void *ThreadMain()', which seems to be ok in native code.
370+
// To emulate supporting both in test suites, use the following form. This is brittle!
371+
if (typeof asm['dynCall_ii'] !== 'undefined') {
372+
result = asm.dynCall_ii(funcPtr, arg); // pthread entry points are always of signature 'void *ThreadMain(void *arg)'
373+
} else {
374+
result = asm.dynCall_i(funcPtr); // as a hack, try signature 'i' as fallback.
375+
}
345376
}
346377
},
347378

src/preamble.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -986,7 +986,12 @@ var STACK_BASE, STACKTOP, STACK_MAX; // stack area
986986
var DYNAMIC_BASE, DYNAMICTOP_PTR; // dynamic area handled by sbrk
987987

988988
#if USE_PTHREADS
989-
if (!ENVIRONMENT_IS_PTHREAD) { // Pthreads have already initialized these variables in src/pthread-main.js, where they were passed to the thread worker at startup time
989+
if (ENVIRONMENT_IS_PTHREAD) { // Pthreads have passed these variables in from src/pthread-main.js, where they were passed to the thread worker at startup time
990+
STATIC_BASE = Module['STATIC_BASE'] || 0;
991+
STATICTOP = Module['STATICTOP'] || 0;
992+
DYNAMIC_BASE = Module['DYNAMIC_BASE'] || 0;
993+
DYNAMICTOP_PTR = Module['DYNAMICTOP_PTR'] || 0;
994+
} else {
990995
#endif
991996
STATIC_BASE = STATICTOP = STACK_BASE = STACKTOP = STACK_MAX = DYNAMIC_BASE = DYNAMICTOP_PTR = 0;
992997
staticSealed = false;
@@ -1174,7 +1179,11 @@ if (typeof SharedArrayBuffer === 'undefined' || typeof Atomics === 'undefined')
11741179

11751180
#if USE_PTHREADS
11761181
if (typeof SharedArrayBuffer !== 'undefined') {
1177-
if (!ENVIRONMENT_IS_PTHREAD) buffer = new SharedArrayBuffer(TOTAL_MEMORY);
1182+
if (ENVIRONMENT_IS_PTHREAD) {
1183+
buffer = Module.buffer;
1184+
} else {
1185+
buffer = new SharedArrayBuffer(TOTAL_MEMORY);
1186+
}
11781187
// Currently SharedArrayBuffer does not have a slice() operation, so polyfill it in.
11791188
// Adapted from https://github.com/ttaubert/node-arraybuffer-slice, (c) 2014 Tim Taubert <[email protected]>
11801189
// arraybuffer-slice may be freely distributed under the MIT license.

src/pthread-main.js

Lines changed: 37 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,18 @@
22
// This is the entry point file that is loaded first by each Web Worker
33
// that executes pthreads on the Emscripten application.
44

5-
// Thread-local:
5+
// Thread-local, communicated via globals:
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

11-
// Thread-local: Each thread has its own allocated stack space.
12-
var STACK_BASE = 0;
13-
var STACKTOP = 0;
14-
var STACK_MAX = 0;
15-
16-
// These are system-wide memory area parameters that are set at main runtime startup in main thread, and stay constant throughout the application.
17-
var buffer; // All pthreads share the same Emscripten HEAP as SharedArrayBuffer with the main execution thread.
18-
var DYNAMICTOP_PTR = 0;
19-
var TOTAL_MEMORY = 0;
20-
var STATICTOP = 0;
21-
var staticSealed = true; // When threads are being initialized, the static memory area has been already sealed a long time ago.
22-
var DYNAMIC_BASE = 0;
23-
24-
var ENVIRONMENT_IS_PTHREAD = true;
10+
// Send the pthreads mode and other params in through Module object settings
11+
var Module = {
12+
ENVIRONMENT: 'PTHREAD'
13+
};
2514

2615
// 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
2716
// Therefore implement custom logging facility for threads running in a worker, which queue the messages to main thread to print.
28-
var Module = {};
29-
3017
function threadPrint() {
3118
var text = Array.prototype.slice.call(arguments).join(' ');
3219
console.log(text);
@@ -43,71 +30,71 @@ Module['print'] = threadPrint;
4330
Module['printErr'] = threadPrintErr;
4431
this.alert = threadAlert;
4532

33+
// If modularized, we can't reuse the module's assert() function.
34+
function assert(condition, text) {
35+
if (!condition) {
36+
abort('Assertion failed: ' + text);
37+
}
38+
}
39+
4640
this.onmessage = function(e) {
4741
if (e.data.cmd === 'load') { // Preload command that is called once per worker to parse and load the Emscripten code.
4842
// Initialize the thread-local field(s):
49-
tempDoublePtr = e.data.tempDoublePtr;
43+
Module['tempDoublePtr'] = e.data.tempDoublePtr;
5044

5145
// Initialize the global "process"-wide fields:
52-
buffer = e.data.buffer;
53-
Module['TOTAL_MEMORY'] = TOTAL_MEMORY = e.data.TOTAL_MEMORY;
54-
STATICTOP = e.data.STATICTOP;
55-
DYNAMIC_BASE = e.data.DYNAMIC_BASE;
56-
DYNAMICTOP_PTR = e.data.DYNAMICTOP_PTR;
46+
Module['buffer'] = e.data.buffer;
47+
Module['TOTAL_MEMORY'] = e.data.TOTAL_MEMORY;
48+
Module['STATICTOP'] = e.data.STATICTOP;
49+
Module['DYNAMIC_BASE'] = e.data.DYNAMIC_BASE;
50+
Module['DYNAMICTOP_PTR'] = e.data.DYNAMICTOP_PTR;
5751

58-
PthreadWorkerInit = e.data.PthreadWorkerInit;
52+
Module['pthreadWorkerInit'] = e.data.PthreadWorkerInit;
5953
importScripts(e.data.url);
54+
if (e.data.modularize) {
55+
// Feed input options into the modularized constructor...
56+
// 'this' is the Worker, which is also global scope.
57+
Module = new this[e.data.moduleExportName](Module);
58+
}
6059
if (typeof FS !== 'undefined') FS.createStandardStreams();
6160
postMessage({ cmd: 'loaded' });
6261
} else if (e.data.cmd === 'objectTransfer') {
63-
PThread.receiveObjectTransfer(e.data);
62+
Module.PThread.receiveObjectTransfer(e.data);
6463
} else if (e.data.cmd === 'run') { // This worker was idle, and now should start executing its pthread entry point.
6564
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.
65+
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.
6766
assert(threadInfoStruct);
6867
selfThreadId = e.data.selfThreadId;
6968
parentThreadId = e.data.parentThreadId;
7069
assert(selfThreadId);
7170
assert(parentThreadId);
72-
// TODO: Emscripten runtime has these variables twice(!), once outside the asm.js module, and a second time inside the asm.js module.
73-
// Review why that is? Can those get out of sync?
74-
STACK_BASE = STACKTOP = e.data.stackBase;
75-
STACK_MAX = STACK_BASE + e.data.stackSize;
76-
assert(STACK_BASE != 0);
77-
assert(STACK_MAX > STACK_BASE);
78-
Runtime.establishStackSpace(e.data.stackBase, e.data.stackBase + e.data.stackSize);
71+
Module.PThread.setStackSpace(e.data.stackBase, e.data.stackBase + e.data.stackSize);
7972
var result = 0;
8073

81-
PThread.receiveObjectTransfer(e.data);
74+
Module.PThread.receiveObjectTransfer(e.data);
8275

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

8578
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 Module['asm']['dynCall_ii'] !== 'undefined') {
89-
result = Module['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 = Module['asm'].dynCall_i(e.data.start_routine); // as a hack, try signature 'i' as fallback.
92-
}
79+
Module.PThread.runThreadFunc(e.data.start_routine, e.data.arg);
9380
} catch(e) {
9481
if (e === 'Canceled!') {
95-
PThread.threadCancel();
82+
Module.PThread.threadCancel();
9683
return;
9784
} 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
85+
Atomics.store(Module.HEAPU32, (threadInfoStruct + 4 /*{{{ C_STRUCTS.pthread.threadExitCode }}}*/ ) >> 2, -2 /*A custom entry specific to Emscripten denoting that the thread crashed.*/);
86+
Atomics.store(Module.HEAPU32, (threadInfoStruct + 0 /*{{{ C_STRUCTS.pthread.threadStatus }}}*/ ) >> 2, 1); // Mark the thread as no longer running.
87+
Module.PThread.wakeAllThreads();
10188
throw e;
10289
}
10390
}
10491
// The thread might have finished without calling pthread_exit(). If so, then perform the exit operation ourselves.
10592
// (This is a no-op if explicit pthread_exit() had been called prior.)
106-
if (!Module['noExitRuntime']) PThread.threadExit(result);
93+
if (!Module['noExitRuntime']) Module.PThread.threadExit(result);
10794
else console.log('pthread noExitRuntime: not quitting.');
10895
} 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();
96+
if (threadInfoStruct && Module.PThread.thisThreadCancelState == 0/*PTHREAD_CANCEL_ENABLE*/) {
97+
Module.PThread.threadCancel();
11198
}
11299
} else {
113100
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)