xbintsc 0.1.0

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 (124) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +191 -0
  3. package/bin/xbintsc.js +26 -0
  4. package/dist/src/ast/factory.d.ts +11 -0
  5. package/dist/src/ast/factory.js +36 -0
  6. package/dist/src/ast/factory.js.map +1 -0
  7. package/dist/src/ast/nodes.d.ts +848 -0
  8. package/dist/src/ast/nodes.js +247 -0
  9. package/dist/src/ast/nodes.js.map +1 -0
  10. package/dist/src/ast/visitor.d.ts +18 -0
  11. package/dist/src/ast/visitor.js +52 -0
  12. package/dist/src/ast/visitor.js.map +1 -0
  13. package/dist/src/binder/binder.d.ts +82 -0
  14. package/dist/src/binder/binder.js +443 -0
  15. package/dist/src/binder/binder.js.map +1 -0
  16. package/dist/src/cli/main.d.ts +16 -0
  17. package/dist/src/cli/main.js +191 -0
  18. package/dist/src/cli/main.js.map +1 -0
  19. package/dist/src/codegen/llvm.d.ts +31 -0
  20. package/dist/src/codegen/llvm.js +1178 -0
  21. package/dist/src/codegen/llvm.js.map +1 -0
  22. package/dist/src/codegen/values.d.ts +23 -0
  23. package/dist/src/codegen/values.js +32 -0
  24. package/dist/src/codegen/values.js.map +1 -0
  25. package/dist/src/diagnostics/diagnostic.d.ts +69 -0
  26. package/dist/src/diagnostics/diagnostic.js +104 -0
  27. package/dist/src/diagnostics/diagnostic.js.map +1 -0
  28. package/dist/src/diagnostics/source.d.ts +43 -0
  29. package/dist/src/diagnostics/source.js +73 -0
  30. package/dist/src/diagnostics/source.js.map +1 -0
  31. package/dist/src/driver/cache.d.ts +37 -0
  32. package/dist/src/driver/cache.js +82 -0
  33. package/dist/src/driver/cache.js.map +1 -0
  34. package/dist/src/driver/compiler.d.ts +41 -0
  35. package/dist/src/driver/compiler.js +128 -0
  36. package/dist/src/driver/compiler.js.map +1 -0
  37. package/dist/src/driver/index.d.ts +4 -0
  38. package/dist/src/driver/index.js +5 -0
  39. package/dist/src/driver/index.js.map +1 -0
  40. package/dist/src/driver/paths.d.ts +4 -0
  41. package/dist/src/driver/paths.js +22 -0
  42. package/dist/src/driver/paths.js.map +1 -0
  43. package/dist/src/driver/toolchain.d.ts +43 -0
  44. package/dist/src/driver/toolchain.js +76 -0
  45. package/dist/src/driver/toolchain.js.map +1 -0
  46. package/dist/src/extensions/node.d.ts +9 -0
  47. package/dist/src/extensions/node.js +20 -0
  48. package/dist/src/extensions/node.js.map +1 -0
  49. package/dist/src/extensions/registry.d.ts +45 -0
  50. package/dist/src/extensions/registry.js +76 -0
  51. package/dist/src/extensions/registry.js.map +1 -0
  52. package/dist/src/index.d.ts +18 -0
  53. package/dist/src/index.js +19 -0
  54. package/dist/src/index.js.map +1 -0
  55. package/dist/src/lexer/scanner.d.ts +48 -0
  56. package/dist/src/lexer/scanner.js +650 -0
  57. package/dist/src/lexer/scanner.js.map +1 -0
  58. package/dist/src/lexer/token.d.ts +170 -0
  59. package/dist/src/lexer/token.js +250 -0
  60. package/dist/src/lexer/token.js.map +1 -0
  61. package/dist/src/parser/parser.d.ts +127 -0
  62. package/dist/src/parser/parser.js +2334 -0
  63. package/dist/src/parser/parser.js.map +1 -0
  64. package/dist/tests/binder/binder.test.d.ts +1 -0
  65. package/dist/tests/binder/binder.test.js +89 -0
  66. package/dist/tests/binder/binder.test.js.map +1 -0
  67. package/dist/tests/cli/main.test.d.ts +1 -0
  68. package/dist/tests/cli/main.test.js +68 -0
  69. package/dist/tests/cli/main.test.js.map +1 -0
  70. package/dist/tests/cli/process.test.d.ts +9 -0
  71. package/dist/tests/cli/process.test.js +57 -0
  72. package/dist/tests/cli/process.test.js.map +1 -0
  73. package/dist/tests/codegen/llvm.test.d.ts +1 -0
  74. package/dist/tests/codegen/llvm.test.js +93 -0
  75. package/dist/tests/codegen/llvm.test.js.map +1 -0
  76. package/dist/tests/driver/cache.test.d.ts +1 -0
  77. package/dist/tests/driver/cache.test.js +66 -0
  78. package/dist/tests/driver/cache.test.js.map +1 -0
  79. package/dist/tests/driver/compiler.test.d.ts +1 -0
  80. package/dist/tests/driver/compiler.test.js +80 -0
  81. package/dist/tests/driver/compiler.test.js.map +1 -0
  82. package/dist/tests/driver/toolchain.test.d.ts +1 -0
  83. package/dist/tests/driver/toolchain.test.js +60 -0
  84. package/dist/tests/driver/toolchain.test.js.map +1 -0
  85. package/dist/tests/e2e/compile.test.d.ts +5 -0
  86. package/dist/tests/e2e/compile.test.js +93 -0
  87. package/dist/tests/e2e/compile.test.js.map +1 -0
  88. package/dist/tests/extensions/registry.test.d.ts +1 -0
  89. package/dist/tests/extensions/registry.test.js +47 -0
  90. package/dist/tests/extensions/registry.test.js.map +1 -0
  91. package/dist/tests/helpers.d.ts +23 -0
  92. package/dist/tests/helpers.js +41 -0
  93. package/dist/tests/helpers.js.map +1 -0
  94. package/dist/tests/lexer/scanner.test.d.ts +1 -0
  95. package/dist/tests/lexer/scanner.test.js +95 -0
  96. package/dist/tests/lexer/scanner.test.js.map +1 -0
  97. package/dist/tests/parser/parser.test.d.ts +1 -0
  98. package/dist/tests/parser/parser.test.js +137 -0
  99. package/dist/tests/parser/parser.test.js.map +1 -0
  100. package/package.json +49 -0
  101. package/runtime/ext_node.c +42 -0
  102. package/runtime/rt.h +177 -0
  103. package/runtime/xt_runtime.c +682 -0
  104. package/scripts/build-runtime.mjs +34 -0
  105. package/src/ast/factory.ts +37 -0
  106. package/src/ast/nodes.ts +1111 -0
  107. package/src/ast/visitor.ts +60 -0
  108. package/src/binder/binder.ts +549 -0
  109. package/src/cli/main.ts +210 -0
  110. package/src/codegen/llvm.ts +1324 -0
  111. package/src/codegen/values.ts +36 -0
  112. package/src/diagnostics/diagnostic.ts +132 -0
  113. package/src/diagnostics/source.ts +97 -0
  114. package/src/driver/cache.ts +96 -0
  115. package/src/driver/compiler.ts +197 -0
  116. package/src/driver/index.ts +4 -0
  117. package/src/driver/paths.ts +24 -0
  118. package/src/driver/toolchain.ts +103 -0
  119. package/src/extensions/node.ts +23 -0
  120. package/src/extensions/registry.ts +101 -0
  121. package/src/index.ts +33 -0
  122. package/src/lexer/scanner.ts +650 -0
  123. package/src/lexer/token.ts +266 -0
  124. package/src/parser/parser.ts +2396 -0
