xbintsc 0.3.81 → 0.5.6

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 (188) hide show
  1. package/AGENTS.md +8 -4
  2. package/README.md +16 -5
  3. package/README.zh-CN.md +14 -6
  4. package/dist/src/ast/common.d.ts +23 -0
  5. package/dist/src/cli/main.js +68 -24
  6. package/dist/src/cli/main.js.map +1 -1
  7. package/dist/src/codegen/generator/calls/access.js +6 -2
  8. package/dist/src/codegen/generator/calls/access.js.map +1 -1
  9. package/dist/src/codegen/generator/calls/closures.js +4 -0
  10. package/dist/src/codegen/generator/calls/closures.js.map +1 -1
  11. package/dist/src/codegen/generator/calls/invocation.js +9 -4
  12. package/dist/src/codegen/generator/calls/invocation.js.map +1 -1
  13. package/dist/src/codegen/generator/context.d.ts +50 -0
  14. package/dist/src/codegen/generator/context.js +124 -13
  15. package/dist/src/codegen/generator/context.js.map +1 -1
  16. package/dist/src/codegen/generator/debug.d.ts +33 -0
  17. package/dist/src/codegen/generator/debug.js +97 -0
  18. package/dist/src/codegen/generator/debug.js.map +1 -0
  19. package/dist/src/codegen/generator/expressions/assignment.js +9 -5
  20. package/dist/src/codegen/generator/expressions/assignment.js.map +1 -1
  21. package/dist/src/codegen/generator/expressions/operators.js +35 -9
  22. package/dist/src/codegen/generator/expressions/operators.js.map +1 -1
  23. package/dist/src/codegen/generator/expressions/primary.js +11 -3
  24. package/dist/src/codegen/generator/expressions/primary.js.map +1 -1
  25. package/dist/src/codegen/generator/generator.d.ts +3 -1
  26. package/dist/src/codegen/generator/generator.js +3 -1
  27. package/dist/src/codegen/generator/generator.js.map +1 -1
  28. package/dist/src/codegen/generator/module.d.ts +1 -1
  29. package/dist/src/codegen/generator/module.js +82 -12
  30. package/dist/src/codegen/generator/module.js.map +1 -1
  31. package/dist/src/codegen/generator/numeric.d.ts +26 -0
  32. package/dist/src/codegen/generator/numeric.js +127 -0
  33. package/dist/src/codegen/generator/numeric.js.map +1 -0
  34. package/dist/src/codegen/generator/state.d.ts +17 -0
  35. package/dist/src/codegen/generator/state.js +9 -0
  36. package/dist/src/codegen/generator/state.js.map +1 -1
  37. package/dist/src/codegen/generator/statements/control-flow.js +2 -4
  38. package/dist/src/codegen/generator/statements/control-flow.js.map +1 -1
  39. package/dist/src/codegen/generator/statements/dispatch.js +3 -0
  40. package/dist/src/codegen/generator/statements/dispatch.js.map +1 -1
  41. package/dist/src/codegen/generator/statements/variables.js +8 -2
  42. package/dist/src/codegen/generator/statements/variables.js.map +1 -1
  43. package/dist/src/codegen/generator/tables.d.ts +16 -0
  44. package/dist/src/codegen/generator/tables.js +99 -2
  45. package/dist/src/codegen/generator/tables.js.map +1 -1
  46. package/dist/src/driver/bundler/merge.js +41 -5
  47. package/dist/src/driver/bundler/merge.js.map +1 -1
  48. package/dist/src/driver/compiler.d.ts +23 -4
  49. package/dist/src/driver/compiler.js +46 -57
  50. package/dist/src/driver/compiler.js.map +1 -1
  51. package/dist/src/driver/fingerprint.d.ts +27 -0
  52. package/dist/src/driver/fingerprint.js +88 -0
  53. package/dist/src/driver/fingerprint.js.map +1 -0
  54. package/dist/src/driver/toolchain-provider.d.ts +9 -2
  55. package/dist/src/driver/toolchain-provider.js +15 -15
  56. package/dist/src/driver/toolchain-provider.js.map +1 -1
  57. package/dist/src/driver/toolchain.d.ts +58 -1
  58. package/dist/src/driver/toolchain.js +169 -22
  59. package/dist/src/driver/toolchain.js.map +1 -1
  60. package/dist/src/parser/expressions.js +20 -1
  61. package/dist/src/parser/expressions.js.map +1 -1
  62. package/dist/tests/cli/main.test.js +41 -1
  63. package/dist/tests/cli/main.test.js.map +1 -1
  64. package/dist/tests/codegen/closures.test.js +8 -0
  65. package/dist/tests/codegen/closures.test.js.map +1 -1
  66. package/dist/tests/codegen/expressions.test.js +14 -3
  67. package/dist/tests/codegen/expressions.test.js.map +1 -1
  68. package/dist/tests/codegen/llvm.test.js +23 -0
  69. package/dist/tests/codegen/llvm.test.js.map +1 -1
  70. package/dist/tests/driver/toolchain-provider.test.js +49 -2
  71. package/dist/tests/driver/toolchain-provider.test.js.map +1 -1
  72. package/dist/tests/driver/toolchain.test.js +62 -3
  73. package/dist/tests/driver/toolchain.test.js.map +1 -1
  74. package/dist/tests/e2e/async-await.test.d.ts +10 -0
  75. package/dist/tests/e2e/async-await.test.js +247 -0
  76. package/dist/tests/e2e/async-await.test.js.map +1 -0
  77. package/dist/tests/e2e/child-process.test.d.ts +10 -0
  78. package/dist/tests/e2e/child-process.test.js +59 -0
  79. package/dist/tests/e2e/child-process.test.js.map +1 -0
  80. package/dist/tests/e2e/compile-objects-classes.test.js +47 -0
  81. package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
  82. package/dist/tests/e2e/error-stack.test.d.ts +9 -0
  83. package/dist/tests/e2e/error-stack.test.js +94 -0
  84. package/dist/tests/e2e/error-stack.test.js.map +1 -0
  85. package/dist/tests/e2e/runtime-coverage-globals.test.js +46 -0
  86. package/dist/tests/e2e/runtime-coverage-globals.test.js.map +1 -1
  87. package/dist/tests/e2e/string-concat.test.d.ts +9 -0
  88. package/dist/tests/e2e/string-concat.test.js +102 -0
  89. package/dist/tests/e2e/string-concat.test.js.map +1 -0
  90. package/dist/tests/e2e/string-utf16.test.d.ts +6 -0
  91. package/dist/tests/e2e/string-utf16.test.js +133 -0
  92. package/dist/tests/e2e/string-utf16.test.js.map +1 -0
  93. package/dist/tests/helpers.d.ts +9 -0
  94. package/dist/tests/helpers.js +26 -3
  95. package/dist/tests/helpers.js.map +1 -1
  96. package/dist/tests/parser/expressions.test.js +7 -0
  97. package/dist/tests/parser/expressions.test.js.map +1 -1
  98. package/dist/tests/version.test.d.ts +1 -0
  99. package/dist/tests/version.test.js +24 -0
  100. package/dist/tests/version.test.js.map +1 -0
  101. package/doc/DESIGN.md +20 -12
  102. package/doc/ai/cli.md +2 -0
  103. package/doc/ai/contributing.md +16 -8
  104. package/doc/ai/extensions.md +3 -2
  105. package/doc/ai/language-support.md +15 -12
  106. package/doc/ai/troubleshooting.md +43 -5
  107. package/doc/ai/zh-CN/cli.md +2 -0
  108. package/doc/ai/zh-CN/contributing.md +11 -7
  109. package/doc/ai/zh-CN/extensions.md +2 -1
  110. package/doc/ai/zh-CN/language-support.md +15 -12
  111. package/doc/ai/zh-CN/troubleshooting.md +38 -4
  112. package/doc/implemented.md +16 -13
  113. package/doc/requirements.md +13 -4
  114. package/doc/unimplemented.md +20 -18
  115. package/doc/zh-CN/DESIGN.md +15 -9
  116. package/doc/zh-CN/implemented.md +15 -12
  117. package/doc/zh-CN/requirements.md +12 -3
  118. package/doc/zh-CN/unimplemented.md +19 -17
  119. package/package.json +3 -1
  120. package/runtime/ext_node/buffer/parts/construction.inc +2 -2
  121. package/runtime/ext_node/buffer/parts/methods.inc +1 -1
  122. package/runtime/ext_node/buffer/parts/search.inc +1 -1
  123. package/runtime/ext_node/child_process/child_process.c +25 -0
  124. package/runtime/ext_node/fs/fd_ops.c +1 -1
  125. package/runtime/rt.h +49 -1
  126. package/runtime/rt_internal.h +136 -4
  127. package/runtime/xt_alloc.c +20 -2
  128. package/runtime/xt_async.c +197 -0
  129. package/runtime/xt_bigint/conversions.inc +1 -1
  130. package/runtime/xt_builtins.c +169 -43
  131. package/runtime/xt_constructors.c +389 -0
  132. package/runtime/xt_containers/access.inc +61 -24
  133. package/runtime/xt_containers/arrays.inc +26 -2
  134. package/runtime/xt_containers/objects.inc +206 -8
  135. package/runtime/xt_coroutine.c +212 -0
  136. package/runtime/xt_generator.c +31 -162
  137. package/runtime/xt_io/exception.inc +171 -2
  138. package/runtime/xt_io/inspect.inc +48 -13
  139. package/runtime/xt_io.c +5 -0
  140. package/runtime/xt_promise.c +62 -17
  141. package/runtime/xt_stdlib/arrays.inc +2 -2
  142. package/runtime/xt_stdlib/dispatch.inc +20 -4
  143. package/runtime/xt_stdlib/strings.inc +98 -29
  144. package/runtime/xt_stdlib2/array-statics.inc +3 -2
  145. package/runtime/xt_stdlib2/arrays.inc +1 -1
  146. package/runtime/xt_stdlib2/error.inc +33 -6
  147. package/runtime/xt_stdlib2/json.inc +46 -16
  148. package/runtime/xt_stdlib2/number-string-statics.inc +24 -13
  149. package/runtime/xt_stdlib2/objects.inc +1 -1
  150. package/runtime/xt_stdlib2/regexp-match.inc +50 -35
  151. package/runtime/xt_stdlib2/set.inc +6 -0
  152. package/runtime/xt_stdlib2/strings.inc +81 -42
  153. package/runtime/xt_symbol.c +1 -1
  154. package/runtime/xt_typed_array/construction.inc +43 -2
  155. package/runtime/xt_typed_array.c +4 -0
  156. package/runtime/xt_values/comparison.inc +2 -2
  157. package/runtime/xt_values/constructors.inc +18 -5
  158. package/runtime/xt_values/conversions.inc +4 -3
  159. package/runtime/xt_values/strings.inc +94 -4
  160. package/runtime/xt_values/utf16.inc +340 -0
  161. package/runtime/xt_values.c +1 -0
  162. package/scripts/bench.ts +172 -0
  163. package/scripts/build-runtime.ts +2 -1
  164. package/scripts/check-version.ts +53 -0
  165. package/src/ast/common.ts +23 -0
  166. package/src/cli/main.ts +68 -26
  167. package/src/codegen/generator/calls/access.ts +7 -2
  168. package/src/codegen/generator/calls/closures.ts +3 -0
  169. package/src/codegen/generator/calls/invocation.ts +9 -3
  170. package/src/codegen/generator/context.ts +129 -18
  171. package/src/codegen/generator/debug.ts +132 -0
  172. package/src/codegen/generator/expressions/assignment.ts +9 -5
  173. package/src/codegen/generator/expressions/operators.ts +47 -8
  174. package/src/codegen/generator/expressions/primary.ts +11 -3
  175. package/src/codegen/generator/generator.ts +18 -2
  176. package/src/codegen/generator/module.ts +83 -13
  177. package/src/codegen/generator/numeric.ts +158 -0
  178. package/src/codegen/generator/state.ts +26 -0
  179. package/src/codegen/generator/statements/control-flow.ts +2 -3
  180. package/src/codegen/generator/statements/dispatch.ts +3 -0
  181. package/src/codegen/generator/statements/variables.ts +8 -2
  182. package/src/codegen/generator/tables.ts +110 -2
  183. package/src/driver/bundler/merge.ts +39 -5
  184. package/src/driver/compiler.ts +65 -55
  185. package/src/driver/fingerprint.ts +85 -0
  186. package/src/driver/toolchain-provider.ts +17 -16
  187. package/src/driver/toolchain.ts +210 -21
  188. package/src/parser/expressions.ts +20 -1
