Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/src/jsifier.mjs
4128 views
1
/**
2
* @license
3
* Copyright 2010 The Emscripten Authors
4
* SPDX-License-Identifier: MIT
5
*/
6
7
// Convert analyzed data to javascript. Everything has already been calculated
8
// before this stage, which just does the final conversion to JavaScript.
9
10
import assert from 'node:assert';
11
import * as fs from 'node:fs/promises';
12
import {
13
ATMODULES,
14
ATEXITS,
15
ATINITS,
16
ATPOSTCTORS,
17
ATPRERUNS,
18
ATMAINS,
19
ATPOSTRUNS,
20
defineI64Param,
21
indentify,
22
makeReturn64,
23
modifyJSFunction,
24
preprocess,
25
processMacros,
26
receiveI64ParamAsI53,
27
} from './parseTools.mjs';
28
import {
29
addToCompileTimeContext,
30
error,
31
errorOccured,
32
isDecorator,
33
isJsOnlySymbol,
34
compileTimeContext,
35
printErr,
36
readFile,
37
runInMacroContext,
38
warn,
39
warnOnce,
40
warningOccured,
41
localFile,
42
} from './utility.mjs';
43
import {LibraryManager, librarySymbols} from './modules.mjs';
44
45
const addedLibraryItems = {};
46
47
const extraLibraryFuncs = [];
48
49
// Experimental feature to check for invalid __deps entries.
50
// See `EMCC_CHECK_DEPS` in in the environment to try it out.
51
const CHECK_DEPS = process.env.EMCC_CHECK_DEPS;
52
53
// Some JS-implemented library functions are proxied to be called on the main
54
// browser thread, if the Emscripten runtime is executing in a Web Worker.
55
// Each such proxied function is identified via an ordinal number (this is not
56
// the same namespace as function pointers in general).
57
const proxiedFunctionTable = [];
58
59
// Mangles the given C/JS side function name to assembly level function name (adds an underscore)
60
function mangleCSymbolName(f) {
61
if (f === '__main_argc_argv') {
62
f = 'main';
63
}
64
return f[0] == '$' ? f.slice(1) : '_' + f;
65
}
66
67
// Splits out items that pass filter. Returns also the original sans the filtered
68
function splitter(array, filter) {
69
const splitOut = array.filter(filter);
70
const leftIn = array.filter((x) => !filter(x));
71
return {leftIn, splitOut};
72
}
73
74
function escapeJSONKey(x) {
75
if (/^[\d\w_]+$/.exec(x) || x[0] === '"' || x[0] === "'") return x;
76
assert(!x.includes("'"), 'cannot have internal single quotes in keys: ' + x);
77
return "'" + x + "'";
78
}
79
80
// JSON.stringify will completely omit function objects. This function is
81
// similar but preserves functions.
82
function stringifyWithFunctions(obj) {
83
if (typeof obj == 'function') return obj.toString();
84
if (obj === null || typeof obj != 'object') return JSON.stringify(obj);
85
if (Array.isArray(obj)) {
86
return '[' + obj.map(stringifyWithFunctions).join(',') + ']';
87
}
88
89
// preserve the type of the object if it is one of [Map, Set, WeakMap, WeakSet].
90
const builtinContainers = runInMacroContext('[Map, Set, WeakMap, WeakSet]', {
91
filename: '<internal>',
92
});
93
for (const container of builtinContainers) {
94
if (obj instanceof container) {
95
const className = container.name;
96
assert(!obj.size, `cannot stringify ${className} with data`);
97
return `new ${className}`;
98
}
99
}
100
101
var rtn = '{\n';
102
for (const [key, value] of Object.entries(obj)) {
103
var str = stringifyWithFunctions(value);
104
// Handle JS method syntax where the function property starts with its own
105
// name. e.g. `foo(a) {}` (or `async foo(a) {}`)
106
if (typeof value === 'function' && (str.startsWith(key) || str.startsWith('async ' + key))) {
107
rtn += str + ',\n';
108
} else {
109
rtn += escapeJSONKey(key) + ':' + str + ',\n';
110
}
111
}
112
return rtn + '}';
113
}
114
115
function isDefined(symName) {
116
if (WASM_EXPORTS.has(symName) || SIDE_MODULE_EXPORTS.has(symName)) {
117
return true;
118
}
119
if (symName == '__main_argc_argv' && SIDE_MODULE_EXPORTS.has('main')) {
120
return true;
121
}
122
// 'invoke_' symbols are created at runtime in library_dylink.py so can
123
// always be considered as defined.
124
if (RELOCATABLE && symName.startsWith('invoke_')) {
125
return true;
126
}
127
return false;
128
}
129
130
function resolveAlias(symbol) {
131
var value = LibraryManager.library[symbol];
132
if (typeof value == 'string' && value[0] != '=' && LibraryManager.library.hasOwnProperty(value)) {
133
return value;
134
}
135
return symbol;
136
}
137
138
function getTransitiveDeps(symbol) {
139
// TODO(sbc): Use some kind of cache to avoid quadratic behaviour here.
140
const transitiveDeps = new Set();
141
const seen = new Set();
142
const toVisit = [symbol];
143
while (toVisit.length) {
144
const sym = toVisit.pop();
145
if (!seen.has(sym)) {
146
let directDeps = LibraryManager.library[sym + '__deps'] || [];
147
directDeps = directDeps.filter((d) => typeof d === 'string');
148
for (const dep of directDeps) {
149
const resolved = resolveAlias(dep);
150
if (VERBOSE && !transitiveDeps.has(dep)) {
151
printErr(`adding dependency ${symbol} -> ${dep}`);
152
}
153
transitiveDeps.add(resolved);
154
toVisit.push(resolved);
155
}
156
seen.add(sym);
157
}
158
}
159
return Array.from(transitiveDeps);
160
}
161
162
function shouldPreprocess(fileName) {
163
var content = readFile(fileName).trim();
164
return content.startsWith('#preprocess\n') || content.startsWith('#preprocess\r\n');
165
}
166
167
function getIncludeFile(fileName, alwaysPreprocess, shortName) {
168
shortName ??= fileName;
169
let result = `// include: ${shortName}\n`;
170
const doPreprocess = alwaysPreprocess || shouldPreprocess(fileName);
171
if (doPreprocess) {
172
result += processMacros(preprocess(fileName), fileName);
173
} else {
174
result += readFile(fileName);
175
}
176
result += `// end include: ${shortName}\n`;
177
return result;
178
}
179
180
function getSystemIncludeFile(fileName) {
181
return getIncludeFile(localFile(fileName), /*alwaysPreprocess=*/ true, /*shortName=*/ fileName);
182
}
183
184
function preJS() {
185
let result = '';
186
for (const fileName of PRE_JS_FILES) {
187
result += getIncludeFile(fileName);
188
}
189
return result;
190
}
191
192
// Certain library functions have specific indirect dependencies. See the
193
// comments alongside eaach of these.
194
const checkDependenciesSkip = new Set([
195
'_mmap_js',
196
'_emscripten_throw_longjmp',
197
'_emscripten_receive_on_main_thread_js',
198
'emscripten_start_fetch',
199
'emscripten_start_wasm_audio_worklet_thread_async',
200
]);
201
202
const checkDependenciesIgnore = new Set([
203
// These are added in bulk to whole library files are so are not precise
204
'$PThread',
205
'$WebGPU',
206
'$SDL',
207
'$GLUT',
208
'$GLEW',
209
'$Browser',
210
'$AL',
211
'$GL',
212
'$IDBStore',
213
// These are added purely for their side effects
214
'$polyfillWaitAsync',
215
'$GLImmediateSetup',
216
'$emscriptenGetAudioObject',
217
// These get conservatively injected via i53ConversionDeps
218
'$bigintToI53Checked',
219
'$convertI32PairToI53Checked',
220
'setTempRet0',
221
]);
222
223
/**
224
* Hacky attempt to find unused `__deps` entries. This is not enabled by default
225
* but can be enabled by setting CHECK_DEPS above.
226
* TODO: Use a more precise method such as tokenising using acorn.
227
*/
228
function checkDependencies(symbol, snippet, deps, postset) {
229
if (checkDependenciesSkip.has(symbol)) {
230
return;
231
}
232
for (const dep of deps) {
233
if (typeof dep === 'function') {
234
continue;
235
}
236
if (checkDependenciesIgnore.has(dep)) {
237
continue;
238
}
239
const mangled = mangleCSymbolName(dep);
240
if (!snippet.includes(mangled) && (!postset || !postset.includes(mangled))) {
241
error(`${symbol}: unused dependency: ${dep}`);
242
}
243
}
244
}
245
246
function addImplicitDeps(snippet, deps) {
247
// There are some common dependencies that we inject automatically by
248
// conservatively scanning the input functions for their usage.
249
// Specifically, these are dependencies that are very common and would be
250
// burdensome to add manually to all functions.
251
// The first four are deps that are automatically/conditionally added
252
// by the {{{ makeDynCall }}}, and {{{ runtimeKeepalivePush/Pop }}} macros.
253
const autoDeps = [
254
'getDynCaller',
255
'getWasmTableEntry',
256
'runtimeKeepalivePush',
257
'runtimeKeepalivePop',
258
'UTF8ToString',
259
];
260
for (const dep of autoDeps) {
261
if (snippet.includes(dep + '(')) {
262
deps.push('$' + dep);
263
}
264
}
265
}
266
267
function handleI64Signatures(symbol, snippet, sig, i53abi) {
268
// Handle i64 parameters and return values.
269
//
270
// When WASM_BIGINT is enabled these arrive as BigInt values which we
271
// convert to int53 JS numbers. If necessary, we also convert the return
272
// value back into a BigInt.
273
//
274
// When WASM_BIGINT is not enabled we receive i64 values as a pair of i32
275
// numbers which is converted to single int53 number. In necessary, we also
276
// split the return value into a pair of i32 numbers.
277
return modifyJSFunction(snippet, (args, body, async_, oneliner) => {
278
let argLines = args.split('\n');
279
argLines = argLines.map((line) => line.split('//')[0]);
280
const argNames = argLines
281
.join(' ')
282
.split(',')
283
.map((name) => name.trim());
284
const newArgs = [];
285
let argConversions = '';
286
if (sig.length > argNames.length + 1) {
287
error(`handleI64Signatures: signature too long for ${symbol}`);
288
return snippet;
289
}
290
for (let i = 0; i < argNames.length; i++) {
291
const name = argNames[i];
292
// If sig is shorter than argNames list then argType will be undefined
293
// here, which will result in the default case below.
294
const argType = sig[i + 1];
295
if (WASM_BIGINT && ((MEMORY64 && argType == 'p') || (i53abi && argType == 'j'))) {
296
argConversions += ` ${receiveI64ParamAsI53(name, undefined, false)}\n`;
297
} else {
298
if (argType == 'j' && i53abi) {
299
argConversions += ` ${receiveI64ParamAsI53(name, undefined, false)}\n`;
300
newArgs.push(defineI64Param(name));
301
} else if (argType == 'p' && CAN_ADDRESS_2GB) {
302
argConversions += ` ${name} >>>= 0;\n`;
303
newArgs.push(name);
304
} else {
305
newArgs.push(name);
306
}
307
}
308
}
309
310
if (!WASM_BIGINT) {
311
args = newArgs.join(',');
312
}
313
314
if ((sig[0] == 'j' && i53abi) || (sig[0] == 'p' && MEMORY64)) {
315
const await_ = async_ ? 'await ' : '';
316
// For functions that where we need to mutate the return value, we
317
// also need to wrap the body in an inner function.
318
if (oneliner) {
319
if (argConversions) {
320
return `${async_}(${args}) => {
321
${argConversions}
322
return ${makeReturn64(await_ + body)};
323
}`;
324
}
325
return `${async_}(${args}) => ${makeReturn64(await_ + body)};`;
326
}
327
return `\
328
${async_}function(${args}) {
329
${argConversions}
330
var ret = (() => { ${body} })();
331
return ${makeReturn64(await_ + 'ret')};
332
}`;
333
}
334
335
// Otherwise no inner function is needed and we covert the arguments
336
// before executing the function body.
337
if (oneliner) {
338
body = `return ${body}`;
339
}
340
return `\
341
${async_}function(${args}) {
342
${argConversions}
343
${body};
344
}`;
345
});
346
}
347
348
export async function runJSify(outputFile, symbolsOnly) {
349
const libraryItems = [];
350
const symbolDeps = {};
351
const asyncFuncs = [];
352
let postSets = [];
353
354
LibraryManager.load();
355
356
let outputHandle = process.stdout;
357
if (outputFile) {
358
outputHandle = await fs.open(outputFile, 'w');
359
}
360
361
async function writeOutput(str) {
362
await outputHandle.write(str + '\n');
363
}
364
365
const symbolsNeeded = DEFAULT_LIBRARY_FUNCS_TO_INCLUDE;
366
symbolsNeeded.push(...extraLibraryFuncs);
367
for (const sym of EXPORTED_RUNTIME_METHODS) {
368
if ('$' + sym in LibraryManager.library) {
369
symbolsNeeded.push('$' + sym);
370
}
371
}
372
373
for (const key of Object.keys(LibraryManager.library)) {
374
if (!isDecorator(key)) {
375
if (INCLUDE_FULL_LIBRARY || EXPORTED_FUNCTIONS.has(mangleCSymbolName(key))) {
376
symbolsNeeded.push(key);
377
}
378
}
379
}
380
381
function processLibraryFunction(snippet, symbol, mangled, deps, isStub) {
382
// It is possible that when printing the function as a string on Windows,
383
// the js interpreter we are in returns the string with Windows line endings
384
// \r\n. This is undesirable, since line endings are managed in the form \n
385
// in the output for binary file writes, so make sure the endings are
386
// uniform.
387
snippet = snippet.toString().replace(/\r\n/gm, '\n');
388
389
// Is this a shorthand `foo() {}` method syntax?
390
// If so, prepend a function keyword so that it's valid syntax when extracted.
391
if (snippet.startsWith(symbol)) {
392
snippet = 'function ' + snippet;
393
}
394
395
if (isStub) {
396
return snippet;
397
}
398
399
// apply LIBRARY_DEBUG if relevant
400
if (LIBRARY_DEBUG && !isJsOnlySymbol(symbol)) {
401
snippet = modifyJSFunction(snippet, (args, body, _async, oneliner) => {
402
var run_func;
403
if (oneliner) {
404
run_func = `var ret = ${body}`;
405
} else {
406
run_func = `var ret = (() => { ${body} })();`;
407
}
408
return `\
409
function(${args}) {
410
dbg("[library call:${mangled}: " + Array.prototype.slice.call(arguments).map(prettyPrint) + "]");
411
${run_func}
412
dbg(" [ return:" + prettyPrint(ret));
413
return ret;
414
}`;
415
});
416
}
417
418
const sig = LibraryManager.library[symbol + '__sig'];
419
const i53abi = LibraryManager.library[symbol + '__i53abi'];
420
if (i53abi) {
421
if (!sig) {
422
error(`JS library error: '__i53abi' decorator requires '__sig' decorator: '${symbol}'`);
423
}
424
if (!sig.includes('j')) {
425
error(
426
`JS library error: '__i53abi' only makes sense when '__sig' includes 'j' (int64): '${symbol}'`,
427
);
428
}
429
}
430
if (
431
sig &&
432
((i53abi && sig.includes('j')) || ((MEMORY64 || CAN_ADDRESS_2GB) && sig.includes('p')))
433
) {
434
snippet = handleI64Signatures(symbol, snippet, sig, i53abi);
435
compileTimeContext.i53ConversionDeps.forEach((d) => deps.push(d));
436
}
437
438
const proxyingMode = LibraryManager.library[symbol + '__proxy'];
439
if (proxyingMode) {
440
if (proxyingMode !== 'sync' && proxyingMode !== 'async' && proxyingMode !== 'none') {
441
throw new Error(`Invalid proxyingMode ${symbol}__proxy: '${proxyingMode}' specified!`);
442
}
443
if (SHARED_MEMORY && proxyingMode != 'none') {
444
const sync = proxyingMode === 'sync';
445
if (PTHREADS) {
446
snippet = modifyJSFunction(snippet, (args, body, async_, oneliner) => {
447
if (oneliner) {
448
body = `return ${body}`;
449
}
450
const rtnType = sig && sig.length ? sig[0] : null;
451
const proxyFunc =
452
MEMORY64 && rtnType == 'p' ? 'proxyToMainThreadPtr' : 'proxyToMainThread';
453
deps.push('$' + proxyFunc);
454
return `
455
${async_}function(${args}) {
456
if (ENVIRONMENT_IS_PTHREAD)
457
return ${proxyFunc}(${proxiedFunctionTable.length}, 0, ${+sync}${args ? ', ' : ''}${args});
458
${body}
459
}\n`;
460
});
461
} else if (WASM_WORKERS && ASSERTIONS) {
462
// In ASSERTIONS builds add runtime checks that proxied functions are not attempted to be called in Wasm Workers
463
// (since there is no automatic proxying architecture available)
464
snippet = modifyJSFunction(
465
snippet,
466
(args, body) => `
467
function(${args}) {
468
assert(!ENVIRONMENT_IS_WASM_WORKER, "Attempted to call proxied function '${mangled}' in a Wasm Worker, but in Wasm Worker enabled builds, proxied function architecture is not available!");
469
${body}
470
}\n`,
471
);
472
}
473
proxiedFunctionTable.push(mangled);
474
}
475
}
476
477
return snippet;
478
}
479
480
function symbolHandler(symbol) {
481
// In LLVM, exceptions generate a set of functions of form
482
// __cxa_find_matching_catch_1(), __cxa_find_matching_catch_2(), etc. where
483
// the number specifies the number of arguments. In Emscripten, route all
484
// these to a single function 'findMatchingCatch' that takes an array
485
// of argument.
486
if (LINK_AS_CXX && !WASM_EXCEPTIONS && symbol.startsWith('__cxa_find_matching_catch_')) {
487
if (DISABLE_EXCEPTION_THROWING) {
488
error(
489
'DISABLE_EXCEPTION_THROWING was set (likely due to -fno-exceptions), which means no C++ exception throwing support code is linked in, but exception catching code appears. Either do not set DISABLE_EXCEPTION_THROWING (if you do want exception throwing) or compile all source files with -fno-exceptions (so that no exceptions support code is required); also make sure DISABLE_EXCEPTION_CATCHING is set to the right value - if you want exceptions, it should be off, and vice versa.',
490
);
491
return;
492
}
493
if (!(symbol in LibraryManager.library)) {
494
// Create a new __cxa_find_matching_catch variant on demand.
495
const num = +symbol.split('_').slice(-1)[0];
496
compileTimeContext.addCxaCatch(num);
497
}
498
// Continue, with the code below emitting the proper JavaScript based on
499
// what we just added to the library.
500
}
501
502
function addFromLibrary(symbol, dependent, force = false) {
503
// don't process any special identifiers. These are looked up when
504
// processing the base name of the identifier.
505
if (isDecorator(symbol)) {
506
return;
507
}
508
509
// if the function was implemented in compiled code, there is no need to
510
// include the js version
511
if (WASM_EXPORTS.has(symbol) && !force) {
512
return;
513
}
514
515
if (symbol in addedLibraryItems) {
516
return;
517
}
518
addedLibraryItems[symbol] = true;
519
520
if (!(symbol + '__deps' in LibraryManager.library)) {
521
LibraryManager.library[symbol + '__deps'] = [];
522
}
523
524
const deps = LibraryManager.library[symbol + '__deps'];
525
let sig = LibraryManager.library[symbol + '__sig'];
526
if (!WASM_BIGINT && sig && sig[0] == 'j') {
527
// Without WASM_BIGINT functions that return i64 depend on setTempRet0
528
// to return the upper 32-bits of the result.
529
// See makeReturn64 in parseTools.py.
530
deps.push('setTempRet0');
531
}
532
533
let isAsyncFunction = false;
534
if (ASYNCIFY) {
535
const original = LibraryManager.library[symbol];
536
if (typeof original == 'function') {
537
isAsyncFunction = LibraryManager.library[symbol + '__async'];
538
}
539
if (isAsyncFunction) {
540
asyncFuncs.push(symbol);
541
}
542
}
543
544
if (symbolsOnly) {
545
if (LibraryManager.library.hasOwnProperty(symbol)) {
546
// Resolve aliases before looking up deps
547
var resolvedSymbol = resolveAlias(symbol);
548
var transtiveDeps = getTransitiveDeps(resolvedSymbol);
549
symbolDeps[symbol] = transtiveDeps.filter(
550
(d) => !isJsOnlySymbol(d) && !(d in LibraryManager.library),
551
);
552
}
553
return;
554
}
555
556
// This gets set to true in the case of dynamic linking for symbols that
557
// are undefined in the main module. In this case we create a stub that
558
// will resolve the correct symbol at runtime, or assert if its missing.
559
let isStub = false;
560
561
const mangled = mangleCSymbolName(symbol);
562
563
if (!LibraryManager.library.hasOwnProperty(symbol)) {
564
const isWeakImport = WEAK_IMPORTS.has(symbol);
565
if (!isDefined(symbol) && !isWeakImport) {
566
if (PROXY_TO_PTHREAD && !MAIN_MODULE && symbol == '__main_argc_argv') {
567
error('PROXY_TO_PTHREAD proxies main() for you, but no main exists');
568
return;
569
}
570
let undefinedSym = symbol;
571
if (symbol === '__main_argc_argv') {
572
undefinedSym = 'main/__main_argc_argv';
573
}
574
let msg = 'undefined symbol: ' + undefinedSym;
575
if (dependent) msg += ` (referenced by ${dependent})`;
576
if (ERROR_ON_UNDEFINED_SYMBOLS) {
577
error(msg);
578
warnOnce(
579
'To disable errors for undefined symbols use `-sERROR_ON_UNDEFINED_SYMBOLS=0`',
580
);
581
warnOnce(
582
mangled +
583
' may need to be added to EXPORTED_FUNCTIONS if it arrives from a system library',
584
);
585
} else if (VERBOSE || WARN_ON_UNDEFINED_SYMBOLS) {
586
warn(msg);
587
}
588
if (symbol === '__main_argc_argv' && STANDALONE_WASM) {
589
warn('To build in STANDALONE_WASM mode without a main(), use emcc --no-entry');
590
}
591
}
592
if (!RELOCATABLE) {
593
// emit a stub that will fail at runtime
594
LibraryManager.library[symbol] = new Function(`abort('missing function: ${symbol}');`);
595
// We have already warned/errored about this function, so for the purposes of Closure use, mute all type checks
596
// regarding this function, marking ot a variadic function that can take in anything and return anything.
597
// (not useful to warn/error multiple times)
598
LibraryManager.library[symbol + '__docs'] = '/** @type {function(...*):?} */';
599
isStub = true;
600
} else {
601
// Create a stub for this symbol which can later be replaced by the
602
// dynamic linker. If this stub is called before the symbol is
603
// resolved assert in debug builds or trap in release builds.
604
let target = `wasmImports['${symbol}']`;
605
if (ASYNCIFY) {
606
// See the definition of asyncifyStubs in preamble.js for why this
607
// is needed.
608
target = `asyncifyStubs['${symbol}']`;
609
}
610
let assertion = '';
611
if (ASSERTIONS) {
612
assertion += `if (!${target} || ${target}.stub) abort("external symbol '${symbol}' is missing. perhaps a side module was not linked in? if this function was expected to arrive from a system library, try to build the MAIN_MODULE with EMCC_FORCE_STDLIBS=1 in the environment");\n`;
613
}
614
const functionBody = assertion + `return ${target}(...args);`;
615
LibraryManager.library[symbol] = new Function('...args', functionBody);
616
isStub = true;
617
}
618
}
619
620
librarySymbols.push(mangled);
621
622
const original = LibraryManager.library[symbol];
623
let snippet = original;
624
625
// Check for dependencies on `__internal` symbols from user libraries.
626
const isUserSymbol = LibraryManager.library[symbol + '__user'];
627
deps.forEach((dep) => {
628
if (isUserSymbol && LibraryManager.library[dep + '__internal']) {
629
warn(`user library symbol '${symbol}' depends on internal symbol '${dep}'`);
630
}
631
});
632
633
let isFunction = false;
634
let aliasTarget;
635
636
const postsetId = symbol + '__postset';
637
const postset = LibraryManager.library[postsetId];
638
if (postset) {
639
// A postset is either code to run right now, or some text we should emit.
640
// If it's code, it may return some text to emit as well.
641
const postsetString = typeof postset == 'function' ? postset() : postset;
642
if (postsetString && !addedLibraryItems[postsetId]) {
643
addedLibraryItems[postsetId] = true;
644
postSets.push(postsetString + ';');
645
}
646
}
647
648
if (typeof snippet == 'string') {
649
if (snippet[0] != '=') {
650
if (LibraryManager.library[snippet]) {
651
// Redirection for aliases. We include the parent, and at runtime
652
// make ourselves equal to it. This avoid having duplicate
653
// functions with identical content.
654
aliasTarget = snippet;
655
snippet = mangleCSymbolName(aliasTarget);
656
deps.push(aliasTarget);
657
}
658
}
659
} else if (typeof snippet == 'object') {
660
snippet = stringifyWithFunctions(snippet);
661
addImplicitDeps(snippet, deps);
662
} else if (typeof snippet == 'function') {
663
isFunction = true;
664
snippet = processLibraryFunction(snippet, symbol, mangled, deps, isStub);
665
addImplicitDeps(snippet, deps);
666
if (CHECK_DEPS && !isUserSymbol) {
667
checkDependencies(symbol, snippet, deps, postset?.toString());
668
}
669
}
670
671
if (VERBOSE) {
672
printErr(`adding ${symbol} (referenced by ${dependent})`);
673
}
674
function addDependency(dep) {
675
// dependencies can be JS functions, which we just run
676
if (typeof dep == 'function') {
677
return dep();
678
}
679
// $noExitRuntime is special since there are conditional usages of it
680
// in libcore.js and libpthread.js. These happen before deps are
681
// processed so depending on it via `__deps` doesn't work.
682
if (dep === '$noExitRuntime') {
683
error(
684
'noExitRuntime cannot be referenced via __deps mechanism. Use DEFAULT_LIBRARY_FUNCS_TO_INCLUDE or EXPORTED_RUNTIME_METHODS',
685
);
686
}
687
return addFromLibrary(dep, `${symbol}, referenced by ${dependent}`, dep === aliasTarget);
688
}
689
let contentText;
690
if (isFunction) {
691
// Emit the body of a JS library function.
692
if (
693
(USE_ASAN || USE_LSAN || UBSAN_RUNTIME) &&
694
LibraryManager.library[symbol + '__noleakcheck']
695
) {
696
contentText = modifyJSFunction(
697
snippet,
698
(args, body) => `(${args}) => withBuiltinMalloc(() => {${body}})`,
699
);
700
deps.push('$withBuiltinMalloc');
701
} else {
702
contentText = snippet; // Regular JS function that will be executed in the context of the calling thread.
703
}
704
// Give the function the correct (mangled) name. Overwrite it if it's
705
// already named. This must happen after the last call to
706
// modifyJSFunction which could have changed or removed the name.
707
if (contentText.match(/^\s*([^}]*)\s*=>/s)) {
708
// Handle arrow functions
709
contentText = `var ${mangled} = ` + contentText + ';';
710
} else if (contentText.startsWith('class ')) {
711
contentText = contentText.replace(/^class /, `class ${mangled} `);
712
} else {
713
// Handle regular (non-arrow) functions
714
contentText = contentText.replace(/function(?:\s+([^(]+))?\s*\(/, `function ${mangled}(`);
715
}
716
} else if (typeof snippet == 'string' && snippet.startsWith(';')) {
717
// In JS libraries
718
// foo: ';[code here verbatim]'
719
// emits
720
// 'var foo;[code here verbatim];'
721
contentText = 'var ' + mangled + snippet;
722
if (snippet[snippet.length - 1] != ';' && snippet[snippet.length - 1] != '}') {
723
contentText += ';';
724
}
725
} else if (typeof snippet == 'undefined') {
726
// wasmTable is kind of special. In the normal configuration we export
727
// it from the wasm module under the name `__indirect_function_table`
728
// but we declare it as an 'undefined'. It then gets assigned manually
729
// once the wasm module is available.
730
// TODO(sbc): This is kind of hacky, we should come up with a better solution.
731
var isDirectWasmExport = WASM_ESM_INTEGRATION && mangled == 'wasmTable';
732
if (isDirectWasmExport) {
733
contentText = '';
734
} else {
735
contentText = `var ${mangled};`;
736
}
737
} else {
738
// In JS libraries
739
// foo: '=[value]'
740
// emits
741
// 'var foo = [value];'
742
if (typeof snippet == 'string' && snippet[0] == '=') {
743
snippet = snippet.slice(1);
744
}
745
contentText = `var ${mangled} = ${snippet};`;
746
}
747
748
if (MODULARIZE == 'instance' && (EXPORT_ALL || EXPORTED_FUNCTIONS.has(mangled)) && !isStub) {
749
// In MODULARIZE=instance mode mark JS library symbols are exported at
750
// the point of declaration.
751
contentText = 'export ' + contentText;
752
}
753
754
// Relocatable code needs signatures to create proper wrappers.
755
if (sig && RELOCATABLE) {
756
if (!WASM_BIGINT) {
757
sig = sig[0].replace('j', 'i') + sig.slice(1).replace(/j/g, 'ii');
758
}
759
contentText += `\n${mangled}.sig = '${sig}';`;
760
}
761
if (ASYNCIFY && isAsyncFunction) {
762
contentText += `\n${mangled}.isAsync = true;`;
763
}
764
if (isStub) {
765
contentText += `\n${mangled}.stub = true;`;
766
if (ASYNCIFY && MAIN_MODULE) {
767
contentText += `\nasyncifyStubs['${symbol}'] = undefined;`;
768
}
769
}
770
771
let commentText = '';
772
if (force) {
773
commentText += '/** @suppress {duplicate } */\n';
774
}
775
776
let docs = LibraryManager.library[symbol + '__docs'];
777
if (docs) {
778
commentText += docs + '\n';
779
}
780
781
if (EMIT_TSD) {
782
LibraryManager.libraryDefinitions[mangled] = {
783
docs: docs ?? null,
784
snippet: snippet ?? null,
785
};
786
}
787
788
const depsText = deps
789
? deps
790
.map(addDependency)
791
.filter((x) => x != '')
792
.join('\n') + '\n'
793
: '';
794
return depsText + commentText + contentText;
795
}
796
797
const JS = addFromLibrary(symbol, 'root reference (e.g. compiled C/C++ code)');
798
libraryItems.push(JS);
799
}
800
801
function includeSystemFile(fileName) {
802
writeOutput(getSystemIncludeFile(fileName));
803
}
804
805
function includeFile(fileName) {
806
writeOutput(getIncludeFile(fileName));
807
}
808
809
function finalCombiner() {
810
const splitPostSets = splitter(postSets, (x) => x.symbol && x.dependencies);
811
postSets = splitPostSets.leftIn;
812
const orderedPostSets = splitPostSets.splitOut;
813
814
let limit = orderedPostSets.length * orderedPostSets.length;
815
for (let i = 0; i < orderedPostSets.length; i++) {
816
for (let j = i + 1; j < orderedPostSets.length; j++) {
817
if (orderedPostSets[j].symbol in orderedPostSets[i].dependencies) {
818
const temp = orderedPostSets[i];
819
orderedPostSets[i] = orderedPostSets[j];
820
orderedPostSets[j] = temp;
821
i--;
822
limit--;
823
assert(limit > 0, 'Could not sort postsets!');
824
break;
825
}
826
}
827
}
828
829
postSets.push(...orderedPostSets);
830
831
const shellFile = MINIMAL_RUNTIME ? 'shell_minimal.js' : 'shell.js';
832
includeSystemFile(shellFile);
833
834
const preFile = MINIMAL_RUNTIME ? 'preamble_minimal.js' : 'preamble.js';
835
includeSystemFile(preFile);
836
837
writeOutput('// Begin JS library code\n');
838
for (const item of libraryItems.concat(postSets)) {
839
writeOutput(indentify(item || '', 2));
840
}
841
writeOutput('// End JS library code\n');
842
843
if (!MINIMAL_RUNTIME) {
844
includeSystemFile('postlibrary.js');
845
}
846
847
if (PTHREADS) {
848
writeOutput(`
849
// proxiedFunctionTable specifies the list of functions that can be called
850
// either synchronously or asynchronously from other threads in postMessage()d
851
// or internally queued events. This way a pthread in a Worker can synchronously
852
// access e.g. the DOM on the main thread.
853
var proxiedFunctionTable = [
854
${proxiedFunctionTable.join(',\n ')}
855
];
856
`);
857
}
858
859
// This is the main 'post' pass. Print out the generated code
860
// that we have here, together with the rest of the output
861
// that we started to print out earlier (see comment on the
862
// "Final shape that will be created").
863
writeOutput('// EMSCRIPTEN_END_FUNCS\n');
864
865
const postFile = MINIMAL_RUNTIME ? 'postamble_minimal.js' : 'postamble.js';
866
includeSystemFile(postFile);
867
868
for (const fileName of POST_JS_FILES) {
869
includeFile(fileName);
870
}
871
872
if (MODULARIZE && MODULARIZE != 'instance') {
873
includeSystemFile('postamble_modularize.js');
874
}
875
876
if (errorOccured()) {
877
throw Error('Aborting compilation due to previous errors');
878
}
879
880
writeOutput(
881
'//FORWARDED_DATA:' +
882
JSON.stringify({
883
librarySymbols,
884
warnings: warningOccured(),
885
asyncFuncs,
886
libraryDefinitions: LibraryManager.libraryDefinitions,
887
ATPRERUNS: ATPRERUNS.join('\n'),
888
ATMODULES: ATMODULES.join('\n'),
889
ATINITS: ATINITS.join('\n'),
890
ATPOSTCTORS: ATPOSTCTORS.join('\n'),
891
ATMAINS: ATMAINS.join('\n'),
892
ATPOSTRUNS: ATPOSTRUNS.join('\n'),
893
ATEXITS: ATEXITS.join('\n'),
894
}),
895
);
896
}
897
898
for (const sym of symbolsNeeded) {
899
symbolHandler(sym);
900
}
901
902
if (symbolsOnly) {
903
writeOutput(
904
JSON.stringify({
905
deps: symbolDeps,
906
asyncFuncs,
907
extraLibraryFuncs,
908
}),
909
);
910
} else {
911
finalCombiner();
912
}
913
914
if (errorOccured()) {
915
throw Error('Aborting compilation due to previous errors');
916
}
917
918
if (outputFile) await outputHandle.close();
919
}
920
921
addToCompileTimeContext({
922
extraLibraryFuncs,
923
addedLibraryItems,
924
preJS,
925
});
926
927