@@ -0,0 +1,1178 @@
1
+ /**
2
+ * LLVM IR code generation.
3
+ *
4
+ * The generator lowers the bound AST to textual LLVM IR. It follows three
5
+ * rules that keep the output simple and correct without a register allocator
6
+ * of our own:
7
+ *
8
+ * 1. Every value crossing a statement or block boundary lives in an
9
+ * `alloca` (emitted in the entry block so LLVM's mem2reg can promote it).
10
+ * 2. Control flow is expressed with explicit basic blocks; conditionals and
11
+ * short-circuit operators materialise into a temporary slot instead of
12
+ * `phi` nodes, which the optimizer folds away.
13
+ * 3. All JavaScript semantics (addition with coercion, comparisons, member
14
+ * lookup, ...) are delegated to the C runtime through `@xt_*` calls.
15
+ *
16
+ * Later pipeline stages (optimization passes, object emission) live in the
17
+ * driver; this module only produces IR text.
18
+ */
19
+ import { SyntaxKind, } from "../ast/nodes.js";
20
+ import { AssignmentOperator, BinaryOperator, PrefixUnaryOperator, PostfixUnaryOperator, } from "../ast/nodes.js";
21
+ import { bind, SymbolKind, } from "../binder/binder.js";
22
+ import { DiagnosticCode } from "../diagnostics/diagnostic.js";
23
+ import { i64, numberLiteral, XT_UNDEFINED, XT_NULL, XT_FALSE, XT_TRUE } from "./values.js";
24
+ const RUNTIME_DECLARATIONS = [
25
+ "declare i64 @xt_undefined()",
26
+ "declare i64 @xt_null()",
27
+ "declare i64 @xt_bool(i32)",
28
+ "declare i64 @xt_number(double)",
29
+ "declare i64 @xt_string_new(i8*, i64)",
30
+ "declare i64 @xt_string_from_cstr(i8*)",
31
+ "declare i64 @xt_arg(i32, i64*, i32)",
32
+ "declare i64 @xt_closure_new(i8*, i32, i64*)",
33
+ "declare i64 @xt_closure_call(i64, i32, i64*)",
34
+ "declare i64 @xt_closure_env(i64, i32)",
35
+ "declare i32 @xt_truthy(i64)",
36
+ "declare double @xt_to_number(i64)",
37
+ "declare i64 @xt_to_string(i64)",
38
+ "declare i64 @xt_typeof(i64)",
39
+ "declare i64 @xt_add(i64, i64)",
40
+ "declare i64 @xt_sub(i64, i64)",
41
+ "declare i64 @xt_mul(i64, i64)",
42
+ "declare i64 @xt_div(i64, i64)",
43
+ "declare i64 @xt_mod(i64, i64)",
44
+ "declare i64 @xt_pow(i64, i64)",
45
+ "declare i64 @xt_neg(i64)",
46
+ "declare i64 @xt_pos(i64)",
47
+ "declare i64 @xt_bit_and(i64, i64)",
48
+ "declare i64 @xt_bit_or(i64, i64)",
49
+ "declare i64 @xt_bit_xor(i64, i64)",
50
+ "declare i64 @xt_bit_not(i64)",
51
+ "declare i64 @xt_shl(i64, i64)",
52
+ "declare i64 @xt_shr(i64, i64)",
53
+ "declare i64 @xt_ushr(i64, i64)",
54
+ "declare i64 @xt_lt(i64, i64)",
55
+ "declare i64 @xt_le(i64, i64)",
56
+ "declare i64 @xt_gt(i64, i64)",
57
+ "declare i64 @xt_ge(i64, i64)",
58
+ "declare i64 @xt_eq(i64, i64)",
59
+ "declare i64 @xt_ne(i64, i64)",
60
+ "declare i64 @xt_seq(i64, i64)",
61
+ "declare i64 @xt_sne(i64, i64)",
62
+ "declare i64 @xt_not(i64)",
63
+ "declare i64 @xt_object_new()",
64
+ "declare i64 @xt_object_get_cstr(i64, i8*)",
65
+ "declare i64 @xt_object_set(i64, i64, i64)",
66
+ "declare i64 @xt_object_has(i64, i64)",
67
+ "declare i64 @xt_array_new(i32, i64*)",
68
+ "declare i64 @xt_array_push(i64, i64)",
69
+ "declare i64 @xt_array_length(i64)",
70
+ "declare i64 @xt_array_spread(i64, i64)",
71
+ "declare i64 @xt_get(i64, i64)",
72
+ "declare i64 @xt_set(i64, i64, i64)",
73
+ "declare i64 @xt_box_new(i64)",
74
+ "declare i64 @xt_box_get(i64)",
75
+ "declare i64 @xt_box_set(i64, i64)",
76
+ "declare i64 @xt_is_nullish(i64)",
77
+ "declare void @xt_throw(i64)",
78
+ "declare void @xt_console_log(i32, i64*)",
79
+ ];
80
+ export function generate(sourceFile, diagnostics, options = {}) {
81
+ const generator = new Generator(sourceFile, diagnostics, options);
82
+ return { ir: generator.run(), binding: generator.binding };
83
+ }
84
+ class Generator {
85
+ sourceFile;
86
+ diagnostics;
87
+ binding;
88
+ globals = [];
89
+ functions = [];
90
+ strings = new Map();
91
+ extraDeclarations = new Set();
92
+ builtins;
93
+ stringCounter = 0;
94
+ current;
95
+ constructor(sourceFile, diagnostics, options = {}) {
96
+ this.sourceFile = sourceFile;
97
+ this.diagnostics = diagnostics;
98
+ this.binding = bind(sourceFile);
99
+ this.builtins = options.builtins ?? {};
100
+ }
101
+ run() {
102
+ for (const fn of this.binding.functions)
103
+ this.emitFunction(fn);
104
+ this.emitMain();
105
+ const header = ["; ModuleID = 'xbintsc'", "source_filename = \"" + this.sourceFile.fileName + "\"", ""];
106
+ return [...header, ...RUNTIME_DECLARATIONS, ...this.extraDeclarations, "", ...this.globals, "", ...this.functions, ""].join("\n");
107
+ }
108
+ // -- module level --------------------------------------------------------
109
+ emitFunction(fn) {
110
+ const name = this.functionName(fn);
111
+ const state = {
112
+ fn,
113
+ buffer: [],
114
+ allocas: [],
115
+ slots: new Map(),
116
+ reg: 0,
117
+ label: 0,
118
+ terminated: false,
119
+ loops: [],
120
+ };
121
+ this.current = state;
122
+ const header = `define i64 @${name}(i64 %env, i32 %argc, i64* %argv) {`;
123
+ this.emit(`%saved.env = alloca i64`);
124
+ this.emit(`store i64 %env, i64* %saved.env`);
125
+ this.emit(`%saved.argc = alloca i32`);
126
+ this.emit(`store i32 %argc, i32* %saved.argc`);
127
+ this.emit(`%saved.argv = alloca i64*`);
128
+ this.emit(`store i64* %argv, i64** %saved.argv`);
129
+ // Parameters.
130
+ for (let index = 0; index < fn.params.length; index++) {
131
+ const symbol = fn.params[index];
132
+ const value = this.reg();
133
+ this.emit(` ${value} = call i64 @xt_arg(i32 %argc, i64* %argv, i32 ${index})`);
134
+ this.declareSlot(symbol, value);
135
+ }
136
+ // Captures threaded through the environment.
137
+ for (const symbol of fn.captures) {
138
+ const index = fn.captureIndex.get(symbol.id);
139
+ const value = this.reg();
140
+ this.emit(` ${value} = call i64 @xt_closure_env(i64 %env, i32 ${index})`);
141
+ this.defineCaptureSlot(symbol, value);
142
+ }
143
+ // Body.
144
+ if (fn.node.kind === SyntaxKind.SourceFile) {
145
+ this.emitStatements(this.sourceFile.statements);
146
+ }
147
+ else if (fn.node.kind === SyntaxKind.ArrowFunction && fn.node.body.kind !== SyntaxKind.Block) {
148
+ const value = this.emitExpression(fn.node.body);
149
+ this.terminate(`ret i64 ${value}`);
150
+ }
151
+ else {
152
+ const body = fn.node.body;
153
+ this.emitStatements(body.statements);
154
+ }
155
+ if (!this.current.terminated)
156
+ this.terminate(`ret i64 ${i64(XT_UNDEFINED)}`);
157
+ const lines = [...state.allocas.map((a) => ` ${a}`), ...state.buffer];
158
+ this.functions.push([header, ...lines, "}", ""].join("\n"));
159
+ }
160
+ emitMain() {
161
+ const moduleName = this.functionName(this.binding.moduleFunction);
162
+ this.functions.push([
163
+ "define i32 @main(i32 %argc, i8** %argv) {",
164
+ ` %result = call i64 @${moduleName}(i64 ${i64(XT_UNDEFINED)}, i32 0, i64* null)`,
165
+ " ret i32 0",
166
+ "}",
167
+ "",
168
+ ].join("\n"));
169
+ }
170
+ functionName(fn) {
171
+ return fn.isModule ? "xt_module" : `xt_fn_${fn.id}`;
172
+ }
173
+ // -- emission primitives -------------------------------------------------
174
+ emit(line) {
175
+ this.current.buffer.push(line);
176
+ }
177
+ terminate(line) {
178
+ this.current.buffer.push(` ${line}`);
179
+ this.current.terminated = true;
180
+ }
181
+ reg() {
182
+ return `%r${this.current.reg++}`;
183
+ }
184
+ label(prefix) {
185
+ return `${prefix}.${this.current.label++}`;
186
+ }
187
+ startBlock(label) {
188
+ this.current.buffer.push(`${label}:`);
189
+ this.current.terminated = false;
190
+ }
191
+ alloca() {
192
+ const ptr = `%slot${this.current.allocas.length}`;
193
+ this.current.allocas.push(`${ptr} = alloca i64`);
194
+ return ptr;
195
+ }
196
+ const(hex) {
197
+ return i64(hex);
198
+ }
199
+ // -- variables -----------------------------------------------------------
200
+ declareSlot(symbol, initial) {
201
+ const ptr = this.alloca();
202
+ if (symbol.boxed) {
203
+ const box = this.reg();
204
+ this.emit(` ${box} = call i64 @xt_box_new(i64 ${initial})`);
205
+ this.emit(` store i64 ${box}, i64* ${ptr}`);
206
+ }
207
+ else {
208
+ this.emit(` store i64 ${initial}, i64* ${ptr}`);
209
+ }
210
+ this.current.slots.set(symbol.id, { ptr, boxed: symbol.boxed });
211
+ }
212
+ defineCaptureSlot(symbol, box) {
213
+ const ptr = this.alloca();
214
+ this.emit(` store i64 ${box}, i64* ${ptr}`);
215
+ // A captured symbol is always boxed; the environment holds the box.
216
+ this.current.slots.set(symbol.id, { ptr, boxed: true });
217
+ }
218
+ readSlot(symbol) {
219
+ const slot = this.current.slots.get(symbol.id);
220
+ if (!slot) {
221
+ // Referenced from an inner function without a capture slot: this should
222
+ // be impossible once the binder has run, but stay defensive.
223
+ this.diagnostics.error(DiagnosticCode.CodegenError, `Internal: no slot for '${symbol.name}' in ${this.current.fn.name}`);
224
+ return i64(XT_UNDEFINED);
225
+ }
226
+ const value = this.reg();
227
+ this.emit(` ${value} = load i64, i64* ${slot.ptr}`);
228
+ if (!slot.boxed)
229
+ return value;
230
+ const unboxed = this.reg();
231
+ this.emit(` ${unboxed} = call i64 @xt_box_get(i64 ${value})`);
232
+ return unboxed;
233
+ }
234
+ writeSlot(symbol, value) {
235
+ const slot = this.current.slots.get(symbol.id);
236
+ if (!slot) {
237
+ this.diagnostics.error(DiagnosticCode.CodegenError, `Internal: no slot for '${symbol.name}'`);
238
+ return;
239
+ }
240
+ if (!slot.boxed) {
241
+ this.emit(` store i64 ${value}, i64* ${slot.ptr}`);
242
+ return;
243
+ }
244
+ const box = this.reg();
245
+ this.emit(` ${box} = load i64, i64* ${slot.ptr}`);
246
+ this.emit(` call i64 @xt_box_set(i64 ${box}, i64 ${value})`);
247
+ }
248
+ // -- statements ----------------------------------------------------------
249
+ emitStatements(statements) {
250
+ for (const statement of statements) {
251
+ if (this.current.terminated)
252
+ break;
253
+ this.emitStatement(statement);
254
+ }
255
+ }
256
+ emitStatement(statement) {
257
+ switch (statement.kind) {
258
+ case SyntaxKind.ExpressionStatement:
259
+ this.emitExpression(statement.expression);
260
+ return;
261
+ case SyntaxKind.VariableStatement:
262
+ this.emitVariableStatement(statement);
263
+ return;
264
+ case SyntaxKind.Block:
265
+ this.emitStatements(statement.statements);
266
+ return;
267
+ case SyntaxKind.EmptyStatement:
268
+ case SyntaxKind.DebuggerStatement:
269
+ case SyntaxKind.FunctionDeclaration:
270
+ case SyntaxKind.InterfaceDeclaration:
271
+ case SyntaxKind.TypeAliasDeclaration:
272
+ return; // functions are emitted at module scope
273
+ case SyntaxKind.IfStatement:
274
+ this.emitIf(statement);
275
+ return;
276
+ case SyntaxKind.WhileStatement:
277
+ this.emitWhile(statement);
278
+ return;
279
+ case SyntaxKind.DoStatement:
280
+ this.emitDo(statement);
281
+ return;
282
+ case SyntaxKind.ForStatement:
283
+ this.emitFor(statement);
284
+ return;
285
+ case SyntaxKind.ForOfStatement:
286
+ case SyntaxKind.ForInStatement:
287
+ this.emitForOf(statement);
288
+ return;
289
+ case SyntaxKind.ReturnStatement:
290
+ this.emitReturn(statement);
291
+ return;
292
+ case SyntaxKind.BreakStatement: {
293
+ const loop = this.current.loops[this.current.loops.length - 1];
294
+ if (loop)
295
+ this.terminate(`br label %${loop.breakLabel}`);
296
+ return;
297
+ }
298
+ case SyntaxKind.ContinueStatement: {
299
+ const loop = this.current.loops[this.current.loops.length - 1];
300
+ if (loop)
301
+ this.terminate(`br label %${loop.continueLabel}`);
302
+ return;
303
+ }
304
+ case SyntaxKind.ThrowStatement: {
305
+ const value = this.emitExpression(statement.expression);
306
+ this.emit(` call void @xt_throw(i64 ${value})`);
307
+ this.terminate("unreachable");
308
+ return;
309
+ }
310
+ case SyntaxKind.ExportDeclaration: {
311
+ const declaration = statement.declaration;
312
+ if (declaration)
313
+ this.emitStatement(declaration);
314
+ return;
315
+ }
316
+ default:
317
+ this.unsupported(statement, "statement");
318
+ }
319
+ }
320
+ emitVariableStatement(statement) {
321
+ for (const declaration of statement.declarationList.declarations) {
322
+ const symbol = this.binding.symbolOfDeclaration.get(declaration);
323
+ if (!symbol)
324
+ continue;
325
+ if (this.current.slots.has(symbol.id))
326
+ continue; // hoisted `var`
327
+ const initial = declaration.initializer
328
+ ? this.emitExpression(declaration.initializer)
329
+ : i64(XT_UNDEFINED);
330
+ this.declareSlot(symbol, initial);
331
+ }
332
+ }
333
+ emitIf(statement) {
334
+ const thenLabel = this.label("if.then");
335
+ const elseLabel = statement.elseStatement ? this.label("if.else") : undefined;
336
+ const endLabel = this.label("if.end");
337
+ const condition = this.emitExpression(statement.condition);
338
+ const truthy = this.reg();
339
+ this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${condition})`);
340
+ const nonzero = this.reg();
341
+ this.emit(` ${nonzero} = icmp ne i32 ${truthy}, 0`);
342
+ this.terminate(`br i1 ${nonzero}, label %${thenLabel}, label %${elseLabel ?? endLabel}`);
343
+ this.startBlock(thenLabel);
344
+ this.emitStatement(statement.thenStatement);
345
+ if (!this.current.terminated)
346
+ this.terminate(`br label %${endLabel}`);
347
+ if (elseLabel) {
348
+ this.startBlock(elseLabel);
349
+ this.emitStatement(statement.elseStatement);
350
+ if (!this.current.terminated)
351
+ this.terminate(`br label %${endLabel}`);
352
+ }
353
+ this.startBlock(endLabel);
354
+ }
355
+ emitWhile(statement) {
356
+ const condLabel = this.label("while.cond");
357
+ const bodyLabel = this.label("while.body");
358
+ const endLabel = this.label("while.end");
359
+ this.terminate(`br label %${condLabel}`);
360
+ this.startBlock(condLabel);
361
+ const condition = this.emitExpression(statement.condition);
362
+ const truthy = this.reg();
363
+ this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${condition})`);
364
+ const nonzero = this.reg();
365
+ this.emit(` ${nonzero} = icmp ne i32 ${truthy}, 0`);
366
+ this.terminate(`br i1 ${nonzero}, label %${bodyLabel}, label %${endLabel}`);
367
+ this.startBlock(bodyLabel);
368
+ this.current.loops.push({ breakLabel: endLabel, continueLabel: condLabel });
369
+ this.emitStatement(statement.statement);
370
+ this.current.loops.pop();
371
+ if (!this.current.terminated)
372
+ this.terminate(`br label %${condLabel}`);
373
+ this.startBlock(endLabel);
374
+ }
375
+ emitDo(statement) {
376
+ const bodyLabel = this.label("do.body");
377
+ const condLabel = this.label("do.cond");
378
+ const endLabel = this.label("do.end");
379
+ this.terminate(`br label %${bodyLabel}`);
380
+ this.startBlock(bodyLabel);
381
+ this.current.loops.push({ breakLabel: endLabel, continueLabel: condLabel });
382
+ this.emitStatement(statement.statement);
383
+ this.current.loops.pop();
384
+ if (!this.current.terminated)
385
+ this.terminate(`br label %${condLabel}`);
386
+ this.startBlock(condLabel);
387
+ const condition = this.emitExpression(statement.condition);
388
+ const truthy = this.reg();
389
+ this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${condition})`);
390
+ const nonzero = this.reg();
391
+ this.emit(` ${nonzero} = icmp ne i32 ${truthy}, 0`);
392
+ this.terminate(`br i1 ${nonzero}, label %${bodyLabel}, label %${endLabel}`);
393
+ this.startBlock(endLabel);
394
+ }
395
+ emitFor(statement) {
396
+ if (statement.initializer && statement.initializer.kind === SyntaxKind.VariableDeclarationList) {
397
+ this.emitVariableStatement({
398
+ kind: SyntaxKind.VariableStatement,
399
+ declarationList: statement.initializer,
400
+ modifiers: [],
401
+ start: statement.initializer.start,
402
+ end: statement.initializer.end,
403
+ });
404
+ }
405
+ else if (statement.initializer) {
406
+ this.emitExpression(statement.initializer);
407
+ }
408
+ const condLabel = this.label("for.cond");
409
+ const bodyLabel = this.label("for.body");
410
+ const updateLabel = this.label("for.update");
411
+ const endLabel = this.label("for.end");
412
+ this.terminate(`br label %${condLabel}`);
413
+ this.startBlock(condLabel);
414
+ if (statement.condition) {
415
+ const condition = this.emitExpression(statement.condition);
416
+ const truthy = this.reg();
417
+ this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${condition})`);
418
+ const nonzero = this.reg();
419
+ this.emit(` ${nonzero} = icmp ne i32 ${truthy}, 0`);
420
+ this.terminate(`br i1 ${nonzero}, label %${bodyLabel}, label %${endLabel}`);
421
+ }
422
+ else {
423
+ this.terminate(`br label %${bodyLabel}`);
424
+ }
425
+ this.startBlock(bodyLabel);
426
+ this.current.loops.push({ breakLabel: endLabel, continueLabel: updateLabel });
427
+ this.emitStatement(statement.statement);
428
+ this.current.loops.pop();
429
+ if (!this.current.terminated)
430
+ this.terminate(`br label %${updateLabel}`);
431
+ this.startBlock(updateLabel);
432
+ if (statement.incrementor)
433
+ this.emitExpression(statement.incrementor);
434
+ this.terminate(`br label %${condLabel}`);
435
+ this.startBlock(endLabel);
436
+ }
437
+ /** `for (const x of xs)` / `for (const k in obj)` over arrays and objects. */
438
+ emitForOf(statement) {
439
+ const iterable = this.emitExpression(statement.expression);
440
+ const indexPtr = this.alloca();
441
+ this.emit(` store i64 ${numberLiteral(0)}, i64* ${indexPtr}`);
442
+ const lengthValue = this.reg();
443
+ this.emit(` ${lengthValue} = call i64 @xt_array_length(i64 ${iterable})`);
444
+ const condLabel = this.label("forof.cond");
445
+ const bodyLabel = this.label("forof.body");
446
+ const updateLabel = this.label("forof.update");
447
+ const endLabel = this.label("forof.end");
448
+ this.terminate(`br label %${condLabel}`);
449
+ this.startBlock(condLabel);
450
+ const index = this.reg();
451
+ this.emit(` ${index} = load i64, i64* ${indexPtr}`);
452
+ const inRange = this.reg();
453
+ this.emit(` ${inRange} = call i64 @xt_lt(i64 ${index}, i64 ${lengthValue})`);
454
+ const truthy = this.reg();
455
+ this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${inRange})`);
456
+ const nonzero = this.reg();
457
+ this.emit(` ${nonzero} = icmp ne i32 ${truthy}, 0`);
458
+ this.terminate(`br i1 ${nonzero}, label %${bodyLabel}, label %${endLabel}`);
459
+ this.startBlock(bodyLabel);
460
+ const element = this.reg();
461
+ this.emit(` ${element} = call i64 @xt_get(i64 ${iterable}, i64 ${index})`);
462
+ this.bindLoopVariable(statement.initializer, element);
463
+ this.current.loops.push({ breakLabel: endLabel, continueLabel: updateLabel });
464
+ this.emitStatement(statement.statement);
465
+ this.current.loops.pop();
466
+ if (!this.current.terminated)
467
+ this.terminate(`br label %${updateLabel}`);
468
+ this.startBlock(updateLabel);
469
+ const next = this.reg();
470
+ this.emit(` ${next} = call i64 @xt_add(i64 ${index}, i64 ${numberLiteral(1)})`);
471
+ this.emit(` store i64 ${next}, i64* ${indexPtr}`);
472
+ this.terminate(`br label %${condLabel}`);
473
+ this.startBlock(endLabel);
474
+ }
475
+ bindLoopVariable(initializer, value) {
476
+ if (initializer.kind === SyntaxKind.VariableDeclarationList) {
477
+ const declaration = initializer.declarations[0];
478
+ if (!declaration)
479
+ return;
480
+ const symbol = this.binding.symbolOfDeclaration.get(declaration);
481
+ if (symbol) {
482
+ if (this.current.slots.has(symbol.id))
483
+ this.writeSlot(symbol, value);
484
+ else
485
+ this.declareSlot(symbol, value);
486
+ }
487
+ return;
488
+ }
489
+ this.emitAssignmentTarget(initializer, value);
490
+ }
491
+ emitReturn(statement) {
492
+ const value = statement.expression ? this.emitExpression(statement.expression) : i64(XT_UNDEFINED);
493
+ this.terminate(`ret i64 ${value}`);
494
+ }
495
+ // -- expressions ---------------------------------------------------------
496
+ emitExpression(node) {
497
+ switch (node.kind) {
498
+ case SyntaxKind.Identifier:
499
+ return this.emitIdentifier(node);
500
+ case SyntaxKind.NumericLiteral: {
501
+ const value = node.value;
502
+ return numberLiteral(value);
503
+ }
504
+ case SyntaxKind.BigIntLiteral:
505
+ return numberLiteral(Number(node.value));
506
+ case SyntaxKind.StringLiteral:
507
+ return this.stringValue(node.value);
508
+ case SyntaxKind.NoSubstitutionTemplateLiteral:
509
+ return this.stringValue(node.value);
510
+ case SyntaxKind.TemplateLiteral:
511
+ return this.emitTemplate(node);
512
+ case SyntaxKind.TrueKeyword:
513
+ return i64(XT_TRUE);
514
+ case SyntaxKind.FalseKeyword:
515
+ return i64(XT_FALSE);
516
+ case SyntaxKind.NullKeyword:
517
+ return i64(XT_NULL);
518
+ case SyntaxKind.UndefinedKeyword:
519
+ return i64(XT_UNDEFINED);
520
+ case SyntaxKind.ParenthesizedExpression:
521
+ return this.emitExpression(node.expression);
522
+ case SyntaxKind.AsExpression:
523
+ case SyntaxKind.SatisfiesExpression:
524
+ case SyntaxKind.NonNullExpression:
525
+ return this.emitExpression(node.expression);
526
+ case SyntaxKind.BinaryExpression:
527
+ return this.emitBinaryOrAssignment(node);
528
+ case SyntaxKind.PrefixUnaryExpression:
529
+ return this.emitPrefix(node);
530
+ case SyntaxKind.PostfixUnaryExpression:
531
+ return this.emitPostfix(node);
532
+ case SyntaxKind.ConditionalExpression:
533
+ return this.emitConditional(node);
534
+ case SyntaxKind.CallExpression:
535
+ return this.emitCall(node);
536
+ case SyntaxKind.PropertyAccessExpression:
537
+ return this.emitPropertyAccess(node);
538
+ case SyntaxKind.ElementAccessExpression:
539
+ return this.emitElementAccess(node);
540
+ case SyntaxKind.ArrayLiteralExpression:
541
+ return this.emitArrayLiteral(node);
542
+ case SyntaxKind.ObjectLiteralExpression:
543
+ return this.emitObjectLiteral(node);
544
+ case SyntaxKind.ArrowFunction:
545
+ case SyntaxKind.FunctionExpression:
546
+ return this.emitClosure(node);
547
+ default:
548
+ this.unsupported(node, "expression");
549
+ return i64(XT_UNDEFINED);
550
+ }
551
+ }
552
+ emitIdentifier(identifier) {
553
+ const symbol = this.binding.symbolOfIdentifier.get(identifier);
554
+ if (symbol) {
555
+ if (symbol.kind === SymbolKind.Function && !this.current.slots.has(symbol.id)) {
556
+ return this.emitFunctionValue(symbol);
557
+ }
558
+ return this.readSlot(symbol);
559
+ }
560
+ switch (identifier.text) {
561
+ case "undefined":
562
+ return i64(XT_UNDEFINED);
563
+ case "NaN":
564
+ return numberLiteral(NaN);
565
+ case "Infinity":
566
+ return numberLiteral(Infinity);
567
+ case "console":
568
+ return i64(XT_UNDEFINED);
569
+ default:
570
+ this.diagnostics.error(DiagnosticCode.CannotFindName, `Cannot find name '${identifier.text}'`, identifier, this.sourceFile.fileName);
571
+ return i64(XT_UNDEFINED);
572
+ }
573
+ }
574
+ emitFunctionValue(symbol) {
575
+ const declaration = symbol.declarations[0];
576
+ const fn = declaration ? this.binding.functionOfNode.get(declaration) : undefined;
577
+ if (!fn)
578
+ return i64(XT_UNDEFINED);
579
+ const cast = this.reg();
580
+ this.emit(` ${cast} = bitcast i64 (i64, i32, i64*)* @${this.functionName(fn)} to i8*`);
581
+ const closure = this.reg();
582
+ this.emit(` ${closure} = call i64 @xt_closure_new(i8* ${cast}, i32 0, i64* null)`);
583
+ return closure;
584
+ }
585
+ emitTemplate(node) {
586
+ let accumulator = this.stringValue(node.head);
587
+ for (const span of node.spans) {
588
+ const expression = this.emitExpression(span.expression);
589
+ const text = this.reg();
590
+ this.emit(` ${text} = call i64 @xt_to_string(i64 ${expression})`);
591
+ const joined = this.reg();
592
+ this.emit(` ${joined} = call i64 @xt_add(i64 ${accumulator}, i64 ${text})`);
593
+ accumulator = joined;
594
+ if (span.literal.length > 0) {
595
+ const literal = this.stringValue(span.literal);
596
+ const withLiteral = this.reg();
597
+ this.emit(` ${withLiteral} = call i64 @xt_add(i64 ${accumulator}, i64 ${literal})`);
598
+ accumulator = withLiteral;
599
+ }
600
+ }
601
+ return accumulator;
602
+ }
603
+ emitBinaryOrAssignment(node) {
604
+ const operator = node.operator;
605
+ if (isAssignmentOperator(operator)) {
606
+ return this.emitAssignment(node);
607
+ }
608
+ switch (operator) {
609
+ case BinaryOperator.AmpersandAmpersand:
610
+ return this.emitShortCircuit(node, "and");
611
+ case BinaryOperator.BarBar:
612
+ return this.emitShortCircuit(node, "or");
613
+ case BinaryOperator.QuestionQuestion:
614
+ return this.emitShortCircuit(node, "nullish");
615
+ case BinaryOperator.Comma: {
616
+ this.emitExpression(node.left);
617
+ return this.emitExpression(node.right);
618
+ }
619
+ default:
620
+ break;
621
+ }
622
+ const left = this.emitExpression(node.left);
623
+ const right = this.emitExpression(node.right);
624
+ const runtime = BINARY_RUNTIME[operator];
625
+ if (!runtime) {
626
+ this.unsupported(node, `operator '${operator}'`);
627
+ return i64(XT_UNDEFINED);
628
+ }
629
+ const result = this.reg();
630
+ this.emit(` ${result} = call i64 @${runtime}(i64 ${left}, i64 ${right})`);
631
+ return result;
632
+ }
633
+ emitShortCircuit(node, kind) {
634
+ const result = this.alloca();
635
+ const left = this.emitExpression(node.left);
636
+ this.emit(` store i64 ${left}, i64* ${result}`);
637
+ const rightLabel = this.label("sc.right");
638
+ const endLabel = this.label("sc.end");
639
+ let condition;
640
+ if (kind === "and") {
641
+ const truthy = this.reg();
642
+ this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${left})`);
643
+ condition = this.reg();
644
+ this.emit(` ${condition} = icmp ne i32 ${truthy}, 0`);
645
+ }
646
+ else if (kind === "or") {
647
+ const truthy = this.reg();
648
+ this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${left})`);
649
+ const isTruthy = this.reg();
650
+ this.emit(` ${isTruthy} = icmp ne i32 ${truthy}, 0`);
651
+ condition = this.reg();
652
+ this.emit(` ${condition} = xor i1 ${isTruthy}, true`);
653
+ }
654
+ else {
655
+ const nullish = this.reg();
656
+ this.emit(` ${nullish} = call i64 @xt_is_nullish(i64 ${left})`);
657
+ const truthy = this.reg();
658
+ this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${nullish})`);
659
+ condition = this.reg();
660
+ this.emit(` ${condition} = icmp ne i32 ${truthy}, 0`);
661
+ }
662
+ this.terminate(`br i1 ${condition}, label %${rightLabel}, label %${endLabel}`);
663
+ this.startBlock(rightLabel);
664
+ const right = this.emitExpression(node.right);
665
+ this.emit(` store i64 ${right}, i64* ${result}`);
666
+ this.terminate(`br label %${endLabel}`);
667
+ this.startBlock(endLabel);
668
+ const value = this.reg();
669
+ this.emit(` ${value} = load i64, i64* ${result}`);
670
+ return value;
671
+ }
672
+ emitConditional(node) {
673
+ const result = this.alloca();
674
+ const condition = this.emitExpression(node.condition);
675
+ const truthy = this.reg();
676
+ this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${condition})`);
677
+ const nonzero = this.reg();
678
+ this.emit(` ${nonzero} = icmp ne i32 ${truthy}, 0`);
679
+ const trueLabel = this.label("cond.true");
680
+ const falseLabel = this.label("cond.false");
681
+ const endLabel = this.label("cond.end");
682
+ this.terminate(`br i1 ${nonzero}, label %${trueLabel}, label %${falseLabel}`);
683
+ this.startBlock(trueLabel);
684
+ const whenTrue = this.emitExpression(node.whenTrue);
685
+ this.emit(` store i64 ${whenTrue}, i64* ${result}`);
686
+ this.terminate(`br label %${endLabel}`);
687
+ this.startBlock(falseLabel);
688
+ const whenFalse = this.emitExpression(node.whenFalse);
689
+ this.emit(` store i64 ${whenFalse}, i64* ${result}`);
690
+ this.terminate(`br label %${endLabel}`);
691
+ this.startBlock(endLabel);
692
+ const value = this.reg();
693
+ this.emit(` ${value} = load i64, i64* ${result}`);
694
+ return value;
695
+ }
696
+ emitPrefix(node) {
697
+ switch (node.operator) {
698
+ case PrefixUnaryOperator.Minus: {
699
+ const operand = this.emitExpression(node.operand);
700
+ const result = this.reg();
701
+ this.emit(` ${result} = call i64 @xt_neg(i64 ${operand})`);
702
+ return result;
703
+ }
704
+ case PrefixUnaryOperator.Plus: {
705
+ const operand = this.emitExpression(node.operand);
706
+ const result = this.reg();
707
+ this.emit(` ${result} = call i64 @xt_pos(i64 ${operand})`);
708
+ return result;
709
+ }
710
+ case PrefixUnaryOperator.Exclamation: {
711
+ const operand = this.emitExpression(node.operand);
712
+ const result = this.reg();
713
+ this.emit(` ${result} = call i64 @xt_not(i64 ${operand})`);
714
+ return result;
715
+ }
716
+ case PrefixUnaryOperator.Tilde: {
717
+ const operand = this.emitExpression(node.operand);
718
+ const result = this.reg();
719
+ this.emit(` ${result} = call i64 @xt_bit_not(i64 ${operand})`);
720
+ return result;
721
+ }
722
+ case PrefixUnaryOperator.TypeOf: {
723
+ const operand = this.emitExpression(node.operand);
724
+ const result = this.reg();
725
+ this.emit(` ${result} = call i64 @xt_typeof(i64 ${operand})`);
726
+ return result;
727
+ }
728
+ case PrefixUnaryOperator.Void:
729
+ if (node.operand)
730
+ this.emitExpression(node.operand);
731
+ return i64(XT_UNDEFINED);
732
+ case PrefixUnaryOperator.PlusPlus:
733
+ case PrefixUnaryOperator.MinusMinus: {
734
+ const one = numberLiteral(1);
735
+ const current = this.emitExpression(node.operand);
736
+ const fn = node.operator === PrefixUnaryOperator.PlusPlus ? "xt_add" : "xt_sub";
737
+ const next = this.reg();
738
+ this.emit(` ${next} = call i64 @${fn}(i64 ${current}, i64 ${one})`);
739
+ this.emitAssignmentTarget(node.operand, next);
740
+ return next;
741
+ }
742
+ default:
743
+ this.unsupported(node, `unary operator '${node.operator}'`);
744
+ return i64(XT_UNDEFINED);
745
+ }
746
+ }
747
+ emitPostfix(node) {
748
+ const one = numberLiteral(1);
749
+ const current = this.emitExpression(node.operand);
750
+ const fn = node.operator === PostfixUnaryOperator.PlusPlus ? "xt_add" : "xt_sub";
751
+ const next = this.reg();
752
+ this.emit(` ${next} = call i64 @${fn}(i64 ${current}, i64 ${one})`);
753
+ this.emitAssignmentTarget(node.operand, next);
754
+ return current;
755
+ }
756
+ emitAssignment(node) {
757
+ const operator = node.operator;
758
+ const target = node.left;
759
+ let value;
760
+ if (operator === AssignmentOperator.Assign) {
761
+ value = this.emitExpression(node.right);
762
+ }
763
+ else if (operator === AssignmentOperator.AmpersandAmpersandAssign ||
764
+ operator === AssignmentOperator.BarBarAssign ||
765
+ operator === AssignmentOperator.QuestionQuestionAssign) {
766
+ value = this.emitLogicalAssignment(node);
767
+ }
768
+ else {
769
+ const current = this.emitExpression(target);
770
+ const right = this.emitExpression(node.right);
771
+ const binaryOperator = compoundToBinary(operator);
772
+ const runtime = BINARY_RUNTIME[binaryOperator];
773
+ if (!runtime) {
774
+ this.unsupported(node, `assignment operator '${operator}'`);
775
+ return i64(XT_UNDEFINED);
776
+ }
777
+ const result = this.reg();
778
+ this.emit(` ${result} = call i64 @${runtime}(i64 ${current}, i64 ${right})`);
779
+ value = result;
780
+ }
781
+ this.emitAssignmentTarget(target, value);
782
+ return value;
783
+ }
784
+ emitLogicalAssignment(node) {
785
+ const operator = node.operator;
786
+ const target = node.left;
787
+ const current = this.emitExpression(target);
788
+ const result = this.alloca();
789
+ this.emit(` store i64 ${current}, i64* ${result}`);
790
+ const rightLabel = this.label("assign.right");
791
+ const endLabel = this.label("assign.end");
792
+ let condition;
793
+ if (operator === AssignmentOperator.AmpersandAmpersandAssign) {
794
+ const truthy = this.reg();
795
+ this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${current})`);
796
+ condition = this.reg();
797
+ this.emit(` ${condition} = icmp ne i32 ${truthy}, 0`);
798
+ }
799
+ else if (operator === AssignmentOperator.BarBarAssign) {
800
+ const truthy = this.reg();
801
+ this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${current})`);
802
+ const isTruthy = this.reg();
803
+ this.emit(` ${isTruthy} = icmp ne i32 ${truthy}, 0`);
804
+ condition = this.reg();
805
+ this.emit(` ${condition} = xor i1 ${isTruthy}, true`);
806
+ }
807
+ else {
808
+ const nullish = this.reg();
809
+ this.emit(` ${nullish} = call i64 @xt_is_nullish(i64 ${current})`);
810
+ const truthy = this.reg();
811
+ this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${nullish})`);
812
+ condition = this.reg();
813
+ this.emit(` ${condition} = icmp ne i32 ${truthy}, 0`);
814
+ }
815
+ this.terminate(`br i1 ${condition}, label %${rightLabel}, label %${endLabel}`);
816
+ this.startBlock(rightLabel);
817
+ const right = this.emitExpression(node.right);
818
+ this.emit(` store i64 ${right}, i64* ${result}`);
819
+ this.terminate(`br label %${endLabel}`);
820
+ this.startBlock(endLabel);
821
+ const value = this.reg();
822
+ this.emit(` ${value} = load i64, i64* ${result}`);
823
+ this.emitAssignmentTarget(target, value);
824
+ return value;
825
+ }
826
+ emitAssignmentTarget(target, value) {
827
+ switch (target.kind) {
828
+ case SyntaxKind.Identifier: {
829
+ const symbol = this.binding.symbolOfIdentifier.get(target);
830
+ if (symbol)
831
+ this.writeSlot(symbol, value);
832
+ return;
833
+ }
834
+ case SyntaxKind.PropertyAccessExpression: {
835
+ const access = target;
836
+ const object = this.emitExpression(access.expression);
837
+ const key = this.stringValue(access.name.text);
838
+ this.emit(` call i64 @xt_set(i64 ${object}, i64 ${key}, i64 ${value})`);
839
+ return;
840
+ }
841
+ case SyntaxKind.ElementAccessExpression: {
842
+ const access = target;
843
+ const object = this.emitExpression(access.expression);
844
+ const key = this.emitExpression(access.argumentExpression);
845
+ this.emit(` call i64 @xt_set(i64 ${object}, i64 ${key}, i64 ${value})`);
846
+ return;
847
+ }
848
+ case SyntaxKind.ParenthesizedExpression:
849
+ this.emitAssignmentTarget(target.expression, value);
850
+ return;
851
+ default:
852
+ this.unsupported(target, "assignment target");
853
+ }
854
+ }
855
+ emitCall(node) {
856
+ const callee = node.expression;
857
+ if (callee.kind === SyntaxKind.PropertyAccessExpression) {
858
+ const special = this.tryEmitBuiltinCall(node, callee);
859
+ if (special)
860
+ return special;
861
+ }
862
+ if (callee.kind === SyntaxKind.Identifier) {
863
+ const symbol = this.binding.symbolOfIdentifier.get(callee);
864
+ if (symbol && symbol.kind === SymbolKind.Function) {
865
+ const declaration = symbol.declarations[0];
866
+ const fn = declaration ? this.binding.functionOfNode.get(declaration) : undefined;
867
+ if (fn) {
868
+ const args = this.emitArguments(node.arguments);
869
+ const result = this.reg();
870
+ this.emit(` ${result} = call i64 @${this.functionName(fn)}(i64 ${i64(XT_UNDEFINED)}, i32 ${args.argc}, i64* ${args.ptr})`);
871
+ return result;
872
+ }
873
+ }
874
+ const builtin = this.builtins[callee.text];
875
+ if (!symbol && builtin) {
876
+ this.extraDeclarations.add(builtin.returnVoid ? `declare void @${builtin.symbol}(i32, i64*)` : `declare i64 @${builtin.symbol}(i32, i64*)`);
877
+ const args = this.emitArguments(node.arguments);
878
+ if (builtin.returnVoid) {
879
+ this.emit(` call void @${builtin.symbol}(i32 ${args.argc}, i64* ${args.ptr})`);
880
+ return i64(XT_UNDEFINED);
881
+ }
882
+ const result = this.reg();
883
+ this.emit(` ${result} = call i64 @${builtin.symbol}(i32 ${args.argc}, i64* ${args.ptr})`);
884
+ return result;
885
+ }
886
+ }
887
+ const calleeValue = this.emitExpression(callee);
888
+ const args = this.emitArguments(node.arguments);
889
+ const result = this.reg();
890
+ this.emit(` ${result} = call i64 @xt_closure_call(i64 ${calleeValue}, i32 ${args.argc}, i64* ${args.ptr})`);
891
+ return result;
892
+ }
893
+ tryEmitBuiltinCall(node, callee) {
894
+ const target = callee.expression;
895
+ const method = callee.name.text;
896
+ if (target.kind === SyntaxKind.Identifier && target.text === "console") {
897
+ const args = this.emitArguments(node.arguments);
898
+ this.emit(` call void @xt_console_log(i32 ${args.argc}, i64* ${args.ptr})`);
899
+ return i64(XT_UNDEFINED);
900
+ }
901
+ // Array and string methods.
902
+ if (method === "push" || method === "pop" || method === "shift" || method === "unshift" || method === "join" || method === "slice" || method === "indexOf" || method === "includes" || method === "map" || method === "forEach" || method === "filter" || method === "reduce") {
903
+ if (method === "push") {
904
+ const object = this.emitExpression(target);
905
+ const args = this.emitArguments(node.arguments);
906
+ let last = object;
907
+ if (args.argc === 0)
908
+ return this.runtimeCall("xt_array_length", [object]);
909
+ for (let index = 0; index < args.argc; index++) {
910
+ const ptr = this.reg();
911
+ this.emit(` ${ptr} = getelementptr i64, i64* ${args.ptr}, i32 ${index}`);
912
+ const element = this.reg();
913
+ this.emit(` ${element} = load i64, i64* ${ptr}`);
914
+ const previous = last;
915
+ last = this.reg();
916
+ this.emit(` ${last} = call i64 @xt_array_push(i64 ${previous}, i64 ${element})`);
917
+ }
918
+ return last;
919
+ }
920
+ this.unsupported(node, `array method '${method}'`);
921
+ return i64(XT_UNDEFINED);
922
+ }
923
+ return undefined;
924
+ }
925
+ runtimeCall(name, args) {
926
+ const result = this.reg();
927
+ this.emit(` ${result} = call i64 @${name}(${args.map((a) => `i64 ${a}`).join(", ")})`);
928
+ return result;
929
+ }
930
+ emitArguments(args) {
931
+ if (args.length === 0)
932
+ return { argc: 0, ptr: "null" };
933
+ const ptr = `%args${this.current.allocas.length}`;
934
+ this.current.allocas.push(`${ptr} = alloca i64, i32 ${args.length}`);
935
+ for (let index = 0; index < args.length; index++) {
936
+ const value = this.emitExpression(args[index]);
937
+ const slot = this.reg();
938
+ this.emit(` ${slot} = getelementptr i64, i64* ${ptr}, i32 ${index}`);
939
+ this.emit(` store i64 ${value}, i64* ${slot}`);
940
+ }
941
+ return { argc: args.length, ptr };
942
+ }
943
+ emitPropertyAccess(node) {
944
+ if (node.name.text === "length") {
945
+ const object = this.emitExpression(node.expression);
946
+ const result = this.reg();
947
+ this.emit(` ${result} = call i64 @xt_array_length(i64 ${object})`);
948
+ return result;
949
+ }
950
+ const object = this.emitExpression(node.expression);
951
+ const key = this.stringValue(node.name.text);
952
+ const result = this.reg();
953
+ this.emit(` ${result} = call i64 @xt_get(i64 ${object}, i64 ${key})`);
954
+ return result;
955
+ }
956
+ emitElementAccess(node) {
957
+ const object = this.emitExpression(node.expression);
958
+ const key = this.emitExpression(node.argumentExpression);
959
+ const result = this.reg();
960
+ this.emit(` ${result} = call i64 @xt_get(i64 ${object}, i64 ${key})`);
961
+ return result;
962
+ }
963
+ emitArrayLiteral(node) {
964
+ const hasSpread = node.elements.some((element) => element.kind === SyntaxKind.SpreadElement);
965
+ if (!hasSpread) {
966
+ const args = this.emitArguments(node.elements);
967
+ const result = this.reg();
968
+ this.emit(` ${result} = call i64 @xt_array_new(i32 ${args.argc}, i64* ${args.ptr})`);
969
+ return result;
970
+ }
971
+ let array = this.runtimeCall("xt_array_new", [`0`, `null`]);
972
+ for (const element of node.elements) {
973
+ if (element.kind === SyntaxKind.SpreadElement) {
974
+ const spread = this.emitExpression(element.expression);
975
+ array = this.runtimeCall("xt_array_spread", [array, spread]);
976
+ }
977
+ else {
978
+ const value = this.emitExpression(element);
979
+ array = this.runtimeCall("xt_array_push", [array, value]);
980
+ }
981
+ }
982
+ return array;
983
+ }
984
+ emitObjectLiteral(node) {
985
+ const object = this.reg();
986
+ this.emit(` ${object} = call i64 @xt_object_new()`);
987
+ for (const property of node.properties) {
988
+ if (property.kind === SyntaxKind.PropertyAssignment) {
989
+ const name = propertyNameText(property.name);
990
+ const key = this.stringValue(name);
991
+ const value = this.emitExpression(property.initializer);
992
+ this.emit(` call i64 @xt_set(i64 ${object}, i64 ${key}, i64 ${value})`);
993
+ }
994
+ else if (property.kind === SyntaxKind.ShorthandPropertyAssignment) {
995
+ const identifier = property.name;
996
+ const key = this.stringValue(identifier.text);
997
+ const value = this.emitIdentifier(identifier);
998
+ this.emit(` call i64 @xt_set(i64 ${object}, i64 ${key}, i64 ${value})`);
999
+ }
1000
+ else {
1001
+ this.unsupported(property, "object spread");
1002
+ }
1003
+ }
1004
+ return object;
1005
+ }
1006
+ emitClosure(node) {
1007
+ const fn = this.binding.functionOfNode.get(node);
1008
+ if (!fn) {
1009
+ this.unsupported(node, "closure");
1010
+ return i64(XT_UNDEFINED);
1011
+ }
1012
+ let envPtr = "null";
1013
+ if (fn.captures.length > 0) {
1014
+ envPtr = `%env${this.current.allocas.length}`;
1015
+ this.current.allocas.push(`${envPtr} = alloca i64, i32 ${fn.captures.length}`);
1016
+ for (let index = 0; index < fn.captures.length; index++) {
1017
+ const symbol = fn.captures[index];
1018
+ // The current function must be able to read the capture: either from
1019
+ // its own slot (local) or from one of its own capture slots.
1020
+ const slot = this.current.slots.get(symbol.id);
1021
+ let raw;
1022
+ if (slot) {
1023
+ raw = this.reg();
1024
+ this.emit(` ${raw} = load i64, i64* ${slot.ptr}`);
1025
+ }
1026
+ else {
1027
+ const outer = this.binding.functionOfNode.get(node);
1028
+ void outer;
1029
+ raw = i64(XT_UNDEFINED);
1030
+ }
1031
+ const target = this.reg();
1032
+ this.emit(` ${target} = getelementptr i64, i64* ${envPtr}, i32 ${index}`);
1033
+ this.emit(` store i64 ${raw}, i64* ${target}`);
1034
+ }
1035
+ }
1036
+ const cast = this.reg();
1037
+ this.emit(` ${cast} = bitcast i64 (i64, i32, i64*)* @${this.functionName(fn)} to i8*`);
1038
+ const closure = this.reg();
1039
+ this.emit(` ${closure} = call i64 @xt_closure_new(i8* ${cast}, i32 ${fn.captures.length}, i64* ${envPtr})`);
1040
+ return closure;
1041
+ }
1042
+ // -- string pool ---------------------------------------------------------
1043
+ stringValue(text) {
1044
+ const entry = this.internString(text);
1045
+ const result = this.reg();
1046
+ this.emit(` ${result} = call i64 @xt_string_new(i8* ${entry.label}, i64 ${entry.length})`);
1047
+ return result;
1048
+ }
1049
+ internString(text) {
1050
+ const existing = this.strings.get(text);
1051
+ if (existing)
1052
+ return existing;
1053
+ const label = `@.str.${this.stringCounter++}`;
1054
+ const bytes = utf8Bytes(text);
1055
+ const entry = { label, length: bytes.length };
1056
+ this.strings.set(text, entry);
1057
+ this.globals.push(`${label} = private unnamed_addr constant [${bytes.length + 1} x i8] c"${escapeBytes(bytes)}\\00"`);
1058
+ return entry;
1059
+ }
1060
+ unsupported(node, what) {
1061
+ const label = kindName(node.kind);
1062
+ this.diagnostics.error(DiagnosticCode.UnsupportedFeature, `xbintsc does not yet support this ${what} (${label})`, node, this.sourceFile.fileName);
1063
+ }
1064
+ }
1065
+ const BINARY_RUNTIME = {
1066
+ [BinaryOperator.Add]: "xt_add",
1067
+ [BinaryOperator.Subtract]: "xt_sub",
1068
+ [BinaryOperator.Multiply]: "xt_mul",
1069
+ [BinaryOperator.Divide]: "xt_div",
1070
+ [BinaryOperator.Remainder]: "xt_mod",
1071
+ [BinaryOperator.Exponent]: "xt_pow",
1072
+ [BinaryOperator.LessThan]: "xt_lt",
1073
+ [BinaryOperator.LessThanEquals]: "xt_le",
1074
+ [BinaryOperator.GreaterThan]: "xt_gt",
1075
+ [BinaryOperator.GreaterThanEquals]: "xt_ge",
1076
+ [BinaryOperator.EqualsEquals]: "xt_eq",
1077
+ [BinaryOperator.ExclamationEquals]: "xt_ne",
1078
+ [BinaryOperator.EqualsEqualsEquals]: "xt_seq",
1079
+ [BinaryOperator.ExclamationEqualsEquals]: "xt_sne",
1080
+ [BinaryOperator.Ampersand]: "xt_bit_and",
1081
+ [BinaryOperator.Bar]: "xt_bit_or",
1082
+ [BinaryOperator.Caret]: "xt_bit_xor",
1083
+ [BinaryOperator.LessThanLessThan]: "xt_shl",
1084
+ [BinaryOperator.GreaterThanGreaterThan]: "xt_shr",
1085
+ [BinaryOperator.GreaterThanGreaterThanGreaterThan]: "xt_ushr",
1086
+ };
1087
+ const ASSIGNMENT_OPERATORS = new Set([
1088
+ "=",
1089
+ "+=",
1090
+ "-=",
1091
+ "*=",
1092
+ "/=",
1093
+ "%=",
1094
+ "**=",
1095
+ "<<=",
1096
+ ">>=",
1097
+ ">>>=",
1098
+ "&=",
1099
+ "|=",
1100
+ "^=",
1101
+ "&&=",
1102
+ "||=",
1103
+ "??=",
1104
+ ]);
1105
+ function isAssignmentOperator(operator) {
1106
+ return ASSIGNMENT_OPERATORS.has(operator);
1107
+ }
1108
+ function compoundToBinary(operator) {
1109
+ const text = operator.slice(0, -1);
1110
+ return text;
1111
+ }
1112
+ function propertyNameText(name) {
1113
+ switch (name.kind) {
1114
+ case SyntaxKind.Identifier:
1115
+ case SyntaxKind.PrivateIdentifier:
1116
+ return name.text;
1117
+ case SyntaxKind.StringLiteral:
1118
+ return name.value;
1119
+ case SyntaxKind.NumericLiteral:
1120
+ return String(name.value);
1121
+ default:
1122
+ return "";
1123
+ }
1124
+ }
1125
+ /** Human readable syntax kind for diagnostics (const enums have no reverse map). */
1126
+ function kindName(kind) {
1127
+ const entry = KIND_NAMES.get(kind);
1128
+ return entry ?? String(kind);
1129
+ }
1130
+ const KIND_NAMES = new Map([
1131
+ [SyntaxKind.CallExpression, "call expression"],
1132
+ [SyntaxKind.NewExpression, "new expression"],
1133
+ [SyntaxKind.ClassDeclaration, "class declaration"],
1134
+ [SyntaxKind.ClassExpression, "class expression"],
1135
+ [SyntaxKind.EnumDeclaration, "enum declaration"],
1136
+ [SyntaxKind.SwitchStatement, "switch statement"],
1137
+ [SyntaxKind.TryStatement, "try statement"],
1138
+ [SyntaxKind.SpreadElement, "spread element"],
1139
+ [SyntaxKind.ThisKeyword, "this expression"],
1140
+ [SyntaxKind.RegularExpressionLiteral, "regular expression"],
1141
+ [SyntaxKind.TaggedTemplateExpression, "tagged template"],
1142
+ [SyntaxKind.DeleteExpression, "delete expression"],
1143
+ [SyntaxKind.AwaitExpression, "await expression"],
1144
+ [SyntaxKind.YieldExpression, "yield expression"],
1145
+ [SyntaxKind.SwitchStatement, "switch statement"],
1146
+ ]);
1147
+ function utf8Bytes(text) {
1148
+ const bytes = [];
1149
+ for (const character of text) {
1150
+ const code = character.codePointAt(0);
1151
+ if (code < 0x80)
1152
+ bytes.push(code);
1153
+ else if (code < 0x800) {
1154
+ bytes.push(0xc0 | (code >> 6), 0x80 | (code & 0x3f));
1155
+ }
1156
+ else if (code < 0x10000) {
1157
+ bytes.push(0xe0 | (code >> 12), 0x80 | ((code >> 6) & 0x3f), 0x80 | (code & 0x3f));
1158
+ }
1159
+ else {
1160
+ bytes.push(0xf0 | (code >> 18), 0x80 | ((code >> 12) & 0x3f), 0x80 | ((code >> 6) & 0x3f), 0x80 | (code & 0x3f));
1161
+ }
1162
+ }
1163
+ return bytes;
1164
+ }
1165
+ function escapeBytes(bytes) {
1166
+ return bytes
1167
+ .map((byte) => {
1168
+ if (byte === 0x22)
1169
+ return '\\22';
1170
+ if (byte === 0x5c)
1171
+ return '\\5C';
1172
+ if (byte >= 0x20 && byte < 0x7f)
1173
+ return String.fromCharCode(byte);
1174
+ return `\\${byte.toString(16).toUpperCase().padStart(2, "0")}`;
1175
+ })
1176
+ .join("");
1177
+ }
1178
+ //# sourceMappingURL=llvm.js.map