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/src/codegen/llvm.ts
CHANGED
|
@@ -41,6 +41,10 @@ import {
|
|
|
41
41
|
type ForStatement,
|
|
42
42
|
type ForOfStatement,
|
|
43
43
|
type ForInStatement,
|
|
44
|
+
type SwitchStatement,
|
|
45
|
+
type CaseClause,
|
|
46
|
+
type TryStatement,
|
|
47
|
+
type DeleteExpression,
|
|
44
48
|
type ReturnStatement,
|
|
45
49
|
type ThrowStatement,
|
|
46
50
|
type ObjectLiteralExpression,
|
|
@@ -77,6 +81,8 @@ interface Slot {
|
|
|
77
81
|
interface LoopLabels {
|
|
78
82
|
readonly breakLabel: string;
|
|
79
83
|
readonly continueLabel: string;
|
|
84
|
+
/** Number of enclosing `try` frames when the loop was entered. */
|
|
85
|
+
readonly tryDepth?: number;
|
|
80
86
|
}
|
|
81
87
|
|
|
82
88
|
interface FunctionState {
|
|
@@ -88,6 +94,10 @@ interface FunctionState {
|
|
|
88
94
|
label: number;
|
|
89
95
|
terminated: boolean;
|
|
90
96
|
readonly loops: LoopLabels[];
|
|
97
|
+
readonly tryFrames: string[];
|
|
98
|
+
readonly escapePointers: string[];
|
|
99
|
+
/** Set when the function contains a `try`, forcing locals to live in memory. */
|
|
100
|
+
usesTry: boolean;
|
|
91
101
|
}
|
|
92
102
|
|
|
93
103
|
const RUNTIME_DECLARATIONS: readonly string[] = [
|
|
@@ -133,6 +143,23 @@ const RUNTIME_DECLARATIONS: readonly string[] = [
|
|
|
133
143
|
"declare i64 @xt_object_get_cstr(i64, i8*)",
|
|
134
144
|
"declare i64 @xt_object_set(i64, i64, i64)",
|
|
135
145
|
"declare i64 @xt_object_has(i64, i64)",
|
|
146
|
+
"declare i64 @xt_object_keys(i64)",
|
|
147
|
+
"declare i64 @xt_object_values(i64)",
|
|
148
|
+
"declare i64 @xt_object_entries(i64)",
|
|
149
|
+
"declare i64 @xt_object_assign(i32, i64*)",
|
|
150
|
+
"declare i64 @xt_object_spread(i64, i64)",
|
|
151
|
+
"declare i64 @xt_call_method(i64, i64, i32, i64*)",
|
|
152
|
+
"declare i64 @xt_math_call(i64, i32, i64*)",
|
|
153
|
+
"declare i64 @xt_parse_int(i32, i64*)",
|
|
154
|
+
"declare i64 @xt_parse_float(i32, i64*)",
|
|
155
|
+
"declare i64 @xt_is_nan(i32, i64*)",
|
|
156
|
+
"declare i64 @xt_is_finite(i32, i64*)",
|
|
157
|
+
"declare i64 @xt_number_ctor(i32, i64*)",
|
|
158
|
+
"declare i64 @xt_string_ctor(i32, i64*)",
|
|
159
|
+
"declare i64 @xt_boolean_ctor(i32, i64*)",
|
|
160
|
+
"declare i64 @xt_in(i64, i64)",
|
|
161
|
+
"declare i64 @xt_delete(i64, i64)",
|
|
162
|
+
"declare i64 @xt_rest_args(i32, i64*, i32)",
|
|
136
163
|
"declare i64 @xt_array_new(i32, i64*)",
|
|
137
164
|
"declare i64 @xt_array_push(i64, i64)",
|
|
138
165
|
"declare i64 @xt_array_length(i64)",
|
|
@@ -144,7 +171,14 @@ const RUNTIME_DECLARATIONS: readonly string[] = [
|
|
|
144
171
|
"declare i64 @xt_box_set(i64, i64)",
|
|
145
172
|
"declare i64 @xt_is_nullish(i64)",
|
|
146
173
|
"declare void @xt_throw(i64)",
|
|
174
|
+
"declare i32 @_setjmp(i8*) returns_twice",
|
|
175
|
+
"declare i8* @xt_try_enter()",
|
|
176
|
+
"declare i64 @xt_try_exception(i8*)",
|
|
177
|
+
"declare void @xt_try_leave(i8*)",
|
|
147
178
|
"declare void @xt_console_log(i32, i64*)",
|
|
179
|
+
"declare void @xt_console_info(i32, i64*)",
|
|
180
|
+
"declare void @xt_console_warn(i32, i64*)",
|
|
181
|
+
"declare void @xt_console_error(i32, i64*)",
|
|
148
182
|
];
|
|
149
183
|
|
|
150
184
|
export interface CodegenResult {
|
|
@@ -205,6 +239,9 @@ class Generator {
|
|
|
205
239
|
label: 0,
|
|
206
240
|
terminated: false,
|
|
207
241
|
loops: [],
|
|
242
|
+
tryFrames: [],
|
|
243
|
+
escapePointers: [],
|
|
244
|
+
usesTry: false,
|
|
208
245
|
};
|
|
209
246
|
this.current = state;
|
|
210
247
|
|
|
@@ -217,11 +254,20 @@ class Generator {
|
|
|
217
254
|
this.emit(`store i64* %argv, i64** %saved.argv`);
|
|
218
255
|
|
|
219
256
|
// Parameters.
|
|
257
|
+
const parameterNodes = (fn.node as { parameters?: Parameter[] }).parameters ?? [];
|
|
220
258
|
for (let index = 0; index < fn.params.length; index++) {
|
|
221
259
|
const symbol = fn.params[index]!;
|
|
260
|
+
const parameter = parameterNodes[index];
|
|
261
|
+
if (parameter?.dotDotDotToken) {
|
|
262
|
+
const rest = this.reg();
|
|
263
|
+
this.emit(` ${rest} = call i64 @xt_rest_args(i32 %argc, i64* %argv, i32 ${index})`);
|
|
264
|
+
this.declareSlot(symbol, rest);
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
222
267
|
const value = this.reg();
|
|
223
268
|
this.emit(` ${value} = call i64 @xt_arg(i32 %argc, i64* %argv, i32 ${index})`);
|
|
224
269
|
this.declareSlot(symbol, value);
|
|
270
|
+
if (parameter?.initializer) this.emitDefaultParameter(symbol, value, parameter.initializer);
|
|
225
271
|
}
|
|
226
272
|
|
|
227
273
|
// Captures threaded through the environment.
|
|
@@ -245,7 +291,10 @@ class Generator {
|
|
|
245
291
|
|
|
246
292
|
if (!this.current.terminated) this.terminate(`ret i64 ${i64(XT_UNDEFINED)}`);
|
|
247
293
|
|
|
248
|
-
const
|
|
294
|
+
const escapes = state.usesTry
|
|
295
|
+
? state.escapePointers.map((ptr) => ` call void asm sideeffect "", "r"(i64* ${ptr})`)
|
|
296
|
+
: [];
|
|
297
|
+
const lines = [...state.allocas.map((a) => ` ${a}`), ...escapes, ...state.buffer];
|
|
249
298
|
this.functions.push([header, ...lines, "}", ""].join("\n"));
|
|
250
299
|
}
|
|
251
300
|
|
|
@@ -293,6 +342,7 @@ class Generator {
|
|
|
293
342
|
private alloca(): string {
|
|
294
343
|
const ptr = `%slot${this.current.allocas.length}`;
|
|
295
344
|
this.current.allocas.push(`${ptr} = alloca i64`);
|
|
345
|
+
this.current.escapePointers.push(ptr);
|
|
296
346
|
return ptr;
|
|
297
347
|
}
|
|
298
348
|
|
|
@@ -355,6 +405,23 @@ class Generator {
|
|
|
355
405
|
this.emit(` call i64 @xt_box_set(i64 ${box}, i64 ${value})`);
|
|
356
406
|
}
|
|
357
407
|
|
|
408
|
+
private emitDefaultParameter(symbol: SymbolInfo, value: string, initializer: Expression): void {
|
|
409
|
+
const isUndefined = this.reg();
|
|
410
|
+
this.emit(` ${isUndefined} = call i64 @xt_seq(i64 ${value}, i64 ${i64(XT_UNDEFINED)})`);
|
|
411
|
+
const truthy = this.reg();
|
|
412
|
+
this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${isUndefined})`);
|
|
413
|
+
const condition = this.reg();
|
|
414
|
+
this.emit(` ${condition} = icmp ne i32 ${truthy}, 0`);
|
|
415
|
+
const applyLabel = this.label("param.default");
|
|
416
|
+
const endLabel = this.label("param.end");
|
|
417
|
+
this.terminate(`br i1 ${condition}, label %${applyLabel}, label %${endLabel}`);
|
|
418
|
+
this.startBlock(applyLabel);
|
|
419
|
+
const fallback = this.emitExpression(initializer);
|
|
420
|
+
this.writeSlot(symbol, fallback);
|
|
421
|
+
this.terminate(`br label %${endLabel}`);
|
|
422
|
+
this.startBlock(endLabel);
|
|
423
|
+
}
|
|
424
|
+
|
|
358
425
|
// -- statements ----------------------------------------------------------
|
|
359
426
|
|
|
360
427
|
private emitStatements(statements: readonly Statement[]): void {
|
|
@@ -397,17 +464,29 @@ class Generator {
|
|
|
397
464
|
case SyntaxKind.ForInStatement:
|
|
398
465
|
this.emitForOf(statement as ForOfStatement | ForInStatement);
|
|
399
466
|
return;
|
|
467
|
+
case SyntaxKind.SwitchStatement:
|
|
468
|
+
this.emitSwitch(statement as SwitchStatement);
|
|
469
|
+
return;
|
|
470
|
+
case SyntaxKind.TryStatement:
|
|
471
|
+
this.emitTry(statement as TryStatement);
|
|
472
|
+
return;
|
|
400
473
|
case SyntaxKind.ReturnStatement:
|
|
401
474
|
this.emitReturn(statement as ReturnStatement);
|
|
402
475
|
return;
|
|
403
476
|
case SyntaxKind.BreakStatement: {
|
|
404
477
|
const loop = this.current.loops[this.current.loops.length - 1];
|
|
405
|
-
if (loop)
|
|
478
|
+
if (loop) {
|
|
479
|
+
this.popTryFramesTo(loop.tryDepth ?? 0);
|
|
480
|
+
this.terminate(`br label %${loop.breakLabel}`);
|
|
481
|
+
}
|
|
406
482
|
return;
|
|
407
483
|
}
|
|
408
484
|
case SyntaxKind.ContinueStatement: {
|
|
409
485
|
const loop = this.current.loops[this.current.loops.length - 1];
|
|
410
|
-
if (loop)
|
|
486
|
+
if (loop) {
|
|
487
|
+
this.popTryFramesTo(loop.tryDepth ?? 0);
|
|
488
|
+
this.terminate(`br label %${loop.continueLabel}`);
|
|
489
|
+
}
|
|
411
490
|
return;
|
|
412
491
|
}
|
|
413
492
|
case SyntaxKind.ThrowStatement: {
|
|
@@ -476,7 +555,7 @@ class Generator {
|
|
|
476
555
|
this.terminate(`br i1 ${nonzero}, label %${bodyLabel}, label %${endLabel}`);
|
|
477
556
|
|
|
478
557
|
this.startBlock(bodyLabel);
|
|
479
|
-
this.current.loops.push({ breakLabel: endLabel, continueLabel: condLabel });
|
|
558
|
+
this.current.loops.push({ breakLabel: endLabel, continueLabel: condLabel, tryDepth: this.current.tryFrames.length });
|
|
480
559
|
this.emitStatement(statement.statement);
|
|
481
560
|
this.current.loops.pop();
|
|
482
561
|
if (!this.current.terminated) this.terminate(`br label %${condLabel}`);
|
|
@@ -489,7 +568,7 @@ class Generator {
|
|
|
489
568
|
const endLabel = this.label("do.end");
|
|
490
569
|
this.terminate(`br label %${bodyLabel}`);
|
|
491
570
|
this.startBlock(bodyLabel);
|
|
492
|
-
this.current.loops.push({ breakLabel: endLabel, continueLabel: condLabel });
|
|
571
|
+
this.current.loops.push({ breakLabel: endLabel, continueLabel: condLabel, tryDepth: this.current.tryFrames.length });
|
|
493
572
|
this.emitStatement(statement.statement);
|
|
494
573
|
this.current.loops.pop();
|
|
495
574
|
if (!this.current.terminated) this.terminate(`br label %${condLabel}`);
|
|
@@ -535,7 +614,7 @@ class Generator {
|
|
|
535
614
|
}
|
|
536
615
|
|
|
537
616
|
this.startBlock(bodyLabel);
|
|
538
|
-
this.current.loops.push({ breakLabel: endLabel, continueLabel: updateLabel });
|
|
617
|
+
this.current.loops.push({ breakLabel: endLabel, continueLabel: updateLabel, tryDepth: this.current.tryFrames.length });
|
|
539
618
|
this.emitStatement(statement.statement);
|
|
540
619
|
this.current.loops.pop();
|
|
541
620
|
if (!this.current.terminated) this.terminate(`br label %${updateLabel}`);
|
|
@@ -546,9 +625,13 @@ class Generator {
|
|
|
546
625
|
this.startBlock(endLabel);
|
|
547
626
|
}
|
|
548
627
|
|
|
549
|
-
/** `for (const x of xs)` / `for (const k in obj)` over arrays and objects. */
|
|
628
|
+
/** `for (const x of xs)` / `for (const k in obj)` over arrays, strings and objects. */
|
|
550
629
|
private emitForOf(statement: ForOfStatement | ForInStatement): void {
|
|
551
|
-
const
|
|
630
|
+
const source = this.emitExpression(statement.expression);
|
|
631
|
+
const isForIn = statement.kind === SyntaxKind.ForInStatement;
|
|
632
|
+
// `for...in` iterates the enumerable keys (indices become strings);
|
|
633
|
+
// `for...of` iterates the values at each index.
|
|
634
|
+
const iterable = isForIn ? this.runtimeCall("xt_object_keys", [source]) : source;
|
|
552
635
|
const indexPtr = this.alloca();
|
|
553
636
|
this.emit(` store i64 ${numberLiteral(0)}, i64* ${indexPtr}`);
|
|
554
637
|
const lengthValue = this.reg();
|
|
@@ -575,7 +658,7 @@ class Generator {
|
|
|
575
658
|
const element = this.reg();
|
|
576
659
|
this.emit(` ${element} = call i64 @xt_get(i64 ${iterable}, i64 ${index})`);
|
|
577
660
|
this.bindLoopVariable(statement.initializer, element);
|
|
578
|
-
this.current.loops.push({ breakLabel: endLabel, continueLabel: updateLabel });
|
|
661
|
+
this.current.loops.push({ breakLabel: endLabel, continueLabel: updateLabel, tryDepth: this.current.tryFrames.length });
|
|
579
662
|
this.emitStatement(statement.statement);
|
|
580
663
|
this.current.loops.pop();
|
|
581
664
|
if (!this.current.terminated) this.terminate(`br label %${updateLabel}`);
|
|
@@ -602,11 +685,174 @@ class Generator {
|
|
|
602
685
|
this.emitAssignmentTarget(initializer as Expression, value);
|
|
603
686
|
}
|
|
604
687
|
|
|
688
|
+
/**
|
|
689
|
+
* JavaScript `switch`: test each `case` with strict equality, then run the
|
|
690
|
+
* matched clause and fall through into the following clauses until `break`.
|
|
691
|
+
*/
|
|
692
|
+
private emitSwitch(statement: SwitchStatement): void {
|
|
693
|
+
const discriminant = this.emitExpression(statement.expression);
|
|
694
|
+
const endLabel = this.label("switch.end");
|
|
695
|
+
const clauses = statement.clauses;
|
|
696
|
+
const labels = clauses.map(() => this.label("switch.case"));
|
|
697
|
+
const testLabels = clauses.map((clause) =>
|
|
698
|
+
clause.kind === SyntaxKind.CaseClause ? this.label("switch.test") : undefined,
|
|
699
|
+
);
|
|
700
|
+
const defaultIndex = clauses.findIndex((clause) => clause.kind === SyntaxKind.DefaultClause);
|
|
701
|
+
const defaultLabel = defaultIndex >= 0 ? labels[defaultIndex]! : endLabel;
|
|
702
|
+
const caseIndexes = clauses
|
|
703
|
+
.map((clause, index) => ({ clause, index }))
|
|
704
|
+
.filter((entry) => entry.clause.kind === SyntaxKind.CaseClause);
|
|
705
|
+
|
|
706
|
+
this.terminate(`br label %${caseIndexes.length > 0 ? testLabels[caseIndexes[0]!.index]! : defaultLabel}`);
|
|
707
|
+
for (let test = 0; test < caseIndexes.length; test++) {
|
|
708
|
+
const entry = caseIndexes[test]!;
|
|
709
|
+
this.startBlock(testLabels[entry.index]!);
|
|
710
|
+
const caseValue = this.emitExpression((entry.clause as CaseClause).expression);
|
|
711
|
+
const equals = this.reg();
|
|
712
|
+
this.emit(` ${equals} = call i64 @xt_seq(i64 ${discriminant}, i64 ${caseValue})`);
|
|
713
|
+
const truthy = this.reg();
|
|
714
|
+
this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${equals})`);
|
|
715
|
+
const condition = this.reg();
|
|
716
|
+
this.emit(` ${condition} = icmp ne i32 ${truthy}, 0`);
|
|
717
|
+
const next = test + 1 < caseIndexes.length ? testLabels[caseIndexes[test + 1]!.index]! : defaultLabel;
|
|
718
|
+
this.terminate(`br i1 ${condition}, label %${labels[entry.index]}, label %${next}`);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
for (let index = 0; index < clauses.length; index++) {
|
|
722
|
+
this.startBlock(labels[index]!);
|
|
723
|
+
const enclosing = this.current.loops[this.current.loops.length - 1];
|
|
724
|
+
this.current.loops.push({ breakLabel: endLabel, continueLabel: enclosing?.continueLabel ?? endLabel, tryDepth: this.current.tryFrames.length });
|
|
725
|
+
for (const child of clauses[index]!.statements) {
|
|
726
|
+
if (this.current.terminated) break;
|
|
727
|
+
this.emitStatement(child);
|
|
728
|
+
}
|
|
729
|
+
this.current.loops.pop();
|
|
730
|
+
if (!this.current.terminated) {
|
|
731
|
+
const next = index + 1 < clauses.length ? labels[index + 1]! : endLabel;
|
|
732
|
+
this.terminate(`br label %${next}`);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
this.startBlock(endLabel);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* `try`/`catch`/`finally` via a runtime setjmp frame. The runtime keeps a
|
|
740
|
+
* stack of frames; `xt_throw` longjmps into the innermost one. A `return`
|
|
741
|
+
* that exits the protected region skips `finally` (a known limitation).
|
|
742
|
+
*/
|
|
743
|
+
private emitTry(statement: TryStatement): void {
|
|
744
|
+
this.current.usesTry = true;
|
|
745
|
+
const frameSlot = this.alloca();
|
|
746
|
+
const exceptionSlot = this.alloca();
|
|
747
|
+
const flagSlot = this.alloca();
|
|
748
|
+
const tryLabel = this.label("try.body");
|
|
749
|
+
const catchLabel = this.label("try.catch");
|
|
750
|
+
const exceptionLabel = this.label("try.exception");
|
|
751
|
+
const rethrowLabel = this.label("try.rethrow");
|
|
752
|
+
const finallyLabel = statement.finallyBlock ? this.label("try.finally") : undefined;
|
|
753
|
+
const endLabel = this.label("try.end");
|
|
754
|
+
const hasCatch = !!statement.catchClause;
|
|
755
|
+
|
|
756
|
+
const frame = this.reg();
|
|
757
|
+
this.emit(` ${frame} = call i8* @xt_try_enter()`);
|
|
758
|
+
this.emit(` store i8* ${frame}, i8** ${frameSlot}`);
|
|
759
|
+
this.emit(` store i64 0, i64* ${flagSlot}`);
|
|
760
|
+
const jump = this.reg();
|
|
761
|
+
this.emit(` ${jump} = call i32 @_setjmp(i8* ${frame})`);
|
|
762
|
+
const isThrow = this.reg();
|
|
763
|
+
this.emit(` ${isThrow} = icmp ne i32 ${jump}, 0`);
|
|
764
|
+
const exceptionTarget = hasCatch ? catchLabel : exceptionLabel;
|
|
765
|
+
this.terminate(`br i1 ${isThrow}, label %${exceptionTarget}, label %${tryLabel}`);
|
|
766
|
+
|
|
767
|
+
// Normal completion of the try block.
|
|
768
|
+
this.startBlock(tryLabel);
|
|
769
|
+
this.current.tryFrames.push(frameSlot);
|
|
770
|
+
this.emitStatements(statement.tryBlock.statements);
|
|
771
|
+
this.current.tryFrames.pop();
|
|
772
|
+
if (!this.current.terminated) {
|
|
773
|
+
const currentFrame = this.reg();
|
|
774
|
+
this.emit(` ${currentFrame} = load i8*, i8** ${frameSlot}`);
|
|
775
|
+
this.emit(` call void @xt_try_leave(i8* ${currentFrame})`);
|
|
776
|
+
this.terminate(`br label %${finallyLabel ?? endLabel}`);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
if (hasCatch) {
|
|
780
|
+
this.startBlock(catchLabel);
|
|
781
|
+
const currentFrame = this.reg();
|
|
782
|
+
this.emit(` ${currentFrame} = load i8*, i8** ${frameSlot}`);
|
|
783
|
+
const exception = this.reg();
|
|
784
|
+
this.emit(` ${exception} = call i64 @xt_try_exception(i8* ${currentFrame})`);
|
|
785
|
+
this.emit(` call void @xt_try_leave(i8* ${currentFrame})`);
|
|
786
|
+
this.bindCatchVariable(statement.catchClause!.variable, exception);
|
|
787
|
+
this.emitStatements(statement.catchClause!.block.statements);
|
|
788
|
+
if (!this.current.terminated) this.terminate(`br label %${finallyLabel ?? endLabel}`);
|
|
789
|
+
} else {
|
|
790
|
+
// Without a catch clause the exception is remembered, then rethrown
|
|
791
|
+
// after `finally` runs.
|
|
792
|
+
this.startBlock(exceptionLabel);
|
|
793
|
+
const currentFrame = this.reg();
|
|
794
|
+
this.emit(` ${currentFrame} = load i8*, i8** ${frameSlot}`);
|
|
795
|
+
const exception = this.reg();
|
|
796
|
+
this.emit(` ${exception} = call i64 @xt_try_exception(i8* ${currentFrame})`);
|
|
797
|
+
this.emit(` call void @xt_try_leave(i8* ${currentFrame})`);
|
|
798
|
+
this.emit(` store i64 ${exception}, i64* ${exceptionSlot}`);
|
|
799
|
+
this.emit(` store i64 1, i64* ${flagSlot}`);
|
|
800
|
+
this.terminate(`br label %${finallyLabel ?? rethrowLabel}`);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
if (finallyLabel) {
|
|
804
|
+
this.startBlock(finallyLabel);
|
|
805
|
+
this.emitStatements(statement.finallyBlock!.statements);
|
|
806
|
+
if (!this.current.terminated) {
|
|
807
|
+
if (hasCatch) {
|
|
808
|
+
this.terminate(`br label %${endLabel}`);
|
|
809
|
+
} else {
|
|
810
|
+
const flag = this.reg();
|
|
811
|
+
this.emit(` ${flag} = load i64, i64* ${flagSlot}`);
|
|
812
|
+
const truthy = this.reg();
|
|
813
|
+
this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${flag})`);
|
|
814
|
+
const shouldRethrow = this.reg();
|
|
815
|
+
this.emit(` ${shouldRethrow} = icmp ne i32 ${truthy}, 0`);
|
|
816
|
+
this.terminate(`br i1 ${shouldRethrow}, label %${rethrowLabel}, label %${endLabel}`);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
if (!hasCatch) {
|
|
822
|
+
this.startBlock(rethrowLabel);
|
|
823
|
+
const exception = this.reg();
|
|
824
|
+
this.emit(` ${exception} = load i64, i64* ${exceptionSlot}`);
|
|
825
|
+
this.emit(` call void @xt_throw(i64 ${exception})`);
|
|
826
|
+
this.terminate("unreachable");
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
this.startBlock(endLabel);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
private bindCatchVariable(variable: Identifier | undefined, value: string): void {
|
|
833
|
+
if (!variable) return;
|
|
834
|
+
const symbol = this.binding.symbolOfDeclaration.get(variable);
|
|
835
|
+
if (!symbol) return;
|
|
836
|
+
if (this.current.slots.has(symbol.id)) this.writeSlot(symbol, value);
|
|
837
|
+
else this.declareSlot(symbol, value);
|
|
838
|
+
}
|
|
839
|
+
|
|
605
840
|
private emitReturn(statement: ReturnStatement): void {
|
|
606
841
|
const value = statement.expression ? this.emitExpression(statement.expression) : i64(XT_UNDEFINED);
|
|
842
|
+
this.popTryFramesTo(0);
|
|
607
843
|
this.terminate(`ret i64 ${value}`);
|
|
608
844
|
}
|
|
609
845
|
|
|
846
|
+
/** Emit `xt_try_leave` for every active frame above `depth` (innermost first). */
|
|
847
|
+
private popTryFramesTo(depth: number): void {
|
|
848
|
+
for (let index = this.current.tryFrames.length - 1; index >= depth; index--) {
|
|
849
|
+
const slot = this.current.tryFrames[index]!;
|
|
850
|
+
const frame = this.reg();
|
|
851
|
+
this.emit(` ${frame} = load i8*, i8** ${slot}`);
|
|
852
|
+
this.emit(` call void @xt_try_leave(i8* ${frame})`);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
610
856
|
// -- expressions ---------------------------------------------------------
|
|
611
857
|
|
|
612
858
|
private emitExpression(node: Expression): string {
|
|
@@ -639,6 +885,8 @@ class Generator {
|
|
|
639
885
|
case SyntaxKind.SatisfiesExpression:
|
|
640
886
|
case SyntaxKind.NonNullExpression:
|
|
641
887
|
return this.emitExpression((node as { expression: Expression }).expression);
|
|
888
|
+
case SyntaxKind.DeleteExpression:
|
|
889
|
+
return this.emitDelete(node as DeleteExpression);
|
|
642
890
|
case SyntaxKind.BinaryExpression:
|
|
643
891
|
return this.emitBinaryOrAssignment(node as BinaryExpression);
|
|
644
892
|
case SyntaxKind.PrefixUnaryExpression:
|
|
@@ -683,6 +931,15 @@ class Generator {
|
|
|
683
931
|
return numberLiteral(Infinity);
|
|
684
932
|
case "console":
|
|
685
933
|
return i64(XT_UNDEFINED);
|
|
934
|
+
case "arguments": {
|
|
935
|
+
const argc = this.reg();
|
|
936
|
+
this.emit(` ${argc} = load i32, i32* %saved.argc`);
|
|
937
|
+
const argv = this.reg();
|
|
938
|
+
this.emit(` ${argv} = load i64*, i64** %saved.argv`);
|
|
939
|
+
const rest = this.reg();
|
|
940
|
+
this.emit(` ${rest} = call i64 @xt_rest_args(i32 ${argc}, i64* ${argv}, i32 0)`);
|
|
941
|
+
return rest;
|
|
942
|
+
}
|
|
686
943
|
default:
|
|
687
944
|
this.diagnostics.error(
|
|
688
945
|
DiagnosticCode.CannotFindName,
|
|
@@ -980,6 +1237,37 @@ class Generator {
|
|
|
980
1237
|
|
|
981
1238
|
private emitCall(node: CallExpression): string {
|
|
982
1239
|
const callee = node.expression;
|
|
1240
|
+
if (node.optional) {
|
|
1241
|
+
const calleeValue = this.emitExpression(callee);
|
|
1242
|
+
return this.emitOptional(calleeValue, () => {
|
|
1243
|
+
const args = this.emitArguments(node.arguments);
|
|
1244
|
+
const result = this.reg();
|
|
1245
|
+
this.emit(` ${result} = call i64 @xt_closure_call(i64 ${calleeValue}, i32 ${args.argc}, i64* ${args.ptr})`);
|
|
1246
|
+
return result;
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
if (
|
|
1250
|
+
(callee.kind === SyntaxKind.PropertyAccessExpression || callee.kind === SyntaxKind.ElementAccessExpression) &&
|
|
1251
|
+
(callee as PropertyAccessExpression | ElementAccessExpression).optional
|
|
1252
|
+
) {
|
|
1253
|
+
// `obj?.method(args)` / `obj?.[key](args)`: guard the receiver, then
|
|
1254
|
+
// dispatch through the runtime, which understands object methods as well
|
|
1255
|
+
// as the built-in array/string methods.
|
|
1256
|
+
const access = callee as PropertyAccessExpression | ElementAccessExpression;
|
|
1257
|
+
const object = this.emitExpression(access.expression);
|
|
1258
|
+
return this.emitOptional(object, () => {
|
|
1259
|
+
const name =
|
|
1260
|
+
access.kind === SyntaxKind.PropertyAccessExpression
|
|
1261
|
+
? this.stringValue((access as PropertyAccessExpression).name.text)
|
|
1262
|
+
: this.emitExpression((access as ElementAccessExpression).argumentExpression);
|
|
1263
|
+
const args = this.emitArguments(node.arguments);
|
|
1264
|
+
const result = this.reg();
|
|
1265
|
+
this.emit(
|
|
1266
|
+
` ${result} = call i64 @xt_call_method(i64 ${object}, i64 ${name}, i32 ${args.argc}, i64* ${args.ptr})`,
|
|
1267
|
+
);
|
|
1268
|
+
return result;
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
983
1271
|
if (callee.kind === SyntaxKind.PropertyAccessExpression) {
|
|
984
1272
|
const special = this.tryEmitBuiltinCall(node, callee as PropertyAccessExpression);
|
|
985
1273
|
if (special) return special;
|
|
@@ -998,6 +1286,13 @@ class Generator {
|
|
|
998
1286
|
return result;
|
|
999
1287
|
}
|
|
1000
1288
|
}
|
|
1289
|
+
const globalFunction = !symbol ? GLOBAL_FUNCTIONS[(callee as Identifier).text] : undefined;
|
|
1290
|
+
if (globalFunction) {
|
|
1291
|
+
const args = this.emitArguments(node.arguments);
|
|
1292
|
+
const result = this.reg();
|
|
1293
|
+
this.emit(` ${result} = call i64 @${globalFunction}(i32 ${args.argc}, i64* ${args.ptr})`);
|
|
1294
|
+
return result;
|
|
1295
|
+
}
|
|
1001
1296
|
const builtin = this.builtins[(callee as Identifier).text];
|
|
1002
1297
|
if (!symbol && builtin) {
|
|
1003
1298
|
this.extraDeclarations.add(
|
|
@@ -1023,31 +1318,52 @@ class Generator {
|
|
|
1023
1318
|
private tryEmitBuiltinCall(node: CallExpression, callee: PropertyAccessExpression): string | undefined {
|
|
1024
1319
|
const target = callee.expression;
|
|
1025
1320
|
const method = callee.name.text;
|
|
1026
|
-
|
|
1321
|
+
const targetIdentifier = target.kind === SyntaxKind.Identifier ? (target as Identifier) : undefined;
|
|
1322
|
+
const targetSymbol = targetIdentifier ? this.binding.symbolOfIdentifier.get(targetIdentifier) : undefined;
|
|
1323
|
+
|
|
1324
|
+
if (targetIdentifier && targetIdentifier.text === "console" && !targetSymbol) {
|
|
1325
|
+
const consoleFn = CONSOLE_METHODS[method];
|
|
1326
|
+
if (consoleFn) {
|
|
1327
|
+
const args = this.emitArguments(node.arguments);
|
|
1328
|
+
this.emit(` call void @${consoleFn}(i32 ${args.argc}, i64* ${args.ptr})`);
|
|
1329
|
+
return i64(XT_UNDEFINED);
|
|
1330
|
+
}
|
|
1331
|
+
return undefined;
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
if (targetIdentifier && targetIdentifier.text === "Math" && !targetSymbol) {
|
|
1335
|
+
if (!MATH_FUNCTIONS.has(method)) return undefined;
|
|
1336
|
+
const name = this.stringValue(method);
|
|
1027
1337
|
const args = this.emitArguments(node.arguments);
|
|
1028
|
-
|
|
1029
|
-
|
|
1338
|
+
const result = this.reg();
|
|
1339
|
+
this.emit(` ${result} = call i64 @xt_math_call(i64 ${name}, i32 ${args.argc}, i64* ${args.ptr})`);
|
|
1340
|
+
return result;
|
|
1030
1341
|
}
|
|
1031
|
-
|
|
1032
|
-
if (
|
|
1033
|
-
if (method === "
|
|
1034
|
-
const
|
|
1342
|
+
|
|
1343
|
+
if (targetIdentifier && targetIdentifier.text === "Object" && !targetSymbol) {
|
|
1344
|
+
if (method === "keys" || method === "values" || method === "entries") {
|
|
1345
|
+
const argument = node.arguments.length > 0 ? this.emitExpression(node.arguments[0]!) : i64(XT_UNDEFINED);
|
|
1346
|
+
const fn = method === "keys" ? "xt_object_keys" : method === "values" ? "xt_object_values" : "xt_object_entries";
|
|
1347
|
+
return this.runtimeCall(fn, [argument]);
|
|
1348
|
+
}
|
|
1349
|
+
if (method === "assign") {
|
|
1035
1350
|
const args = this.emitArguments(node.arguments);
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
const ptr = this.reg();
|
|
1040
|
-
this.emit(` ${ptr} = getelementptr i64, i64* ${args.ptr}, i32 ${index}`);
|
|
1041
|
-
const element = this.reg();
|
|
1042
|
-
this.emit(` ${element} = load i64, i64* ${ptr}`);
|
|
1043
|
-
const previous = last;
|
|
1044
|
-
last = this.reg();
|
|
1045
|
-
this.emit(` ${last} = call i64 @xt_array_push(i64 ${previous}, i64 ${element})`);
|
|
1046
|
-
}
|
|
1047
|
-
return last;
|
|
1351
|
+
const result = this.reg();
|
|
1352
|
+
this.emit(` ${result} = call i64 @xt_object_assign(i32 ${args.argc}, i64* ${args.ptr})`);
|
|
1353
|
+
return result;
|
|
1048
1354
|
}
|
|
1049
|
-
|
|
1050
|
-
|
|
1355
|
+
return undefined;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
if (BUILTIN_METHODS.has(method)) {
|
|
1359
|
+
const object = this.emitExpression(target);
|
|
1360
|
+
const name = this.stringValue(method);
|
|
1361
|
+
const args = this.emitArguments(node.arguments);
|
|
1362
|
+
const result = this.reg();
|
|
1363
|
+
this.emit(
|
|
1364
|
+
` ${result} = call i64 @xt_call_method(i64 ${object}, i64 ${name}, i32 ${args.argc}, i64* ${args.ptr})`,
|
|
1365
|
+
);
|
|
1366
|
+
return result;
|
|
1051
1367
|
}
|
|
1052
1368
|
return undefined;
|
|
1053
1369
|
}
|
|
@@ -1071,26 +1387,82 @@ class Generator {
|
|
|
1071
1387
|
return { argc: args.length, ptr };
|
|
1072
1388
|
}
|
|
1073
1389
|
|
|
1390
|
+
private emitDelete(node: DeleteExpression): string {
|
|
1391
|
+
const target = node.expression;
|
|
1392
|
+
if (target.kind === SyntaxKind.PropertyAccessExpression) {
|
|
1393
|
+
const access = target as PropertyAccessExpression;
|
|
1394
|
+
const object = this.emitExpression(access.expression);
|
|
1395
|
+
const key = this.stringValue(access.name.text);
|
|
1396
|
+
return this.runtimeCall("xt_delete", [object, key]);
|
|
1397
|
+
}
|
|
1398
|
+
if (target.kind === SyntaxKind.ElementAccessExpression) {
|
|
1399
|
+
const access = target as ElementAccessExpression;
|
|
1400
|
+
const object = this.emitExpression(access.expression);
|
|
1401
|
+
const key = this.emitExpression(access.argumentExpression);
|
|
1402
|
+
return this.runtimeCall("xt_delete", [object, key]);
|
|
1403
|
+
}
|
|
1404
|
+
return i64(XT_TRUE);
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1074
1407
|
private emitPropertyAccess(node: PropertyAccessExpression): string {
|
|
1075
|
-
if (
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1408
|
+
if (
|
|
1409
|
+
node.name.text in MATH_CONSTANTS &&
|
|
1410
|
+
node.expression.kind === SyntaxKind.Identifier &&
|
|
1411
|
+
(node.expression as Identifier).text === "Math" &&
|
|
1412
|
+
!this.binding.symbolOfIdentifier.get(node.expression as Identifier)
|
|
1413
|
+
) {
|
|
1414
|
+
return numberLiteral(MATH_CONSTANTS[node.name.text]!);
|
|
1080
1415
|
}
|
|
1081
1416
|
const object = this.emitExpression(node.expression);
|
|
1082
|
-
const
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1417
|
+
const access = (): string => {
|
|
1418
|
+
if (node.name.text === "length") {
|
|
1419
|
+
const result = this.reg();
|
|
1420
|
+
this.emit(` ${result} = call i64 @xt_array_length(i64 ${object})`);
|
|
1421
|
+
return result;
|
|
1422
|
+
}
|
|
1423
|
+
const key = this.stringValue(node.name.text);
|
|
1424
|
+
const result = this.reg();
|
|
1425
|
+
this.emit(` ${result} = call i64 @xt_get(i64 ${object}, i64 ${key})`);
|
|
1426
|
+
return result;
|
|
1427
|
+
};
|
|
1428
|
+
return node.optional ? this.emitOptional(object, access) : access();
|
|
1086
1429
|
}
|
|
1087
1430
|
|
|
1088
1431
|
private emitElementAccess(node: ElementAccessExpression): string {
|
|
1089
1432
|
const object = this.emitExpression(node.expression);
|
|
1090
|
-
const
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1433
|
+
const access = (): string => {
|
|
1434
|
+
const key = this.emitExpression(node.argumentExpression);
|
|
1435
|
+
const result = this.reg();
|
|
1436
|
+
this.emit(` ${result} = call i64 @xt_get(i64 ${object}, i64 ${key})`);
|
|
1437
|
+
return result;
|
|
1438
|
+
};
|
|
1439
|
+
return node.optional ? this.emitOptional(object, access) : access();
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
/**
|
|
1443
|
+
* Evaluate `object` once and, when it is neither `null` nor `undefined`, run
|
|
1444
|
+
* `compute` to produce the value; otherwise short-circuit to `undefined`.
|
|
1445
|
+
*/
|
|
1446
|
+
private emitOptional(objectValue: string, compute: () => string): string {
|
|
1447
|
+
const result = this.alloca();
|
|
1448
|
+
this.emit(` store i64 ${i64(XT_UNDEFINED)}, i64* ${result}`);
|
|
1449
|
+
const nullish = this.reg();
|
|
1450
|
+
this.emit(` ${nullish} = call i64 @xt_is_nullish(i64 ${objectValue})`);
|
|
1451
|
+
const truthy = this.reg();
|
|
1452
|
+
this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${nullish})`);
|
|
1453
|
+
const condition = this.reg();
|
|
1454
|
+
this.emit(` ${condition} = icmp ne i32 ${truthy}, 0`);
|
|
1455
|
+
const someLabel = this.label("opt.some");
|
|
1456
|
+
const endLabel = this.label("opt.end");
|
|
1457
|
+
this.terminate(`br i1 ${condition}, label %${endLabel}, label %${someLabel}`);
|
|
1458
|
+
this.startBlock(someLabel);
|
|
1459
|
+
const value = compute();
|
|
1460
|
+
this.emit(` store i64 ${value}, i64* ${result}`);
|
|
1461
|
+
this.terminate(`br label %${endLabel}`);
|
|
1462
|
+
this.startBlock(endLabel);
|
|
1463
|
+
const merged = this.reg();
|
|
1464
|
+
this.emit(` ${merged} = load i64, i64* ${result}`);
|
|
1465
|
+
return merged;
|
|
1094
1466
|
}
|
|
1095
1467
|
|
|
1096
1468
|
private emitArrayLiteral(node: ArrayLiteralExpression): string {
|
|
@@ -1128,6 +1500,9 @@ class Generator {
|
|
|
1128
1500
|
const key = this.stringValue(identifier.text);
|
|
1129
1501
|
const value = this.emitIdentifier(identifier);
|
|
1130
1502
|
this.emit(` call i64 @xt_set(i64 ${object}, i64 ${key}, i64 ${value})`);
|
|
1503
|
+
} else if (property.kind === SyntaxKind.SpreadElement) {
|
|
1504
|
+
const spread = this.emitExpression((property as { expression: Expression }).expression);
|
|
1505
|
+
this.emit(` call i64 @xt_object_spread(i64 ${object}, i64 ${spread})`);
|
|
1131
1506
|
} else {
|
|
1132
1507
|
this.unsupported(property, "object spread");
|
|
1133
1508
|
}
|
|
@@ -1223,8 +1598,48 @@ const BINARY_RUNTIME: Record<string, string | undefined> = {
|
|
|
1223
1598
|
[BinaryOperator.LessThanLessThan]: "xt_shl",
|
|
1224
1599
|
[BinaryOperator.GreaterThanGreaterThan]: "xt_shr",
|
|
1225
1600
|
[BinaryOperator.GreaterThanGreaterThanGreaterThan]: "xt_ushr",
|
|
1601
|
+
[BinaryOperator.In]: "xt_in",
|
|
1602
|
+
};
|
|
1603
|
+
|
|
1604
|
+
const CONSOLE_METHODS: Record<string, string> = {
|
|
1605
|
+
log: "xt_console_log",
|
|
1606
|
+
info: "xt_console_info",
|
|
1607
|
+
warn: "xt_console_warn",
|
|
1608
|
+
error: "xt_console_error",
|
|
1609
|
+
};
|
|
1610
|
+
|
|
1611
|
+
const MATH_FUNCTIONS = new Set<string>([
|
|
1612
|
+
"abs", "floor", "ceil", "round", "trunc", "sqrt", "cbrt", "pow", "exp", "log", "log2", "log10",
|
|
1613
|
+
"sin", "cos", "tan", "asin", "acos", "atan", "atan2", "hypot", "sign", "random", "min", "max",
|
|
1614
|
+
]);
|
|
1615
|
+
|
|
1616
|
+
const MATH_CONSTANTS: Record<string, number> = {
|
|
1617
|
+
PI: Math.PI,
|
|
1618
|
+
E: Math.E,
|
|
1619
|
+
LN2: Math.LN2,
|
|
1620
|
+
LN10: Math.LN10,
|
|
1621
|
+
LOG2E: Math.LOG2E,
|
|
1622
|
+
LOG10E: Math.LOG10E,
|
|
1623
|
+
SQRT2: Math.SQRT2,
|
|
1624
|
+
SQRT1_2: Math.SQRT1_2,
|
|
1226
1625
|
};
|
|
1227
1626
|
|
|
1627
|
+
const GLOBAL_FUNCTIONS: Record<string, string> = {
|
|
1628
|
+
parseInt: "xt_parse_int",
|
|
1629
|
+
parseFloat: "xt_parse_float",
|
|
1630
|
+
isNaN: "xt_is_nan",
|
|
1631
|
+
isFinite: "xt_is_finite",
|
|
1632
|
+
Number: "xt_number_ctor",
|
|
1633
|
+
String: "xt_string_ctor",
|
|
1634
|
+
Boolean: "xt_boolean_ctor",
|
|
1635
|
+
};
|
|
1636
|
+
|
|
1637
|
+
const BUILTIN_METHODS = new Set<string>([
|
|
1638
|
+
"push", "pop", "shift", "unshift", "join", "slice", "indexOf", "includes", "map", "forEach", "filter",
|
|
1639
|
+
"reduce", "concat", "reverse", "charAt", "charCodeAt", "substring", "substr", "split", "toUpperCase",
|
|
1640
|
+
"toLowerCase", "trim", "replace", "repeat", "startsWith", "endsWith",
|
|
1641
|
+
]);
|
|
1642
|
+
|
|
1228
1643
|
const ASSIGNMENT_OPERATORS = new Set<string>([
|
|
1229
1644
|
"=",
|
|
1230
1645
|
"+=",
|