Skip to content

Commit 9c7603d

Browse files
committed
Move modularization code into js compiler
Prior to this change the mudularization was done as a post-processing step in python. This complicated things since acorn and closure passes would only see the inner code and not the whole module. One concrete benefit is that we can now use `await` in the body of the factory function since closure no longer sees it as top level (which it isn't). Fixes: #23158
1 parent 08aecce commit 9c7603d

19 files changed

+159
-229
lines changed

src/closure-externs/closure-externs.js

+3-15
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
// Special placeholder for `import.meta` and `await import`.
1515
var EMSCRIPTEN$IMPORT$META;
1616
var EMSCRIPTEN$AWAIT$IMPORT;
17+
var EMSCRIPTEN$EXPORT$DEFAULT;
1718

1819
// Don't minify createRequire
1920
var createRequire;
@@ -160,9 +161,10 @@ var wakaUnknownBefore;
160161
// Module loaders externs, for AMD etc.
161162

162163
/**
164+
* @param {Object} deps
163165
* @param {Function} wrapper
164166
*/
165-
var define = function (wrapper) {};
167+
var define = function (deps, wrapper) {};
166168

167169
/**
168170
* @type {Worker}
@@ -231,20 +233,6 @@ var sampleRate;
231233
*/
232234
var id;
233235

234-
/**
235-
* Used in MODULARIZE mode as the name of the incoming module argument.
236-
* This is generated outside of the code we pass to closure so from closure's
237-
* POV this is "extern".
238-
*/
239-
var moduleArg;
240-
241-
/**
242-
* Used in MODULARIZE mode.
243-
* We need to access this after the code we pass to closure so from closure's
244-
* POV this is "extern".
245-
*/
246-
var moduleRtn;
247-
248236
/**
249237
* This was removed from upstream closure compiler in
250238
* https://github.com/google/closure-compiler/commit/f83322c1b.

src/closure-externs/modularize-externs.js

-7
This file was deleted.

src/parseTools.mjs

+31-3
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,17 @@ export function processMacros(text, filename) {
4242
// Simple #if/else/endif preprocessing for a file. Checks if the
4343
// ident checked is true in our global.
4444
// Also handles #include x.js (similar to C #include <file>)
45-
export function preprocess(filename) {
45+
export function preprocess(filename, closureFriendly = true) {
4646
let text = read(filename);
47-
if (EXPORT_ES6 && USE_ES6_IMPORT_META) {
47+
if (closureFriendly && EXPORT_ES6 && USE_ES6_IMPORT_META) {
4848
// `eval`, Terser and Closure don't support module syntax; to allow it,
4949
// we need to temporarily replace `import.meta` and `await import` usages
5050
// with placeholders during preprocess phase, and back after all the other ops.
5151
// See also: `phase_final_emitting` in emcc.py.
5252
text = text
5353
.replace(/\bimport\.meta\b/g, 'EMSCRIPTEN$IMPORT$META')
54-
.replace(/\bawait import\b/g, 'EMSCRIPTEN$AWAIT$IMPORT');
54+
.replace(/\bawait import\b/g, 'EMSCRIPTEN$AWAIT$IMPORT')
55+
.replace(/\bexport default\b/g, 'EMSCRIPTEN$EXPORT$DEFAULT =');
5556
}
5657
// Remove windows line endings, if any
5758
text = text.replace(/\r\n/g, '\n');
@@ -1069,6 +1070,31 @@ function ENVIRONMENT_IS_WORKER_THREAD() {
10691070
return '(' + envs.join('||') + ')';
10701071
}
10711072

1073+
function nodePthreadDetection() {
1074+
// Under node we detect that we are running in a pthread by checking the
1075+
// workerData property.
1076+
if (EXPORT_ES6) {
1077+
return "(await import('worker_threads')).workerData === 'em-pthread'";
1078+
} else {
1079+
return "require('worker_threads').workerData === 'em-pthread'";
1080+
}
1081+
}
1082+
1083+
function declareInstanceExports() {
1084+
const allExports = Array.from(EXPORTED_FUNCTIONS.keys()).concat(
1085+
Array.from(EXPORTED_RUNTIME_METHODS.keys()),
1086+
);
1087+
const mangledExports = allExports.map((e) => `__exp_${e}`);
1088+
const mangledExportsAs = allExports.map((e) => `__exp_${e} as ${e}`);
1089+
// Declare a top level var for each export so that code in the init function
1090+
// can assign to it and update the live module bindings.
1091+
if (allExports.length == 0) return '';
1092+
let rtn = 'var ' + mangledExports.join(', ') + ';\n';
1093+
// Export the functions with their original name.
1094+
rtn += 'export {' + mangledExportsAs.join(', ') + '};\n';
1095+
return rtn;
1096+
}
1097+
10721098
addToCompileTimeContext({
10731099
ATEXITS,
10741100
ATINITS,
@@ -1134,4 +1160,6 @@ addToCompileTimeContext({
11341160
storeException,
11351161
to64,
11361162
toIndexType,
1163+
nodePthreadDetection,
1164+
declareInstanceExports,
11371165
});

src/postamble_modularize.js

+67-12
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,6 @@
66

77
#if WASM_ASYNC_COMPILATION
88

9-
#if USE_READY_PROMISE
10-
moduleRtn = readyPromise;
11-
#else
12-
moduleRtn = {};
13-
#endif
14-
15-
#else // WASM_ASYNC_COMPILATION
16-
17-
moduleRtn = Module;
18-
19-
#endif // WASM_ASYNC_COMPILATION
20-
219
#if ASSERTIONS
2210
// Assertion for attempting to access module properties on the incoming
2311
// moduleArg. In the past we used this object as the prototype of the module
@@ -35,3 +23,70 @@ for (const prop of Object.keys(Module)) {
3523
}
3624
}
3725
#endif
26+
27+
#if USE_READY_PROMISE
28+
return readyPromise;
29+
#else
30+
return {};
31+
#endif
32+
#else // WASM_ASYNC_COMPILATION
33+
return Module;
34+
#endif // WASM_ASYNC_COMPILATION
35+
}; // End factory function
36+
37+
#if ASSERTIONS && MODULARIZE != 'instance'
38+
(() => {
39+
// Create a small, never-async wrapper around {{{ EXPORT_NAME }}} which
40+
// checks for callers incorrectly using it with `new`.
41+
var real_{{{ EXPORT_NAME }}} = {{{ EXPORT_NAME }}};
42+
{{{ EXPORT_NAME }}} = function(arg) {
43+
if (new.target) throw new Error("{{{ EXPORT_NAME }}}() should not be called with `new {{{ EXPORT_NAME }}}()`");
44+
return real_{{{ EXPORT_NAME }}}(arg);
45+
}
46+
})();
47+
#endif
48+
49+
// Export using a UMD style export, or ES6 exports if selected
50+
#if EXPORT_ES6
51+
#if MODULARIZE == 'instance'
52+
{{{ declareInstanceExports() }}}
53+
#else
54+
export default {{{ EXPORT_NAME }}};
55+
#endif
56+
#else
57+
if (typeof exports === 'object' && typeof module === 'object') {
58+
module.exports = {{{ EXPORT_NAME }}};
59+
// This default export looks redundant, but it allows TS to import this
60+
// commonjs style module.
61+
module.exports.default = {{{ EXPORT_NAME }}};
62+
} else if (typeof define === 'function' && define['amd']) {
63+
define([], () => {{{ EXPORT_NAME }}});
64+
}
65+
#endif
66+
67+
68+
#if PTHREADS
69+
70+
// Create code for detecting if we are running in a pthread.
71+
// Normally this detection is done when the module is itself run but
72+
// when running in MODULARIZE mode we need use this to know if we should
73+
// run the module constructor on startup (true only for pthreads).
74+
#if ENVIRONMENT_MAY_BE_WEB || ENVIRONMENT_MAY_BE_WORKER
75+
var isPthread = globalThis.self?.name?.startsWith('em-pthread');
76+
#if ENVIRONMENT_MAY_BE_NODE
77+
// In order to support both web and node we also need to detect node here.
78+
var isNode = typeof globalThis.process?.versions?.node == 'string';
79+
if (isNode) isPthread = {{{ nodePthreadDetection() }}};
80+
#endif
81+
#elif ENVIRONMENT_MAY_BE_NODE
82+
var isPthread = {{{ nodePthreadDetection() }}};
83+
#endif ENVIRONMENT_MAY_BE_WEB || ENVIRONMENT_MAY_BE_WORKER
84+
85+
// When running as a pthread, construct a new instance on startup
86+
#if MODULARIZE == 'instance'
87+
isPthread && init();
88+
#else
89+
isPthread && {{{ EXPORT_NAME }}}();
90+
#endif
91+
92+
#endif // PTHREADS

src/preamble_modularize.js

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#if !MINIMAL_RUNTIME || PTHREADS
2+
#if EXPORT_ES6 && USE_ES6_IMPORT_META
3+
var _scriptName = import.meta.url;
4+
#else
5+
var _scriptName = typeof document != 'undefined' ? document.currentScript?.src : undefined;
6+
#if ENVIRONMENT_MAY_BE_NODE
7+
if (typeof __filename != 'undefined') _scriptName = _scriptName || __filename;
8+
#endif
9+
#endif
10+
#endif
11+
12+
#if MODULARIZE == 'instance'
13+
export default {{{ asyncIf(WASM_ASYNC_COMPILATION || (EXPORT_ES6 && ENVIRONMENT_MAY_BE_NODE)) }}}function init(moduleArg = {}) {
14+
#else
15+
var {{{ EXPORT_NAME }}} = {{{ asyncIf(WASM_ASYNC_COMPILATION || (EXPORT_ES6 && ENVIRONMENT_MAY_BE_NODE)) }}}function(moduleArg = {}) {
16+
#endif
17+
18+
var Module = moduleArg;

src/shell.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
// before the code. Then that object will be used in the code, and you
2222
// can continue to use Module afterwards as well.
2323
#if MODULARIZE
24-
var Module = moduleArg;
24+
#include "preamble_modularize.js"
2525
#elif USE_CLOSURE_COMPILER
2626
/** @type{Object} */
2727
var Module;

src/shell_minimal.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66

77
#if MODULARIZE
8-
var Module = moduleArg;
8+
#include "preamble_modularize.js"
99
#elif USE_CLOSURE_COMPILER
1010
/** @type{Object} */
1111
var Module;

test/code_size/hello_webgl2_wasm.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"a.html": 454,
33
"a.html.gz": 328,
4-
"a.js": 4538,
5-
"a.js.gz": 2320,
4+
"a.js": 6293,
5+
"a.js.gz": 3087,
66
"a.wasm": 10206,
77
"a.wasm.gz": 6663,
8-
"total": 15198,
9-
"total_gz": 9311
8+
"total": 16953,
9+
"total_gz": 10078
1010
}
+4-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"a.html": 346,
33
"a.html.gz": 262,
4-
"a.js": 22202,
5-
"a.js.gz": 11604,
6-
"total": 22548,
7-
"total_gz": 11866
4+
"a.js": 24118,
5+
"a.js.gz": 12472,
6+
"total": 24464,
7+
"total_gz": 12734
88
}

