Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/src/modules.mjs
6161 views
1
/**
2
* @license
3
* Copyright 2011 The Emscripten Authors
4
* SPDX-License-Identifier: MIT
5
*/
6
7
import * as os from 'node:os';
8
import * as fs from 'node:fs';
9
import * as path from 'node:path';
10
import {fileURLToPath} from 'node:url';
11
import assert from 'node:assert';
12
13
import {
14
debugLog,
15
isDecorator,
16
isJsOnlySymbol,
17
error,
18
readFile,
19
pushCurrentFile,
20
popCurrentFile,
21
addToCompileTimeContext,
22
runInMacroContext,
23
mergeInto,
24
localFile,
25
timer,
26
} from './utility.mjs';
27
import {preprocess, processMacros} from './parseTools.mjs';
28
29
// Various namespace-like modules
30
31
// List of symbols that were added from the library.
32
export const librarySymbols = [];
33
// Map of library symbols which are aliases for native symbols
34
// e.g. `wasmTable` -> `__indirect_function_table`
35
export const nativeAliases = {};
36
37
const srcDir = fileURLToPath(new URL('.', import.meta.url));
38
const systemLibdir = path.join(srcDir, 'lib');
39
40
function isBeneath(childPath, parentPath) {
41
const relativePath = path.relative(parentPath, childPath);
42
return !relativePath.startsWith('..') && !path.isAbsolute(relativePath);
43
}
44
45
function calculateLibraries() {
46
// Core system libraries (always linked against)
47
let libraries = [
48
'libint53.js',
49
'libcore.js',
50
'libsigs.js',
51
'libccall.js',
52
'libaddfunction.js',
53
'libgetvalue.js',
54
'libmath.js',
55
'libpath.js',
56
'libstrings.js',
57
'libhtml5.js',
58
'libstack_trace.js',
59
'libwasi.js',
60
'libeventloop.js',
61
'libpromise.js',
62
];
63
64
if (LINK_AS_CXX) {
65
if (DISABLE_EXCEPTION_THROWING && !WASM_EXCEPTIONS) {
66
libraries.push('libexceptions_stub.js');
67
} else {
68
libraries.push('libexceptions.js');
69
}
70
}
71
72
if (!MINIMAL_RUNTIME) {
73
libraries.push('libbrowser.js');
74
libraries.push('libwget.js');
75
}
76
77
if (!STANDALONE_WASM) {
78
libraries.push('libtime.js');
79
}
80
81
if (EMSCRIPTEN_TRACING) {
82
libraries.push('libmemoryprofiler.js');
83
}
84
85
if (SUPPORT_BASE64_EMBEDDING || ENVIRONMENT_MAY_BE_SHELL) {
86
libraries.push('libbase64.js');
87
}
88
89
if (AUTODEBUG) {
90
libraries.push('libautodebug.js');
91
}
92
93
if (!WASMFS) {
94
libraries.push('libsyscall.js');
95
}
96
97
if (MAIN_MODULE || RELOCATABLE) {
98
libraries.push('libdylink.js');
99
}
100
101
if (FILESYSTEM) {
102
libraries.push('libfs_shared.js');
103
if (WASMFS) {
104
libraries.push(
105
'libwasmfs.js',
106
'libwasmfs_js_file.js',
107
'libwasmfs_jsimpl.js',
108
'libwasmfs_fetch.js',
109
'libwasmfs_node.js',
110
'libwasmfs_opfs.js',
111
);
112
} else {
113
// Core filesystem libraries (always linked against, unless -sFILESYSTEM=0 is specified)
114
libraries.push(
115
'libfs.js',
116
'libmemfs.js',
117
'libtty.js',
118
'libpipefs.js', // ok to include it by default since it's only used if the syscall is used
119
'libsockfs.js', // ok to include it by default since it's only used if the syscall is used
120
);
121
122
if (NODERAWFS) {
123
// NODERAWFS requires NODEFS
124
libraries.push('libnodefs.js');
125
libraries.push('libnoderawfs.js');
126
// NODERAWFS overwrites libpath.js
127
libraries.push('libnodepath.js');
128
}
129
}
130
}
131
132
// Additional JS libraries (without AUTO_JS_LIBRARIES, link to these explicitly via -lxxx.js)
133
if (AUTO_JS_LIBRARIES) {
134
libraries.push(
135
'libwebgl.js',
136
'libhtml5_webgl.js',
137
'libopenal.js',
138
'libglut.js',
139
'libxlib.js',
140
'libegl.js',
141
'libuuid.js',
142
'libglew.js',
143
'libidbstore.js',
144
'libasync.js',
145
);
146
if (USE_SDL != 2) {
147
libraries.push('libsdl.js');
148
}
149
} else {
150
if (ASYNCIFY) {
151
libraries.push('libasync.js');
152
}
153
if (USE_SDL == 1) {
154
libraries.push('libsdl.js');
155
}
156
if (USE_SDL == 2) {
157
libraries.push('libegl.js', 'libwebgl.js', 'libhtml5_webgl.js');
158
}
159
}
160
161
if (USE_GLFW) {
162
libraries.push('libglfw.js');
163
}
164
165
if (LZ4) {
166
libraries.push('liblz4.js');
167
}
168
169
if (SHARED_MEMORY) {
170
libraries.push('libatomic.js');
171
}
172
173
if (MAX_WEBGL_VERSION >= 2) {
174
// libwebgl2.js must be included only after libwebgl.js, so if we are
175
// about to include libwebgl2.js, first squeeze in libwebgl.js.
176
libraries.push('libwebgl.js');
177
libraries.push('libwebgl2.js');
178
}
179
180
if (GL_EXPLICIT_UNIFORM_LOCATION || GL_EXPLICIT_UNIFORM_BINDING) {
181
libraries.push('libc_preprocessor.js');
182
}
183
184
if (LEGACY_GL_EMULATION) {
185
libraries.push('libglemu.js');
186
}
187
188
if (!STRICT) {
189
libraries.push('liblegacy.js');
190
}
191
192
if (BOOTSTRAPPING_STRUCT_INFO) {
193
libraries = ['libbootstrap.js', 'libstrings.js', 'libint53.js'];
194
}
195
196
if (SUPPORT_BIG_ENDIAN) {
197
libraries.push('liblittle_endian_heap.js');
198
}
199
200
// Resolve system libraries
201
libraries = libraries.map((filename) => path.join(systemLibdir, filename));
202
203
// Add all user specified JS library files to the link.
204
// These must be added last after all Emscripten-provided system libraries
205
// above, so that users can override built-in JS library symbols in their
206
// own code.
207
libraries.push(...JS_LIBRARIES);
208
209
// Deduplicate libraries to avoid processing any library file multiple times
210
libraries = [...new Set(libraries)]
211
212
return libraries;
213
}
214
215
let tempDir;
216
217
function getTempDir() {
218
if (!tempDir) {
219
const tempRoot = os.tmpdir();
220
tempDir = fs.mkdtempSync(path.join(tempRoot, 'emcc-jscompiler-'));
221
}
222
return tempDir;
223
}
224
225
function preprocessFiles(filenames) {
226
timer.start('preprocessFiles')
227
const results = {};
228
for (const filename of filenames) {
229
debugLog(`pre-processing JS library: ${filename}`);
230
pushCurrentFile(filename);
231
try {
232
results[filename] = processMacros(preprocess(filename), filename);
233
} catch (e) {
234
error(`error preprocessing JS library "${filename}":`);
235
throw e;
236
} finally {
237
popCurrentFile();
238
}
239
}
240
timer.stop('preprocessFiles')
241
return results;
242
}
243
244
export const LibraryManager = {
245
library: {},
246
// The JS and JS docs of each library definition indexed my mangled name.
247
libraryDefinitions: {},
248
structs: {},
249
loaded: false,
250
libraries: [],
251
252
has(name) {
253
if (!path.isAbsolute(name)) {
254
// Our libraries used to be called `library_xxx.js` rather than
255
// `lib_xx.js`. In case we have external code using this function
256
// we check for the old form too.
257
if (name.startsWith('library_')) {
258
name = name.replace('library_', 'lib');
259
}
260
name = path.join(systemLibdir, name);
261
}
262
return this.libraries.includes(name);
263
},
264
265
load() {
266
timer.start('load')
267
268
assert(!this.loaded);
269
this.loaded = true;
270
// Save the list for has() queries later.
271
this.libraries = calculateLibraries();
272
273
const preprocessed = preprocessFiles(this.libraries);
274
275
timer.start('executeJS')
276
for (const [filename, contents] of Object.entries(preprocessed)) {
277
this.executeJSLibraryFile(filename, contents);
278
}
279
timer.stop('executeJS')
280
281
this.addAliasDependencies();
282
283
timer.stop('load')
284
},
285
286
isAlias(entry) {
287
return (typeof entry == 'string' && entry[0] != '=' && (this.library.hasOwnProperty(entry) || WASM_EXPORTS.has(entry)));
288
},
289
290
/**
291
* Automatically add the target of an alias to it's dependency list.
292
*/
293
addAliasDependencies() {
294
const aliases = {};
295
for (const [key, value] of Object.entries(this.library)) {
296
if (this.isAlias(value)) {
297
aliases[key] = value;
298
}
299
}
300
for (const [key, value] of Object.entries(aliases)) {
301
(this.library[key + '__deps'] ??= []).push(value);
302
}
303
},
304
305
executeJSLibraryFile(filename, contents) {
306
const userLibraryProxy = new Proxy(this.library, {
307
set(target, prop, value) {
308
target[prop] = value;
309
if (!isDecorator(prop)) {
310
target[prop + '__user'] = true;
311
}
312
return true;
313
},
314
});
315
316
const isUserLibrary = !isBeneath(filename, systemLibdir);
317
if (isUserLibrary) {
318
debugLog(`executing user JS library: ${filename}`);
319
} else {
320
debugLog(`exectuing system JS library: ${filename}`);
321
}
322
323
let origLibrary;
324
// When we parse user libraries also set `__user` attribute
325
// on each element so that we can distinguish them later.
326
if (isUserLibrary) {
327
origLibrary = this.library;
328
this.library = userLibraryProxy;
329
}
330
pushCurrentFile(filename);
331
let preprocessedName = filename.replace(/\.\w+$/, '.preprocessed$&')
332
if (VERBOSE) {
333
preprocessedName = path.join(getTempDir(), path.basename(filename));
334
}
335
336
try {
337
runInMacroContext(contents, {filename: preprocessedName})
338
} catch (e) {
339
error(`failure to execute JS library "${filename}":`);
340
if (VERBOSE) {
341
fs.writeFileSync(preprocessedName, contents);
342
error(`preprocessed JS saved to ${preprocessedName}`)
343
} else {
344
error('use -sVERBOSE to save preprocessed JS');
345
}
346
throw e;
347
} finally {
348
popCurrentFile();
349
if (origLibrary) {
350
this.library = origLibrary;
351
}
352
}
353
if (VERBOSE) {
354
fs.rmSync(getTempDir(), { recursive: true, force: true });
355
}
356
}
357
};
358
359
// options is optional input object containing mergeInto params
360
// currently, it can contain
361
//
362
// key: noOverride, value: true
363
// if it is set, it prevents symbol redefinition and shows error
364
// in case of redefinition
365
//
366
// key: checkSig, value: true
367
// if it is set, __sig is checked for functions and error is reported
368
// if <function name>__sig is missing
369
function addToLibrary(obj, options = null) {
370
mergeInto(LibraryManager.library, obj, options);
371
}
372
373
let structs = {};
374
let defines = {};
375
376
/**
377
* Read JSON file containing struct and macro/define information
378
* that can then be used in JavaScript via macros.
379
*/
380
function loadStructInfo(filename) {
381
const temp = JSON.parse(readFile(filename));
382
Object.assign(structs, temp.structs);
383
Object.assign(defines, temp.defines);
384
}
385
386
if (!BOOTSTRAPPING_STRUCT_INFO) {
387
// Load struct and define information.
388
if (MEMORY64) {
389
loadStructInfo(localFile('struct_info_generated_wasm64.json'));
390
} else {
391
loadStructInfo(localFile('struct_info_generated.json'));
392
}
393
}
394
395
// Use proxy objects for C_DEFINES and C_STRUCTS so that we can give useful
396
// error messages.
397
const C_STRUCTS = new Proxy(structs, {
398
get(target, prop) {
399
if (!(prop in target)) {
400
throw new Error(
401
`Missing C struct ${prop}! If you just added it to struct_info.json, you need to run ./tools/gen_struct_info.py (then run a second time with --wasm64)`,
402
);
403
}
404
return target[prop];
405
},
406
});
407
408
const C_DEFINES = new Proxy(defines, {
409
get(target, prop) {
410
if (!(prop in target)) {
411
throw new Error(
412
`Missing C define ${prop}! If you just added it to struct_info.json, you need to run ./tools/gen_struct_info.py (then run a second time with --wasm64)`,
413
);
414
}
415
return target[prop];
416
},
417
});
418
419
// shorter alias for C_DEFINES
420
const cDefs = C_DEFINES;
421
422
// Legacy function that existed solely to give error message. These are now
423
// provided by the cDefs proxy object above.
424
function cDefine(key) {
425
return cDefs[key];
426
}
427
428
function isInternalSymbol(ident) {
429
return ident + '__internal' in LibraryManager.library;
430
}
431
432
function getUnusedLibrarySymbols() {
433
const librarySymbolSet = new Set(librarySymbols);
434
const missingSyms = new Set();
435
for (const [ident, value] of Object.entries(LibraryManager.library)) {
436
if (typeof value === 'function' || typeof value === 'number') {
437
if (isJsOnlySymbol(ident) && !isDecorator(ident) && !isInternalSymbol(ident)) {
438
const name = ident.slice(1);
439
if (!librarySymbolSet.has(name)) {
440
missingSyms.add(name);
441
}
442
}
443
}
444
}
445
return missingSyms;
446
}
447
448
// When running with ASSERTIONS enabled we create stubs for each library
449
// function that that was not included in the build. This gives useful errors
450
// when library dependencies are missing from `__deps` or depended on without
451
// being added to DEFAULT_LIBRARY_FUNCS_TO_INCLUDE
452
// TODO(sbc): These errors could potentially be generated at build time via
453
// some kind of acorn pass that searched for uses of these missing symbols.
454
function addMissingLibraryStubs(unusedLibSymbols) {
455
let rtn = '';
456
rtn += 'var missingLibrarySymbols = [\n';
457
for (const sym of unusedLibSymbols) {
458
rtn += ` '${sym}',\n`;
459
}
460
rtn += '];\n';
461
rtn += 'missingLibrarySymbols.forEach(missingLibrarySymbol)\n';
462
return rtn;
463
}
464
465
function exportSymbol(name) {
466
// In MODULARIZE=instance mode symbols are exported by being included in
467
// an export { foo, bar } list so we build up the simple list of names
468
if (MODULARIZE === 'instance') {
469
return name;
470
}
471
return `Module['${name}'] = ${name};`;
472
}
473
474
// export parts of the JS runtime that the user asked for
475
function exportRuntimeSymbols() {
476
// optionally export something.
477
function shouldExport(name) {
478
// Native exports are not available to be exported initially. Instead,
479
// they get exported later in `assignWasmExports`.
480
if (nativeAliases[name]) {
481
return false;
482
}
483
// If requested to be exported, export it.
484
if (EXPORTED_RUNTIME_METHODS.has(name)) {
485
// Unless we are in MODULARIZE=instance mode then HEAP objects are
486
// exported separately in updateMemoryViews
487
if (MODULARIZE == 'instance' || !name.startsWith('HEAP')) {
488
return true;
489
}
490
}
491
return false;
492
}
493
494
// All possible runtime elements that can be exported
495
let runtimeElements = [
496
'run',
497
'out',
498
'err',
499
'callMain',
500
'abort',
501
'wasmExports',
502
'HEAPF32',
503
'HEAPF64',
504
'HEAP8',
505
'HEAPU8',
506
'HEAP16',
507
'HEAPU16',
508
'HEAP32',
509
'HEAPU32',
510
'HEAP64',
511
'HEAPU64',
512
];
513
514
if (SUPPORT_BIG_ENDIAN) {
515
runtimeElements.push('HEAP_DATA_VIEW');
516
}
517
518
if (LOAD_SOURCE_MAP) {
519
runtimeElements.push('WasmSourceMap');
520
}
521
522
if (STACK_OVERFLOW_CHECK) {
523
runtimeElements.push('writeStackCookie');
524
runtimeElements.push('checkStackCookie');
525
}
526
527
if (RETAIN_COMPILER_SETTINGS) {
528
runtimeElements.push('getCompilerSetting');
529
}
530
531
if (RUNTIME_DEBUG) {
532
runtimeElements.push('prettyPrint');
533
}
534
535
// dynCall_* methods are not hardcoded here, as they
536
// depend on the file being compiled. check for them
537
// and add them.
538
for (const name of EXPORTED_RUNTIME_METHODS) {
539
if (/^dynCall_/.test(name)) {
540
// a specific dynCall; add to the list
541
runtimeElements.push(name);
542
}
543
}
544
545
// Add JS library elements such as FS, GL, ENV, etc. These are prefixed with
546
// '$ which indicates they are JS methods.
547
let runtimeElementsSet = new Set(runtimeElements);
548
for (const ident of Object.keys(LibraryManager.library)) {
549
if (isJsOnlySymbol(ident) && !isDecorator(ident) && !isInternalSymbol(ident)) {
550
const jsname = ident.slice(1);
551
// Note that this assertion may be hit when a function is moved into the
552
// JS library. In that case the function should be removed from the list
553
// of runtime elements above.
554
assert(!runtimeElementsSet.has(jsname), 'runtimeElements contains library symbol: ' + ident);
555
runtimeElements.push(jsname);
556
}
557
}
558
559
// check all exported things exist, error when missing
560
runtimeElementsSet = new Set(runtimeElements);
561
for (const name of EXPORTED_RUNTIME_METHODS) {
562
if (!runtimeElementsSet.has(name)) {
563
error(`undefined exported symbol: "${name}" in EXPORTED_RUNTIME_METHODS`);
564
}
565
}
566
567
const exports = runtimeElements.filter(shouldExport);
568
const results = exports.map(exportSymbol);
569
570
if (MODULARIZE == 'instance') {
571
if (results.length == 0) return '';
572
return '// Runtime exports\nexport { ' + results.join(', ') + ' };\n';
573
}
574
575
if (ASSERTIONS && !EXPORT_ALL) {
576
// in ASSERTIONS mode we show a useful error if it is used without being
577
// exported. See `unexportedRuntimeSymbol` in runtime_debug.js.
578
const unusedLibSymbols = getUnusedLibrarySymbols();
579
if (unusedLibSymbols.size) {
580
results.push(addMissingLibraryStubs(unusedLibSymbols));
581
}
582
583
const unexported = [];
584
for (const name of runtimeElements) {
585
if (
586
!EXPORTED_RUNTIME_METHODS.has(name) &&
587
!EXPORTED_FUNCTIONS.has(name) &&
588
!unusedLibSymbols.has(name)
589
) {
590
unexported.push(name);
591
}
592
}
593
594
if (unexported.length || unusedLibSymbols.size) {
595
let unexportedStubs = 'var unexportedSymbols = [\n';
596
for (const sym of unexported) {
597
unexportedStubs += ` '${sym}',\n`;
598
}
599
unexportedStubs += '];\n';
600
unexportedStubs += 'unexportedSymbols.forEach(unexportedRuntimeSymbol);\n';
601
results.push(unexportedStubs);
602
}
603
}
604
605
results.unshift('// Begin runtime exports');
606
results.push('// End runtime exports');
607
return results.join('\n ') + '\n';
608
}
609
610
function exportLibrarySymbols() {
611
assert(MODULARIZE != 'instance');
612
const results = ['// Begin JS library exports'];
613
for (const ident of librarySymbols) {
614
if ((EXPORT_ALL || EXPORTED_FUNCTIONS.has(ident)) && !nativeAliases[ident]) {
615
results.push(exportSymbol(ident));
616
}
617
}
618
results.push('// End JS library exports');
619
return results.join('\n ') + '\n';
620
}
621
622
function exportJSSymbols() {
623
// In MODULARIZE=instance mode JS library symbols are marked with `export`
624
// at the point of declaration.
625
if (MODULARIZE == 'instance') return exportRuntimeSymbols();
626
return exportRuntimeSymbols() + ' ' + exportLibrarySymbols();
627
}
628
629
addToCompileTimeContext({
630
exportJSSymbols,
631
loadStructInfo,
632
LibraryManager,
633
librarySymbols,
634
addToLibrary,
635
cDefs,
636
cDefine,
637
C_STRUCTS,
638
C_DEFINES,
639
});
640
641