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