@@ -169,6 +169,32 @@ export function bundleModules(
169
169
 
170
170
  // Phase 4: concatenate statements, dropping imports and pure export wrappers.
171
171
  const merged: Statement[] = [];
172
+ /* Every node keeps the module it was parsed from, so a later codegen
173
+ * diagnostic can name the right file and line instead of pointing into the
174
+ * entry module (whose text happens to start at the same offsets). */
175
+ const origins = new Map<object, string>();
176
+ const trackOrigin = (record: ModuleRecord, root: Node): void => {
177
+ const visit = (node: Node): void => {
178
+ origins.set(node, record.path);
179
+ const fields = node as unknown as Record<string, unknown>;
180
+ for (const key of Object.keys(fields)) {
181
+ if (key === "kind" || key === "start" || key === "end" || key === "text" || key === "value" || key === "raw") {
182
+ continue;
183
+ }
184
+ const value = fields[key];
185
+ if (Array.isArray(value)) {
186
+ for (const child of value) if (isNode(child)) visit(child);
187
+ } else if (isNode(value)) {
188
+ visit(value);
189
+ }
190
+ }
191
+ };
192
+ visit(root);
193
+ };
194
+ const push = (record: ModuleRecord, statement: Statement): void => {
195
+ trackOrigin(record, statement);
196
+ merged.push(statement);
197
+ };
172
198
  const first = records[records.length - 1]!;
173
199
  for (const record of records) {
174
200
  for (const statement of record.sourceFile.statements) {
@@ -181,25 +207,25 @@ export function bundleModules(
181
207
  externalSpecifiers,
182
208
  );
183
209
  if (resolution.kind === "external") {
184
- merged.push(statement);
210
+ push(record, statement);
185
211
  } else {
186
212
  const synthetic = namespaceStatements.get(statement);
187
- if (synthetic) merged.push(synthetic);
213
+ if (synthetic) push(record, synthetic);
188
214
  }
189
215
  continue;
190
216
  }
191
217
  if (statement.kind === SyntaxKind.ExportDeclaration) {
192
218
  const declaration = statement as ExportDeclaration;
193
219
  const inner = (declaration as unknown as { declaration?: Statement }).declaration;
194
- if (inner) merged.push(inner);
220
+ if (inner) push(record, inner);
195
221
  continue;
196
222
  }
197
223
  if (statement.kind === SyntaxKind.ExportAssignment) {
198
224
  const assignment = statement as ExportAssignment;
199
- merged.push(exportAssignmentToStatement(record, assignment));
225
+ push(record, exportAssignmentToStatement(record, assignment));
200
226
  continue;
201
227
  }
202
- merged.push(statement);
228
+ push(record, statement);
203
229
  }
204
230
  }