test/code_size/hello_webgl_wasm.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"a.html": 454,
33
"a.html.gz": 328,
4-
"a.js": 4076,
5-
"a.js.gz": 2163,
4+
"a.js": 5799,
5+
"a.js.gz": 2911,
66
"a.wasm": 10206,
77
"a.wasm.gz": 6663,
8-
"total": 14736,
9-
"total_gz": 9154
8+
"total": 16459,
9+
"total_gz": 9902
1010
}
+4-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"a.html": 346,
33
"a.html.gz": 262,
4-
"a.js": 21728,
5-
"a.js.gz": 11435,
6-
"total": 22074,
7-
"total_gz": 11697
4+
"a.js": 23615,
5+
"a.js.gz": 12296,
6+
"total": 23961,
7+
"total_gz": 12558
88
}

test/modularize_post_js.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55

66
#if PTHREADS
77
// Avoid instantiating the module on pthreads.
8-
if (!isPthread)
8+
#if EXPORT_ES6
9+
const isMainThread = (await import('worker_threads')).isMainThread;
10+
#else
11+
const { isMainThread } = require('worker_threads');
12+
#endif
13+
if (isMainThread)
914
#endif
1015
{{{ EXPORT_NAME }}}();
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1541
1+
1520
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3210
1+
3112

