xbintsc 0.1.3 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -9
- package/dist/src/codegen/llvm.js +422 -44
- package/dist/src/codegen/llvm.js.map +1 -1
- package/dist/src/parser/parser.js +4 -0
- package/dist/src/parser/parser.js.map +1 -1
- package/dist/tests/e2e/compile.test.js +122 -2
- package/dist/tests/e2e/compile.test.js.map +1 -1
- package/dist/tests/parser/parser.test.js +9 -1
- package/dist/tests/parser/parser.test.js.map +1 -1
- package/package.json +1 -1
- package/runtime/rt.h +32 -0
- package/runtime/xt_runtime.c +696 -6
- package/src/codegen/llvm.ts +458 -43
- package/src/parser/parser.ts +4 -0
package/README.md
CHANGED
|
@@ -178,6 +178,11 @@ to the C symbol with the uniform `(argc, argv)` calling convention. Adding a
|
|
|
178
178
|
module means dropping a folder under `src/extensions/node/` and its C
|
|
179
179
|
counterpart under `runtime/ext_node/`; the core compiler never changes.
|
|
180
180
|
|
|
181
|
+
Node module coverage:
|
|
182
|
+
|
|
183
|
+
- [Node extension: implemented](./doc/node-implemented.md)
|
|
184
|
+
- [Node extension: unimplemented](./doc/node-unimplemented.md)
|
|
185
|
+
|
|
181
186
|
## Tests
|
|
182
187
|
|
|
183
188
|
```bash
|
|
@@ -196,12 +201,5 @@ Tests are organised by module under `tests/` (`lexer`, `parser`, `binder`,
|
|
|
196
201
|
|
|
197
202
|
## Language subset
|
|
198
203
|
|
|
199
|
-
Implemented
|
|
200
|
-
|
|
201
|
-
`continue`/`return`/`throw`, objects, arrays, member/element access, assignments,
|
|
202
|
-
template literals, `console.log`, extension builtins, and the TypeScript type
|
|
203
|
-
syntax is parsed and ignored (types are erased).
|
|
204
|
-
|
|
205
|
-
Not yet implemented: classes, enums, `switch`, `try`/`catch`, `new`, `this`,
|
|
206
|
-
object spread, `async`/generators, and most standard library methods (`Array`
|
|
207
|
-
methods other than `push`, `Math`, etc.).
|
|
204
|
+
- [Implemented features](./doc/implemented.md)
|
|
205
|
+
- [Unimplemented features](./doc/unimplemented.md)
|
package/dist/src/codegen/llvm.js
CHANGED
|
@@ -64,6 +64,23 @@ const RUNTIME_DECLARATIONS = [
|
|
|
64
64
|
"declare i64 @xt_object_get_cstr(i64, i8*)",
|
|
65
65
|
"declare i64 @xt_object_set(i64, i64, i64)",
|
|
66
66
|
"declare i64 @xt_object_has(i64, i64)",
|
|
67
|
+
"declare i64 @xt_object_keys(i64)",
|
|
68
|
+
"declare i64 @xt_object_values(i64)",
|
|
69
|
+
"declare i64 @xt_object_entries(i64)",
|
|
70
|
+
"declare i64 @xt_object_assign(i32, i64*)",
|
|
71
|
+
"declare i64 @xt_object_spread(i64, i64)",
|
|
72
|
+
"declare i64 @xt_call_method(i64, i64, i32, i64*)",
|
|
73
|
+
"declare i64 @xt_math_call(i64, i32, i64*)",
|
|
74
|
+
"declare i64 @xt_parse_int(i32, i64*)",
|
|
75
|
+
"declare i64 @xt_parse_float(i32, i64*)",
|
|
76
|
+
"declare i64 @xt_is_nan(i32, i64*)",
|
|
77
|
+
"declare i64 @xt_is_finite(i32, i64*)",
|
|
78
|
+
"declare i64 @xt_number_ctor(i32, i64*)",
|
|
79
|
+
"declare i64 @xt_string_ctor(i32, i64*)",
|
|
80
|
+
"declare i64 @xt_boolean_ctor(i32, i64*)",
|
|
81
|
+
"declare i64 @xt_in(i64, i64)",
|
|
82
|
+
"declare i64 @xt_delete(i64, i64)",
|
|
83
|
+
"declare i64 @xt_rest_args(i32, i64*, i32)",
|
|
67
84
|
"declare i64 @xt_array_new(i32, i64*)",
|
|
68
85
|
"declare i64 @xt_array_push(i64, i64)",
|
|
69
86
|
"declare i64 @xt_array_length(i64)",
|
|
@@ -75,7 +92,14 @@ const RUNTIME_DECLARATIONS = [
|
|
|
75
92
|
"declare i64 @xt_box_set(i64, i64)",
|
|
76
93
|
"declare i64 @xt_is_nullish(i64)",
|
|
77
94
|
"declare void @xt_throw(i64)",
|
|
95
|
+
"declare i32 @_setjmp(i8*) returns_twice",
|
|
96
|
+
"declare i8* @xt_try_enter()",
|
|
97
|
+
"declare i64 @xt_try_exception(i8*)",
|
|
98
|
+
"declare void @xt_try_leave(i8*)",
|
|
78
99
|
"declare void @xt_console_log(i32, i64*)",
|
|
100
|
+
"declare void @xt_console_info(i32, i64*)",
|
|
101
|
+
"declare void @xt_console_warn(i32, i64*)",
|
|
102
|
+
"declare void @xt_console_error(i32, i64*)",
|
|
79
103
|
];
|
|
80
104
|
export function generate(sourceFile, diagnostics, options = {}) {
|
|
81
105
|
const generator = new Generator(sourceFile, diagnostics, options);
|
|
@@ -117,6 +141,9 @@ class Generator {
|
|
|
117
141
|
label: 0,
|
|
118
142
|
terminated: false,
|
|
119
143
|
loops: [],
|
|
144
|
+
tryFrames: [],
|
|
145
|
+
escapePointers: [],
|
|
146
|
+
usesTry: false,
|
|
120
147
|
};
|
|
121
148
|
this.current = state;
|
|
122
149
|
const header = `define i64 @${name}(i64 %env, i32 %argc, i64* %argv) {`;
|
|
@@ -127,11 +154,21 @@ class Generator {
|
|
|
127
154
|
this.emit(`%saved.argv = alloca i64*`);
|
|
128
155
|
this.emit(`store i64* %argv, i64** %saved.argv`);
|
|
129
156
|
// Parameters.
|
|
157
|
+
const parameterNodes = fn.node.parameters ?? [];
|
|
130
158
|
for (let index = 0; index < fn.params.length; index++) {
|
|
131
159
|
const symbol = fn.params[index];
|
|
160
|
+
const parameter = parameterNodes[index];
|
|
161
|
+
if (parameter?.dotDotDotToken) {
|
|
162
|
+
const rest = this.reg();
|
|
163
|
+
this.emit(` ${rest} = call i64 @xt_rest_args(i32 %argc, i64* %argv, i32 ${index})`);
|
|
164
|
+
this.declareSlot(symbol, rest);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
132
167
|
const value = this.reg();
|
|
133
168
|
this.emit(` ${value} = call i64 @xt_arg(i32 %argc, i64* %argv, i32 ${index})`);
|
|
134
169
|
this.declareSlot(symbol, value);
|
|
170
|
+
if (parameter?.initializer)
|
|
171
|
+
this.emitDefaultParameter(symbol, value, parameter.initializer);
|
|
135
172
|
}
|
|
136
173
|
// Captures threaded through the environment.
|
|
137
174
|
for (const symbol of fn.captures) {
|
|
@@ -154,7 +191,10 @@ class Generator {
|
|
|
154
191
|
}
|
|
155
192
|
if (!this.current.terminated)
|
|
156
193
|
this.terminate(`ret i64 ${i64(XT_UNDEFINED)}`);
|
|
157
|
-
const
|
|
194
|
+
const escapes = state.usesTry
|
|
195
|
+
? state.escapePointers.map((ptr) => ` call void asm sideeffect "", "r"(i64* ${ptr})`)
|
|
196
|
+
: [];
|
|
197
|
+
const lines = [...state.allocas.map((a) => ` ${a}`), ...escapes, ...state.buffer];
|
|
158
198
|
this.functions.push([header, ...lines, "}", ""].join("\n"));
|
|
159
199
|
}
|
|
160
200
|
emitMain() {
|
|
@@ -191,6 +231,7 @@ class Generator {
|
|
|
191
231
|
alloca() {
|
|
192
232
|
const ptr = `%slot${this.current.allocas.length}`;
|
|
193
233
|
this.current.allocas.push(`${ptr} = alloca i64`);
|
|
234
|
+
this.current.escapePointers.push(ptr);
|
|
194
235
|
return ptr;
|
|
195
236
|
}
|
|
196
237
|
const(hex) {
|
|
@@ -245,6 +286,22 @@ class Generator {
|
|
|
245
286
|
this.emit(` ${box} = load i64, i64* ${slot.ptr}`);
|
|
246
287
|
this.emit(` call i64 @xt_box_set(i64 ${box}, i64 ${value})`);
|
|
247
288
|
}
|
|
289
|
+
emitDefaultParameter(symbol, value, initializer) {
|
|
290
|
+
const isUndefined = this.reg();
|
|
291
|
+
this.emit(` ${isUndefined} = call i64 @xt_seq(i64 ${value}, i64 ${i64(XT_UNDEFINED)})`);
|
|
292
|
+
const truthy = this.reg();
|
|
293
|
+
this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${isUndefined})`);
|
|
294
|
+
const condition = this.reg();
|
|
295
|
+
this.emit(` ${condition} = icmp ne i32 ${truthy}, 0`);
|
|
296
|
+
const applyLabel = this.label("param.default");
|
|
297
|
+
const endLabel = this.label("param.end");
|
|
298
|
+
this.terminate(`br i1 ${condition}, label %${applyLabel}, label %${endLabel}`);
|
|
299
|
+
this.startBlock(applyLabel);
|
|
300
|
+
const fallback = this.emitExpression(initializer);
|
|
301
|
+
this.writeSlot(symbol, fallback);
|
|
302
|
+
this.terminate(`br label %${endLabel}`);
|
|
303
|
+
this.startBlock(endLabel);
|
|
304
|
+
}
|
|
248
305
|
// -- statements ----------------------------------------------------------
|
|
249
306
|
emitStatements(statements) {
|
|
250
307
|
for (const statement of statements) {
|
|
@@ -286,19 +343,29 @@ class Generator {
|
|
|
286
343
|
case SyntaxKind.ForInStatement:
|
|
287
344
|
this.emitForOf(statement);
|
|
288
345
|
return;
|
|
346
|
+
case SyntaxKind.SwitchStatement:
|
|
347
|
+
this.emitSwitch(statement);
|
|
348
|
+
return;
|
|
349
|
+
case SyntaxKind.TryStatement:
|
|
350
|
+
this.emitTry(statement);
|
|
351
|
+
return;
|
|
289
352
|
case SyntaxKind.ReturnStatement:
|
|
290
353
|
this.emitReturn(statement);
|
|
291
354
|
return;
|
|
292
355
|
case SyntaxKind.BreakStatement: {
|
|
293
356
|
const loop = this.current.loops[this.current.loops.length - 1];
|
|
294
|
-
if (loop)
|
|
357
|
+
if (loop) {
|
|
358
|
+
this.popTryFramesTo(loop.tryDepth ?? 0);
|
|
295
359
|
this.terminate(`br label %${loop.breakLabel}`);
|
|
360
|
+
}
|
|
296
361
|
return;
|
|
297
362
|
}
|
|
298
363
|
case SyntaxKind.ContinueStatement: {
|
|
299
364
|
const loop = this.current.loops[this.current.loops.length - 1];
|
|
300
|
-
if (loop)
|
|
365
|
+
if (loop) {
|
|
366
|
+
this.popTryFramesTo(loop.tryDepth ?? 0);
|
|
301
367
|
this.terminate(`br label %${loop.continueLabel}`);
|
|
368
|
+
}
|
|
302
369
|
return;
|
|
303
370
|
}
|
|
304
371
|
case SyntaxKind.ThrowStatement: {
|
|
@@ -365,7 +432,7 @@ class Generator {
|
|
|
365
432
|
this.emit(` ${nonzero} = icmp ne i32 ${truthy}, 0`);
|
|
366
433
|
this.terminate(`br i1 ${nonzero}, label %${bodyLabel}, label %${endLabel}`);
|
|
367
434
|
this.startBlock(bodyLabel);
|
|
368
|
-
this.current.loops.push({ breakLabel: endLabel, continueLabel: condLabel });
|
|
435
|
+
this.current.loops.push({ breakLabel: endLabel, continueLabel: condLabel, tryDepth: this.current.tryFrames.length });
|
|
369
436
|
this.emitStatement(statement.statement);
|
|
370
437
|
this.current.loops.pop();
|
|
371
438
|
if (!this.current.terminated)
|
|
@@ -378,7 +445,7 @@ class Generator {
|
|
|
378
445
|
const endLabel = this.label("do.end");
|
|
379
446
|
this.terminate(`br label %${bodyLabel}`);
|
|
380
447
|
this.startBlock(bodyLabel);
|
|
381
|
-
this.current.loops.push({ breakLabel: endLabel, continueLabel: condLabel });
|
|
448
|
+
this.current.loops.push({ breakLabel: endLabel, continueLabel: condLabel, tryDepth: this.current.tryFrames.length });
|
|
382
449
|
this.emitStatement(statement.statement);
|
|
383
450
|
this.current.loops.pop();
|
|
384
451
|
if (!this.current.terminated)
|
|
@@ -423,7 +490,7 @@ class Generator {
|
|
|
423
490
|
this.terminate(`br label %${bodyLabel}`);
|
|
424
491
|
}
|
|
425
492
|
this.startBlock(bodyLabel);
|
|
426
|
-
this.current.loops.push({ breakLabel: endLabel, continueLabel: updateLabel });
|
|
493
|
+
this.current.loops.push({ breakLabel: endLabel, continueLabel: updateLabel, tryDepth: this.current.tryFrames.length });
|
|
427
494
|
this.emitStatement(statement.statement);
|
|
428
495
|
this.current.loops.pop();
|
|
429
496
|
if (!this.current.terminated)
|
|
@@ -434,9 +501,13 @@ class Generator {
|
|
|
434
501
|
this.terminate(`br label %${condLabel}`);
|
|
435
502
|
this.startBlock(endLabel);
|
|
436
503
|
}
|
|
437
|
-
/** `for (const x of xs)` / `for (const k in obj)` over arrays and objects. */
|
|
504
|
+
/** `for (const x of xs)` / `for (const k in obj)` over arrays, strings and objects. */
|
|
438
505
|
emitForOf(statement) {
|
|
439
|
-
const
|
|
506
|
+
const source = this.emitExpression(statement.expression);
|
|
507
|
+
const isForIn = statement.kind === SyntaxKind.ForInStatement;
|
|
508
|
+
// `for...in` iterates the enumerable keys (indices become strings);
|
|
509
|
+
// `for...of` iterates the values at each index.
|
|
510
|
+
const iterable = isForIn ? this.runtimeCall("xt_object_keys", [source]) : source;
|
|
440
511
|
const indexPtr = this.alloca();
|
|
441
512
|
this.emit(` store i64 ${numberLiteral(0)}, i64* ${indexPtr}`);
|
|
442
513
|
const lengthValue = this.reg();
|
|
@@ -460,7 +531,7 @@ class Generator {
|
|
|
460
531
|
const element = this.reg();
|
|
461
532
|
this.emit(` ${element} = call i64 @xt_get(i64 ${iterable}, i64 ${index})`);
|
|
462
533
|
this.bindLoopVariable(statement.initializer, element);
|
|
463
|
-
this.current.loops.push({ breakLabel: endLabel, continueLabel: updateLabel });
|
|
534
|
+
this.current.loops.push({ breakLabel: endLabel, continueLabel: updateLabel, tryDepth: this.current.tryFrames.length });
|
|
464
535
|
this.emitStatement(statement.statement);
|
|
465
536
|
this.current.loops.pop();
|
|
466
537
|
if (!this.current.terminated)
|
|
@@ -488,10 +559,167 @@ class Generator {
|
|
|
488
559
|
}
|
|
489
560
|
this.emitAssignmentTarget(initializer, value);
|
|
490
561
|
}
|
|
562
|
+
/**
|
|
563
|
+
* JavaScript `switch`: test each `case` with strict equality, then run the
|
|
564
|
+
* matched clause and fall through into the following clauses until `break`.
|
|
565
|
+
*/
|
|
566
|
+
emitSwitch(statement) {
|
|
567
|
+
const discriminant = this.emitExpression(statement.expression);
|
|
568
|
+
const endLabel = this.label("switch.end");
|
|
569
|
+
const clauses = statement.clauses;
|
|
570
|
+
const labels = clauses.map(() => this.label("switch.case"));
|
|
571
|
+
const testLabels = clauses.map((clause) => clause.kind === SyntaxKind.CaseClause ? this.label("switch.test") : undefined);
|
|
572
|
+
const defaultIndex = clauses.findIndex((clause) => clause.kind === SyntaxKind.DefaultClause);
|
|
573
|
+
const defaultLabel = defaultIndex >= 0 ? labels[defaultIndex] : endLabel;
|
|
574
|
+
const caseIndexes = clauses
|
|
575
|
+
.map((clause, index) => ({ clause, index }))
|
|
576
|
+
.filter((entry) => entry.clause.kind === SyntaxKind.CaseClause);
|
|
577
|
+
this.terminate(`br label %${caseIndexes.length > 0 ? testLabels[caseIndexes[0].index] : defaultLabel}`);
|
|
578
|
+
for (let test = 0; test < caseIndexes.length; test++) {
|
|
579
|
+
const entry = caseIndexes[test];
|
|
580
|
+
this.startBlock(testLabels[entry.index]);
|
|
581
|
+
const caseValue = this.emitExpression(entry.clause.expression);
|
|
582
|
+
const equals = this.reg();
|
|
583
|
+
this.emit(` ${equals} = call i64 @xt_seq(i64 ${discriminant}, i64 ${caseValue})`);
|
|
584
|
+
const truthy = this.reg();
|
|
585
|
+
this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${equals})`);
|
|
586
|
+
const condition = this.reg();
|
|
587
|
+
this.emit(` ${condition} = icmp ne i32 ${truthy}, 0`);
|
|
588
|
+
const next = test + 1 < caseIndexes.length ? testLabels[caseIndexes[test + 1].index] : defaultLabel;
|
|
589
|
+
this.terminate(`br i1 ${condition}, label %${labels[entry.index]}, label %${next}`);
|
|
590
|
+
}
|
|
591
|
+
for (let index = 0; index < clauses.length; index++) {
|
|
592
|
+
this.startBlock(labels[index]);
|
|
593
|
+
const enclosing = this.current.loops[this.current.loops.length - 1];
|
|
594
|
+
this.current.loops.push({ breakLabel: endLabel, continueLabel: enclosing?.continueLabel ?? endLabel, tryDepth: this.current.tryFrames.length });
|
|
595
|
+
for (const child of clauses[index].statements) {
|
|
596
|
+
if (this.current.terminated)
|
|
597
|
+
break;
|
|
598
|
+
this.emitStatement(child);
|
|
599
|
+
}
|
|
600
|
+
this.current.loops.pop();
|
|
601
|
+
if (!this.current.terminated) {
|
|
602
|
+
const next = index + 1 < clauses.length ? labels[index + 1] : endLabel;
|
|
603
|
+
this.terminate(`br label %${next}`);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
this.startBlock(endLabel);
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* `try`/`catch`/`finally` via a runtime setjmp frame. The runtime keeps a
|
|
610
|
+
* stack of frames; `xt_throw` longjmps into the innermost one. A `return`
|
|
611
|
+
* that exits the protected region skips `finally` (a known limitation).
|
|
612
|
+
*/
|
|
613
|
+
emitTry(statement) {
|
|
614
|
+
this.current.usesTry = true;
|
|
615
|
+
const frameSlot = this.alloca();
|
|
616
|
+
const exceptionSlot = this.alloca();
|
|
617
|
+
const flagSlot = this.alloca();
|
|
618
|
+
const tryLabel = this.label("try.body");
|
|
619
|
+
const catchLabel = this.label("try.catch");
|
|
620
|
+
const exceptionLabel = this.label("try.exception");
|
|
621
|
+
const rethrowLabel = this.label("try.rethrow");
|
|
622
|
+
const finallyLabel = statement.finallyBlock ? this.label("try.finally") : undefined;
|
|
623
|
+
const endLabel = this.label("try.end");
|
|
624
|
+
const hasCatch = !!statement.catchClause;
|
|
625
|
+
const frame = this.reg();
|
|
626
|
+
this.emit(` ${frame} = call i8* @xt_try_enter()`);
|
|
627
|
+
this.emit(` store i8* ${frame}, i8** ${frameSlot}`);
|
|
628
|
+
this.emit(` store i64 0, i64* ${flagSlot}`);
|
|
629
|
+
const jump = this.reg();
|
|
630
|
+
this.emit(` ${jump} = call i32 @_setjmp(i8* ${frame})`);
|
|
631
|
+
const isThrow = this.reg();
|
|
632
|
+
this.emit(` ${isThrow} = icmp ne i32 ${jump}, 0`);
|
|
633
|
+
const exceptionTarget = hasCatch ? catchLabel : exceptionLabel;
|
|
634
|
+
this.terminate(`br i1 ${isThrow}, label %${exceptionTarget}, label %${tryLabel}`);
|
|
635
|
+
// Normal completion of the try block.
|
|
636
|
+
this.startBlock(tryLabel);
|
|
637
|
+
this.current.tryFrames.push(frameSlot);
|
|
638
|
+
this.emitStatements(statement.tryBlock.statements);
|
|
639
|
+
this.current.tryFrames.pop();
|
|
640
|
+
if (!this.current.terminated) {
|
|
641
|
+
const currentFrame = this.reg();
|
|
642
|
+
this.emit(` ${currentFrame} = load i8*, i8** ${frameSlot}`);
|
|
643
|
+
this.emit(` call void @xt_try_leave(i8* ${currentFrame})`);
|
|
644
|
+
this.terminate(`br label %${finallyLabel ?? endLabel}`);
|
|
645
|
+
}
|
|
646
|
+
if (hasCatch) {
|
|
647
|
+
this.startBlock(catchLabel);
|
|
648
|
+
const currentFrame = this.reg();
|
|
649
|
+
this.emit(` ${currentFrame} = load i8*, i8** ${frameSlot}`);
|
|
650
|
+
const exception = this.reg();
|
|
651
|
+
this.emit(` ${exception} = call i64 @xt_try_exception(i8* ${currentFrame})`);
|
|
652
|
+
this.emit(` call void @xt_try_leave(i8* ${currentFrame})`);
|
|
653
|
+
this.bindCatchVariable(statement.catchClause.variable, exception);
|
|
654
|
+
this.emitStatements(statement.catchClause.block.statements);
|
|
655
|
+
if (!this.current.terminated)
|
|
656
|
+
this.terminate(`br label %${finallyLabel ?? endLabel}`);
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
// Without a catch clause the exception is remembered, then rethrown
|
|
660
|
+
// after `finally` runs.
|
|
661
|
+
this.startBlock(exceptionLabel);
|
|
662
|
+
const currentFrame = this.reg();
|
|
663
|
+
this.emit(` ${currentFrame} = load i8*, i8** ${frameSlot}`);
|
|
664
|
+
const exception = this.reg();
|
|
665
|
+
this.emit(` ${exception} = call i64 @xt_try_exception(i8* ${currentFrame})`);
|
|
666
|
+
this.emit(` call void @xt_try_leave(i8* ${currentFrame})`);
|
|
667
|
+
this.emit(` store i64 ${exception}, i64* ${exceptionSlot}`);
|
|
668
|
+
this.emit(` store i64 1, i64* ${flagSlot}`);
|
|
669
|
+
this.terminate(`br label %${finallyLabel ?? rethrowLabel}`);
|
|
670
|
+
}
|
|
671
|
+
if (finallyLabel) {
|
|
672
|
+
this.startBlock(finallyLabel);
|
|
673
|
+
this.emitStatements(statement.finallyBlock.statements);
|
|
674
|
+
if (!this.current.terminated) {
|
|
675
|
+
if (hasCatch) {
|
|
676
|
+
this.terminate(`br label %${endLabel}`);
|
|
677
|
+
}
|
|
678
|
+
else {
|
|
679
|
+
const flag = this.reg();
|
|
680
|
+
this.emit(` ${flag} = load i64, i64* ${flagSlot}`);
|
|
681
|
+
const truthy = this.reg();
|
|
682
|
+
this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${flag})`);
|
|
683
|
+
const shouldRethrow = this.reg();
|
|
684
|
+
this.emit(` ${shouldRethrow} = icmp ne i32 ${truthy}, 0`);
|
|
685
|
+
this.terminate(`br i1 ${shouldRethrow}, label %${rethrowLabel}, label %${endLabel}`);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
if (!hasCatch) {
|
|
690
|
+
this.startBlock(rethrowLabel);
|
|
691
|
+
const exception = this.reg();
|
|
692
|
+
this.emit(` ${exception} = load i64, i64* ${exceptionSlot}`);
|
|
693
|
+
this.emit(` call void @xt_throw(i64 ${exception})`);
|
|
694
|
+
this.terminate("unreachable");
|
|
695
|
+
}
|
|
696
|
+
this.startBlock(endLabel);
|
|
697
|
+
}
|
|
698
|
+
bindCatchVariable(variable, value) {
|
|
699
|
+
if (!variable)
|
|
700
|
+
return;
|
|
701
|
+
const symbol = this.binding.symbolOfDeclaration.get(variable);
|
|
702
|
+
if (!symbol)
|
|
703
|
+
return;
|
|
704
|
+
if (this.current.slots.has(symbol.id))
|
|
705
|
+
this.writeSlot(symbol, value);
|
|
706
|
+
else
|
|
707
|
+
this.declareSlot(symbol, value);
|
|
708
|
+
}
|
|
491
709
|
emitReturn(statement) {
|
|
492
710
|
const value = statement.expression ? this.emitExpression(statement.expression) : i64(XT_UNDEFINED);
|
|
711
|
+
this.popTryFramesTo(0);
|
|
493
712
|
this.terminate(`ret i64 ${value}`);
|
|
494
713
|
}
|
|
714
|
+
/** Emit `xt_try_leave` for every active frame above `depth` (innermost first). */
|
|
715
|
+
popTryFramesTo(depth) {
|
|
716
|
+
for (let index = this.current.tryFrames.length - 1; index >= depth; index--) {
|
|
717
|
+
const slot = this.current.tryFrames[index];
|
|
718
|
+
const frame = this.reg();
|
|
719
|
+
this.emit(` ${frame} = load i8*, i8** ${slot}`);
|
|
720
|
+
this.emit(` call void @xt_try_leave(i8* ${frame})`);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
495
723
|
// -- expressions ---------------------------------------------------------
|
|
496
724
|
emitExpression(node) {
|
|
497
725
|
switch (node.kind) {
|
|
@@ -523,6 +751,8 @@ class Generator {
|
|
|
523
751
|
case SyntaxKind.SatisfiesExpression:
|
|
524
752
|
case SyntaxKind.NonNullExpression:
|
|
525
753
|
return this.emitExpression(node.expression);
|
|
754
|
+
case SyntaxKind.DeleteExpression:
|
|
755
|
+
return this.emitDelete(node);
|
|
526
756
|
case SyntaxKind.BinaryExpression:
|
|
527
757
|
return this.emitBinaryOrAssignment(node);
|
|
528
758
|
case SyntaxKind.PrefixUnaryExpression:
|
|
@@ -566,6 +796,15 @@ class Generator {
|
|
|
566
796
|
return numberLiteral(Infinity);
|
|
567
797
|
case "console":
|
|
568
798
|
return i64(XT_UNDEFINED);
|
|
799
|
+
case "arguments": {
|
|
800
|
+
const argc = this.reg();
|
|
801
|
+
this.emit(` ${argc} = load i32, i32* %saved.argc`);
|
|
802
|
+
const argv = this.reg();
|
|
803
|
+
this.emit(` ${argv} = load i64*, i64** %saved.argv`);
|
|
804
|
+
const rest = this.reg();
|
|
805
|
+
this.emit(` ${rest} = call i64 @xt_rest_args(i32 ${argc}, i64* ${argv}, i32 0)`);
|
|
806
|
+
return rest;
|
|
807
|
+
}
|
|
569
808
|
default:
|
|
570
809
|
this.diagnostics.error(DiagnosticCode.CannotFindName, `Cannot find name '${identifier.text}'`, identifier, this.sourceFile.fileName);
|
|
571
810
|
return i64(XT_UNDEFINED);
|
|
@@ -854,6 +1093,32 @@ class Generator {
|
|
|
854
1093
|
}
|
|
855
1094
|
emitCall(node) {
|
|
856
1095
|
const callee = node.expression;
|
|
1096
|
+
if (node.optional) {
|
|
1097
|
+
const calleeValue = this.emitExpression(callee);
|
|
1098
|
+
return this.emitOptional(calleeValue, () => {
|
|
1099
|
+
const args = this.emitArguments(node.arguments);
|
|
1100
|
+
const result = this.reg();
|
|
1101
|
+
this.emit(` ${result} = call i64 @xt_closure_call(i64 ${calleeValue}, i32 ${args.argc}, i64* ${args.ptr})`);
|
|
1102
|
+
return result;
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
if ((callee.kind === SyntaxKind.PropertyAccessExpression || callee.kind === SyntaxKind.ElementAccessExpression) &&
|
|
1106
|
+
callee.optional) {
|
|
1107
|
+
// `obj?.method(args)` / `obj?.[key](args)`: guard the receiver, then
|
|
1108
|
+
// dispatch through the runtime, which understands object methods as well
|
|
1109
|
+
// as the built-in array/string methods.
|
|
1110
|
+
const access = callee;
|
|
1111
|
+
const object = this.emitExpression(access.expression);
|
|
1112
|
+
return this.emitOptional(object, () => {
|
|
1113
|
+
const name = access.kind === SyntaxKind.PropertyAccessExpression
|
|
1114
|
+
? this.stringValue(access.name.text)
|
|
1115
|
+
: this.emitExpression(access.argumentExpression);
|
|
1116
|
+
const args = this.emitArguments(node.arguments);
|
|
1117
|
+
const result = this.reg();
|
|
1118
|
+
this.emit(` ${result} = call i64 @xt_call_method(i64 ${object}, i64 ${name}, i32 ${args.argc}, i64* ${args.ptr})`);
|
|
1119
|
+
return result;
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
857
1122
|
if (callee.kind === SyntaxKind.PropertyAccessExpression) {
|
|
858
1123
|
const special = this.tryEmitBuiltinCall(node, callee);
|
|
859
1124
|
if (special)
|
|
@@ -871,6 +1136,13 @@ class Generator {
|
|
|
871
1136
|
return result;
|
|
872
1137
|
}
|
|
873
1138
|
}
|
|
1139
|
+
const globalFunction = !symbol ? GLOBAL_FUNCTIONS[callee.text] : undefined;
|
|
1140
|
+
if (globalFunction) {
|
|
1141
|
+
const args = this.emitArguments(node.arguments);
|
|
1142
|
+
const result = this.reg();
|
|
1143
|
+
this.emit(` ${result} = call i64 @${globalFunction}(i32 ${args.argc}, i64* ${args.ptr})`);
|
|
1144
|
+
return result;
|
|
1145
|
+
}
|
|
874
1146
|
const builtin = this.builtins[callee.text];
|
|
875
1147
|
if (!symbol && builtin) {
|
|
876
1148
|
this.extraDeclarations.add(builtin.returnVoid ? `declare void @${builtin.symbol}(i32, i64*)` : `declare i64 @${builtin.symbol}(i32, i64*)`);
|
|
@@ -893,32 +1165,47 @@ class Generator {
|
|
|
893
1165
|
tryEmitBuiltinCall(node, callee) {
|
|
894
1166
|
const target = callee.expression;
|
|
895
1167
|
const method = callee.name.text;
|
|
896
|
-
|
|
1168
|
+
const targetIdentifier = target.kind === SyntaxKind.Identifier ? target : undefined;
|
|
1169
|
+
const targetSymbol = targetIdentifier ? this.binding.symbolOfIdentifier.get(targetIdentifier) : undefined;
|
|
1170
|
+
if (targetIdentifier && targetIdentifier.text === "console" && !targetSymbol) {
|
|
1171
|
+
const consoleFn = CONSOLE_METHODS[method];
|
|
1172
|
+
if (consoleFn) {
|
|
1173
|
+
const args = this.emitArguments(node.arguments);
|
|
1174
|
+
this.emit(` call void @${consoleFn}(i32 ${args.argc}, i64* ${args.ptr})`);
|
|
1175
|
+
return i64(XT_UNDEFINED);
|
|
1176
|
+
}
|
|
1177
|
+
return undefined;
|
|
1178
|
+
}
|
|
1179
|
+
if (targetIdentifier && targetIdentifier.text === "Math" && !targetSymbol) {
|
|
1180
|
+
if (!MATH_FUNCTIONS.has(method))
|
|
1181
|
+
return undefined;
|
|
1182
|
+
const name = this.stringValue(method);
|
|
897
1183
|
const args = this.emitArguments(node.arguments);
|
|
898
|
-
|
|
899
|
-
|
|
1184
|
+
const result = this.reg();
|
|
1185
|
+
this.emit(` ${result} = call i64 @xt_math_call(i64 ${name}, i32 ${args.argc}, i64* ${args.ptr})`);
|
|
1186
|
+
return result;
|
|
900
1187
|
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
const
|
|
1188
|
+
if (targetIdentifier && targetIdentifier.text === "Object" && !targetSymbol) {
|
|
1189
|
+
if (method === "keys" || method === "values" || method === "entries") {
|
|
1190
|
+
const argument = node.arguments.length > 0 ? this.emitExpression(node.arguments[0]) : i64(XT_UNDEFINED);
|
|
1191
|
+
const fn = method === "keys" ? "xt_object_keys" : method === "values" ? "xt_object_values" : "xt_object_entries";
|
|
1192
|
+
return this.runtimeCall(fn, [argument]);
|
|
1193
|
+
}
|
|
1194
|
+
if (method === "assign") {
|
|
905
1195
|
const args = this.emitArguments(node.arguments);
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
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;
|
|
1196
|
+
const result = this.reg();
|
|
1197
|
+
this.emit(` ${result} = call i64 @xt_object_assign(i32 ${args.argc}, i64* ${args.ptr})`);
|
|
1198
|
+
return result;
|
|
919
1199
|
}
|
|
920
|
-
|
|
921
|
-
|
|
1200
|
+
return undefined;
|
|
1201
|
+
}
|
|
1202
|
+
if (BUILTIN_METHODS.has(method)) {
|
|
1203
|
+
const object = this.emitExpression(target);
|
|
1204
|
+
const name = this.stringValue(method);
|
|
1205
|
+
const args = this.emitArguments(node.arguments);
|
|
1206
|
+
const result = this.reg();
|
|
1207
|
+
this.emit(` ${result} = call i64 @xt_call_method(i64 ${object}, i64 ${name}, i32 ${args.argc}, i64* ${args.ptr})`);
|
|
1208
|
+
return result;
|
|
922
1209
|
}
|
|
923
1210
|
return undefined;
|
|
924
1211
|
}
|
|
@@ -940,25 +1227,77 @@ class Generator {
|
|
|
940
1227
|
}
|
|
941
1228
|
return { argc: args.length, ptr };
|
|
942
1229
|
}
|
|
1230
|
+
emitDelete(node) {
|
|
1231
|
+
const target = node.expression;
|
|
1232
|
+
if (target.kind === SyntaxKind.PropertyAccessExpression) {
|
|
1233
|
+
const access = target;
|
|
1234
|
+
const object = this.emitExpression(access.expression);
|
|
1235
|
+
const key = this.stringValue(access.name.text);
|
|
1236
|
+
return this.runtimeCall("xt_delete", [object, key]);
|
|
1237
|
+
}
|
|
1238
|
+
if (target.kind === SyntaxKind.ElementAccessExpression) {
|
|
1239
|
+
const access = target;
|
|
1240
|
+
const object = this.emitExpression(access.expression);
|
|
1241
|
+
const key = this.emitExpression(access.argumentExpression);
|
|
1242
|
+
return this.runtimeCall("xt_delete", [object, key]);
|
|
1243
|
+
}
|
|
1244
|
+
return i64(XT_TRUE);
|
|
1245
|
+
}
|
|
943
1246
|
emitPropertyAccess(node) {
|
|
944
|
-
if (node.name.text
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
this.
|
|
948
|
-
return
|
|
1247
|
+
if (node.name.text in MATH_CONSTANTS &&
|
|
1248
|
+
node.expression.kind === SyntaxKind.Identifier &&
|
|
1249
|
+
node.expression.text === "Math" &&
|
|
1250
|
+
!this.binding.symbolOfIdentifier.get(node.expression)) {
|
|
1251
|
+
return numberLiteral(MATH_CONSTANTS[node.name.text]);
|
|
949
1252
|
}
|
|
950
1253
|
const object = this.emitExpression(node.expression);
|
|
951
|
-
const
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1254
|
+
const access = () => {
|
|
1255
|
+
if (node.name.text === "length") {
|
|
1256
|
+
const result = this.reg();
|
|
1257
|
+
this.emit(` ${result} = call i64 @xt_array_length(i64 ${object})`);
|
|
1258
|
+
return result;
|
|
1259
|
+
}
|
|
1260
|
+
const key = this.stringValue(node.name.text);
|
|
1261
|
+
const result = this.reg();
|
|
1262
|
+
this.emit(` ${result} = call i64 @xt_get(i64 ${object}, i64 ${key})`);
|
|
1263
|
+
return result;
|
|
1264
|
+
};
|
|
1265
|
+
return node.optional ? this.emitOptional(object, access) : access();
|
|
955
1266
|
}
|
|
956
1267
|
emitElementAccess(node) {
|
|
957
1268
|
const object = this.emitExpression(node.expression);
|
|
958
|
-
const
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
1269
|
+
const access = () => {
|
|
1270
|
+
const key = this.emitExpression(node.argumentExpression);
|
|
1271
|
+
const result = this.reg();
|
|
1272
|
+
this.emit(` ${result} = call i64 @xt_get(i64 ${object}, i64 ${key})`);
|
|
1273
|
+
return result;
|
|
1274
|
+
};
|
|
1275
|
+
return node.optional ? this.emitOptional(object, access) : access();
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Evaluate `object` once and, when it is neither `null` nor `undefined`, run
|
|
1279
|
+
* `compute` to produce the value; otherwise short-circuit to `undefined`.
|
|
1280
|
+
*/
|
|
1281
|
+
emitOptional(objectValue, compute) {
|
|
1282
|
+
const result = this.alloca();
|
|
1283
|
+
this.emit(` store i64 ${i64(XT_UNDEFINED)}, i64* ${result}`);
|
|
1284
|
+
const nullish = this.reg();
|
|
1285
|
+
this.emit(` ${nullish} = call i64 @xt_is_nullish(i64 ${objectValue})`);
|
|
1286
|
+
const truthy = this.reg();
|
|
1287
|
+
this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${nullish})`);
|
|
1288
|
+
const condition = this.reg();
|
|
1289
|
+
this.emit(` ${condition} = icmp ne i32 ${truthy}, 0`);
|
|
1290
|
+
const someLabel = this.label("opt.some");
|
|
1291
|
+
const endLabel = this.label("opt.end");
|
|
1292
|
+
this.terminate(`br i1 ${condition}, label %${endLabel}, label %${someLabel}`);
|
|
1293
|
+
this.startBlock(someLabel);
|
|
1294
|
+
const value = compute();
|
|
1295
|
+
this.emit(` store i64 ${value}, i64* ${result}`);
|
|
1296
|
+
this.terminate(`br label %${endLabel}`);
|
|
1297
|
+
this.startBlock(endLabel);
|
|
1298
|
+
const merged = this.reg();
|
|
1299
|
+
this.emit(` ${merged} = load i64, i64* ${result}`);
|
|
1300
|
+
return merged;
|
|
962
1301
|
}
|
|
963
1302
|
emitArrayLiteral(node) {
|
|
964
1303
|
const hasSpread = node.elements.some((element) => element.kind === SyntaxKind.SpreadElement);
|
|
@@ -997,6 +1336,10 @@ class Generator {
|
|
|
997
1336
|
const value = this.emitIdentifier(identifier);
|
|
998
1337
|
this.emit(` call i64 @xt_set(i64 ${object}, i64 ${key}, i64 ${value})`);
|
|
999
1338
|
}
|
|
1339
|
+
else if (property.kind === SyntaxKind.SpreadElement) {
|
|
1340
|
+
const spread = this.emitExpression(property.expression);
|
|
1341
|
+
this.emit(` call i64 @xt_object_spread(i64 ${object}, i64 ${spread})`);
|
|
1342
|
+
}
|
|
1000
1343
|
else {
|
|
1001
1344
|
this.unsupported(property, "object spread");
|
|
1002
1345
|
}
|
|
@@ -1083,7 +1426,42 @@ const BINARY_RUNTIME = {
|
|
|
1083
1426
|
[BinaryOperator.LessThanLessThan]: "xt_shl",
|
|
1084
1427
|
[BinaryOperator.GreaterThanGreaterThan]: "xt_shr",
|
|
1085
1428
|
[BinaryOperator.GreaterThanGreaterThanGreaterThan]: "xt_ushr",
|
|
1429
|
+
[BinaryOperator.In]: "xt_in",
|
|
1430
|
+
};
|
|
1431
|
+
const CONSOLE_METHODS = {
|
|
1432
|
+
log: "xt_console_log",
|
|
1433
|
+
info: "xt_console_info",
|
|
1434
|
+
warn: "xt_console_warn",
|
|
1435
|
+
error: "xt_console_error",
|
|
1436
|
+
};
|
|
1437
|
+
const MATH_FUNCTIONS = new Set([
|
|
1438
|
+
"abs", "floor", "ceil", "round", "trunc", "sqrt", "cbrt", "pow", "exp", "log", "log2", "log10",
|
|
1439
|
+
"sin", "cos", "tan", "asin", "acos", "atan", "atan2", "hypot", "sign", "random", "min", "max",
|
|
1440
|
+
]);
|
|
1441
|
+
const MATH_CONSTANTS = {
|
|
1442
|
+
PI: Math.PI,
|
|
1443
|
+
E: Math.E,
|
|
1444
|
+
LN2: Math.LN2,
|
|
1445
|
+
LN10: Math.LN10,
|
|
1446
|
+
LOG2E: Math.LOG2E,
|
|
1447
|
+
LOG10E: Math.LOG10E,
|
|
1448
|
+
SQRT2: Math.SQRT2,
|
|
1449
|
+
SQRT1_2: Math.SQRT1_2,
|
|
1450
|
+
};
|
|
1451
|
+
const GLOBAL_FUNCTIONS = {
|
|
1452
|
+
parseInt: "xt_parse_int",
|
|
1453
|
+
parseFloat: "xt_parse_float",
|
|
1454
|
+
isNaN: "xt_is_nan",
|
|
1455
|
+
isFinite: "xt_is_finite",
|
|
1456
|
+
Number: "xt_number_ctor",
|
|
1457
|
+
String: "xt_string_ctor",
|
|
1458
|
+
Boolean: "xt_boolean_ctor",
|
|
1086
1459
|
};
|
|
1460
|
+
const BUILTIN_METHODS = new Set([
|
|
1461
|
+
"push", "pop", "shift", "unshift", "join", "slice", "indexOf", "includes", "map", "forEach", "filter",
|
|
1462
|
+
"reduce", "concat", "reverse", "charAt", "charCodeAt", "substring", "substr", "split", "toUpperCase",
|
|
1463
|
+
"toLowerCase", "trim", "replace", "repeat", "startsWith", "endsWith",
|
|
1464
|
+
]);
|
|
1087
1465
|
const ASSIGNMENT_OPERATORS = new Set([
|
|
1088
1466
|
"=",
|
|
1089
1467
|
"+=",
|