205
231
 
@@ -217,6 +243,9 @@ export function bundleModules(
217
243
  kind: SyntaxKind.SourceFile,
218
244
  statements: merged,
219
245
  fileName: first.path,
246
+ origins,
247
+ moduleTexts: new Map(records.map((record) => [record.path, record.text])),
248
+ modulePrefixes: records.map((record) => record.prefix),
220
249
  text: records.map((record) => record.text).join("\n"),
221
250
  start: 0,
222
251
  end: 0,
@@ -248,6 +277,11 @@ function collectBindingIdentifiers(name: BindingName, out: string[]): void {
248
277
  }
249
278
  }
250
279
 
280
+ /** True for anything shaped like an AST node (constructed nodes carry no trivia). */
281
+ function isNode(value: unknown): value is Node {
282
+ return !!value && typeof value === "object" && "kind" in (value as object);
283
+ }
284
+
251
285
  /** Lower `import * as ns from "./mod"` to `const m0_ns = { ...exports }`. */
252
286
  function namespaceImportToStatement(
253
287
  statement: Statement,
@@ -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.5.6";
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,21 +277,24 @@ 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 });
@@ -272,10 +309,11 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
272
309
  if (emit === "ir") {
273
310
  cache.record(cacheKey, outputs);
274
311
  cache.save();
275
- return { outputPath, irPath, cached: false, diagnostics: [], ir };
312
+ return { outputPath, irPath, cached: false, diagnostics: [], ir, timings };
276
313
  }