tools/acorn-optimizer.mjs

+1-4
Original file line numberDiff line numberDiff line change
@@ -889,10 +889,7 @@ function emitDCEGraph(ast) {
889889
// Scoping must balance out.
890890
assert(specialScopes === 0);
891891
// We must have found the info we need.
892-
assert(
893-
foundWasmImportsAssign,
894-
'could not find the assignment to "wasmImports". perhaps --pre-js or --post-js code moved it out of the global scope? (things like that should be done after emcc runs, as they do not need to be run through the optimizer which is the special thing about --pre-js/--post-js code)',
895-
);
892+
assert(foundWasmImportsAssign, 'could not find the assignment to "wasmImports"');
896893
// Read exports that were declared in extraInfo
897894
if (extraInfo) {
898895
for (const exp of extraInfo.exports) {

tools/building.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,14 @@ def closure_compiler(filename, advanced=True, extra_closure_args=None):
554554
CLOSURE_EXTERNS = [path_from_root('src/closure-externs/closure-externs.js')]
555555

556556
if settings.MODULARIZE:
557-
CLOSURE_EXTERNS += [path_from_root('src/closure-externs/modularize-externs.js')]
557+
temp = shared.get_temp_files().get('.js', prefix='emcc_closure_externs_').name
558+
utils.write_file(temp, f'''
559+
/**
560+
* @suppress {{duplicate}}
561+
*/
562+
var {settings.EXPORT_NAME};
563+
''')
564+
CLOSURE_EXTERNS += [temp]
558565

559566
if settings.USE_WEBGPU:
560567
CLOSURE_EXTERNS += [path_from_root('src/closure-externs/webgpu-externs.js')]

tools/emscripten.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -899,8 +899,7 @@ def can_use_await():
899899
# function.
900900
# However, because closure does not see this (it runs only on the inner code),
901901
# it sees this as a top-level-await, which it does not yet support.
902-
# FIXME(https://github.com/emscripten-core/emscripten/issues/23158)
903-
return settings.MODULARIZE and not settings.USE_CLOSURE_COMPILER
902+
return settings.MODULARIZE
904903

905904

906905
def make_export_wrappers(function_exports):

0 commit comments

Comments
 (0)