Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/src/parseTools.mjs
4128 views
1
/**
2
* @license
3
* Copyright 2010 The Emscripten Authors
4
* SPDX-License-Identifier: MIT
5
*
6
* Helpers and tools for use at compile time by JavaScript library files.
7
*
8
* Tests live in test/other/test_parseTools.js.
9
*/
10
11
import * as path from 'node:path';
12
import {existsSync} from 'node:fs';
13
import assert from 'node:assert';
14
15
import {
16
addToCompileTimeContext,
17
error,
18
readFile,
19
runInMacroContext,
20
pushCurrentFile,
21
popCurrentFile,
22
localFile,
23
warn,
24
srcDir,
25
} from './utility.mjs';
26
27
const FOUR_GB = 4 * 1024 * 1024 * 1024;
28
const WASM_PAGE_SIZE = 64 * 1024;
29
const FLOAT_TYPES = new Set(['float', 'double']);
30
// Represents a browser version that is not supported at all.
31
const TARGET_NOT_SUPPORTED = 0x7fffffff;
32
33
// Does simple 'macro' substitution, using Django-like syntax,
34
// {{{ code }}} will be replaced with |eval(code)|.
35
// NOTE: Be careful with that ret check. If ret is |0|, |ret ? ret.toString() : ''| would result in ''!
36
export function processMacros(text, filename) {
37
// The `?` here in makes the regex non-greedy so it matches with the closest
38
// set of closing braces.
39
// `[\s\S]` works like `.` but include newline.
40
pushCurrentFile(filename);
41
try {
42
return text.replace(/{{{([\s\S]+?)}}}/g, (_, str) => {
43
const ret = runInMacroContext(str, {filename: filename});
44
return ret?.toString() ?? '';
45
});
46
} finally {
47
popCurrentFile();
48
}
49
}
50
51
function findIncludeFile(filename, currentDir) {
52
if (path.isAbsolute(filename)) {
53
return existsSync(filename) ? filename : null;
54
}
55
56
// Search for include files either relative to the including file,
57
// or in the src root directory.
58
const includePath = [currentDir, srcDir];
59
for (const p of includePath) {
60
const f = path.join(p, filename);
61
if (existsSync(f)) {
62
return f;
63
}
64
}
65
66
return null;
67
}
68
69
// Simple #if/else/endif preprocessing for a file. Checks if the
70
// ident checked is true in our global.
71
// Also handles #include x.js (similar to C #include <file>)
72
export function preprocess(filename) {
73
let text = readFile(filename);
74
if (EXPORT_ES6) {
75
// `eval`, Terser and Closure don't support module syntax; to allow it,
76
// we need to temporarily replace `import.meta` and `await import` usages
77
// with placeholders during preprocess phase, and back after all the other ops.
78
// See also: `phase_final_emitting` in emcc.py.
79
text = text
80
.replace(/\bimport\.meta\b/g, 'EMSCRIPTEN$IMPORT$META')
81
.replace(/\bawait import\b/g, 'EMSCRIPTEN$AWAIT$IMPORT');
82
}
83
if (MODULARIZE) {
84
// Same for out use of "top-level-await" which is not actually top level
85
// in the case of MODULARIZE.
86
text = text.replace(/\bawait createWasm\(\)/g, 'EMSCRIPTEN$AWAIT(createWasm())');
87
}
88
// Remove windows line endings, if any
89
text = text.replace(/\r\n/g, '\n');
90
91
const IGNORE = 0;
92
const SHOW = 1;
93
// This state is entered after we have shown one of the block of an if/elif/else sequence.
94
// Once we enter this state we dont show any blocks or evaluate any
95
// conditions until the sequence ends.
96
const IGNORE_ALL = 2;
97
const showStack = [];
98
const showCurrentLine = () => showStack.every((x) => x == SHOW);
99
100
const fileExt = filename.split('.').pop().toLowerCase();
101
const isHtml = fileExt === 'html' || fileExt === 'htm' ? true : false;
102
let inStyle = false;
103
const lines = text.split('\n');
104
// text.split yields an extra empty element at the end if text itself ends with a newline.
105
if (!lines[lines.length - 1]) {
106
lines.pop();
107
}
108
109
let ret = '';
110
let emptyLine = false;
111
112
pushCurrentFile(filename);
113
try {
114
for (let [i, line] of lines.entries()) {
115
if (isHtml) {
116
if (line.includes('<style') && !inStyle) {
117
inStyle = true;
118
}
119
if (line.includes('</style') && inStyle) {
120
inStyle = false;
121
}
122
if (inStyle) {
123
if (showCurrentLine()) {
124
ret += line + '\n';
125
}
126
continue;
127
}
128
}
129
130
const trimmed = line.trim();
131
if (trimmed.startsWith('#')) {
132
const first = trimmed.split(' ', 1)[0];
133
if (first == '#if' || first == '#ifdef' || first == '#elif') {
134
if (first == '#ifdef') {
135
warn('use of #ifdef in js library. Use #if instead.');
136
}
137
if (first == '#elif') {
138
const curr = showStack.pop();
139
if (curr == SHOW || curr == IGNORE_ALL) {
140
// If we showed to previous block we enter the IGNORE_ALL state
141
// and stay there until endif is seen
142
showStack.push(IGNORE_ALL);
143
continue;
144
}
145
}
146
const after = trimmed.substring(trimmed.indexOf(' '));
147
const truthy = !!runInMacroContext(after, {
148
filename,
149
lineOffset: i,
150
columnOffset: line.indexOf(after),
151
});
152
showStack.push(truthy ? SHOW : IGNORE);
153
} else if (first === '#include') {
154
if (showCurrentLine()) {
155
let includeFile = line.slice(line.indexOf(' ') + 1);
156
if (includeFile.startsWith('"')) {
157
includeFile = includeFile.slice(1, -1);
158
}
159
const absPath = findIncludeFile(includeFile, path.dirname(filename));
160
if (!absPath) {
161
error(`file not found: ${includeFile}`, i + 1);
162
continue;
163
}
164
const result = preprocess(absPath);
165
if (result) {
166
ret += `// include: ${includeFile}\n`;
167
ret += result;
168
ret += `// end include: ${includeFile}\n`;
169
}
170
}
171
} else if (first === '#else') {
172
if (showStack.length == 0) {
173
error('#else without matching #if', i + 1);
174
}
175
const curr = showStack.pop();
176
if (curr == IGNORE) {
177
showStack.push(SHOW);
178
} else {
179
showStack.push(IGNORE);
180
}
181
} else if (first === '#endif') {
182
if (showStack.length == 0) {
183
error('#endif without matching #if', i + 1);
184
}
185
showStack.pop();
186
} else if (first === '#warning') {
187
if (showCurrentLine()) {
188
warn(`#warning ${trimmed.substring(trimmed.indexOf(' ')).trim()}`, i + 1);
189
}
190
} else if (first === '#error') {
191
if (showCurrentLine()) {
192
error(`#error ${trimmed.substring(trimmed.indexOf(' ')).trim()}`, i + 1);
193
}
194
} else if (first === '#preprocess') {
195
// Do nothing
196
} else {
197
error(`Unknown preprocessor directive ${first}`, i + 1);
198
}
199
} else {
200
if (showCurrentLine()) {
201
// Never emit more than one empty line at a time.
202
if (emptyLine && !line) {
203
continue;
204
}
205
ret += line + '\n';
206
if (!line) {
207
emptyLine = true;
208
} else {
209
emptyLine = false;
210
}
211
}
212
}
213
}
214
assert(
215
showStack.length == 0,
216
`preprocessing error in file ${filename}, \
217
no matching #endif found (${showStack.length$}' unmatched preprocessing directives on stack)`,
218
);
219
return ret;
220
} finally {
221
popCurrentFile();
222
}
223
}
224
225
// Returns true if ident is a niceIdent (see toNiceIdent). Also allow () and spaces.
226
function isNiceIdent(ident) {
227
return /^\(?[$_]+[\w$_\d ]*\)?$/.test(ident);
228
}
229
230
export const POINTER_SIZE = MEMORY64 ? 8 : 4;
231
const POINTER_MAX = MEMORY64 ? 'Number.MAX_SAFE_INTEGER' : '0xFFFFFFFF';
232
const STACK_ALIGN = 16;
233
const POINTER_BITS = POINTER_SIZE * 8;
234
const POINTER_TYPE = `u${POINTER_BITS}`;
235
const POINTER_JS_TYPE = MEMORY64 ? "'bigint'" : "'number'";
236
const POINTER_SHIFT = MEMORY64 ? '3' : '2';
237
const POINTER_HEAP = MEMORY64 ? 'HEAP64' : 'HEAP32';
238
const LONG_TYPE = `i${POINTER_BITS}`;
239
240
const SIZE_TYPE = POINTER_TYPE;
241
242
// Similar to POINTER_TYPE, but this is the actual wasm type that is
243
// used in practice, while POINTER_TYPE is the more refined internal
244
// type (that is unsigned, where as core wasm does not have unsigned
245
// types).
246
const POINTER_WASM_TYPE = `i${POINTER_BITS}`;
247
248
function isPointerType(type) {
249
return type.endsWith('*');
250
}
251
252
// Given an expression like (VALUE=VALUE*2,VALUE<10?VALUE:t+1) , this will
253
// replace VALUE with value. If value is not a simple identifier of a variable,
254
// value will be replaced with tempVar.
255
function makeInlineCalculation(expression, value, tempVar) {
256
if (!isNiceIdent(value)) {
257
expression = `${tempVar} = ${value},${expression}`;
258
value = tempVar;
259
}
260
return `(${expression.replace(/VALUE/g, value)})`;
261
}
262
263
// XXX Make all i64 parts signed
264
265
// Splits a number (an integer in a double, possibly > 32 bits) into an i64
266
// value, represented by a low and high i32 pair.
267
// Will suffer from rounding and truncation.
268
function splitI64(value) {
269
if (WASM_BIGINT) {
270
// Nothing to do: just make sure it is a BigInt (as it must be of that
271
// type, to be sent into wasm).
272
return `BigInt(${value})`;
273
}
274
275
// general idea:
276
//
277
// $1$0 = ~~$d >>> 0;
278
// $1$1 = Math.abs($d) >= 1 ? (
279
// $d > 0 ? Math.floor(($d)/ 4294967296.0) >>> 0
280
// : Math.ceil(Math.min(-4294967296.0, $d - $1$0)/ 4294967296.0)
281
// ) : 0;
282
//
283
// We need to min on positive values here, since our input might be a double,
284
// and large values are rounded, so they can be slightly higher than expected.
285
// And if we get 4294967296, that will turn into a 0 if put into a HEAP32 or
286
// |0'd, etc.
287
//
288
// For negatives, we need to ensure a -1 if the value is overall negative,
289
// even if not significant negative component
290
291
const low = value + '>>>0';
292
// prettier-ignore
293
const high = makeInlineCalculation(
294
asmCoercion('Math.abs(VALUE)', 'double') + ' >= ' + asmEnsureFloat('1', 'double') + ' ? ' +
295
'(VALUE > ' + asmEnsureFloat('0', 'double') + ' ? ' +
296
asmCoercion('Math.floor((VALUE)/' +
297
asmEnsureFloat(4294967296, 'double') + ')', 'double') + '>>>0' +
298
' : ' +
299
asmFloatToInt(asmCoercion('Math.ceil((VALUE - +((' + asmFloatToInt('VALUE') + ')>>>0))/' +
300
asmEnsureFloat(4294967296, 'double') + ')', 'double')) + '>>>0' +
301
')' +
302
' : 0',
303
value,
304
'tempDouble',
305
);
306
return [low, high];
307
}
308
309
// Misc
310
311
export function indentify(text, indent) {
312
// Don't try to indentify huge strings - we may run out of memory
313
if (text.length > 1024 * 1024) return text;
314
if (typeof indent == 'number') {
315
const len = indent;
316
indent = '';
317
for (let i = 0; i < len; i++) {
318
indent += ' ';
319
}
320
}
321
return text.replace(/\n/g, `\n${indent}`);
322
}
323
324
// Correction tools
325
326
function getNativeTypeSize(type) {
327
// prettier-ignore
328
switch (type) {
329
case 'i1': case 'i8': case 'u8': return 1;
330
case 'i16': case 'u16': return 2;
331
case 'i32': case 'u32': return 4;
332
case 'i64': case 'u64': return 8;
333
case 'float': return 4;
334
case 'double': return 8;
335
default: {
336
if (type.endsWith('*')) {
337
return POINTER_SIZE;
338
}
339
if (type[0] === 'i') {
340
const bits = Number(type.slice(1));
341
// [FIXME] Cannot use assert here since this function is included directly
342
// in the runtime JS library, where assert is not always available.
343
// assert(bits % 8 === 0, `getNativeTypeSize invalid bits ${bits}, ${type} type`);
344
return bits / 8;
345
}
346
return 0;
347
}
348
}
349
}
350
351
function getHeapOffset(offset, type) {
352
const sz = getNativeTypeSize(type);
353
if (sz == 1) {
354
return offset;
355
}
356
if (MEMORY64 == 1) {
357
return `((${offset})/${sz})`;
358
}
359
const shifts = Math.log(sz) / Math.LN2;
360
if (CAN_ADDRESS_2GB) {
361
return `((${offset})>>>${shifts})`;
362
}
363
return `((${offset})>>${shifts})`;
364
}
365
366
function ensureDot(value) {
367
value = value.toString();
368
// if already dotted, or Infinity or NaN, nothing to do here
369
// if smaller than 1 and running js opts, we always need to force a coercion
370
// (0.001 will turn into 1e-3, which has no .)
371
if (value.includes('.') || /[IN]/.test(value)) return value;
372
const e = value.indexOf('e');
373
if (e < 0) return value + '.0';
374
return value.slice(0, e) + '.0' + value.slice(e);
375
}
376
377
export function isNumber(x) {
378
// XXX this does not handle 0xabc123 etc. We should likely also do x == parseInt(x) (which handles that), and remove hack |// handle 0x... as well|
379
return x == parseFloat(x) || (typeof x == 'string' && x.match(/^-?\d+$/)) || x == 'NaN';
380
}
381
382
// ensures that a float type has either 5.5 (clearly a float) or +5 (float due to asm coercion)
383
function asmEnsureFloat(value, type) {
384
if (!isNumber(value)) return value;
385
if (type === 'float') {
386
// normally ok to just emit Math.fround(0), but if the constant is large we
387
// may need a .0 (if it can't fit in an int)
388
if (value == 0) return 'Math.fround(0)';
389
value = ensureDot(value);
390
return `Math.fround(${value})`;
391
}
392
if (FLOAT_TYPES.has(type)) {
393
return ensureDot(value);
394
}
395
return value;
396
}
397
398
function asmCoercion(value, type) {
399
assert(arguments.length == 2, 'asmCoercion takes exactly two arguments');
400
if (type == 'void') {
401
return value;
402
}
403
if (FLOAT_TYPES.has(type)) {
404
if (isNumber(value)) {
405
return asmEnsureFloat(value, type);
406
}
407
if (type === 'float') {
408
return `Math.fround(${value})`;
409
}
410
return `(+(${value}))`;
411
}
412
return `((${value})|0)`;
413
}
414
415
function asmFloatToInt(x) {
416
return `(~~(${x}))`;
417
}
418
419
// See makeSetValue
420
function makeGetValue(ptr, pos, type) {
421
assert(arguments.length == 3, 'makeGetValue expects 3 arguments');
422
423
const offset = calcFastOffset(ptr, pos);
424
if (type === 'i53' || type === 'u53') {
425
// Set `unsigned` based on the type name.
426
const unsigned = type.startsWith('u');
427
return `readI53From${unsigned ? 'U' : 'I'}64(${offset})`;
428
}
429
430
const slab = getHeapForType(type);
431
let ret = `${slab}[${getHeapOffset(offset, type)}]`;
432
if (MEMORY64 && isPointerType(type)) {
433
ret = `Number(${ret})`;
434
}
435
return ret;
436
}
437
438
/**
439
* @param {number} ptr The pointer. Used to find both the slab and the offset in that slab. If the pointer
440
* is just an integer, then this is almost redundant, but in general the pointer type
441
* may in the future include information about which slab as well. So, for now it is
442
* possible to put |0| here, but if a pointer is available, that is more future-proof.
443
* @param {number} pos The position in that slab - the offset. Added to any offset in the pointer itself.
444
* @param {number} value The value to set.
445
* @param {string} type A string defining the type. Used to find the slab (HEAPU8, HEAP16, HEAPU32, etc.).
446
* which means we should write to all slabs, ignore type differences if any on reads, etc.
447
* @return {string} JS code for performing the memory set operation
448
*/
449
function makeSetValue(ptr, pos, value, type) {
450
var rtn = makeSetValueImpl(ptr, pos, value, type);
451
if (ASSERTIONS == 2 && (type.startsWith('i') || type.startsWith('u'))) {
452
const width = getBitWidth(type);
453
const assertion = `checkInt${width}(${value})`;
454
rtn += `;${assertion}`;
455
}
456
return rtn;
457
}
458
459
function makeSetValueImpl(ptr, pos, value, type) {
460
if (type == 'i64' && !WASM_BIGINT) {
461
// If we lack BigInt support we must fall back to an reading a pair of I32
462
// values.
463
// prettier-ignore
464
return '(tempI64 = [' + splitI64(value) + '], ' +
465
makeSetValueImpl(ptr, pos, 'tempI64[0]', 'i32') + ',' +
466
makeSetValueImpl(ptr, getFastValue(pos, '+', getNativeTypeSize('i32')), 'tempI64[1]', 'i32') + ')';
467
}
468
469
const offset = calcFastOffset(ptr, pos);
470
471
if (type === 'i53') {
472
return `writeI53ToI64(${offset}, ${value})`;
473
}
474
475
const slab = getHeapForType(type);
476
if (slab == 'HEAPU64' || slab == 'HEAP64') {
477
value = `BigInt(${value})`;
478
}
479
return `${slab}[${getHeapOffset(offset, type)}] = ${value}`;
480
}
481
482
function makeHEAPView(which, start, end) {
483
// The makeHEAPView, for legacy reasons, takes a heap "suffix"
484
// rather than the heap "type" that used by other APIs here.
485
const type = {
486
8: 'i8',
487
U8: 'u8',
488
16: 'i16',
489
U16: 'u16',
490
32: 'i32',
491
U32: 'u32',
492
64: 'i64',
493
U64: 'u64',
494
F32: 'float',
495
F64: 'double',
496
}[which];
497
const heap = getHeapForType(type);
498
start = getHeapOffset(start, type);
499
end = getHeapOffset(end, type);
500
return `${heap}.subarray((${start}), ${end})`;
501
}
502
503
// Given two values and an operation, returns the result of that operation.
504
// Tries to do as much as possible at compile time.
505
function getFastValue(a, op, b) {
506
// In the past we supported many operations, but today we only use addition.
507
assert(op == '+');
508
509
// Convert 'true' and 'false' to '1' and '0'.
510
a = a === 'true' ? '1' : a === 'false' ? '0' : a;
511
b = b === 'true' ? '1' : b === 'false' ? '0' : b;
512
513
let aNumber = null;
514
let bNumber = null;
515
if (typeof a == 'number') {
516
aNumber = a;
517
a = a.toString();
518
} else if (isNumber(a)) {
519
aNumber = parseFloat(a);
520
}
521
if (typeof b == 'number') {
522
bNumber = b;
523
b = b.toString();
524
} else if (isNumber(b)) {
525
bNumber = parseFloat(b);
526
}
527
528
// First check if we can do the addition at compile time
529
if (aNumber !== null && bNumber !== null) {
530
return (aNumber + bNumber).toString();
531
}
532
533
// If one of them is a number, keep it last
534
if (aNumber !== null) {
535
const c = b;
536
b = a;
537
a = c;
538
const cNumber = bNumber;
539
bNumber = aNumber;
540
aNumber = cNumber;
541
}
542
543
if (aNumber === 0) {
544
return b;
545
} else if (bNumber === 0) {
546
return a;
547
}
548
549
if (b[0] === '-') {
550
op = '-';
551
b = b.slice(1);
552
}
553
554
return `(${a})${op}(${b})`;
555
}
556
557
function calcFastOffset(ptr, pos) {
558
return getFastValue(ptr, '+', pos);
559
}
560
561
function getBitWidth(type) {
562
if (type == 'i53' || type == 'u53') return 53;
563
return getNativeTypeSize(type) * 8;
564
}
565
566
function getHeapForType(type) {
567
assert(type);
568
if (isPointerType(type)) {
569
type = POINTER_TYPE;
570
}
571
if (WASM_BIGINT) {
572
switch (type) {
573
case 'i64':
574
return 'HEAP64';
575
case 'u64':
576
return 'HEAPU64';
577
}
578
}
579
// prettier-ignore
580
switch (type) {
581
case 'i1': // fallthrough
582
case 'i8': return 'HEAP8';
583
case 'u8': return 'HEAPU8';
584
case 'i16': return 'HEAP16';
585
case 'u16': return 'HEAPU16';
586
case 'i32': return 'HEAP32';
587
case 'u32': return 'HEAPU32';
588
case 'double': return 'HEAPF64';
589
case 'float': return 'HEAPF32';
590
case 'i64': // fallthrough
591
case 'u64': error('use i53/u53, or avoid i64/u64 without WASM_BIGINT');
592
}
593
assert(false, `bad heap type: ${type}`);
594
}
595
596
export function makeReturn64(value) {
597
if (WASM_BIGINT) {
598
return `BigInt(${value})`;
599
}
600
const pair = splitI64(value);
601
// `return (a, b, c)` in JavaScript will execute `a`, and `b` and return the final
602
// element `c`
603
return `(setTempRet0(${pair[1]}), ${pair[0]})`;
604
}
605
606
function makeThrow(excPtr) {
607
if (ASSERTIONS && DISABLE_EXCEPTION_CATCHING) {
608
var assertInfo =
609
'Exception thrown, but exception catching is not enabled. Compile with -sNO_DISABLE_EXCEPTION_CATCHING or -sEXCEPTION_CATCHING_ALLOWED=[..] to catch.';
610
if (MAIN_MODULE) {
611
assertInfo +=
612
' (note: in dynamic linking, if a side module wants exceptions, the main module must be built with that support)';
613
}
614
return `assert(false, '${assertInfo}');`;
615
}
616
return `throw ${excPtr};`;
617
}
618
619
function storeException(varName, excPtr) {
620
var exceptionToStore = EXCEPTION_STACK_TRACES ? `new CppException(${excPtr})` : `${excPtr}`;
621
return `${varName} = ${exceptionToStore};`;
622
}
623
624
function charCode(char) {
625
return char.charCodeAt(0);
626
}
627
628
function makeDynCall(sig, funcPtr, promising = false) {
629
assert(
630
!sig.includes('j'),
631
'Cannot specify 64-bit signatures ("j" in signature string) with makeDynCall!',
632
);
633
assert(!(DYNCALLS && promising), 'DYNCALLS cannot be used with JSPI.');
634
635
let args = [];
636
for (let i = 1; i < sig.length; ++i) {
637
args.push(`a${i}`);
638
}
639
args = args.join(', ');
640
641
const needArgConversion = MEMORY64 && sig.includes('p');
642
let callArgs = args;
643
if (needArgConversion) {
644
callArgs = [];
645
for (let i = 1; i < sig.length; ++i) {
646
if (sig[i] == 'p') {
647
callArgs.push(`BigInt(a${i})`);
648
} else {
649
callArgs.push(`a${i}`);
650
}
651
}
652
callArgs = callArgs.join(', ');
653
}
654
655
// Normalize any 'p' characters to either 'j' (wasm64) or 'i' (wasm32)
656
if (sig.includes('p')) {
657
let normalizedSig = '';
658
for (let sigChr of sig) {
659
if (sigChr == 'p') {
660
sigChr = MEMORY64 ? 'j' : 'i';
661
}
662
normalizedSig += sigChr;
663
}
664
sig = normalizedSig;
665
}
666
667
if (funcPtr === undefined) {
668
warn(`
669
Legacy use of {{{ makeDynCall("${sig}") }}}(funcPtr, arg1, arg2, ...). \
670
Starting from Emscripten 2.0.2 (Aug 31st 2020), syntax for makeDynCall has changed. \
671
New syntax is {{{ makeDynCall("${sig}", "funcPtr") }}}(arg1, arg2, ...). \
672
Please update to new syntax.`);
673
674
if (DYNCALLS) {
675
if (!hasExportedSymbol(`dynCall_${sig}`)) {
676
if (ASSERTIONS) {
677
return `((${args}) => { throw 'Internal Error! Attempted to invoke wasm function pointer with signature "${sig}", but no such functions have gotten exported!' })`;
678
} else {
679
return `((${args}) => {} /* a dynamic function call to signature ${sig}, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */)`;
680
}
681
}
682
return `((cb, ${args}) => getDynCaller("${sig}", cb)(${callArgs}))`;
683
} else {
684
return `((cb, ${args}) => getWasmTableEntry(cb)(${callArgs}))`;
685
}
686
}
687
688
if (DYNCALLS) {
689
if (!hasExportedSymbol(`dynCall_${sig}`)) {
690
if (ASSERTIONS) {
691
return `((${args}) => { throw 'Internal Error! Attempted to invoke wasm function pointer with signature "${sig}", but no such functions have gotten exported!' })`;
692
} else {
693
return `((${args}) => {} /* a dynamic function call to signature ${sig}, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */)`;
694
}
695
}
696
697
const dyncall = `dynCall_${sig}`;
698
if (sig.length > 1) {
699
return `((${args}) => ${dyncall}(${funcPtr}, ${callArgs}))`;
700
}
701
return `(() => ${dyncall}(${funcPtr}))`;
702
}
703
704
let getWasmTableEntry = `getWasmTableEntry(${funcPtr})`;
705
if (promising) {
706
getWasmTableEntry = `WebAssembly.promising(${getWasmTableEntry})`;
707
}
708
709
if (needArgConversion) {
710
return `((${args}) => ${getWasmTableEntry}.call(null, ${callArgs}))`;
711
}
712
return getWasmTableEntry;
713
}
714
715
function makeEval(code) {
716
if (DYNAMIC_EXECUTION == 0) {
717
// Treat eval as error.
718
return "abort('DYNAMIC_EXECUTION=0 was set, cannot eval');";
719
}
720
let ret = '';
721
if (DYNAMIC_EXECUTION == 2) {
722
// Warn on evals, but proceed.
723
ret +=
724
"err('Warning: DYNAMIC_EXECUTION=2 was set, but calling eval in the following location:');\n";
725
ret += 'err(stackTrace());\n';
726
}
727
ret += code;
728
return ret;
729
}
730
731
// Add code that runs before the wasm modules is loaded. This is the first
732
// point at which the global `Module` object is guaranteed to exist. This hook
733
// is mostly used to read incoming `Module` properties.
734
export const ATMODULES = [];
735
function addAtModule(code) {
736
ATMODULES.push(code);
737
}
738
739
// Add code to run soon after the Wasm module has been loaded. This is the first
740
// injection point before all the other addAt<X> functions below. The code will
741
// be executed after the runtime `onPreRuns` callbacks.
742
export const ATPRERUNS = [];
743
function addAtPreRun(code) {
744
ATPRERUNS.push(code);
745
}
746
747
// Add code to run after the Wasm module is loaded, but before static
748
// constructors and main (if applicable). The code will be executed after the
749
// runtime `onInits` callbacks.
750
export const ATINITS = [];
751
function addAtInit(code) {
752
ATINITS.push(code);
753
}
754
755
// Add code to run after static constructors, but before main (if applicable).
756
// The code will be executed after the runtime `onPostCtors` callbacks.
757
export const ATPOSTCTORS = [];
758
function addAtPostCtor(code) {
759
ATPOSTCTORS.push(code);
760
}
761
762
// Add code to run right before main is called. This is only available if the
763
// the Wasm module has a main function. The code will be executed after the
764
// runtime `onMains` callbacks.
765
export const ATMAINS = [];
766
function addAtPreMain(code) {
767
ATMAINS.push(code);
768
}
769
770
// Add code to run after main has executed and the runtime is shutdown. This is
771
// only available when the Wasm module has a main function and -sEXIT_RUNTIME is
772
// set. The code will be executed after the runtime `onExits` callbacks.
773
export const ATEXITS = [];
774
function addAtExit(code) {
775
if (EXIT_RUNTIME) {
776
ATEXITS.push(code);
777
}
778
}
779
780
// Add code to run after main and ATEXITS (if applicable). The code will be
781
// executed after the runtime `onPostRuns` callbacks.
782
export const ATPOSTRUNS = [];
783
function addAtPostRun(code) {
784
ATPOSTRUNS.push(code);
785
}
786
787
function makeRetainedCompilerSettings() {
788
const ignore = new Set();
789
if (STRICT) {
790
for (const setting of LEGACY_SETTINGS) {
791
ignore.add(setting);
792
}
793
}
794
795
const ret = {};
796
for (const x in global) {
797
if (!ignore.has(x) && x[0] !== '_' && x == x.toUpperCase()) {
798
const value = global[x];
799
if (
800
typeof value == 'number' ||
801
typeof value == 'boolean' ||
802
typeof value == 'string' ||
803
Array.isArray(x)
804
) {
805
ret[x] = value;
806
}
807
}
808
}
809
return ret;
810
}
811
812
// Receives a function as text, and a function that constructs a modified
813
// function, to which we pass the parsed-out arguments, body, and possible
814
// "async" prefix of the input function. Returns the output of that function.
815
export function modifyJSFunction(text, func) {
816
// Match a function with a name.
817
let async_;
818
let args;
819
let rest;
820
let oneliner = false;
821
let match = text.match(/^\s*(async\s+)?function\s+([^(]*)?\s*\(([^)]*)\)/);
822
if (match) {
823
async_ = match[1] || '';
824
args = match[3];
825
rest = text.slice(match[0].length);
826
} else {
827
// Match an arrow function
828
let match = text.match(/^\s*(var (\w+) = )?(async\s+)?\(([^)]*)\)\s+=>\s+/);
829
if (match) {
830
async_ = match[3] || '';
831
args = match[4];
832
rest = text.slice(match[0].length);
833
rest = rest.trim();
834
oneliner = rest[0] != '{';
835
} else {
836
// Match a function without a name (we could probably use a single regex
837
// for both, but it would be more complex).
838
match = text.match(/^\s*(async\s+)?function\(([^)]*)\)/);
839
assert(match, `could not match function:\n${text}\n`);
840
async_ = match[1] || '';
841
args = match[2];
842
rest = text.slice(match[0].length);
843
}
844
}
845
let body = rest;
846
if (!oneliner) {
847
const bodyStart = rest.indexOf('{');
848
const bodyEnd = rest.lastIndexOf('}');
849
assert(bodyEnd > 0);
850
body = rest.substring(bodyStart + 1, bodyEnd);
851
}
852
return func(args, body, async_, oneliner);
853
}
854
855
export function runIfMainThread(text) {
856
if (WASM_WORKERS || PTHREADS) {
857
return `if (${ENVIRONMENT_IS_MAIN_THREAD()}) { ${text} }`;
858
} else {
859
return text;
860
}
861
}
862
863
function runIfWorkerThread(text) {
864
if (WASM_WORKERS || PTHREADS) {
865
return `if (${ENVIRONMENT_IS_WORKER_THREAD()}) { ${text} }`;
866
} else {
867
return '';
868
}
869
}
870
871
function expectToReceiveOnModule(name) {
872
return INCOMING_MODULE_JS_API.has(name);
873
}
874
875
// Return true if the user requested that a library symbol be included
876
// either via DEFAULT_LIBRARY_FUNCS_TO_INCLUDE or EXPORTED_RUNTIME_METHODS.
877
function isSymbolNeeded(symName) {
878
if (DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.includes(symName)) {
879
return true;
880
}
881
if (symName.startsWith('$') && EXPORTED_RUNTIME_METHODS.has(symName.slice(1))) {
882
return true;
883
}
884
return false;
885
}
886
887
function checkReceiving(name) {
888
// ALL_INCOMING_MODULE_JS_API contains all valid incoming module API symbols
889
// so calling makeModuleReceive* with a symbol not in this list is an error
890
assert(ALL_INCOMING_MODULE_JS_API.has(name), `${name} is not part of INCOMING_MODULE_JS_API`);
891
}
892
893
// Make code to receive a value on the incoming Module object.
894
function makeModuleReceive(localName, moduleName) {
895
moduleName ||= localName;
896
checkReceiving(moduleName);
897
let ret = '';
898
if (expectToReceiveOnModule(moduleName)) {
899
// Usually the local we use is the same as the Module property name,
900
// but sometimes they must differ.
901
ret = `if (Module['${moduleName}']) ${localName} = Module['${moduleName}'];`;
902
}
903
return ret;
904
}
905
906
function makeModuleReceiveExpr(name, defaultValue) {
907
checkReceiving(name);
908
if (expectToReceiveOnModule(name)) {
909
return `Module['${name}'] || ${defaultValue}`;
910
} else {
911
return `${defaultValue}`;
912
}
913
}
914
915
function makeModuleReceiveWithVar(localName, moduleName, defaultValue) {
916
moduleName ||= localName;
917
checkReceiving(moduleName);
918
let ret = `var ${localName}`;
919
if (defaultValue) {
920
ret += ` = ${defaultValue}`;
921
}
922
ret += ';';
923
if (expectToReceiveOnModule(moduleName)) {
924
addAtModule(`if (Module['${moduleName}']) ${localName} = Module['${moduleName}'];`);
925
}
926
return ret;
927
}
928
929
function makeRemovedFSAssert(fsName) {
930
assert(ASSERTIONS);
931
const lower = fsName.toLowerCase();
932
if (JS_LIBRARIES.includes(localFile(path.join('lib', `lib${lower}.js`)))) return '';
933
return `var ${fsName} = '${fsName} is no longer included by default; build with -l${lower}.js';`;
934
}
935
936
// Given an array of elements [elem1,elem2,elem3], returns a string "['elem1','elem2','elem3']"
937
function buildStringArray(array) {
938
if (array.length > 0) {
939
return "['" + array.join("','") + "']";
940
} else {
941
return '[]';
942
}
943
}
944
945
function hasExportedSymbol(sym) {
946
return WASM_EXPORTS.has(sym);
947
}
948
949
// Called when global runtime symbols such as wasmMemory, wasmExports and
950
// wasmTable are set. In this case we maybe need to re-export them on the
951
// Module object.
952
function receivedSymbol(sym) {
953
if (EXPORTED_RUNTIME_METHODS.has(sym)) {
954
return `Module['${sym}'] = ${sym};`;
955
}
956
return '';
957
}
958
959
// JS API I64 param handling: if we have BigInt support, the ABI is simple,
960
// it is a BigInt. Otherwise, we legalize into pairs of i32s.
961
export function defineI64Param(name) {
962
if (WASM_BIGINT) {
963
return name;
964
}
965
return `${name}_low, ${name}_high`;
966
}
967
968
export function receiveI64ParamAsI53(name, onError, handleErrors = true) {
969
var errorHandler = handleErrors ? `if (isNaN(${name})) { return ${onError}; }` : '';
970
if (WASM_BIGINT) {
971
// Just convert the bigint into a double.
972
return `${name} = bigintToI53Checked(${name});${errorHandler}`;
973
}
974
// Convert the high/low pair to a Number, checking for
975
// overflow of the I53 range and returning onError in that case.
976
return `var ${name} = convertI32PairToI53Checked(${name}_low, ${name}_high);${errorHandler}`;
977
}
978
979
function receiveI64ParamAsI53Unchecked(name) {
980
if (WASM_BIGINT) {
981
return `${name} = Number(${name});`;
982
}
983
return `var ${name} = convertI32PairToI53(${name}_low, ${name}_high);`;
984
}
985
986
// Convert a pointer value under wasm64 from BigInt (used at local level API
987
// level) to Number (used in JS library code). No-op under wasm32.
988
function from64(x) {
989
if (!MEMORY64) return '';
990
return `${x} = Number(${x});`;
991
}
992
993
// Like from64 above but generate an expression instead of an assignment
994
// statement.
995
function from64Expr(x) {
996
if (!MEMORY64) return x;
997
return `Number(${x})`;
998
}
999
1000
// Converts a value to BigInt if building for wasm64, with both 64-bit pointers
1001
// and 64-bit memory. Used for indices into the memory tables, for example.
1002
function toIndexType(x) {
1003
if (MEMORY64 == 1) return `BigInt(${x})`;
1004
return x;
1005
}
1006
1007
// Converts a value to BigInt if building for wasm64, regardless of whether the
1008
// memory is 32- or 64-bit. Used for passing pointer-width values to native
1009
// code (since pointers are presented as Number in JS and BigInt in wasm we need
1010
// this conversion before passing them).
1011
function to64(x) {
1012
if (!MEMORY64) return x;
1013
return `BigInt(${x})`;
1014
}
1015
1016
function asyncIf(condition) {
1017
return condition ? 'async ' : '';
1018
}
1019
1020
function awaitIf(condition) {
1021
return condition ? 'await ' : '';
1022
}
1023
1024
// Adds a call to runtimeKeepalivePush, if needed by the current build
1025
// configuration.
1026
// We skip this completely in MINIMAL_RUNTIME and also in builds that
1027
// don't ever need to exit the runtime.
1028
function runtimeKeepalivePush() {
1029
if (MINIMAL_RUNTIME || (EXIT_RUNTIME == 0 && PTHREADS == 0)) return '';
1030
return 'runtimeKeepalivePush();';
1031
}
1032
1033
// Adds a call to runtimeKeepalivePush, if needed by the current build
1034
// configuration.
1035
// We skip this completely in MINIMAL_RUNTIME and also in builds that
1036
// don't ever need to exit the runtime.
1037
function runtimeKeepalivePop() {
1038
if (MINIMAL_RUNTIME || (EXIT_RUNTIME == 0 && PTHREADS == 0)) return '';
1039
return 'runtimeKeepalivePop();';
1040
}
1041
1042
// Some web functions like TextDecoder.decode() may not work with a view of a
1043
// SharedArrayBuffer, see https://github.com/whatwg/encoding/issues/172
1044
// To avoid that, this function allows obtaining an unshared copy of an
1045
// ArrayBuffer.
1046
function getUnsharedTextDecoderView(heap, start, end) {
1047
const shared = `${heap}.slice(${start}, ${end})`;
1048
const unshared = `${heap}.subarray(${start}, ${end})`;
1049
1050
// No need to worry about this in non-shared memory builds
1051
if (!SHARED_MEMORY) return unshared;
1052
1053
// If asked to get an unshared view to what we know will be a shared view, or if in -Oz,
1054
// then unconditionally do a .slice() for smallest code size.
1055
if (SHRINK_LEVEL == 2 || heap == 'HEAPU8') return shared;
1056
1057
// Otherwise, generate a runtime type check: must do a .slice() if looking at
1058
// a SAB, or can use .subarray() otherwise. Note: We compare with
1059
// `ArrayBuffer` here to avoid referencing `SharedArrayBuffer` which could be
1060
// undefined.
1061
return `${heap}.buffer instanceof ArrayBuffer ? ${unshared} : ${shared}`;
1062
}
1063
1064
function getEntryFunction() {
1065
var entryFunction = 'main';
1066
if (STANDALONE_WASM) {
1067
if (EXPECT_MAIN) {
1068
entryFunction = '_start';
1069
} else {
1070
entryFunction = '_initialize';
1071
}
1072
} else if (PROXY_TO_PTHREAD) {
1073
// User requested the PROXY_TO_PTHREAD option, so call a stub main which pthread_create()s a new thread
1074
// that will call the user's real main() for the application.
1075
entryFunction = '_emscripten_proxy_main';
1076
}
1077
if (MAIN_MODULE) {
1078
return `resolveGlobalSymbol('${entryFunction}').sym;`;
1079
}
1080
return `_${entryFunction}`;
1081
}
1082
1083
function formattedMinNodeVersion() {
1084
var major = MIN_NODE_VERSION / 10000;
1085
var minor = (MIN_NODE_VERSION / 100) % 100;
1086
var rev = MIN_NODE_VERSION % 100;
1087
return `v${major}.${minor}.${rev}`;
1088
}
1089
1090
function getPerformanceNow() {
1091
if (DETERMINISTIC) {
1092
return 'deterministicNow';
1093
} else {
1094
return 'performance.now';
1095
}
1096
}
1097
1098
function ENVIRONMENT_IS_MAIN_THREAD() {
1099
return `(!${ENVIRONMENT_IS_WORKER_THREAD()})`;
1100
}
1101
1102
function ENVIRONMENT_IS_WORKER_THREAD() {
1103
assert(PTHREADS || WASM_WORKERS);
1104
var envs = [];
1105
if (PTHREADS) envs.push('ENVIRONMENT_IS_PTHREAD');
1106
if (WASM_WORKERS) envs.push('ENVIRONMENT_IS_WASM_WORKER');
1107
return '(' + envs.join('||') + ')';
1108
}
1109
1110
function nodeDetectionCode() {
1111
if (ENVIRONMENT == 'node') {
1112
// The only environment where this code is intended to run is Node.js.
1113
// Return unconditional true so that later Closure optimizer will be able to
1114
// optimize code size.
1115
return 'true';
1116
}
1117
return "typeof process == 'object' && process.versions?.node && process.type != 'renderer'";
1118
}
1119
1120
function nodePthreadDetection() {
1121
// Under node we detect that we are running in a pthread by checking the
1122
// workerData property.
1123
if (EXPORT_ES6) {
1124
return "(await import('worker_threads')).workerData === 'em-pthread'";
1125
} else {
1126
return "require('worker_threads').workerData === 'em-pthread'";
1127
}
1128
}
1129
1130
function nodeWWDetection() {
1131
// Under node we detect that we are running in a wasm worker by checking the
1132
// workerData property.
1133
if (EXPORT_ES6) {
1134
return "(await import('worker_threads')).workerData === 'em-ww'";
1135
} else {
1136
return "require('worker_threads').workerData === 'em-ww'";
1137
}
1138
}
1139
1140
addToCompileTimeContext({
1141
ATEXITS,
1142
ATPRERUNS,
1143
ATINITS,
1144
ATPOSTCTORS,
1145
ATMAINS,
1146
ATPOSTRUNS,
1147
FOUR_GB,
1148
LONG_TYPE,
1149
POINTER_HEAP,
1150
POINTER_BITS,
1151
POINTER_JS_TYPE,
1152
POINTER_MAX,
1153
POINTER_SHIFT,
1154
POINTER_SIZE,
1155
POINTER_TYPE,
1156
POINTER_WASM_TYPE,
1157
SIZE_TYPE,
1158
STACK_ALIGN,
1159
TARGET_NOT_SUPPORTED,
1160
WASM_PAGE_SIZE,
1161
ENVIRONMENT_IS_MAIN_THREAD,
1162
ENVIRONMENT_IS_WORKER_THREAD,
1163
addAtExit,
1164
addAtPreRun,
1165
addAtModule,
1166
addAtInit,
1167
addAtPostCtor,
1168
addAtPreMain,
1169
addAtPostRun,
1170
asyncIf,
1171
awaitIf,
1172
buildStringArray,
1173
charCode,
1174
defineI64Param,
1175
expectToReceiveOnModule,
1176
formattedMinNodeVersion,
1177
from64,
1178
from64Expr,
1179
getEntryFunction,
1180
getHeapForType,
1181
getHeapOffset,
1182
getNativeTypeSize,
1183
getPerformanceNow,
1184
getUnsharedTextDecoderView,
1185
hasExportedSymbol,
1186
isSymbolNeeded,
1187
makeDynCall,
1188
makeEval,
1189
makeGetValue,
1190
makeHEAPView,
1191
makeModuleReceive,
1192
makeModuleReceiveExpr,
1193
makeModuleReceiveWithVar,
1194
makeRemovedFSAssert,
1195
makeRetainedCompilerSettings,
1196
makeReturn64,
1197
makeSetValue,
1198
makeThrow,
1199
modifyJSFunction,
1200
nodeDetectionCode,
1201
receiveI64ParamAsI53,
1202
receiveI64ParamAsI53Unchecked,
1203
receivedSymbol,
1204
runIfMainThread,
1205
runIfWorkerThread,
1206
runtimeKeepalivePop,
1207
runtimeKeepalivePush,
1208
splitI64,
1209
storeException,
1210
to64,
1211
toIndexType,
1212
nodePthreadDetection,
1213
nodeWWDetection,
1214
});
1215
1216