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
@@ -54,11 +54,35 @@ export class GeneratorContext {
54
54
  readonly importDefaults = new Map<number, ModuleExport>();
55
55
  readonly globals: string[] = [];
56
56
  readonly functions: string[] = [];
57
- readonly strings = new Map<string, { label: string; length: number }>();
57
+ readonly strings = new Map<string, { label: string; length: number; slot: string }>();
58
58
  readonly classGlobals = new Map<number, string>();
59
59
  readonly moduleGlobals = new Map<number, string>();
60
60
  readonly extraDeclarations = new Set<string>();
61
+ /** Static C strings used only as metadata (stack frame names/files). */
62
+ readonly staticStrings = new Map<string, string>();
63
+ /** Lazily built line-start indexes per source file, for stack frames. */
64
+ readonly lineStartsByFile = new Map<string, number[]>();
61
65
  stringCounter = 0;
66
+ staticCounter = 0;
67
+ /**
68
+ * How many `UnsupportedFeature` diagnostics the function currently being
69
+ * emitted produced. Unsupported constructs can leave the generator with a
70
+ * partially-bound tree; reporting the resulting invariant failures as
71
+ * "Internal: …" only buries the actionable error, so they are suppressed
72
+ * while this is non-zero.
73
+ */
74
+ unsupportedCount = 0;
75
+ /** Emit DWARF debug metadata (`--debug`). */
76
+ readonly debug: boolean;
77
+ /** Numbered metadata definitions, emitted after the functions. */
78
+ metadataCounter = 0;
79
+ readonly metadata: string[] = [];
80
+ readonly debugFiles = new Map<string, string>();
81
+ readonly debugSubprograms = new Map<number, string>();
82
+ readonly debugLocations = new Map<string, string>();
83
+ debugUnit?: string;
84
+ debugFlags: string[] = [];
85
+ debugSubroutineType?: string;
62
86
  current!: FunctionState;
63
87
 
64
88
  constructor(sourceFile: SourceFileNode, diagnostics: DiagnosticBag, options: CodegenOptions = {}) {
@@ -69,6 +93,7 @@ export class GeneratorContext {
69
93
  this.modules = options.modules ?? {};
70
94
  this.moduleHints = options.moduleHints ?? {};
71
95
  this.target = options.target ?? { platform: process.platform, arch: process.arch };
96
+ this.debug = options.debug === true;
72
97
  this.resolveImports();
73
98
  }
74
99
 
@@ -120,7 +145,7 @@ export class GeneratorContext {
120
145
  DiagnosticCode.ModuleNotFound,
121
146
  `Module '"${specifier}"' has no exported member '${importedName}'`,
122
147
  specifierNode.name,
123
- this.sourceFile.fileName,
148
+ this.fileNameOf(specifierNode.name),
124
149
  );
125
150
  this.missingModuleSymbols.add(symbol.id);
126
151
  }
@@ -148,7 +173,7 @@ export class GeneratorContext {
148
173
  DiagnosticCode.ModuleNotFound,
149
174
  `module '${specifier}' is provided by the '${provider}' extension; pass --ext ${provider}`,
150
175
  declaration.moduleSpecifier,
151
- this.sourceFile.fileName,
176
+ this.fileNameOf(declaration.moduleSpecifier),
152
177
  );
153
178
  this.markMissingModuleSymbols(declaration);
154
179
  return;
@@ -165,7 +190,7 @@ export class GeneratorContext {
165
190
  `module '${specifier}' is not supported: xbintsc can import built-in platform modules, ` +
166
191
  "relative '.ts' files and ESM packages under node_modules; CommonJS packages are not supported",
167
192
  declaration.moduleSpecifier,
168
- this.sourceFile.fileName,
193
+ this.fileNameOf(declaration.moduleSpecifier),
169
194
  );
170
195
  for (const symbol of symbols) this.missingModuleSymbols.add(symbol.id);
171
196
  }
