xbintsc 0.3.79 → 0.3.87
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/README.md +13 -5
- package/README.zh-CN.md +13 -6
- package/dist/src/ast/common.d.ts +23 -0
- package/dist/src/cli/main.js +68 -24
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/codegen/generator/calls/invocation.js +5 -1
- package/dist/src/codegen/generator/calls/invocation.js.map +1 -1
- package/dist/src/codegen/generator/context.d.ts +50 -0
- package/dist/src/codegen/generator/context.js +124 -13
- package/dist/src/codegen/generator/context.js.map +1 -1
- package/dist/src/codegen/generator/debug.d.ts +33 -0
- package/dist/src/codegen/generator/debug.js +97 -0
- package/dist/src/codegen/generator/debug.js.map +1 -0
- package/dist/src/codegen/generator/expressions/assignment.js +9 -5
- package/dist/src/codegen/generator/expressions/assignment.js.map +1 -1
- package/dist/src/codegen/generator/expressions/operators.js +35 -9
- package/dist/src/codegen/generator/expressions/operators.js.map +1 -1
- package/dist/src/codegen/generator/expressions/primary.js +5 -2
- package/dist/src/codegen/generator/expressions/primary.js.map +1 -1
- package/dist/src/codegen/generator/generator.d.ts +3 -1
- package/dist/src/codegen/generator/generator.js +3 -1
- package/dist/src/codegen/generator/generator.js.map +1 -1
- package/dist/src/codegen/generator/module.d.ts +1 -0
- package/dist/src/codegen/generator/module.js +76 -6
- package/dist/src/codegen/generator/module.js.map +1 -1
- package/dist/src/codegen/generator/numeric.d.ts +26 -0
- package/dist/src/codegen/generator/numeric.js +127 -0
- package/dist/src/codegen/generator/numeric.js.map +1 -0
- package/dist/src/codegen/generator/state.d.ts +17 -0
- package/dist/src/codegen/generator/state.js +5 -0
- package/dist/src/codegen/generator/state.js.map +1 -1
- package/dist/src/codegen/generator/statements/control-flow.js +1 -1
- package/dist/src/codegen/generator/statements/control-flow.js.map +1 -1
- package/dist/src/codegen/generator/statements/dispatch.js +3 -0
- package/dist/src/codegen/generator/statements/dispatch.js.map +1 -1
- package/dist/src/codegen/generator/statements/variables.js +8 -2
- package/dist/src/codegen/generator/statements/variables.js.map +1 -1
- package/dist/src/codegen/generator/tables.d.ts +7 -0
- package/dist/src/codegen/generator/tables.js +25 -0
- package/dist/src/codegen/generator/tables.js.map +1 -1
- package/dist/src/driver/bundler/merge.js +41 -5
- package/dist/src/driver/bundler/merge.js.map +1 -1
- package/dist/src/driver/compiler.d.ts +22 -3
- package/dist/src/driver/compiler.js +49 -58
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/driver/fingerprint.d.ts +27 -0
- package/dist/src/driver/fingerprint.js +88 -0
- package/dist/src/driver/fingerprint.js.map +1 -0
- package/dist/src/driver/toolchain-provider.d.ts +9 -2
- package/dist/src/driver/toolchain-provider.js +15 -15
- package/dist/src/driver/toolchain-provider.js.map +1 -1
- package/dist/src/driver/toolchain.d.ts +58 -1
- package/dist/src/driver/toolchain.js +169 -22
- package/dist/src/driver/toolchain.js.map +1 -1
- package/dist/src/extensions/gui/index.js +10 -2
- package/dist/src/extensions/gui/index.js.map +1 -1
- package/dist/src/parser/expressions.js +20 -1
- package/dist/src/parser/expressions.js.map +1 -1
- package/dist/tests/cli/main.test.js +41 -1
- package/dist/tests/cli/main.test.js.map +1 -1
- package/dist/tests/codegen/expressions.test.js +14 -3
- package/dist/tests/codegen/expressions.test.js.map +1 -1
- package/dist/tests/codegen/llvm.test.js +23 -0
- package/dist/tests/codegen/llvm.test.js.map +1 -1
- package/dist/tests/driver/toolchain-provider.test.js +49 -2
- package/dist/tests/driver/toolchain-provider.test.js.map +1 -1
- package/dist/tests/driver/toolchain.test.js +62 -3
- package/dist/tests/driver/toolchain.test.js.map +1 -1
- package/dist/tests/e2e/child-process.test.d.ts +10 -0
- package/dist/tests/e2e/child-process.test.js +59 -0
- package/dist/tests/e2e/child-process.test.js.map +1 -0
- package/dist/tests/e2e/compile-objects-classes.test.js +47 -0
- package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
- package/dist/tests/e2e/error-stack.test.d.ts +9 -0
- package/dist/tests/e2e/error-stack.test.js +94 -0
- package/dist/tests/e2e/error-stack.test.js.map +1 -0
- package/dist/tests/e2e/gui-example-svg.test.d.ts +21 -0
- package/dist/tests/e2e/gui-example-svg.test.js +105 -0
- package/dist/tests/e2e/gui-example-svg.test.js.map +1 -0
- package/dist/tests/e2e/gui-transform.test.d.ts +7 -0
- package/dist/tests/e2e/gui-transform.test.js +198 -0
- package/dist/tests/e2e/gui-transform.test.js.map +1 -0
- package/dist/tests/e2e/string-concat.test.d.ts +9 -0
- package/dist/tests/e2e/string-concat.test.js +102 -0
- package/dist/tests/e2e/string-concat.test.js.map +1 -0
- package/dist/tests/helpers.d.ts +9 -0
- package/dist/tests/helpers.js +26 -3
- package/dist/tests/helpers.js.map +1 -1
- package/dist/tests/parser/expressions.test.js +7 -0
- package/dist/tests/parser/expressions.test.js.map +1 -1
- package/dist/tests/version.test.d.ts +1 -0
- package/dist/tests/version.test.js +24 -0
- package/dist/tests/version.test.js.map +1 -0
- package/doc/DESIGN.md +20 -12
- package/doc/ai/cli.md +7 -0
- package/doc/ai/contributing.md +7 -1
- package/doc/ai/extensions.md +5 -1
- package/doc/ai/language-support.md +4 -4
- package/doc/ai/troubleshooting.md +39 -2
- package/doc/ai/zh-CN/cli.md +2 -0
- package/doc/ai/zh-CN/contributing.md +6 -2
- package/doc/ai/zh-CN/extensions.md +5 -1
- package/doc/ai/zh-CN/language-support.md +4 -4
- package/doc/ai/zh-CN/troubleshooting.md +35 -2
- package/doc/gui/animation.md +53 -2
- package/doc/gui/api.md +1 -1
- package/doc/gui/dom.md +6 -1
- package/doc/gui/input.md +4 -1
- package/doc/gui/known-issues.md +16 -4
- package/doc/gui/layout.md +36 -0
- package/doc/gui/rendering.md +15 -0
- package/doc/gui.md +32 -3
- package/doc/implemented.md +4 -4
- package/doc/requirements.md +13 -4
- package/doc/unimplemented.md +11 -10
- package/doc/zh-CN/DESIGN.md +15 -9
- package/doc/zh-CN/gui/animation.md +49 -2
- package/doc/zh-CN/gui/api.md +1 -1
- package/doc/zh-CN/gui/dom.md +6 -1
- package/doc/zh-CN/gui/input.md +2 -0
- package/doc/zh-CN/gui/known-issues.md +12 -2
- package/doc/zh-CN/gui/layout.md +31 -0
- package/doc/zh-CN/gui/rendering.md +11 -0
- package/doc/zh-CN/gui.md +28 -3
- package/doc/zh-CN/implemented.md +4 -4
- package/doc/zh-CN/requirements.md +12 -3
- package/doc/zh-CN/unimplemented.md +10 -9
- package/package.json +3 -1
- package/runtime/ext_gui/document.cpp +21 -3
- package/runtime/ext_gui/document.h +4 -1
- package/runtime/ext_gui/dom_api_node.cpp +6 -4
- package/runtime/ext_gui/layout.cpp +78 -1
- package/runtime/ext_gui/layout.h +21 -0
- package/runtime/ext_gui/paint.cpp +48 -15
- package/runtime/ext_gui/paint.h +8 -2
- package/runtime/ext_gui/renderer.cpp +4 -2
- package/runtime/ext_gui/renderer_image.cpp +2 -1
- package/runtime/ext_gui/renderer_internal.h +6 -1
- package/runtime/ext_gui/renderer_text.cpp +37 -15
- package/runtime/ext_gui/style.h +10 -0
- package/runtime/ext_gui/style_print.cpp +6 -0
- package/runtime/ext_gui/style_values.cpp +43 -0
- package/runtime/ext_gui/transform.cpp +412 -0
- package/runtime/ext_gui/transform.h +112 -0
- package/runtime/ext_gui/window.cpp +6 -4
- package/runtime/ext_node/buffer/parts/construction.inc +2 -2
- package/runtime/ext_node/buffer/parts/methods.inc +1 -1
- package/runtime/ext_node/buffer/parts/search.inc +1 -1
- package/runtime/ext_node/child_process/child_process.c +25 -0
- package/runtime/ext_node/fs/fd_ops.c +1 -1
- package/runtime/rt.h +17 -1
- package/runtime/rt_internal.h +47 -4
- package/runtime/xt_alloc.c +14 -2
- package/runtime/xt_bigint/conversions.inc +1 -1
- package/runtime/xt_builtins.c +136 -14
- package/runtime/xt_containers/access.inc +17 -16
- package/runtime/xt_containers/objects.inc +4 -3
- package/runtime/xt_io/exception.inc +171 -2
- package/runtime/xt_io/inspect.inc +15 -13
- package/runtime/xt_io.c +5 -0
- package/runtime/xt_stdlib/arrays.inc +2 -2
- package/runtime/xt_stdlib/dispatch.inc +2 -2
- package/runtime/xt_stdlib/strings.inc +33 -21
- package/runtime/xt_stdlib2/array-statics.inc +2 -1
- package/runtime/xt_stdlib2/arrays.inc +1 -1
- package/runtime/xt_stdlib2/error.inc +22 -6
- package/runtime/xt_stdlib2/json.inc +3 -2
- package/runtime/xt_stdlib2/number-string-statics.inc +3 -1
- package/runtime/xt_stdlib2/regexp-match.inc +48 -33
- package/runtime/xt_stdlib2/strings.inc +23 -14
- package/runtime/xt_symbol.c +1 -1
- package/runtime/xt_values/comparison.inc +2 -2
- package/runtime/xt_values/constructors.inc +10 -5
- package/runtime/xt_values/conversions.inc +4 -3
- package/runtime/xt_values/strings.inc +78 -4
- package/scripts/bench.ts +172 -0
- package/scripts/build-runtime.ts +2 -1
- package/scripts/check-version.ts +53 -0
- package/src/ast/common.ts +23 -0
- package/src/cli/main.ts +68 -26
- package/src/codegen/generator/calls/invocation.ts +5 -0
- package/src/codegen/generator/context.ts +129 -18
- package/src/codegen/generator/debug.ts +132 -0
- package/src/codegen/generator/expressions/assignment.ts +9 -5
- package/src/codegen/generator/expressions/operators.ts +47 -8
- package/src/codegen/generator/expressions/primary.ts +5 -2
- package/src/codegen/generator/generator.ts +18 -2
- package/src/codegen/generator/module.ts +78 -6
- package/src/codegen/generator/numeric.ts +158 -0
- package/src/codegen/generator/state.ts +22 -0
- package/src/codegen/generator/statements/control-flow.ts +1 -1
- package/src/codegen/generator/statements/dispatch.ts +3 -0
- package/src/codegen/generator/statements/variables.ts +8 -2
- package/src/codegen/generator/tables.ts +26 -0
- package/src/driver/bundler/merge.ts +39 -5
- package/src/driver/compiler.ts +68 -56
- package/src/driver/fingerprint.ts +85 -0
- package/src/driver/toolchain-provider.ts +17 -16
- package/src/driver/toolchain.ts +210 -21
- package/src/extensions/gui/index.ts +10 -2
- package/src/parser/expressions.ts +20 -1
package/src/driver/compiler.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
11
|
-
import { basename, extname, join, resolve } from "node:path";
|
|
11
|
+
import { basename, dirname, extname, join, resolve } from "node:path";
|
|
12
12
|
import { DiagnosticBag, DiagnosticCode, type Diagnostic } from "../diagnostics/diagnostic.js";
|
|
13
13
|
import { SourceFile } from "../diagnostics/source.js";
|
|
14
14
|
import { sourceTextBytes } from "../diagnostics/source-text.js";
|
|
@@ -18,6 +18,7 @@ import { SyntaxKind } from "../ast/nodes.js";
|
|
|
18
18
|
import { bundleModules } from "./modules.js";
|
|
19
19
|
import { createDefaultRegistry, type ExtensionRegistry } from "../extensions/registry.js";
|
|
20
20
|
import { BuildCache, hashParts } from "./cache.js";
|
|
21
|
+
import { compilerFingerprint, registryFingerprint, runtimeFingerprint } from "./fingerprint.js";
|
|
21
22
|
import { findRuntimeDir } from "./paths.js";
|
|
22
23
|
import { findRuntimeLibrary } from "./runtime-lib.js";
|
|
23
24
|
import { resolveToolchain } from "./toolchain-provider.js";
|
|
@@ -31,10 +32,17 @@ import {
|
|
|
31
32
|
compileIr,
|
|
32
33
|
link,
|
|
33
34
|
realRunner,
|
|
35
|
+
RUNTIME_CFLAGS,
|
|
34
36
|
type Runner,
|
|
35
37
|
} from "./toolchain.js";
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
/**
|
|
40
|
+
* The compiler's own version. It is part of the incremental cache key, so it
|
|
41
|
+
* must be bumped for every release (a test enforces that it matches
|
|
42
|
+
* `package.json`); a stale version would let an upgraded compiler reuse binaries
|
|
43
|
+
* produced by the previous one.
|
|
44
|
+
*/
|
|
45
|
+
export const COMPILER_VERSION = "0.3.87";
|
|
38
46
|
|
|
39
47
|
export type EmitKind = "exe" | "obj" | "ir";
|
|
40
48
|
|
|
@@ -57,6 +65,14 @@ export interface BuildOptions {
|
|
|
57
65
|
readonly icon?: string;
|
|
58
66
|
/** Application metadata: name, icon and macOS bundle settings. */
|
|
59
67
|
readonly app?: AppConfig;
|
|
68
|
+
/** Emit LLVM debug metadata so `-g` produces DWARF/CodeView (default false). */
|
|
69
|
+
readonly debug?: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** One phase of a build, in milliseconds (reported by `--time`). */
|
|
73
|
+
export interface PhaseTiming {
|
|
74
|
+
readonly phase: "frontend" | "codegen" | "runtime" | "clang" | "link";
|
|
75
|
+
readonly ms: number;
|
|
60
76
|
}
|
|
61
77
|
|
|
62
78
|
export interface BuildResult {
|
|
@@ -67,6 +83,8 @@ export interface BuildResult {
|
|
|
67
83
|
readonly ir?: string;
|
|
68
84
|
/** macOS `.app` bundle produced by `app.bundle` (darwin only). */
|
|
69
85
|
readonly bundlePath?: string;
|
|
86
|
+
/** Per-phase timings, collected for `xbintsc build --time`. */
|
|
87
|
+
readonly timings?: readonly PhaseTiming[];
|
|
70
88
|
}
|
|
71
89
|
|
|
72
90
|
export interface CompileStringResult {
|
|
@@ -75,7 +93,12 @@ export interface CompileStringResult {
|
|
|
75
93
|
}
|
|
76
94
|
|
|
77
95
|
/** Compile TypeScript source text to LLVM IR without touching the toolchain. */
|
|
78
|
-
export function compileString(
|
|
96
|
+
export function compileString(
|
|
97
|
+
source: string,
|
|
98
|
+
fileName = "input.ts",
|
|
99
|
+
extensions?: ExtensionRegistry,
|
|
100
|
+
options: { readonly debug?: boolean } = {},
|
|
101
|
+
): CompileStringResult {
|
|
79
102
|
const file = new SourceFile(fileName, source);
|
|
80
103
|
const diagnostics = new DiagnosticBag();
|
|
81
104
|
const parser = new Parser(file, diagnostics);
|
|
@@ -85,6 +108,7 @@ export function compileString(source: string, fileName = "input.ts", extensions?
|
|
|
85
108
|
builtins: registry.builtins(),
|
|
86
109
|
modules: registry.modules(),
|
|
87
110
|
moduleHints: registry.moduleHints(),
|
|
111
|
+
debug: options.debug === true,
|
|
88
112
|
});
|
|
89
113
|
return { ir, diagnostics: diagnostics.diagnostics };
|
|
90
114
|
}
|
|
@@ -106,7 +130,11 @@ function externalModuleSpecifiers(registry: ExtensionRegistry): Set<string> {
|
|
|
106
130
|
* Compile a file to LLVM IR, bundling every reachable relative module first so
|
|
107
131
|
* that imports from other TypeScript sources resolve exactly like `build`.
|
|
108
132
|
*/
|
|
109
|
-
export function compileEntry(
|
|
133
|
+
export function compileEntry(
|
|
134
|
+
entryPath: string,
|
|
135
|
+
extensions?: ExtensionRegistry,
|
|
136
|
+
options: { readonly debug?: boolean } = {},
|
|
137
|
+
): CompileStringResult {
|
|
110
138
|
const absoluteEntry = resolve(entryPath);
|
|
111
139
|
const sourceText = sourceTextBytes(readFileSync(absoluteEntry, "utf8"));
|
|
112
140
|
const file = new SourceFile(absoluteEntry, sourceText);
|
|
@@ -128,6 +156,7 @@ export function compileEntry(entryPath: string, extensions?: ExtensionRegistry):
|
|
|
128
156
|
builtins: registry.builtins(),
|
|
129
157
|
modules: registry.modules(),
|
|
130
158
|
moduleHints: registry.moduleHints(),
|
|
159
|
+
debug: options.debug === true,
|
|
131
160
|
});
|
|
132
161
|
return { ir, diagnostics: diagnostics.diagnostics };
|
|
133
162
|
}
|
|
@@ -173,6 +202,8 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
173
202
|
|
|
174
203
|
const absoluteEntry = resolve(entryPath);
|
|
175
204
|
const sourceText = sourceTextBytes(readFileSync(absoluteEntry, "utf8"));
|
|
205
|
+
const buildStarted = Date.now();
|
|
206
|
+
const timings: PhaseTiming[] = [];
|
|
176
207
|
|
|
177
208
|
const baseName = basename(absoluteEntry, extname(absoluteEntry));
|
|
178
209
|
const outputPath =
|
|
@@ -203,6 +234,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
203
234
|
cacheText = bundled.text;
|
|
204
235
|
}
|
|
205
236
|
}
|
|
237
|
+
timings.push({ phase: "frontend", ms: Date.now() - buildStarted });
|
|
206
238
|
|
|
207
239
|
const app = options.app ?? {};
|
|
208
240
|
const iconPath = options.icon ?? app.icon;
|
|
@@ -222,6 +254,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
222
254
|
const runtimeDir = findRuntimeDir();
|
|
223
255
|
const cacheKey = hashParts([
|
|
224
256
|
COMPILER_VERSION,
|
|
257
|
+
compilerFingerprint(),
|
|
225
258
|
cacheText,
|
|
226
259
|
emit,
|
|
227
260
|
optimize,
|
|
@@ -232,6 +265,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
232
265
|
app.name ?? "",
|
|
233
266
|
String(app.bundle ?? false),
|
|
234
267
|
app.bundleId ?? "",
|
|
268
|
+
String(options.debug === true),
|
|
235
269
|
]);
|
|
236
270
|
|
|
237
271
|
// A `.app` bundle is deterministic from the output path and name, so it can be
|
|
@@ -243,34 +277,43 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
243
277
|
const cache = new BuildCache(cacheDir);
|
|
244
278
|
const outputs = bundle ? [outputPath, bundle] : [outputPath];
|
|
245
279
|
if (!options.force && !diagnostics.hasErrors && cache.isFresh(cacheKey, outputs)) {
|
|
246
|
-
return { outputPath, cached: true, diagnostics: [], ...(bundle ? { bundlePath: bundle } : {}) };
|
|
280
|
+
return { outputPath, cached: true, diagnostics: [], timings, ...(bundle ? { bundlePath: bundle } : {}) };
|
|
247
281
|
}
|
|
248
282
|
|
|
283
|
+
const codegenStarted = Date.now();
|
|
249
284
|
const { ir } = generate(sourceFile, diagnostics, {
|
|
250
285
|
builtins: registry.builtins(),
|
|
251
286
|
modules: registry.modules(),
|
|
252
287
|
moduleHints: registry.moduleHints(),
|
|
288
|
+
debug: options.debug === true,
|
|
253
289
|
});
|
|
290
|
+
timings.push({ phase: "codegen", ms: Date.now() - codegenStarted });
|
|
254
291
|
|
|
255
292
|
if (options.verbose) {
|
|
256
293
|
process.stderr.write(`xbintsc: generated ${ir.length} bytes of LLVM IR\n`);
|
|
257
294
|
}
|
|
258
295
|
|
|
259
296
|
if (diagnostics.hasErrors) {
|
|
260
|
-
return { outputPath, cached: false, diagnostics: diagnostics.diagnostics, ir };
|
|
297
|
+
return { outputPath, cached: false, diagnostics: diagnostics.diagnostics, ir, timings };
|
|
261
298
|
}
|
|
262
299
|
|
|
263
300
|
mkdirSync(outDir, { recursive: true });
|
|
301
|
+
/* An explicit `-o path/to/app.exe` (or `output` in the config) may name a
|
|
302
|
+
* directory that does not exist yet: the IR and object files land in `outDir`,
|
|
303
|
+
* but clang writes the *executable* to `outputPath`, and a missing parent
|
|
304
|
+
* directory there makes the link fail with `LNK1104: cannot open file`. */
|
|
305
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
264
306
|
const irPath = resolve(outDir, basename(absoluteEntry, extname(absoluteEntry)) + ".ll");
|
|
265
307
|
writeFileSync(irPath, ir);
|
|
266
308
|
|
|
267
309
|
if (emit === "ir") {
|
|
268
310
|
cache.record(cacheKey, outputs);
|
|
269
311
|
cache.save();
|
|
270
|
-
return { outputPath, irPath, cached: false, diagnostics: [], ir };
|
|
312
|
+
return { outputPath, irPath, cached: false, diagnostics: [], ir, timings };
|
|
271
313
|
}
|
|
272
314
|
|
|
273
315
|
// -- toolchain -----------------------------------------------------------
|
|
316
|
+
const clangStarted = Date.now();
|
|
274
317
|
const toolchain = options.clang
|
|
275
318
|
? { clang: options.clang, linkerArgs: [] as string[], env: {} as Record<string, string> }
|
|
276
319
|
: resolveToolchain(runner);
|
|
@@ -278,14 +321,16 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
278
321
|
mkdirSync(cacheDir, { recursive: true });
|
|
279
322
|
|
|
280
323
|
const objectPath = emit === "obj" ? outputPath : resolve(outDir, baseName + ".o");
|
|
281
|
-
compileIr(runner, { clang, irPath, objectPath, optimize, env: toolchain.env });
|
|
324
|
+
compileIr(runner, { clang, irPath, objectPath, optimize, env: toolchain.env, debug: options.debug === true });
|
|
325
|
+
timings.push({ phase: "clang", ms: Date.now() - clangStarted });
|
|
282
326
|
|
|
283
327
|
if (emit === "obj") {
|
|
284
328
|
cache.record(cacheKey, outputs);
|
|
285
329
|
cache.save();
|
|
286
|
-
return { outputPath, irPath, cached: false, diagnostics: [], ir };
|
|
330
|
+
return { outputPath, irPath, cached: false, diagnostics: [], ir, timings };
|
|
287
331
|
}
|
|
288
332
|
|
|
333
|
+
const runtimeStarted = Date.now();
|
|
289
334
|
const { runtimeObjects, extensionObjects, nativeObjects } = ensureRuntimeObjects(
|
|
290
335
|
runner,
|
|
291
336
|
clang,
|
|
@@ -295,6 +340,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
295
340
|
resolvePreferPrebuilt(options.preferPrebuilt),
|
|
296
341
|
toolchain.env,
|
|
297
342
|
);
|
|
343
|
+
timings.push({ phase: "runtime", ms: Date.now() - runtimeStarted });
|
|
298
344
|
|
|
299
345
|
/* On Windows a GUI program links as a **Windows-subsystem** executable, so
|
|
300
346
|
* launching it does not flash a console window. That subsystem's C runtime
|
|
@@ -331,6 +377,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
331
377
|
}
|
|
332
378
|
}
|
|
333
379
|
|
|
380
|
+
const linkStarted = Date.now();
|
|
334
381
|
link(runner, {
|
|
335
382
|
clang,
|
|
336
383
|
objectPaths: [
|
|
@@ -355,6 +402,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
355
402
|
optimize,
|
|
356
403
|
env: toolchain.env,
|
|
357
404
|
});
|
|
405
|
+
timings.push({ phase: "link", ms: Date.now() - linkStarted });
|
|
358
406
|
|
|
359
407
|
const bundlePath =
|
|
360
408
|
bundle !== undefined
|
|
@@ -370,7 +418,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
370
418
|
|
|
371
419
|
cache.record(cacheKey, outputs);
|
|
372
420
|
cache.save();
|
|
373
|
-
return { outputPath, irPath, cached: false, diagnostics: [], ir, ...(bundlePath ? { bundlePath } : {}) };
|
|
421
|
+
return { outputPath, irPath, cached: false, diagnostics: [], ir, timings, ...(bundlePath ? { bundlePath } : {}) };
|
|
374
422
|
}
|
|
375
423
|
|
|
376
424
|
/** Default `CFBundleIdentifier` for a bundle when none is configured. */
|
|
@@ -404,51 +452,6 @@ export const RUNTIME_SOURCES = [
|
|
|
404
452
|
"xt_io.c",
|
|
405
453
|
] as const;
|
|
406
454
|
|
|
407
|
-
/**
|
|
408
|
-
* Fingerprint every runtime artifact that can change a linked executable: the
|
|
409
|
-
* C sources, shared headers and `#include`d `.inc` fragments plus the shipped
|
|
410
|
-
* prebuilt archives. It feeds the executable cache key, so a runtime change
|
|
411
|
-
* invalidates a cached binary even when the entry source is untouched — the
|
|
412
|
-
* per-object cache cannot express that, because the freshness check runs before
|
|
413
|
-
* any compilation.
|
|
414
|
-
*/
|
|
415
|
-
function runtimeFingerprint(runtimeDir: string): string {
|
|
416
|
-
if (!existsSync(runtimeDir)) return "";
|
|
417
|
-
const parts: string[] = [];
|
|
418
|
-
const walk = (dir: string): void => {
|
|
419
|
-
for (const entry of readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
420
|
-
const full = join(dir, entry.name);
|
|
421
|
-
if (entry.isDirectory()) {
|
|
422
|
-
walk(full);
|
|
423
|
-
} else if (/\.(c|h|inc)$/.test(entry.name)) {
|
|
424
|
-
parts.push(entry.name, readFileSync(full, "utf8"));
|
|
425
|
-
} else {
|
|
426
|
-
parts.push(entry.name, readFileSync(full).toString("base64"));
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
};
|
|
430
|
-
walk(runtimeDir);
|
|
431
|
-
return hashParts(parts);
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
/**
|
|
435
|
-
* Fingerprint everything an extension contributes that can change the linked
|
|
436
|
-
* executable but does not live under `runtime/`: its name, linker flags and the
|
|
437
|
-
* contents of any pre-built C++/Rust objects. Keeps a cached binary fresh when
|
|
438
|
-
* a native extension is rebuilt, or when `--ext-native` flags change.
|
|
439
|
-
*/
|
|
440
|
-
function registryFingerprint(registry: ExtensionRegistry): string {
|
|
441
|
-
const parts: string[] = [];
|
|
442
|
-
for (const extension of registry.all()) {
|
|
443
|
-
parts.push(extension.name);
|
|
444
|
-
parts.push(...(extension.linkerFlags?.() ?? []));
|
|
445
|
-
for (const object of extension.nativeObjects?.() ?? []) {
|
|
446
|
-
parts.push(object, existsSync(object) ? readFileSync(object).toString("base64") : "");
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
return hashParts(parts);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
455
|
/**
|
|
453
456
|
* Compile the core runtime and every extension source, reusing cached object
|
|
454
457
|
* files keyed on the C source hash. Returns the object paths to link.
|
|
@@ -482,7 +485,16 @@ function ensureRuntimeObjects(
|
|
|
482
485
|
|
|
483
486
|
const compileOne = (sourcePath: string, tag: string): string => {
|
|
484
487
|
const body = readFileSync(sourcePath, "utf8");
|
|
485
|
-
const key = hashParts([
|
|
488
|
+
const key = hashParts([
|
|
489
|
+
"runtime",
|
|
490
|
+
COMPILER_VERSION,
|
|
491
|
+
RUNTIME_CFLAGS.join(" "),
|
|
492
|
+
tag,
|
|
493
|
+
headerStamp,
|
|
494
|
+
body,
|
|
495
|
+
clang,
|
|
496
|
+
process.platform,
|
|
497
|
+
]);
|
|
486
498
|
const objectPath = join(cacheDir, `${tag}-${key}.o`);
|
|
487
499
|
if (!existsSync(objectPath)) {
|
|
488
500
|
compileC(runner, clang, sourcePath, objectPath, runtimeDir, env);
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content fingerprints for the incremental cache.
|
|
3
|
+
*
|
|
4
|
+
* The executable cache is keyed on everything that can change the linked
|
|
5
|
+
* binary but is not part of the entry source: the C runtime, the compiler's own
|
|
6
|
+
* sources, and whatever a native extension contributes. The per-object cache
|
|
7
|
+
* cannot express these, because the freshness check runs before compilation.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
11
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
import type { ExtensionRegistry } from "../extensions/registry.js";
|
|
14
|
+
import { hashParts } from "./cache.js";
|
|
15
|
+
|
|
16
|
+
/** Runtime sources, headers and `#include`d `.inc` fragments, plus prebuilt archives. */
|
|
17
|
+
export function runtimeFingerprint(runtimeDir: string): string {
|
|
18
|
+
if (!existsSync(runtimeDir)) return "";
|
|
19
|
+
const parts: string[] = [];
|
|
20
|
+
const walk = (dir: string): void => {
|
|
21
|
+
for (const entry of readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
22
|
+
const full = join(dir, entry.name);
|
|
23
|
+
if (entry.isDirectory()) {
|
|
24
|
+
walk(full);
|
|
25
|
+
} else if (/\.(c|h|inc)$/.test(entry.name)) {
|
|
26
|
+
parts.push(entry.name, readFileSync(full, "utf8"));
|
|
27
|
+
} else {
|
|
28
|
+
parts.push(entry.name, readFileSync(full).toString("base64"));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
walk(runtimeDir);
|
|
33
|
+
return hashParts(parts);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Content hash of the compiler's own TypeScript sources, when they are present.
|
|
38
|
+
*
|
|
39
|
+
* `COMPILER_VERSION` is bumped per release, but a developer editing codegen
|
|
40
|
+
* between releases would otherwise reuse a binary produced by the previous
|
|
41
|
+
* compiler for the same input. Hashing `src/` closes that hole in a source
|
|
42
|
+
* checkout. A released archive ships only `bin/` and `runtime/`, so the marker
|
|
43
|
+
* check fails there and the version alone keys the cache — which is correct,
|
|
44
|
+
* because release automation keeps it in sync with package.json.
|
|
45
|
+
*/
|
|
46
|
+
export function compilerFingerprint(): string {
|
|
47
|
+
try {
|
|
48
|
+
// In a checkout this module is `<root>/src/driver/fingerprint.ts`.
|
|
49
|
+
const sourceRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
50
|
+
if (!existsSync(join(sourceRoot, "codegen", "llvm.ts"))) return "";
|
|
51
|
+
if (!existsSync(join(sourceRoot, "driver", "compiler.ts"))) return "";
|
|
52
|
+
const parts: string[] = [];
|
|
53
|
+
const walk = (dir: string): void => {
|
|
54
|
+
for (const entry of readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
55
|
+
const full = join(dir, entry.name);
|
|
56
|
+
if (entry.isDirectory()) {
|
|
57
|
+
walk(full);
|
|
58
|
+
} else if (entry.name.endsWith(".ts")) {
|
|
59
|
+
parts.push(relative(sourceRoot, full).replace(/\\/g, "/"), readFileSync(full, "utf8"));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
walk(sourceRoot);
|
|
64
|
+
return hashParts(parts);
|
|
65
|
+
} catch {
|
|
66
|
+
// A self-hosted or packaged compiler may not expose its sources.
|
|
67
|
+
return "";
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Everything an extension contributes besides its C sources: name, linker flags
|
|
73
|
+
* and the contents of pre-built C++/Rust objects.
|
|
74
|
+
*/
|
|
75
|
+
export function registryFingerprint(registry: ExtensionRegistry): string {
|
|
76
|
+
const parts: string[] = [];
|
|
77
|
+
for (const extension of registry.all()) {
|
|
78
|
+
parts.push(extension.name);
|
|
79
|
+
parts.push(...(extension.linkerFlags?.() ?? []));
|
|
80
|
+
for (const object of extension.nativeObjects?.() ?? []) {
|
|
81
|
+
parts.push(object, existsSync(object) ? readFileSync(object).toString("base64") : "");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return hashParts(parts);
|
|
85
|
+
}
|
|
@@ -6,18 +6,23 @@
|
|
|
6
6
|
* *which* driver to use, in a fixed priority order:
|
|
7
7
|
*
|
|
8
8
|
* 1. an explicit `xbintsc_CLANG` / `xbintsc_TOOLCHAIN`
|
|
9
|
-
* 2. the system `PATH` (`findClang`)
|
|
9
|
+
* 2. the system `PATH` (`findClang`), preferring a clang >= 16
|
|
10
|
+
* 3. the LLVM build bundled with Visual Studio on Windows
|
|
10
11
|
*
|
|
11
12
|
* xbintsc does not ship a compiler. Every host must provide a clang-compatible
|
|
12
13
|
* toolchain (see `doc/requirements.md`): the Xcode Command Line Tools on macOS,
|
|
13
14
|
* the distribution's clang/lld on Linux, and LLVM plus the Visual Studio C++
|
|
14
15
|
* build tools (the MSVC ABI) on Windows.
|
|
15
16
|
*
|
|
17
|
+
* The version is checked here, not left to the first failed C compile: the
|
|
18
|
+
* emitted IR needs LLVM 16+ (opaque pointers), and a "clang 15 is too old"
|
|
19
|
+
* error up front is far more actionable than a wall of IR type errors later.
|
|
20
|
+
*
|
|
16
21
|
* Extra linker arguments (for example `-fuse-ld=lld`) can be supplied through
|
|
17
22
|
* `xbintsc_LINKER_ARGS`.
|
|
18
23
|
*/
|
|
19
24
|
|
|
20
|
-
import {
|
|
25
|
+
import { locateClang, realRunner, type ClangVersion, type Runner } from "./toolchain.js";
|
|
21
26
|
|
|
22
27
|
export type ToolchainSource = "env" | "system";
|
|
23
28
|
|
|
@@ -26,6 +31,8 @@ export interface ResolvedToolchain {
|
|
|
26
31
|
readonly clang: string;
|
|
27
32
|
/** Where the driver came from. */
|
|
28
33
|
readonly source: ToolchainSource;
|
|
34
|
+
/** Parsed `clang --version`, when the driver prints one. */
|
|
35
|
+
readonly version?: ClangVersion;
|
|
29
36
|
/** Extra arguments to pass to the link step. */
|
|
30
37
|
readonly linkerArgs: readonly string[];
|
|
31
38
|
/** Extra environment for the toolchain subprocesses, merged over the current
|
|
@@ -33,14 +40,6 @@ export interface ResolvedToolchain {
|
|
|
33
40
|
readonly env: Record<string, string>;
|
|
34
41
|
}
|
|
35
42
|
|
|
36
|
-
function works(runner: Runner, command: string): boolean {
|
|
37
|
-
try {
|
|
38
|
-
return runner.run(command, ["--version"]).status === 0;
|
|
39
|
-
} catch {
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
43
|
function linkerArgs(fallback: readonly string[]): string[] {
|
|
45
44
|
const raw = process.env.xbintsc_LINKER_ARGS;
|
|
46
45
|
if (raw) return raw.split(" ").filter((part) => part.length > 0);
|
|
@@ -49,10 +48,12 @@ function linkerArgs(fallback: readonly string[]): string[] {
|
|
|
49
48
|
|
|
50
49
|
/** Resolve the toolchain that `build` should use. See the module docblock. */
|
|
51
50
|
export function resolveToolchain(runner: Runner = realRunner): ResolvedToolchain {
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
51
|
+
const location = locateClang(runner);
|
|
52
|
+
return {
|
|
53
|
+
clang: location.command,
|
|
54
|
+
source: location.source,
|
|
55
|
+
...(location.version ? { version: location.version } : {}),
|
|
56
|
+
linkerArgs: linkerArgs([]),
|
|
57
|
+
env: {},
|
|
58
|
+
};
|
|
58
59
|
}
|