277
314
 
278
315
  // -- toolchain -----------------------------------------------------------
316
+ const clangStarted = Date.now();
279
317
  const toolchain = options.clang
280
318
  ? { clang: options.clang, linkerArgs: [] as string[], env: {} as Record<string, string> }
281
319
  : resolveToolchain(runner);
@@ -283,14 +321,16 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
283
321
  mkdirSync(cacheDir, { recursive: true });
284
322
 
285
323
  const objectPath = emit === "obj" ? outputPath : resolve(outDir, baseName + ".o");
286
- 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 });
287
326
 
288
327
  if (emit === "obj") {
289
328
  cache.record(cacheKey, outputs);
290
329
  cache.save();
291
- return { outputPath, irPath, cached: false, diagnostics: [], ir };
330
+ return { outputPath, irPath, cached: false, diagnostics: [], ir, timings };
292
331
  }
293
332
 
333
+ const runtimeStarted = Date.now();
294
334
  const { runtimeObjects, extensionObjects, nativeObjects } = ensureRuntimeObjects(
295
335
  runner,
296
336
  clang,
@@ -300,6 +340,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
300
340
  resolvePreferPrebuilt(options.preferPrebuilt),
301
341
  toolchain.env,
302
342
  );
343
+ timings.push({ phase: "runtime", ms: Date.now() - runtimeStarted });
303
344
 
