visaljs 0.0.9 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD_PARTY_LICENSES +1 -1
- package/cli.js +2732 -771
- package/node/include/js_node.h +1 -0
- package/node/prelude/http.js +11 -9
- package/node/prelude/internal/http_chars.ts +22 -0
- package/node/prelude/net.js +18 -14
- package/node/src/host_sys.c +9 -0
- package/package.json +1 -1
- package/prebuilt/win32-x64/boringssl-ce573858472b7fc9.a +0 -0
- package/prebuilt/win32-x64/brotli-20d86a61f30baeb3.a +0 -0
- package/prebuilt/win32-x64/uv-b11bae65d7fced82.a +0 -0
- package/prebuilt/win32-x64/zlib-4ce2317a4c3f5fb5.a +0 -0
- package/prebuilt/win32-x64/zstd-015315ac7c784c73.a +0 -0
- package/runtime/include/js_buffer.h +185 -0
- package/runtime/include/js_builtins.h +53 -0
- package/runtime/include/js_exception.h +3 -0
- package/runtime/include/js_function.h +1 -1
- package/runtime/include/js_object.h +21 -3
- package/runtime/include/js_ops.h +27 -0
- package/runtime/include/js_runtime.h +1 -0
- package/runtime/include/js_web.h +0 -3
- package/runtime/include/jscontext.h +7 -0
- package/runtime/src/builtins.c +17 -4
- package/runtime/src/function.c +37 -10
- package/runtime/src/gc.c +3 -1
- package/runtime/src/iterator.c +5 -1
- package/runtime/src/object.c +42 -6
- package/runtime/src/typedarray.c +23 -0
package/cli.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire, isBuiltin } from "node:module";
|
|
3
|
+
import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads";
|
|
3
4
|
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
5
|
import { basename, delimiter, dirname, extname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
|
|
5
6
|
import { parseArgs, promisify } from "node:util";
|
|
7
|
+
import { getHeapStatistics } from "node:v8";
|
|
6
8
|
import ts from "typescript";
|
|
7
9
|
import { RolldownMagicString, rolldown } from "rolldown";
|
|
8
10
|
import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
|
|
9
11
|
import { createHash } from "node:crypto";
|
|
10
|
-
import { availableParallelism, homedir, tmpdir
|
|
12
|
+
import { availableParallelism, homedir, tmpdir } from "node:os";
|
|
11
13
|
//#region \0rolldown/runtime.js
|
|
12
14
|
var __defProp$1 = Object.defineProperty;
|
|
13
15
|
var __exportAll = (all, no_symbols) => {
|
|
@@ -98,6 +100,19 @@ function hoistHostImports(body) {
|
|
|
98
100
|
function parseModule(text, file, map, entry, hostPrimitives = {}) {
|
|
99
101
|
return parse(text, file, true, map, entry, hostPrimitives);
|
|
100
102
|
}
|
|
103
|
+
/** A static unit as one module: its types ignored, its files' top levels one after another.
|
|
104
|
+
* Its functions can't be printed (Function.prototype.toString): their source is in many files. */
|
|
105
|
+
function convertStaticUnit(unit, hostPrimitives) {
|
|
106
|
+
const programs = unit.files.map((sf) => new Converter(sf, true, void 0, resolve(sf.fileName), hostPrimitives, unit).program());
|
|
107
|
+
return {
|
|
108
|
+
...programs[0],
|
|
109
|
+
static: true,
|
|
110
|
+
body: hoistHostImports(programs.flatMap((p) => p.body)),
|
|
111
|
+
exportedNames: programs.flatMap((p) => p.exportedNames),
|
|
112
|
+
source: "",
|
|
113
|
+
unsupported: programs.flatMap((p) => p.unsupported)
|
|
114
|
+
};
|
|
115
|
+
}
|
|
101
116
|
function parse(text, file, module, map, entry, hostPrimitives = {}) {
|
|
102
117
|
const setExternalModuleIndicator = (s) => {
|
|
103
118
|
s.externalModuleIndicator = module ? s : void 0;
|
|
@@ -113,15 +128,26 @@ function parse(text, file, module, map, entry, hostPrimitives = {}) {
|
|
|
113
128
|
}
|
|
114
129
|
return new Converter(sf, module, map, resolve(entry ?? file), hostPrimitives).program();
|
|
115
130
|
}
|
|
116
|
-
function locAt(sf, pos, map) {
|
|
131
|
+
function locAt(sf, pos, map, file = normalize(sf.fileName)) {
|
|
117
132
|
const { line, character } = sf.getLineAndCharacterOfPosition(pos);
|
|
118
133
|
const loc = {
|
|
119
|
-
file
|
|
134
|
+
file,
|
|
120
135
|
line: line + 1,
|
|
121
136
|
column: character + 1
|
|
122
137
|
};
|
|
123
138
|
return map?.original(loc.line, loc.column) ?? loc;
|
|
124
139
|
}
|
|
140
|
+
/** A numeric literal written as a floating-point one (1.0, 1e3): a double to static code, as in C. */
|
|
141
|
+
function isFloatWritten(raw) {
|
|
142
|
+
return /[.eE]/.test(raw) && !/^0[xXbBoO]/.test(raw);
|
|
143
|
+
}
|
|
144
|
+
/** `import type …`, or an import of types only (`import { type a }`). */
|
|
145
|
+
function typeOnly(node) {
|
|
146
|
+
const clause = node.importClause;
|
|
147
|
+
if (!clause || clause.isTypeOnly) return clause !== void 0;
|
|
148
|
+
const bindings = clause.namedBindings;
|
|
149
|
+
return !clause.name && bindings !== void 0 && ts.isNamedImports(bindings) && bindings.elements.every((e) => e.isTypeOnly);
|
|
150
|
+
}
|
|
125
151
|
/** TypeScript rejects `a?.#x` (still parsed as usual); ES2022 allows it. */
|
|
126
152
|
const PRIVATE_IN_OPTIONAL_CHAIN = 18030;
|
|
127
153
|
/** Readable names for constructs the compiler doesn't handle. */
|
|
@@ -192,12 +218,15 @@ var Converter = class {
|
|
|
192
218
|
map;
|
|
193
219
|
entry;
|
|
194
220
|
hostPrimitives;
|
|
195
|
-
|
|
221
|
+
/** Converting a static unit's TypeScript: types are skipped, not errors. */
|
|
222
|
+
unit;
|
|
223
|
+
constructor(sf, module, map, entry, hostPrimitives, unit) {
|
|
196
224
|
this.sf = sf;
|
|
197
225
|
this.module = module;
|
|
198
226
|
this.map = map;
|
|
199
227
|
this.entry = entry;
|
|
200
228
|
this.hostPrimitives = hostPrimitives;
|
|
229
|
+
this.unit = unit;
|
|
201
230
|
}
|
|
202
231
|
/** The absolute path of the original file `node` comes from (for import.meta and import()). */
|
|
203
232
|
originalFile(node) {
|
|
@@ -205,7 +234,7 @@ var Converter = class {
|
|
|
205
234
|
return this.map?.originalSource(line + 1, character + 1) ?? resolve(this.sf.fileName);
|
|
206
235
|
}
|
|
207
236
|
loc(node) {
|
|
208
|
-
return locAt(this.sf, node.getStart(this.sf), this.map);
|
|
237
|
+
return locAt(this.sf, node.getStart(this.sf), this.map, this.unit?.fileName(this.sf));
|
|
209
238
|
}
|
|
210
239
|
fail(kind, detail, node) {
|
|
211
240
|
if (kind === "NotSupported") throw new Deferred(detail, node);
|
|
@@ -234,6 +263,7 @@ var Converter = class {
|
|
|
234
263
|
}
|
|
235
264
|
/** TypeScript-only syntax the parser accepts in .js files: a JS syntax error. */
|
|
236
265
|
noTypes(node) {
|
|
266
|
+
if (this.unit) return;
|
|
237
267
|
if (node.type) this.fail("SyntaxError", "type annotations are TypeScript, not JavaScript", node.type);
|
|
238
268
|
if (node.typeParameters || node.typeArguments) this.fail("SyntaxError", "type arguments are TypeScript, not JavaScript", node);
|
|
239
269
|
}
|
|
@@ -258,7 +288,7 @@ var Converter = class {
|
|
|
258
288
|
return {
|
|
259
289
|
type: "Program",
|
|
260
290
|
loc: {
|
|
261
|
-
file: normalize(this.sf.fileName),
|
|
291
|
+
file: this.unit?.fileName(this.sf) ?? normalize(this.sf.fileName),
|
|
262
292
|
line: 1,
|
|
263
293
|
column: 1
|
|
264
294
|
},
|
|
@@ -282,10 +312,12 @@ var Converter = class {
|
|
|
282
312
|
}
|
|
283
313
|
identifier(node) {
|
|
284
314
|
if (!ts.isIdentifier(node)) this.fail("SyntaxError", "identifier expected", node);
|
|
315
|
+
const renamed = this.unit?.rename(node);
|
|
285
316
|
const id = {
|
|
286
317
|
type: "Identifier",
|
|
287
318
|
loc: this.loc(node),
|
|
288
|
-
name: node.text
|
|
319
|
+
name: renamed ?? node.text,
|
|
320
|
+
...renamed ? { original: node.text } : {}
|
|
289
321
|
};
|
|
290
322
|
if (this.map) {
|
|
291
323
|
const { line, character } = this.sf.getLineAndCharacterOfPosition(node.getStart(this.sf));
|
|
@@ -456,11 +488,12 @@ var Converter = class {
|
|
|
456
488
|
kind: flags === ts.NodeFlags.Let ? "let" : flags === ts.NodeFlags.Const ? "const" : "var",
|
|
457
489
|
declarations: list.declarations.map((d) => {
|
|
458
490
|
this.noTypes(d);
|
|
459
|
-
if (d.exclamationToken) this.fail("SyntaxError", "definite assignment assertions are TypeScript", d);
|
|
491
|
+
if (d.exclamationToken && !this.unit) this.fail("SyntaxError", "definite assignment assertions are TypeScript", d);
|
|
492
|
+
const fn = d.initializer && (ts.isArrowFunction(d.initializer) || ts.isFunctionExpression(d.initializer)) ? d.initializer : void 0;
|
|
460
493
|
return {
|
|
461
494
|
type: "VariableDeclarator",
|
|
462
495
|
loc: this.loc(d),
|
|
463
|
-
id: this.bindingPattern(d.name),
|
|
496
|
+
id: this.declaredInt(this.bindingPattern(d.name), d.type, fn?.type),
|
|
464
497
|
init: d.initializer ? this.expression(d.initializer) : null
|
|
465
498
|
};
|
|
466
499
|
})
|
|
@@ -511,8 +544,16 @@ var Converter = class {
|
|
|
511
544
|
return this.orUnsupported(node, () => {
|
|
512
545
|
const loc = this.loc(node);
|
|
513
546
|
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === "visaljs:host") return this.hostImport(node);
|
|
547
|
+
if (this.unit && ts.isImportDeclaration(node) && (this.unit.linked(node) || typeOnly(node))) return {
|
|
548
|
+
type: "EmptyStatement",
|
|
549
|
+
loc
|
|
550
|
+
};
|
|
514
551
|
if (ts.isImportDeclaration(node) || ts.isImportEqualsDeclaration(node)) this.fail("NotSupported", "import declarations: the bundler resolves them", node);
|
|
515
552
|
if (ts.isExportDeclaration(node)) {
|
|
553
|
+
if (node.isTypeOnly && this.unit) return {
|
|
554
|
+
type: "EmptyStatement",
|
|
555
|
+
loc
|
|
556
|
+
};
|
|
516
557
|
if (node.isTypeOnly) this.fail("SyntaxError", "type-only exports are TypeScript", node);
|
|
517
558
|
if (node.moduleSpecifier) this.fail("NotSupported", "export … from: the bundler resolves them", node);
|
|
518
559
|
const clause = node.exportClause;
|
|
@@ -606,11 +647,12 @@ var Converter = class {
|
|
|
606
647
|
return {
|
|
607
648
|
type: "FunctionDeclaration",
|
|
608
649
|
loc,
|
|
609
|
-
id: f.name ? this.identifier(f.name) : {
|
|
650
|
+
id: f.name ? this.declaredInt(this.identifier(f.name), void 0, f.type) : {
|
|
610
651
|
type: "Identifier",
|
|
611
652
|
loc,
|
|
612
653
|
name: "*default*"
|
|
613
654
|
},
|
|
655
|
+
...this.returnsInt(f),
|
|
614
656
|
...this.parameters(f.parameters),
|
|
615
657
|
source: this.range(f),
|
|
616
658
|
body: this.block(f.body),
|
|
@@ -796,6 +838,11 @@ var Converter = class {
|
|
|
796
838
|
}
|
|
797
839
|
case ts.SyntaxKind.InterfaceDeclaration:
|
|
798
840
|
case ts.SyntaxKind.TypeAliasDeclaration:
|
|
841
|
+
if (this.unit) return {
|
|
842
|
+
type: "EmptyStatement",
|
|
843
|
+
loc
|
|
844
|
+
};
|
|
845
|
+
return this.fail("SyntaxError", "TypeScript declarations are not JavaScript", node);
|
|
799
846
|
case ts.SyntaxKind.EnumDeclaration:
|
|
800
847
|
case ts.SyntaxKind.ModuleDeclaration: return this.fail("SyntaxError", "TypeScript declarations are not JavaScript", node);
|
|
801
848
|
default: return this.unsupported(node);
|
|
@@ -845,14 +892,14 @@ var Converter = class {
|
|
|
845
892
|
let rest = null;
|
|
846
893
|
list.forEach((p, i) => {
|
|
847
894
|
this.noTypes(p);
|
|
848
|
-
if (p.questionToken) this.fail("SyntaxError", "optional parameters are TypeScript", p);
|
|
895
|
+
if (p.questionToken && !this.unit) this.fail("SyntaxError", "optional parameters are TypeScript", p);
|
|
849
896
|
if (p.dotDotDotToken) {
|
|
850
897
|
if (i !== list.length - 1) this.fail("SyntaxError", "A rest parameter must be last in a parameter list", p);
|
|
851
898
|
if (p.initializer) this.fail("SyntaxError", "Rest parameter may not have a default initializer", p);
|
|
852
899
|
rest = this.bindingPattern(p.name);
|
|
853
900
|
return;
|
|
854
901
|
}
|
|
855
|
-
params.push(this.bindingPattern(p.name));
|
|
902
|
+
params.push(this.declaredInt(this.bindingPattern(p.name), p.type));
|
|
856
903
|
defaults.push(p.initializer ? this.expression(p.initializer) : null);
|
|
857
904
|
});
|
|
858
905
|
return {
|
|
@@ -879,6 +926,7 @@ var Converter = class {
|
|
|
879
926
|
type: "FunctionExpression",
|
|
880
927
|
loc: this.loc(f),
|
|
881
928
|
id: !method && !ts.isConstructorDeclaration(f) && f.name && ts.isIdentifier(f.name) ? this.identifier(f.name) : null,
|
|
929
|
+
...this.returnsInt(f),
|
|
882
930
|
...this.parameters(f.parameters),
|
|
883
931
|
source: this.range(cls?.ctor && cls.node ? cls.node : f),
|
|
884
932
|
body: this.block(f.body),
|
|
@@ -1187,6 +1235,7 @@ var Converter = class {
|
|
|
1187
1235
|
type: "FunctionExpression",
|
|
1188
1236
|
loc,
|
|
1189
1237
|
id: null,
|
|
1238
|
+
...this.returnsInt(f),
|
|
1190
1239
|
...this.parameters(f.parameters),
|
|
1191
1240
|
source: this.range(f),
|
|
1192
1241
|
body,
|
|
@@ -1201,6 +1250,23 @@ var Converter = class {
|
|
|
1201
1250
|
...this.asyncFlag(f)
|
|
1202
1251
|
};
|
|
1203
1252
|
}
|
|
1253
|
+
/** Static code: the integers a declaration names, on its identifier: its type's, and (a
|
|
1254
|
+
* function's name, or a const holding a function) its function's return type's. */
|
|
1255
|
+
declaredInt(id, type, returns) {
|
|
1256
|
+
if (!this.unit || id.type !== "Identifier") return id;
|
|
1257
|
+
const int = this.unit.intKind(type);
|
|
1258
|
+
const returnsInt = this.unit.intKind(returns);
|
|
1259
|
+
return {
|
|
1260
|
+
...id,
|
|
1261
|
+
...int ? { int } : {},
|
|
1262
|
+
...returnsInt ? { returnsInt } : {}
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
/** Static code: the integer a function's declared return type names. */
|
|
1266
|
+
returnsInt(f) {
|
|
1267
|
+
const int = this.unit?.intKind(f.type);
|
|
1268
|
+
return int ? { returnsInt: int } : {};
|
|
1269
|
+
}
|
|
1204
1270
|
/** `async`: async functions and async generators. */
|
|
1205
1271
|
asyncFlag(f) {
|
|
1206
1272
|
return f.modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) ? { async: true } : {};
|
|
@@ -1225,6 +1291,7 @@ var Converter = class {
|
|
|
1225
1291
|
}
|
|
1226
1292
|
chainLink(node) {
|
|
1227
1293
|
if (!ts.isOptionalChain(node)) return this.expression(node);
|
|
1294
|
+
if (ts.isNonNullExpression(node) && this.unit) return this.chainLink(node.expression);
|
|
1228
1295
|
if (ts.isNonNullExpression(node)) this.fail("SyntaxError", "non-null assertions are TypeScript, not JavaScript", node);
|
|
1229
1296
|
const optional = node.questionDotToken ? { optional: true } : {};
|
|
1230
1297
|
const loc = this.loc(node);
|
|
@@ -1389,7 +1456,11 @@ var Converter = class {
|
|
|
1389
1456
|
type: "Property",
|
|
1390
1457
|
kind: "init",
|
|
1391
1458
|
loc,
|
|
1392
|
-
key:
|
|
1459
|
+
key: {
|
|
1460
|
+
type: "Identifier",
|
|
1461
|
+
loc: this.loc(p.name),
|
|
1462
|
+
name: p.name.text
|
|
1463
|
+
},
|
|
1393
1464
|
computed: false,
|
|
1394
1465
|
value: this.identifier(p.name),
|
|
1395
1466
|
isProto: false
|
|
@@ -1412,7 +1483,8 @@ var Converter = class {
|
|
|
1412
1483
|
case ts.SyntaxKind.NumericLiteral: return {
|
|
1413
1484
|
type: "Literal",
|
|
1414
1485
|
loc,
|
|
1415
|
-
value: Number(node.text)
|
|
1486
|
+
value: Number(node.text),
|
|
1487
|
+
...this.unit && isFloatWritten(node.getText(this.sf)) ? { float: true } : {}
|
|
1416
1488
|
};
|
|
1417
1489
|
case ts.SyntaxKind.StringLiteral:
|
|
1418
1490
|
case ts.SyntaxKind.NoSubstitutionTemplateLiteral: return {
|
|
@@ -1691,16 +1763,30 @@ var Converter = class {
|
|
|
1691
1763
|
case ts.SyntaxKind.AsExpression:
|
|
1692
1764
|
case ts.SyntaxKind.SatisfiesExpression:
|
|
1693
1765
|
case ts.SyntaxKind.NonNullExpression:
|
|
1694
|
-
case ts.SyntaxKind.TypeAssertionExpression:
|
|
1766
|
+
case ts.SyntaxKind.TypeAssertionExpression:
|
|
1767
|
+
if (this.unit) return this.expression(node.expression);
|
|
1768
|
+
return this.fail("SyntaxError", "type assertions are TypeScript, not JavaScript", node);
|
|
1695
1769
|
default: return this.unsupported(node);
|
|
1696
1770
|
}
|
|
1697
1771
|
}
|
|
1698
1772
|
};
|
|
1699
1773
|
//#endregion
|
|
1700
1774
|
//#region compiler/src/frontend/sourcemap.ts
|
|
1701
|
-
const
|
|
1775
|
+
const FIELDS = 5;
|
|
1776
|
+
const GEN_COLUMN = 0;
|
|
1777
|
+
const SOURCE = 1;
|
|
1778
|
+
const LINE = 2;
|
|
1779
|
+
const COLUMN = 3;
|
|
1780
|
+
const NAME = 4;
|
|
1781
|
+
const BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
1782
|
+
const DIGIT = (/* @__PURE__ */ new Int8Array(128)).fill(-1);
|
|
1783
|
+
for (let i = 0; i < 64; i++) DIGIT[BASE64.charCodeAt(i)] = i;
|
|
1784
|
+
const COMMA = 44;
|
|
1785
|
+
const SEMICOLON = 59;
|
|
1702
1786
|
var SourceMap = class {
|
|
1703
|
-
|
|
1787
|
+
segments = new Int32Array(1024 * FIELDS);
|
|
1788
|
+
/** Line n's segments: from lineStarts[n] to lineStarts[n + 1] (segment indexes). */
|
|
1789
|
+
lineStarts = [0];
|
|
1704
1790
|
sources;
|
|
1705
1791
|
rawSources;
|
|
1706
1792
|
names;
|
|
@@ -1710,93 +1796,114 @@ var SourceMap = class {
|
|
|
1710
1796
|
this.rawSources = raw.sources.map((s) => s ?? "");
|
|
1711
1797
|
this.sources = this.rawSources.map(resolveSource);
|
|
1712
1798
|
this.names = raw.names ?? [];
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1799
|
+
const mappings = raw.mappings;
|
|
1800
|
+
const fields = [
|
|
1801
|
+
0,
|
|
1802
|
+
0,
|
|
1803
|
+
0,
|
|
1804
|
+
0,
|
|
1805
|
+
0
|
|
1806
|
+
];
|
|
1807
|
+
let count = 0, lineStart = 0, genColumn = 0, source = 0, line = 0, column = 0, name = 0;
|
|
1808
|
+
for (let i = 0; i <= mappings.length; i++) {
|
|
1809
|
+
const c = i < mappings.length ? mappings.charCodeAt(i) : SEMICOLON;
|
|
1810
|
+
if (c !== COMMA && c !== SEMICOLON) continue;
|
|
1811
|
+
const n = decodeVlq(mappings, lineStart, i, fields);
|
|
1812
|
+
if (n > 0) {
|
|
1813
|
+
genColumn += fields[0];
|
|
1814
|
+
if (n >= 4) {
|
|
1815
|
+
source += fields[1];
|
|
1816
|
+
line += fields[2];
|
|
1817
|
+
column += fields[3];
|
|
1818
|
+
if (n >= 5) name += fields[4];
|
|
1730
1819
|
}
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
]
|
|
1820
|
+
if ((count + 1) * FIELDS > this.segments.length) {
|
|
1821
|
+
const grown = new Int32Array(this.segments.length * 2);
|
|
1822
|
+
grown.set(this.segments);
|
|
1823
|
+
this.segments = grown;
|
|
1824
|
+
}
|
|
1825
|
+
const at = count++ * FIELDS;
|
|
1826
|
+
this.segments[at + GEN_COLUMN] = genColumn;
|
|
1827
|
+
this.segments[at + SOURCE] = n >= 4 ? source : -1;
|
|
1828
|
+
this.segments[at + LINE] = n >= 4 ? line : 0;
|
|
1829
|
+
this.segments[at + COLUMN] = n >= 4 ? column : 0;
|
|
1830
|
+
this.segments[at + NAME] = n >= 5 ? name : -1;
|
|
1831
|
+
}
|
|
1832
|
+
lineStart = i + 1;
|
|
1833
|
+
if (c === SEMICOLON) {
|
|
1834
|
+
this.sortLine(this.lineStarts[this.lineStarts.length - 1], count);
|
|
1835
|
+
this.lineStarts.push(count);
|
|
1836
|
+
genColumn = 0;
|
|
1742
1837
|
}
|
|
1743
|
-
segments.sort((a, b) => a[0] - b[0]);
|
|
1744
|
-
this.lines.push(segments);
|
|
1745
1838
|
}
|
|
1839
|
+
this.segments = this.segments.slice(0, count * FIELDS);
|
|
1746
1840
|
}
|
|
1747
1841
|
/** The original location of a 1-based bundle position: the segment at or before it on
|
|
1748
1842
|
* its line (else the line's first), or null where the bundler generated the code. */
|
|
1749
1843
|
original(line, column) {
|
|
1750
|
-
const
|
|
1751
|
-
if (
|
|
1844
|
+
const at = this.segmentAt(line, column, false);
|
|
1845
|
+
if (at < 0 || this.segments[at + SOURCE] < 0) return null;
|
|
1752
1846
|
return {
|
|
1753
|
-
file: this.sources[
|
|
1754
|
-
line:
|
|
1755
|
-
column:
|
|
1847
|
+
file: this.sources[this.segments[at + SOURCE]],
|
|
1848
|
+
line: this.segments[at + LINE] + 1,
|
|
1849
|
+
column: this.segments[at + COLUMN] + 1
|
|
1756
1850
|
};
|
|
1757
1851
|
}
|
|
1758
1852
|
/** The original file of a 1-based bundle position, as the map's `sources` names it (absolute
|
|
1759
1853
|
* paths from the driver), or null. */
|
|
1760
1854
|
originalSource(line, column) {
|
|
1761
|
-
const
|
|
1762
|
-
return
|
|
1855
|
+
const at = this.segmentAt(line, column, false);
|
|
1856
|
+
return at >= 0 && this.segments[at + SOURCE] >= 0 ? this.rawSources[this.segments[at + SOURCE]] : null;
|
|
1763
1857
|
}
|
|
1764
1858
|
/** The original name of the identifier starting exactly at a 1-based bundle position. */
|
|
1765
1859
|
originalName(line, column) {
|
|
1766
|
-
const
|
|
1767
|
-
return
|
|
1860
|
+
const at = this.segmentAt(line, column, true);
|
|
1861
|
+
return at >= 0 && this.segments[at + NAME] >= 0 ? this.names[this.segments[at + NAME]] : void 0;
|
|
1768
1862
|
}
|
|
1863
|
+
/** The offset in `segments` of the segment for a 1-based position, or -1. */
|
|
1769
1864
|
segmentAt(line, column, exact) {
|
|
1770
|
-
|
|
1771
|
-
|
|
1865
|
+
if (line < 1 || line >= this.lineStarts.length) return -1;
|
|
1866
|
+
const first = this.lineStarts[line - 1], last = this.lineStarts[line] - 1;
|
|
1867
|
+
if (last < first) return -1;
|
|
1772
1868
|
const col = column - 1;
|
|
1773
|
-
let lo =
|
|
1869
|
+
let lo = first, hi = last, found = -1;
|
|
1774
1870
|
while (lo <= hi) {
|
|
1775
1871
|
const mid = lo + hi >> 1;
|
|
1776
|
-
if (segments[mid]
|
|
1872
|
+
if (this.segments[mid * FIELDS + GEN_COLUMN] <= col) {
|
|
1777
1873
|
found = mid;
|
|
1778
1874
|
lo = mid + 1;
|
|
1779
1875
|
} else hi = mid - 1;
|
|
1780
1876
|
}
|
|
1781
|
-
if (exact) return found >= 0 && segments[found]
|
|
1782
|
-
return
|
|
1877
|
+
if (exact) return found >= 0 && this.segments[found * FIELDS + GEN_COLUMN] === col ? found * FIELDS : -1;
|
|
1878
|
+
return (found >= 0 ? found : first) * FIELDS;
|
|
1879
|
+
}
|
|
1880
|
+
/** Segments `from` to `to` in generated column order (maps almost always have them so). */
|
|
1881
|
+
sortLine(from, to) {
|
|
1882
|
+
let sorted = true;
|
|
1883
|
+
for (let s = from + 1; s < to && sorted; s++) sorted = this.segments[(s - 1) * FIELDS] <= this.segments[s * FIELDS];
|
|
1884
|
+
if (sorted) return;
|
|
1885
|
+
const line = [...Array(to - from).keys()].map((k) => this.segments.slice((from + k) * FIELDS, (from + k + 1) * FIELDS));
|
|
1886
|
+
line.sort((a, b) => a[GEN_COLUMN] - b[GEN_COLUMN]);
|
|
1887
|
+
line.forEach((seg, k) => this.segments.set(seg, (from + k) * FIELDS));
|
|
1783
1888
|
}
|
|
1784
1889
|
};
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
let value = 0, shift = 0;
|
|
1788
|
-
for (
|
|
1789
|
-
const
|
|
1790
|
-
|
|
1890
|
+
/** Decodes the VLQ fields of mappings[from, to) into `out`; returns how many there are. */
|
|
1891
|
+
function decodeVlq(mappings, from, to, out) {
|
|
1892
|
+
let n = 0, value = 0, shift = 0;
|
|
1893
|
+
for (let i = from; i < to; i++) {
|
|
1894
|
+
const c = mappings.charCodeAt(i);
|
|
1895
|
+
const digit = c < 128 ? DIGIT[c] : -1;
|
|
1896
|
+
if (digit < 0) throw new Error(`invalid source map mapping '${mappings.slice(from, to)}'`);
|
|
1791
1897
|
value += (digit & 31) << shift;
|
|
1792
1898
|
if (digit & 32) shift += 5;
|
|
1793
1899
|
else {
|
|
1794
|
-
out.
|
|
1900
|
+
if (n < out.length) out[n] = value & 1 ? -(value >>> 1) : value >>> 1;
|
|
1901
|
+
n++;
|
|
1795
1902
|
value = 0;
|
|
1796
1903
|
shift = 0;
|
|
1797
1904
|
}
|
|
1798
1905
|
}
|
|
1799
|
-
return
|
|
1906
|
+
return n;
|
|
1800
1907
|
}
|
|
1801
1908
|
//#endregion
|
|
1802
1909
|
//#region compiler/src/sema/analyze.ts
|
|
@@ -2676,6 +2783,134 @@ var Resolver = class {
|
|
|
2676
2783
|
}
|
|
2677
2784
|
};
|
|
2678
2785
|
//#endregion
|
|
2786
|
+
//#region ir/src/types.ts
|
|
2787
|
+
const T = {
|
|
2788
|
+
None: 0,
|
|
2789
|
+
Undefined: 1,
|
|
2790
|
+
Null: 2,
|
|
2791
|
+
Boolean: 4,
|
|
2792
|
+
Int32: 8,
|
|
2793
|
+
Double: 16,
|
|
2794
|
+
String: 32,
|
|
2795
|
+
Symbol: 64,
|
|
2796
|
+
BigInt: 128,
|
|
2797
|
+
Object: 256,
|
|
2798
|
+
Empty: 512,
|
|
2799
|
+
Int64: 1024
|
|
2800
|
+
};
|
|
2801
|
+
const Number_ = T.Int32 | T.Double | T.Int64;
|
|
2802
|
+
const Primitive = T.Undefined | T.Null | T.Boolean | Number_ | T.String | T.Symbol | T.BigInt;
|
|
2803
|
+
const Any = Primitive | T.Object;
|
|
2804
|
+
/** Nothing known: any JS value, or EMPTY (what an uninitialized binding reads). */
|
|
2805
|
+
const Unknown = Any | T.Empty;
|
|
2806
|
+
function isSubtype(a, b) {
|
|
2807
|
+
return (a & ~b) === 0;
|
|
2808
|
+
}
|
|
2809
|
+
const NAMED$1 = [
|
|
2810
|
+
[Any | T.Empty, "Any|Empty"],
|
|
2811
|
+
[Any, "Any"],
|
|
2812
|
+
[Primitive, "Primitive"],
|
|
2813
|
+
[Number_, "Number"]
|
|
2814
|
+
];
|
|
2815
|
+
const BITS = [
|
|
2816
|
+
[T.Undefined, "Undefined"],
|
|
2817
|
+
[T.Null, "Null"],
|
|
2818
|
+
[T.Boolean, "Boolean"],
|
|
2819
|
+
[T.Int32, "Int32"],
|
|
2820
|
+
[T.Double, "Double"],
|
|
2821
|
+
[T.String, "String"],
|
|
2822
|
+
[T.Symbol, "Symbol"],
|
|
2823
|
+
[T.BigInt, "BigInt"],
|
|
2824
|
+
[T.Object, "Object"],
|
|
2825
|
+
[T.Empty, "Empty"],
|
|
2826
|
+
[T.Int64, "Int64"]
|
|
2827
|
+
];
|
|
2828
|
+
function typeName(t) {
|
|
2829
|
+
for (const [bits, name] of NAMED$1) if (t === bits) return name;
|
|
2830
|
+
if (t === T.None) return "None";
|
|
2831
|
+
const parts = [];
|
|
2832
|
+
let rest = t;
|
|
2833
|
+
if ((rest & Number_) === Number_) {
|
|
2834
|
+
parts.push("Number");
|
|
2835
|
+
rest &= ~Number_;
|
|
2836
|
+
}
|
|
2837
|
+
for (const [bit, name] of BITS) if (rest & bit) parts.push(name);
|
|
2838
|
+
return parts.join("|");
|
|
2839
|
+
}
|
|
2840
|
+
//#endregion
|
|
2841
|
+
//#region ir/src/views.ts
|
|
2842
|
+
/** Each kind's constructor, and its JSTypedArrayType in C. */
|
|
2843
|
+
const VIEWS = {
|
|
2844
|
+
i8: {
|
|
2845
|
+
ctor: "Int8Array",
|
|
2846
|
+
c: "JS_TA_INT8"
|
|
2847
|
+
},
|
|
2848
|
+
u8: {
|
|
2849
|
+
ctor: "Uint8Array",
|
|
2850
|
+
c: "JS_TA_UINT8"
|
|
2851
|
+
},
|
|
2852
|
+
i32: {
|
|
2853
|
+
ctor: "Int32Array",
|
|
2854
|
+
c: "JS_TA_INT32"
|
|
2855
|
+
},
|
|
2856
|
+
f64: {
|
|
2857
|
+
ctor: "Float64Array",
|
|
2858
|
+
c: "JS_TA_FLOAT64"
|
|
2859
|
+
}
|
|
2860
|
+
};
|
|
2861
|
+
const BY_CTOR = new Map(Object.entries(VIEWS).map(([kind, v]) => [v.ctor, kind]));
|
|
2862
|
+
/** The kind whose constructor is named `ctor`. */
|
|
2863
|
+
function viewOf(ctor) {
|
|
2864
|
+
return BY_CTOR.get(ctor);
|
|
2865
|
+
}
|
|
2866
|
+
//#endregion
|
|
2867
|
+
//#region ir/src/static.ts
|
|
2868
|
+
function isStatic(fn) {
|
|
2869
|
+
return fn.dialect === "static";
|
|
2870
|
+
}
|
|
2871
|
+
/** By the name the source writes: tsc erases `int` and `i32` to number, so they are known by name. */
|
|
2872
|
+
const STATIC_TYPES = {
|
|
2873
|
+
boolean: {
|
|
2874
|
+
type: T.Boolean,
|
|
2875
|
+
words: "boolean"
|
|
2876
|
+
},
|
|
2877
|
+
number: {
|
|
2878
|
+
type: T.Int32 | T.Double,
|
|
2879
|
+
words: "number"
|
|
2880
|
+
},
|
|
2881
|
+
int: {
|
|
2882
|
+
type: T.Int64,
|
|
2883
|
+
int: "i64",
|
|
2884
|
+
words: "number"
|
|
2885
|
+
},
|
|
2886
|
+
i32: {
|
|
2887
|
+
type: T.Int32,
|
|
2888
|
+
int: "i32",
|
|
2889
|
+
words: "i32"
|
|
2890
|
+
},
|
|
2891
|
+
string: {
|
|
2892
|
+
type: T.String,
|
|
2893
|
+
words: "string"
|
|
2894
|
+
},
|
|
2895
|
+
undefined: {
|
|
2896
|
+
type: T.Undefined,
|
|
2897
|
+
words: "undefined"
|
|
2898
|
+
},
|
|
2899
|
+
null: {
|
|
2900
|
+
type: T.Null,
|
|
2901
|
+
words: "null"
|
|
2902
|
+
}
|
|
2903
|
+
};
|
|
2904
|
+
/** A declared parameter's type as ERR_INVALID_ARG_TYPE words it: "of type string or undefined",
|
|
2905
|
+
* "an instance of Float64Array". */
|
|
2906
|
+
function expectedText(t, view) {
|
|
2907
|
+
if (view) return `an instance of ${VIEWS[view].ctor}`;
|
|
2908
|
+
const parts = Object.values(STATIC_TYPES).filter((s) => isSubtype(s.type, t));
|
|
2909
|
+
const widest = parts.filter((s) => !parts.some((o) => o !== s && isSubtype(s.type, o.type)));
|
|
2910
|
+
const names = [...new Set(widest.map((s) => s.words))];
|
|
2911
|
+
return `of type ${names.length > 2 ? `${names.slice(0, -1).join(", ")}, or ${names.at(-1)}` : names.join(" or ")}`;
|
|
2912
|
+
}
|
|
2913
|
+
//#endregion
|
|
2679
2914
|
//#region ir/src/ir.ts
|
|
2680
2915
|
const pure = (arity) => ({
|
|
2681
2916
|
arity,
|
|
@@ -2866,6 +3101,29 @@ const OPS = {
|
|
|
2866
3101
|
...pure(1),
|
|
2867
3102
|
allocates: false
|
|
2868
3103
|
},
|
|
3104
|
+
char_code_at: {
|
|
3105
|
+
...pure(2),
|
|
3106
|
+
allocates: true
|
|
3107
|
+
},
|
|
3108
|
+
math: pure(1),
|
|
3109
|
+
int_add: pure(2),
|
|
3110
|
+
int_sub: pure(2),
|
|
3111
|
+
int_mul: pure(2),
|
|
3112
|
+
to_i64: pure(1),
|
|
3113
|
+
to_i32: pure(1),
|
|
3114
|
+
int_div: {
|
|
3115
|
+
...pure(2),
|
|
3116
|
+
mayThrow: true
|
|
3117
|
+
},
|
|
3118
|
+
int_mod: {
|
|
3119
|
+
...pure(2),
|
|
3120
|
+
mayThrow: true
|
|
3121
|
+
},
|
|
3122
|
+
i64: pure(2),
|
|
3123
|
+
identical: pure(2),
|
|
3124
|
+
strict_eq_atom: pure(2),
|
|
3125
|
+
is_type: pure(1),
|
|
3126
|
+
strict_eq_num: pure(2),
|
|
2869
3127
|
in: generic(2),
|
|
2870
3128
|
instanceof: generic(2),
|
|
2871
3129
|
to_property_key: generic(1),
|
|
@@ -3136,6 +3394,45 @@ const OPS = {
|
|
|
3136
3394
|
call_runtime: call,
|
|
3137
3395
|
take_exception: pure(0)
|
|
3138
3396
|
};
|
|
3397
|
+
/** What a typed parameter of type `t` takes: an int any Number (the function converts it, as C). */
|
|
3398
|
+
function accepts(t) {
|
|
3399
|
+
return isSubtype(t, T.Int64) ? Number_ : t;
|
|
3400
|
+
}
|
|
3401
|
+
/** Whether a direct call of a typed function ("use static") may take its typed entry: each
|
|
3402
|
+
* declared parameter's argument proven of its type (`type`: the types; inference passes the ones
|
|
3403
|
+
* it is inferring). Otherwise the call goes through the checked entry, which throws a TypeError
|
|
3404
|
+
* for an argument that isn't: tsc lets typed code pass a number to an `int`, so a check remains. */
|
|
3405
|
+
function declaredCallFits(i, callee, type = (v) => v.type) {
|
|
3406
|
+
const declared = callee.meta?.declared;
|
|
3407
|
+
if (!declared) return true;
|
|
3408
|
+
return declared.every((t, k) => {
|
|
3409
|
+
if (t === void 0) return true;
|
|
3410
|
+
const o = i.operands[k];
|
|
3411
|
+
const a = o ? type(o) & ~T.Empty : T.Undefined;
|
|
3412
|
+
return a !== T.None && isSubtype(a, accepts(t));
|
|
3413
|
+
});
|
|
3414
|
+
}
|
|
3415
|
+
/** Whether a typed export's call takes its typed entry: as many arguments as it declares, each
|
|
3416
|
+
* proven of its declared type (inference, unboxing and C emission all ask this). A typed array
|
|
3417
|
+
* never is: the type lattice doesn't tell one from any other object. */
|
|
3418
|
+
function typedCallFits(i, type = (v) => v.type) {
|
|
3419
|
+
const tc = i.typedCall;
|
|
3420
|
+
if (!tc || i.operands.length - 2 !== tc.params.length || tc.params.some((p) => p.view)) return false;
|
|
3421
|
+
return i.operands.slice(2).every((o, k) => {
|
|
3422
|
+
const t = type(o) & ~T.Empty;
|
|
3423
|
+
return t !== T.None && isSubtype(t, accepts(tc.params[k].type));
|
|
3424
|
+
});
|
|
3425
|
+
}
|
|
3426
|
+
/** Whether i reads a string's length: its own, read-only property, an int32; reading it runs no code. */
|
|
3427
|
+
function isStringLength(i) {
|
|
3428
|
+
const s = i.operands[0];
|
|
3429
|
+
return i.op === "get_prop" && i.key !== void 0 && "name" in i.key && i.key.name === "length" && s !== void 0 && s.type !== T.None && isSubtype(s.type, T.String);
|
|
3430
|
+
}
|
|
3431
|
+
/** Whether i reads a Float64Array's element in typed code: a double, past the end undefined as
|
|
3432
|
+
* a number (NaN), as its type, number, says. */
|
|
3433
|
+
function readsDouble(i) {
|
|
3434
|
+
return i.taView === "f64" && (i.op === "get_index" || i.op === "get_elem") && isStatic(i.block.fn);
|
|
3435
|
+
}
|
|
3139
3436
|
/** The key for `o.name` or `{ 1: v }`: canonical array indices (0 … 2^32 - 2) become index keys. */
|
|
3140
3437
|
function propKey(v) {
|
|
3141
3438
|
const s = String(v);
|
|
@@ -3165,59 +3462,6 @@ function isExceptionalEdge(from, to) {
|
|
|
3165
3462
|
return from.handler === to && !normalSuccessors(from).includes(to);
|
|
3166
3463
|
}
|
|
3167
3464
|
//#endregion
|
|
3168
|
-
//#region ir/src/types.ts
|
|
3169
|
-
const T = {
|
|
3170
|
-
None: 0,
|
|
3171
|
-
Undefined: 1,
|
|
3172
|
-
Null: 2,
|
|
3173
|
-
Boolean: 4,
|
|
3174
|
-
Int32: 8,
|
|
3175
|
-
Double: 16,
|
|
3176
|
-
String: 32,
|
|
3177
|
-
Symbol: 64,
|
|
3178
|
-
BigInt: 128,
|
|
3179
|
-
Object: 256,
|
|
3180
|
-
Empty: 512
|
|
3181
|
-
};
|
|
3182
|
-
const Number_ = T.Int32 | T.Double;
|
|
3183
|
-
const Primitive = T.Undefined | T.Null | T.Boolean | Number_ | T.String | T.Symbol | T.BigInt;
|
|
3184
|
-
const Any = Primitive | T.Object;
|
|
3185
|
-
/** Nothing known: any JS value, or EMPTY (what an uninitialized binding reads). */
|
|
3186
|
-
const Unknown = Any | T.Empty;
|
|
3187
|
-
function isSubtype(a, b) {
|
|
3188
|
-
return (a & ~b) === 0;
|
|
3189
|
-
}
|
|
3190
|
-
const NAMED = [
|
|
3191
|
-
[Any | T.Empty, "Any|Empty"],
|
|
3192
|
-
[Any, "Any"],
|
|
3193
|
-
[Primitive, "Primitive"],
|
|
3194
|
-
[Number_, "Number"]
|
|
3195
|
-
];
|
|
3196
|
-
const BITS = [
|
|
3197
|
-
[T.Undefined, "Undefined"],
|
|
3198
|
-
[T.Null, "Null"],
|
|
3199
|
-
[T.Boolean, "Boolean"],
|
|
3200
|
-
[T.Int32, "Int32"],
|
|
3201
|
-
[T.Double, "Double"],
|
|
3202
|
-
[T.String, "String"],
|
|
3203
|
-
[T.Symbol, "Symbol"],
|
|
3204
|
-
[T.BigInt, "BigInt"],
|
|
3205
|
-
[T.Object, "Object"],
|
|
3206
|
-
[T.Empty, "Empty"]
|
|
3207
|
-
];
|
|
3208
|
-
function typeName(t) {
|
|
3209
|
-
for (const [bits, name] of NAMED) if (t === bits) return name;
|
|
3210
|
-
if (t === T.None) return "None";
|
|
3211
|
-
const parts = [];
|
|
3212
|
-
let rest = t;
|
|
3213
|
-
if ((rest & Number_) === Number_) {
|
|
3214
|
-
parts.push("Number");
|
|
3215
|
-
rest &= ~Number_;
|
|
3216
|
-
}
|
|
3217
|
-
for (const [bit, name] of BITS) if (rest & bit) parts.push(name);
|
|
3218
|
-
return parts.join("|");
|
|
3219
|
-
}
|
|
3220
|
-
//#endregion
|
|
3221
3465
|
//#region ir/src/builder.ts
|
|
3222
3466
|
var FunctionBuilder = class {
|
|
3223
3467
|
fn;
|
|
@@ -3528,7 +3772,7 @@ function printFunction(fn) {
|
|
|
3528
3772
|
for (const phi of b.phis) lines.push(` ${def(phi)}phi ${phi.incoming.map((i) => `[${bb(i.block)}: ${name(i.value)}]`).join(", ")}`);
|
|
3529
3773
|
for (const i of b.instrs) {
|
|
3530
3774
|
if (i.op === "param") continue;
|
|
3531
|
-
lines.push(` ${def(i)}${instr(i)}${i.numberFirst ? " ?number" : ""}${i.builtin ? ` ?${i.builtin}` : ""}${i.mayThrow ? " !mayThrow" : ""}`);
|
|
3775
|
+
lines.push(` ${def(i)}${instr(i)}${i.numberFirst ? " ?number" : ""}${i.builtin ? ` ?${i.builtin}` : ""}${i.keyedSite ? " ?keyed" : ""}${i.mayThrow ? " !mayThrow" : ""}`);
|
|
3532
3776
|
}
|
|
3533
3777
|
if (b.term) lines.push(` ${term(b.term)}`);
|
|
3534
3778
|
}
|
|
@@ -3561,6 +3805,11 @@ function printFunction(fn) {
|
|
|
3561
3805
|
case "call_runtime": return `${i.op} ${i.target}(${args})`;
|
|
3562
3806
|
case "construct_direct": return `construct_direct ${i.target}(${args})`;
|
|
3563
3807
|
case "same_function": return `same_function ${args}, ${i.target}`;
|
|
3808
|
+
case "i64": return `i64 ${i.target} ${args}`;
|
|
3809
|
+
case "is_type": return `${i.index ? "!" : ""}is_type ${args}, ${JSON.stringify(i.target)}`;
|
|
3810
|
+
case "identical":
|
|
3811
|
+
case "strict_eq_atom":
|
|
3812
|
+
case "strict_eq_num": return `${i.index ? "!" : ""}${i.op} ${args}`;
|
|
3564
3813
|
case "new_instance": return `new_instance ${args}, ${i.index} slots`;
|
|
3565
3814
|
case "get_prop":
|
|
3566
3815
|
case "set_prop":
|
|
@@ -3706,12 +3955,15 @@ function verify(fn) {
|
|
|
3706
3955
|
boxed: Any | T.Empty,
|
|
3707
3956
|
f64: Number_,
|
|
3708
3957
|
i32: T.Int32,
|
|
3958
|
+
i64: T.Int64 | T.Int32,
|
|
3709
3959
|
bool: T.Boolean
|
|
3710
3960
|
};
|
|
3711
3961
|
const reprFits = (repr, t) => isSubtype(t, REPR_TYPE[repr]);
|
|
3962
|
+
const int64 = (t) => (t & T.Int64) !== 0 && isSubtype(t, T.Int64 | T.Int32);
|
|
3712
3963
|
for (const i of [...fn.params, ...fn.blocks.flatMap((b) => [...b.phis, ...b.instrs])]) {
|
|
3713
3964
|
if (!reprFits(i.repr, i.type)) bad(`%${i.id} ${i.op} is ${i.repr} but its type is ${typeName(i.type)}`);
|
|
3714
|
-
if (
|
|
3965
|
+
if (!isStatic(fn) && int64(i.type)) bad(`%${i.id} ${i.op} is an int64 outside static code`);
|
|
3966
|
+
if (i.repr !== "boxed" && (i.mayThrow && i.op !== "call_direct" && i.op !== "int_div" && i.op !== "int_mod" && !readsDouble(i) && !typedCallFits(i) || !OPS[i.op].result)) bad(`%${i.id} ${i.op} is ${i.repr} but can throw or has no result`);
|
|
3715
3967
|
}
|
|
3716
3968
|
const ret = fn.returnRepr ?? "boxed";
|
|
3717
3969
|
for (const b of fn.blocks) if (b.term?.kind === "return" && !reprFits(ret, b.term.value.type)) bad(`bb${b.id} returns ${typeName(b.term.value.type)} from a function returning ${ret}`);
|
|
@@ -3809,6 +4061,21 @@ function hasSpread(items) {
|
|
|
3809
4061
|
function notSupported(detail, node) {
|
|
3810
4062
|
throw new CompileError("NotSupported", detail, node.loc);
|
|
3811
4063
|
}
|
|
4064
|
+
/** Static code's integer operators: an `i64` (+ - * & | ^ << >> >>>; / and % by a constant other
|
|
4065
|
+
* than 0), or int_div and int_mod. */
|
|
4066
|
+
const INT_OPERATORS = /* @__PURE__ */ new Set([
|
|
4067
|
+
"+",
|
|
4068
|
+
"-",
|
|
4069
|
+
"*",
|
|
4070
|
+
"/",
|
|
4071
|
+
"%",
|
|
4072
|
+
"&",
|
|
4073
|
+
"|",
|
|
4074
|
+
"^",
|
|
4075
|
+
"<<",
|
|
4076
|
+
">>",
|
|
4077
|
+
">>>"
|
|
4078
|
+
]);
|
|
3812
4079
|
/** A function's `name` from its binding: a module's anonymous default export is "default"
|
|
3813
4080
|
* (§16.2.3.7), though its hidden binding is `*default*`. */
|
|
3814
4081
|
function functionName(binding) {
|
|
@@ -3859,7 +4126,26 @@ function lower(program, info) {
|
|
|
3859
4126
|
const captured = [...scope.bindings.values()].filter((b) => b.captured);
|
|
3860
4127
|
if (captured.length) envs.set(scope, new Map(captured.map((b, i) => [b, i])));
|
|
3861
4128
|
}
|
|
3862
|
-
const
|
|
4129
|
+
const declaredFunctions = /* @__PURE__ */ new Map();
|
|
4130
|
+
const ownNames = /* @__PURE__ */ new Map();
|
|
4131
|
+
for (const fn of info.functions.values()) {
|
|
4132
|
+
const node = fn.node;
|
|
4133
|
+
if (node.type === "FunctionDeclaration") {
|
|
4134
|
+
const binding = info.declarations.get(node.id);
|
|
4135
|
+
if (!declaredFunctions.has(binding)) declaredFunctions.set(binding, fn);
|
|
4136
|
+
} else if (node.type === "FunctionExpression" && node.id) ownNames.set(info.declarations.get(node.id), fn);
|
|
4137
|
+
}
|
|
4138
|
+
const classesById = /* @__PURE__ */ new Map();
|
|
4139
|
+
for (const sc of info.scopes.values()) {
|
|
4140
|
+
const cls = sc.node;
|
|
4141
|
+
if (sc.kind === "class" && cls.id && !classesById.has(cls.id)) classesById.set(cls.id, cls);
|
|
4142
|
+
}
|
|
4143
|
+
let printAssigned = false;
|
|
4144
|
+
const readsOwnName = /* @__PURE__ */ new Set();
|
|
4145
|
+
for (const r of info.references.values()) {
|
|
4146
|
+
if (r.id.name === "print" && r.write && !r.binding) printAssigned = true;
|
|
4147
|
+
if (r.binding && ownNames.get(r.binding) === r.func) readsOwnName.add(r.func);
|
|
4148
|
+
}
|
|
3863
4149
|
const m = {
|
|
3864
4150
|
info,
|
|
3865
4151
|
slots,
|
|
@@ -3868,15 +4154,25 @@ function lower(program, info) {
|
|
|
3868
4154
|
inferredNames: /* @__PURE__ */ new Map(),
|
|
3869
4155
|
printAssigned,
|
|
3870
4156
|
globals,
|
|
3871
|
-
importMetaSlots: /* @__PURE__ */ new Map()
|
|
4157
|
+
importMetaSlots: /* @__PURE__ */ new Map(),
|
|
4158
|
+
declaredFunctions,
|
|
4159
|
+
classesById,
|
|
4160
|
+
readsOwnName,
|
|
4161
|
+
static: program.static === true
|
|
3872
4162
|
};
|
|
3873
4163
|
const main = new FunctionLowering(m, info.program).run();
|
|
3874
4164
|
const functions = [];
|
|
3875
|
-
|
|
3876
|
-
for (const
|
|
3877
|
-
|
|
4165
|
+
const nodes = [];
|
|
4166
|
+
for (const fn of info.functions.values()) {
|
|
4167
|
+
if (fn === info.program) continue;
|
|
4168
|
+
functions.push(new FunctionLowering(m, fn).run());
|
|
4169
|
+
nodes.push(fn.node);
|
|
4170
|
+
}
|
|
4171
|
+
functions.forEach((f, i) => {
|
|
4172
|
+
const node = nodes[i];
|
|
3878
4173
|
if (node.type === "FunctionExpression" && !node.id) f.meta.jsName = m.inferredNames.get(node) ?? "";
|
|
3879
|
-
}
|
|
4174
|
+
});
|
|
4175
|
+
if (m.static) for (const fn of [main, ...functions]) fn.dialect = "static";
|
|
3880
4176
|
return {
|
|
3881
4177
|
main,
|
|
3882
4178
|
functions,
|
|
@@ -3947,8 +4243,7 @@ var FunctionLowering = class {
|
|
|
3947
4243
|
this.b = new FunctionBuilder("main", [], fn.strict, node.loc);
|
|
3948
4244
|
return;
|
|
3949
4245
|
}
|
|
3950
|
-
const
|
|
3951
|
-
const readsSelf = self !== null && [...this.info.references.values()].some((r) => r.binding === self && r.func === fn);
|
|
4246
|
+
const readsSelf = (node.type === "FunctionExpression" && node.id ? this.info.declarations.get(node.id) : null) !== null && m.readsOwnName.has(fn);
|
|
3952
4247
|
const takesArguments = fn.argumentsBinding !== null;
|
|
3953
4248
|
const mappedArguments = takesArguments && !fn.strict && fn.simpleParams;
|
|
3954
4249
|
const classCtor = node.type === "FunctionExpression" ? node.classCtor : null;
|
|
@@ -4085,7 +4380,6 @@ var FunctionLowering = class {
|
|
|
4085
4380
|
name: "arguments"
|
|
4086
4381
|
});
|
|
4087
4382
|
}
|
|
4088
|
-
/** A parameter with a default: the argument, or the default when it's undefined. */
|
|
4089
4383
|
/** After gen_start or async_start: the resume function's first block. */
|
|
4090
4384
|
startBody() {
|
|
4091
4385
|
const b = this.b;
|
|
@@ -4474,6 +4768,7 @@ var FunctionLowering = class {
|
|
|
4474
4768
|
return true;
|
|
4475
4769
|
}
|
|
4476
4770
|
write(binding, value) {
|
|
4771
|
+
if (this.m.static && binding.node.int && value.op !== "empty") value = this.toInt(value, binding.node.int);
|
|
4477
4772
|
if (this.isGlobalVar(binding)) {
|
|
4478
4773
|
this.b.emit("set_prop", [this.b.emit("global_this", []), value], { key: propKey(binding.name) });
|
|
4479
4774
|
return;
|
|
@@ -4516,6 +4811,35 @@ var FunctionLowering = class {
|
|
|
4516
4811
|
});
|
|
4517
4812
|
return ref.tdz ? b.emit("check_tdz", [v], { name: binding.name }) : v;
|
|
4518
4813
|
}
|
|
4814
|
+
/** Static code: whether `e` is an integer, as C's types say: an int binding, an integer
|
|
4815
|
+
* literal, a call of a function declared to return one, or integer arithmetic of those. A
|
|
4816
|
+
* `number` is a double, whatever values it holds. */
|
|
4817
|
+
isInt(e) {
|
|
4818
|
+
if (!this.m.static) return false;
|
|
4819
|
+
const declared = (id) => this.info.references.get(id)?.binding?.node;
|
|
4820
|
+
switch (e.type) {
|
|
4821
|
+
case "Literal": return typeof e.value === "number" && Number.isInteger(e.value) && !e.float;
|
|
4822
|
+
case "Identifier": return declared(e)?.int !== void 0;
|
|
4823
|
+
case "UnaryExpression": return (e.operator === "-" || e.operator === "~") && this.isInt(e.argument);
|
|
4824
|
+
case "UpdateExpression": return this.isInt(e.argument);
|
|
4825
|
+
case "BinaryExpression": return INT_OPERATORS.has(e.operator) && this.isInt(e.left) && this.isInt(e.right);
|
|
4826
|
+
case "AssignmentExpression": return e.target.type === "Identifier" && this.isInt(e.target);
|
|
4827
|
+
case "ConditionalExpression": return this.isInt(e.consequent) && this.isInt(e.alternate);
|
|
4828
|
+
case "CallExpression": return e.callee.type === "Identifier" && declared(e.callee)?.returnsInt !== void 0;
|
|
4829
|
+
default: return false;
|
|
4830
|
+
}
|
|
4831
|
+
}
|
|
4832
|
+
/** Static code's integer arithmetic, C's: / and % by a constant other than 0 can't throw. */
|
|
4833
|
+
int64(operator, left, right) {
|
|
4834
|
+
if ((operator === "/" || operator === "%") && !(right.op === "const" && right.lit.value !== 0)) return this.b.emit(operator === "/" ? "int_div" : "int_mod", [left, right]);
|
|
4835
|
+
return this.b.emit("i64", [left, right], { target: operator });
|
|
4836
|
+
}
|
|
4837
|
+
/** Static code: `value` converted to an integer, as C converts what is stored in one. */
|
|
4838
|
+
toInt(value, kind) {
|
|
4839
|
+
const op = kind === "i32" ? "to_i32" : "to_i64";
|
|
4840
|
+
if (value.op === op || kind === "i64" && (value.op === "i64" || value.op === "int_div" || value.op === "int_mod")) return value;
|
|
4841
|
+
return this.b.emit(op, [value]);
|
|
4842
|
+
}
|
|
4519
4843
|
/** Assigns to an identifier (not a declaration's initialization). */
|
|
4520
4844
|
assign(id, value) {
|
|
4521
4845
|
const ref = this.info.references.get(id);
|
|
@@ -4607,6 +4931,8 @@ var FunctionLowering = class {
|
|
|
4607
4931
|
case "ReturnStatement": {
|
|
4608
4932
|
let value = s.argument ? this.expression(s.argument) : b.const(void 0);
|
|
4609
4933
|
if (s.argument && this.asyncGenerator) value = b.emit("await", [value]);
|
|
4934
|
+
const returnsInt = this.fn.node.type === "Program" ? void 0 : this.fn.node.returnsInt;
|
|
4935
|
+
if (this.m.static && returnsInt) value = this.toInt(value, returnsInt);
|
|
4610
4936
|
if (this.derivedCtor) value = s.argument ? b.emit("derived_return", [value, this.loadBinding(this.fn.thisBinding)]) : this.readThis();
|
|
4611
4937
|
b.writeVariable(this.retval, value);
|
|
4612
4938
|
return this.transfer({ kind: "return" }, this.frames.length);
|
|
@@ -5103,6 +5429,7 @@ var FunctionLowering = class {
|
|
|
5103
5429
|
if (e.operator === "delete") return this.delete(e);
|
|
5104
5430
|
const v = e.operator === "typeof" && e.argument.type === "Identifier" ? this.read(this.info.references.get(e.argument), e.argument, true) : this.expression(e.argument);
|
|
5105
5431
|
b.loc = e.loc;
|
|
5432
|
+
if ((e.operator === "-" || e.operator === "~") && this.isInt(e.argument)) return e.operator === "-" ? this.int64("-", b.const(0), v) : this.int64("^", v, b.const(-1));
|
|
5106
5433
|
switch (e.operator) {
|
|
5107
5434
|
case "-": return b.emit("neg", [v]);
|
|
5108
5435
|
case "+": return b.emit("to_number", [v]);
|
|
@@ -5117,6 +5444,11 @@ var FunctionLowering = class {
|
|
|
5117
5444
|
const target = this.targetRef(e.argument, true);
|
|
5118
5445
|
const old = this.readTarget(target);
|
|
5119
5446
|
b.loc = e.loc;
|
|
5447
|
+
if (this.isInt(e.argument)) {
|
|
5448
|
+
const updated = this.int64(e.operator === "++" ? "+" : "-", old, b.const(1));
|
|
5449
|
+
this.writeTarget(target, updated);
|
|
5450
|
+
return e.prefix ? updated : old;
|
|
5451
|
+
}
|
|
5120
5452
|
const num = b.emit("to_numeric", [old]);
|
|
5121
5453
|
const updated = b.emit(e.operator === "++" ? "inc" : "dec", [num]);
|
|
5122
5454
|
this.writeTarget(target, updated);
|
|
@@ -5131,6 +5463,7 @@ var FunctionLowering = class {
|
|
|
5131
5463
|
const left = this.expression(e.left);
|
|
5132
5464
|
const right = this.expression(e.right);
|
|
5133
5465
|
b.loc = e.loc;
|
|
5466
|
+
if (this.isInt(e)) return this.int64(e.operator, left, right);
|
|
5134
5467
|
return b.emit(BINARY[e.operator], [left, right]);
|
|
5135
5468
|
}
|
|
5136
5469
|
case "LogicalExpression": return this.logical(e.operator, () => this.expression(e.left), () => this.expression(e.right));
|
|
@@ -5400,7 +5733,8 @@ var FunctionLowering = class {
|
|
|
5400
5733
|
const old = this.readTarget(target);
|
|
5401
5734
|
const rhs = this.expression(e.value);
|
|
5402
5735
|
b.loc = e.loc;
|
|
5403
|
-
const
|
|
5736
|
+
const operator = e.operator.slice(0, -1);
|
|
5737
|
+
const value = INT_OPERATORS.has(operator) && this.isInt(e.target) && this.isInt(e.value) ? this.int64(operator, old, rhs) : b.emit(BINARY[operator], [old, rhs]);
|
|
5404
5738
|
this.writeTarget(target, value);
|
|
5405
5739
|
return value;
|
|
5406
5740
|
}
|
|
@@ -5476,8 +5810,6 @@ var FunctionLowering = class {
|
|
|
5476
5810
|
value
|
|
5477
5811
|
]);
|
|
5478
5812
|
}
|
|
5479
|
-
/** An assignment target, evaluated once. `readFirst`: a compound assignment or update,
|
|
5480
|
-
* which converts a computed key once for both the read and the write. */
|
|
5481
5813
|
/** A binding's current value, without a TDZ check. */
|
|
5482
5814
|
loadBinding(binding) {
|
|
5483
5815
|
if (this.isLocal(binding)) return this.b.readVariable(binding);
|
|
@@ -5626,6 +5958,8 @@ var FunctionLowering = class {
|
|
|
5626
5958
|
this.exitScope(scope);
|
|
5627
5959
|
return ctor;
|
|
5628
5960
|
}
|
|
5961
|
+
/** An assignment target, evaluated once. `readFirst`: a compound assignment or update,
|
|
5962
|
+
* which converts a computed key once for both the read and the write. */
|
|
5629
5963
|
targetRef(t, readFirst) {
|
|
5630
5964
|
if (t.type === "Identifier") return { id: t };
|
|
5631
5965
|
const ref = this.memberRef(t);
|
|
@@ -5746,7 +6080,7 @@ var FunctionLowering = class {
|
|
|
5746
6080
|
}
|
|
5747
6081
|
return this.genericCall(this.expression(callee), b.const(void 0), e, calleeText(callee));
|
|
5748
6082
|
}
|
|
5749
|
-
const target =
|
|
6083
|
+
const target = this.m.declaredFunctions.get(binding);
|
|
5750
6084
|
if (binding.scope !== this.info.program.scope || binding.reassigned || !target || hasSpread(e.arguments) || target.argumentsBinding || target.generator) return this.genericCall(this.expression(callee), b.const(void 0), e, calleeText(callee));
|
|
5751
6085
|
const decl = target.node;
|
|
5752
6086
|
const args = e.arguments.map((a) => this.expression(a));
|
|
@@ -5768,9 +6102,9 @@ var FunctionLowering = class {
|
|
|
5768
6102
|
const binding = this.info.references.get(e.callee)?.binding;
|
|
5769
6103
|
if (!binding || binding.reassigned) return null;
|
|
5770
6104
|
let ctor;
|
|
5771
|
-
if (binding.kind === "function") ctor =
|
|
6105
|
+
if (binding.kind === "function") ctor = this.m.declaredFunctions.get(binding)?.node;
|
|
5772
6106
|
else if (binding.kind === "class" || binding.kind === "const" && binding.scope.kind === "class") {
|
|
5773
|
-
const cls =
|
|
6107
|
+
const cls = this.m.classesById.get(binding.node);
|
|
5774
6108
|
if (cls && !cls.superClass) ctor = cls.ctor;
|
|
5775
6109
|
} else {
|
|
5776
6110
|
const init = this.info.initializers.get(binding);
|
|
@@ -6032,8 +6366,7 @@ const RUNTIME = {
|
|
|
6032
6366
|
function argArray(args) {
|
|
6033
6367
|
return args.length ? `(const JSValue[]){ ${args.join(", ")} }` : "NULL";
|
|
6034
6368
|
}
|
|
6035
|
-
|
|
6036
|
-
function ident(name) {
|
|
6369
|
+
function ident$1(name) {
|
|
6037
6370
|
let out = "";
|
|
6038
6371
|
for (const ch of name) out += /[A-Za-z0-9_]/.test(ch) ? ch : `_u${ch.codePointAt(0).toString(16)}_`;
|
|
6039
6372
|
return out;
|
|
@@ -6218,12 +6551,14 @@ const C_TYPE = {
|
|
|
6218
6551
|
boxed: "JSValue",
|
|
6219
6552
|
f64: "double",
|
|
6220
6553
|
i32: "int32_t",
|
|
6554
|
+
i64: "int64_t",
|
|
6221
6555
|
bool: "bool"
|
|
6222
6556
|
};
|
|
6223
6557
|
const C_ZERO = {
|
|
6224
6558
|
boxed: "JS_UNDEFINED",
|
|
6225
6559
|
f64: "0",
|
|
6226
6560
|
i32: "0",
|
|
6561
|
+
i64: "0",
|
|
6227
6562
|
bool: "false"
|
|
6228
6563
|
};
|
|
6229
6564
|
/** What a direct entry returns after a throw: unboxed, any value, the pending
|
|
@@ -6232,17 +6567,50 @@ const C_FAIL = {
|
|
|
6232
6567
|
boxed: "JS_EXCEPTION",
|
|
6233
6568
|
f64: "0",
|
|
6234
6569
|
i32: "0",
|
|
6570
|
+
i64: "0",
|
|
6235
6571
|
bool: "false"
|
|
6236
6572
|
};
|
|
6237
6573
|
/** A C expression of repr `from` as repr `to`, when the type allows it (an int32_t only ever
|
|
6238
6574
|
* holds int32s, so converting to one can't lose anything). */
|
|
6239
6575
|
function convert(expr, from, to) {
|
|
6240
6576
|
if (from === to) return expr;
|
|
6577
|
+
if (from === "i64") return to === "boxed" ? `js_number((double)(${expr}))` : to === "f64" ? `(double)(${expr})` : to === "i32" ? `(int32_t)(${expr})` : `((${expr}) != 0)`;
|
|
6578
|
+
if (to === "i64") return from === "i32" ? `(int64_t)(${expr})` : from === "f64" ? `js_f64_to_i64(${expr})` : from === "bool" ? `(int64_t)(${expr})` : `js_value_to_i64(${expr})`;
|
|
6241
6579
|
if (from === "i32") return to === "boxed" ? `js_int32(${expr})` : `(double)(${expr})`;
|
|
6242
6580
|
if (to === "boxed") return from === "bool" ? `js_bool(${expr})` : `js_number(${expr})`;
|
|
6243
6581
|
if (to === "i32") return from === "f64" ? `(int32_t)(${expr})` : `js_as_int32_value(${expr})`;
|
|
6244
6582
|
return to === "bool" ? `js_as_bool(${expr})` : `js_as_number(${expr})`;
|
|
6245
6583
|
}
|
|
6584
|
+
/** The narrowest repr that holds every value of a type: an int32_t, an int64_t, a double, or boxed. */
|
|
6585
|
+
function reprOf(t) {
|
|
6586
|
+
if (isSubtype(t, T.Int32)) return "i32";
|
|
6587
|
+
if (isSubtype(t, T.Int64 | T.Int32)) return "i64";
|
|
6588
|
+
return isSubtype(t, Number_) ? "f64" : "boxed";
|
|
6589
|
+
}
|
|
6590
|
+
/** The operators on proven Numbers (specialize's num_* forms): their C is unboxed() either way. */
|
|
6591
|
+
const NUM_OPS = /* @__PURE__ */ new Set([
|
|
6592
|
+
"num_add",
|
|
6593
|
+
"num_sub",
|
|
6594
|
+
"num_mul",
|
|
6595
|
+
"num_div",
|
|
6596
|
+
"num_mod",
|
|
6597
|
+
"num_neg",
|
|
6598
|
+
"num_inc",
|
|
6599
|
+
"num_dec",
|
|
6600
|
+
"num_lt",
|
|
6601
|
+
"num_le",
|
|
6602
|
+
"num_gt",
|
|
6603
|
+
"num_ge",
|
|
6604
|
+
"num_eq",
|
|
6605
|
+
"num_ne",
|
|
6606
|
+
"num_bitand",
|
|
6607
|
+
"num_bitor",
|
|
6608
|
+
"num_bitxor",
|
|
6609
|
+
"num_bitnot",
|
|
6610
|
+
"num_shl",
|
|
6611
|
+
"num_sar",
|
|
6612
|
+
"num_shr"
|
|
6613
|
+
]);
|
|
6246
6614
|
/** Number comparisons: a C bool from doubles or int32s. */
|
|
6247
6615
|
const BOOL_OPS = /* @__PURE__ */ new Set([
|
|
6248
6616
|
"num_lt",
|
|
@@ -6252,7 +6620,6 @@ const BOOL_OPS = /* @__PURE__ */ new Set([
|
|
|
6252
6620
|
"num_eq",
|
|
6253
6621
|
"num_ne"
|
|
6254
6622
|
]);
|
|
6255
|
-
/** A Number as a C double literal. */
|
|
6256
6623
|
function cDouble(d) {
|
|
6257
6624
|
if (Number.isNaN(d)) return "NAN";
|
|
6258
6625
|
if (d === Infinity) return "INFINITY";
|
|
@@ -6261,9 +6628,47 @@ function cDouble(d) {
|
|
|
6261
6628
|
if (Number.isSafeInteger(d)) return `${d}.0`;
|
|
6262
6629
|
return hexFloat(d);
|
|
6263
6630
|
}
|
|
6264
|
-
/**
|
|
6631
|
+
/** A charCodeAt whose index inference proved in the string: its result an int32. */
|
|
6632
|
+
const inString = (i) => isSubtype(i.type, T.Int32) && i.type !== T.None;
|
|
6633
|
+
/** Static code's integer operator on int64_ts, as C's: wrapping (as unsigned), shifts mod 64,
|
|
6634
|
+
* INT64_MIN / -1 wrapping too (C leaves it undefined). */
|
|
6635
|
+
function int64Op(op, x, y) {
|
|
6636
|
+
const wrap = (o) => `(int64_t)((uint64_t)${x} ${o} (uint64_t)${y})`;
|
|
6637
|
+
switch (op) {
|
|
6638
|
+
case "+":
|
|
6639
|
+
case "-":
|
|
6640
|
+
case "*": return wrap(op);
|
|
6641
|
+
case "/": return `(${y} == -1 ? (int64_t)(0 - (uint64_t)${x}) : ${x} / ${y})`;
|
|
6642
|
+
case "%": return `(${y} == -1 ? INT64_C(0) : ${x} % ${y})`;
|
|
6643
|
+
case "&":
|
|
6644
|
+
case "|":
|
|
6645
|
+
case "^": return `(${x} ${op} ${y})`;
|
|
6646
|
+
case "<<": return `(int64_t)((uint64_t)${x} << (${y} & 63))`;
|
|
6647
|
+
case ">>": return `(${x} >> (${y} & 63))`;
|
|
6648
|
+
case ">>>": return `(int64_t)((uint64_t)${x} >> (${y} & 63))`;
|
|
6649
|
+
default: throw new Error(`i64 ${op}`);
|
|
6650
|
+
}
|
|
6651
|
+
}
|
|
6652
|
+
/** `typeof v === name` (or v == null, name "nullish") as a C condition: js_typeof's cases. */
|
|
6653
|
+
function isTypeTest(name, v) {
|
|
6654
|
+
switch (name) {
|
|
6655
|
+
case "nullish": return `js_is_nullish(${v})`;
|
|
6656
|
+
case "undefined": return `js_is_undefined(${v})`;
|
|
6657
|
+
case "boolean": return `js_is_bool(${v})`;
|
|
6658
|
+
case "number": return `js_is_number(${v})`;
|
|
6659
|
+
case "string": return `js_is_string(${v})`;
|
|
6660
|
+
case "symbol": return `js_is_symbol(${v})`;
|
|
6661
|
+
case "bigint": return `js_is_bigint(${v})`;
|
|
6662
|
+
case "function": return `js_is_callable(${v})`;
|
|
6663
|
+
case "object": return `(js_is_null(${v}) || (js_is_object(${v}) && !js_is_callable(${v})))`;
|
|
6664
|
+
default: return "false";
|
|
6665
|
+
}
|
|
6666
|
+
}
|
|
6667
|
+
/** Inline caches: one per *_prop site with a named key, in mod_ics[]; a keyed site's key, in
|
|
6668
|
+
* mod_keys[] (GC roots). */
|
|
6265
6669
|
var Caches = class {
|
|
6266
6670
|
count = 0;
|
|
6671
|
+
keys = 0;
|
|
6267
6672
|
};
|
|
6268
6673
|
function constant(v, strings) {
|
|
6269
6674
|
if (v === void 0) return "JS_UNDEFINED";
|
|
@@ -6278,10 +6683,10 @@ function constant(v, strings) {
|
|
|
6278
6683
|
return `js_double(${hexFloat(v)})`;
|
|
6279
6684
|
}
|
|
6280
6685
|
function cFunctionName(fn, isMain, prefix = "") {
|
|
6281
|
-
return isMain ? `${prefix}js_script` : `${prefix}jsf_${ident(fn.name)}`;
|
|
6686
|
+
return isMain ? `${prefix}js_script` : `${prefix}jsf_${ident$1(fn.name)}`;
|
|
6282
6687
|
}
|
|
6283
6688
|
function infoName(fnName, prefix) {
|
|
6284
|
-
return `${prefix}jsfi_${ident(fnName)}`;
|
|
6689
|
+
return `${prefix}jsfi_${ident$1(fnName)}`;
|
|
6285
6690
|
}
|
|
6286
6691
|
/** A function's `this` parameter in its direct entry (after the environment and the
|
|
6287
6692
|
* function object), when it takes one. */
|
|
@@ -6300,6 +6705,20 @@ function instanceSlots(fn) {
|
|
|
6300
6705
|
for (const b of fn.blocks) for (const i of b.instrs) if (addsToThis$1(fn, i)) keys.add(i.key.name);
|
|
6301
6706
|
return keys.size ? Math.min(keys.size, 16) : 4;
|
|
6302
6707
|
}
|
|
6708
|
+
/** A C test that `v` (evaluated more than once) has one of the kinds in `t`. */
|
|
6709
|
+
function typeTest(t, v) {
|
|
6710
|
+
const tests = [];
|
|
6711
|
+
if (t & T.Undefined) tests.push(`js_is_undefined(${v})`);
|
|
6712
|
+
if (t & T.Null) tests.push(`js_is_null(${v})`);
|
|
6713
|
+
if (t & T.Boolean) tests.push(`js_is_bool(${v})`);
|
|
6714
|
+
if ((t & Number_) === Number_ || t & (T.Double | T.Int64)) tests.push(`js_is_number(${v})`);
|
|
6715
|
+
else if (t & T.Int32) tests.push(`js_is_int32(${v})`);
|
|
6716
|
+
if (t & T.String) tests.push(`js_is_string(${v})`);
|
|
6717
|
+
if (t & T.Symbol) tests.push(`js_is_symbol(${v})`);
|
|
6718
|
+
if (t & T.BigInt) tests.push(`js_is_bigint(${v})`);
|
|
6719
|
+
if (t & T.Object) tests.push(`js_is_object(${v})`);
|
|
6720
|
+
return tests.length ? tests.join(" || ") : "false";
|
|
6721
|
+
}
|
|
6303
6722
|
/** The generic entry: unpacks argv into the direct entry's parameters. */
|
|
6304
6723
|
function genericEntry(fn, prefix) {
|
|
6305
6724
|
const meta = fn.meta;
|
|
@@ -6324,9 +6743,17 @@ function genericEntry(fn, prefix) {
|
|
|
6324
6743
|
].filter((p) => !(p === "callee" && (meta.takesCallee || meta.takesEnv)) && !(p === "this_val" && meta.takesThis) && !((p === "argc" || p === "argv") && usesArgv) && !(p === "new_target" && meta.takesNewTarget));
|
|
6325
6744
|
const call = `${direct}(${["ctx", ...args.map((a, k) => convert(a, "boxed", fn.params[k].repr))].join(", ")})`;
|
|
6326
6745
|
const ret = fn.returnRepr ?? "boxed";
|
|
6746
|
+
const hidden = fn.params.length - declared - Number(meta.hasRest);
|
|
6747
|
+
const checks = (meta.declared ?? []).flatMap((t, k) => {
|
|
6748
|
+
if (t === void 0) return [];
|
|
6749
|
+
const v = `(argc > ${k - hidden} ? argv[${k - hidden}] : JS_UNDEFINED)`;
|
|
6750
|
+
const view = meta.declaredViews?.[k];
|
|
6751
|
+
return [` if (!(${view ? `js_ta_is(ctx, ${v}, ${VIEWS[view].c})` : typeTest(t, v)})) return js_throw_invalid_arg_type(ctx, ${cString(fn.params[k].name ?? `argument ${k - hidden + 1}`)}, ${cString(expectedText(t, view))}, ${v});`];
|
|
6752
|
+
});
|
|
6327
6753
|
return [
|
|
6328
|
-
|
|
6754
|
+
`${meta.declared ? "" : "static "}JSValue ${direct}__generic(JSContext *ctx, JSValue callee, JSValue this_val, uint32_t argc, const JSValue *argv, JSValue new_target) {`,
|
|
6329
6755
|
...unused.length ? [` ${unused.map((u) => `(void)${u};`).join(" ")}`] : [],
|
|
6756
|
+
...checks,
|
|
6330
6757
|
...ret === "boxed" ? [` return ${call};`] : [` ${C_TYPE[ret]} r = ${call};`, ` return js_has_exception(ctx) ? JS_EXCEPTION : ${convert("r", ret, "boxed")};`],
|
|
6331
6758
|
"}"
|
|
6332
6759
|
].join("\n");
|
|
@@ -6361,6 +6788,8 @@ function functionInfo(fn, strings, embedSource, shared = false) {
|
|
|
6361
6788
|
var Hosts = class {
|
|
6362
6789
|
table;
|
|
6363
6790
|
used = /* @__PURE__ */ new Set();
|
|
6791
|
+
/** Typed entries of a typed unit this module calls: their prototypes. */
|
|
6792
|
+
typed = /* @__PURE__ */ new Map();
|
|
6364
6793
|
constructor(table) {
|
|
6365
6794
|
this.table = table;
|
|
6366
6795
|
}
|
|
@@ -6371,7 +6800,7 @@ var Hosts = class {
|
|
|
6371
6800
|
return host;
|
|
6372
6801
|
}
|
|
6373
6802
|
decls() {
|
|
6374
|
-
return [...this.used].sort().map((c) => `JSValue ${c}(JSContext *, JSValue, JSValue, uint32_t, const JSValue *, JSValue); /* host primitive */`);
|
|
6803
|
+
return [...[...this.used].sort().map((c) => `JSValue ${c}(JSContext *, JSValue, JSValue, uint32_t, const JSValue *, JSValue); /* host primitive */`), ...[...this.typed.values()].sort()];
|
|
6375
6804
|
}
|
|
6376
6805
|
};
|
|
6377
6806
|
var FunctionEmitter = class {
|
|
@@ -6388,6 +6817,9 @@ var FunctionEmitter = class {
|
|
|
6388
6817
|
/** Values something needs: instructions and terminators, and the phis feeding them. */
|
|
6389
6818
|
used = /* @__PURE__ */ new Set();
|
|
6390
6819
|
labels = /* @__PURE__ */ new Set();
|
|
6820
|
+
/** An exception leaves the function: through one shared return, which GCC compiles faster than
|
|
6821
|
+
* a return at each check (4% of a large program's compile time). */
|
|
6822
|
+
exits = false;
|
|
6391
6823
|
lines = [];
|
|
6392
6824
|
lastLoc = null;
|
|
6393
6825
|
constructor(fn, cname, strings, caches, hosts, callees) {
|
|
@@ -6459,9 +6891,8 @@ var FunctionEmitter = class {
|
|
|
6459
6891
|
v(i) {
|
|
6460
6892
|
return this.as(i, "boxed");
|
|
6461
6893
|
}
|
|
6462
|
-
/** A value's C variable. */
|
|
6463
6894
|
name(i) {
|
|
6464
|
-
return `v${i.id}${i.name ? `_${ident(i.name)}` : ""}`;
|
|
6895
|
+
return `v${i.id}${i.name ? `_${ident$1(i.name)}` : ""}`;
|
|
6465
6896
|
}
|
|
6466
6897
|
/** A value as a C expression in representation `repr` (the optimizer's unbox pass
|
|
6467
6898
|
* proved the type, so unboxing can't fail). */
|
|
@@ -6470,6 +6901,7 @@ var FunctionEmitter = class {
|
|
|
6470
6901
|
const value = i.lit.value;
|
|
6471
6902
|
if (repr === "f64") return cDouble(value);
|
|
6472
6903
|
if (repr === "i32") return value === -2147483648 ? "INT32_MIN" : `${value}`;
|
|
6904
|
+
if (repr === "i64") return Number.isSafeInteger(value) ? `INT64_C(${value})` : `js_f64_to_i64(${cDouble(value)})`;
|
|
6473
6905
|
if (repr === "bool") return value ? "true" : "false";
|
|
6474
6906
|
return constant(value, this.strings);
|
|
6475
6907
|
}
|
|
@@ -6478,9 +6910,21 @@ var FunctionEmitter = class {
|
|
|
6478
6910
|
}
|
|
6479
6911
|
/** A num_* operator on unboxed operands: plain C. Doubles; int32s where both operands are
|
|
6480
6912
|
* (an int32 result is one inference proved can't overflow); bitwise ones through ToInt32. */
|
|
6481
|
-
unboxed(i) {
|
|
6913
|
+
unboxed(i, repr = i.repr) {
|
|
6482
6914
|
const isInt = (o) => isSubtype(o.type, T.Int32);
|
|
6483
|
-
|
|
6915
|
+
const isLong = (o) => isSubtype(o.type, T.Int64 | T.Int32);
|
|
6916
|
+
if (BOOL_OPS.has(i.op) && i.operands.every(isLong) && i.operands.some((o) => (o.type & T.Int64) !== 0)) {
|
|
6917
|
+
const [x, y] = i.operands.map((o) => this.as(o, "i64"));
|
|
6918
|
+
switch (i.op) {
|
|
6919
|
+
case "num_lt": return `${x} < ${y}`;
|
|
6920
|
+
case "num_le": return `${x} <= ${y}`;
|
|
6921
|
+
case "num_gt": return `${x} > ${y}`;
|
|
6922
|
+
case "num_ge": return `${x} >= ${y}`;
|
|
6923
|
+
case "num_eq": return `${x} == ${y}`;
|
|
6924
|
+
case "num_ne": return `${x} != ${y}`;
|
|
6925
|
+
}
|
|
6926
|
+
}
|
|
6927
|
+
if (i.operands.every(isInt) && (repr === "i32" || BOOL_OPS.has(i.op))) {
|
|
6484
6928
|
const [x, y] = i.operands.map((o) => this.as(o, "i32"));
|
|
6485
6929
|
switch (i.op) {
|
|
6486
6930
|
case "num_add": return `${x} + ${y}`;
|
|
@@ -6497,7 +6941,7 @@ var FunctionEmitter = class {
|
|
|
6497
6941
|
}
|
|
6498
6942
|
const [a, b] = i.operands.map((o) => this.as(o, "f64"));
|
|
6499
6943
|
const [ia, ib] = i.operands.map((o) => isInt(o) ? this.as(o, "i32") : `js_f64_to_int32(${this.as(o, "f64")})`);
|
|
6500
|
-
const bits = (expr) =>
|
|
6944
|
+
const bits = (expr) => repr === "i32" ? expr : `(double)(${expr})`;
|
|
6501
6945
|
switch (i.op) {
|
|
6502
6946
|
case "num_add": return `${a} + ${b}`;
|
|
6503
6947
|
case "num_sub": return `${a} - ${b}`;
|
|
@@ -6554,7 +6998,6 @@ var FunctionEmitter = class {
|
|
|
6554
6998
|
this.labels.add(to);
|
|
6555
6999
|
return [...this.copies(from, to), `goto bb${to.id};`].join(" ");
|
|
6556
7000
|
}
|
|
6557
|
-
/** Where an exception raised in `b` goes. */
|
|
6558
7001
|
/** `new F(…)` of a constructor of this module. When F's value is that
|
|
6559
7002
|
* function (made in this realm), `this` comes from js_new_instance and the constructor's direct
|
|
6560
7003
|
* entry runs; an object it returns wins, as in [[Construct]] (§10.2.2). Anything else, through
|
|
@@ -6571,7 +7014,7 @@ var FunctionEmitter = class {
|
|
|
6571
7014
|
...meta.takesNewTarget ? ["c"] : []
|
|
6572
7015
|
];
|
|
6573
7016
|
const passed = i.operands.slice(1).map((o, k) => this.as(o, callee.params[hidden.length + k]?.repr ?? "boxed"));
|
|
6574
|
-
const call = `${this.strings.prefix}jsf_${ident(callee.name)}(ctx${[...hidden, ...passed].map((a) => `, ${a}`).join("")})`;
|
|
7017
|
+
const call = `${this.strings.prefix}jsf_${ident$1(callee.name)}(ctx${[...hidden, ...passed].map((a) => `, ${a}`).join("")})`;
|
|
6575
7018
|
const ret = callee.returnRepr ?? "boxed";
|
|
6576
7019
|
const result = out ? `${out} = ` : "";
|
|
6577
7020
|
this.lines.push(` { JSValue c = ${args[0]};`, ` if (JS_LIKELY(js_is_function(c) && js_as_function(c)->info == &${infoName(callee.name, this.strings.prefix)} && js_as_function(c)->realm == ctx)) {`, ` JSValue t = js_new_instance(ctx, c, ${instanceSlots(callee)}, ${this.cache()});`, ` if (JS_UNLIKELY(js_is_exception(t))) { ${fail} }`, ...ret === "boxed" ? [
|
|
@@ -6586,6 +7029,72 @@ var FunctionEmitter = class {
|
|
|
6586
7029
|
...out ? [` ${result}t;`] : []
|
|
6587
7030
|
], " } else {", ` JSValue r = js_construct(ctx, c, ${args.length - 1}, ${argArray(args.slice(1))}, JS_UNDEFINED, ${cString(i.text ?? "")});`, ` if (JS_UNLIKELY(js_is_exception(r))) { ${fail} }`, ...out ? [` ${result}r;`] : [" js_discard(r);"], " } }");
|
|
6588
7031
|
}
|
|
7032
|
+
/** An access of a typed array's view (the typed-arrays pass): the variant for its key's repr
|
|
7033
|
+
* (_l an int64_t, _i an int32_t, _d a double); a set with `strict`, of a double unboxed. A
|
|
7034
|
+
* double read (readsDouble) stores into `d`. */
|
|
7035
|
+
viewAccess(i, args, strict) {
|
|
7036
|
+
const key = i.operands[1];
|
|
7037
|
+
const [suffix, k] = key.repr === "i64" ? ["_l", this.as(key, "i64")] : key.repr === "i32" ? ["_i", this.as(key, "i32")] : key.repr === "f64" ? ["_d", this.as(key, "f64")] : ["", args[1]];
|
|
7038
|
+
const view = `&${this.name(i.operands[0])}_view`;
|
|
7039
|
+
if (strict === void 0) return readsDouble(i) ? `js_tav_num_f64${suffix}(ctx, ${view}, ${args[0]}, ${k}, &d)` : `js_tav_get_${i.taView}${suffix}(ctx, ${view}, ${args[0]}, ${k})`;
|
|
7040
|
+
const value = i.operands[2];
|
|
7041
|
+
return i.taView === "f64" && value.repr === "f64" ? `js_tav_put_f64${suffix}(ctx, ${view}, ${args[0]}, ${k}, ${this.as(value, "f64")}, ${strict})` : `js_tav_set_${i.taView}${suffix}(ctx, ${view}, ${args[0]}, ${k}, ${args[2]}, ${strict})`;
|
|
7042
|
+
}
|
|
7043
|
+
/** A double read of a Float64Array's view: false from the access is an exception. */
|
|
7044
|
+
readDouble(i, args) {
|
|
7045
|
+
this.lines.push(` { double d; if (JS_UNLIKELY(!${this.viewAccess(i, args)})) { ${this.onException(i.block)} }`, this.hasVar(i) ? ` ${this.name(i)} = ${convert("d", "f64", i.repr)}; }` : " }");
|
|
7046
|
+
}
|
|
7047
|
+
/** A call returning a JSValue: its exception checked, its result into i's variable. */
|
|
7048
|
+
boxedCall(i, call) {
|
|
7049
|
+
const out = this.hasVar(i) ? this.name(i) : null;
|
|
7050
|
+
this.lines.push(` { JSValue r = ${call};`, ` if (JS_UNLIKELY(js_is_exception(r))) { ${this.onException(i.block)} }`, ...out ? [` ${out} = ${convert("r", "boxed", i.repr)}; }`] : [" js_discard(r); }"]);
|
|
7051
|
+
}
|
|
7052
|
+
/** The string parameters whose units this function reads at indices inference proved in them:
|
|
7053
|
+
* their units taken once, on entry (strings don't change, and the GC doesn't move cells; a
|
|
7054
|
+
* rope not yet flattened has none, and each read takes the slow path). */
|
|
7055
|
+
stringViewList = null;
|
|
7056
|
+
stringViews() {
|
|
7057
|
+
this.stringViewList ??= new Set(this.fn.blocks.flatMap((b) => b.instrs).filter((i) => i.op === "char_code_at" && inString(i) && i.operands[0].op === "param").map((i) => i.operands[0]));
|
|
7058
|
+
return this.stringViewList;
|
|
7059
|
+
}
|
|
7060
|
+
/** The typed arrays this function holds views of (the typed-arrays pass). */
|
|
7061
|
+
viewList = null;
|
|
7062
|
+
views() {
|
|
7063
|
+
this.viewList ??= [.../* @__PURE__ */ new Set([...this.fn.params, ...this.fn.blocks.flatMap((b) => b.instrs)])].filter((v) => v.taView && (v.op === "param" || v.op === "new"));
|
|
7064
|
+
return this.viewList;
|
|
7065
|
+
}
|
|
7066
|
+
/** A call of a typed unit's export: its typed entry when the arguments are proven of the
|
|
7067
|
+
* declared types (as many as it declares); else, for as many arguments as it declares, the
|
|
7068
|
+
* typed entry when C's tests of them pass; else its checked entry, called directly. */
|
|
7069
|
+
typedCall(i, tc, args) {
|
|
7070
|
+
const operands = i.operands.slice(2);
|
|
7071
|
+
const fits = typedCallFits(i);
|
|
7072
|
+
if (!fits && operands.length !== tc.params.length) {
|
|
7073
|
+
this.genericCall(i, tc, args);
|
|
7074
|
+
return;
|
|
7075
|
+
}
|
|
7076
|
+
this.hosts.typed.set(tc.direct, `${C_TYPE[tc.returnRepr]} ${tc.direct}(JSContext *${tc.params.map((p) => `, ${C_TYPE[p.repr]}`).join("")}); /* typed entry */`);
|
|
7077
|
+
const tests = fits ? [] : operands.flatMap((o, k) => {
|
|
7078
|
+
const { type, view } = tc.params[k];
|
|
7079
|
+
if (view) return [`js_ta_is(ctx, ${args[k + 2]}, ${VIEWS[view].c})`];
|
|
7080
|
+
return isSubtype(o.type & ~T.Empty, type) ? [] : [`(${typeTest(type, args[k + 2])})`];
|
|
7081
|
+
});
|
|
7082
|
+
const call = `${tc.direct}(ctx${operands.map((o, k) => `, ${tests.length ? convert(args[k + 2], "boxed", tc.params[k].repr) : this.as(o, tc.params[k].repr)}`).join("")})`;
|
|
7083
|
+
if (tests.length) this.lines.push(` if (${tests.join(" && ")}) {`);
|
|
7084
|
+
if (tc.returnRepr === "boxed") this.boxedCall(i, call);
|
|
7085
|
+
else {
|
|
7086
|
+
const out = this.hasVar(i) ? this.name(i) : null;
|
|
7087
|
+
this.lines.push(` { ${C_TYPE[tc.returnRepr]} r = ${call};`, ` if (JS_UNLIKELY(js_has_exception(ctx))) { ${this.onException(i.block)} }`, ...out ? [` ${out} = ${convert("r", tc.returnRepr, i.repr)}; }`] : [" (void)r; }"]);
|
|
7088
|
+
}
|
|
7089
|
+
if (!tests.length) return;
|
|
7090
|
+
this.lines.push(" } else {");
|
|
7091
|
+
this.genericCall(i, tc, args);
|
|
7092
|
+
this.lines.push(" }");
|
|
7093
|
+
}
|
|
7094
|
+
genericCall(i, tc, args) {
|
|
7095
|
+
this.hosts.used.add(tc.generic);
|
|
7096
|
+
this.boxedCall(i, `${tc.generic}(ctx, JS_UNDEFINED, ${args[1]}, ${args.length - 2}, ${argArray(args.slice(2))}, JS_UNDEFINED)`);
|
|
7097
|
+
}
|
|
6589
7098
|
/** The values this function calls, or checks as an inlined call's callee. */
|
|
6590
7099
|
calledValues = null;
|
|
6591
7100
|
called() {
|
|
@@ -6596,11 +7105,17 @@ var FunctionEmitter = class {
|
|
|
6596
7105
|
return this.calledValues;
|
|
6597
7106
|
}
|
|
6598
7107
|
onException(b) {
|
|
6599
|
-
|
|
7108
|
+
if (b.handler) return this.goto(b, b.handler);
|
|
7109
|
+
if (this.gen) return `return ${C_FAIL[this.fn.returnRepr ?? "boxed"]};`;
|
|
7110
|
+
this.exits = true;
|
|
7111
|
+
return "goto exc_;";
|
|
6600
7112
|
}
|
|
6601
7113
|
key(i) {
|
|
6602
7114
|
return keyValue(i.key, this.strings);
|
|
6603
7115
|
}
|
|
7116
|
+
keyedCache() {
|
|
7117
|
+
return `&${this.strings.prefix}mod_keys[${this.caches.keys++}], ${this.cache()}`;
|
|
7118
|
+
}
|
|
6604
7119
|
cache() {
|
|
6605
7120
|
return `&${this.strings.prefix}mod_ics[${this.caches.count++}]`;
|
|
6606
7121
|
}
|
|
@@ -6675,7 +7190,7 @@ var FunctionEmitter = class {
|
|
|
6675
7190
|
expr = `js_new_arguments(ctx, ${args[0]}, JS_UNDEFINED, JS_UNDEFINED, NULL, 0)`;
|
|
6676
7191
|
break;
|
|
6677
7192
|
}
|
|
6678
|
-
const table = `${this.strings.prefix}jsam_${ident(this.fn.name)}`;
|
|
7193
|
+
const table = `${this.strings.prefix}jsam_${ident$1(this.fn.name)}`;
|
|
6679
7194
|
const entries = i.table.length ? i.table.map((t) => t < 0 ? "UINT32_MAX" : String(t)) : ["UINT32_MAX"];
|
|
6680
7195
|
this.strings.decls.push(datum("uint32_t", `${table}[]`, `{ ${entries.join(", ")} }`));
|
|
6681
7196
|
expr = `js_new_arguments(ctx, ${args[0]}, ${args[1]}, ${args[2]}, ${table}, ${i.table.length})`;
|
|
@@ -6727,7 +7242,7 @@ var FunctionEmitter = class {
|
|
|
6727
7242
|
const length = "name" in i.key && i.key.name === "length";
|
|
6728
7243
|
const str = i.operands[0].type !== T.None && isSubtype(i.operands[0].type, T.String);
|
|
6729
7244
|
if (str && length) {
|
|
6730
|
-
expr = `
|
|
7245
|
+
expr = convert(`(int32_t)js_string_length(${this.as(i.operands[0], "boxed")})`, "i32", i.repr);
|
|
6731
7246
|
break;
|
|
6732
7247
|
}
|
|
6733
7248
|
const get = str ? "js_get_string_prop" : length ? "js_get_length" : this.called().has(i) ? "js_get_method" : "js_get_prop";
|
|
@@ -6735,19 +7250,37 @@ var FunctionEmitter = class {
|
|
|
6735
7250
|
break;
|
|
6736
7251
|
}
|
|
6737
7252
|
case "get_elem":
|
|
6738
|
-
|
|
7253
|
+
if (readsDouble(i)) return this.readDouble(i, args);
|
|
7254
|
+
if (i.taView) {
|
|
7255
|
+
expr = this.viewAccess(i, args);
|
|
7256
|
+
break;
|
|
7257
|
+
}
|
|
7258
|
+
expr = i.keyedSite ? `js_get_keyed(ctx, ${args[0]}, ${args[1]}, ${this.keyedCache()})` : `js_get_elem(ctx, ${args[0]}, ${args[1]})`;
|
|
6739
7259
|
break;
|
|
6740
7260
|
case "get_index":
|
|
6741
|
-
|
|
7261
|
+
if (readsDouble(i)) return this.readDouble(i, args);
|
|
7262
|
+
if (i.taView) {
|
|
7263
|
+
expr = this.viewAccess(i, args);
|
|
7264
|
+
break;
|
|
7265
|
+
}
|
|
7266
|
+
expr = i.operands[1].repr === "f64" || i.operands[1].repr === "i64" ? `js_get_index_ta_f64(ctx, ${args[0]}, ${this.as(i.operands[1], "f64")})` : `js_get_index_ta(ctx, ${args[0]}, ${args[1]})`;
|
|
6742
7267
|
break;
|
|
6743
7268
|
case "set_prop":
|
|
6744
7269
|
status = "name" in i.key ? `${i.addsProp || addsToThis$1(this.fn, i) ? "js_set_prop_add" : "js_set_prop"}(ctx, ${args[0]}, ${this.key(i)}, ${args[1]}, ${strict}, ${this.cache()})` : `js_set_value(ctx, ${args[0]}, ${this.key(i)}, ${args[1]}, ${strict})`;
|
|
6745
7270
|
break;
|
|
6746
7271
|
case "set_elem":
|
|
6747
|
-
|
|
7272
|
+
if (i.taView) {
|
|
7273
|
+
status = this.viewAccess(i, args, strict);
|
|
7274
|
+
break;
|
|
7275
|
+
}
|
|
7276
|
+
status = i.keyedSite ? `js_set_keyed(ctx, ${args[0]}, ${args[1]}, ${args[2]}, ${strict}, ${this.keyedCache()})` : `js_set_elem(ctx, ${args[0]}, ${args[1]}, ${args[2]}, ${strict})`;
|
|
6748
7277
|
break;
|
|
6749
7278
|
case "set_index":
|
|
6750
|
-
|
|
7279
|
+
if (i.taView) {
|
|
7280
|
+
status = this.viewAccess(i, args, strict);
|
|
7281
|
+
break;
|
|
7282
|
+
}
|
|
7283
|
+
status = i.operands[1].repr === "f64" || i.operands[1].repr === "i64" ? `js_set_index_ta_f64(ctx, ${args[0]}, ${this.as(i.operands[1], "f64")}, ${args[2]}, ${strict})` : `js_set_index_ta(ctx, ${args[0]}, ${args[1]}, ${args[2]}, ${strict})`;
|
|
6751
7284
|
break;
|
|
6752
7285
|
case "delete_prop":
|
|
6753
7286
|
expr = `js_delete_value(ctx, ${args[0]}, ${this.key(i)}, ${strict})`;
|
|
@@ -6851,6 +7384,18 @@ var FunctionEmitter = class {
|
|
|
6851
7384
|
expr = `js_construct_spread(ctx, ${args[0]}, ${args[1]}, ${cString(i.target)})`;
|
|
6852
7385
|
break;
|
|
6853
7386
|
case "call":
|
|
7387
|
+
if (i.typedCall) {
|
|
7388
|
+
this.typedCall(i, i.typedCall, args);
|
|
7389
|
+
return;
|
|
7390
|
+
}
|
|
7391
|
+
if (i.builtin === "apply") {
|
|
7392
|
+
expr = `js_call_apply(ctx, ${args[0]}, ${args[1]}, ${args[2] ?? "JS_UNDEFINED"}, ${args[3] ?? "JS_UNDEFINED"}, ${cString(i.target)})`;
|
|
7393
|
+
break;
|
|
7394
|
+
}
|
|
7395
|
+
if (i.builtin === "call") {
|
|
7396
|
+
expr = `js_call_call(ctx, ${args[0]}, ${args[1]}, ${args.length - 2}, ${argArray(args.slice(2))}, ${cString(i.target)})`;
|
|
7397
|
+
break;
|
|
7398
|
+
}
|
|
6854
7399
|
if (i.builtin) {
|
|
6855
7400
|
expr = `js_call_${i.builtin}(ctx, ${args[0]}, ${args[1]}, ${args[2]}, ${cString(i.target)})`;
|
|
6856
7401
|
break;
|
|
@@ -6859,6 +7404,10 @@ var FunctionEmitter = class {
|
|
|
6859
7404
|
break;
|
|
6860
7405
|
case "new":
|
|
6861
7406
|
expr = `js_construct(ctx, ${args[0]}, ${args.length - 1}, ${argArray(args.slice(1))}, JS_UNDEFINED, ${cString(i.target)})`;
|
|
7407
|
+
if (i.taView && this.hasVar(i)) {
|
|
7408
|
+
this.lines.push(` ${this.name(i)} = ${expr};`, ` if (JS_UNLIKELY(js_is_exception(${this.name(i)}))) { ${this.onException(i.block)} }`, ` ${this.name(i)}_view = js_ta_view(ctx, ${this.name(i)}, ${VIEWS[i.taView].c});`);
|
|
7409
|
+
return;
|
|
7410
|
+
}
|
|
6862
7411
|
break;
|
|
6863
7412
|
case "check_tdz":
|
|
6864
7413
|
expr = `js_check_tdz(ctx, ${args[0]}, ${cString(i.name ?? "variable")})`;
|
|
@@ -6866,6 +7415,65 @@ var FunctionEmitter = class {
|
|
|
6866
7415
|
case "construct_direct":
|
|
6867
7416
|
this.constructDirect(i, args);
|
|
6868
7417
|
return;
|
|
7418
|
+
case "math": {
|
|
7419
|
+
const f = {
|
|
7420
|
+
sqrt: "sqrt",
|
|
7421
|
+
abs: "fabs",
|
|
7422
|
+
floor: "floor",
|
|
7423
|
+
ceil: "ceil",
|
|
7424
|
+
trunc: "trunc"
|
|
7425
|
+
}[i.target];
|
|
7426
|
+
expr = convert(`${f}(${this.as(i.operands[0], "f64")})`, "f64", i.repr);
|
|
7427
|
+
break;
|
|
7428
|
+
}
|
|
7429
|
+
case "int_div":
|
|
7430
|
+
case "int_mod": {
|
|
7431
|
+
const [x, y] = i.operands.map((o) => this.as(o, "i64"));
|
|
7432
|
+
this.lines.push(` if (JS_UNLIKELY(${y} == 0)) { js_discard(js_throw_range_error(ctx, "Division by zero")); ${this.onException(i.block)} }`);
|
|
7433
|
+
if (this.hasVar(i)) this.lines.push(` ${this.name(i)} = ${convert(int64Op(i.op === "int_div" ? "/" : "%", x, y), "i64", i.repr)};`);
|
|
7434
|
+
return;
|
|
7435
|
+
}
|
|
7436
|
+
case "i64":
|
|
7437
|
+
expr = convert(int64Op(i.target, this.as(i.operands[0], "i64"), this.as(i.operands[1], "i64")), "i64", i.repr);
|
|
7438
|
+
break;
|
|
7439
|
+
case "to_i32": {
|
|
7440
|
+
const o = i.operands[0];
|
|
7441
|
+
const from = reprOf(o.type);
|
|
7442
|
+
expr = convert(from === "i32" ? this.as(o, "i32") : from === "i64" ? `(int32_t)(uint32_t)${this.as(o, "i64")}` : from === "f64" ? `js_f64_to_int32(${this.as(o, "f64")})` : `(int32_t)(uint32_t)js_value_to_i64(${this.as(o, "boxed")})`, "i32", i.repr);
|
|
7443
|
+
break;
|
|
7444
|
+
}
|
|
7445
|
+
case "to_i64": {
|
|
7446
|
+
const o = i.operands[0];
|
|
7447
|
+
const from = reprOf(o.type);
|
|
7448
|
+
expr = convert(convert(this.as(o, from), from, "i64"), "i64", i.repr);
|
|
7449
|
+
break;
|
|
7450
|
+
}
|
|
7451
|
+
case "int_add":
|
|
7452
|
+
case "int_sub":
|
|
7453
|
+
case "int_mul": {
|
|
7454
|
+
const [x, y] = i.operands.map((o) => `(uint32_t)${this.as(o, "i32")}`);
|
|
7455
|
+
expr = convert(`(int32_t)(${x} ${i.op === "int_add" ? "+" : i.op === "int_sub" ? "-" : "*"} ${y})`, "i32", i.repr);
|
|
7456
|
+
break;
|
|
7457
|
+
}
|
|
7458
|
+
case "char_code_at": {
|
|
7459
|
+
if (!inString(i)) {
|
|
7460
|
+
expr = convert(`js_string_char_code_at(ctx, ${args[0]}, ${this.as(i.operands[1], "f64")})`, "f64", i.repr);
|
|
7461
|
+
break;
|
|
7462
|
+
}
|
|
7463
|
+
const s = i.operands[0];
|
|
7464
|
+
const k = `(uint32_t)${this.as(i.operands[1], "i32")}`;
|
|
7465
|
+
expr = convert(this.stringViews().has(s) ? `(${this.name(s)}_units.length ? (int32_t)js_view_at(${this.name(s)}_units, ${k}) : js_string_unit_at(ctx, ${args[0]}, ${k}))` : `js_string_unit_at(ctx, ${args[0]}, ${k})`, "i32", i.repr);
|
|
7466
|
+
break;
|
|
7467
|
+
}
|
|
7468
|
+
case "identical":
|
|
7469
|
+
case "strict_eq_atom":
|
|
7470
|
+
case "strict_eq_num":
|
|
7471
|
+
case "is_type": {
|
|
7472
|
+
const test = i.op === "identical" ? `js_same_bits(${args[0]}, ${args[1]})` : i.op === "strict_eq_num" ? `js_strict_eq_num(${this.v(i.operands[0])}, ${cDouble(i.operands[1].lit.value)})` : i.op === "strict_eq_atom" ? `js_strict_eq_atom(ctx, ${args[0]}, ${args[1]})` : isTypeTest(i.target, args[0]);
|
|
7473
|
+
const cond = i.index ? `!${test}` : test;
|
|
7474
|
+
expr = i.repr === "bool" ? `(${cond})` : `js_bool(${cond})`;
|
|
7475
|
+
break;
|
|
7476
|
+
}
|
|
6869
7477
|
case "same_function": {
|
|
6870
7478
|
const cond = `js_is_function(${args[0]}) && js_as_function(${args[0]})->info == &${infoName(i.target, this.strings.prefix)} && js_as_function(${args[0]})->realm == ctx`;
|
|
6871
7479
|
expr = i.repr === "bool" ? `(${cond})` : `js_bool(${cond})`;
|
|
@@ -6882,8 +7490,13 @@ var FunctionEmitter = class {
|
|
|
6882
7490
|
break;
|
|
6883
7491
|
case "call_direct": {
|
|
6884
7492
|
const callee = this.callees.get(i.target);
|
|
7493
|
+
if (callee?.meta?.declared && !declaredCallFits(i, callee)) {
|
|
7494
|
+
const hidden = callee.meta.declared.findIndex((t) => t !== void 0);
|
|
7495
|
+
this.boxedCall(i, `${this.strings.prefix}jsf_${ident$1(i.target)}__generic(ctx, JS_UNDEFINED, JS_UNDEFINED, ${i.operands.length - hidden}, ${argArray(i.operands.slice(hidden).map((o) => this.as(o, "boxed")))}, JS_UNDEFINED)`);
|
|
7496
|
+
return;
|
|
7497
|
+
}
|
|
6885
7498
|
const passed = i.operands.map((o, k) => this.as(o, callee?.params[k]?.repr ?? "boxed"));
|
|
6886
|
-
const call = `${this.strings.prefix}jsf_${ident(i.target)}(ctx${passed.map((a) => `, ${a}`).join("")})`;
|
|
7499
|
+
const call = `${this.strings.prefix}jsf_${ident$1(i.target)}(ctx${passed.map((a) => `, ${a}`).join("")})`;
|
|
6887
7500
|
const ret = callee?.returnRepr ?? "boxed";
|
|
6888
7501
|
if (ret === "boxed") {
|
|
6889
7502
|
const result = this.hasVar(i) ? this.name(i) : "r";
|
|
@@ -6896,6 +7509,12 @@ var FunctionEmitter = class {
|
|
|
6896
7509
|
if (i.mayThrow) this.lines.push(` if (JS_UNLIKELY(js_has_exception(ctx))) { ${this.onException(i.block)} }`);
|
|
6897
7510
|
return;
|
|
6898
7511
|
}
|
|
7512
|
+
case "not": {
|
|
7513
|
+
const o = i.operands[0];
|
|
7514
|
+
const bool = o.type !== T.None && isSubtype(o.type, T.Boolean);
|
|
7515
|
+
expr = i.repr === "boxed" && !bool ? `js_not(${args[0]})` : convert(`!(${bool ? this.as(o, "bool") : `js_truthy(${args[0]})`})`, "bool", i.repr);
|
|
7516
|
+
break;
|
|
7517
|
+
}
|
|
6899
7518
|
case "call_runtime": {
|
|
6900
7519
|
const r = RUNTIME[i.target];
|
|
6901
7520
|
if (!r) throw new Error(`unknown runtime function ${i.target}`);
|
|
@@ -6907,12 +7526,18 @@ var FunctionEmitter = class {
|
|
|
6907
7526
|
expr = this.unboxed(i);
|
|
6908
7527
|
break;
|
|
6909
7528
|
}
|
|
7529
|
+
if (NUM_OPS.has(i.op)) {
|
|
7530
|
+
const raw = BOOL_OPS.has(i.op) ? "bool" : reprOf(i.type);
|
|
7531
|
+
expr = convert(this.unboxed(i, raw), raw, "boxed");
|
|
7532
|
+
break;
|
|
7533
|
+
}
|
|
6910
7534
|
const [fn, withCtx] = CALLS[i.op];
|
|
6911
7535
|
expr = `${fn}${i.numberFirst ? "_fast" : ""}(${[...withCtx ? ["ctx"] : [], ...args].join(", ")})`;
|
|
6912
7536
|
}
|
|
6913
7537
|
}
|
|
6914
7538
|
if (status !== null) {
|
|
6915
7539
|
this.lines.push(` if (JS_UNLIKELY(${status} < 0)) { ${this.onException(i.block)} }`);
|
|
7540
|
+
this.retakeViews(i);
|
|
6916
7541
|
return;
|
|
6917
7542
|
}
|
|
6918
7543
|
expr = expr;
|
|
@@ -6921,6 +7546,14 @@ var FunctionEmitter = class {
|
|
|
6921
7546
|
if (i.mayThrow) this.lines.push(` if (JS_UNLIKELY(js_is_exception(${this.name(i)}))) { ${this.onException(i.block)} }`);
|
|
6922
7547
|
} else if (i.mayThrow) this.lines.push(` if (JS_UNLIKELY(js_is_exception(${expr}))) { ${this.onException(i.block)} }`);
|
|
6923
7548
|
else if (i.op === "take_exception" || !OPS[i.op].result || OPS[i.op].callsUser) this.lines.push(` js_discard(${expr});`);
|
|
7549
|
+
this.retakeViews(i);
|
|
7550
|
+
}
|
|
7551
|
+
/** After an operation that may run code (a call, a getter, valueOf), a parameter's view again:
|
|
7552
|
+
* that code may have detached or resized its buffer. A view's own accesses take it again on
|
|
7553
|
+
* their slow paths. */
|
|
7554
|
+
retakeViews(i) {
|
|
7555
|
+
if (!OPS[i.op].callsUser || !i.mayThrow || i.taView) return;
|
|
7556
|
+
for (const p of this.views()) if (p.op === "param") this.lines.push(` ${this.name(p)}_view = js_ta_view(ctx, ${this.name(p)}, ${VIEWS[p.taView].c});`);
|
|
6924
7557
|
}
|
|
6925
7558
|
terminator(b) {
|
|
6926
7559
|
const t = b.term;
|
|
@@ -7016,8 +7649,13 @@ var FunctionEmitter = class {
|
|
|
7016
7649
|
const signature = this.prototype().slice(0, -1);
|
|
7017
7650
|
const body = this.emitBlocks(fn.blocks, null);
|
|
7018
7651
|
const locals = [];
|
|
7019
|
-
for (const b of fn.blocks) for (const i of [...b.phis, ...b.instrs]) if (this.hasVar(i))
|
|
7652
|
+
for (const b of fn.blocks) for (const i of [...b.phis, ...b.instrs]) if (this.hasVar(i)) {
|
|
7653
|
+
locals.push(` ${C_TYPE[i.repr]} ${this.name(i)} = ${C_ZERO[i.repr]};`);
|
|
7654
|
+
if (i.op === "new" && i.taView) locals.push(` JSTAView ${this.name(i)}_view = { NULL, 0 };`);
|
|
7655
|
+
}
|
|
7020
7656
|
const unusedParams = fn.params.filter((p) => !this.used.has(p)).map((p) => ` (void)${this.name(p)};`);
|
|
7657
|
+
for (const p of this.views().filter((v) => v.op === "param")) locals.push(` JSTAView ${this.name(p)}_view = js_ta_view(ctx, ${this.name(p)}, ${VIEWS[p.taView].c});`);
|
|
7658
|
+
for (const p of this.stringViews()) locals.push(` JSStrView ${this.name(p)}_units = { NULL, 0, true }; (void)js_string_units(${this.name(p)}, &${this.name(p)}_units);`);
|
|
7021
7659
|
const calls = fn.blocks.some((b) => b.instrs.some((i) => OPS[i.op].callsUser));
|
|
7022
7660
|
const usesCtx = calls || body.some((l) => /\bctx\b/.test(l));
|
|
7023
7661
|
const ret = fn.returnRepr ?? "boxed";
|
|
@@ -7028,11 +7666,12 @@ var FunctionEmitter = class {
|
|
|
7028
7666
|
...unusedParams,
|
|
7029
7667
|
...calls ? [ret === "boxed" ? " JS_CHECK_STACK(ctx);" : ` JS_CHECK_STACK_OR(ctx, ${C_FAIL[ret]});`] : [],
|
|
7030
7668
|
...body,
|
|
7669
|
+
...this.exits ? [`exc_: return ${C_FAIL[ret]};`] : [],
|
|
7031
7670
|
"}"
|
|
7032
7671
|
].join("\n");
|
|
7033
7672
|
}
|
|
7034
7673
|
linkage() {
|
|
7035
|
-
return this.shared ? "JS_MAYBE_UNUSED " : "static JS_MAYBE_UNUSED ";
|
|
7674
|
+
return this.shared || this.fn.meta?.declared ? "JS_MAYBE_UNUSED " : "static JS_MAYBE_UNUSED ";
|
|
7036
7675
|
}
|
|
7037
7676
|
/** The entry's prototype: in program.h when it's shared, else in the function's own file. */
|
|
7038
7677
|
prototype() {
|
|
@@ -7048,13 +7687,60 @@ var FunctionEmitter = class {
|
|
|
7048
7687
|
function emitProgram(m, options = {}) {
|
|
7049
7688
|
return emitModule(m, "", null, options);
|
|
7050
7689
|
}
|
|
7051
|
-
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
|
|
7055
|
-
const
|
|
7056
|
-
|
|
7057
|
-
|
|
7690
|
+
/** A test262 harness file, compiled once and linked into the test host: its tables
|
|
7691
|
+
* and `<prefix>js_script` are exported, `<prefix>register(rt)` registers the tables, and the
|
|
7692
|
+
* units run the script. `atoms`: the atoms the harness files before it in the same C file define. */
|
|
7693
|
+
function emitHarnessScript(m, prefix, atoms, options = {}) {
|
|
7694
|
+
const e = prepare(m, prefix, {
|
|
7695
|
+
...options,
|
|
7696
|
+
harness: {
|
|
7697
|
+
atoms,
|
|
7698
|
+
register: "",
|
|
7699
|
+
scripts: []
|
|
7700
|
+
}
|
|
7701
|
+
});
|
|
7702
|
+
e.strings.externalInFile = true;
|
|
7703
|
+
e.main.shared = true;
|
|
7704
|
+
const emitters = [...e.functions, e.main];
|
|
7705
|
+
const bodies = emitters.map((f) => f.emit());
|
|
7706
|
+
const infos = m.functions.map((fn) => functionInfo(fn, e.strings, e.embedSource));
|
|
7707
|
+
const nGlobals = Math.max(1, m.globals.length);
|
|
7708
|
+
const cells = e.strings.cells;
|
|
7709
|
+
return {
|
|
7710
|
+
c: [
|
|
7711
|
+
GENERATED,
|
|
7712
|
+
"#include <stdlib.h>",
|
|
7713
|
+
"#include \"js_runtime.h\"",
|
|
7714
|
+
"",
|
|
7715
|
+
...e.hosts.used.size ? [...e.hosts.decls(), ""] : [],
|
|
7716
|
+
...dataLines(m, prefix, `${prefix}register`, options, e, ""),
|
|
7717
|
+
"",
|
|
7718
|
+
...emitters.flatMap((f) => [f.prototype(), ...f.resumePrototype()]),
|
|
7719
|
+
...m.functions.map((fn) => genericPrototype(fn, prefix)),
|
|
7720
|
+
"",
|
|
7721
|
+
...infos,
|
|
7722
|
+
...infos.length ? [""] : [],
|
|
7723
|
+
...bodies.flatMap((b) => [b, ""]),
|
|
7724
|
+
...m.functions.flatMap((fn) => [genericEntry(fn, prefix), ""]),
|
|
7725
|
+
`void ${prefix}register(JSRuntime *rt) {`,
|
|
7726
|
+
...cells.length ? [` js_register_static_atoms(rt, ${prefix}mod_atoms, ${cells.length});`] : [],
|
|
7727
|
+
...e.caches.count ? [` js_register_prop_caches(rt, ${prefix}mod_ics, ${e.caches.count});`] : [],
|
|
7728
|
+
...e.caches.keys ? [` js_register_roots(rt, ${prefix}mod_keys, ${e.caches.keys}); /* zeroed: EMPTY, no key yet */`] : [],
|
|
7729
|
+
` for (size_t i = 0; i < ${nGlobals}; i++) ${prefix}mod_globals[i] = JS_UNDEFINED;`,
|
|
7730
|
+
` js_register_roots(rt, ${prefix}mod_globals, ${nGlobals});`,
|
|
7731
|
+
"}",
|
|
7732
|
+
""
|
|
7733
|
+
].join("\n"),
|
|
7734
|
+
atoms: e.strings.atoms
|
|
7735
|
+
};
|
|
7736
|
+
}
|
|
7737
|
+
function prepare(m, p, options) {
|
|
7738
|
+
const strings = new Strings(p, options.regexps, options.harness?.atoms);
|
|
7739
|
+
const caches = new Caches();
|
|
7740
|
+
const hosts = new Hosts(options.hostPrimitives ?? {});
|
|
7741
|
+
const callees = new Map(m.functions.map((fn) => [fn.name, fn]));
|
|
7742
|
+
return {
|
|
7743
|
+
strings,
|
|
7058
7744
|
caches,
|
|
7059
7745
|
hosts,
|
|
7060
7746
|
functions: m.functions.map((fn) => new FunctionEmitter(fn, cFunctionName(fn, false, p), strings, caches, hosts, callees)),
|
|
@@ -7063,7 +7749,7 @@ function prepare(m, p, options) {
|
|
|
7063
7749
|
};
|
|
7064
7750
|
}
|
|
7065
7751
|
function genericPrototype(fn, p) {
|
|
7066
|
-
return
|
|
7752
|
+
return `${fn.meta.declared ? "" : "static "}JSValue ${cFunctionName(fn, false, p)}__generic(JSContext *ctx, JSValue callee, JSValue this_val, uint32_t argc, const JSValue *argv, JSValue new_target);`;
|
|
7067
7753
|
}
|
|
7068
7754
|
/** The module's data, once every function is emitted: globals, literals, caches, tables.
|
|
7069
7755
|
* `storage`: "static " in one file; "" in a split program's program.c, which defines them for
|
|
@@ -7079,6 +7765,7 @@ function dataLines(m, p, entry, options, e, storage) {
|
|
|
7079
7765
|
...e.embedSource ? [sourceCell(m.source, p).define(storage)] : [],
|
|
7080
7766
|
...atoms.length ? [`static const JSString *const ${p}mod_atoms[] = { ${atoms.map((a) => `(const JSString *)&${a}`).join(", ")} };`] : [],
|
|
7081
7767
|
...e.caches.count ? [`${storage}JSPropCache ${p}mod_ics[${e.caches.count}];`] : [],
|
|
7768
|
+
...e.caches.keys ? [`${storage}JSValue ${p}mod_keys[${e.caches.keys}];`] : [],
|
|
7082
7769
|
...options.components?.length && !entry ? [`static const JSComponent *const ${p}mod_components[] = { ${options.components.map((c) => `&${c}`).join(", ")} };`] : [],
|
|
7083
7770
|
...options.core && !entry ? [
|
|
7084
7771
|
`static const JSCoreEntry ${p}mod_core[] = {`,
|
|
@@ -7096,6 +7783,7 @@ function mainLines(m, p, entry, options, e) {
|
|
|
7096
7783
|
" JSRuntime *rt = js_runtime_new();",
|
|
7097
7784
|
...atoms.length ? [` js_register_static_atoms(rt, ${p}mod_atoms, ${atoms.length});`] : [],
|
|
7098
7785
|
...e.caches.count ? [` js_register_prop_caches(rt, ${p}mod_ics, ${e.caches.count});`] : [],
|
|
7786
|
+
...e.caches.keys ? [` js_register_roots(rt, ${p}mod_keys, ${e.caches.keys}); /* zeroed: EMPTY, no key yet */`] : [],
|
|
7099
7787
|
...options.harness?.scripts.length ? [` void ${options.harness.register}(JSRuntime *rt);`, ` ${options.harness.register}(rt); /* the harness's tables */`] : [],
|
|
7100
7788
|
options.core && !entry ? ` js_use_core(rt, ${p}mod_core, ${options.core.length}); /* the core methods it names */` : " js_use_all_core(rt); /* every core method */",
|
|
7101
7789
|
" JSContext *ctx = js_context_new(rt);",
|
|
@@ -7166,17 +7854,20 @@ function partition(fns, size) {
|
|
|
7166
7854
|
if (used.length <= 1) return null;
|
|
7167
7855
|
return new Map(fns.map((fn, i) => [fn, used.indexOf(raw[i]) + 1]));
|
|
7168
7856
|
}
|
|
7169
|
-
/** The program's C files.
|
|
7170
|
-
*
|
|
7171
|
-
*
|
|
7172
|
-
*
|
|
7173
|
-
function emitProgramFiles(m, options = {}) {
|
|
7857
|
+
/** The program's C files: program.c alone under `options.partSize` instructions, else
|
|
7858
|
+
* program_1.c … program_N.c (the functions, compiled in parallel), program.h and program.c (the
|
|
7859
|
+
* data and main()). Yielded as each is made, so a caller that writes each away never holds a
|
|
7860
|
+
* large program's C (hundreds of megabytes) at once. */
|
|
7861
|
+
function* emitProgramFiles(m, options = {}) {
|
|
7174
7862
|
const fns = [...m.functions, m.main];
|
|
7175
7863
|
const parts = options.partSize ? partition(fns, options.partSize) : null;
|
|
7176
|
-
if (!parts)
|
|
7177
|
-
|
|
7178
|
-
|
|
7179
|
-
|
|
7864
|
+
if (!parts) {
|
|
7865
|
+
yield {
|
|
7866
|
+
name: "program.c",
|
|
7867
|
+
text: emitProgram(m, options)
|
|
7868
|
+
};
|
|
7869
|
+
return;
|
|
7870
|
+
}
|
|
7180
7871
|
const e = prepare(m, "", options);
|
|
7181
7872
|
const emitters = new Map(fns.map((fn, i) => [fn, i < m.functions.length ? e.functions[i] : e.main]));
|
|
7182
7873
|
const byName = new Map(m.functions.map((fn) => [fn.name, fn]));
|
|
@@ -7189,46 +7880,15 @@ function emitProgramFiles(m, options = {}) {
|
|
|
7189
7880
|
if (i.op === "call_direct" || i.op === "construct_direct") emitters.get(target).shared = true;
|
|
7190
7881
|
if (i.op !== "call_direct") sharedInfos.add(target);
|
|
7191
7882
|
}
|
|
7192
|
-
const
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
"",
|
|
7200
|
-
...e.hosts.used.size ? [...e.hosts.decls(), ""] : [],
|
|
7201
|
-
"#ifndef VISALJS_PROGRAM_DATA /* program.c has the definitions */",
|
|
7202
|
-
`extern JSValue mod_globals[${Math.max(1, m.globals.length)}];`,
|
|
7203
|
-
...e.caches.count ? [`extern JSPropCache mod_ics[${e.caches.count}];`] : [],
|
|
7204
|
-
...e.strings.decls.map((d) => d.declare),
|
|
7205
|
-
...e.embedSource ? [sourceCell(m.source, "").declare] : [],
|
|
7206
|
-
"#endif",
|
|
7207
|
-
"",
|
|
7208
|
-
...fns.filter((fn) => emitters.get(fn).shared).map((fn) => emitters.get(fn).prototype()),
|
|
7209
|
-
...[...sharedInfos].map((fn) => `extern const JSFunctionInfo ${infoName(fn.name, "")};`),
|
|
7210
|
-
""
|
|
7211
|
-
];
|
|
7212
|
-
const data = [
|
|
7213
|
-
GENERATED,
|
|
7214
|
-
"#define VISALJS_PROGRAM_DATA",
|
|
7215
|
-
"#include \"program.h\"",
|
|
7216
|
-
"",
|
|
7217
|
-
...dataLines(m, "", null, options, e, ""),
|
|
7218
|
-
"",
|
|
7219
|
-
...mainLines(m, "", null, options, e)
|
|
7220
|
-
];
|
|
7221
|
-
const files = [{
|
|
7222
|
-
name: "program.h",
|
|
7223
|
-
text: header.join("\n")
|
|
7224
|
-
}, {
|
|
7225
|
-
name: "program.c",
|
|
7226
|
-
text: data.join("\n")
|
|
7227
|
-
}];
|
|
7228
|
-
const count = Math.max(...parts.values());
|
|
7229
|
-
for (let k = 1; k <= count; k++) {
|
|
7230
|
-
const own = fns.filter((fn) => parts.get(fn) === k);
|
|
7883
|
+
const byPart = /* @__PURE__ */ new Map();
|
|
7884
|
+
for (const fn of fns) {
|
|
7885
|
+
const k = parts.get(fn);
|
|
7886
|
+
if (!byPart.has(k)) byPart.set(k, []);
|
|
7887
|
+
byPart.get(k).push(fn);
|
|
7888
|
+
}
|
|
7889
|
+
for (const [k, own] of byPart) {
|
|
7231
7890
|
const made = own.filter((fn) => fn !== m.main);
|
|
7891
|
+
const bodies = own.map((fn) => emitters.get(fn).emit());
|
|
7232
7892
|
const text = [
|
|
7233
7893
|
GENERATED,
|
|
7234
7894
|
"#include \"program.h\"",
|
|
@@ -7239,17 +7899,50 @@ function emitProgramFiles(m, options = {}) {
|
|
|
7239
7899
|
}),
|
|
7240
7900
|
...made.map((fn) => genericPrototype(fn, "")),
|
|
7241
7901
|
"",
|
|
7242
|
-
...made.map((fn) =>
|
|
7902
|
+
...made.map((fn) => functionInfo(fn, e.strings, e.embedSource, sharedInfos.has(fn))),
|
|
7243
7903
|
...made.length ? [""] : [],
|
|
7244
|
-
...
|
|
7904
|
+
...bodies.flatMap((body) => [body, ""]),
|
|
7245
7905
|
...made.flatMap((fn) => [genericEntry(fn, ""), ""])
|
|
7246
7906
|
];
|
|
7247
|
-
|
|
7907
|
+
yield {
|
|
7248
7908
|
name: `program_${k}.c`,
|
|
7249
7909
|
text: text.join("\n")
|
|
7250
|
-
}
|
|
7910
|
+
};
|
|
7251
7911
|
}
|
|
7252
|
-
|
|
7912
|
+
yield {
|
|
7913
|
+
name: "program.h",
|
|
7914
|
+
text: [
|
|
7915
|
+
GENERATED,
|
|
7916
|
+
"/* What the program's files share: program.c defines the data, program_N.c the functions. */",
|
|
7917
|
+
"#include <stdlib.h>",
|
|
7918
|
+
"#include \"js_runtime.h\"",
|
|
7919
|
+
"",
|
|
7920
|
+
...e.hosts.used.size ? [...e.hosts.decls(), ""] : [],
|
|
7921
|
+
"#ifndef VISALJS_PROGRAM_DATA /* program.c has the definitions */",
|
|
7922
|
+
`extern JSValue mod_globals[${Math.max(1, m.globals.length)}];`,
|
|
7923
|
+
...e.caches.count ? [`extern JSPropCache mod_ics[${e.caches.count}];`] : [],
|
|
7924
|
+
...e.caches.keys ? [`extern JSValue mod_keys[${e.caches.keys}];`] : [],
|
|
7925
|
+
...e.strings.decls.map((d) => d.declare),
|
|
7926
|
+
...e.embedSource ? [sourceCell(m.source, "").declare] : [],
|
|
7927
|
+
"#endif",
|
|
7928
|
+
"",
|
|
7929
|
+
...fns.filter((fn) => emitters.get(fn).shared).map((fn) => emitters.get(fn).prototype()),
|
|
7930
|
+
...[...sharedInfos].map((fn) => `extern const JSFunctionInfo ${infoName(fn.name, "")};`),
|
|
7931
|
+
""
|
|
7932
|
+
].join("\n")
|
|
7933
|
+
};
|
|
7934
|
+
yield {
|
|
7935
|
+
name: "program.c",
|
|
7936
|
+
text: [
|
|
7937
|
+
GENERATED,
|
|
7938
|
+
"#define VISALJS_PROGRAM_DATA",
|
|
7939
|
+
"#include \"program.h\"",
|
|
7940
|
+
"",
|
|
7941
|
+
...dataLines(m, "", null, options, e, ""),
|
|
7942
|
+
"",
|
|
7943
|
+
...mainLines(m, "", null, options, e)
|
|
7944
|
+
].join("\n")
|
|
7945
|
+
};
|
|
7253
7946
|
}
|
|
7254
7947
|
//#endregion
|
|
7255
7948
|
//#region runtime/components.ts
|
|
@@ -7795,7 +8488,7 @@ function pauses(fn) {
|
|
|
7795
8488
|
* optimizer's to choose. */
|
|
7796
8489
|
function closedFunctions(m) {
|
|
7797
8490
|
const byName = new Map(m.functions.map((fn) => [fn.name, fn]));
|
|
7798
|
-
const closed = new Set(m.functions.filter((fn) => fn.meta && !pauses(fn)));
|
|
8491
|
+
const closed = new Set(m.functions.filter((fn) => fn.meta && !fn.meta.external && !pauses(fn)));
|
|
7799
8492
|
for (const fn of functionsOf(m)) for (const b of fn.blocks) for (const i of b.instrs) {
|
|
7800
8493
|
if (i.op !== "make_function" && i.op !== "call_direct") continue;
|
|
7801
8494
|
const target = byName.get(i.target);
|
|
@@ -7817,6 +8510,35 @@ function callers(m) {
|
|
|
7817
8510
|
return result;
|
|
7818
8511
|
}
|
|
7819
8512
|
//#endregion
|
|
8513
|
+
//#region optimizer/src/constant-keys.ts
|
|
8514
|
+
const constantKeys = {
|
|
8515
|
+
name: "constant-keys",
|
|
8516
|
+
run(m, count) {
|
|
8517
|
+
const stores = /* @__PURE__ */ new Map();
|
|
8518
|
+
for (const fn of functionsOf(m)) for (const b of fn.blocks) for (const i of b.instrs) if (i.op === "store_global" && i.operands[0].op !== "empty") stores.set(i.index, (stores.get(i.index) ?? 0) + 1);
|
|
8519
|
+
const storedOnce = (key) => {
|
|
8520
|
+
const load = key.op === "check_tdz" ? key.operands[0] : key;
|
|
8521
|
+
return load.op === "load_global" && stores.get(load.index) === 1;
|
|
8522
|
+
};
|
|
8523
|
+
for (const fn of functionsOf(m)) for (const b of fn.blocks) for (const i of b.instrs) {
|
|
8524
|
+
if (i.op !== "get_elem" && i.op !== "set_elem") continue;
|
|
8525
|
+
const key = i.operands[1];
|
|
8526
|
+
if (key.op === "const" && typeof key.lit.value === "string") {
|
|
8527
|
+
const k = propKey(key.lit.value);
|
|
8528
|
+
if (!("name" in k)) continue;
|
|
8529
|
+
i.op = i.op === "get_elem" ? "get_prop" : "set_prop";
|
|
8530
|
+
i.key = k;
|
|
8531
|
+
i.operands.splice(1, 1);
|
|
8532
|
+
count("named", "element access", 1, "element accesses");
|
|
8533
|
+
} else if (storedOnce(key)) {
|
|
8534
|
+
i.keyedSite = true;
|
|
8535
|
+
count("cached", "keyed site");
|
|
8536
|
+
}
|
|
8537
|
+
}
|
|
8538
|
+
return m;
|
|
8539
|
+
}
|
|
8540
|
+
};
|
|
8541
|
+
//#endregion
|
|
7820
8542
|
//#region optimizer/src/dce.ts
|
|
7821
8543
|
/** Operations with no effect but their result. */
|
|
7822
8544
|
const REMOVABLE = /* @__PURE__ */ new Set([
|
|
@@ -7864,7 +8586,15 @@ const REMOVABLE = /* @__PURE__ */ new Set([
|
|
|
7864
8586
|
"host_function",
|
|
7865
8587
|
"same_function",
|
|
7866
8588
|
"function_env",
|
|
7867
|
-
"construct_result"
|
|
8589
|
+
"construct_result",
|
|
8590
|
+
"char_code_at",
|
|
8591
|
+
"math",
|
|
8592
|
+
"int_add",
|
|
8593
|
+
"int_sub",
|
|
8594
|
+
"int_mul",
|
|
8595
|
+
"to_i64",
|
|
8596
|
+
"to_i32",
|
|
8597
|
+
"i64"
|
|
7868
8598
|
]);
|
|
7869
8599
|
const dce = {
|
|
7870
8600
|
name: "dce",
|
|
@@ -7902,6 +8632,45 @@ function dceFunction(fn) {
|
|
|
7902
8632
|
return dead.size;
|
|
7903
8633
|
}
|
|
7904
8634
|
//#endregion
|
|
8635
|
+
//#region optimizer/src/typed-arrays.ts
|
|
8636
|
+
const typedArrays = {
|
|
8637
|
+
name: "typed-arrays",
|
|
8638
|
+
run(m, count) {
|
|
8639
|
+
for (const fn of functionsOf(m)) {
|
|
8640
|
+
if (pauses(fn)) continue;
|
|
8641
|
+
const made = /* @__PURE__ */ new Map();
|
|
8642
|
+
fn.meta?.declaredViews?.forEach((kind, k) => {
|
|
8643
|
+
if (kind) made.set(fn.params[k], kind);
|
|
8644
|
+
});
|
|
8645
|
+
for (const b of fn.blocks) for (const i of b.instrs) {
|
|
8646
|
+
const ctor = i.operands[0];
|
|
8647
|
+
const kind = i.op === "new" && i.operands.length === 2 && ctor?.op === "global_name" ? viewOf(ctor.target) : void 0;
|
|
8648
|
+
if (kind) made.set(i, kind);
|
|
8649
|
+
}
|
|
8650
|
+
if (!made.size) continue;
|
|
8651
|
+
const accesses = /* @__PURE__ */ new Map();
|
|
8652
|
+
const escape = (v) => made.delete(v);
|
|
8653
|
+
for (const b of fn.blocks) {
|
|
8654
|
+
for (const phi of b.phis) for (const inc of phi.incoming) escape(inc.value);
|
|
8655
|
+
for (const i of b.instrs) i.operands.forEach((o, k) => {
|
|
8656
|
+
if (!made.has(o)) return;
|
|
8657
|
+
if ((i.op === "get_elem" || i.op === "set_elem") && k === 0 && i.operands[1] !== o && i.operands[2] !== o) accesses.set(o, [...accesses.get(o) ?? [], i]);
|
|
8658
|
+
else escape(o);
|
|
8659
|
+
});
|
|
8660
|
+
const t = b.term;
|
|
8661
|
+
if (t?.kind === "branch") escape(t.cond);
|
|
8662
|
+
if (t?.kind === "return" || t?.kind === "throw") escape(t.value);
|
|
8663
|
+
}
|
|
8664
|
+
for (const [array, kind] of made) {
|
|
8665
|
+
array.taView = kind;
|
|
8666
|
+
for (const a of accesses.get(array) ?? []) a.taView = kind;
|
|
8667
|
+
count("viewed", "typed array");
|
|
8668
|
+
}
|
|
8669
|
+
}
|
|
8670
|
+
return m;
|
|
8671
|
+
}
|
|
8672
|
+
};
|
|
8673
|
+
//#endregion
|
|
7905
8674
|
//#region optimizer/src/elide-arguments.ts
|
|
7906
8675
|
const elideArguments = {
|
|
7907
8676
|
name: "elide-arguments",
|
|
@@ -7937,6 +8706,285 @@ function elide(fn) {
|
|
|
7937
8706
|
return candidates.size;
|
|
7938
8707
|
}
|
|
7939
8708
|
//#endregion
|
|
8709
|
+
//#region optimizer/src/specialize.ts
|
|
8710
|
+
const NUMBER_FORM = {
|
|
8711
|
+
add: "num_add",
|
|
8712
|
+
sub: "num_sub",
|
|
8713
|
+
mul: "num_mul",
|
|
8714
|
+
div: "num_div",
|
|
8715
|
+
mod: "num_mod",
|
|
8716
|
+
neg: "num_neg",
|
|
8717
|
+
inc: "num_inc",
|
|
8718
|
+
dec: "num_dec",
|
|
8719
|
+
lt: "num_lt",
|
|
8720
|
+
le: "num_le",
|
|
8721
|
+
gt: "num_gt",
|
|
8722
|
+
ge: "num_ge",
|
|
8723
|
+
eq: "num_eq",
|
|
8724
|
+
ne: "num_ne",
|
|
8725
|
+
strict_eq: "num_eq",
|
|
8726
|
+
strict_ne: "num_ne",
|
|
8727
|
+
bitand: "num_bitand",
|
|
8728
|
+
bitor: "num_bitor",
|
|
8729
|
+
bitxor: "num_bitxor",
|
|
8730
|
+
bitnot: "num_bitnot",
|
|
8731
|
+
shl: "num_shl",
|
|
8732
|
+
sar: "num_sar",
|
|
8733
|
+
shr: "num_shr"
|
|
8734
|
+
};
|
|
8735
|
+
/** The generic operators with a Number path (the rest of NUMBER_FORM's, and ToNumeric). */
|
|
8736
|
+
const NUMBER_FIRST = /* @__PURE__ */ new Set([
|
|
8737
|
+
"add",
|
|
8738
|
+
"sub",
|
|
8739
|
+
"mul",
|
|
8740
|
+
"div",
|
|
8741
|
+
"mod",
|
|
8742
|
+
"neg",
|
|
8743
|
+
"inc",
|
|
8744
|
+
"dec",
|
|
8745
|
+
"lt",
|
|
8746
|
+
"le",
|
|
8747
|
+
"gt",
|
|
8748
|
+
"ge",
|
|
8749
|
+
"eq",
|
|
8750
|
+
"ne",
|
|
8751
|
+
"bitand",
|
|
8752
|
+
"bitor",
|
|
8753
|
+
"bitxor",
|
|
8754
|
+
"bitnot",
|
|
8755
|
+
"shl",
|
|
8756
|
+
"sar",
|
|
8757
|
+
"shr",
|
|
8758
|
+
"to_numeric"
|
|
8759
|
+
]);
|
|
8760
|
+
/** Math functions of one Number whose C form generated code may call inline. */
|
|
8761
|
+
const MATH_INLINE = /* @__PURE__ */ new Set([
|
|
8762
|
+
"sqrt",
|
|
8763
|
+
"abs",
|
|
8764
|
+
"floor",
|
|
8765
|
+
"ceil",
|
|
8766
|
+
"trunc"
|
|
8767
|
+
]);
|
|
8768
|
+
/** The built-in a call may run inline, if any: `Math.sqrt(x)` …, `s.charCodeAt(i)`,
|
|
8769
|
+
* `String.fromCharCode(c)` with one argument, `f.apply(…)` and `f.call(…)` (named by its C
|
|
8770
|
+
* helper's suffix). */
|
|
8771
|
+
function inlineBuiltin(i) {
|
|
8772
|
+
const callee = i.op === "call" ? i.operands[0] : null;
|
|
8773
|
+
if (callee?.op !== "get_prop" || !("name" in callee.key)) return null;
|
|
8774
|
+
const name = callee.key.name;
|
|
8775
|
+
if ((name === "apply" && i.operands.length <= 4 || name === "call") && i.operands[1] === callee.operands[0]) return name;
|
|
8776
|
+
if (i.operands.length !== 3) return null;
|
|
8777
|
+
const global = (g) => callee.operands[0].op === "global_name" && callee.operands[0].target === g;
|
|
8778
|
+
if (MATH_INLINE.has(name) && global("Math")) return `math_${name}`;
|
|
8779
|
+
if (name === "fromCharCode" && global("String")) return "from_char_code";
|
|
8780
|
+
if (name === "charCodeAt" && i.operands[1] === callee.operands[0]) return "char_code_at";
|
|
8781
|
+
return null;
|
|
8782
|
+
}
|
|
8783
|
+
/** What typed code ("use static", which trusts the built-ins to be the standard ones) makes of a
|
|
8784
|
+
* built-in call: s.charCodeAt(i) of a string, or Math's sqrt/abs/floor/ceil/trunc of a Number,
|
|
8785
|
+
* the C helper's suffix; null for anything else. `type`: the values' types. */
|
|
8786
|
+
function trustedBuiltin(i, type) {
|
|
8787
|
+
if (!isStatic(i.block.fn)) return null;
|
|
8788
|
+
const name = inlineBuiltin(i);
|
|
8789
|
+
if (name === "char_code_at") return isSubtype(type(i.operands[1]), T.String) ? name : null;
|
|
8790
|
+
if (name?.startsWith("math_")) return isSubtype(type(i.operands[2]), Number_) ? name : null;
|
|
8791
|
+
return null;
|
|
8792
|
+
}
|
|
8793
|
+
/** The bitwise operators: ToInt32 (ToUint32) of their operands first. */
|
|
8794
|
+
const TRUNCATING = /* @__PURE__ */ new Set([
|
|
8795
|
+
"num_bitor",
|
|
8796
|
+
"num_bitand",
|
|
8797
|
+
"num_bitxor",
|
|
8798
|
+
"num_shl",
|
|
8799
|
+
"num_sar",
|
|
8800
|
+
"num_shr"
|
|
8801
|
+
]);
|
|
8802
|
+
/** Wrapping int32 arithmetic (`(a + b) | 0` of int32s), by the Number operator. */
|
|
8803
|
+
const INT_FORM = {
|
|
8804
|
+
num_add: "int_add",
|
|
8805
|
+
num_sub: "int_sub",
|
|
8806
|
+
num_mul: "int_mul",
|
|
8807
|
+
num_inc: "int_add",
|
|
8808
|
+
num_dec: "int_sub"
|
|
8809
|
+
};
|
|
8810
|
+
/** Whether the Number operator `x` of int32s gives an exact double, whose low 32 bits are then
|
|
8811
|
+
* the wrapping int32 operation's: a sum always; a product only when it stays within 2^53, as
|
|
8812
|
+
* against a constant of at most 2^22 (`h * 16777619 >>> 0`, FNV's prime, rounds). */
|
|
8813
|
+
function exactInDouble(x) {
|
|
8814
|
+
if (x.op !== "num_mul") return true;
|
|
8815
|
+
return x.operands.some((o) => o.op === "const" && Math.abs(o.lit.value) <= 2 ** 22);
|
|
8816
|
+
}
|
|
8817
|
+
/** A constant 1 before `i`, for x++ as x + 1. */
|
|
8818
|
+
function one(i) {
|
|
8819
|
+
const b = i.block;
|
|
8820
|
+
const c = {
|
|
8821
|
+
id: nextId(b.fn),
|
|
8822
|
+
op: "const",
|
|
8823
|
+
operands: [],
|
|
8824
|
+
type: T.Int32,
|
|
8825
|
+
repr: "boxed",
|
|
8826
|
+
mayThrow: false,
|
|
8827
|
+
block: b,
|
|
8828
|
+
loc: i.loc,
|
|
8829
|
+
lit: { value: 1 }
|
|
8830
|
+
};
|
|
8831
|
+
b.instrs.splice(b.instrs.indexOf(i), 0, c);
|
|
8832
|
+
return c;
|
|
8833
|
+
}
|
|
8834
|
+
function nextId(fn) {
|
|
8835
|
+
return 1 + Math.max(0, ...fn.params.map((p) => p.id), ...fn.blocks.flatMap((b) => [...b.phis, ...b.instrs].map((i) => i.id)));
|
|
8836
|
+
}
|
|
8837
|
+
/** Kinds of value whose === compares contents, not bits: two Numbers (1 and 1.0 boxed
|
|
8838
|
+
* differently, NaN), two strings (equal units in two cells), two BigInts. */
|
|
8839
|
+
const BY_CONTENT = Number_ | T.String | T.BigInt;
|
|
8840
|
+
const NONE = Symbol("no literal");
|
|
8841
|
+
/** A comparison js_strict_equals (js_eq) needn't make: `typeof x === "function"`, `x == null`,
|
|
8842
|
+
* `x === "literal"`, and === where the operands can't both be Numbers, strings or BigInts
|
|
8843
|
+
* (`x === undefined`, objects): a C bool test of boxed values. Rewrites `i`; false: kept. */
|
|
8844
|
+
function simplerComparison(i) {
|
|
8845
|
+
const strict = i.op === "strict_eq" || i.op === "strict_ne";
|
|
8846
|
+
if (!strict && i.op !== "eq" && i.op !== "ne") return false;
|
|
8847
|
+
const negated = i.op === "strict_ne" || i.op === "ne" ? 1 : 0;
|
|
8848
|
+
const literal = (v) => v.op === "const" ? v.lit.value : NONE;
|
|
8849
|
+
for (const [x, y] of [[i.operands[0], i.operands[1]], [i.operands[1], i.operands[0]]]) {
|
|
8850
|
+
const lit = literal(y);
|
|
8851
|
+
if (x.op === "typeof" && typeof lit === "string") {
|
|
8852
|
+
Object.assign(i, {
|
|
8853
|
+
op: "is_type",
|
|
8854
|
+
operands: [x.operands[0]],
|
|
8855
|
+
target: lit,
|
|
8856
|
+
index: negated
|
|
8857
|
+
});
|
|
8858
|
+
return true;
|
|
8859
|
+
}
|
|
8860
|
+
if (!strict && (lit === null || lit === void 0)) {
|
|
8861
|
+
Object.assign(i, {
|
|
8862
|
+
op: "is_type",
|
|
8863
|
+
operands: [x],
|
|
8864
|
+
target: "nullish",
|
|
8865
|
+
index: negated
|
|
8866
|
+
});
|
|
8867
|
+
return true;
|
|
8868
|
+
}
|
|
8869
|
+
if (strict && typeof lit === "string") {
|
|
8870
|
+
Object.assign(i, {
|
|
8871
|
+
op: "strict_eq_atom",
|
|
8872
|
+
operands: [x, y],
|
|
8873
|
+
index: negated
|
|
8874
|
+
});
|
|
8875
|
+
return true;
|
|
8876
|
+
}
|
|
8877
|
+
if (strict && typeof lit === "number" && !Number.isNaN(lit) && !(x.op === "const" && typeof x.lit.value === "number")) {
|
|
8878
|
+
Object.assign(i, {
|
|
8879
|
+
op: "strict_eq_num",
|
|
8880
|
+
operands: [x, y],
|
|
8881
|
+
index: negated
|
|
8882
|
+
});
|
|
8883
|
+
return true;
|
|
8884
|
+
}
|
|
8885
|
+
}
|
|
8886
|
+
if (strict && (i.operands[0].type & i.operands[1].type & BY_CONTENT) === 0) {
|
|
8887
|
+
Object.assign(i, {
|
|
8888
|
+
op: "identical",
|
|
8889
|
+
index: negated
|
|
8890
|
+
});
|
|
8891
|
+
return true;
|
|
8892
|
+
}
|
|
8893
|
+
return false;
|
|
8894
|
+
}
|
|
8895
|
+
const INDEX_FORM = {
|
|
8896
|
+
get_elem: "get_index",
|
|
8897
|
+
set_elem: "set_index"
|
|
8898
|
+
};
|
|
8899
|
+
const specialize = {
|
|
8900
|
+
name: "specialize",
|
|
8901
|
+
run(m, count) {
|
|
8902
|
+
for (const fn of functionsOf(m)) {
|
|
8903
|
+
let edges = false;
|
|
8904
|
+
const depth = loopDepths(fn);
|
|
8905
|
+
const uses = useCounts(fn);
|
|
8906
|
+
const unread = /* @__PURE__ */ new Set();
|
|
8907
|
+
for (const b of fn.blocks) for (const i of b.instrs) {
|
|
8908
|
+
const trusted = trustedBuiltin(i, (v) => v.type);
|
|
8909
|
+
if (trusted) {
|
|
8910
|
+
edges ||= i.mayThrow;
|
|
8911
|
+
clearMayThrow(i);
|
|
8912
|
+
const method = i.operands[0];
|
|
8913
|
+
if (method.mayThrow) edges = true;
|
|
8914
|
+
if (uses.get(method) === 1) {
|
|
8915
|
+
unread.add(method);
|
|
8916
|
+
const holder = method.operands[0];
|
|
8917
|
+
if (trusted !== "char_code_at" && holder.op === "global_name" && uses.get(holder) === 2) {
|
|
8918
|
+
unread.add(holder);
|
|
8919
|
+
edges = true;
|
|
8920
|
+
}
|
|
8921
|
+
}
|
|
8922
|
+
if (trusted === "char_code_at") {
|
|
8923
|
+
i.op = "char_code_at";
|
|
8924
|
+
i.operands = [i.operands[1], i.operands[2]];
|
|
8925
|
+
} else {
|
|
8926
|
+
i.op = "math";
|
|
8927
|
+
i.target = trusted.slice(5);
|
|
8928
|
+
i.operands = [i.operands[2]];
|
|
8929
|
+
}
|
|
8930
|
+
count("inlined", "built-in call");
|
|
8931
|
+
continue;
|
|
8932
|
+
}
|
|
8933
|
+
if (isStringLength(i) && i.mayThrow) {
|
|
8934
|
+
edges = true;
|
|
8935
|
+
clearMayThrow(i);
|
|
8936
|
+
count("inlined", "string length");
|
|
8937
|
+
continue;
|
|
8938
|
+
}
|
|
8939
|
+
const builtin = inlineBuiltin(i);
|
|
8940
|
+
if (builtin) {
|
|
8941
|
+
i.builtin = builtin;
|
|
8942
|
+
count("inlined", "built-in call");
|
|
8943
|
+
continue;
|
|
8944
|
+
}
|
|
8945
|
+
const index = INDEX_FORM[i.op];
|
|
8946
|
+
if (index && isSubtype(i.operands[1].type, Number_)) {
|
|
8947
|
+
i.op = index;
|
|
8948
|
+
count("inlined", "element access", 1, "element accesses");
|
|
8949
|
+
continue;
|
|
8950
|
+
}
|
|
8951
|
+
const form = NUMBER_FORM[i.op];
|
|
8952
|
+
const numbers = i.operands.every((o) => isSubtype(o.type, Number_) && o.type !== T.None);
|
|
8953
|
+
if (!numbers && simplerComparison(i)) {
|
|
8954
|
+
edges ||= i.mayThrow;
|
|
8955
|
+
clearMayThrow(i);
|
|
8956
|
+
count("simplified", "comparison");
|
|
8957
|
+
continue;
|
|
8958
|
+
}
|
|
8959
|
+
if (!form || !numbers) {
|
|
8960
|
+
if (NUMBER_FIRST.has(i.op) && depth.get(b) > 0 && i.operands.every((o) => o.type & Number_)) {
|
|
8961
|
+
i.numberFirst = true;
|
|
8962
|
+
count("added", "number fast path");
|
|
8963
|
+
}
|
|
8964
|
+
continue;
|
|
8965
|
+
}
|
|
8966
|
+
edges ||= i.mayThrow;
|
|
8967
|
+
clearMayThrow(i);
|
|
8968
|
+
i.op = form;
|
|
8969
|
+
count("inlined", "number operator");
|
|
8970
|
+
if (TRUNCATING.has(form)) {
|
|
8971
|
+
for (const x of i.operands) if (INT_FORM[x.op] && uses.get(x) === 1 && x.operands.every((o) => isSubtype(o.type, T.Int32)) && exactInDouble(x)) {
|
|
8972
|
+
if (x.op === "num_inc" || x.op === "num_dec") x.operands = [x.operands[0], one(x)];
|
|
8973
|
+
x.op = INT_FORM[x.op];
|
|
8974
|
+
count("wrapped", "int operation");
|
|
8975
|
+
}
|
|
8976
|
+
}
|
|
8977
|
+
}
|
|
8978
|
+
if (unread.size) {
|
|
8979
|
+
for (const v of unread) clearMayThrow(v);
|
|
8980
|
+
removeInstrs(fn, unread);
|
|
8981
|
+
}
|
|
8982
|
+
if (edges) removeUnreachable(fn);
|
|
8983
|
+
}
|
|
8984
|
+
return m;
|
|
8985
|
+
}
|
|
8986
|
+
};
|
|
8987
|
+
//#endregion
|
|
7940
8988
|
//#region optimizer/src/infer-types.ts
|
|
7941
8989
|
const inferTypes = {
|
|
7942
8990
|
name: "infer-types",
|
|
@@ -7965,7 +9013,8 @@ function inferModule(m) {
|
|
|
7965
9013
|
};
|
|
7966
9014
|
for (const fn of m.functions) {
|
|
7967
9015
|
if (!pauses(fn)) sig.returns.set(fn.name, T.None);
|
|
7968
|
-
|
|
9016
|
+
const declared = fn.meta?.declared;
|
|
9017
|
+
fn.params.forEach((p, k) => sig.params.set(p, declared?.[k] ?? (closed.has(fn) ? T.None : Unknown & ~T.Empty)));
|
|
7969
9018
|
}
|
|
7970
9019
|
const byName = new Map(m.functions.map((fn) => [fn.name, fn]));
|
|
7971
9020
|
const callerMap = callers(m);
|
|
@@ -7984,7 +9033,7 @@ function inferModule(m) {
|
|
|
7984
9033
|
}
|
|
7985
9034
|
for (const b of fn.blocks) for (const i of b.instrs) {
|
|
7986
9035
|
const callee = i.op === "call_direct" ? byName.get(i.target) : void 0;
|
|
7987
|
-
if (!callee || !closed.has(callee)) continue;
|
|
9036
|
+
if (!callee || !closed.has(callee) || callee.meta?.declared) continue;
|
|
7988
9037
|
callee.params.forEach((p, k) => {
|
|
7989
9038
|
const old = sig.params.get(p);
|
|
7990
9039
|
const next = old | i.operands[k].type & ~T.Empty;
|
|
@@ -8044,7 +9093,11 @@ const BOOLEAN = /* @__PURE__ */ new Set([
|
|
|
8044
9093
|
"delete_prop",
|
|
8045
9094
|
"delete_elem",
|
|
8046
9095
|
"private_in",
|
|
8047
|
-
"same_function"
|
|
9096
|
+
"same_function",
|
|
9097
|
+
"identical",
|
|
9098
|
+
"strict_eq_atom",
|
|
9099
|
+
"is_type",
|
|
9100
|
+
"strict_eq_num"
|
|
8048
9101
|
]);
|
|
8049
9102
|
const OBJECT = /* @__PURE__ */ new Set([
|
|
8050
9103
|
"new_object",
|
|
@@ -8096,9 +9149,8 @@ function bounds(fn, type) {
|
|
|
8096
9149
|
while ((v.op === "to_numeric" || v.op === "to_number" || v.op === "check_tdz") && isSubtype(type(v), Number_)) v = v.operands[0];
|
|
8097
9150
|
return v;
|
|
8098
9151
|
};
|
|
8099
|
-
const
|
|
9152
|
+
const proves = (x, at, holds) => {
|
|
8100
9153
|
idom ??= dominators(fn);
|
|
8101
|
-
if (!int32(x)) return false;
|
|
8102
9154
|
for (let a = at;;) {
|
|
8103
9155
|
const p = a.preds.length === 1 ? a.preds[0] : null;
|
|
8104
9156
|
const term = p?.term;
|
|
@@ -8108,12 +9160,7 @@ function bounds(fn, type) {
|
|
|
8108
9160
|
if (rel && (l === x || r === x)) {
|
|
8109
9161
|
if (term.else === a) rel = NEGATED[rel];
|
|
8110
9162
|
if (r === x) rel = FLIPPED[rel];
|
|
8111
|
-
|
|
8112
|
-
const lit = other.op === "const" && typeof other.lit.value === "number" ? other.lit.value : null;
|
|
8113
|
-
if (int32(other)) {
|
|
8114
|
-
if (dir === "up" && (rel === "<" || rel === "<=" && lit !== null && lit < 2147483647)) return true;
|
|
8115
|
-
if (dir === "down" && (rel === ">" || rel === ">=" && lit !== null && lit > -2147483648)) return true;
|
|
8116
|
-
}
|
|
9163
|
+
if (holds(rel, l === x ? r : l)) return true;
|
|
8117
9164
|
}
|
|
8118
9165
|
}
|
|
8119
9166
|
const up = idom.get(a);
|
|
@@ -8121,7 +9168,30 @@ function bounds(fn, type) {
|
|
|
8121
9168
|
a = up;
|
|
8122
9169
|
}
|
|
8123
9170
|
};
|
|
8124
|
-
|
|
9171
|
+
const bound = (x, at, dir) => int32(x) && proves(x, at, (rel, other) => {
|
|
9172
|
+
const lit = other.op === "const" && typeof other.lit.value === "number" ? other.lit.value : null;
|
|
9173
|
+
if (!int32(other)) return false;
|
|
9174
|
+
if (dir === "up") return rel === "<" || rel === "<=" && lit !== null && lit < 2147483647;
|
|
9175
|
+
return rel === ">" || rel === ">=" && lit !== null && lit > -2147483648;
|
|
9176
|
+
});
|
|
9177
|
+
const nonNegative = (x, seen = /* @__PURE__ */ new Set()) => {
|
|
9178
|
+
x = root(x);
|
|
9179
|
+
if (seen.has(x)) return true;
|
|
9180
|
+
if (x.op === "const") return typeof x.lit.value === "number" && x.lit.value >= 0;
|
|
9181
|
+
if (x.op === "phi") return seen.add(x) && x.incoming.every((inc) => nonNegative(inc.value, seen));
|
|
9182
|
+
if (x.op === "inc" || x.op === "num_inc") return nonNegative(x.operands[0], seen);
|
|
9183
|
+
return false;
|
|
9184
|
+
};
|
|
9185
|
+
const lengthOf = (v) => {
|
|
9186
|
+
v = root(v);
|
|
9187
|
+
const s = v.operands[0];
|
|
9188
|
+
return v.op === "get_prop" && v.key && "name" in v.key && v.key.name === "length" && s && isSubtype(type(s) & ~T.Empty, T.String) && type(s) !== T.None ? root(s) : void 0;
|
|
9189
|
+
};
|
|
9190
|
+
const inString = (s, x, at) => int32(x) && nonNegative(x) && proves(root(x), at, (rel, other) => rel === "<" && lengthOf(other) === root(s));
|
|
9191
|
+
return Object.assign(bound, {
|
|
9192
|
+
root,
|
|
9193
|
+
inString
|
|
9194
|
+
});
|
|
8125
9195
|
}
|
|
8126
9196
|
/** x + 1 or x - 1 (`i++`, `i += 1`, `i--`) of an int32 x the branches bound: an int32. */
|
|
8127
9197
|
function step(i, bound) {
|
|
@@ -8157,7 +9227,7 @@ function transfer(i, type, sig, bound = null) {
|
|
|
8157
9227
|
case "bigint": return T.BigInt;
|
|
8158
9228
|
case "switch_string": return T.Int32;
|
|
8159
9229
|
case "to_number": return Number_;
|
|
8160
|
-
case "to_numeric": return numeric(t(a));
|
|
9230
|
+
case "to_numeric": return isSubtype(t(a), Number_) && t(a) !== T.None ? t(a) : numeric(t(a));
|
|
8161
9231
|
case "add": {
|
|
8162
9232
|
const ta = t(a), tb = t(b);
|
|
8163
9233
|
if (ta === T.String || tb === T.String) return T.String;
|
|
@@ -8201,6 +9271,27 @@ function transfer(i, type, sig, bound = null) {
|
|
|
8201
9271
|
case "num_ne": return T.Boolean;
|
|
8202
9272
|
case "get_prop": return i.key && "name" in i.key && i.key.name === "length" && isSubtype(t(a), T.String) ? T.Int32 : Unknown;
|
|
8203
9273
|
case "call_direct": return sig?.returns.get(i.target) ?? Unknown;
|
|
9274
|
+
case "get_elem":
|
|
9275
|
+
case "get_index":
|
|
9276
|
+
if (readsDouble(i)) return T.Int32 | T.Double;
|
|
9277
|
+
if (i.taView && isStatic(i.block.fn)) return T.Int32 | T.Undefined;
|
|
9278
|
+
return Unknown;
|
|
9279
|
+
case "char_code_at":
|
|
9280
|
+
case "math": return Number_;
|
|
9281
|
+
case "int_add":
|
|
9282
|
+
case "int_sub":
|
|
9283
|
+
case "int_mul": return T.Int32;
|
|
9284
|
+
case "to_i64":
|
|
9285
|
+
case "i64":
|
|
9286
|
+
case "int_div":
|
|
9287
|
+
case "int_mod": return T.Int64;
|
|
9288
|
+
case "to_i32": return T.Int32;
|
|
9289
|
+
case "call": {
|
|
9290
|
+
const trusted = trustedBuiltin(i, t);
|
|
9291
|
+
if (trusted === "char_code_at" && bound?.inString(b, i.operands[2], i.block)) return T.Int32;
|
|
9292
|
+
if (trusted) return Number_;
|
|
9293
|
+
return i.typedCall ? i.typedCall.returns : Unknown;
|
|
9294
|
+
}
|
|
8204
9295
|
default: return Unknown;
|
|
8205
9296
|
}
|
|
8206
9297
|
}
|
|
@@ -8230,8 +9321,9 @@ function inferFunction(fn, sig = null) {
|
|
|
8230
9321
|
}
|
|
8231
9322
|
//#endregion
|
|
8232
9323
|
//#region optimizer/src/inline.ts
|
|
8233
|
-
/** A callee's largest size, in instructions.
|
|
8234
|
-
|
|
9324
|
+
/** A callee's largest size, in instructions. 40 made a large program's C 10% bigger than 20 did
|
|
9325
|
+
* (each copy is C to compile) and ran no faster. */
|
|
9326
|
+
const INLINE_SIZE = 20;
|
|
8235
9327
|
/** A caller's largest size it grows to, in instructions. */
|
|
8236
9328
|
const CALLER_SIZE = 3e3;
|
|
8237
9329
|
const ROUNDS = 2;
|
|
@@ -8776,123 +9868,18 @@ function foldBranch(b) {
|
|
|
8776
9868
|
return true;
|
|
8777
9869
|
}
|
|
8778
9870
|
//#endregion
|
|
8779
|
-
//#region optimizer/src/specialize.ts
|
|
8780
|
-
/** Each generic operator's Number form. */
|
|
8781
|
-
const NUMBER_FORM = {
|
|
8782
|
-
add: "num_add",
|
|
8783
|
-
sub: "num_sub",
|
|
8784
|
-
mul: "num_mul",
|
|
8785
|
-
div: "num_div",
|
|
8786
|
-
mod: "num_mod",
|
|
8787
|
-
neg: "num_neg",
|
|
8788
|
-
inc: "num_inc",
|
|
8789
|
-
dec: "num_dec",
|
|
8790
|
-
lt: "num_lt",
|
|
8791
|
-
le: "num_le",
|
|
8792
|
-
gt: "num_gt",
|
|
8793
|
-
ge: "num_ge",
|
|
8794
|
-
eq: "num_eq",
|
|
8795
|
-
ne: "num_ne",
|
|
8796
|
-
strict_eq: "num_eq",
|
|
8797
|
-
strict_ne: "num_ne",
|
|
8798
|
-
bitand: "num_bitand",
|
|
8799
|
-
bitor: "num_bitor",
|
|
8800
|
-
bitxor: "num_bitxor",
|
|
8801
|
-
bitnot: "num_bitnot",
|
|
8802
|
-
shl: "num_shl",
|
|
8803
|
-
sar: "num_sar",
|
|
8804
|
-
shr: "num_shr"
|
|
8805
|
-
};
|
|
8806
|
-
/** The generic operators with a Number path (the rest of NUMBER_FORM's, and ToNumeric). */
|
|
8807
|
-
const NUMBER_FIRST = /* @__PURE__ */ new Set([
|
|
8808
|
-
"add",
|
|
8809
|
-
"sub",
|
|
8810
|
-
"mul",
|
|
8811
|
-
"div",
|
|
8812
|
-
"mod",
|
|
8813
|
-
"neg",
|
|
8814
|
-
"inc",
|
|
8815
|
-
"dec",
|
|
8816
|
-
"lt",
|
|
8817
|
-
"le",
|
|
8818
|
-
"gt",
|
|
8819
|
-
"ge",
|
|
8820
|
-
"eq",
|
|
8821
|
-
"ne",
|
|
8822
|
-
"bitand",
|
|
8823
|
-
"bitor",
|
|
8824
|
-
"bitxor",
|
|
8825
|
-
"bitnot",
|
|
8826
|
-
"shl",
|
|
8827
|
-
"sar",
|
|
8828
|
-
"shr",
|
|
8829
|
-
"to_numeric"
|
|
8830
|
-
]);
|
|
8831
|
-
/** Math functions of one Number whose C form generated code may call inline. */
|
|
8832
|
-
const MATH_INLINE = /* @__PURE__ */ new Set([
|
|
8833
|
-
"sqrt",
|
|
8834
|
-
"abs",
|
|
8835
|
-
"floor",
|
|
8836
|
-
"ceil",
|
|
8837
|
-
"trunc"
|
|
8838
|
-
]);
|
|
8839
|
-
/** The built-in a one-argument call may run inline, if any: `Math.sqrt(x)` …,
|
|
8840
|
-
* `s.charCodeAt(i)`, `String.fromCharCode(c)` (named by its C helper's suffix). */
|
|
8841
|
-
function inlineBuiltin(i) {
|
|
8842
|
-
const callee = i.op === "call" && i.operands.length === 3 ? i.operands[0] : null;
|
|
8843
|
-
if (callee?.op !== "get_prop" || !("name" in callee.key)) return null;
|
|
8844
|
-
const name = callee.key.name;
|
|
8845
|
-
const global = (g) => callee.operands[0].op === "global_name" && callee.operands[0].target === g;
|
|
8846
|
-
if (MATH_INLINE.has(name) && global("Math")) return `math_${name}`;
|
|
8847
|
-
if (name === "fromCharCode" && global("String")) return "from_char_code";
|
|
8848
|
-
if (name === "charCodeAt" && i.operands[1] === callee.operands[0]) return "char_code_at";
|
|
8849
|
-
return null;
|
|
8850
|
-
}
|
|
8851
|
-
/** Element access's form for a Number key. */
|
|
8852
|
-
const INDEX_FORM = {
|
|
8853
|
-
get_elem: "get_index",
|
|
8854
|
-
set_elem: "set_index"
|
|
8855
|
-
};
|
|
8856
|
-
const specialize = {
|
|
8857
|
-
name: "specialize",
|
|
8858
|
-
run(m, count) {
|
|
8859
|
-
for (const fn of functionsOf(m)) {
|
|
8860
|
-
let edges = false;
|
|
8861
|
-
const depth = loopDepths(fn);
|
|
8862
|
-
for (const b of fn.blocks) for (const i of b.instrs) {
|
|
8863
|
-
const builtin = inlineBuiltin(i);
|
|
8864
|
-
if (builtin) {
|
|
8865
|
-
i.builtin = builtin;
|
|
8866
|
-
count("inlined", "built-in call");
|
|
8867
|
-
continue;
|
|
8868
|
-
}
|
|
8869
|
-
const index = INDEX_FORM[i.op];
|
|
8870
|
-
if (index && isSubtype(i.operands[1].type, Number_)) {
|
|
8871
|
-
i.op = index;
|
|
8872
|
-
count("inlined", "element access", 1, "element accesses");
|
|
8873
|
-
continue;
|
|
8874
|
-
}
|
|
8875
|
-
const form = NUMBER_FORM[i.op];
|
|
8876
|
-
if (!form || !i.operands.every((o) => isSubtype(o.type, Number_))) {
|
|
8877
|
-
if (NUMBER_FIRST.has(i.op) && depth.get(b) > 0 && i.operands.every((o) => o.type & Number_)) {
|
|
8878
|
-
i.numberFirst = true;
|
|
8879
|
-
count("added", "number fast path");
|
|
8880
|
-
}
|
|
8881
|
-
continue;
|
|
8882
|
-
}
|
|
8883
|
-
edges ||= i.mayThrow;
|
|
8884
|
-
clearMayThrow(i);
|
|
8885
|
-
i.op = form;
|
|
8886
|
-
count("inlined", "number operator");
|
|
8887
|
-
}
|
|
8888
|
-
if (edges) removeUnreachable(fn);
|
|
8889
|
-
}
|
|
8890
|
-
return m;
|
|
8891
|
-
}
|
|
8892
|
-
};
|
|
8893
|
-
//#endregion
|
|
8894
9871
|
//#region optimizer/src/unbox.ts
|
|
8895
9872
|
const F64 = /* @__PURE__ */ new Set([
|
|
9873
|
+
"char_code_at",
|
|
9874
|
+
"math",
|
|
9875
|
+
"int_add",
|
|
9876
|
+
"int_sub",
|
|
9877
|
+
"int_mul",
|
|
9878
|
+
"to_i64",
|
|
9879
|
+
"to_i32",
|
|
9880
|
+
"int_div",
|
|
9881
|
+
"int_mod",
|
|
9882
|
+
"i64",
|
|
8896
9883
|
"num_add",
|
|
8897
9884
|
"num_sub",
|
|
8898
9885
|
"num_mul",
|
|
@@ -8917,11 +9904,21 @@ const BOOL = /* @__PURE__ */ new Set([
|
|
|
8917
9904
|
"num_eq",
|
|
8918
9905
|
"num_ne"
|
|
8919
9906
|
]);
|
|
9907
|
+
/** Boolean tests of boxed operands: their result is a C bool, their operands stay boxed. */
|
|
9908
|
+
const TESTS = /* @__PURE__ */ new Set([
|
|
9909
|
+
"same_function",
|
|
9910
|
+
"identical",
|
|
9911
|
+
"strict_eq_atom",
|
|
9912
|
+
"is_type",
|
|
9913
|
+
"strict_eq_num"
|
|
9914
|
+
]);
|
|
9915
|
+
const isBool = (t) => t !== T.None && isSubtype(t, T.Boolean);
|
|
8920
9916
|
/** How the report names each unboxed repr. */
|
|
8921
9917
|
const REPR_NOUN = {
|
|
8922
9918
|
boxed: "",
|
|
8923
9919
|
f64: "double",
|
|
8924
9920
|
i32: "int32",
|
|
9921
|
+
i64: "int64",
|
|
8925
9922
|
bool: "bool"
|
|
8926
9923
|
};
|
|
8927
9924
|
/** Boxing a double for a use, against unboxing a JSValue (per use, before loop weights). */
|
|
@@ -8942,6 +9939,7 @@ const unbox = {
|
|
|
8942
9939
|
function unboxedRepr(t) {
|
|
8943
9940
|
if (t === T.None) return null;
|
|
8944
9941
|
if (isSubtype(t, T.Int32)) return "i32";
|
|
9942
|
+
if (isSubtype(t, T.Int64 | T.Int32)) return "i64";
|
|
8945
9943
|
if (isSubtype(t, Number_)) return "f64";
|
|
8946
9944
|
if (isSubtype(t, T.Boolean)) return "bool";
|
|
8947
9945
|
return null;
|
|
@@ -8950,7 +9948,7 @@ function unboxModule(m) {
|
|
|
8950
9948
|
const closed = closedFunctions(m);
|
|
8951
9949
|
const byName = new Map(m.functions.map((fn) => [fn.name, fn]));
|
|
8952
9950
|
const fns = functionsOf(m).filter((fn) => !pauses(fn));
|
|
8953
|
-
for (const fn of closed) {
|
|
9951
|
+
for (const fn of m.functions.filter((fn) => closed.has(fn) || fn.meta?.declared && !pauses(fn))) {
|
|
8954
9952
|
const repr = unboxedRepr(fn.blocks.flatMap((b) => b.term?.kind === "return" ? [b.term.value] : []).reduce((u, v) => u | v.type, T.None));
|
|
8955
9953
|
if (repr) fn.returnRepr = repr;
|
|
8956
9954
|
}
|
|
@@ -8988,26 +9986,34 @@ function unboxModule(m) {
|
|
|
8988
9986
|
index: t.kind === "return" ? 1 : 0
|
|
8989
9987
|
});
|
|
8990
9988
|
}
|
|
8991
|
-
const
|
|
9989
|
+
const declared = fn.meta?.declared;
|
|
9990
|
+
const params = closed.has(fn) ? fn.params : declared ? fn.params.filter((_, k) => declared[k] !== void 0) : [];
|
|
8992
9991
|
for (const v of [...params, ...fn.blocks.flatMap((b) => b.phis)]) {
|
|
8993
9992
|
const repr = unboxedRepr(v.type);
|
|
8994
9993
|
if (repr === "bool") v.repr = "bool";
|
|
8995
9994
|
else if (repr) candidates.push(v);
|
|
8996
9995
|
}
|
|
8997
|
-
for (const b of fn.blocks) for (const i of b.instrs) if (F64.has(i.op)) candidates.push(i);
|
|
8998
|
-
else if (BOOL.has(i.op) || i.op === "
|
|
8999
|
-
else if (i.op === "call_direct")
|
|
9996
|
+
for (const b of fn.blocks) for (const i of b.instrs) if (F64.has(i.op) || readsDouble(i) || isStringLength(i)) candidates.push(i);
|
|
9997
|
+
else if (BOOL.has(i.op) || TESTS.has(i.op) || i.op === "not") i.repr = "bool";
|
|
9998
|
+
else if (i.op === "call_direct") {
|
|
9999
|
+
const callee = byName.get(i.target);
|
|
10000
|
+
i.repr = callee && declaredCallFits(i, callee) ? callee.returnRepr ?? "boxed" : "boxed";
|
|
10001
|
+
} else if (typedCallFits(i)) i.repr = i.typedCall.returnRepr === "i64" ? "f64" : i.typedCall.returnRepr;
|
|
9000
10002
|
}
|
|
9001
|
-
for (const c of candidates) c.repr = isSubtype(c.type, T.Int32) ? "i32" : "f64";
|
|
10003
|
+
for (const c of candidates) c.repr = isSubtype(c.type, T.Int32) ? "i32" : isSubtype(c.type, T.Int64 | T.Int32) ? "i64" : "f64";
|
|
9002
10004
|
const wants = (u) => {
|
|
9003
10005
|
if (!u.user) return u.index === 1 ? u.block.fn.returnRepr ?? "boxed" : "boxed";
|
|
9004
10006
|
if (u.user.op === "phi") return u.user.repr;
|
|
9005
|
-
if (
|
|
10007
|
+
if (u.user.op === "not") return isBool(u.user.operands[0].type) ? "bool" : "boxed";
|
|
10008
|
+
if (F64.has(u.user.op) || BOOL.has(u.user.op)) return "f64";
|
|
9006
10009
|
if ((u.user.op === "get_index" || u.user.op === "set_index") && u.index === 1) return "f64";
|
|
10010
|
+
if ((u.user.op === "set_index" || u.user.op === "set_elem") && u.index === 2 && u.user.taView === "f64") return "f64";
|
|
9007
10011
|
if (u.user.op === "call_direct") {
|
|
9008
10012
|
const callee = byName.get(u.user.target);
|
|
9009
|
-
|
|
10013
|
+
if (callee?.meta?.declared && !declaredCallFits(u.user, callee)) return "boxed";
|
|
10014
|
+
return callee && (closed.has(callee) || callee.meta?.declared?.[u.index] !== void 0) ? callee.params[u.index].repr : "boxed";
|
|
9010
10015
|
}
|
|
10016
|
+
if (u.index >= 2 && typedCallFits(u.user)) return u.user.typedCall.params[u.index - 2].repr;
|
|
9011
10017
|
return "boxed";
|
|
9012
10018
|
};
|
|
9013
10019
|
for (let changed = true; changed;) {
|
|
@@ -9032,6 +10038,8 @@ const DEFAULT_PASSES = [
|
|
|
9032
10038
|
promoteGlobals,
|
|
9033
10039
|
inline,
|
|
9034
10040
|
dce,
|
|
10041
|
+
constantKeys,
|
|
10042
|
+
typedArrays,
|
|
9035
10043
|
inferTypes,
|
|
9036
10044
|
simplify,
|
|
9037
10045
|
specialize,
|
|
@@ -10299,6 +11307,10 @@ const primitives = {
|
|
|
10299
11307
|
c: "js_host_timer_unref",
|
|
10300
11308
|
length: 2
|
|
10301
11309
|
},
|
|
11310
|
+
loopNow: {
|
|
11311
|
+
c: "js_host_loop_now",
|
|
11312
|
+
length: 0
|
|
11313
|
+
},
|
|
10302
11314
|
timerStart: {
|
|
10303
11315
|
c: "js_host_timer_start",
|
|
10304
11316
|
length: 4
|
|
@@ -11255,9 +12267,259 @@ function hostPrimitives(hosts) {
|
|
|
11255
12267
|
return Object.assign({}, ...hosts.map((h) => h.primitives));
|
|
11256
12268
|
}
|
|
11257
12269
|
//#endregion
|
|
12270
|
+
//#region cli/src/static/types.ts
|
|
12271
|
+
const TYPES_FILE = resolve(import.meta.dirname, "..", "..", "types", "visaljs.d.ts");
|
|
12272
|
+
/** The integer a type annotation names: "visaljs"'s `int` or `i32`, through imports and aliases. */
|
|
12273
|
+
function intKind(checker, node) {
|
|
12274
|
+
if (!node || !ts.isTypeReferenceNode(node)) return void 0;
|
|
12275
|
+
let symbol = checker.getSymbolAtLocation(node.typeName);
|
|
12276
|
+
if (symbol && symbol.flags & ts.SymbolFlags.Alias) symbol = checker.getAliasedSymbol(symbol);
|
|
12277
|
+
const declaration = symbol?.declarations?.[0];
|
|
12278
|
+
if (!symbol || !declaration || !ts.isTypeAliasDeclaration(declaration)) return void 0;
|
|
12279
|
+
if (resolve(declaration.getSourceFile().fileName) !== TYPES_FILE) return intKind(checker, declaration.type);
|
|
12280
|
+
return Object.hasOwn(STATIC_TYPES, symbol.name) ? STATIC_TYPES[symbol.name].int : void 0;
|
|
12281
|
+
}
|
|
12282
|
+
/** What a parameter of each integer takes: an int any Number (an int64_t, converted by the
|
|
12283
|
+
* caller as C would); an i32 any Number too, converted to it on entry. */
|
|
12284
|
+
const INT_PARAMETERS = {
|
|
12285
|
+
i64: STATIC_TYPES.int.type,
|
|
12286
|
+
i32: STATIC_TYPES.number.type
|
|
12287
|
+
};
|
|
12288
|
+
/** The declared parameter types of a file's top-level functions, by where each starts: each a
|
|
12289
|
+
* type static code takes, or an error. */
|
|
12290
|
+
function declaredSignatures(checker, sf, out) {
|
|
12291
|
+
for (const st of sf.statements) {
|
|
12292
|
+
if (!ts.isFunctionDeclaration(st) || !st.name) continue;
|
|
12293
|
+
const types = st.parameters.map((p) => {
|
|
12294
|
+
const kind = intKind(checker, p.type);
|
|
12295
|
+
if (kind) return INT_PARAMETERS[kind];
|
|
12296
|
+
const type = checker.getTypeAtLocation(p);
|
|
12297
|
+
const t = irType(type);
|
|
12298
|
+
if (t === null) throw compileError(p, `parameter '${p.name.getText(sf)}' of '${st.name.text}': ${checker.typeToString(type)} isn't a static type yet`);
|
|
12299
|
+
return t;
|
|
12300
|
+
});
|
|
12301
|
+
const views = st.parameters.map((p) => viewOf(checker.getTypeAtLocation(p).getSymbol()?.name ?? ""));
|
|
12302
|
+
out.set(locKey(st), {
|
|
12303
|
+
types,
|
|
12304
|
+
views
|
|
12305
|
+
});
|
|
12306
|
+
}
|
|
12307
|
+
}
|
|
12308
|
+
/** tsc's primitive types (and their literals), by the name STATIC_TYPES knows them by. */
|
|
12309
|
+
const PRIMITIVE_FLAGS = [
|
|
12310
|
+
[ts.TypeFlags.Number | ts.TypeFlags.NumberLiteral, "number"],
|
|
12311
|
+
[ts.TypeFlags.String | ts.TypeFlags.StringLiteral, "string"],
|
|
12312
|
+
[ts.TypeFlags.Boolean | ts.TypeFlags.BooleanLiteral, "boolean"],
|
|
12313
|
+
[ts.TypeFlags.Undefined, "undefined"],
|
|
12314
|
+
[ts.TypeFlags.Null, "null"]
|
|
12315
|
+
];
|
|
12316
|
+
/** A TypeScript type as the IR's, if static code takes it. */
|
|
12317
|
+
function irType(t) {
|
|
12318
|
+
if (t.isUnion()) {
|
|
12319
|
+
let u = T.None;
|
|
12320
|
+
for (const part of t.types) {
|
|
12321
|
+
const p = irType(part);
|
|
12322
|
+
if (p === null) return null;
|
|
12323
|
+
u |= p;
|
|
12324
|
+
}
|
|
12325
|
+
return u;
|
|
12326
|
+
}
|
|
12327
|
+
const name = PRIMITIVE_FLAGS.find(([flags]) => t.flags & flags)?.[1];
|
|
12328
|
+
if (name) return STATIC_TYPES[name].type;
|
|
12329
|
+
return viewOf(t.getSymbol()?.name ?? "") ? T.Object : null;
|
|
12330
|
+
}
|
|
12331
|
+
//#endregion
|
|
12332
|
+
//#region cli/src/static/link.ts
|
|
12333
|
+
/** A file's name in locations, as the program's: relative to the working directory. */
|
|
12334
|
+
function fileName(sf) {
|
|
12335
|
+
return normalize(relative(process.cwd(), sf.fileName));
|
|
12336
|
+
}
|
|
12337
|
+
/** A node's position as the adapter writes a location: the key that finds its IR function. */
|
|
12338
|
+
function locKey(node) {
|
|
12339
|
+
const sf = node.getSourceFile();
|
|
12340
|
+
const { line, character } = sf.getLineAndCharacterOfPosition(node.getStart(sf));
|
|
12341
|
+
return `${fileName(sf)}:${line + 1}:${character + 1}`;
|
|
12342
|
+
}
|
|
12343
|
+
function compileError(node, detail) {
|
|
12344
|
+
const sf = node.getSourceFile();
|
|
12345
|
+
const { line, character } = sf.getLineAndCharacterOfPosition(node.getStart(sf));
|
|
12346
|
+
return new CompileError("SyntaxError", detail, {
|
|
12347
|
+
file: fileName(sf),
|
|
12348
|
+
line: line + 1,
|
|
12349
|
+
column: character + 1
|
|
12350
|
+
});
|
|
12351
|
+
}
|
|
12352
|
+
/** The file an import names, when it is one of the unit's. */
|
|
12353
|
+
function importedFile(checker, node, files) {
|
|
12354
|
+
const symbol = node.moduleSpecifier ? checker.getSymbolAtLocation(node.moduleSpecifier) : void 0;
|
|
12355
|
+
const file = symbol?.valueDeclaration && ts.isSourceFile(symbol.valueDeclaration) ? resolve(symbol.valueDeclaration.fileName) : void 0;
|
|
12356
|
+
return file && files.has(file) ? file : void 0;
|
|
12357
|
+
}
|
|
12358
|
+
/** The names a file's top level declares, with their symbols. */
|
|
12359
|
+
function topLevel(checker, sf) {
|
|
12360
|
+
const out = /* @__PURE__ */ new Map();
|
|
12361
|
+
const add = (name) => {
|
|
12362
|
+
const symbol = checker.getSymbolAtLocation(name);
|
|
12363
|
+
if (symbol) out.set(name.text, symbol);
|
|
12364
|
+
};
|
|
12365
|
+
const addBinding = (b) => {
|
|
12366
|
+
if (ts.isIdentifier(b)) add(b);
|
|
12367
|
+
else for (const e of b.elements) if (!ts.isOmittedExpression(e)) addBinding(e.name);
|
|
12368
|
+
};
|
|
12369
|
+
for (const st of sf.statements) if ((ts.isFunctionDeclaration(st) || ts.isClassDeclaration(st)) && st.name) add(st.name);
|
|
12370
|
+
else if (ts.isVariableStatement(st)) for (const d of st.declarationList.declarations) addBinding(d.name);
|
|
12371
|
+
else if (ts.isImportDeclaration(st) && ts.isStringLiteral(st.moduleSpecifier) && st.moduleSpecifier.text === "visaljs:host") {
|
|
12372
|
+
const bindings = st.importClause?.namedBindings;
|
|
12373
|
+
if (bindings && ts.isNamedImports(bindings)) for (const e of bindings.elements) add(e.name);
|
|
12374
|
+
}
|
|
12375
|
+
return out;
|
|
12376
|
+
}
|
|
12377
|
+
/** The files in dependency order (an imported file before its importers); a cycle is an error. */
|
|
12378
|
+
function ordered(checker, sources, files) {
|
|
12379
|
+
const byFile = new Map(sources.map((sf) => [resolve(sf.fileName), sf]));
|
|
12380
|
+
const out = [];
|
|
12381
|
+
const state = /* @__PURE__ */ new Map();
|
|
12382
|
+
const visit = (sf, from) => {
|
|
12383
|
+
if (state.get(sf) === "done") return;
|
|
12384
|
+
if (state.get(sf) === "visiting") throw compileError(from, `static files import each other in a cycle (${sf.fileName}): their top levels would run in no order`);
|
|
12385
|
+
state.set(sf, "visiting");
|
|
12386
|
+
for (const st of sf.statements) if (ts.isImportDeclaration(st) || ts.isExportDeclaration(st)) {
|
|
12387
|
+
const file = importedFile(checker, st, files);
|
|
12388
|
+
if (file) visit(byFile.get(file), st);
|
|
12389
|
+
}
|
|
12390
|
+
state.set(sf, "done");
|
|
12391
|
+
out.push(sf);
|
|
12392
|
+
};
|
|
12393
|
+
for (const sf of sources) visit(sf, void 0);
|
|
12394
|
+
return out;
|
|
12395
|
+
}
|
|
12396
|
+
/** Links the unit's files (checked by tsc, and by the subset). */
|
|
12397
|
+
function linkUnit(program, files, hostPrimitives) {
|
|
12398
|
+
const checker = program.getTypeChecker();
|
|
12399
|
+
const fileSet = new Set(files.map((f) => resolve(f)));
|
|
12400
|
+
const sources = ordered(checker, files.map((f) => program.getSourceFile(f)), fileSet);
|
|
12401
|
+
const renamed = /* @__PURE__ */ new Map();
|
|
12402
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12403
|
+
sources.forEach((sf, k) => {
|
|
12404
|
+
for (const [name, symbol] of topLevel(checker, sf)) {
|
|
12405
|
+
if (seen.has(name)) renamed.set(symbol, `${name}$${k}`);
|
|
12406
|
+
seen.add(name);
|
|
12407
|
+
}
|
|
12408
|
+
});
|
|
12409
|
+
const declarationOf = (id) => {
|
|
12410
|
+
const symbol = ts.isShorthandPropertyAssignment(id.parent) && id.parent.name === id ? checker.getShorthandAssignmentValueSymbol(id.parent) : checker.getSymbolAtLocation(id);
|
|
12411
|
+
if (!symbol) return void 0;
|
|
12412
|
+
if (renamed.has(symbol) || !(symbol.flags & ts.SymbolFlags.Alias)) return symbol;
|
|
12413
|
+
return checker.getAliasedSymbol(symbol);
|
|
12414
|
+
};
|
|
12415
|
+
const unit = {
|
|
12416
|
+
files: sources,
|
|
12417
|
+
rename: (id) => {
|
|
12418
|
+
const symbol = declarationOf(id);
|
|
12419
|
+
return symbol ? renamed.get(symbol) : void 0;
|
|
12420
|
+
},
|
|
12421
|
+
linked: (node) => importedFile(checker, node, fileSet) !== void 0,
|
|
12422
|
+
fileName,
|
|
12423
|
+
intKind: (type) => intKind(checker, type)
|
|
12424
|
+
};
|
|
12425
|
+
const exports = /* @__PURE__ */ new Map();
|
|
12426
|
+
const exportedFrom = /* @__PURE__ */ new Map();
|
|
12427
|
+
const exportFunction = (name, at, declaration) => {
|
|
12428
|
+
if (exportedFrom.has(name)) throw compileError(at, `typed unit: '${name}' is exported by ${exportedFrom.get(name)} and ${at.getSourceFile().fileName}`);
|
|
12429
|
+
if (Object.hasOwn(hostPrimitives, name)) throw compileError(at, `typed unit: export '${name}' is a host primitive's name`);
|
|
12430
|
+
if (!declaration || !ts.isFunctionDeclaration(declaration)) throw compileError(at, `typed unit: export '${name}' is not a function declaration whose parameters are all number, string or boolean`);
|
|
12431
|
+
exportedFrom.set(name, at.getSourceFile().fileName);
|
|
12432
|
+
exports.set(name, locKey(declaration));
|
|
12433
|
+
};
|
|
12434
|
+
for (const sf of sources) for (const { name, at } of exportsOf(sf)) exportFunction(name, at, ts.isExportSpecifier(at) ? checker.getExportSpecifierLocalTargetSymbol(at)?.valueDeclaration : ts.isFunctionDeclaration(at.parent) ? at.parent : void 0);
|
|
12435
|
+
return {
|
|
12436
|
+
ast: convertStaticUnit(unit, hostPrimitives),
|
|
12437
|
+
exports
|
|
12438
|
+
};
|
|
12439
|
+
}
|
|
12440
|
+
/** A static file's exported values, by name: where each is named (a function's name, a
|
|
12441
|
+
* variable's declaration, an export specifier). Read from the syntax alone, for the program's
|
|
12442
|
+
* bundle too (boundary.ts). */
|
|
12443
|
+
function exportsOf(sf) {
|
|
12444
|
+
const out = [];
|
|
12445
|
+
for (const st of sf.statements) {
|
|
12446
|
+
const exported = ts.canHaveModifiers(st) && ts.getModifiers(st)?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
|
|
12447
|
+
if (exported && ts.isFunctionDeclaration(st) && st.name) out.push({
|
|
12448
|
+
name: st.name.text,
|
|
12449
|
+
at: st.name
|
|
12450
|
+
});
|
|
12451
|
+
else if (exported && ts.isVariableStatement(st)) for (const d of st.declarationList.declarations) out.push({
|
|
12452
|
+
name: d.name.getText(sf),
|
|
12453
|
+
at: d
|
|
12454
|
+
});
|
|
12455
|
+
else if (ts.isExportDeclaration(st) && !st.isTypeOnly && !st.moduleSpecifier && st.exportClause && ts.isNamedExports(st.exportClause)) {
|
|
12456
|
+
for (const spec of st.exportClause.elements) if (!spec.isTypeOnly) out.push({
|
|
12457
|
+
name: spec.name.text,
|
|
12458
|
+
at: spec
|
|
12459
|
+
});
|
|
12460
|
+
}
|
|
12461
|
+
}
|
|
12462
|
+
return out;
|
|
12463
|
+
}
|
|
12464
|
+
//#endregion
|
|
12465
|
+
//#region cli/src/static/boundary.ts
|
|
12466
|
+
/** A static file's module in the program's bundle: the file's path, then this suffix (messages
|
|
12467
|
+
* name the file). */
|
|
12468
|
+
const STATIC_MODULE = "?static";
|
|
12469
|
+
const isStaticModule = (id) => id.endsWith(STATIC_MODULE);
|
|
12470
|
+
/** A file that starts with the "use static" directive (after comments). */
|
|
12471
|
+
const DIRECTIVE = /^(?:\s|\/\/[^\n]*\n|\/\*[\s\S]*?\*\/)*(["'])use static\1/;
|
|
12472
|
+
function isStaticFile(file) {
|
|
12473
|
+
return /\.[cm]?tsx?$/.test(file) && DIRECTIVE.test(readFileSync(file, "utf8"));
|
|
12474
|
+
}
|
|
12475
|
+
/** The program's bundle: a static file's import resolves to its module of host primitives;
|
|
12476
|
+
* `found` collects the static files. */
|
|
12477
|
+
function staticImports(found) {
|
|
12478
|
+
return {
|
|
12479
|
+
name: "visaljs-static-imports",
|
|
12480
|
+
async resolveId(source, importer, { kind }) {
|
|
12481
|
+
if (source === "visaljs:host" || !importer || kind !== "import-statement") return null;
|
|
12482
|
+
const r = await this.resolve(source, importer, { skipSelf: true });
|
|
12483
|
+
if (!r || r.external || !isStaticFile(r.id)) return null;
|
|
12484
|
+
found.add(r.id);
|
|
12485
|
+
return r.id + STATIC_MODULE;
|
|
12486
|
+
},
|
|
12487
|
+
load(id) {
|
|
12488
|
+
if (!isStaticModule(id)) return null;
|
|
12489
|
+
const file = id.slice(0, -7);
|
|
12490
|
+
const names = exportsOf(ts.createSourceFile(file, readFileSync(file, "utf8"), ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)).map((e) => e.name);
|
|
12491
|
+
return names.length ? `export { ${names.join(", ")} } from ${JSON.stringify(HOST_MODULE)};\n` : "";
|
|
12492
|
+
}
|
|
12493
|
+
};
|
|
12494
|
+
}
|
|
12495
|
+
/** The program's calls of static exports: a call whose callee is the export's host import (the
|
|
12496
|
+
* host function, or the module binding one store sets to it). Before optimizing. */
|
|
12497
|
+
function annotateTypedCalls(m, calls) {
|
|
12498
|
+
const all = [m.main, ...m.functions];
|
|
12499
|
+
const slots = /* @__PURE__ */ new Map();
|
|
12500
|
+
const stores = /* @__PURE__ */ new Map();
|
|
12501
|
+
for (const fn of all) for (const b of fn.blocks) for (const i of b.instrs) {
|
|
12502
|
+
if (i.op !== "store_global" || i.operands[0].op === "empty") continue;
|
|
12503
|
+
stores.set(i.index, (stores.get(i.index) ?? 0) + 1);
|
|
12504
|
+
const v = i.operands[0];
|
|
12505
|
+
if (v.op === "host_function" && Object.hasOwn(calls, v.target)) slots.set(i.index, v.target);
|
|
12506
|
+
}
|
|
12507
|
+
const target = (callee) => {
|
|
12508
|
+
const v = callee.op === "check_tdz" ? callee.operands[0] : callee;
|
|
12509
|
+
if (v.op === "host_function") return Object.hasOwn(calls, v.target) ? v.target : void 0;
|
|
12510
|
+
if (v.op === "load_global" && stores.get(v.index) === 1) return slots.get(v.index);
|
|
12511
|
+
};
|
|
12512
|
+
for (const fn of all) for (const b of fn.blocks) for (const i of b.instrs) {
|
|
12513
|
+
const name = i.op === "call" ? target(i.operands[0]) : void 0;
|
|
12514
|
+
if (name) i.typedCall = calls[name];
|
|
12515
|
+
}
|
|
12516
|
+
}
|
|
12517
|
+
//#endregion
|
|
11258
12518
|
//#region cli/src/bundle.ts
|
|
11259
12519
|
/** A bundling failure, already formatted as `file:line:col: Kind: message` lines. */
|
|
11260
12520
|
var BundleError = class extends Error {};
|
|
12521
|
+
/** Bundles `entry` into one ES module, `node:*` imports resolved to the host packages' modules.
|
|
12522
|
+
* Warnings go to `warn`, with their location ("file:line:col: ", or ""). */
|
|
11261
12523
|
async function bundle(entry, hosts, warn, extras = {}) {
|
|
11262
12524
|
const rollup = (implicit, warn, required) => rollupOnce(entry, hosts, implicit, warn, required, extras);
|
|
11263
12525
|
const b = await rollup([], warn, []);
|
|
@@ -11273,11 +12535,8 @@ function programCode(code, cwd, hosts) {
|
|
|
11273
12535
|
const preludes = hosts.map((h) => h.preludeDir + sep);
|
|
11274
12536
|
return code.replace(/^\/\/#region (.*)\n[^]*?^\/\/#endregion$/gm, (region, path) => preludes.some((dir) => resolve(cwd, path.replace(/^(?:\0|\\0)visaljs-runtime:/, "")).startsWith(dir)) ? "" : region);
|
|
11275
12537
|
}
|
|
11276
|
-
/**
|
|
11277
|
-
*
|
|
11278
|
-
* ``a(`react-dom`)``, where `a` is a bundle's createRequire(import.meta.url) (Vite's, for the
|
|
11279
|
-
* packages it left external). Only in code that calls createRequire; a call of a plain name with
|
|
11280
|
-
* one string, naming a package (not a built-in) that resolves from the entry. A call that isn't a
|
|
12538
|
+
/** `given`, and the packages a bundle names in ``a(`react-dom`)``, where `a` is a createRequire()
|
|
12539
|
+
* the bundler couldn't follow (Vite's, for the packages it left external). A call that isn't a
|
|
11281
12540
|
* require() but names an installed package bundles it needlessly: the build lists them. */
|
|
11282
12541
|
function requiredPackages(code, entry, given) {
|
|
11283
12542
|
if (!/\bcreateRequire\s*\(/.test(code)) return [];
|
|
@@ -11307,11 +12566,12 @@ async function rollupOnce(entry, hosts, implicit, warn, required, extras) {
|
|
|
11307
12566
|
cwd: dirname(resolve(entry)),
|
|
11308
12567
|
platform: "node",
|
|
11309
12568
|
logLevel: "warn",
|
|
11310
|
-
onLog: (_level, log) => log.code !== "EVAL" && warn(logText(log), logWhere(log)),
|
|
12569
|
+
onLog: (_level, log) => log.code !== "EVAL" && !(log.code === "MODULE_LEVEL_DIRECTIVE" && log.message.includes("\"use static\"")) && warn(logText(log), logWhere(log)),
|
|
11311
12570
|
...extras.nodeEnv === void 0 ? {} : { transform: { define: { "process.env.NODE_ENV": JSON.stringify(extras.nodeEnv) } } },
|
|
11312
12571
|
plugins: [
|
|
11313
12572
|
hostModules(hosts),
|
|
11314
12573
|
commonJS(),
|
|
12574
|
+
...ignoredModules$1(extras.ignored ?? []),
|
|
11315
12575
|
missingRequires(warn),
|
|
11316
12576
|
depdWrappers(),
|
|
11317
12577
|
implicitEntry(entry, implicit),
|
|
@@ -11345,9 +12605,8 @@ async function rollupOnce(entry, hosts, implicit, warn, required, extras) {
|
|
|
11345
12605
|
throw new BundleError(errors.map((log) => formatLog(log, log.code === "PARSE_ERROR" ? "SyntaxError" : "error")).join("\n"));
|
|
11346
12606
|
}
|
|
11347
12607
|
}
|
|
11348
|
-
/**
|
|
11349
|
-
*
|
|
11350
|
-
* primitives. */
|
|
12608
|
+
/** `node:fs` (or `fs`) resolves to its host package's prelude module, bundled like the program's
|
|
12609
|
+
* code; only prelude modules may import the host primitives. */
|
|
11351
12610
|
function hostModules(hosts) {
|
|
11352
12611
|
const modules = hostModules$1(hosts);
|
|
11353
12612
|
const preludes = hosts.map((h) => h.preludeDir + sep);
|
|
@@ -11355,7 +12614,7 @@ function hostModules(hosts) {
|
|
|
11355
12614
|
name: "visaljs-host",
|
|
11356
12615
|
resolveId(source, importer, { kind }) {
|
|
11357
12616
|
if (source === "visaljs:host") {
|
|
11358
|
-
if (importer && (preludes.some((dir) => importer.startsWith(dir)) || importer.startsWith(RUNTIME_COPY))) return {
|
|
12617
|
+
if (importer && (preludes.some((dir) => importer.startsWith(dir)) || importer.startsWith(RUNTIME_COPY) || isStaticModule(importer))) return {
|
|
11359
12618
|
id: source,
|
|
11360
12619
|
external: true
|
|
11361
12620
|
};
|
|
@@ -11386,14 +12645,11 @@ module.exports = m;
|
|
|
11386
12645
|
}
|
|
11387
12646
|
};
|
|
11388
12647
|
}
|
|
11389
|
-
/** CommonJS modules
|
|
11390
|
-
*
|
|
11391
|
-
*
|
|
11392
|
-
*
|
|
11393
|
-
*
|
|
11394
|
-
* declares them at its top as the original file's absolute path and directory at build time, as
|
|
11395
|
-
* `import.meta`'s `filename` and `dirname` are. ES modules keep them undefined (code tests
|
|
11396
|
-
* `typeof __dirname` to tell which it is). */
|
|
12648
|
+
/** CommonJS modules (as Node decides: `.cjs`, or `.js` without module syntax outside a `"type":
|
|
12649
|
+
* "module"` package) run sloppy, though the bundle is strict: each wrapper gets COMMONJS_DIRECTIVE,
|
|
12650
|
+
* and one saying "use strict" (a directive the bundler drops) gets a strict function of its own.
|
|
12651
|
+
* `__filename` and `__dirname` are the file's at build time; ES modules keep them undefined (code
|
|
12652
|
+
* tests `typeof __dirname`). */
|
|
11397
12653
|
function commonJS() {
|
|
11398
12654
|
const types = /* @__PURE__ */ new Map();
|
|
11399
12655
|
const packageType = (dir) => {
|
|
@@ -11451,15 +12707,14 @@ function commonJS() {
|
|
|
11451
12707
|
}
|
|
11452
12708
|
return marked ? {
|
|
11453
12709
|
code: s.toString(),
|
|
11454
|
-
map: s.generateMap({ hires:
|
|
12710
|
+
map: s.generateMap({ hires: "boundary" }).toString()
|
|
11455
12711
|
} : null;
|
|
11456
12712
|
}
|
|
11457
12713
|
};
|
|
11458
12714
|
}
|
|
11459
|
-
/** A require() of a file
|
|
11460
|
-
*
|
|
11461
|
-
*
|
|
11462
|
-
* standalone output), and addons they fall back from (sharp, in a try). A warning says where. */
|
|
12715
|
+
/** A require() of a missing file or a native addon throws when reached, as in Node, instead of
|
|
12716
|
+
* failing the build: packages require() files their published build leaves out (Next.js's dev
|
|
12717
|
+
* server) and addons they fall back from (sharp, in a try). */
|
|
11463
12718
|
function missingRequires(warn) {
|
|
11464
12719
|
return {
|
|
11465
12720
|
name: "visaljs-missing-requires",
|
|
@@ -11481,10 +12736,59 @@ function missingRequires(warn) {
|
|
|
11481
12736
|
};
|
|
11482
12737
|
}
|
|
11483
12738
|
const MISSING = "\0visaljs-missing:";
|
|
11484
|
-
/**
|
|
11485
|
-
*
|
|
11486
|
-
|
|
11487
|
-
|
|
12739
|
+
/** Stand-ins for the `ignored` modules. A "call" one whose export names can't be read is bundled
|
|
12740
|
+
* instead. */
|
|
12741
|
+
function ignoredModules$1(ignored) {
|
|
12742
|
+
if (!ignored.length) return [];
|
|
12743
|
+
const matching = (id) => {
|
|
12744
|
+
const path = id.replaceAll("\\", "/");
|
|
12745
|
+
return ignored.find((m) => /\.[cm]?js$/.test(m.module) ? path.endsWith(`/node_modules/${m.module}`) : path.includes(`/node_modules/${m.module}/`));
|
|
12746
|
+
};
|
|
12747
|
+
return [{
|
|
12748
|
+
name: "visaljs-ignored",
|
|
12749
|
+
async resolveId(source, importer, { kind }) {
|
|
12750
|
+
if (kind !== "require-call" || !importer || source.startsWith("\0")) return null;
|
|
12751
|
+
const resolved = await this.resolve(source, importer, { skipSelf: true });
|
|
12752
|
+
const m = resolved && !resolved.external ? matching(resolved.id) : void 0;
|
|
12753
|
+
if (!resolved || !m) return null;
|
|
12754
|
+
const code = m.stub === "call" ? readFileSync(resolved.id, "utf8") : "";
|
|
12755
|
+
const names = m.stub === "call" ? exportNames(code) : [];
|
|
12756
|
+
if (m.stub === "call" && !names.length) return null;
|
|
12757
|
+
return IGNORED + JSON.stringify({
|
|
12758
|
+
module: m.module,
|
|
12759
|
+
reason: m.reason,
|
|
12760
|
+
code: m.code ?? "MODULE_NOT_FOUND",
|
|
12761
|
+
names,
|
|
12762
|
+
esModule: code.includes("Object.defineProperty(exports, \"__esModule\"")
|
|
12763
|
+
});
|
|
12764
|
+
},
|
|
12765
|
+
load(id) {
|
|
12766
|
+
if (!id.startsWith(IGNORED)) return null;
|
|
12767
|
+
const { module, reason, code, names, esModule } = JSON.parse(id.slice(IGNORED.length));
|
|
12768
|
+
if (!names.length) {
|
|
12769
|
+
const message = code === "MODULE_NOT_FOUND" ? `Cannot find module '${module}' (${reason})` : `${module}: ${reason}`;
|
|
12770
|
+
return `throw Object.assign(new Error(${JSON.stringify(message)}), { code: ${JSON.stringify(code)} });\n`;
|
|
12771
|
+
}
|
|
12772
|
+
const message = JSON.stringify(`${module} isn't in the compiled program (${reason})`);
|
|
12773
|
+
return (esModule ? "Object.defineProperty(exports, \"__esModule\", { value: true });\n" : "") + names.map((n) => `exports.${n} = function () { throw new Error(${message}); };\n`).join("");
|
|
12774
|
+
}
|
|
12775
|
+
}];
|
|
12776
|
+
}
|
|
12777
|
+
const IGNORED = "\0visaljs-ignored:";
|
|
12778
|
+
/** A CommonJS module's export names, as compilers write them: `exports.a = …`,
|
|
12779
|
+
* `Object.defineProperty(exports, "a", …)`, and TypeScript's and SWC's `_export(exports, { a: …, b: … })`. */
|
|
12780
|
+
function exportNames(code) {
|
|
12781
|
+
const names = /* @__PURE__ */ new Set();
|
|
12782
|
+
for (const m of code.matchAll(/\bexports\.([A-Za-z_$][\w$]*)\s*=(?!=)/g)) names.add(m[1]);
|
|
12783
|
+
for (const m of code.matchAll(/\bObject\.defineProperty\(exports, "([A-Za-z_$][\w$]*)"/g)) names.add(m[1]);
|
|
12784
|
+
const block = /\b_export\(exports, \{\n([\s\S]*?)\n\}\);/.exec(code)?.[1];
|
|
12785
|
+
if (block) for (const m of block.matchAll(/^ {4}([A-Za-z_$][\w$]*): function\(\) \{/gm)) names.add(m[1]);
|
|
12786
|
+
names.delete("__esModule");
|
|
12787
|
+
return [...names];
|
|
12788
|
+
}
|
|
12789
|
+
/** depd (Express 4, body-parser, Next.js's send) wraps deprecated functions with new Function,
|
|
12790
|
+
* which needs eval, for their arity: a closure instead (length 0). Its call is the same wherever
|
|
12791
|
+
* it's bundled or vendored. */
|
|
11488
12792
|
function depdWrappers() {
|
|
11489
12793
|
const call = /\bnew Function\((?=\s*(["'])fn\1\s*,\s*\1log\1\s*,\s*\1deprecate\1\s*,\s*\1message\1\s*,\s*\1site\1\s*,)/g;
|
|
11490
12794
|
const helper = "function __visaljs_depdWrapper() { return function (fn, log, deprecate, message, site) { return function () { log.call(deprecate, message, site); return fn.apply(this, arguments); }; }; }\n";
|
|
@@ -11572,10 +12876,10 @@ function gcCheckFlags() {
|
|
|
11572
12876
|
if (mode === "stress") return ["-DJS_GC_STRESS=1"];
|
|
11573
12877
|
throw new Error(`VISALJS_GC_CHECKS=${mode}: expected verify or stress`);
|
|
11574
12878
|
}
|
|
11575
|
-
/** $CC alone, else each
|
|
11576
|
-
*
|
|
11577
|
-
*
|
|
11578
|
-
*
|
|
12879
|
+
/** $CC alone, else each usual C compiler on the PATH that runs, in order of preference: a build
|
|
12880
|
+
* falls back to the next. Clang first: it compiles a large program's C in half GCC's time and
|
|
12881
|
+
* memory, and its code ran faster (7% fewer instructions per HTTP request). Zig's clang comes
|
|
12882
|
+
* last: it brings its own libc headers and linker. */
|
|
11579
12883
|
function findCompilers() {
|
|
11580
12884
|
if (process.env.CC) {
|
|
11581
12885
|
if (!compilerVersion(process.env.CC)) throw new Error(`$CC (${process.env.CC}) doesn't run`);
|
|
@@ -11583,8 +12887,8 @@ function findCompilers() {
|
|
|
11583
12887
|
}
|
|
11584
12888
|
const found = /* @__PURE__ */ new Map();
|
|
11585
12889
|
for (const cc of [
|
|
11586
|
-
"cc",
|
|
11587
12890
|
"clang",
|
|
12891
|
+
"cc",
|
|
11588
12892
|
"gcc",
|
|
11589
12893
|
"zig cc"
|
|
11590
12894
|
]) {
|
|
@@ -11616,7 +12920,6 @@ function command(cc) {
|
|
|
11616
12920
|
const [program = cc, ...args] = cc.trim().split(/\s+/);
|
|
11617
12921
|
return [program, ...args];
|
|
11618
12922
|
}
|
|
11619
|
-
/** Runs the compiler with `args`, synchronously (execFileSync's options and result). */
|
|
11620
12923
|
function runCompiler(cc, args, options) {
|
|
11621
12924
|
const [program, ...prefix] = command(cc);
|
|
11622
12925
|
return execFileSync(program, [...prefix, ...args], options);
|
|
@@ -11745,13 +13048,9 @@ function compileCommand(cc, src, common) {
|
|
|
11745
13048
|
...cxx(common)
|
|
11746
13049
|
];
|
|
11747
13050
|
}
|
|
11748
|
-
/**
|
|
11749
|
-
*
|
|
11750
|
-
*
|
|
11751
|
-
* is the library's sources and flags, not the compiler's, so any C compiler for the platform
|
|
11752
|
-
* links them. They're -O2 builds, used when the build is one (not --debug or VISALJS_GC_CHECKS)
|
|
11753
|
-
* and the compiler targets this machine. VISALJS_PREBUILT names another directory of them (a
|
|
11754
|
-
* cross-compiler's target's, or build/prebuilt/<target> in this repository), or 0 for none. */
|
|
13051
|
+
/** A prebuilt library (cli/scripts/prebuilt.ts, built with Zig): prebuilt/<target>/<name>-<prebuiltKey>.a.
|
|
13052
|
+
* The key leaves out the compiler, so any C compiler for the platform links it. Only for -O2 builds
|
|
13053
|
+
* whose compiler targets this machine; VISALJS_PREBUILT names another directory, or 0 for none. */
|
|
11755
13054
|
function prebuiltArchive(tc, lib) {
|
|
11756
13055
|
if (tc.opt.join(" ") !== PREBUILT_OPT.join(" ")) return void 0;
|
|
11757
13056
|
const env = process.env.VISALJS_PREBUILT;
|
|
@@ -11762,9 +13061,9 @@ function prebuiltArchive(tc, lib) {
|
|
|
11762
13061
|
}
|
|
11763
13062
|
[...COMMON.filter((f) => f !== "-g")];
|
|
11764
13063
|
const PREBUILT_OPT = ["-O2"];
|
|
11765
|
-
/** A prebuilt library's key: its
|
|
11766
|
-
* and
|
|
11767
|
-
* the
|
|
13064
|
+
/** A prebuilt library's key: its files' paths (from runtime/'s parent: the same in this repository
|
|
13065
|
+
* and the package) and contents, and its flags. Changing PREBUILT_COMMON or PREBUILT_OPT bumps
|
|
13066
|
+
* the version, the key's first line. */
|
|
11768
13067
|
function prebuiltKey(lib) {
|
|
11769
13068
|
const root = dirname(RUNTIME_DIR);
|
|
11770
13069
|
const portable = (s) => s.split(root).join("<root>").replaceAll("\\", "/");
|
|
@@ -11778,13 +13077,13 @@ function hostTarget() {
|
|
|
11778
13077
|
const musl = process.platform === "linux" && !process.report.getReport().header?.glibcVersionRuntime;
|
|
11779
13078
|
return `${process.platform}-${process.arch}${musl ? "-musl" : ""}`;
|
|
11780
13079
|
}
|
|
11781
|
-
let installed;
|
|
13080
|
+
let installed$1;
|
|
11782
13081
|
function installedPrebuilt() {
|
|
11783
|
-
if (installed === void 0) {
|
|
13082
|
+
if (installed$1 === void 0) {
|
|
11784
13083
|
const dir = join(dirname(RUNTIME_DIR), "prebuilt", hostTarget());
|
|
11785
|
-
installed = existsSync(dir) ? dir : "";
|
|
13084
|
+
installed$1 = existsSync(dir) ? dir : "";
|
|
11786
13085
|
}
|
|
11787
|
-
return installed;
|
|
13086
|
+
return installed$1;
|
|
11788
13087
|
}
|
|
11789
13088
|
/** Runs the compiler commands, as many at once as there are processors (in a child Node process:
|
|
11790
13089
|
* the driver's API is synchronous; the commands on its stdin). Throws when one fails, after its output. */
|
|
@@ -11861,17 +13160,14 @@ function programArchives(tc, hosts, building) {
|
|
|
11861
13160
|
const libraries = [...hosts.flatMap((h) => h.libraries(RUNTIME_INCLUDE)).filter((l) => l.sources.length), ...runtimeLibraries()];
|
|
11862
13161
|
return [...libraries.map((l) => libraryArchive(tc, l, building)), ...libraries.flatMap((l) => l.link ?? []).map((l) => `-l${l}`)];
|
|
11863
13162
|
}
|
|
11864
|
-
/** Compiles the program's C files and links them with `archives` (programArchives)
|
|
11865
|
-
*
|
|
11866
|
-
*
|
|
11867
|
-
*
|
|
11868
|
-
* One file compiles and links in one command. A split program's files compile to
|
|
11869
|
-
* objects, `jobs` at once, then link; `compiled` hears as each one is done. Asynchronous: the
|
|
11870
|
-
* build's report keeps drawing while the compilers run. */
|
|
13163
|
+
/** Compiles the program's C files and links them with `archives` (programArchives), stripping at
|
|
13164
|
+
* this link when asked. A split program's files compile `jobs` at once; `compiled` hears as each
|
|
13165
|
+
* is done. Asynchronous, so the build's report keeps drawing. */
|
|
11871
13166
|
async function compileProgram(tc, cFiles, output, archives, { extraFlags = [], jobs = compileJobs(), compiled } = {}) {
|
|
11872
13167
|
const flags = [
|
|
11873
13168
|
...COMMON,
|
|
11874
13169
|
...tc.opt,
|
|
13170
|
+
...tc.strip ? ["-g0"] : [],
|
|
11875
13171
|
...extraFlags
|
|
11876
13172
|
];
|
|
11877
13173
|
const link = [
|
|
@@ -11918,10 +13214,11 @@ async function compileProgram(tc, cFiles, output, archives, { extraFlags = [], j
|
|
|
11918
13214
|
...link
|
|
11919
13215
|
]);
|
|
11920
13216
|
}
|
|
11921
|
-
/** How many of a split program's files compile at once: one per processor
|
|
11922
|
-
* of memory (a
|
|
11923
|
-
|
|
11924
|
-
|
|
13217
|
+
/** How many of a split program's files compile at once: one per processor and per `perJob` bytes
|
|
13218
|
+
* of available memory (a cgroup's limit counts). A file needs up to about 1.1 GB at -O2 with
|
|
13219
|
+
* debug info, 0.55 GB without. */
|
|
13220
|
+
function compileJobs(perJob = 15e8) {
|
|
13221
|
+
return Math.max(1, Math.min(availableParallelism(), Math.floor(process.availableMemory() / perJob)));
|
|
11925
13222
|
}
|
|
11926
13223
|
/** Runs the compiler; its stderr passes through (but for notes the program can't act on), and its
|
|
11927
13224
|
* tail is kept to recognize a broken toolchain. Rejects when it fails. */
|
|
@@ -12104,8 +13401,244 @@ ${readFileSync(VISALJS_LICENSE, "utf8").trimEnd()}
|
|
|
12104
13401
|
${sections.join("\n")}`;
|
|
12105
13402
|
}
|
|
12106
13403
|
//#endregion
|
|
12107
|
-
//#region cli/src/
|
|
12108
|
-
|
|
13404
|
+
//#region cli/src/static/subset.ts
|
|
13405
|
+
const K = ts.SyntaxKind;
|
|
13406
|
+
const STATEMENTS = /* @__PURE__ */ new Set([
|
|
13407
|
+
K.FunctionDeclaration,
|
|
13408
|
+
K.VariableStatement,
|
|
13409
|
+
K.VariableDeclarationList,
|
|
13410
|
+
K.VariableDeclaration,
|
|
13411
|
+
K.Block,
|
|
13412
|
+
K.IfStatement,
|
|
13413
|
+
K.ForStatement,
|
|
13414
|
+
K.ForOfStatement,
|
|
13415
|
+
K.WhileStatement,
|
|
13416
|
+
K.DoStatement,
|
|
13417
|
+
K.ReturnStatement,
|
|
13418
|
+
K.BreakStatement,
|
|
13419
|
+
K.ContinueStatement,
|
|
13420
|
+
K.ExpressionStatement,
|
|
13421
|
+
K.SwitchStatement,
|
|
13422
|
+
K.CaseBlock,
|
|
13423
|
+
K.CaseClause,
|
|
13424
|
+
K.DefaultClause,
|
|
13425
|
+
K.ThrowStatement,
|
|
13426
|
+
K.TryStatement,
|
|
13427
|
+
K.CatchClause,
|
|
13428
|
+
K.EmptyStatement,
|
|
13429
|
+
K.LabeledStatement,
|
|
13430
|
+
K.TypeAliasDeclaration,
|
|
13431
|
+
K.InterfaceDeclaration
|
|
13432
|
+
]);
|
|
13433
|
+
const EXPRESSIONS = /* @__PURE__ */ new Set([
|
|
13434
|
+
K.Identifier,
|
|
13435
|
+
K.NumericLiteral,
|
|
13436
|
+
K.StringLiteral,
|
|
13437
|
+
K.NoSubstitutionTemplateLiteral,
|
|
13438
|
+
K.TemplateExpression,
|
|
13439
|
+
K.TemplateHead,
|
|
13440
|
+
K.TemplateMiddle,
|
|
13441
|
+
K.TemplateTail,
|
|
13442
|
+
K.TemplateSpan,
|
|
13443
|
+
K.TrueKeyword,
|
|
13444
|
+
K.FalseKeyword,
|
|
13445
|
+
K.NullKeyword,
|
|
13446
|
+
K.BinaryExpression,
|
|
13447
|
+
K.PrefixUnaryExpression,
|
|
13448
|
+
K.PostfixUnaryExpression,
|
|
13449
|
+
K.ConditionalExpression,
|
|
13450
|
+
K.CallExpression,
|
|
13451
|
+
K.NewExpression,
|
|
13452
|
+
K.PropertyAccessExpression,
|
|
13453
|
+
K.ElementAccessExpression,
|
|
13454
|
+
K.ArrayLiteralExpression,
|
|
13455
|
+
K.ObjectLiteralExpression,
|
|
13456
|
+
K.PropertyAssignment,
|
|
13457
|
+
K.ShorthandPropertyAssignment,
|
|
13458
|
+
K.ParenthesizedExpression,
|
|
13459
|
+
K.ArrowFunction,
|
|
13460
|
+
K.FunctionExpression,
|
|
13461
|
+
K.TypeOfExpression,
|
|
13462
|
+
K.NonNullExpression,
|
|
13463
|
+
K.AsExpression,
|
|
13464
|
+
K.SatisfiesExpression,
|
|
13465
|
+
K.Parameter
|
|
13466
|
+
]);
|
|
13467
|
+
/** What isn't allowed yet, in words, for the error. */
|
|
13468
|
+
const NAMED = {
|
|
13469
|
+
[K.ClassDeclaration]: "classes",
|
|
13470
|
+
[K.ClassExpression]: "classes",
|
|
13471
|
+
[K.ThisKeyword]: "`this`",
|
|
13472
|
+
[K.SuperKeyword]: "`super`",
|
|
13473
|
+
[K.EnumDeclaration]: "enums",
|
|
13474
|
+
[K.ModuleDeclaration]: "namespaces",
|
|
13475
|
+
[K.Decorator]: "decorators",
|
|
13476
|
+
[K.YieldExpression]: "generators",
|
|
13477
|
+
[K.AwaitExpression]: "async functions",
|
|
13478
|
+
[K.DeleteExpression]: "`delete`",
|
|
13479
|
+
[K.ForInStatement]: "for…in",
|
|
13480
|
+
[K.SpreadElement]: "spread",
|
|
13481
|
+
[K.SpreadAssignment]: "spread",
|
|
13482
|
+
[K.ObjectBindingPattern]: "destructuring",
|
|
13483
|
+
[K.ArrayBindingPattern]: "destructuring",
|
|
13484
|
+
[K.TaggedTemplateExpression]: "tagged templates",
|
|
13485
|
+
[K.RegularExpressionLiteral]: "regular expressions",
|
|
13486
|
+
[K.BigIntLiteral]: "BigInt",
|
|
13487
|
+
[K.VoidExpression]: "`void`",
|
|
13488
|
+
[K.MetaProperty]: "import.meta and new.target",
|
|
13489
|
+
[K.ImportKeyword]: "import()",
|
|
13490
|
+
[K.MethodDeclaration]: "methods",
|
|
13491
|
+
[K.GetAccessor]: "getters",
|
|
13492
|
+
[K.SetAccessor]: "setters",
|
|
13493
|
+
[K.WithStatement]: "`with`",
|
|
13494
|
+
[K.DebuggerStatement]: "`debugger`",
|
|
13495
|
+
[K.ExportAssignment]: "default exports"
|
|
13496
|
+
};
|
|
13497
|
+
/** A module's top level: declarations only (top-level code runs, in an order across files). */
|
|
13498
|
+
const TOP_LEVEL = /* @__PURE__ */ new Set([
|
|
13499
|
+
K.FunctionDeclaration,
|
|
13500
|
+
K.VariableStatement,
|
|
13501
|
+
K.TypeAliasDeclaration,
|
|
13502
|
+
K.InterfaceDeclaration,
|
|
13503
|
+
K.ImportDeclaration,
|
|
13504
|
+
K.ExportDeclaration,
|
|
13505
|
+
K.EmptyStatement
|
|
13506
|
+
]);
|
|
13507
|
+
const notYet = (what) => `static code doesn't have ${what} yet`;
|
|
13508
|
+
/** Checks the unit's files against the subset; throws the first error. */
|
|
13509
|
+
function checkSubset(program, files) {
|
|
13510
|
+
const checker = program.getTypeChecker();
|
|
13511
|
+
const fileSet = new Set(files.map((f) => resolve(f)));
|
|
13512
|
+
for (const file of files) program.getSourceFile(file).statements.forEach((st, k) => {
|
|
13513
|
+
if (k === 0 && ts.isExpressionStatement(st) && ts.isStringLiteral(st.expression)) return;
|
|
13514
|
+
if (NAMED[st.kind]) throw compileError(st, notYet(NAMED[st.kind]));
|
|
13515
|
+
if (!TOP_LEVEL.has(st.kind)) throw compileError(st, "a static file only declares: top-level statements would run code, in no order across files");
|
|
13516
|
+
if (ts.isImportDeclaration(st)) return checkImport(checker, st, fileSet);
|
|
13517
|
+
if (ts.isExportDeclaration(st)) {
|
|
13518
|
+
if (st.moduleSpecifier) throw compileError(st, notYet("re-exports (export … from)"));
|
|
13519
|
+
return;
|
|
13520
|
+
}
|
|
13521
|
+
visit(st);
|
|
13522
|
+
});
|
|
13523
|
+
}
|
|
13524
|
+
function checkImport(checker, st, files) {
|
|
13525
|
+
const specifier = st.moduleSpecifier.text;
|
|
13526
|
+
if (specifier === "visaljs:host" || specifier === "visaljs" || importedFile(checker, st, files)) {
|
|
13527
|
+
if (st.importClause?.name || st.importClause?.namedBindings && ts.isNamespaceImport(st.importClause.namedBindings)) throw compileError(st, notYet("default and namespace imports"));
|
|
13528
|
+
return;
|
|
13529
|
+
}
|
|
13530
|
+
throw compileError(st.moduleSpecifier, `static code imports only static files, "visaljs" and ${HOST_MODULE}: '${specifier}' isn't one`);
|
|
13531
|
+
}
|
|
13532
|
+
/** A type: anything tsc takes but `any`. */
|
|
13533
|
+
function visitType(node) {
|
|
13534
|
+
if (node.kind === K.AnyKeyword) throw compileError(node, "`any` isn't a static type: name the type");
|
|
13535
|
+
ts.forEachChild(node, visitType);
|
|
13536
|
+
}
|
|
13537
|
+
function visit(node) {
|
|
13538
|
+
if (ts.isTypeNode(node)) return visitType(node);
|
|
13539
|
+
if (NAMED[node.kind]) throw compileError(node, notYet(NAMED[node.kind]));
|
|
13540
|
+
if (ts.isToken(node) || ts.isModifier(node) || ts.isTypeParameterDeclaration(node)) return;
|
|
13541
|
+
if (ts.isFunctionLike(node) && (node.asteriskToken || ts.getModifiers(node)?.some((m) => m.kind === K.AsyncKeyword))) throw compileError(node, notYet("generators and async functions"));
|
|
13542
|
+
if (ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node)) return visitType(node);
|
|
13543
|
+
if (!STATEMENTS.has(node.kind) && !EXPRESSIONS.has(node.kind)) throw compileError(node, notYet(ts.SyntaxKind[node.kind]));
|
|
13544
|
+
ts.forEachChild(node, visit);
|
|
13545
|
+
}
|
|
13546
|
+
/** A function's name in C, as the emitter makes it (its ident). */
|
|
13547
|
+
function ident(name) {
|
|
13548
|
+
let out = "";
|
|
13549
|
+
for (const ch of name) out += /[A-Za-z0-9_]/.test(ch) ? ch : `_u${ch.codePointAt(0).toString(16)}_`;
|
|
13550
|
+
return out;
|
|
13551
|
+
}
|
|
13552
|
+
/** Compiles the typed files as one unit. */
|
|
13553
|
+
function buildTypedUnit(files, primitives) {
|
|
13554
|
+
const program = ts.createProgram([...files, TYPES_FILE], {
|
|
13555
|
+
strict: true,
|
|
13556
|
+
noEmit: true,
|
|
13557
|
+
target: ts.ScriptTarget.ESNext,
|
|
13558
|
+
module: ts.ModuleKind.ESNext,
|
|
13559
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
13560
|
+
allowImportingTsExtensions: true,
|
|
13561
|
+
skipLibCheck: true
|
|
13562
|
+
});
|
|
13563
|
+
checkSubset(program, files);
|
|
13564
|
+
const errors = ts.getPreEmitDiagnostics(program).filter((d) => d.category === ts.DiagnosticCategory.Error && files.includes(d.file?.fileName ?? ""));
|
|
13565
|
+
if (errors.length) throw new Error(ts.formatDiagnostics(errors, {
|
|
13566
|
+
getCanonicalFileName: (f) => f,
|
|
13567
|
+
getCurrentDirectory: () => process.cwd(),
|
|
13568
|
+
getNewLine: () => "\n"
|
|
13569
|
+
}));
|
|
13570
|
+
const checker = program.getTypeChecker();
|
|
13571
|
+
const signatures = /* @__PURE__ */ new Map();
|
|
13572
|
+
for (const file of files) declaredSignatures(checker, program.getSourceFile(file), signatures);
|
|
13573
|
+
const { ast, exports: exported } = linkUnit(program, files, primitives);
|
|
13574
|
+
const ir = lower(ast, analyze(ast));
|
|
13575
|
+
const at = (fn) => fn.loc ? `${fn.loc.file}:${fn.loc.line}:${fn.loc.column}` : "";
|
|
13576
|
+
for (const fn of ir.functions) {
|
|
13577
|
+
const meta = fn.meta;
|
|
13578
|
+
const sig = signatures.get(at(fn));
|
|
13579
|
+
if (!sig) continue;
|
|
13580
|
+
const hidden = fn.params.length - sig.types.length;
|
|
13581
|
+
meta.declared = fn.params.map((_, k) => k < hidden ? void 0 : sig.types[k - hidden]);
|
|
13582
|
+
meta.declaredViews = fn.params.map((_, k) => k < hidden ? void 0 : sig.views[k - hidden]);
|
|
13583
|
+
meta.external = true;
|
|
13584
|
+
}
|
|
13585
|
+
const exports = {};
|
|
13586
|
+
const exportedFns = /* @__PURE__ */ new Map();
|
|
13587
|
+
for (const [name, loc] of exported) {
|
|
13588
|
+
const fn = ir.functions.find((f) => at(f) === loc);
|
|
13589
|
+
exportedFns.set(name, fn.name);
|
|
13590
|
+
exports[name] = {
|
|
13591
|
+
c: `vt_jsf_${ident(fn.name)}__generic`,
|
|
13592
|
+
length: fn.meta.length
|
|
13593
|
+
};
|
|
13594
|
+
}
|
|
13595
|
+
const report = [];
|
|
13596
|
+
const optimized = optimize(ir, void 0, report);
|
|
13597
|
+
const calls = {};
|
|
13598
|
+
for (const [name, fnName] of exportedFns) {
|
|
13599
|
+
const fn = optimized.functions.find((f) => f.name === fnName);
|
|
13600
|
+
const declared = fn.meta.declared;
|
|
13601
|
+
if (declared.some((t) => t === void 0)) continue;
|
|
13602
|
+
let returns = T.None;
|
|
13603
|
+
for (const b of fn.blocks) if (b.term?.kind === "return") returns |= b.term.value.type & ~T.Empty;
|
|
13604
|
+
calls[name] = {
|
|
13605
|
+
direct: `vt_jsf_${ident(fn.name)}`,
|
|
13606
|
+
generic: exports[name].c,
|
|
13607
|
+
params: fn.params.map((p, k) => ({
|
|
13608
|
+
type: declared[k],
|
|
13609
|
+
repr: p.repr,
|
|
13610
|
+
...fn.meta.declaredViews?.[k] ? { view: fn.meta.declaredViews[k] } : {}
|
|
13611
|
+
})),
|
|
13612
|
+
returns: returns === T.None ? T.Undefined : returns & T.Int64 ? returns & ~T.Int64 | T.Int32 | T.Double : returns,
|
|
13613
|
+
returnRepr: fn.returnRepr ?? "boxed"
|
|
13614
|
+
};
|
|
13615
|
+
}
|
|
13616
|
+
const unit = emitHarnessScript(optimized, "vt_", /* @__PURE__ */ new Map(), { hostPrimitives: primitives });
|
|
13617
|
+
return {
|
|
13618
|
+
c: unit.c,
|
|
13619
|
+
atoms: new Map(unit.atoms),
|
|
13620
|
+
exports,
|
|
13621
|
+
calls,
|
|
13622
|
+
report,
|
|
13623
|
+
names: [...programNames(ast)]
|
|
13624
|
+
};
|
|
13625
|
+
}
|
|
13626
|
+
//#endregion
|
|
13627
|
+
//#region cli/src/pipeline/errors.ts
|
|
13628
|
+
/** An integration's failure, as the message to print. */
|
|
13629
|
+
var IntegrationError = class extends Error {};
|
|
13630
|
+
/** A command `ctx.run` ran that failed: its output, for the integration to look into. */
|
|
13631
|
+
var CommandError = class extends IntegrationError {
|
|
13632
|
+
output;
|
|
13633
|
+
constructor(message, output) {
|
|
13634
|
+
super(message);
|
|
13635
|
+
this.output = output;
|
|
13636
|
+
}
|
|
13637
|
+
};
|
|
13638
|
+
//#endregion
|
|
13639
|
+
//#region cli/src/integrations/project.ts
|
|
13640
|
+
/** The project names no entry, or several. */
|
|
13641
|
+
var EntryError = class extends IntegrationError {};
|
|
12109
13642
|
const NODE_VALUE_OPTIONS = /* @__PURE__ */ new Set([
|
|
12110
13643
|
"-r",
|
|
12111
13644
|
"--require",
|
|
@@ -12203,18 +13736,31 @@ function projectEntry(root) {
|
|
|
12203
13736
|
};
|
|
12204
13737
|
throw new EntryError(`no entry file: checked ${existsSync(pkgFile) ? `package.json's "bin", "scripts.start" and "main", and index.js` : "a package.json and index.js"}${missing.length ? ` (${missing.join("; ")})` : ""}`);
|
|
12205
13738
|
}
|
|
13739
|
+
const project = {
|
|
13740
|
+
id: "project",
|
|
13741
|
+
name: "package.json",
|
|
13742
|
+
provides: "entry",
|
|
13743
|
+
detect: () => true,
|
|
13744
|
+
hooks: { prepare(ctx) {
|
|
13745
|
+
const entry = projectEntry(ctx.root);
|
|
13746
|
+
ctx.entryFile = entry.file;
|
|
13747
|
+
ctx.output = join(ctx.root, entry.name);
|
|
13748
|
+
ctx.note(`Entry: ${relative(ctx.root, entry.file).replaceAll("\\", "/")} (${entry.from})`, "📄");
|
|
13749
|
+
if (entry.ignoredArgs.length) ctx.note(`its arguments (${entry.ignoredArgs.join(" ")}) aren't built in: give them when you run the binary`);
|
|
13750
|
+
} }
|
|
13751
|
+
};
|
|
12206
13752
|
//#endregion
|
|
12207
|
-
//#region cli/src/
|
|
12208
|
-
/** A framework's failure, as the message to print. */
|
|
12209
|
-
var FrameworkError = class extends Error {};
|
|
12210
|
-
//#endregion
|
|
12211
|
-
//#region cli/src/frameworks/project.ts
|
|
13753
|
+
//#region cli/src/pipeline/packages.ts
|
|
12212
13754
|
/** The project's package.json, or {} without one. */
|
|
12213
13755
|
function packageJson(root) {
|
|
12214
13756
|
const file = join(root, "package.json");
|
|
12215
|
-
|
|
13757
|
+
if (!existsSync(file)) return {};
|
|
13758
|
+
try {
|
|
13759
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
13760
|
+
} catch (e) {
|
|
13761
|
+
throw new IntegrationError(`cannot read ${file}: ${e.message}`);
|
|
13762
|
+
}
|
|
12216
13763
|
}
|
|
12217
|
-
/** The project's package.json's dependencies and devDependencies. */
|
|
12218
13764
|
function dependencies(root) {
|
|
12219
13765
|
const pkg = packageJson(root);
|
|
12220
13766
|
return {
|
|
@@ -12222,25 +13768,36 @@ function dependencies(root) {
|
|
|
12222
13768
|
...pkg.dependencies
|
|
12223
13769
|
};
|
|
12224
13770
|
}
|
|
13771
|
+
/** Whether `name` is installed for the project in `root`: in node_modules here or in a directory
|
|
13772
|
+
* above (a workspace's), as Node finds it, or in pnpm's store there (node_modules/.pnpm/<name>@…),
|
|
13773
|
+
* where a dependency's dependencies are. */
|
|
13774
|
+
function installed(root, name) {
|
|
13775
|
+
const store = `${name.replace("/", "+")}@`;
|
|
13776
|
+
for (let dir = root;; dir = dirname(dir)) {
|
|
13777
|
+
const modules = join(dir, "node_modules");
|
|
13778
|
+
if (existsSync(join(modules, name, "package.json"))) return true;
|
|
13779
|
+
const pnpm = join(modules, ".pnpm");
|
|
13780
|
+
if (existsSync(pnpm) && readdirSync(pnpm).some((d) => d.startsWith(store))) return true;
|
|
13781
|
+
if (dirname(dir) === dir) return false;
|
|
13782
|
+
}
|
|
13783
|
+
}
|
|
12225
13784
|
/** The script a package installed in the project runs as `name` (its package.json's "bin"). */
|
|
12226
13785
|
function packageBin(root, pkg, name) {
|
|
12227
13786
|
let manifest;
|
|
12228
13787
|
try {
|
|
12229
13788
|
manifest = createRequire(join(root, "package.json")).resolve(`${pkg}/package.json`);
|
|
12230
13789
|
} catch {
|
|
12231
|
-
throw new
|
|
13790
|
+
throw new IntegrationError(`${pkg} isn't installed in ${root}: run npm install first`);
|
|
12232
13791
|
}
|
|
12233
13792
|
const { bin } = JSON.parse(readFileSync(manifest, "utf8"));
|
|
12234
13793
|
const script = typeof bin === "string" ? bin : bin?.[name];
|
|
12235
|
-
if (!script) throw new
|
|
13794
|
+
if (!script) throw new IntegrationError(`${pkg} has no ${name} command`);
|
|
12236
13795
|
return join(dirname(manifest), script);
|
|
12237
13796
|
}
|
|
12238
13797
|
//#endregion
|
|
12239
|
-
//#region cli/src/
|
|
13798
|
+
//#region cli/src/integrations/next.ts
|
|
12240
13799
|
const TABLE = "\0visaljs-next-table";
|
|
12241
13800
|
const UNTRACED = "\0visaljs-next-untraced:";
|
|
12242
|
-
const UNCALLED_PREFIX = "\0visaljs-next-uncalled:";
|
|
12243
|
-
/** next build's config, as the standalone server.js gets it, and its build directory (.next). */
|
|
12244
13801
|
function serverConfig(root) {
|
|
12245
13802
|
for (const distDir of [".next", ...configuredDistDir(root)]) {
|
|
12246
13803
|
const file = join(root, distDir, "required-server-files.json");
|
|
@@ -12252,10 +13809,9 @@ function serverConfig(root) {
|
|
|
12252
13809
|
};
|
|
12253
13810
|
}
|
|
12254
13811
|
}
|
|
12255
|
-
if (existsSync(join(root, "out", "index.html")) || existsSync(join(root, "out", "404.html"))) throw new
|
|
12256
|
-
throw new
|
|
13812
|
+
if (existsSync(join(root, "out", "index.html")) || existsSync(join(root, "out", "404.html"))) throw new IntegrationError(`${root} is a static export (output: "export" in next.config): it has no server to compile. Serve out/ with any static file server, or remove output: "export" to compile the Next.js server`);
|
|
13813
|
+
throw new IntegrationError(`${root} has no Next.js build (no .next/required-server-files.json): run \`next build\`, or build without --no-framework-build`);
|
|
12257
13814
|
}
|
|
12258
|
-
/** Whether next build used Turbopack (its default since Next.js 16): its chunks' runtime is there. */
|
|
12259
13815
|
function isTurbopackBuild(root, distDir) {
|
|
12260
13816
|
const chunks = join(root, distDir, "server", "chunks");
|
|
12261
13817
|
return existsSync(join(chunks, "[turbopack]_runtime.js")) || existsSync(join(chunks, "ssr", "[turbopack]_runtime.js"));
|
|
@@ -12263,7 +13819,7 @@ function isTurbopackBuild(root, distDir) {
|
|
|
12263
13819
|
function configuredDistDir(root) {
|
|
12264
13820
|
return readdirSync(root).filter((d) => d !== ".next" && existsSync(join(root, d, "required-server-files.json")));
|
|
12265
13821
|
}
|
|
12266
|
-
/** Every file under `dir`,
|
|
13822
|
+
/** Every file under `dir`, relative to it, with "/". */
|
|
12267
13823
|
function files(dir, under = "") {
|
|
12268
13824
|
const out = [];
|
|
12269
13825
|
for (const entry of readdirSync(join(dir, under))) {
|
|
@@ -12273,9 +13829,6 @@ function files(dir, under = "") {
|
|
|
12273
13829
|
}
|
|
12274
13830
|
return out;
|
|
12275
13831
|
}
|
|
12276
|
-
/** The table's module (CommonJS: each entry is required when it's first loaded): .next/server's
|
|
12277
|
-
* modules by their path under .next, and its client reference manifests as functions of the
|
|
12278
|
-
* context Next evaluates them in. */
|
|
12279
13832
|
function tableModule(root, distDir) {
|
|
12280
13833
|
const build = join(root, distDir);
|
|
12281
13834
|
const server = files(join(build, "server")).filter((f) => f.endsWith(".js")).map((f) => `server/${f}`);
|
|
@@ -12284,7 +13837,6 @@ function tableModule(root, distDir) {
|
|
|
12284
13837
|
const manifests = server.filter(manifest).map((f) => ` ${JSON.stringify(f)}: function (globalThis) { var process = globalThis.process;\n${readFileSync(join(build, f), "utf8")}\n},`);
|
|
12285
13838
|
return `"use strict";\nexports.modules = {\n${modules.join("\n")}\n};\nexports.manifests = {\n${manifests.join("\n")}\n};\n`;
|
|
12286
13839
|
}
|
|
12287
|
-
/** The files next build traced as the server's and its routes' (their .nft.json files): absolute paths. */
|
|
12288
13840
|
function tracedFiles(root, distDir) {
|
|
12289
13841
|
const build = join(root, distDir);
|
|
12290
13842
|
const traces = [...files(build, "").filter((f) => !f.startsWith("standalone/") && !f.startsWith("cache/") && f.endsWith(".nft.json"))];
|
|
@@ -12295,9 +13847,9 @@ function tracedFiles(root, distDir) {
|
|
|
12295
13847
|
}
|
|
12296
13848
|
return traced;
|
|
12297
13849
|
}
|
|
12298
|
-
/**
|
|
12299
|
-
*
|
|
12300
|
-
*
|
|
13850
|
+
/** The variables Next picks a route runtime by, as this build's server has them: NEXT_RUNTIME (only
|
|
13851
|
+
* the edge runtime sets it), TURBOPACK (set for a Turbopack build), __NEXT_EXPERIMENTAL_REACT (as
|
|
13852
|
+
* next build defines it). */
|
|
12301
13853
|
function runtimeVariables(root, config) {
|
|
12302
13854
|
let experimental;
|
|
12303
13855
|
try {
|
|
@@ -12312,25 +13864,106 @@ function runtimeVariables(root, config) {
|
|
|
12312
13864
|
...experimental ? {} : { __NEXT_EXPERIMENTAL_REACT: "undefined" }
|
|
12313
13865
|
};
|
|
12314
13866
|
}
|
|
12315
|
-
|
|
13867
|
+
function hasPagesRoutes(root, distDir) {
|
|
13868
|
+
const file = join(root, distDir, "server", "pages-manifest.json");
|
|
13869
|
+
if (!existsSync(file)) return true;
|
|
13870
|
+
return Object.values(JSON.parse(readFileSync(file, "utf8"))).some((f) => f.endsWith(".js"));
|
|
13871
|
+
}
|
|
12316
13872
|
function hasMiddleware(root, distDir) {
|
|
12317
13873
|
const file = join(root, distDir, "server", "middleware-manifest.json");
|
|
12318
13874
|
if (!existsSync(file)) return true;
|
|
12319
13875
|
const { middleware = {}, functions = {} } = JSON.parse(readFileSync(file, "utf8"));
|
|
12320
13876
|
return Object.keys(middleware).length > 0 || Object.keys(functions).length > 0;
|
|
12321
13877
|
}
|
|
12322
|
-
|
|
12323
|
-
|
|
12324
|
-
[
|
|
12325
|
-
|
|
12326
|
-
|
|
12327
|
-
|
|
12328
|
-
|
|
12329
|
-
|
|
12330
|
-
|
|
12331
|
-
|
|
12332
|
-
|
|
13878
|
+
function ignoredModules(root, distDir) {
|
|
13879
|
+
const dev = "Next's dev mode: visaljs builds its production server";
|
|
13880
|
+
return [
|
|
13881
|
+
{
|
|
13882
|
+
module: "next/dist/server/dev/next-dev-server.js",
|
|
13883
|
+
reason: dev
|
|
13884
|
+
},
|
|
13885
|
+
{
|
|
13886
|
+
module: "next/dist/server/lib/router-utils/setup-dev-bundler.js",
|
|
13887
|
+
reason: dev
|
|
13888
|
+
},
|
|
13889
|
+
{
|
|
13890
|
+
module: "next/dist/telemetry/storage.js",
|
|
13891
|
+
reason: dev
|
|
13892
|
+
},
|
|
13893
|
+
{
|
|
13894
|
+
module: "next/dist/client/dev/hot-reloader/app/hot-reloader-app.js",
|
|
13895
|
+
reason: dev
|
|
13896
|
+
},
|
|
13897
|
+
{
|
|
13898
|
+
module: "next/dist/server/next-typescript.js",
|
|
13899
|
+
reason: "Next's TypeScript plugin for editors"
|
|
13900
|
+
},
|
|
13901
|
+
{
|
|
13902
|
+
module: "next/dist/build/next-config-ts/transpile-config.js",
|
|
13903
|
+
stub: "call",
|
|
13904
|
+
reason: "the server gets the config next build saved"
|
|
13905
|
+
},
|
|
13906
|
+
{
|
|
13907
|
+
module: "next/dist/build/swc/index.js",
|
|
13908
|
+
stub: "call",
|
|
13909
|
+
reason: "SWC is a native addon, which visaljs can't load"
|
|
13910
|
+
},
|
|
13911
|
+
...hasPagesRoutes(root, distDir) ? [] : [{
|
|
13912
|
+
module: "next/dist/server/route-modules/pages/module.compiled.js",
|
|
13913
|
+
reason: "the Pages Router's renderer, and this build has no Pages routes"
|
|
13914
|
+
}],
|
|
13915
|
+
...hasMiddleware(root, distDir) ? [] : [{
|
|
13916
|
+
module: "next/dist/server/web/sandbox/index.js",
|
|
13917
|
+
reason: "the edge sandbox runs middleware, which this build has none of"
|
|
13918
|
+
}]
|
|
13919
|
+
];
|
|
13920
|
+
}
|
|
12333
13921
|
const RUNTIME_PICKERS = /[\\/]next[\\/]dist[\\/]server[\\/]route-modules[\\/](?:[\w-]+[\\/]module\.compiled|app-page[\\/]vendored[\\/]\w+[\\/]entrypoints)\.js$/;
|
|
13922
|
+
function constants(root, config) {
|
|
13923
|
+
const testProxy = config.experimental?.testProxy;
|
|
13924
|
+
const runners = testRunners(root);
|
|
13925
|
+
return [
|
|
13926
|
+
...runners ? [{
|
|
13927
|
+
file: /[\\/]next[\\/]dist[\\/]server[\\/]config-schema\.js$/,
|
|
13928
|
+
find: "const _nexttest = require(\"../cli/next-test\");",
|
|
13929
|
+
replace: `const _nexttest = { SUPPORTED_TEST_RUNNERS_LIST: ${runners} };`
|
|
13930
|
+
}] : [],
|
|
13931
|
+
{
|
|
13932
|
+
file: /[\\/]next[\\/]dist[\\/]server[\\/]lib[\\/]start-server\.js$/,
|
|
13933
|
+
find: "const { dir, isDev, hostname,",
|
|
13934
|
+
replace: "const isDev = false, { dir, hostname,"
|
|
13935
|
+
},
|
|
13936
|
+
...testProxy ? [] : [
|
|
13937
|
+
{
|
|
13938
|
+
file: /[\\/]next[\\/]dist[\\/]server[\\/]next-server\.js$/,
|
|
13939
|
+
find: "this.serverOptions.experimentalTestProxy",
|
|
13940
|
+
replace: "false"
|
|
13941
|
+
},
|
|
13942
|
+
{
|
|
13943
|
+
file: /[\\/]next[\\/]dist[\\/]server[\\/]lib[\\/]router-server\.js$/,
|
|
13944
|
+
find: "config.experimental.testProxy",
|
|
13945
|
+
replace: "false"
|
|
13946
|
+
},
|
|
13947
|
+
{
|
|
13948
|
+
file: /[\\/]next[\\/]dist[\\/]server[\\/]web[\\/]adapter\.js$/,
|
|
13949
|
+
find: "process.env.NEXT_PRIVATE_TEST_PROXY === 'true'",
|
|
13950
|
+
replace: "false"
|
|
13951
|
+
}
|
|
13952
|
+
]
|
|
13953
|
+
].map((c) => ({
|
|
13954
|
+
...c,
|
|
13955
|
+
missing: "skip"
|
|
13956
|
+
}));
|
|
13957
|
+
}
|
|
13958
|
+
function testRunners(root) {
|
|
13959
|
+
try {
|
|
13960
|
+
const file = createRequire(join(root, "package.json")).resolve("next/dist/cli/next-test.js");
|
|
13961
|
+
const list = /\bconst SUPPORTED_TEST_RUNNERS_LIST = (\[[\s\w'",]*\]);/.exec(readFileSync(file, "utf8"))?.[1];
|
|
13962
|
+
return list && JSON.stringify(JSON.parse(list.replaceAll("'", "\"").replace(/,\s*\]$/, "]")));
|
|
13963
|
+
} catch {
|
|
13964
|
+
return;
|
|
13965
|
+
}
|
|
13966
|
+
}
|
|
12334
13967
|
const LOADS = [
|
|
12335
13968
|
{
|
|
12336
13969
|
file: /[\\/]next[\\/]dist[\\/]server[\\/]require\.js$/,
|
|
@@ -12348,42 +13981,8 @@ const LOADS = [
|
|
|
12348
13981
|
replace: "globalThis.__visaljs_next.evaluate(path, contextObject, () => (0, _vm.runInNewContext)(content, contextObject))"
|
|
12349
13982
|
}
|
|
12350
13983
|
];
|
|
12351
|
-
|
|
12352
|
-
|
|
12353
|
-
detect: (root) => "next" in dependencies(root) && !("vinext" in dependencies(root)),
|
|
12354
|
-
async build(root) {
|
|
12355
|
-
const bin = packageBin(root, "next", "next");
|
|
12356
|
-
const standalone = join(root, ".next", "standalone");
|
|
12357
|
-
const hadStandalone = existsSync(standalone);
|
|
12358
|
-
try {
|
|
12359
|
-
const env = {
|
|
12360
|
-
...process.env,
|
|
12361
|
-
NEXT_PRIVATE_STANDALONE: "1",
|
|
12362
|
-
NEXT_TELEMETRY_DISABLED: "1",
|
|
12363
|
-
FORCE_COLOR: "0"
|
|
12364
|
-
};
|
|
12365
|
-
await promisify(execFile)(process.execPath, [bin, "build"], {
|
|
12366
|
-
cwd: root,
|
|
12367
|
-
maxBuffer: 64 << 20,
|
|
12368
|
-
env
|
|
12369
|
-
});
|
|
12370
|
-
} catch (e) {
|
|
12371
|
-
const { stdout = "", stderr = "" } = e;
|
|
12372
|
-
throw new FrameworkError(`next build failed in ${root}:\n${`${stdout}${stderr}`.trimEnd().split("\n").slice(-30).join("\n")}`);
|
|
12373
|
-
} finally {
|
|
12374
|
-
if (!hadStandalone) rmSync(standalone, {
|
|
12375
|
-
recursive: true,
|
|
12376
|
-
force: true
|
|
12377
|
-
});
|
|
12378
|
-
}
|
|
12379
|
-
return "Built the app with next build, as a standalone build (.next/)";
|
|
12380
|
-
},
|
|
12381
|
-
entry(root) {
|
|
12382
|
-
const { config, distDir } = serverConfig(root);
|
|
12383
|
-
if (!existsSync(join(root, distDir, "server", "app")) && !existsSync(join(root, distDir, "server", "pages"))) throw new FrameworkError(`${root}'s ${distDir}/server has no app/ or pages/: this Next.js build isn't supported yet`);
|
|
12384
|
-
if (config.output !== "standalone") throw new FrameworkError(`${root}'s ${distDir} isn't a standalone build, whose traces visaljs bundles from: build without --no-framework-build (visaljs runs next build as one, without changing next.config), or set output: "standalone"`);
|
|
12385
|
-
if (!isTurbopackBuild(root, distDir)) throw new FrameworkError(`${root}'s ${distDir} was built with webpack (next build --webpack): visaljs compiles Turbopack builds, next build's default, for now. Build without --webpack`);
|
|
12386
|
-
return `import { modules, manifests } from ${JSON.stringify(TABLE)};
|
|
13984
|
+
function entrySource(config, distDir) {
|
|
13985
|
+
return `import { modules, manifests } from ${JSON.stringify(TABLE)};
|
|
12387
13986
|
const config = ${JSON.stringify(config)};
|
|
12388
13987
|
const key = (path) => {
|
|
12389
13988
|
const p = String(path).replaceAll("\\\\", "/");
|
|
@@ -12418,67 +14017,102 @@ await startServer({
|
|
|
12418
14017
|
process.exit(1);
|
|
12419
14018
|
});
|
|
12420
14019
|
`;
|
|
12421
|
-
|
|
12422
|
-
|
|
12423
|
-
|
|
12424
|
-
|
|
12425
|
-
|
|
12426
|
-
|
|
12427
|
-
|
|
12428
|
-
|
|
12429
|
-
|
|
12430
|
-
|
|
12431
|
-
|
|
12432
|
-
|
|
12433
|
-
|
|
12434
|
-
|
|
12435
|
-
|
|
12436
|
-
|
|
12437
|
-
|
|
12438
|
-
|
|
12439
|
-
|
|
12440
|
-
|
|
12441
|
-
|
|
12442
|
-
|
|
12443
|
-
|
|
12444
|
-
|
|
12445
|
-
|
|
12446
|
-
|
|
12447
|
-
|
|
12448
|
-
|
|
12449
|
-
|
|
12450
|
-
|
|
12451
|
-
|
|
12452
|
-
|
|
12453
|
-
|
|
12454
|
-
|
|
12455
|
-
|
|
12456
|
-
|
|
12457
|
-
|
|
12458
|
-
|
|
12459
|
-
|
|
12460
|
-
|
|
12461
|
-
|
|
12462
|
-
if (!load) return null;
|
|
12463
|
-
if (!code.includes(load.find)) this.error(`${relative(root, id)} no longer loads the build as visaljs expects (${load.find}): this Next.js version isn't supported yet`);
|
|
12464
|
-
return {
|
|
12465
|
-
code: code.replaceAll(load.find, load.replace),
|
|
12466
|
-
map: null
|
|
12467
|
-
};
|
|
12468
|
-
}
|
|
14020
|
+
}
|
|
14021
|
+
function buildModules(root, distDir) {
|
|
14022
|
+
const traced = tracedFiles(root, distDir);
|
|
14023
|
+
const packages = join(root, "node_modules") + sep;
|
|
14024
|
+
return {
|
|
14025
|
+
name: "visaljs-next",
|
|
14026
|
+
async resolveId(source, importer, { kind }) {
|
|
14027
|
+
if (source === TABLE) return source;
|
|
14028
|
+
if (kind !== "require-call" || source.startsWith("\0") || !importer?.startsWith(packages)) return null;
|
|
14029
|
+
const resolved = await this.resolve(source, importer, { skipSelf: true });
|
|
14030
|
+
if (!resolved || resolved.external || !resolved.id.startsWith(packages) || traced.has(resolved.id)) return resolved;
|
|
14031
|
+
return UNTRACED + source;
|
|
14032
|
+
},
|
|
14033
|
+
load(id) {
|
|
14034
|
+
if (id === TABLE) return tableModule(root, distDir);
|
|
14035
|
+
if (!id.startsWith(UNTRACED)) return null;
|
|
14036
|
+
const message = `Cannot find module '${id.slice(23)}' (next build didn't trace it as the server's)`;
|
|
14037
|
+
return `throw Object.assign(new Error(${JSON.stringify(message)}), { code: "MODULE_NOT_FOUND" });\n`;
|
|
14038
|
+
}
|
|
14039
|
+
};
|
|
14040
|
+
}
|
|
14041
|
+
const next = {
|
|
14042
|
+
id: "next",
|
|
14043
|
+
name: "Next.js",
|
|
14044
|
+
provides: "entry",
|
|
14045
|
+
detect: (project) => project.packages.has("next"),
|
|
14046
|
+
hooks: {
|
|
14047
|
+
async frameworkBuild(ctx) {
|
|
14048
|
+
const bin = packageBin(ctx.root, "next", "next");
|
|
14049
|
+
const standalone = join(ctx.root, ".next", "standalone");
|
|
14050
|
+
const hadStandalone = existsSync(standalone);
|
|
14051
|
+
try {
|
|
14052
|
+
await ctx.run([bin, "build"], { env: {
|
|
14053
|
+
NEXT_PRIVATE_STANDALONE: "1",
|
|
14054
|
+
NEXT_TELEMETRY_DISABLED: "1"
|
|
14055
|
+
} });
|
|
14056
|
+
} finally {
|
|
14057
|
+
if (!hadStandalone) rmSync(standalone, {
|
|
14058
|
+
recursive: true,
|
|
14059
|
+
force: true
|
|
14060
|
+
});
|
|
12469
14061
|
}
|
|
12470
|
-
|
|
12471
|
-
|
|
12472
|
-
|
|
12473
|
-
|
|
12474
|
-
|
|
12475
|
-
|
|
12476
|
-
|
|
12477
|
-
|
|
14062
|
+
return "Built the app with next build, as a standalone build (.next/)";
|
|
14063
|
+
},
|
|
14064
|
+
prepare(ctx) {
|
|
14065
|
+
const { root } = ctx;
|
|
14066
|
+
const { config, distDir } = serverConfig(root);
|
|
14067
|
+
if (!existsSync(join(root, distDir, "server", "app")) && !existsSync(join(root, distDir, "server", "pages"))) throw new IntegrationError(`${root}'s ${distDir}/server has no app/ or pages/: this Next.js build isn't supported yet`);
|
|
14068
|
+
if (config.output !== "standalone") throw new IntegrationError(`${root}'s ${distDir} isn't a standalone build, whose traces visaljs bundles from: build without --no-framework-build (visaljs runs next build as one, without changing next.config), or set output: "standalone"`);
|
|
14069
|
+
if (!isTurbopackBuild(root, distDir)) throw new IntegrationError(`${root}'s ${distDir} was built with webpack (next build --webpack): visaljs compiles Turbopack builds, next build's default, for now. Build without --webpack`);
|
|
14070
|
+
ctx.entrySource = entrySource(config, distDir);
|
|
14071
|
+
return {
|
|
14072
|
+
config,
|
|
14073
|
+
distDir
|
|
14074
|
+
};
|
|
14075
|
+
},
|
|
14076
|
+
bundle(ctx, { config, distDir }) {
|
|
14077
|
+
for (const m of ignoredModules(ctx.root, distDir)) ctx.ignore(m);
|
|
14078
|
+
const variables = runtimeVariables(ctx.root, config);
|
|
14079
|
+
const read = new RegExp(`\\bprocess\\.env\\.(${Object.keys(variables).join("|")})\\b(?!\\s*(?:=[^=]|\\?\\?=|\\|\\|=|&&=))`, "g");
|
|
14080
|
+
ctx.rewrite({
|
|
14081
|
+
file: RUNTIME_PICKERS,
|
|
14082
|
+
find: read,
|
|
14083
|
+
replace: (_, name) => variables[name],
|
|
14084
|
+
missing: "skip"
|
|
14085
|
+
});
|
|
14086
|
+
for (const r of [...constants(ctx.root, config), ...LOADS]) ctx.rewrite(r);
|
|
14087
|
+
ctx.plugin(buildModules(ctx.root, distDir));
|
|
14088
|
+
},
|
|
14089
|
+
done(ctx, { distDir }) {
|
|
14090
|
+
const { root, output } = ctx;
|
|
14091
|
+
const inside = relative(root, output);
|
|
14092
|
+
const bin = !inside.startsWith("..") && !isAbsolute(inside) ? `./${inside.replaceAll("\\", "/")}` : output;
|
|
14093
|
+
const project = relative(process.cwd(), root);
|
|
14094
|
+
const cd = project && project !== "." ? `cd ${project.startsWith("..") ? root : project} && ` : "";
|
|
14095
|
+
ctx.note(`Run it in the project: it serves ${distDir}/ and public/ from the working directory, as \`next start\` does: ${cd}PORT=3000 ${bin}`);
|
|
14096
|
+
ctx.note(`To deploy it, copy ${basename(output)}, ${distDir}/ (without ${distDir}/cache) and public/ to the server, and run it there the same way`);
|
|
14097
|
+
}
|
|
12478
14098
|
}
|
|
12479
14099
|
};
|
|
12480
14100
|
//#endregion
|
|
12481
|
-
//#region cli/src/
|
|
14101
|
+
//#region cli/src/integrations/sharp.ts
|
|
14102
|
+
const sharp = {
|
|
14103
|
+
id: "sharp",
|
|
14104
|
+
name: "sharp",
|
|
14105
|
+
detect: (project) => project.installed("sharp"),
|
|
14106
|
+
hooks: { bundle(ctx) {
|
|
14107
|
+
ctx.ignore({
|
|
14108
|
+
module: "sharp",
|
|
14109
|
+
reason: "sharp is a native addon, which visaljs can't load",
|
|
14110
|
+
code: "ERR_DLOPEN_FAILED"
|
|
14111
|
+
});
|
|
14112
|
+
} }
|
|
14113
|
+
};
|
|
14114
|
+
//#endregion
|
|
14115
|
+
//#region cli/src/integrations/vinext.ts
|
|
12482
14116
|
/** The Vite config file and mode the project's build script gives `vite build` (-c, -m), if any. */
|
|
12483
14117
|
function buildScriptOptions(root) {
|
|
12484
14118
|
const words = packageJson(root).scripts?.build?.split(/\s+/) ?? [];
|
|
@@ -12498,7 +14132,6 @@ const WORKERS_FILES = [
|
|
|
12498
14132
|
"wrangler.toml",
|
|
12499
14133
|
"cloudflare.config.ts"
|
|
12500
14134
|
];
|
|
12501
|
-
/** Whether the project is made for Cloudflare Workers: its own build makes a Worker. */
|
|
12502
14135
|
function isWorkersProject(root) {
|
|
12503
14136
|
return WORKERS_FILES.some((f) => existsSync(join(root, f))) || "@cloudflare/vite-plugin" in dependencies(root);
|
|
12504
14137
|
}
|
|
@@ -12520,81 +14153,262 @@ const flat = async (plugins) => (await Promise.all((plugins ?? []).map(async (p)
|
|
|
12520
14153
|
const plugins = (await flat(loaded?.config.plugins)).filter((p) => !p.name?.startsWith("vite-plugin-cloudflare"));
|
|
12521
14154
|
export default { ...loaded?.config, root, plugins };
|
|
12522
14155
|
`;
|
|
14156
|
+
const BUILD_IMPORT = {
|
|
14157
|
+
file: /[\\/]vinext[\\/]dist[\\/]server[\\/]prod-server\.js$/,
|
|
14158
|
+
find: "import(resolveServerEntryImportUrl(entryPath))"
|
|
14159
|
+
};
|
|
12523
14160
|
const MARKER = join("dist", "server", "vinext-server.json");
|
|
12524
14161
|
const BUILD_ENTRIES = ["dist/server/index.js", "dist/server/entry.js"];
|
|
12525
14162
|
//#endregion
|
|
12526
|
-
//#region cli/src/
|
|
12527
|
-
const
|
|
12528
|
-
|
|
12529
|
-
|
|
12530
|
-
|
|
12531
|
-
|
|
12532
|
-
|
|
12533
|
-
|
|
12534
|
-
|
|
12535
|
-
|
|
12536
|
-
|
|
12537
|
-
|
|
12538
|
-
|
|
14163
|
+
//#region cli/src/integrations/index.ts
|
|
14164
|
+
const INTEGRATIONS = [
|
|
14165
|
+
{
|
|
14166
|
+
id: "vinext",
|
|
14167
|
+
name: "vinext",
|
|
14168
|
+
provides: "entry",
|
|
14169
|
+
overrides: ["next"],
|
|
14170
|
+
detect: (project) => project.packages.has("vinext"),
|
|
14171
|
+
hooks: {
|
|
14172
|
+
async frameworkBuild(ctx) {
|
|
14173
|
+
const { root } = ctx;
|
|
14174
|
+
const vite = packageBin(root, "vite", "vite");
|
|
14175
|
+
const { config, mode } = buildScriptOptions(root);
|
|
14176
|
+
const workers = isWorkersProject(root);
|
|
14177
|
+
const args = [vite, "build"];
|
|
14178
|
+
if (mode) args.push("--mode", mode);
|
|
14179
|
+
const env = {};
|
|
14180
|
+
const wrapper = join(root, "node_modules", ".visaljs", "vite.config.mjs");
|
|
14181
|
+
if (workers) {
|
|
14182
|
+
mkdirSync(dirname(wrapper), { recursive: true });
|
|
14183
|
+
writeFileSync(wrapper, WORKERS_CONFIG);
|
|
14184
|
+
args.push("--config", wrapper);
|
|
14185
|
+
if (config) env.VISALJS_VITE_CONFIG = join(root, config);
|
|
14186
|
+
if (mode) env.VISALJS_VITE_MODE = mode;
|
|
14187
|
+
} else if (config) args.push("--config", config);
|
|
14188
|
+
try {
|
|
14189
|
+
await ctx.run(args, { env });
|
|
14190
|
+
} catch (e) {
|
|
14191
|
+
if (!(e instanceof CommandError)) throw e;
|
|
14192
|
+
const cloudflare = /failed to resolve import "(cloudflare:[\w-]+)" from "([^"]+)"/.exec(e.output);
|
|
14193
|
+
if (workers && cloudflare) throw new IntegrationError(`${relative(root, cloudflare[2])} imports ${cloudflare[1]}: Cloudflare Workers' own APIs (bindings: env, KV, D1, …) have no Node equivalent, so visaljs can't build this app for Node`);
|
|
14194
|
+
throw e;
|
|
14195
|
+
} finally {
|
|
14196
|
+
if (workers) rmSync(dirname(wrapper), {
|
|
14197
|
+
recursive: true,
|
|
14198
|
+
force: true
|
|
14199
|
+
});
|
|
14200
|
+
}
|
|
14201
|
+
return workers ? "Built the app for Node with vite build, without its Cloudflare plugin (dist/)" : "Built the app with vite build (dist/)";
|
|
14202
|
+
},
|
|
14203
|
+
prepare(ctx) {
|
|
14204
|
+
const { root } = ctx;
|
|
14205
|
+
if (!existsSync(join(root, MARKER))) throw new IntegrationError(`${root} has no vinext build (no ${MARKER}): run \`vite build\`, or build without --no-framework-build`);
|
|
14206
|
+
if (!BUILD_ENTRIES.some((f) => existsSync(join(root, f)))) throw new IntegrationError(isWorkersProject(root) ? `${root}'s dist/ is a Cloudflare Workers build, which has no Node server: build without --no-framework-build, and visaljs builds the app for Node` : `${root}'s vinext build has no Node server (${BUILD_ENTRIES.join(", ")}): this vinext setup isn't supported yet`);
|
|
14207
|
+
ctx.entrySource = "import { startProdServer } from \"vinext/server/prod-server\";\nawait startProdServer();\n";
|
|
14208
|
+
},
|
|
14209
|
+
bundle(ctx) {
|
|
14210
|
+
const { root } = ctx;
|
|
14211
|
+
const choose = BUILD_ENTRIES.filter((f) => existsSync(join(root, f))).map((f) => `entryPath.replaceAll("\\\\", "/").endsWith(${JSON.stringify("/" + f)}) ? import(${JSON.stringify(join(root, f))}) : `).join("");
|
|
14212
|
+
ctx.rewrite({
|
|
14213
|
+
...BUILD_IMPORT,
|
|
14214
|
+
replace: `(${choose}Promise.reject(new Error("visaljs: the build has no " + entryPath)))`
|
|
14215
|
+
});
|
|
14216
|
+
const externals = join(root, "dist", "server", "vinext-externals.json");
|
|
14217
|
+
if (existsSync(externals)) for (const name of JSON.parse(readFileSync(externals, "utf8"))) ctx.require(name);
|
|
14218
|
+
},
|
|
14219
|
+
done(ctx) {
|
|
14220
|
+
ctx.note("It serves ./dist under the working directory, as `vinext start` does: run it from the project");
|
|
14221
|
+
if (isWorkersProject(ctx.root)) ctx.note("Built for Node, not Workers: the project's own build still makes its Worker");
|
|
14222
|
+
}
|
|
14223
|
+
}
|
|
14224
|
+
},
|
|
14225
|
+
next,
|
|
14226
|
+
sharp
|
|
14227
|
+
];
|
|
14228
|
+
/** The entry when the project uses no framework. */
|
|
14229
|
+
const FALLBACK = project;
|
|
14230
|
+
//#endregion
|
|
14231
|
+
//#region cli/src/pipeline/index.ts
|
|
14232
|
+
/** The stages before bundling, for the project in `root` (absolute). `output` is -o's. */
|
|
14233
|
+
async function runPipeline(root, options) {
|
|
14234
|
+
const { flags, report } = options;
|
|
14235
|
+
const project = {
|
|
14236
|
+
root,
|
|
14237
|
+
pkg: packageJson(root),
|
|
14238
|
+
packages: new Set(Object.keys(dependencies(root))),
|
|
14239
|
+
installed: (name) => installed(root, name),
|
|
14240
|
+
flags
|
|
14241
|
+
};
|
|
14242
|
+
const detected = (options.integrations ?? INTEGRATIONS).filter((i) => i.detect(project));
|
|
14243
|
+
const replaced = new Set(detected.flatMap((i) => i.overrides ?? []));
|
|
14244
|
+
const kept = detected.filter((i) => !replaced.has(i.id));
|
|
14245
|
+
const frameworks = kept.filter((i) => i.provides === "entry");
|
|
14246
|
+
if (frameworks.length > 1) throw new IntegrationError(`the project uses ${frameworks.map((f) => f.name).join(" and ")}: visaljs builds one framework's server`);
|
|
14247
|
+
const framework = frameworks[0] ?? options.fallback ?? FALLBACK;
|
|
14248
|
+
if (frameworks.length) report.info(` 🧭 A ${framework.name} app: compiling its production server`);
|
|
14249
|
+
const used = [framework, ...kept.filter((i) => i !== framework)];
|
|
14250
|
+
const open = (fields) => {
|
|
14251
|
+
let closed = false;
|
|
14252
|
+
const guard = () => {
|
|
14253
|
+
if (closed) throw new Error("an integration's context was used after its stage was over");
|
|
12539
14254
|
};
|
|
12540
|
-
const
|
|
12541
|
-
|
|
12542
|
-
|
|
12543
|
-
|
|
12544
|
-
|
|
12545
|
-
|
|
12546
|
-
|
|
12547
|
-
|
|
12548
|
-
|
|
12549
|
-
|
|
12550
|
-
|
|
12551
|
-
|
|
12552
|
-
|
|
12553
|
-
|
|
12554
|
-
}
|
|
12555
|
-
|
|
12556
|
-
|
|
12557
|
-
|
|
12558
|
-
|
|
12559
|
-
}
|
|
12560
|
-
|
|
12561
|
-
|
|
12562
|
-
|
|
12563
|
-
|
|
14255
|
+
const context = {
|
|
14256
|
+
...project,
|
|
14257
|
+
note(text, icon) {
|
|
14258
|
+
guard();
|
|
14259
|
+
report.info(icon ? ` ${icon} ${text}` : ` • ${text}`);
|
|
14260
|
+
},
|
|
14261
|
+
warn(message) {
|
|
14262
|
+
guard();
|
|
14263
|
+
report.warn(message);
|
|
14264
|
+
}
|
|
14265
|
+
};
|
|
14266
|
+
return {
|
|
14267
|
+
ctx: Object.defineProperties(context, Object.getOwnPropertyDescriptors(fields(guard))),
|
|
14268
|
+
close: () => closed = true
|
|
14269
|
+
};
|
|
14270
|
+
};
|
|
14271
|
+
if (!flags.skipFrameworkBuild) for (const i of used) {
|
|
14272
|
+
const hook = i.hooks.frameworkBuild;
|
|
14273
|
+
if (!hook) continue;
|
|
14274
|
+
const { ctx, close } = open((guard) => ({ async run(args, { env } = {}) {
|
|
14275
|
+
guard();
|
|
14276
|
+
try {
|
|
14277
|
+
return await promisify(execFile)(process.execPath, args, {
|
|
14278
|
+
cwd: root,
|
|
14279
|
+
maxBuffer: 64 << 20,
|
|
14280
|
+
env: {
|
|
14281
|
+
...process.env,
|
|
14282
|
+
FORCE_COLOR: "0",
|
|
14283
|
+
...env
|
|
14284
|
+
}
|
|
14285
|
+
});
|
|
14286
|
+
} catch (e) {
|
|
14287
|
+
const { stdout = "", stderr = "" } = e;
|
|
14288
|
+
const output = `${stdout}${stderr}`;
|
|
14289
|
+
throw new CommandError(`${[basename(args[0] ?? "").replace(/\.[cm]?js$/, ""), args[1]].filter(Boolean).join(" ")} failed in ${root}:\n${output.trimEnd().split("\n").slice(-30).join("\n")}`, output);
|
|
14290
|
+
}
|
|
14291
|
+
} }));
|
|
14292
|
+
await report.step("🏗️ ", `Building the ${i.name} app`, () => hook(ctx), (what) => what || `Built the ${i.name} app`);
|
|
14293
|
+
close();
|
|
14294
|
+
}
|
|
14295
|
+
const decided = {};
|
|
14296
|
+
const states = /* @__PURE__ */ new Map();
|
|
14297
|
+
for (const i of used) {
|
|
14298
|
+
const hook = i.hooks.prepare;
|
|
14299
|
+
if (!hook) continue;
|
|
14300
|
+
const decide = (guard) => {
|
|
14301
|
+
guard();
|
|
14302
|
+
if (i !== framework) throw new Error(`${i.name} sets the entry or the output, which only the integration that provides the entry sets`);
|
|
14303
|
+
};
|
|
14304
|
+
const { ctx, close } = open((guard) => ({
|
|
14305
|
+
get entryFile() {
|
|
14306
|
+
return decided.entryFile;
|
|
14307
|
+
},
|
|
14308
|
+
set entryFile(file) {
|
|
14309
|
+
decide(guard);
|
|
14310
|
+
decided.entryFile = file;
|
|
14311
|
+
},
|
|
14312
|
+
get entrySource() {
|
|
14313
|
+
return decided.entrySource;
|
|
14314
|
+
},
|
|
14315
|
+
set entrySource(source) {
|
|
14316
|
+
decide(guard);
|
|
14317
|
+
decided.entrySource = source;
|
|
14318
|
+
},
|
|
14319
|
+
get output() {
|
|
14320
|
+
return decided.output;
|
|
14321
|
+
},
|
|
14322
|
+
set output(file) {
|
|
14323
|
+
decide(guard);
|
|
14324
|
+
decided.output = file;
|
|
14325
|
+
}
|
|
14326
|
+
}));
|
|
14327
|
+
states.set(i, await hook(ctx));
|
|
14328
|
+
close();
|
|
14329
|
+
}
|
|
14330
|
+
const entry = decided.entryFile ?? join(root, "visaljs-entry.js");
|
|
14331
|
+
const output = options.output ?? decided.output ?? join(root, basename(root));
|
|
14332
|
+
const rewrites = [];
|
|
14333
|
+
const plugins = [];
|
|
14334
|
+
const requires = [];
|
|
14335
|
+
const ignored = [];
|
|
14336
|
+
for (const i of used) {
|
|
14337
|
+
const hook = i.hooks.bundle;
|
|
14338
|
+
if (!hook) continue;
|
|
14339
|
+
const { ctx, close } = open((guard) => ({
|
|
14340
|
+
entry,
|
|
14341
|
+
output,
|
|
14342
|
+
rewrite(r) {
|
|
14343
|
+
guard();
|
|
14344
|
+
rewrites.push({
|
|
14345
|
+
...r,
|
|
14346
|
+
by: i.name
|
|
14347
|
+
});
|
|
14348
|
+
},
|
|
14349
|
+
plugin(p) {
|
|
14350
|
+
guard();
|
|
14351
|
+
plugins.push(p);
|
|
14352
|
+
},
|
|
14353
|
+
require(name) {
|
|
14354
|
+
guard();
|
|
14355
|
+
requires.push(name);
|
|
14356
|
+
},
|
|
14357
|
+
ignore(m) {
|
|
14358
|
+
guard();
|
|
14359
|
+
ignored.push(m);
|
|
14360
|
+
}
|
|
14361
|
+
}));
|
|
14362
|
+
await hook(ctx, states.get(i));
|
|
14363
|
+
close();
|
|
14364
|
+
}
|
|
14365
|
+
return {
|
|
14366
|
+
entry,
|
|
14367
|
+
output,
|
|
14368
|
+
extras: {
|
|
14369
|
+
...decided.entrySource !== void 0 && { source: decided.entrySource },
|
|
14370
|
+
plugins: [...rewritePlugin(root, rewrites), ...plugins],
|
|
14371
|
+
requires,
|
|
14372
|
+
ignored
|
|
14373
|
+
},
|
|
14374
|
+
done(binary) {
|
|
14375
|
+
for (const i of used) {
|
|
14376
|
+
const hook = i.hooks.done;
|
|
14377
|
+
if (!hook) continue;
|
|
14378
|
+
const { ctx, close } = open(() => ({
|
|
14379
|
+
entry,
|
|
14380
|
+
output: binary
|
|
14381
|
+
}));
|
|
14382
|
+
hook(ctx, states.get(i));
|
|
14383
|
+
close();
|
|
14384
|
+
}
|
|
12564
14385
|
}
|
|
12565
|
-
|
|
12566
|
-
|
|
12567
|
-
|
|
12568
|
-
|
|
12569
|
-
|
|
12570
|
-
|
|
12571
|
-
|
|
12572
|
-
|
|
12573
|
-
|
|
12574
|
-
|
|
12575
|
-
|
|
12576
|
-
|
|
12577
|
-
|
|
12578
|
-
|
|
12579
|
-
|
|
12580
|
-
|
|
12581
|
-
|
|
12582
|
-
|
|
12583
|
-
map: null
|
|
12584
|
-
};
|
|
14386
|
+
};
|
|
14387
|
+
}
|
|
14388
|
+
function rewritePlugin(root, rewrites) {
|
|
14389
|
+
if (!rewrites.length) return [];
|
|
14390
|
+
return [{
|
|
14391
|
+
name: "visaljs-rewrites",
|
|
14392
|
+
transform: {
|
|
14393
|
+
filter: { id: rewrites.map((r) => r.file) },
|
|
14394
|
+
handler(code, id) {
|
|
14395
|
+
let text = code;
|
|
14396
|
+
for (const r of rewrites) {
|
|
14397
|
+
if (!r.file.test(id)) continue;
|
|
14398
|
+
if (typeof r.find === "string" ? !text.includes(r.find) : text.search(r.find) < 0) {
|
|
14399
|
+
if (r.missing === "skip") continue;
|
|
14400
|
+
this.error(`${relative(root, id)} no longer has the code visaljs rewrites (${String(r.find)}): this ${r.by} version isn't supported yet`);
|
|
14401
|
+
}
|
|
14402
|
+
const replace = typeof r.replace === "string" ? () => r.replace : r.replace;
|
|
14403
|
+
text = typeof r.find === "string" ? text.replaceAll(r.find, replace) : text.replace(r.find, replace);
|
|
12585
14404
|
}
|
|
14405
|
+
return text === code ? null : {
|
|
14406
|
+
code: text,
|
|
14407
|
+
map: null
|
|
14408
|
+
};
|
|
12586
14409
|
}
|
|
12587
|
-
}
|
|
12588
|
-
}
|
|
12589
|
-
requires(root) {
|
|
12590
|
-
const file = join(root, "dist", "server", "vinext-externals.json");
|
|
12591
|
-
return existsSync(file) ? JSON.parse(readFileSync(file, "utf8")) : [];
|
|
12592
|
-
},
|
|
12593
|
-
notes: (root) => ["It serves ./dist under the working directory, as `vinext start` does: run it from the project", ...isWorkersProject(root) ? ["Built for Node, not Workers: the project's own build still makes its Worker"] : []]
|
|
12594
|
-
}, next];
|
|
12595
|
-
/** The framework of the project in `root`, or undefined. */
|
|
12596
|
-
function detectFramework(root) {
|
|
12597
|
-
return FRAMEWORKS.find((f) => f.detect(root));
|
|
14410
|
+
}
|
|
14411
|
+
}];
|
|
12598
14412
|
}
|
|
12599
14413
|
//#endregion
|
|
12600
14414
|
//#region cli/src/regexp.ts
|
|
@@ -12708,6 +14522,10 @@ var Report = class {
|
|
|
12708
14522
|
this.#write(`${text}\n`);
|
|
12709
14523
|
if (this.#live && this.#running) this.#draw();
|
|
12710
14524
|
}
|
|
14525
|
+
/** A line of the report, as `note`, but only when reporting (not --quiet). */
|
|
14526
|
+
info(text) {
|
|
14527
|
+
if (this.#style) this.note(text);
|
|
14528
|
+
}
|
|
12711
14529
|
/** A warning, after its location (`where`, if any): printed even when quiet, its label in yellow. */
|
|
12712
14530
|
warn(message, where = "") {
|
|
12713
14531
|
this.note(`${where}${this.#paint("1;33", "warning:")} ${message}`);
|
|
@@ -12763,7 +14581,6 @@ function lineCount(text) {
|
|
|
12763
14581
|
const VERSION = "0.0.7";
|
|
12764
14582
|
/** A program's C splits into files of about this many IR instructions. */
|
|
12765
14583
|
const PART_SIZE = 2e4;
|
|
12766
|
-
/** A TypeScript file, by its extension: the bundler strips its types. */
|
|
12767
14584
|
const TYPESCRIPT = /\.(?:[cm]?ts|tsx)$/;
|
|
12768
14585
|
const USAGE = `usage: visaljs build [entry.js | entry.ts | project directory] [options]
|
|
12769
14586
|
visaljs licenses print the licenses to ship with a binary you distribute
|
|
@@ -12785,6 +14602,7 @@ options:
|
|
|
12785
14602
|
--debug build without optimization (-O0), keeping symbols and debug info
|
|
12786
14603
|
--symbols keep symbols and debug info in the optimized build (for profilers such as perf)
|
|
12787
14604
|
--no-optimize skip the IR optimizer (to compare, or to rule it out)
|
|
14605
|
+
--no-static compile "use static" files as plain TypeScript (to compare)
|
|
12788
14606
|
-j, --jobs <n> compile at most n C files at once (default: one per processor, memory permitting)
|
|
12789
14607
|
--embed-source embed the source text: Function.prototype.toString returns it
|
|
12790
14608
|
--all-builtins install every built-in, not only those the program names
|
|
@@ -12818,6 +14636,7 @@ async function main(argv, io) {
|
|
|
12818
14636
|
debug: { type: "boolean" },
|
|
12819
14637
|
symbols: { type: "boolean" },
|
|
12820
14638
|
"no-optimize": { type: "boolean" },
|
|
14639
|
+
"no-static": { type: "boolean" },
|
|
12821
14640
|
jobs: {
|
|
12822
14641
|
type: "string",
|
|
12823
14642
|
short: "j"
|
|
@@ -12875,33 +14694,30 @@ async function main(argv, io) {
|
|
|
12875
14694
|
}
|
|
12876
14695
|
}
|
|
12877
14696
|
async function build(entry, options, io) {
|
|
12878
|
-
|
|
12879
|
-
|
|
12880
|
-
|
|
14697
|
+
const dump = options["dump-ast"] === true || options["dump-ir"] === true;
|
|
14698
|
+
const style = options.quiet || dump ? void 0 : io.report;
|
|
14699
|
+
const report = new Report(io.stderr, style);
|
|
14700
|
+
const binary = !dump && !options["emit-c"];
|
|
14701
|
+
const hosts = DEFAULT_HOSTS;
|
|
14702
|
+
let primitives = hostPrimitives(hosts);
|
|
14703
|
+
const nodeEnv = options["node-env"] === "" ? void 0 : options["node-env"] ?? "production";
|
|
14704
|
+
let pipeline;
|
|
12881
14705
|
if (statSync(entry, { throwIfNoEntry: false })?.isDirectory()) {
|
|
12882
|
-
|
|
12883
|
-
|
|
12884
|
-
|
|
12885
|
-
|
|
12886
|
-
|
|
12887
|
-
|
|
12888
|
-
|
|
12889
|
-
|
|
12890
|
-
|
|
12891
|
-
|
|
12892
|
-
project = {
|
|
12893
|
-
...projectEntry(root),
|
|
12894
|
-
root
|
|
12895
|
-
};
|
|
12896
|
-
} catch (e) {
|
|
12897
|
-
if (!(e instanceof EntryError)) throw e;
|
|
12898
|
-
io.stderr(`visaljs: ${entry === "." ? "the current directory" : entry}: ${e.message}: give an entry file\n`);
|
|
12899
|
-
return 1;
|
|
12900
|
-
}
|
|
12901
|
-
entry = project.file;
|
|
14706
|
+
try {
|
|
14707
|
+
pipeline = await runPipeline(resolve(entry), {
|
|
14708
|
+
flags: { skipFrameworkBuild: options["no-framework-build"] === true },
|
|
14709
|
+
output: options.output,
|
|
14710
|
+
report
|
|
14711
|
+
});
|
|
14712
|
+
} catch (e) {
|
|
14713
|
+
if (!(e instanceof IntegrationError)) throw e;
|
|
14714
|
+
io.stderr(e instanceof EntryError ? `visaljs: ${entry === "." ? "the current directory" : entry}: ${e.message}: give an entry file\n` : `visaljs: ${e.message}\n`);
|
|
14715
|
+
return 1;
|
|
12902
14716
|
}
|
|
14717
|
+
entry = pipeline.entry;
|
|
12903
14718
|
}
|
|
12904
|
-
|
|
14719
|
+
let source = pipeline?.extras.source;
|
|
14720
|
+
if (source === void 0) try {
|
|
12905
14721
|
source = readFileSync(entry, "utf8");
|
|
12906
14722
|
} catch (e) {
|
|
12907
14723
|
io.stderr(`visaljs: cannot read ${entry}: ${e.message}\n`);
|
|
@@ -12911,35 +14727,6 @@ async function build(entry, options, io) {
|
|
|
12911
14727
|
io.stderr(`visaljs: ${entry} is TypeScript: it's compiled through the bundler, without ${options.script ? "--script" : "--no-bundle"}\n`);
|
|
12912
14728
|
return 1;
|
|
12913
14729
|
}
|
|
12914
|
-
const dump = options["dump-ast"] === true || options["dump-ir"] === true;
|
|
12915
|
-
const style = options.quiet || dump ? void 0 : io.report;
|
|
12916
|
-
const report = new Report(io.stderr, style);
|
|
12917
|
-
const binary = !dump && !options["emit-c"];
|
|
12918
|
-
const hosts = DEFAULT_HOSTS;
|
|
12919
|
-
const primitives = hostPrimitives(hosts);
|
|
12920
|
-
let frameworkExtras = {};
|
|
12921
|
-
const nodeEnv = options["node-env"] === "" ? void 0 : options["node-env"] ?? "production";
|
|
12922
|
-
if (project && style) {
|
|
12923
|
-
report.note(` 📄 Entry: ${relative(project.root, project.file).replaceAll("\\", "/")} (${project.from})`);
|
|
12924
|
-
if (project.ignoredArgs.length) report.note(` • its arguments (${project.ignoredArgs.join(" ")}) aren't built in: give them when you run the binary`);
|
|
12925
|
-
}
|
|
12926
|
-
if (framework) {
|
|
12927
|
-
const { fw, root } = framework;
|
|
12928
|
-
if (style) report.note(` 🧭 A ${fw.name} app: compiling its production server`);
|
|
12929
|
-
try {
|
|
12930
|
-
if (fw.build && options["no-framework-build"] !== true) await report.step("🏗️ ", `Building the ${fw.name} app`, () => fw.build(root), (what) => what);
|
|
12931
|
-
source = fw.entry(root);
|
|
12932
|
-
} catch (e) {
|
|
12933
|
-
if (!(e instanceof FrameworkError)) throw e;
|
|
12934
|
-
io.stderr(`visaljs: ${e.message}\n`);
|
|
12935
|
-
return 1;
|
|
12936
|
-
}
|
|
12937
|
-
frameworkExtras = {
|
|
12938
|
-
source,
|
|
12939
|
-
plugins: fw.plugins?.(root) ?? [],
|
|
12940
|
-
requires: fw.requires?.(root) ?? []
|
|
12941
|
-
};
|
|
12942
|
-
}
|
|
12943
14730
|
let compilers;
|
|
12944
14731
|
if (binary) try {
|
|
12945
14732
|
compilers = await report.step("🔍", "Detecting C compilers", findCompilers, detected);
|
|
@@ -12947,122 +14734,295 @@ async function build(entry, options, io) {
|
|
|
12947
14734
|
io.stderr(`visaljs: ${e.message}\n`);
|
|
12948
14735
|
return 1;
|
|
12949
14736
|
}
|
|
12950
|
-
const
|
|
12951
|
-
const bundled = !script && options["no-bundle"] !== true;
|
|
14737
|
+
const bundled = !(options.script === true) && options["no-bundle"] !== true;
|
|
12952
14738
|
let map;
|
|
14739
|
+
const typedFiles = /* @__PURE__ */ new Set();
|
|
14740
|
+
let typed;
|
|
12953
14741
|
if (bundled) try {
|
|
12954
14742
|
const b = await report.step("📦", "Rolling the modules into a single file", () => bundle(entry, hosts, (message, where) => report.warn(message, where), {
|
|
12955
|
-
...
|
|
14743
|
+
...pipeline?.extras,
|
|
12956
14744
|
nodeEnv,
|
|
12957
|
-
allFeatures: options["all-features"] === true
|
|
14745
|
+
allFeatures: options["all-features"] === true,
|
|
14746
|
+
plugins: [...pipeline?.extras.plugins ?? [], ...options["no-static"] ? [] : [staticImports(typedFiles)]]
|
|
12958
14747
|
}), (b) => `Rolled ${count(b.modules, "module")} into a single file (${size(Buffer.byteLength(b.code))})` + (b.required.length ? `; for a require() at run time: ${b.required.join(", ")}` : ""));
|
|
12959
14748
|
source = b.code;
|
|
12960
|
-
if (b.map) map =
|
|
14749
|
+
if (b.map) map = b.map;
|
|
14750
|
+
if (typedFiles.size) {
|
|
14751
|
+
const files = [...typedFiles];
|
|
14752
|
+
typed = await report.step("🧷", "Compiling the typed files", () => buildTypedUnit(files, primitives), (u) => [`Compiled ${count(files.length, "typed file")}: ${count(Object.keys(u.exports).length, "export")}`, ...formatReport(u.report).map((line) => ` • ${line}`)].join("\n"));
|
|
14753
|
+
primitives = {
|
|
14754
|
+
...primitives,
|
|
14755
|
+
...typed.exports
|
|
14756
|
+
};
|
|
14757
|
+
}
|
|
12961
14758
|
} catch (e) {
|
|
12962
14759
|
if (!(e instanceof BundleError)) throw e;
|
|
12963
14760
|
io.stderr(`${e.message}\n`);
|
|
12964
14761
|
return 1;
|
|
12965
14762
|
}
|
|
12966
|
-
|
|
12967
|
-
if (!Number.isInteger(jobs) || jobs < 1) {
|
|
14763
|
+
if (options.jobs !== void 0 && (!Number.isInteger(Number(options.jobs)) || Number(options.jobs) < 1)) {
|
|
12968
14764
|
io.stderr(`visaljs: --jobs takes a positive whole number, not '${options.jobs}'\n`);
|
|
12969
14765
|
return 2;
|
|
12970
14766
|
}
|
|
12971
|
-
|
|
14767
|
+
const emitDir = options["emit-c"];
|
|
14768
|
+
let output = "";
|
|
14769
|
+
if (binary) {
|
|
14770
|
+
output = pipeline?.output ?? options.output ?? basename(entry).replace(/\.(?:[cm]?[jt]s|[jt]sx)$/, "");
|
|
14771
|
+
if (process.platform === "win32" && !extname(output)) output += ".exe";
|
|
14772
|
+
if (resolve(output) === resolve(entry) || statSync(output, { throwIfNoEntry: false })?.isDirectory()) {
|
|
14773
|
+
io.stderr(`visaljs: the binary would be ${displayPath(resolve(output))}, which is ${resolve(output) === resolve(entry) ? "the entry file" : "a directory"}: give another with -o\n`);
|
|
14774
|
+
return 1;
|
|
14775
|
+
}
|
|
14776
|
+
}
|
|
14777
|
+
const dir = emitDir ?? (binary ? scratchDir() : "");
|
|
12972
14778
|
try {
|
|
12973
|
-
const
|
|
12974
|
-
|
|
12975
|
-
|
|
14779
|
+
const typedLink = typed ? {
|
|
14780
|
+
atoms: [...typed.atoms],
|
|
14781
|
+
prefix: "vt_",
|
|
14782
|
+
names: typed.names,
|
|
14783
|
+
calls: typed.calls
|
|
14784
|
+
} : void 0;
|
|
14785
|
+
const c = await compileInWorker({
|
|
14786
|
+
source,
|
|
14787
|
+
entry,
|
|
14788
|
+
map,
|
|
14789
|
+
options,
|
|
14790
|
+
primitives,
|
|
14791
|
+
compilers,
|
|
14792
|
+
dir,
|
|
14793
|
+
emitDir,
|
|
14794
|
+
typed: typedLink
|
|
14795
|
+
}, report);
|
|
14796
|
+
if ("compileError" in c) {
|
|
14797
|
+
io.stderr(`${c.compileError}\n`);
|
|
14798
|
+
return 1;
|
|
14799
|
+
}
|
|
14800
|
+
compilers = c.compilers;
|
|
14801
|
+
if (typeof c.result === "string") {
|
|
14802
|
+
io.stdout(c.result);
|
|
12976
14803
|
return 0;
|
|
12977
14804
|
}
|
|
12978
|
-
const
|
|
12979
|
-
|
|
12980
|
-
|
|
12981
|
-
|
|
12982
|
-
|
|
12983
|
-
const unsupported = firstUnsupported(ast);
|
|
12984
|
-
if (unsupported) throw unsupported;
|
|
12985
|
-
const passes = [];
|
|
12986
|
-
const ir = await report.step("🔧", "Converting to IR and optimizing", () => optimize(lower(ast, info), options["no-optimize"] ? [] : void 0, passes), (ir) => [`Converted ${count(ir.functions.length + 1, "function")} to IR and optimized them`, ...formatReport(passes).map((line) => ` • ${line}`)].join("\n"));
|
|
12987
|
-
if (options["dump-ir"]) {
|
|
12988
|
-
io.stdout(printModule(ir));
|
|
14805
|
+
const written = c.result;
|
|
14806
|
+
if (emitDir) {
|
|
14807
|
+
if (bundled) writeFileSync(join(emitDir, "bundle.js"), source);
|
|
14808
|
+
if (typed) writeFileSync(join(emitDir, "typed.c"), typed.c);
|
|
14809
|
+
report.finish("📂", (time) => `Wrote ${written.length > 1 ? `${written.length} files to ${displayPath(resolve(emitDir))}` : displayPath(resolve(emitDir, "program.c"))} in ${time}`);
|
|
12989
14810
|
return 0;
|
|
12990
14811
|
}
|
|
12991
|
-
const
|
|
12992
|
-
|
|
12993
|
-
|
|
12994
|
-
|
|
12995
|
-
|
|
12996
|
-
|
|
12997
|
-
|
|
12998
|
-
|
|
12999
|
-
|
|
13000
|
-
|
|
13001
|
-
|
|
13002
|
-
|
|
13003
|
-
|
|
13004
|
-
|
|
13005
|
-
|
|
13006
|
-
|
|
13007
|
-
|
|
13008
|
-
|
|
13009
|
-
|
|
13010
|
-
|
|
13011
|
-
|
|
13012
|
-
|
|
13013
|
-
|
|
13014
|
-
|
|
13015
|
-
|
|
13016
|
-
|
|
13017
|
-
|
|
13018
|
-
|
|
13019
|
-
|
|
13020
|
-
|
|
13021
|
-
|
|
13022
|
-
for (const f of files) writeFileSync(join(dir, f.name), f.text);
|
|
13023
|
-
const cFiles = files.filter((f) => f.name.endsWith(".c")).map((f) => join(dir, f.name));
|
|
13024
|
-
await withCompiler(compilers, report, async (cc) => {
|
|
13025
|
-
const tc = {
|
|
13026
|
-
cc,
|
|
13027
|
-
opt,
|
|
13028
|
-
strip: !options.debug && !options.symbols
|
|
13029
|
-
};
|
|
13030
|
-
const built = [];
|
|
13031
|
-
const archives = await report.step("📚", "Preparing the runtime libraries", () => programArchives(tc, hosts, (name) => {
|
|
13032
|
-
built.push(`lib${name}`);
|
|
13033
|
-
report.relabel(`Building lib${name} with ${describeCompiler(cc)} (first build only: it's cached after)`);
|
|
13034
|
-
}), () => built.length ? `Built ${built.join(", ")} (cached for next time)` : "Runtime libraries ready (cached ⚡)");
|
|
13035
|
-
const compiling = `Compiling with ${describeCompiler(cc)}`;
|
|
13036
|
-
const parallel = cFiles.length > 1 ? `${cFiles.length} files, ${Math.min(jobs, cFiles.length)} at a time` : "";
|
|
13037
|
-
await report.step("🔨", parallel ? `${compiling} (${parallel})` : compiling, () => compileProgram(tc, cFiles, output, archives, {
|
|
13038
|
-
jobs,
|
|
13039
|
-
compiled: (done, total) => report.relabel(done < total ? `${compiling} (${done}/${total} files, ${Math.min(jobs, total)} at a time)` : `Linking with ${describeCompiler(cc)}`)
|
|
13040
|
-
}), () => `Compiled the binary with ${describeCompiler(cc)} (${[...opt, ...parallel ? [parallel] : []].join(", ")}, ${tc.strip ? "stripped" : "with debug info"})`);
|
|
13041
|
-
});
|
|
13042
|
-
} catch (e) {
|
|
13043
|
-
io.stderr(`visaljs: C compilation failed: ${e.message}\n`);
|
|
13044
|
-
return 1;
|
|
14812
|
+
const cFiles = written.filter((name) => name.endsWith(".c")).map((name) => join(dir, name));
|
|
14813
|
+
if (typed) {
|
|
14814
|
+
writeFileSync(join(dir, "typed.c"), typed.c);
|
|
14815
|
+
cFiles.push(join(dir, "typed.c"));
|
|
14816
|
+
}
|
|
14817
|
+
const opt = [...options.debug ? ["-O0"] : ["-O2"], ...gcCheckFlags()];
|
|
14818
|
+
const strip = !options.debug && !options.symbols;
|
|
14819
|
+
const jobs = options.jobs === void 0 ? compileJobs(strip ? 8e8 : 15e8) : Number(options.jobs);
|
|
14820
|
+
try {
|
|
14821
|
+
await withCompiler(compilers, report, async (cc) => {
|
|
14822
|
+
const tc = {
|
|
14823
|
+
cc,
|
|
14824
|
+
opt,
|
|
14825
|
+
strip
|
|
14826
|
+
};
|
|
14827
|
+
const built = [];
|
|
14828
|
+
const archives = await report.step("📚", "Preparing the runtime libraries", () => programArchives(tc, hosts, (name) => {
|
|
14829
|
+
built.push(`lib${name}`);
|
|
14830
|
+
report.relabel(`Building lib${name} with ${describeCompiler(cc)} (first build only: it's cached after)`);
|
|
14831
|
+
}), () => built.length ? `Built ${built.join(", ")} (cached for next time)` : "Runtime libraries ready (cached ⚡)");
|
|
14832
|
+
const compiling = `Compiling with ${describeCompiler(cc)}`;
|
|
14833
|
+
const parallel = cFiles.length > 1 ? `${cFiles.length} files, ${Math.min(jobs, cFiles.length)} at a time` : "";
|
|
14834
|
+
await report.step("🔨", parallel ? `${compiling} (${parallel})` : compiling, () => compileProgram(tc, cFiles, output, archives, {
|
|
14835
|
+
jobs,
|
|
14836
|
+
compiled: (done, total) => report.relabel(done < total ? `${compiling} (${done}/${total} files, ${Math.min(jobs, total)} at a time)` : `Linking with ${describeCompiler(cc)}`)
|
|
14837
|
+
}), () => `Compiled the binary with ${describeCompiler(cc)} (${[...opt, ...parallel ? [parallel] : []].join(", ")}, ${tc.strip ? "stripped" : "with debug info"})`);
|
|
14838
|
+
});
|
|
14839
|
+
} catch (e) {
|
|
14840
|
+
io.stderr(`visaljs: C compilation failed: ${e.message}\n`);
|
|
14841
|
+
return 1;
|
|
14842
|
+
}
|
|
13045
14843
|
} finally {
|
|
13046
|
-
rmSync(dir, {
|
|
14844
|
+
if (binary) rmSync(dir, {
|
|
13047
14845
|
recursive: true,
|
|
13048
14846
|
force: true
|
|
13049
14847
|
});
|
|
13050
14848
|
}
|
|
13051
14849
|
const bytes = statSync(output).size;
|
|
13052
14850
|
report.finish("🎉", (time) => `Built ${displayPath(resolve(output))} in ${time}: ${size(bytes)}, ${sizeQuip(bytes)}`);
|
|
13053
|
-
|
|
14851
|
+
pipeline?.done(resolve(output));
|
|
13054
14852
|
return 0;
|
|
13055
14853
|
}
|
|
13056
|
-
|
|
13057
|
-
|
|
14854
|
+
const WORKER = "visaljs-compile";
|
|
14855
|
+
/** compileToC in a worker thread, so its heap (gigabytes for a large program) goes back to the
|
|
14856
|
+
* system for the C compilers when it ends. The worker is this module (bundled: cli.js). */
|
|
14857
|
+
function compileInWorker(job, report) {
|
|
14858
|
+
const heap = Math.floor(getHeapStatistics().heap_size_limit / 2 ** 20);
|
|
14859
|
+
const worker = new Worker(new URL(import.meta.url), {
|
|
14860
|
+
workerData: { [WORKER]: job },
|
|
14861
|
+
resourceLimits: { maxOldGenerationSizeMb: heap }
|
|
14862
|
+
});
|
|
14863
|
+
return new Promise((resolve, reject) => {
|
|
14864
|
+
let step;
|
|
14865
|
+
let result;
|
|
14866
|
+
worker.on("message", (m) => {
|
|
14867
|
+
switch (m.kind) {
|
|
14868
|
+
case "step":
|
|
14869
|
+
report.step(m.icon, m.label, () => new Promise((done, fail) => step = {
|
|
14870
|
+
done,
|
|
14871
|
+
fail: () => fail(new Error(m.label))
|
|
14872
|
+
}), (text) => text).catch(() => {});
|
|
14873
|
+
break;
|
|
14874
|
+
case "done":
|
|
14875
|
+
step?.done(m.text);
|
|
14876
|
+
break;
|
|
14877
|
+
case "fail":
|
|
14878
|
+
step?.fail();
|
|
14879
|
+
break;
|
|
14880
|
+
case "relabel":
|
|
14881
|
+
report.relabel(m.label);
|
|
14882
|
+
break;
|
|
14883
|
+
case "note":
|
|
14884
|
+
report.note(m.text);
|
|
14885
|
+
break;
|
|
14886
|
+
case "warn":
|
|
14887
|
+
report.warn(m.message, m.where);
|
|
14888
|
+
break;
|
|
14889
|
+
case "result":
|
|
14890
|
+
result = m.result;
|
|
14891
|
+
worker.terminate();
|
|
14892
|
+
break;
|
|
14893
|
+
case "crash": reject(Object.assign(new Error(m.stack.split("\n")[0]), { stack: m.stack }));
|
|
14894
|
+
}
|
|
14895
|
+
});
|
|
14896
|
+
worker.on("error", reject);
|
|
14897
|
+
worker.on("exit", () => result ? resolve(result) : reject(/* @__PURE__ */ new Error("the compiler's worker stopped without a result")));
|
|
14898
|
+
});
|
|
14899
|
+
}
|
|
14900
|
+
async function runCompileJob(job) {
|
|
14901
|
+
const post = (m) => parentPort.postMessage(m);
|
|
14902
|
+
const reporter = {
|
|
14903
|
+
async step(icon, label, run, done) {
|
|
14904
|
+
post({
|
|
14905
|
+
kind: "step",
|
|
14906
|
+
icon,
|
|
14907
|
+
label
|
|
14908
|
+
});
|
|
14909
|
+
try {
|
|
14910
|
+
const result = await run();
|
|
14911
|
+
post({
|
|
14912
|
+
kind: "done",
|
|
14913
|
+
text: done(result)
|
|
14914
|
+
});
|
|
14915
|
+
return result;
|
|
14916
|
+
} catch (e) {
|
|
14917
|
+
post({ kind: "fail" });
|
|
14918
|
+
throw e;
|
|
14919
|
+
}
|
|
14920
|
+
},
|
|
14921
|
+
relabel: (label) => post({
|
|
14922
|
+
kind: "relabel",
|
|
14923
|
+
label
|
|
14924
|
+
}),
|
|
14925
|
+
note: (text) => post({
|
|
14926
|
+
kind: "note",
|
|
14927
|
+
text
|
|
14928
|
+
}),
|
|
14929
|
+
warn: (message, where = "") => post({
|
|
14930
|
+
kind: "warn",
|
|
14931
|
+
message,
|
|
14932
|
+
where
|
|
14933
|
+
})
|
|
14934
|
+
};
|
|
14935
|
+
let compilers = job.compilers;
|
|
14936
|
+
try {
|
|
14937
|
+
const map = job.map === void 0 ? void 0 : new SourceMap(job.map, displayPath);
|
|
14938
|
+
post({
|
|
14939
|
+
kind: "result",
|
|
14940
|
+
result: {
|
|
14941
|
+
result: await compileToC(job.source, job.entry, map, job.options, reporter, job.primitives, () => compilers ??= findCompilers(), job.typed, (f) => {
|
|
14942
|
+
if (job.emitDir) mkdirSync(job.emitDir, { recursive: true });
|
|
14943
|
+
writeFileSync(join(job.dir, f.name), f.text);
|
|
14944
|
+
}),
|
|
14945
|
+
compilers
|
|
14946
|
+
}
|
|
14947
|
+
});
|
|
14948
|
+
} catch (e) {
|
|
14949
|
+
if (e instanceof CompileError) post({
|
|
14950
|
+
kind: "result",
|
|
14951
|
+
result: { compileError: e.message }
|
|
14952
|
+
});
|
|
14953
|
+
else post({
|
|
14954
|
+
kind: "crash",
|
|
14955
|
+
stack: e.stack ?? String(e)
|
|
14956
|
+
});
|
|
14957
|
+
}
|
|
14958
|
+
}
|
|
14959
|
+
if (!isMainThread && workerData?.[WORKER]) await runCompileJob(workerData[WORKER]);
|
|
14960
|
+
/** The program's C files' names, or the text --dump-ast or --dump-ir prints. Its own function, as
|
|
14961
|
+
* is toIR, so the syntax tree and then the IR become garbage: a large program's take gigabytes. */
|
|
14962
|
+
async function compileToC(source, entry, map, options, report, primitives, compilers, typed, write) {
|
|
14963
|
+
const program = await toIR(source, entry, map, options, report, primitives, compilers, typed?.calls);
|
|
14964
|
+
if (typeof program === "string") return program;
|
|
14965
|
+
const { ir, regexps } = program;
|
|
14966
|
+
const names = /* @__PURE__ */ new Set([...program.names, ...typed?.names ?? []]);
|
|
14967
|
+
const all = options["all-builtins"] === true;
|
|
14968
|
+
return (await report.step("📝", "Emitting C code", () => {
|
|
14969
|
+
const files = emitProgramFiles(ir, {
|
|
14970
|
+
embedSource: options["embed-source"] === true,
|
|
14971
|
+
components: (all ? COMPONENTS : reachableComponents(names)).map((c) => c.symbol),
|
|
14972
|
+
core: all ? void 0 : namedCore(names),
|
|
14973
|
+
regexps,
|
|
14974
|
+
hostPrimitives: primitives,
|
|
14975
|
+
...typed ? { harness: {
|
|
14976
|
+
atoms: new Map(typed.atoms),
|
|
14977
|
+
register: `${typed.prefix}register`,
|
|
14978
|
+
scripts: [typed.prefix]
|
|
14979
|
+
} } : {},
|
|
14980
|
+
partSize: Number(process.env.VISALJS_PART_SIZE) || PART_SIZE
|
|
14981
|
+
});
|
|
14982
|
+
const out = {
|
|
14983
|
+
names: [],
|
|
14984
|
+
lines: 0,
|
|
14985
|
+
bytes: 0
|
|
14986
|
+
};
|
|
14987
|
+
for (const f of files) {
|
|
14988
|
+
write(f);
|
|
14989
|
+
out.names.push(f.name);
|
|
14990
|
+
out.lines += lineCount(f.text);
|
|
14991
|
+
out.bytes += Buffer.byteLength(f.text);
|
|
14992
|
+
}
|
|
14993
|
+
return out;
|
|
14994
|
+
}, ({ names, lines, bytes }) => `Emitted ${count(lines, "line")} of C code (${size(bytes)}${names.length > 1 ? `, in ${names.length} files` : ""})`)).names;
|
|
14995
|
+
}
|
|
14996
|
+
async function toIR(source, entry, map, options, report, primitives, compilers, typedCalls) {
|
|
14997
|
+
const ast = await report.step("🌳", "Parsing the JavaScript", () => options.script ? parseScript(source, entry) : parseModule(source, map ? "bundle.js" : entry, map, resolve(entry), primitives), () => `Parsed ${count(lineCount(source), "line")} of JavaScript`);
|
|
14998
|
+
if (options["dump-ast"]) return JSON.stringify(ast, null, 2) + "\n";
|
|
14999
|
+
const info = analyze(ast);
|
|
15000
|
+
const evalWarn = evalWarning(evalCalls(ast, info), options.verbose === true);
|
|
15001
|
+
if (evalWarn) report.warn(evalWarn);
|
|
15002
|
+
const literals = regexpLiterals(ast);
|
|
15003
|
+
const regexps = literals.length ? await report.step("🧩", "Compiling the regular expressions", () => withCompiler(compilers(), report, (cc) => compileRegExps(cc, literals)), () => `Compiled ${count(literals.length, "regular expression")}`) : /* @__PURE__ */ new Map();
|
|
15004
|
+
const unsupported = firstUnsupported(ast);
|
|
15005
|
+
if (unsupported) throw unsupported;
|
|
15006
|
+
const passes = [];
|
|
15007
|
+
const ir = await report.step("🔧", "Converting to IR and optimizing", () => {
|
|
15008
|
+
const lowered = lower(ast, info);
|
|
15009
|
+
if (typedCalls) annotateTypedCalls(lowered, typedCalls);
|
|
15010
|
+
return optimize(lowered, options["no-optimize"] ? [] : void 0, passes);
|
|
15011
|
+
}, (ir) => [`Converted ${count(ir.functions.length + 1, "function")} to IR and optimized them`, ...formatReport(passes).map((line) => ` • ${line}`)].join("\n"));
|
|
15012
|
+
if (options["dump-ir"]) return printModule(ir);
|
|
15013
|
+
return {
|
|
15014
|
+
ir,
|
|
15015
|
+
names: programNames(ast),
|
|
15016
|
+
regexps
|
|
15017
|
+
};
|
|
15018
|
+
}
|
|
13058
15019
|
function detected(compilers) {
|
|
13059
15020
|
const names = compilers.map(describeCompiler);
|
|
13060
15021
|
if (names.length === 1) return `Detected ${names[0]}${process.env.CC ? " (from $CC)" : ""}`;
|
|
13061
15022
|
return [`Detected ${count(names.length, "C compiler")} (tried in this order, falling back to the next if one fails):`, ...names.map((name, i) => ` ${i + 1}. ${name}`)].join("\n");
|
|
13062
15023
|
}
|
|
13063
|
-
/** Runs `run` with each compiler
|
|
13064
|
-
*
|
|
13065
|
-
* are dropped, so later steps start with the one that worked. The last failure is rethrown. */
|
|
15024
|
+
/** Runs `run` with each compiler until one succeeds. A failure that isn't the program's (not a
|
|
15025
|
+
* CompileError) moves on to the next, dropping the failed one from `compilers` for later steps. */
|
|
13066
15026
|
async function withCompiler(compilers, report, run) {
|
|
13067
15027
|
for (;;) {
|
|
13068
15028
|
const cc = compilers[0];
|
|
@@ -13077,8 +15037,7 @@ async function withCompiler(compilers, report, run) {
|
|
|
13077
15037
|
}
|
|
13078
15038
|
}
|
|
13079
15039
|
}
|
|
13080
|
-
/** How diagnostics and #line name an original file
|
|
13081
|
-
* it's inside it, else absolute. Code the bundler generated keeps its own name. */
|
|
15040
|
+
/** How diagnostics and #line name an original file. */
|
|
13082
15041
|
function displayPath(source) {
|
|
13083
15042
|
if (source.startsWith("\0")) return source.slice(1);
|
|
13084
15043
|
const rel = relative(process.cwd(), source);
|
|
@@ -13086,14 +15045,16 @@ function displayPath(source) {
|
|
|
13086
15045
|
}
|
|
13087
15046
|
//#endregion
|
|
13088
15047
|
//#region cli/src/cli.ts
|
|
13089
|
-
|
|
13090
|
-
|
|
13091
|
-
|
|
13092
|
-
|
|
13093
|
-
|
|
13094
|
-
|
|
13095
|
-
|
|
13096
|
-
|
|
13097
|
-
}
|
|
15048
|
+
if (isMainThread) {
|
|
15049
|
+
const tty = process.stderr.isTTY === true;
|
|
15050
|
+
process.exitCode = await main(process.argv.slice(2), {
|
|
15051
|
+
stdout: (text) => process.stdout.write(text),
|
|
15052
|
+
stderr: (text) => process.stderr.write(text),
|
|
15053
|
+
report: {
|
|
15054
|
+
live: tty,
|
|
15055
|
+
color: tty && !process.env.NO_COLOR && process.env.TERM !== "dumb"
|
|
15056
|
+
}
|
|
15057
|
+
});
|
|
15058
|
+
}
|
|
13098
15059
|
//#endregion
|
|
13099
15060
|
export {};
|