@@ -230,11 +255,12 @@ export class GeneratorContext {
230
255
  // -- emission primitives -------------------------------------------------
231
256
 
232
257
  emit(line: string): void {
233
- this.current.buffer.push(line);
258
+ const location = this.debug ? this.current.debugLocation : undefined;
259
+ this.current.buffer.push(location === undefined ? line : `${line}, !dbg ${location}`);
234
260
  }
235
261
 
236
262
  terminate(line: string): void {
237
- this.current.buffer.push(` ${line}`);
263
+ this.emit(` ${line}`);
238
264
  this.current.terminated = true;
239
265
  }
240
266
 
@@ -283,6 +309,19 @@ export class GeneratorContext {
283
309
  return ptr;
284
310
  }
285
311
 
312
+ /** Lay already-computed values out as a contiguous `i64*` argument buffer. */
313
+ emitValueArguments(values: readonly string[]): { argc: string; ptr: string } {
314
+ if (values.length === 0) return { argc: "0", ptr: "null" };
315
+ const ptr = `%args${this.current.allocas.length}`;
316
+ this.current.allocas.push(`${ptr} = alloca i64, i32 ${values.length}`);
317
+ for (let index = 0; index < values.length; index++) {
318
+ const slot = this.reg();
319
+ this.emit(` ${slot} = getelementptr i64, i64* ${ptr}, i32 ${index}`);
320
+ this.emit(` store i64 ${values[index]}, i64* ${slot}`);
321
+ }
322
+ return { argc: String(values.length), ptr };
323
+ }
324
+
286
325
  const(value: bigint): string {
287
326
  return i64(value);
288
327
  }
@@ -333,11 +372,15 @@ export class GeneratorContext {
333
372
  const slot = this.current.slots.get(symbol.id);
334
373
  if (!slot) {
335
374
  // Referenced from an inner function without a capture slot: this should
336
- // be impossible once the binder has run, but stay defensive.
337
- this.diagnostics.error(
338
- DiagnosticCode.CodegenError,
339
- `Internal: no slot for '${symbol.name}' in ${this.current.fn.name}`,
340
- );
375
+ // be impossible once the binder has run, but stay defensive. After an
376
+ // unsupported construct the tree can be partially bound, so do not add
377
+ // an "Internal" error on top of the actionable one.
378
+ if (this.unsupportedCount === 0) {
379
+ this.diagnostics.error(
380
+ DiagnosticCode.CodegenError,
381
+ `Internal: no slot for '${symbol.name}' in ${this.current.fn.name}`,
382
+ );
383
+ }
341
384
  return i64(XT_UNDEFINED);
342
385
  }
343
386
  const value = this.reg();
@@ -362,7 +405,9 @@ export class GeneratorContext {
362
405
  }
363
406
  const slot = this.current.slots.get(symbol.id);
364
407
  if (!slot) {
365
- this.diagnostics.error(DiagnosticCode.CodegenError, `Internal: no slot for '${symbol.name}'`);
408
+ if (this.unsupportedCount === 0) {
409
+ this.diagnostics.error(DiagnosticCode.CodegenError, `Internal: no slot for '${symbol.name}'`);
410
+ }
366
411
  return;
367
412
  }
368
413
  if (!slot.boxed) {
@@ -390,28 +435,94 @@ export class GeneratorContext {
390
435
  stringValue(text: string): string {
391
436
  const entry = this.internString(text);
392
437
  const result = this.reg();
393
- this.emit(` ${result} = call i64 @xt_string_new(i8* ${entry.label}, i64 ${entry.length})`);
438
+ // The value is created once in `main` and shared by every use, so a string
439
+ // literal (especially a property name in a hot loop) no longer allocates on
440
+ // each evaluation.
441
+ this.emit(` ${result} = load i64, i64* ${entry.slot}`);
394
442
  return result;
395
443
  }
396
444
 
397
- internString(text: string): { label: string; length: number } {
445
+ internString(text: string): { label: string; length: number; slot: string } {
398
446
  const existing = this.strings.get(text);
399
447
  if (existing) return existing;
400
- const label = `@.str.${this.stringCounter++}`;
448
+ const index = this.stringCounter++;
449
+ const label = `@.str.${index}`;
450
+ const slot = `@.strv.${index}`;
401
451
  const bytes = utf8Bytes(text);
402
- const entry = { label, length: bytes.length };
452
+ const entry = { label, length: bytes.length, slot };
403
453
  this.strings.set(text, entry);
404
454
  this.globals.push(`${label} = private unnamed_addr constant [${bytes.length + 1} x i8] c"${escapeBytes(bytes)}\\00"`);
455
+ // A GC-rooted global holding the runtime string value, initialised in main.
456
+ this.globals.push(`${slot} = internal global i64 0`);
405
457
  return entry;
406
458
  }
407
459
 
460
+ /**
461
+ * Intern a string purely as static module data (no `xt_string` value), for
462
+ * metadata such as shadow-stack frame names and file paths.
463
+ */
464
+ internStatic(text: string): string {
465
+ const existing = this.staticStrings.get(text);
466
+ if (existing) return existing;
467
+ const label = `@.meta.${this.staticCounter++}`;
468
+ const bytes = utf8Bytes(text);
469
+ this.staticStrings.set(text, label);
470
+ this.globals.push(`${label} = private unnamed_addr constant [${bytes.length + 1} x i8] c"${escapeBytes(bytes)}\\00"`);
471
+ return label;
472
+ }
473
+
474
+ /** Strip a bundler-generated module prefix (`m1_`) from a display name. */
475
+ displayNameOf(name: string): string {
476
+ for (const prefix of this.sourceFile.modulePrefixes ?? []) {
477
+ if (name.startsWith(prefix) && name.length > prefix.length) return name.slice(prefix.length);
478
+ }
479
+ return name;
480
+ }
481
+
482
+ /** 1-based line and column of `node` in the module it came from. */
483
+ positionOf(node: Node): { line: number; column: number } {
484
+ const file = this.fileNameOf(node);
485
+ const text = this.sourceFile.moduleTexts?.get(file) ?? this.sourceFile.text;
486
+ let starts = this.lineStartsByFile.get(file);
487
+ if (!starts) {
488
+ starts = [0];
489
+ for (let index = 0; index < text.length; index++) {
490
+ if (text.charCodeAt(index) === 10) starts.push(index + 1);
491
+ }
492
+ this.lineStartsByFile.set(file, starts);
493
+ }
494
+ const offset = Math.max(0, Math.min(node.start, text.length));
495
+ let low = 0;
496
+ let high = starts.length - 1;
497
+ while (low < high) {
498
+ const middle = (low + high + 1) >> 1;
499
+ if (starts[middle]! <= offset) low = middle;
500
+ else high = middle - 1;
501
+ }
502
+ return { line: low + 1, column: offset - starts[low]! + 1 };
503
+ }
504
+
505
+ /** 1-based line of `node` in the module it came from (for stack frames). */
506
+ lineOf(node: Node): number {
507
+ return this.positionOf(node).line;
508
+ }
509
+
510
+ /** The source file a node belongs to. Bundling merges every module into one
511
+ * `SourceFile` while keeping module-local offsets, so the origin map is what
512
+ * keeps diagnostics pointing at the file the user actually wrote.
513
+ */
514
+ fileNameOf(node: Node): string {
515
+ return this.sourceFile.origins?.get(node) ?? this.sourceFile.fileName;
516
+ }
517
+
408
518
  unsupported(node: Node, what: string): void {
409
519
  const label = kindName(node.kind);
520
+ this.unsupportedCount++;
410
521
  this.diagnostics.error(
411
522
  DiagnosticCode.UnsupportedFeature,
412
523
  `xbintsc does not yet support this ${what} (${label})`,
413
524
  node,
414
- this.sourceFile.fileName,
525
+ this.fileNameOf(node),
415
526
  );
416
527
  }
417
528
 
@@ -429,7 +540,7 @@ export class GeneratorContext {
429
540
  DiagnosticCode.UnsupportedFeature,
430
541
  `CommonJS \`require()\` is not supported; ${replacement}`,
431
542
  node,
432
- this.sourceFile.fileName,
543
+ this.fileNameOf(node),
433
544
  );
434
545
  }
435
546
  }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * DWARF/CodeView metadata for `xbintsc build --debug`.
3
+ *
4
+ * The generated IR declares a compile unit, one `!DISubprogram` per compiled
5
+ * function and one cached `!DILocation` per statement position. With `-g`,
6
+ * clang turns these into DWARF (Linux/macOS) or CodeView (Windows), so a native
7
+ * debugger maps instructions back to the TypeScript source.
8
+ *
9
+ * Metadata is only emitted when debug is enabled: the nodes are large, and the
10
+ * default IR must stay byte-identical for the self-hosting fixpoint.
11
+ */
12
+
13
+ import type { Node } from "../../ast/nodes.js";
14
+ import type { FunctionInfo } from "../../binder/binder.js";
15
+ import { escapeBytes, utf8Bytes } from "./tables.js";
16
+ import type { Generator } from "./generator.js";
17
+
18
+ export interface DebugMethods {
19
+ /** Allocate a numbered metadata node (`!0`, `!1`, …). */
20
+ metadataId(this: Generator): string;
21
+ /** Allocate a metadata node and register its definition. */
22
+ addMetadata(this: Generator, definition: string): string;
23
+ /** `!DIFile` for a source path, normalising separators like `source_filename`. */
24
+ debugFileFor(this: Generator, fileName: string): string;
25
+ /** `!DISubprogram` for a compiled function (created on first use). */
26
+ debugSubprogramFor(this: Generator, fn: FunctionInfo): string | undefined;
27
+ /**
28
+ * Point subsequent instructions at `node`'s source position. Locations are
29
+ * cached per function/line/column so a statement emits one metadata node
30
+ * instead of one per instruction.
31
+ */
32
+ setDebugLocation(this: Generator, node: Node): void;
33
+ /** Module-level metadata declarations and definitions (`--debug`). */
34
+ debugMetadataLines(this: Generator): string[];
35
+ }
36
+
37
+ function escapeMetadata(text: string): string {
38
+ return escapeBytes(utf8Bytes(text));
39
+ }
40
+
41
+ /**
42
+ * Create the compile unit and module flags once. LLVM requires the `Debug Info
43
+ * Version` flag for any `!dbg` attachment, and clang only emits DWARF/CodeView
44
+ * for a module that declares a compile unit.
45
+ */
46
+ function ensureDebugUnit(self: Generator): void {
47
+ if (self.debugUnit !== undefined) return;
48
+ const entryFile = self.debugFileFor(self.sourceFile.fileName);
49
+ const unit = self.addMetadata(
50
+ 'distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: ' +
51
+ `${entryFile}, producer: "xbintsc", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)`,
52
+ );
53
+ self.debugUnit = unit;
54
+ self.debugFlags.push(
55
+ self.addMetadata('!{i32 7, !"Dwarf Version", i32 4}'),
56
+ self.addMetadata('!{i32 2, !"Debug Info Version", i32 3}'),
57
+ );
58
+ const types = self.addMetadata("!{null}");
59
+ self.debugSubroutineType = self.addMetadata(`!DISubroutineType(types: ${types})`);
60
+ }
61
+
62
+ export const debugMethods: DebugMethods = {
63
+ metadataId() {
64
+ return `!${this.metadataCounter++}`;
65
+ },
66
+
67
+ addMetadata(definition) {
68
+ const id = this.metadataId();
69
+ this.metadata.push(`${id} = ${definition}`);
70
+ return id;
71
+ },
72
+
73
+ debugFileFor(fileName) {
74
+ const normalized = fileName.split("\\").join("/");
75
+ const existing = this.debugFiles.get(normalized);
76
+ if (existing) return existing;
77
+ const slash = normalized.lastIndexOf("/");
78
+ const directory = slash >= 0 ? normalized.slice(0, slash) : ".";
79
+ const name = slash >= 0 ? normalized.slice(slash + 1) : normalized;
80
+ const id = this.addMetadata(
81
+ `!DIFile(filename: "${escapeMetadata(name)}", directory: "${escapeMetadata(directory)}")`,
82
+ );
83
+ this.debugFiles.set(normalized, id);
84
+ return id;
85
+ },
86
+
87
+ debugSubprogramFor(fn) {
88
+ if (!this.debug) return undefined;
89
+ const existing = this.debugSubprograms.get(fn.id);
90
+ if (existing) return existing;
91
+ ensureDebugUnit(this);
92
+ const file = this.debugFileFor(this.fileNameOf(fn.node));
93
+ const position = this.positionOf(fn.node);
94
+ const displayName = fn.isModule
95
+ ? "module"
96
+ : fn.isConstructor && fn.classInfo
97
+ ? `new ${this.displayNameOf(fn.classInfo.name)}`
98
+ : this.displayNameOf(fn.name.length > 0 ? fn.name : "anonymous");
99
+ const subprogram = this.addMetadata(
100
+ 'distinct !DISubprogram(name: ' +
101
+ `"${escapeMetadata(displayName)}", scope: ${this.debugUnit}, file: ${file}, ` +
102
+ `line: ${position.line}, type: ${this.debugSubroutineType}, scopeLine: ${position.line}, ` +
103
+ `spFlags: DISPFlagDefinition, unit: ${this.debugUnit})`,
104
+ );
105
+ this.debugSubprograms.set(fn.id, subprogram);
106
+ return subprogram;
107
+ },
108
+
109
+ setDebugLocation(node) {
110
+ if (!this.debug || this.current.debugSubprogram === undefined) return;
111
+ const position = this.positionOf(node);
112
+ const key = `${this.current.debugSubprogram}:${position.line}:${position.column}`;
113
+ let location = this.debugLocations.get(key);
114
+ if (!location) {
115
+ location = this.addMetadata(
116
+ `!DILocation(line: ${position.line}, column: ${position.column}, scope: ${this.current.debugSubprogram})`,
117
+ );
118
+ this.debugLocations.set(key, location);
119
+ }
120
+ this.current.debugLocation = location;
121
+ },
122
+
123
+ debugMetadataLines() {
124
+ if (!this.debug || this.debugUnit === undefined) return [];
125
+ return [
126
+ `!llvm.dbg.cu = !{${this.debugUnit}}`,
127
+ `!llvm.module.flags = !{${this.debugFlags.join(", ")}}`,
128
+ ...this.metadata,
129
+ "",
130
+ ];
131
+ },
132
+ };
@@ -58,9 +58,7 @@ export const assignmentExpressionMethods: AssignmentExpressionMethods = {
58
58
  this.unsupported(node, `assignment operator '${operator}'`);
59
59
  return i64(XT_UNDEFINED);
60
60
  }
61
- const result = this.reg();
62
- this.emit(` ${result} = call i64 @${runtime}(i64 ${current}, i64 ${right})`);
63
- value = result;
61
+ value = this.emitBinaryOperation(current, right, binaryOperator, runtime);
64
62
  }
65
63
  this.emitAssignmentTarget(target, value);
66
64
  return value;
@@ -120,7 +118,7 @@ export const assignmentExpressionMethods: AssignmentExpressionMethods = {
120
118
  ? `Cannot assign to '${symbol.name}' because it is an import.`
121
119
  : `Cannot assign to '${symbol.name}' because it is a constant.`,
122
120
  identifier,
123
- this.sourceFile.fileName,
121
+ this.fileNameOf(identifier),
124
122
  );
125
123
  return;
126
124
  }
@@ -187,9 +185,13 @@ export const assignmentExpressionMethods: AssignmentExpressionMethods = {
187
185
 
188
186
  /** `{ a, b } = value` / `for ({ a } of xs)`: assign each property of `value`. */
189
187
  emitObjectAssignmentTarget(pattern: ObjectLiteralExpression, value: string): void {
188
+ const excludedKeys: string[] = [];
190
189
  for (const property of pattern.properties) {
191
190
  if (property.kind === SyntaxKind.SpreadElement) {
192
- this.unsupported(property, "object rest destructuring");
191
+ const rest = this.reg();
192
+ const keys = this.emitValueArguments(excludedKeys);
193
+ this.emit(` ${rest} = call i64 @xt_object_rest(i64 ${value}, i32 ${keys.argc}, i64* ${keys.ptr})`);
194
+ this.emitAssignmentTarget((property as SpreadElement).expression, rest);
193
195
  continue;
194
196
  }
195
197
  if (property.kind === SyntaxKind.PropertyAssignment) {
@@ -200,6 +202,7 @@ export const assignmentExpressionMethods: AssignmentExpressionMethods = {
200
202
  } else {
201
203
  key = this.stringValue(propertyNameText(assignment.name));
202
204
  }
205
+ excludedKeys.push(key);
203
206
  let target: Expression = assignment.initializer;
204
207
  let fallback: Expression | undefined;
205
208
  if (isPlainAssignment(target)) {
@@ -215,6 +218,7 @@ export const assignmentExpressionMethods: AssignmentExpressionMethods = {
215
218
  }
216
219
  const shorthand = property as ShorthandPropertyAssignment;
217
220
  const key = this.stringValue(shorthand.name.text);
221
+ excludedKeys.push(key);
218
222
  let elementValue = this.reg();
219
223
  this.emit(` ${elementValue} = call i64 @xt_get(i64 ${value}, i64 ${key})`);
220
224
  if (shorthand.initializer) elementValue = this.emitBindingDefault(elementValue, shorthand.initializer);
@@ -7,15 +7,34 @@ import {
7
7
  BinaryOperator,
8
8
  PostfixUnaryOperator,
9
9
  PrefixUnaryOperator,
10
+ SyntaxKind,
10
11
  type BinaryExpression,
11
12
  type ConditionalExpression,
13
+ type Expression,
14
+ type Identifier,
12
15
  type PostfixUnaryExpression,
13
16
  type PrefixUnaryExpression,
14
17
  } from "../../../ast/nodes.js";
15
18
  import { i64, numberLiteral, XT_UNDEFINED } from "../../values.js";
16
- import { BINARY_RUNTIME, isAssignmentOperator } from "../tables.js";
19
+ import { BINARY_RUNTIME, BUILTIN_INSTANCEOF, isAssignmentOperator } from "../tables.js";
17
20
  import type { Generator } from "../generator.js";
18
21
 
22
+ /** True for an expression whose value is known to be a string. */
23
+ function isStringLike(node: Expression): boolean {
24
+ return node.kind === SyntaxKind.StringLiteral || node.kind === SyntaxKind.NoSubstitutionTemplateLiteral;
25
+ }
26
+
27
+ /**
28
+ * The built-in constructor a right-hand `instanceof` operand names, if any.
29
+ * A user binding with the same name shadows it.
30
+ */
31
+ function builtinInstanceofId(generator: Generator, expression: Expression): number | undefined {
32
+ if (expression.kind !== SyntaxKind.Identifier) return undefined;
33
+ const identifier = expression as Identifier;
34
+ if (generator.binding.symbolOfIdentifier.get(identifier)) return undefined;
35
+ return BUILTIN_INSTANCEOF[identifier.text];
36
+ }
37
+
19
38
  export interface OperatorExpressionMethods {
20
39
  emitBinaryOrAssignment(this: Generator, node: BinaryExpression): string;
21
40
  emitShortCircuit(this: Generator, node: BinaryExpression, kind: "and" | "or" | "nullish"): string;
@@ -44,6 +63,17 @@ export const operatorExpressionMethods: OperatorExpressionMethods = {
44
63
  default:
45
64
  break;
46
65
  }
66
+ // `x instanceof Array` and friends: the built-in constructor is not a
67
+ // first-class value, so compare by the value's runtime kind instead.
68
+ if (operator === BinaryOperator.InstanceOf) {
69
+ const builtin = builtinInstanceofId(this, node.right);
70
+ if (builtin !== undefined) {
71
+ const leftValue = this.emitExpression(node.left);
72
+ const result = this.reg();
73
+ this.emit(` ${result} = call i64 @xt_instance_of_builtin(i64 ${leftValue}, i32 ${builtin})`);
74
+ return result;
75
+ }
76
+ }
47
77
  const left = this.emitExpression(node.left);
48
78
  const right = this.emitExpression(node.right);
49
79
  const runtime = BINARY_RUNTIME[operator];
@@ -51,9 +81,10 @@ export const operatorExpressionMethods: OperatorExpressionMethods = {
51
81
  this.unsupported(node, `operator '${operator}'`);
52
82
  return i64(XT_UNDEFINED);
53
83
  }
54
- const result = this.reg();
55
- this.emit(` ${result} = call i64 @${runtime}(i64 ${left}, i64 ${right})`);
56
- return result;
84
+ // `+` concatenates when either side is known to be a string; skip the
85
+ // numeric tag checks for that shape and call the runtime directly.
86
+ const knownStringConcat = operator === "+" && (isStringLike(node.left) || isStringLike(node.right));
87
+ return this.emitBinaryOperation(left, right, operator, runtime, !knownStringConcat);
57
88
  },
58
89
 
59
90
  emitShortCircuit(node: BinaryExpression, kind: "and" | "or" | "nullish"): string {
@@ -159,8 +190,12 @@ export const operatorExpressionMethods: OperatorExpressionMethods = {
159
190
  const one = numberLiteral(1);
160
191
  const current = this.emitExpression(node.operand);
161
192
  const fn = node.operator === PrefixUnaryOperator.PlusPlus ? "xt_add" : "xt_sub";
162
- const next = this.reg();
163
- this.emit(` ${next} = call i64 @${fn}(i64 ${current}, i64 ${one})`);
193
+ const next = this.emitBinaryOperation(
194
+ current,
195
+ one,
196
+ node.operator === PrefixUnaryOperator.PlusPlus ? "+" : "-",
197
+ fn,
198
+ );
164
199
  this.emitAssignmentTarget(node.operand, next);
165
200
  return next;
166
201
  }
@@ -174,8 +209,12 @@ export const operatorExpressionMethods: OperatorExpressionMethods = {
174
209
  const one = numberLiteral(1);
175
210
  const current = this.emitExpression(node.operand);
176
211
  const fn = node.operator === PostfixUnaryOperator.PlusPlus ? "xt_add" : "xt_sub";
177
- const next = this.reg();
178
- this.emit(` ${next} = call i64 @${fn}(i64 ${current}, i64 ${one})`);
212
+ const next = this.emitBinaryOperation(
213
+ current,
214
+ one,
215
+ node.operator === PostfixUnaryOperator.PlusPlus ? "+" : "-",
216
+ fn,
217
+ );
179
218
  this.emitAssignmentTarget(node.operand, next);
180
219
  return current;
181
220
  },
@@ -193,7 +193,7 @@ export const primaryExpressionMethods: PrimaryExpressionMethods = {
193
193
  DiagnosticCode.CodegenError,
194
194
  `Imported binding '${identifier.text}' cannot be used as a value`,
195
195
  identifier,
196
- this.sourceFile.fileName,
196
+ this.fileNameOf(identifier),
197
197
  );
198
198
  return i64(XT_UNDEFINED);
199
199
  }
@@ -244,7 +244,7 @@ export const primaryExpressionMethods: PrimaryExpressionMethods = {
244
244
  DiagnosticCode.CannotFindName,
245
245
  `Cannot find name '${identifier.text}'`,
246
246
  identifier,
247
- this.sourceFile.fileName,
247
+ this.fileNameOf(identifier),
248
248
  );
249
249
  return i64(XT_UNDEFINED);
250
250
  }
@@ -292,6 +292,9 @@ export const primaryExpressionMethods: PrimaryExpressionMethods = {
292
292
 
293
293
  emitNew(node: NewExpression): string {
294
294
  const callee = node.expression;
295
+ // A `new` expression is where the innermost frame's line should point for
296
+ // `Error.stack`; harmless for every other constructor.
297
+ this.emit(` call void @xt_stack_set_line(i32 ${this.lineOf(node)})`);
295
298
  if (callee.kind === SyntaxKind.Identifier) {
296
299
  const identifier = callee as Identifier;
297
300
  const symbol = this.binding.symbolOfIdentifier.get(identifier);
@@ -14,13 +14,29 @@ import { moduleMethods, type ModuleMethods } from "./module.js";
14
14
  import { statementMethods, type StatementMethods } from "./statements.js";
15
15
  import { expressionMethods, type ExpressionMethods } from "./expressions.js";
16
16
  import { callMethods, type CallMethods } from "./calls.js";
17
+ import { numericMethods, type NumericMethods } from "./numeric.js";
18
+ import { debugMethods, type DebugMethods } from "./debug.js";
17
19
  import type { CodegenOptions, CodegenResult } from "./state.js";
18
20
 
19
21
  export class Generator extends GeneratorContext {}
20
22
 
21
- export interface Generator extends ModuleMethods, StatementMethods, ExpressionMethods, CallMethods {}
23
+ export interface Generator
24
+ extends ModuleMethods,
25
+ StatementMethods,
26
+ ExpressionMethods,
27
+ CallMethods,
28
+ NumericMethods,
29
+ DebugMethods {}
22
30
 
23
- Object.assign(Generator.prototype, moduleMethods, statementMethods, expressionMethods, callMethods);
31
+ Object.assign(
32
+ Generator.prototype,
33
+ moduleMethods,
34
+ statementMethods,
35
+ expressionMethods,
36
+ callMethods,
37
+ numericMethods,
38
+ debugMethods,
39
+ );
24
40
 
25
41
  export type { CodegenOptions, CodegenResult } from "./state.js";
26
42