304
345
  /* On Windows a GUI program links as a **Windows-subsystem** executable, so
305
346
  * launching it does not flash a console window. That subsystem's C runtime
@@ -336,6 +377,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
336
377
  }
337
378
  }
338
379
 
380
+ const linkStarted = Date.now();
339
381
  link(runner, {
340
382
  clang,
341
383
  objectPaths: [
@@ -360,6 +402,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
360
402
  optimize,
361
403
  env: toolchain.env,
362
404
  });
405
+ timings.push({ phase: "link", ms: Date.now() - linkStarted });
363
406
 
364
407
  const bundlePath =
365
408
  bundle !== undefined
@@ -375,7 +418,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
375
418
 
376
419
  cache.record(cacheKey, outputs);
377
420
  cache.save();
378
- return { outputPath, irPath, cached: false, diagnostics: [], ir, ...(bundlePath ? { bundlePath } : {}) };
421
+ return { outputPath, irPath, cached: false, diagnostics: [], ir, timings, ...(bundlePath ? { bundlePath } : {}) };
379
422
  }
380
423
 
381
424
  /** Default `CFBundleIdentifier` for a bundle when none is configured. */
@@ -401,59 +444,17 @@ export const RUNTIME_SOURCES = [
401
444
  "xt_stdlib.c",
402
445
  "xt_stdlib2.c",
403
446
  "xt_promise.c",
447
+ "xt_async.c",
404
448
  "xt_loop.c",
405
449
  "xt_generator.c",
450
+ "xt_coroutine.c",
406
451
  "xt_fetch.c",
407
452
  "xt_symbol.c",
408
453
  "xt_builtins.c",
454
+ "xt_constructors.c",
409
455
  "xt_io.c",
410
456
  ] as const;
411
457
 
412
- /**
413
- * Fingerprint every runtime artifact that can change a linked executable: the
414
- * C sources, shared headers and `#include`d `.inc` fragments plus the shipped
415
- * prebuilt archives. It feeds the executable cache key, so a runtime change
416
- * invalidates a cached binary even when the entry source is untouched — the
417
- * per-object cache cannot express that, because the freshness check runs before
418
- * any compilation.
419
- */
420
- function runtimeFingerprint(runtimeDir: string): string {
421
- if (!existsSync(runtimeDir)) return "";
422
- const parts: string[] = [];
423
- const walk = (dir: string): void => {
424
- for (const entry of readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
425
- const full = join(dir, entry.name);
426
- if (entry.isDirectory()) {
427
- walk(full);
428
- } else if (/\.(c|h|inc)$/.test(entry.name)) {
429
- parts.push(entry.name, readFileSync(full, "utf8"));
430
- } else {
431
- parts.push(entry.name, readFileSync(full).toString("base64"));
432
- }
433
- }
434
- };
435
- walk(runtimeDir);
436
- return hashParts(parts);
437
- }
438
-
439
- /**
440
- * Fingerprint everything an extension contributes that can change the linked
441
- * executable but does not live under `runtime/`: its name, linker flags and the
442
- * contents of any pre-built C++/Rust objects. Keeps a cached binary fresh when
443
- * a native extension is rebuilt, or when `--ext-native` flags change.
444
- */
445
- function registryFingerprint(registry: ExtensionRegistry): string {
446
- const parts: string[] = [];
447
- for (const extension of registry.all()) {
448
- parts.push(extension.name);
449
- parts.push(...(extension.linkerFlags?.() ?? []));
450
- for (const object of extension.nativeObjects?.() ?? []) {
451
- parts.push(object, existsSync(object) ? readFileSync(object).toString("base64") : "");
452
- }
453
- }
454
- return hashParts(parts);
455
- }
456
-
457
458
  /**
458
459
  * Compile the core runtime and every extension source, reusing cached object
459
460
  * files keyed on the C source hash. Returns the object paths to link.
@@ -487,7 +488,16 @@ function ensureRuntimeObjects(
487
488
 
488
489
  const compileOne = (sourcePath: string, tag: string): string => {
489
490
  const body = readFileSync(sourcePath, "utf8");
490
- const key = hashParts(["runtime", COMPILER_VERSION, tag, headerStamp, body, clang, process.platform]);
491
+ const key = hashParts([
492
+ "runtime",
493
+ COMPILER_VERSION,
494
+ RUNTIME_CFLAGS.join(" "),
495
+ tag,
496
+ headerStamp,
497
+ body,
498
+ clang,
499
+ process.platform,
500
+ ]);
491
501
  const objectPath = join(cacheDir, `${tag}-${key}.o`);
492
502
  if (!existsSync(objectPath)) {
493
503
  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
  }