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
@@ -29,9 +29,16 @@ function isParameterProperty(parameter: Parameter): boolean {
29
29
  );
30
30
  }
31
31
 
32
+ /** A readable, IR-symbol-safe suffix for a function name. */
33
+ function symbolSuffix(name: string): string {
34
+ if (name.length === 0 || name.startsWith("<")) return "";
35
+ return name.replace(/[^A-Za-z0-9_]/g, "_").replace(/^_+/, "").slice(0, 32);
36
+ }
37
+
32
38
  export interface ModuleMethods {
33
39
  run(this: Generator): string;
34
40
  emitFunction(this: Generator, fn: FunctionInfo): void;
41
+ emitFunctionReturn(this: Generator, value: string): void;
35
42
  emitMain(this: Generator): void;
36
43
  functionName(this: Generator, fn: FunctionInfo): string;
37
44
  wrapAsync(this: Generator, value: string): string;
@@ -77,13 +84,32 @@ export const moduleMethods: ModuleMethods = {
77
84
  ];
78
85
  const runtimeDeclarations = new Set(RUNTIME_DECLARATIONS);
79
86
  const extraDeclarations = [...this.extraDeclarations].filter((line) => !runtimeDeclarations.has(line));
80
- return [...header, ...RUNTIME_DECLARATIONS, ...extraDeclarations, "", ...this.globals, "", ...this.functions, ""].join("\n");
87
+ return [
88
+ ...header,
89
+ ...RUNTIME_DECLARATIONS,
90
+ ...extraDeclarations,
91
+ "",
92
+ ...this.globals,
93
+ "",
94
+ ...this.functions,
95
+ "",
96
+ ...this.debugMetadataLines(),
97
+ ].join("\n");
81
98
  },
82
99
 
83
100
  // -- module level --------------------------------------------------------
84
101
 
