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.
Files changed (201) hide show
  1. package/README.md +13 -5
  2. package/README.zh-CN.md +13 -6
  3. package/dist/src/ast/common.d.ts +23 -0
  4. package/dist/src/cli/main.js +68 -24
  5. package/dist/src/cli/main.js.map +1 -1
  6. package/dist/src/codegen/generator/calls/invocation.js +5 -1
  7. package/dist/src/codegen/generator/calls/invocation.js.map +1 -1
  8. package/dist/src/codegen/generator/context.d.ts +50 -0
  9. package/dist/src/codegen/generator/context.js +124 -13
  10. package/dist/src/codegen/generator/context.js.map +1 -1
  11. package/dist/src/codegen/generator/debug.d.ts +33 -0
  12. package/dist/src/codegen/generator/debug.js +97 -0
  13. package/dist/src/codegen/generator/debug.js.map +1 -0
  14. package/dist/src/codegen/generator/expressions/assignment.js +9 -5
  15. package/dist/src/codegen/generator/expressions/assignment.js.map +1 -1
  16. package/dist/src/codegen/generator/expressions/operators.js +35 -9
  17. package/dist/src/codegen/generator/expressions/operators.js.map +1 -1
  18. package/dist/src/codegen/generator/expressions/primary.js +5 -2
  19. package/dist/src/codegen/generator/expressions/primary.js.map +1 -1
  20. package/dist/src/codegen/generator/generator.d.ts +3 -1
  21. package/dist/src/codegen/generator/generator.js +3 -1
  22. package/dist/src/codegen/generator/generator.js.map +1 -1
  23. package/dist/src/codegen/generator/module.d.ts +1 -0
  24. package/dist/src/codegen/generator/module.js +76 -6
  25. package/dist/src/codegen/generator/module.js.map +1 -1
  26. package/dist/src/codegen/generator/numeric.d.ts +26 -0
  27. package/dist/src/codegen/generator/numeric.js +127 -0
  28. package/dist/src/codegen/generator/numeric.js.map +1 -0
  29. package/dist/src/codegen/generator/state.d.ts +17 -0
  30. package/dist/src/codegen/generator/state.js +5 -0
  31. package/dist/src/codegen/generator/state.js.map +1 -1
  32. package/dist/src/codegen/generator/statements/control-flow.js +1 -1
  33. package/dist/src/codegen/generator/statements/control-flow.js.map +1 -1
  34. package/dist/src/codegen/generator/statements/dispatch.js +3 -0
  35. package/dist/src/codegen/generator/statements/dispatch.js.map +1 -1
  36. package/dist/src/codegen/generator/statements/variables.js +8 -2
  37. package/dist/src/codegen/generator/statements/variables.js.map +1 -1
  38. package/dist/src/codegen/generator/tables.d.ts +7 -0
  39. package/dist/src/codegen/generator/tables.js +25 -0
  40. package/dist/src/codegen/generator/tables.js.map +1 -1
  41. package/dist/src/driver/bundler/merge.js +41 -5
  42. package/dist/src/driver/bundler/merge.js.map +1 -1
  43. package/dist/src/driver/compiler.d.ts +22 -3
  44. package/dist/src/driver/compiler.js +49 -58
  45. package/dist/src/driver/compiler.js.map +1 -1
  46. package/dist/src/driver/fingerprint.d.ts +27 -0
  47. package/dist/src/driver/fingerprint.js +88 -0
  48. package/dist/src/driver/fingerprint.js.map +1 -0
  49. package/dist/src/driver/toolchain-provider.d.ts +9 -2
  50. package/dist/src/driver/toolchain-provider.js +15 -15
  51. package/dist/src/driver/toolchain-provider.js.map +1 -1
  52. package/dist/src/driver/toolchain.d.ts +58 -1
  53. package/dist/src/driver/toolchain.js +169 -22
  54. package/dist/src/driver/toolchain.js.map +1 -1
  55. package/dist/src/extensions/gui/index.js +10 -2
  56. package/dist/src/extensions/gui/index.js.map +1 -1
  57. package/dist/src/parser/expressions.js +20 -1
  58. package/dist/src/parser/expressions.js.map +1 -1
  59. package/dist/tests/cli/main.test.js +41 -1
  60. package/dist/tests/cli/main.test.js.map +1 -1
  61. package/dist/tests/codegen/expressions.test.js +14 -3
  62. package/dist/tests/codegen/expressions.test.js.map +1 -1
  63. package/dist/tests/codegen/llvm.test.js +23 -0
  64. package/dist/tests/codegen/llvm.test.js.map +1 -1
  65. package/dist/tests/driver/toolchain-provider.test.js +49 -2
  66. package/dist/tests/driver/toolchain-provider.test.js.map +1 -1
  67. package/dist/tests/driver/toolchain.test.js +62 -3
  68. package/dist/tests/driver/toolchain.test.js.map +1 -1
  69. package/dist/tests/e2e/child-process.test.d.ts +10 -0
  70. package/dist/tests/e2e/child-process.test.js +59 -0
  71. package/dist/tests/e2e/child-process.test.js.map +1 -0
  72. package/dist/tests/e2e/compile-objects-classes.test.js +47 -0
  73. package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
  74. package/dist/tests/e2e/error-stack.test.d.ts +9 -0
  75. package/dist/tests/e2e/error-stack.test.js +94 -0
  76. package/dist/tests/e2e/error-stack.test.js.map +1 -0
  77. package/dist/tests/e2e/gui-example-svg.test.d.ts +21 -0
  78. package/dist/tests/e2e/gui-example-svg.test.js +105 -0
  79. package/dist/tests/e2e/gui-example-svg.test.js.map +1 -0
  80. package/dist/tests/e2e/gui-transform.test.d.ts +7 -0
  81. package/dist/tests/e2e/gui-transform.test.js +198 -0
  82. package/dist/tests/e2e/gui-transform.test.js.map +1 -0
  83. package/dist/tests/e2e/string-concat.test.d.ts +9 -0
  84. package/dist/tests/e2e/string-concat.test.js +102 -0
  85. package/dist/tests/e2e/string-concat.test.js.map +1 -0
  86. package/dist/tests/helpers.d.ts +9 -0
  87. package/dist/tests/helpers.js +26 -3
  88. package/dist/tests/helpers.js.map +1 -1
  89. package/dist/tests/parser/expressions.test.js +7 -0
  90. package/dist/tests/parser/expressions.test.js.map +1 -1
  91. package/dist/tests/version.test.d.ts +1 -0
  92. package/dist/tests/version.test.js +24 -0
  93. package/dist/tests/version.test.js.map +1 -0
  94. package/doc/DESIGN.md +20 -12
  95. package/doc/ai/cli.md +7 -0
  96. package/doc/ai/contributing.md +7 -1
  97. package/doc/ai/extensions.md +5 -1
  98. package/doc/ai/language-support.md +4 -4
  99. package/doc/ai/troubleshooting.md +39 -2
  100. package/doc/ai/zh-CN/cli.md +2 -0
  101. package/doc/ai/zh-CN/contributing.md +6 -2
  102. package/doc/ai/zh-CN/extensions.md +5 -1
  103. package/doc/ai/zh-CN/language-support.md +4 -4
  104. package/doc/ai/zh-CN/troubleshooting.md +35 -2
  105. package/doc/gui/animation.md +53 -2
  106. package/doc/gui/api.md +1 -1
  107. package/doc/gui/dom.md +6 -1
  108. package/doc/gui/input.md +4 -1
  109. package/doc/gui/known-issues.md +16 -4
  110. package/doc/gui/layout.md +36 -0
  111. package/doc/gui/rendering.md +15 -0
  112. package/doc/gui.md +32 -3
  113. package/doc/implemented.md +4 -4
  114. package/doc/requirements.md +13 -4
  115. package/doc/unimplemented.md +11 -10
  116. package/doc/zh-CN/DESIGN.md +15 -9
  117. package/doc/zh-CN/gui/animation.md +49 -2
  118. package/doc/zh-CN/gui/api.md +1 -1
  119. package/doc/zh-CN/gui/dom.md +6 -1
  120. package/doc/zh-CN/gui/input.md +2 -0
  121. package/doc/zh-CN/gui/known-issues.md +12 -2
  122. package/doc/zh-CN/gui/layout.md +31 -0
  123. package/doc/zh-CN/gui/rendering.md +11 -0
  124. package/doc/zh-CN/gui.md +28 -3
  125. package/doc/zh-CN/implemented.md +4 -4
  126. package/doc/zh-CN/requirements.md +12 -3
  127. package/doc/zh-CN/unimplemented.md +10 -9
  128. package/package.json +3 -1
  129. package/runtime/ext_gui/document.cpp +21 -3
  130. package/runtime/ext_gui/document.h +4 -1
  131. package/runtime/ext_gui/dom_api_node.cpp +6 -4
  132. package/runtime/ext_gui/layout.cpp +78 -1
  133. package/runtime/ext_gui/layout.h +21 -0
  134. package/runtime/ext_gui/paint.cpp +48 -15
  135. package/runtime/ext_gui/paint.h +8 -2
  136. package/runtime/ext_gui/renderer.cpp +4 -2
  137. package/runtime/ext_gui/renderer_image.cpp +2 -1
  138. package/runtime/ext_gui/renderer_internal.h +6 -1
  139. package/runtime/ext_gui/renderer_text.cpp +37 -15
  140. package/runtime/ext_gui/style.h +10 -0
  141. package/runtime/ext_gui/style_print.cpp +6 -0
  142. package/runtime/ext_gui/style_values.cpp +43 -0
  143. package/runtime/ext_gui/transform.cpp +412 -0
  144. package/runtime/ext_gui/transform.h +112 -0
  145. package/runtime/ext_gui/window.cpp +6 -4
  146. package/runtime/ext_node/buffer/parts/construction.inc +2 -2
  147. package/runtime/ext_node/buffer/parts/methods.inc +1 -1
  148. package/runtime/ext_node/buffer/parts/search.inc +1 -1
  149. package/runtime/ext_node/child_process/child_process.c +25 -0
  150. package/runtime/ext_node/fs/fd_ops.c +1 -1
  151. package/runtime/rt.h +17 -1
  152. package/runtime/rt_internal.h +47 -4
  153. package/runtime/xt_alloc.c +14 -2
  154. package/runtime/xt_bigint/conversions.inc +1 -1
  155. package/runtime/xt_builtins.c +136 -14
  156. package/runtime/xt_containers/access.inc +17 -16
  157. package/runtime/xt_containers/objects.inc +4 -3
  158. package/runtime/xt_io/exception.inc +171 -2
  159. package/runtime/xt_io/inspect.inc +15 -13
  160. package/runtime/xt_io.c +5 -0
  161. package/runtime/xt_stdlib/arrays.inc +2 -2
  162. package/runtime/xt_stdlib/dispatch.inc +2 -2
  163. package/runtime/xt_stdlib/strings.inc +33 -21
  164. package/runtime/xt_stdlib2/array-statics.inc +2 -1
  165. package/runtime/xt_stdlib2/arrays.inc +1 -1
  166. package/runtime/xt_stdlib2/error.inc +22 -6
  167. package/runtime/xt_stdlib2/json.inc +3 -2
  168. package/runtime/xt_stdlib2/number-string-statics.inc +3 -1
  169. package/runtime/xt_stdlib2/regexp-match.inc +48 -33
  170. package/runtime/xt_stdlib2/strings.inc +23 -14
  171. package/runtime/xt_symbol.c +1 -1
  172. package/runtime/xt_values/comparison.inc +2 -2
  173. package/runtime/xt_values/constructors.inc +10 -5
  174. package/runtime/xt_values/conversions.inc +4 -3
  175. package/runtime/xt_values/strings.inc +78 -4
  176. package/scripts/bench.ts +172 -0
  177. package/scripts/build-runtime.ts +2 -1
  178. package/scripts/check-version.ts +53 -0
  179. package/src/ast/common.ts +23 -0
  180. package/src/cli/main.ts +68 -26
  181. package/src/codegen/generator/calls/invocation.ts +5 -0
  182. package/src/codegen/generator/context.ts +129 -18
  183. package/src/codegen/generator/debug.ts +132 -0
  184. package/src/codegen/generator/expressions/assignment.ts +9 -5
  185. package/src/codegen/generator/expressions/operators.ts +47 -8
  186. package/src/codegen/generator/expressions/primary.ts +5 -2
  187. package/src/codegen/generator/generator.ts +18 -2
  188. package/src/codegen/generator/module.ts +78 -6
  189. package/src/codegen/generator/numeric.ts +158 -0
  190. package/src/codegen/generator/state.ts +22 -0
  191. package/src/codegen/generator/statements/control-flow.ts +1 -1
  192. package/src/codegen/generator/statements/dispatch.ts +3 -0
  193. package/src/codegen/generator/statements/variables.ts +8 -2
  194. package/src/codegen/generator/tables.ts +26 -0
  195. package/src/driver/bundler/merge.ts +39 -5
  196. package/src/driver/compiler.ts +68 -56
  197. package/src/driver/fingerprint.ts +85 -0
  198. package/src/driver/toolchain-provider.ts +17 -16
  199. package/src/driver/toolchain.ts +210 -21
  200. package/src/extensions/gui/index.ts +10 -2
  201. package/src/parser/expressions.ts +20 -1
@@ -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
- export const COMPILER_VERSION = "0.1.0";
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(source: string, fileName = "input.ts", extensions?: ExtensionRegistry): CompileStringResult {
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(entryPath: string, extensions?: ExtensionRegistry): CompileStringResult {
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(["runtime", COMPILER_VERSION, tag, headerStamp, body, clang, process.platform]);
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 { findClang, realRunner, type Runner } from "./toolchain.js";
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 explicit = process.env.xbintsc_CLANG ?? process.env.xbintsc_TOOLCHAIN;
53
- if (explicit && works(runner, explicit)) {
54
- return { clang: explicit, source: "env", linkerArgs: linkerArgs([]), env: {} };
55
- }
56
-
57
- return { clang: findClang(runner), source: "system", linkerArgs: linkerArgs([]), env: {} };
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
  }