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