85
102
  emitFunction(fn: FunctionInfo): void {
103
+ this.unsupportedCount = 0;
104
+ // `async function*` would need an async iterator and a promise-returning
105
+ // `.next()`; the generator/async lowering below would silently produce a
106
+ // synchronous generator instead, so reject it rather than miscompile.
107
+ if (fn.isAsync && fn.isGenerator) {
108
+ this.unsupported(fn.node, "async generator function");
109
+ return;
110
+ }
86
111
  const name = this.functionName(fn);
112
+ const debugSubprogram = this.debugSubprogramFor(fn);
87
113
  const state: FunctionState = {
88
114
  fn,
89
115
  buffer: [],
@@ -98,10 +124,14 @@ export const moduleMethods: ModuleMethods = {
98
124
  finallyStack: [],
99
125
  escapePointers: [],
100
126
  usesTry: false,
127
+ trackFrames: !fn.isGenerator,
128
+ ...(debugSubprogram ? { debugSubprogram } : {}),
101
129
  };
102
130
  this.current = state;
131
+ if (debugSubprogram) this.setDebugLocation(fn.node);
103
132
 
104
- const header = `define i64 @${name}(i64 %this, i64 %env, i32 %argc, i64* %argv) {`;
133
+ const debugSuffix = debugSubprogram ? ` !dbg ${debugSubprogram}` : "";
134
+ const header = `define i64 @${name}(i64 %this, i64 %env, i32 %argc, i64* %argv)${debugSuffix} {`;
105
135
  this.emit(`%saved.env = alloca i64`);
106
136
  this.emit(`store i64 %env, i64* %saved.env`);
107
137
  this.emit(`%saved.this = alloca i64`);
@@ -112,6 +142,23 @@ export const moduleMethods: ModuleMethods = {
112
142
  this.emit(`%saved.argv = alloca i64*`);
113
143
  this.emit(`store i64* %argv, i64** %saved.argv`);
114
144
 
145
+ // Shadow-stack frame: `Error.stack` and fatal-signal dumps walk this.
146
+ if (state.trackFrames) {
147
+ const displayName = fn.isModule
148
+ ? "module"
149
+ : fn.isConstructor && fn.classInfo
150
+ ? `new ${this.displayNameOf(fn.classInfo.name)}`
151
+ : this.displayNameOf(fn.name.length > 0 ? fn.name : "anonymous");
152
+ const namePointer = this.internStatic(displayName);
153
+ /* Normalise separators like `source_filename`: the compiler may run under
154
+ * Node (`\`) or as a self-hosted binary (`/`), and the IR must stay
155
+ * byte-identical across the two. */
156
+ const filePointer = this.internStatic(this.fileNameOf(fn.node).split("\\").join("/"));
157
+ this.emit(
158
+ ` call void @xt_stack_push(i8* ${namePointer}, i8* ${filePointer}, i32 ${this.lineOf(fn.node)})`,
159
+ );
160
+ }
161
+
115
162
  // Parameters.
116
163
  const parameterNodes = (fn.node as { parameters?: Parameter[] }).parameters ?? [];
117
164
  for (let index = 0; index < fn.params.length; index++) {
@@ -207,7 +254,7 @@ export const moduleMethods: ModuleMethods = {
207
254
  this.emitStatements(this.sourceFile.statements);
208
255
  } else if (fn.node.kind === SyntaxKind.ArrowFunction && (fn.node as ArrowFunction).body.kind !== SyntaxKind.Block) {
209
256
  const value = this.emitExpression((fn.node as ArrowFunction).body as Expression);
210
- this.terminate(`ret i64 ${fn.isAsync ? this.wrapAsync(value) : value}`);
257
+ this.emitFunctionReturn(fn.isAsync ? this.wrapAsync(value) : value);
211
258
  } else {
212
259
  const body = (fn.node as { body: Block }).body;
213
260
  if (body) this.emitStatements(body.statements);
@@ -215,7 +262,7 @@ export const moduleMethods: ModuleMethods = {
215
262
 
216
263
  if (!this.current.terminated) {
217
264
  const fallback = fn.isAsync ? this.wrapAsync(i64(XT_UNDEFINED)) : i64(XT_UNDEFINED);
218
- this.terminate(`ret i64 ${fallback}`);
265
+ this.emitFunctionReturn(fallback);
219
266
  }
220
267
 
221
268
  const escapes = state.usesTry
@@ -229,8 +276,22 @@ export const moduleMethods: ModuleMethods = {
229
276
  const moduleName = this.functionName(this.binding.moduleFunction);
230
277
  // Module bindings and class constructors live in globals; register them as
231
278
  // GC roots before the module body runs so a collection cannot free them.
232
- const roots = [...this.classGlobals.values(), ...this.moduleGlobals.values()];
279
+ const stringEntries = [...this.strings.values()];
280
+ const roots = [
281
+ ...this.classGlobals.values(),
282
+ ...this.moduleGlobals.values(),
283
+ ...stringEntries.map((entry) => entry.slot),
284
+ ];
233
285
  const rootLines = roots.map((name) => ` call void @xt_gc_add_root(i64* ${name})`);
286
+ // Every interned string literal is materialised once here and shared by all
287
+ // uses, so property names and other literals stop allocating per access.
288
+ const stringInit = stringEntries.flatMap((entry) => {
289
+ const register = `%strv.${entry.slot.replace("@.strv.", "")}`;
290
+ return [
291
+ ` ${register} = call i64 @xt_string_new(i8* ${entry.label}, i64 ${entry.length})`,
292
+ ` store i64 ${register}, i64* ${entry.slot}`,
293
+ ];
294
+ });
234
295
  this.functions.push(
235
296
  [
236
297
  "define i32 @main(i32 %argc, i8** %argv) {",
@@ -239,6 +300,7 @@ export const moduleMethods: ModuleMethods = {
239
300
  " call void @xt_gc_set_stack_base(i8* %stackbase.ptr)",
240
301
  " call void @xt_gc_init()",
241
302
  ...rootLines,
303
+ ...stringInit,
242
304
  " call void @xt_gc_arm()",
243
305
  " call void @xt_set_program_args(i32 %argc, i8** %argv)",
244
306
  ` %result = call i64 @${moduleName}(i64 ${i64(XT_UNDEFINED)}, i64 ${i64(XT_UNDEFINED)}, i32 0, i64* null)`,
@@ -252,7 +314,17 @@ export const moduleMethods: ModuleMethods = {
252
314
  },
253
315
 
254
316
  functionName(fn: FunctionInfo): string {
255
- return fn.isModule ? "xt_module" : `xt_fn_${fn.id}`;
317
+ if (fn.isModule) return "xt_module";
318
+ /* Keep the original name in the symbol so native debuggers, crash
319
+ * backtraces and `--emit ir` are readable (`xt_fn_3_sum`). */
320
+ const suffix = symbolSuffix(this.displayNameOf(fn.name));
321
+ return suffix.length > 0 ? `xt_fn_${fn.id}_${suffix}` : `xt_fn_${fn.id}`;
322
+ },
323
+
324
+ /** Emit the function's return, dropping its shadow-stack frame first. */
325
+ emitFunctionReturn(value: string): void {
326
+ if (this.current.trackFrames) this.emit(" call void @xt_stack_pop()");
327
+ this.terminate(`ret i64 ${value}`);
256
328
  },
257
329
 
258
330
  /** Wrap a returned value in a resolved promise (for `async` functions). */
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Inline fast paths for numeric operators.
3
+ *
4
+ * Every JavaScript operator used to be a runtime call (`xt_add`, `xt_lt`, …).
5
+ * That is correct but expensive in numeric code: a `for` loop paid three calls
6
+ * per iteration, and clang cannot inline across the generated IR / C runtime
7
+ * boundary. For two unboxed doubles the operation is just a hardware
8
+ * instruction, so emit that inline and keep the runtime call as the cold path
9
+ * for every other combination (strings, BigInt, coercion, …).
10
+ *
11
+ * Doubles are stored bit-for-bit in `xt_value`, so the tag check is an integer
12
+ * mask and the operation is `bitcast` → `fadd`/`fcmp` → `bitcast`.
13
+ */
14
+
15
+ import { i64, XT_FALSE, XT_NUMBER_MASK, XT_TRUE } from "../values.js";
16
+ import type { Generator } from "./generator.js";
17
+
18
+ export interface NumericMethods {
19
+ /**
20
+ * Emit `left <operator> right`, using an inline double fast path when both
21
+ * operands turn out to be numbers and {@link allowFastPath} is set.
22
+ */
23
+ emitBinaryOperation(
24
+ this: Generator,
25
+ left: string,
26
+ right: string,
27
+ operator: string,
28
+ runtime: string,
29
+ allowFastPath?: boolean,
30
+ ): string;
31
+ emitNumericBinary(this: Generator, left: string, right: string, instruction: string, runtime: string): string;
32
+ emitNumericComparison(this: Generator, left: string, right: string, predicate: string, runtime: string): string;
33
+ /** Emit an i1 SSA value that is true when `value` is an unboxed double. */
34
+ isNumberValue(this: Generator, value: string): string;
35
+ }
36
+
37
+ /** Canonical quiet NaN; the runtime uses the same pattern in `xt_from_double`. */
38
+ const CANONICAL_NAN = 0x7ff8000000000000n;
39
+
40
+ const NUMERIC_ARITHMETIC: Record<string, string | undefined> = {
41
+ "+": "fadd",
42
+ "-": "fsub",
43
+ "*": "fmul",
44
+ "/": "fdiv",
45
+ "%": "frem",
46
+ };
47
+
48
+ const NUMERIC_COMPARISON: Record<string, string | undefined> = {
49
+ "<": "olt",
50
+ "<=": "ole",
51
+ ">": "ogt",
52
+ ">=": "oge",
53
+ "==": "oeq",
54
+ "!=": "une",
55
+ "===": "oeq",
56
+ "!==": "une",
57
+ };
58
+
59
+ export const numericMethods: NumericMethods = {
60
+ emitBinaryOperation(left, right, operator, runtime, allowFastPath = true) {
61
+ if (allowFastPath) {
62
+ const instruction = NUMERIC_ARITHMETIC[operator];
63
+ if (instruction) return this.emitNumericBinary(left, right, instruction, runtime);
64
+ const predicate = NUMERIC_COMPARISON[operator];
65
+ if (predicate) return this.emitNumericComparison(left, right, predicate, runtime);
66
+ }
67
+ const result = this.reg();
68
+ this.emit(` ${result} = call i64 @${runtime}(i64 ${left}, i64 ${right})`);
69
+ return result;
70
+ },
71
+
72
+ /** Emit an i1 SSA value that is true when `value` is an unboxed double. */
73
+ isNumberValue(value: string): string {
74
+ const masked = this.reg();
75
+ this.emit(` ${masked} = and i64 ${value}, ${i64(XT_NUMBER_MASK)}`);
76
+ const result = this.reg();
77
+ this.emit(` ${result} = icmp ne i64 ${masked}, ${i64(XT_NUMBER_MASK)}`);
78
+ return result;
79
+ },
80
+
81
+ emitNumericBinary(left, right, instruction, runtime) {
82
+ const result = this.alloca();
83
+ const leftIsNumber = this.isNumberValue(left);
84
+ const rightIsNumber = this.isNumberValue(right);
85
+ const bothNumbers = this.reg();
86
+ this.emit(` ${bothNumbers} = and i1 ${leftIsNumber}, ${rightIsNumber}`);
87
+ const fast = this.label("num.fast");
88
+ const slow = this.label("num.slow");
89
+ const end = this.label("num.end");
90
+ this.terminate(`br i1 ${bothNumbers}, label %${fast}, label %${slow}`);
91
+
92
+ this.startBlock(fast);
93
+ const leftDouble = this.reg();
94
+ this.emit(` ${leftDouble} = bitcast i64 ${left} to double`);
95
+ const rightDouble = this.reg();
96
+ this.emit(` ${rightDouble} = bitcast i64 ${right} to double`);
97
+ const computed = this.reg();
98
+ this.emit(` ${computed} = ${instruction} double ${leftDouble}, ${rightDouble}`);
99
+ // Hardware NaNs carry many payloads; the value model only reserves the
100
+ // canonical quiet NaN, and `-NaN` would otherwise collide with XT_UNDEFINED.
101
+ const bits = this.reg();
102
+ this.emit(` ${bits} = bitcast double ${computed} to i64`);
103
+ const unordered = this.reg();
104
+ this.emit(` ${unordered} = fcmp uno double ${computed}, ${computed}`);
105
+ const canonical = this.reg();
106
+ this.emit(` ${canonical} = select i1 ${unordered}, i64 ${i64(CANONICAL_NAN)}, i64 ${bits}`);
107
+ this.emit(` store i64 ${canonical}, i64* ${result}`);
108
+ this.terminate(`br label %${end}`);
109
+
110
+ this.startBlock(slow);
111
+ const called = this.reg();
112
+ this.emit(` ${called} = call i64 @${runtime}(i64 ${left}, i64 ${right})`);
113
+ this.emit(` store i64 ${called}, i64* ${result}`);
114
+ this.terminate(`br label %${end}`);
115
+
116
+ this.startBlock(end);
117
+ const value = this.reg();
118
+ this.emit(` ${value} = load i64, i64* ${result}`);
119
+ return value;
120
+ },
121
+
122
+ emitNumericComparison(left, right, predicate, runtime) {
123
+ const result = this.alloca();
124
+ const leftIsNumber = this.isNumberValue(left);
125
+ const rightIsNumber = this.isNumberValue(right);
126
+ const bothNumbers = this.reg();
127
+ this.emit(` ${bothNumbers} = and i1 ${leftIsNumber}, ${rightIsNumber}`);
128
+ const fast = this.label("cmp.fast");
129
+ const slow = this.label("cmp.slow");
130
+ const end = this.label("cmp.end");
131
+ this.terminate(`br i1 ${bothNumbers}, label %${fast}, label %${slow}`);
132
+
133
+ this.startBlock(fast);
134
+ const leftDouble = this.reg();
135
+ this.emit(` ${leftDouble} = bitcast i64 ${left} to double`);
136
+ const rightDouble = this.reg();
137
+ this.emit(` ${rightDouble} = bitcast i64 ${right} to double`);
138
+ // Ordered comparisons are false for NaN and unordered `!=` is true, which
139
+ // is exactly ECMAScript's behaviour for two numbers.
140
+ const compared = this.reg();
141
+ this.emit(` ${compared} = fcmp ${predicate} double ${leftDouble}, ${rightDouble}`);
142
+ const value = this.reg();
143
+ this.emit(` ${value} = select i1 ${compared}, i64 ${i64(XT_TRUE)}, i64 ${i64(XT_FALSE)}`);
144
+ this.emit(` store i64 ${value}, i64* ${result}`);
145
+ this.terminate(`br label %${end}`);
146
+
147
+ this.startBlock(slow);
148
+ const called = this.reg();
149
+ this.emit(` ${called} = call i64 @${runtime}(i64 ${left}, i64 ${right})`);
150
+ this.emit(` store i64 ${called}, i64* ${result}`);
151
+ this.terminate(`br label %${end}`);
152
+
153
+ this.startBlock(end);
154
+ const merged = this.reg();
155
+ this.emit(` ${merged} = load i64, i64* ${result}`);
156
+ return merged;
157
+ },
158
+ };
@@ -50,6 +50,16 @@ export interface FunctionState {
50
50
  thisPtr?: string;
51
51
  /** Set when the function contains a `try`, forcing locals to live in memory. */
52
52
  usesTry: boolean;
53
+ /**
54
+ * True when the function pushes/pops a shadow-stack frame. Generator bodies
55
+ * are excluded: they switch stacks and can be suspended, so a global shadow
56
+ * stack would attribute their frames to the wrong caller.
57
+ */
58
+ trackFrames: boolean;
59
+ /** `!DISubprogram` for this function when DWARF emission is enabled. */
60
+ debugSubprogram?: string;
61
+ /** Current `!DILocation` attached to every emitted instruction. */
62
+ debugLocation?: string;
53
63
  }
54
64
 
55
65
  export interface CodegenResult {
@@ -74,6 +84,13 @@ export interface CodegenOptions {
74
84
  * defaults to the running process; it is overridable for tests.
75
85
  */
76
86
  readonly target?: { readonly platform: string; readonly arch: string };
87
+ /**
88
+ * Emit LLVM debug metadata (`!DICompileUnit`, `!DISubprogram`,
89
+ * `!DILocation`) so `clang -g` produces DWARF/CodeView for the generated
90
+ * program. Off by default: the metadata is large and only useful when
91
+ * debugging; `xbintsc build --debug` turns it on.
92
+ */
93
+ readonly debug?: boolean;
77
94
  }
78
95
 
79
96
  export const RUNTIME_DECLARATIONS: readonly string[] = [
@@ -93,6 +110,7 @@ export const RUNTIME_DECLARATIONS: readonly string[] = [
93
110
  "declare i64 @xt_this()",
94
111
  "declare i64 @xt_new(i64, i32, i64*)",
95
112
  "declare i64 @xt_instance_of(i64, i64)",
113
+ "declare i64 @xt_instance_of_builtin(i64, i32)",
96
114
  "declare i64 @xt_function_set_prototype(i64, i64)",
97
115
  "declare i64 @xt_function_get_prototype(i64)",
98
116
  "declare i64 @xt_object_new_with_proto(i64)",
@@ -225,6 +243,7 @@ export const RUNTIME_DECLARATIONS: readonly string[] = [
225
243
  "declare i64 @xt_object_entries(i64)",
226
244
  "declare i64 @xt_object_assign(i32, i64*)",
227
245
  "declare i64 @xt_object_spread(i64, i64)",
246
+ "declare i64 @xt_object_rest(i64, i32, i64*)",
228
247
  "declare i64 @xt_call_method(i64, i64, i32, i64*)",
229
248
  "declare i64 @xt_math_call(i64, i32, i64*)",
230
249
  "declare i64 @xt_parse_int(i32, i64*)",
@@ -298,6 +317,9 @@ export const RUNTIME_DECLARATIONS: readonly string[] = [
298
317
  "declare i8* @xt_try_enter()",
299
318
  "declare i64 @xt_try_exception(i8*)",
300
319
  "declare void @xt_try_leave(i8*)",
320
+ "declare void @xt_stack_push(i8*, i8*, i32)",
321
+ "declare void @xt_stack_pop()",
322
+ "declare void @xt_stack_set_line(i32)",
301
323
  "declare void @xt_console_log(i32, i64*)",
302
324
  "declare void @xt_console_info(i32, i64*)",
303
325
  "declare void @xt_console_warn(i32, i64*)",
@@ -255,7 +255,7 @@ export const controlFlowStatementMethods: ControlFlowStatementMethods = {
255
255
  this.runFinallysBeforeExit(0);
256
256
  if (this.current.terminated) return;
257
257
  this.popTryFramesTo(0);
258
- this.terminate(`ret i64 ${value}`);
258
+ this.emitFunctionReturn(value);
259
259
  },
260
260
 
261
261
  /** Emit `xt_try_leave` for every active frame above `depth` (innermost first). */
@@ -41,6 +41,9 @@ export const statementDispatchMethods: StatementDispatchMethods = {
41
41
  },
42
42
 
43
43
  emitStatement(statement: Statement): void {
44
+ /* Every instruction emitted while lowering this statement (including
45
+ * nested expressions) points at its source position when DWARF is on. */
46
+ this.setDebugLocation(statement);
44
47
  switch (statement.kind) {
45
48
  case SyntaxKind.ExpressionStatement:
46
49
  this.emitExpression((statement as { expression: Expression }).expression);
@@ -87,13 +87,19 @@ export const variableStatementMethods: VariableStatementMethods = {
87
87
  return;
88
88
  }
89
89
  const pattern = name as ObjectBindingPattern;
90
+ /* Keys named by the pattern are excluded from `...rest`. */
91
+ const excludedKeys: string[] = [];
90
92
  for (const element of pattern.elements) {
91
- const key = element.propertyName ?? (element.name.kind === SyntaxKind.Identifier ? element.name : undefined);
92
93
  if (element.dotDotDotToken) {
93
- this.unsupported(element, "object rest destructuring");
94
+ const rest = this.reg();
95
+ const keys = this.emitValueArguments(excludedKeys);
96
+ this.emit(` ${rest} = call i64 @xt_object_rest(i64 ${value}, i32 ${keys.argc}, i64* ${keys.ptr})`);
97
+ this.emitBindingPattern(element.name, rest);
94
98
  continue;
95
99
  }
100
+ const key = element.propertyName ?? (element.name.kind === SyntaxKind.Identifier ? element.name : undefined);
96
101
  const keyValue = this.stringValue(key ? propertyNameText(key) : "undefined");
102
+ excludedKeys.push(keyValue);
97
103
  let elementValue = this.reg();
98
104
  this.emit(` ${elementValue} = call i64 @xt_get(i64 ${value}, i64 ${keyValue})`);
99
105
  if (element.initializer) elementValue = this.emitBindingDefault(elementValue, element.initializer);
@@ -22,6 +22,32 @@ export function requiresSetjmpex(platform: string, arch: string): boolean {
22
22
  return platform === "win32" && arch === "arm64";
23
23
  }
24
24
 
25
+ /**
26
+ * Built-in constructors usable on the right of `instanceof`, mapped to the id
27
+ * `xt_instance_of_builtin` expects. Keep in sync with the `XT_INSTANCE_*` enum
28
+ * in `runtime/xt_builtins.c`; the typed-array ids follow the runtime's
29
+ * `XT_TA_*` order (Uint8Array first).
30
+ */
31
+ export const BUILTIN_INSTANCEOF: Readonly<Record<string, number>> = {
32
+ Array: 0,
33
+ Object: 1,
34
+ Function: 2,
35
+ Map: 3,
36
+ Set: 4,
37
+ Date: 5,
38
+ RegExp: 6,
39
+ Promise: 7,
40
+ Uint8Array: 16,
41
+ Int8Array: 17,
42
+ Uint8ClampedArray: 18,
43
+ Uint16Array: 19,
44
+ Int16Array: 20,
45
+ Uint32Array: 21,
46
+ Int32Array: 22,
47
+ Float32Array: 23,
48
+ Float64Array: 24,
49
+ };
50
+
25
51
  export const BINARY_RUNTIME: Record<string, string | undefined> = {
26
52
  [BinaryOperator.Add]: "xt_add",
27
53
  [BinaryOperator.Subtract]: "xt_sub",
@@ -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,