xbintsc 0.1.3 → 0.1.8
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 +14 -13
- package/dist/src/ast/common.d.ts +33 -0
- package/dist/src/ast/common.js +7 -0
- package/dist/src/ast/common.js.map +1 -0
- package/dist/src/ast/declarations.d.ts +110 -0
- package/dist/src/ast/declarations.js +5 -0
- package/dist/src/ast/declarations.js.map +1 -0
- package/dist/src/ast/expressions.d.ts +219 -0
- package/dist/src/ast/expressions.js +6 -0
- package/dist/src/ast/expressions.js.map +1 -0
- package/dist/src/ast/kinds.d.ts +160 -0
- package/dist/src/ast/kinds.js +171 -0
- package/dist/src/ast/kinds.js.map +1 -0
- package/dist/src/ast/modules.d.ts +61 -0
- package/dist/src/ast/modules.js +5 -0
- package/dist/src/ast/modules.js.map +1 -0
- package/dist/src/ast/nodes.d.ts +11 -839
- package/dist/src/ast/nodes.js +11 -237
- package/dist/src/ast/nodes.js.map +1 -1
- package/dist/src/ast/operators.d.ts +71 -0
- package/dist/src/ast/operators.js +77 -0
- package/dist/src/ast/operators.js.map +1 -0
- package/dist/src/ast/statements.d.ts +120 -0
- package/dist/src/ast/statements.js +5 -0
- package/dist/src/ast/statements.js.map +1 -0
- package/dist/src/ast/types.d.ts +123 -0
- package/dist/src/ast/types.js +6 -0
- package/dist/src/ast/types.js.map +1 -0
- package/dist/src/codegen/generator/calls.d.ts +22 -0
- package/dist/src/codegen/generator/calls.js +301 -0
- package/dist/src/codegen/generator/calls.js.map +1 -0
- package/dist/src/codegen/generator/context.d.ts +45 -0
- package/dist/src/codegen/generator/context.js +128 -0
- package/dist/src/codegen/generator/context.js.map +1 -0
- package/dist/src/codegen/generator/expressions.d.ts +22 -0
- package/dist/src/codegen/generator/expressions.js +383 -0
- package/dist/src/codegen/generator/expressions.js.map +1 -0
- package/dist/src/codegen/generator/generator.d.ts +22 -0
- package/dist/src/codegen/generator/generator.js +21 -0
- package/dist/src/codegen/generator/generator.js.map +1 -0
- package/dist/src/codegen/generator/module.d.ts +15 -0
- package/dist/src/codegen/generator/module.js +114 -0
- package/dist/src/codegen/generator/module.js.map +1 -0
- package/dist/src/codegen/generator/state.d.ts +40 -0
- package/dist/src/codegen/generator/state.js +84 -0
- package/dist/src/codegen/generator/state.js.map +1 -0
- package/dist/src/codegen/generator/statements.d.ts +22 -0
- package/dist/src/codegen/generator/statements.js +425 -0
- package/dist/src/codegen/generator/statements.js.map +1 -0
- package/dist/src/codegen/generator/tables.d.ts +19 -0
- package/dist/src/codegen/generator/tables.js +153 -0
- package/dist/src/codegen/generator/tables.js.map +1 -0
- package/dist/src/codegen/llvm.d.ts +4 -13
- package/dist/src/codegen/llvm.js +4 -1159
- package/dist/src/codegen/llvm.js.map +1 -1
- package/dist/src/driver/compiler.js +13 -4
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/lexer/char.d.ts +78 -0
- package/dist/src/lexer/char.js +106 -0
- package/dist/src/lexer/char.js.map +1 -0
- package/dist/src/lexer/scanner.js +1 -98
- package/dist/src/lexer/scanner.js.map +1 -1
- package/dist/src/parser/context.d.ts +58 -0
- package/dist/src/parser/context.js +174 -0
- package/dist/src/parser/context.js.map +1 -0
- package/dist/src/parser/declarations.d.ts +26 -0
- package/dist/src/parser/declarations.js +461 -0
- package/dist/src/parser/declarations.js.map +1 -0
- package/dist/src/parser/expressions.d.ts +27 -0
- package/dist/src/parser/expressions.js +437 -0
- package/dist/src/parser/expressions.js.map +1 -0
- package/dist/src/parser/helpers.d.ts +7 -0
- package/dist/src/parser/helpers.js +36 -0
- package/dist/src/parser/helpers.js.map +1 -0
- package/dist/src/parser/literals.d.ts +16 -0
- package/dist/src/parser/literals.js +193 -0
- package/dist/src/parser/literals.js.map +1 -0
- package/dist/src/parser/modules.d.ts +12 -0
- package/dist/src/parser/modules.js +175 -0
- package/dist/src/parser/modules.js.map +1 -0
- package/dist/src/parser/operators.d.ts +12 -0
- package/dist/src/parser/operators.js +172 -0
- package/dist/src/parser/operators.js.map +1 -0
- package/dist/src/parser/parser.d.ts +17 -122
- package/dist/src/parser/parser.js +18 -2307
- package/dist/src/parser/parser.js.map +1 -1
- package/dist/src/parser/speculation.d.ts +6 -0
- package/dist/src/parser/speculation.js +10 -0
- package/dist/src/parser/speculation.js.map +1 -0
- package/dist/src/parser/statements.d.ts +28 -0
- package/dist/src/parser/statements.js +402 -0
- package/dist/src/parser/statements.js.map +1 -0
- package/dist/src/parser/types.d.ts +21 -0
- package/dist/src/parser/types.js +338 -0
- package/dist/src/parser/types.js.map +1 -0
- 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/rt_internal.h +106 -0
- package/runtime/xt_alloc.c +38 -0
- package/runtime/xt_builtins.c +192 -0
- package/runtime/xt_containers.c +225 -0
- package/runtime/xt_io.c +133 -0
- package/runtime/xt_stdlib.c +502 -0
- package/runtime/xt_values.c +291 -0
- package/scripts/build-runtime.mjs +8 -1
- package/src/ast/common.ts +210 -0
- package/src/ast/declarations.ts +130 -0
- package/src/ast/expressions.ts +275 -0
- package/src/ast/kinds.ts +176 -0
- package/src/ast/modules.ts +73 -0
- package/src/ast/nodes.ts +11 -1101
- package/src/ast/operators.ts +76 -0
- package/src/ast/statements.ts +148 -0
- package/src/ast/types.ts +173 -0
- package/src/codegen/generator/calls.ts +355 -0
- package/src/codegen/generator/context.ts +156 -0
- package/src/codegen/generator/expressions.ts +431 -0
- package/src/codegen/generator/generator.ts +35 -0
- package/src/codegen/generator/module.ts +139 -0
- package/src/codegen/generator/state.ts +126 -0
- package/src/codegen/generator/statements.ts +474 -0
- package/src/codegen/generator/tables.ts +171 -0
- package/src/codegen/llvm.ts +4 -1305
- package/src/driver/compiler.ts +17 -5
- package/src/lexer/char.ts +116 -0
- package/src/lexer/scanner.ts +9 -109
- package/src/parser/context.ts +196 -0
- package/src/parser/declarations.ts +502 -0
- package/src/parser/expressions.ts +465 -0
- package/src/parser/helpers.ts +42 -0
- package/src/parser/literals.ts +207 -0
- package/src/parser/modules.ts +194 -0
- package/src/parser/operators.ts +181 -0
- package/src/parser/parser.ts +35 -2369
- package/src/parser/speculation.ts +9 -0
- package/src/parser/statements.ts +457 -0
- package/src/parser/types.ts +365 -0
- package/runtime/xt_runtime.c +0 -682
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared state types and the runtime declaration list for the LLVM generator.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { FunctionInfo } from "../../binder/binder.js";
|
|
6
|
+
import type { BindResult } from "../../binder/binder.js";
|
|
7
|
+
import type { BuiltinFunction } from "../../extensions/registry.js";
|
|
8
|
+
|
|
9
|
+
export interface Slot {
|
|
10
|
+
/** Register holding the `i64*` to the storage. */
|
|
11
|
+
readonly ptr: string;
|
|
12
|
+
readonly boxed: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface LoopLabels {
|
|
16
|
+
readonly breakLabel: string;
|
|
17
|
+
readonly continueLabel: string;
|
|
18
|
+
/** Number of enclosing `try` frames when the loop was entered. */
|
|
19
|
+
readonly tryDepth?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface FunctionState {
|
|
23
|
+
readonly fn: FunctionInfo;
|
|
24
|
+
readonly buffer: string[];
|
|
25
|
+
readonly allocas: string[];
|
|
26
|
+
readonly slots: Map<number, Slot>;
|
|
27
|
+
reg: number;
|
|
28
|
+
label: number;
|
|
29
|
+
terminated: boolean;
|
|
30
|
+
readonly loops: LoopLabels[];
|
|
31
|
+
readonly tryFrames: string[];
|
|
32
|
+
readonly escapePointers: string[];
|
|
33
|
+
/** Set when the function contains a `try`, forcing locals to live in memory. */
|
|
34
|
+
usesTry: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface CodegenResult {
|
|
38
|
+
readonly ir: string;
|
|
39
|
+
readonly binding: BindResult;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface CodegenOptions {
|
|
43
|
+
/** Global names supplied by registered extensions. */
|
|
44
|
+
readonly builtins?: Readonly<Record<string, BuiltinFunction>>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const RUNTIME_DECLARATIONS: readonly string[] = [
|
|
48
|
+
"declare i64 @xt_undefined()",
|
|
49
|
+
"declare i64 @xt_null()",
|
|
50
|
+
"declare i64 @xt_bool(i32)",
|
|
51
|
+
"declare i64 @xt_number(double)",
|
|
52
|
+
"declare i64 @xt_string_new(i8*, i64)",
|
|
53
|
+
"declare i64 @xt_string_from_cstr(i8*)",
|
|
54
|
+
"declare i64 @xt_arg(i32, i64*, i32)",
|
|
55
|
+
"declare i64 @xt_closure_new(i8*, i32, i64*)",
|
|
56
|
+
"declare i64 @xt_closure_call(i64, i32, i64*)",
|
|
57
|
+
"declare i64 @xt_closure_env(i64, i32)",
|
|
58
|
+
"declare i32 @xt_truthy(i64)",
|
|
59
|
+
"declare double @xt_to_number(i64)",
|
|
60
|
+
"declare i64 @xt_to_string(i64)",
|
|
61
|
+
"declare i64 @xt_typeof(i64)",
|
|
62
|
+
"declare i64 @xt_add(i64, i64)",
|
|
63
|
+
"declare i64 @xt_sub(i64, i64)",
|
|
64
|
+
"declare i64 @xt_mul(i64, i64)",
|
|
65
|
+
"declare i64 @xt_div(i64, i64)",
|
|
66
|
+
"declare i64 @xt_mod(i64, i64)",
|
|
67
|
+
"declare i64 @xt_pow(i64, i64)",
|
|
68
|
+
"declare i64 @xt_neg(i64)",
|
|
69
|
+
"declare i64 @xt_pos(i64)",
|
|
70
|
+
"declare i64 @xt_bit_and(i64, i64)",
|
|
71
|
+
"declare i64 @xt_bit_or(i64, i64)",
|
|
72
|
+
"declare i64 @xt_bit_xor(i64, i64)",
|
|
73
|
+
"declare i64 @xt_bit_not(i64)",
|
|
74
|
+
"declare i64 @xt_shl(i64, i64)",
|
|
75
|
+
"declare i64 @xt_shr(i64, i64)",
|
|
76
|
+
"declare i64 @xt_ushr(i64, i64)",
|
|
77
|
+
"declare i64 @xt_lt(i64, i64)",
|
|
78
|
+
"declare i64 @xt_le(i64, i64)",
|
|
79
|
+
"declare i64 @xt_gt(i64, i64)",
|
|
80
|
+
"declare i64 @xt_ge(i64, i64)",
|
|
81
|
+
"declare i64 @xt_eq(i64, i64)",
|
|
82
|
+
"declare i64 @xt_ne(i64, i64)",
|
|
83
|
+
"declare i64 @xt_seq(i64, i64)",
|
|
84
|
+
"declare i64 @xt_sne(i64, i64)",
|
|
85
|
+
"declare i64 @xt_not(i64)",
|
|
86
|
+
"declare i64 @xt_object_new()",
|
|
87
|
+
"declare i64 @xt_object_get_cstr(i64, i8*)",
|
|
88
|
+
"declare i64 @xt_object_set(i64, i64, i64)",
|
|
89
|
+
"declare i64 @xt_object_has(i64, i64)",
|
|
90
|
+
"declare i64 @xt_object_keys(i64)",
|
|
91
|
+
"declare i64 @xt_object_values(i64)",
|
|
92
|
+
"declare i64 @xt_object_entries(i64)",
|
|
93
|
+
"declare i64 @xt_object_assign(i32, i64*)",
|
|
94
|
+
"declare i64 @xt_object_spread(i64, i64)",
|
|
95
|
+
"declare i64 @xt_call_method(i64, i64, i32, i64*)",
|
|
96
|
+
"declare i64 @xt_math_call(i64, i32, i64*)",
|
|
97
|
+
"declare i64 @xt_parse_int(i32, i64*)",
|
|
98
|
+
"declare i64 @xt_parse_float(i32, i64*)",
|
|
99
|
+
"declare i64 @xt_is_nan(i32, i64*)",
|
|
100
|
+
"declare i64 @xt_is_finite(i32, i64*)",
|
|
101
|
+
"declare i64 @xt_number_ctor(i32, i64*)",
|
|
102
|
+
"declare i64 @xt_string_ctor(i32, i64*)",
|
|
103
|
+
"declare i64 @xt_boolean_ctor(i32, i64*)",
|
|
104
|
+
"declare i64 @xt_in(i64, i64)",
|
|
105
|
+
"declare i64 @xt_delete(i64, i64)",
|
|
106
|
+
"declare i64 @xt_rest_args(i32, i64*, i32)",
|
|
107
|
+
"declare i64 @xt_array_new(i32, i64*)",
|
|
108
|
+
"declare i64 @xt_array_push(i64, i64)",
|
|
109
|
+
"declare i64 @xt_array_length(i64)",
|
|
110
|
+
"declare i64 @xt_array_spread(i64, i64)",
|
|
111
|
+
"declare i64 @xt_get(i64, i64)",
|
|
112
|
+
"declare i64 @xt_set(i64, i64, i64)",
|
|
113
|
+
"declare i64 @xt_box_new(i64)",
|
|
114
|
+
"declare i64 @xt_box_get(i64)",
|
|
115
|
+
"declare i64 @xt_box_set(i64, i64)",
|
|
116
|
+
"declare i64 @xt_is_nullish(i64)",
|
|
117
|
+
"declare void @xt_throw(i64)",
|
|
118
|
+
"declare i32 @_setjmp(i8*) returns_twice",
|
|
119
|
+
"declare i8* @xt_try_enter()",
|
|
120
|
+
"declare i64 @xt_try_exception(i8*)",
|
|
121
|
+
"declare void @xt_try_leave(i8*)",
|
|
122
|
+
"declare void @xt_console_log(i32, i64*)",
|
|
123
|
+
"declare void @xt_console_info(i32, i64*)",
|
|
124
|
+
"declare void @xt_console_warn(i32, i64*)",
|
|
125
|
+
"declare void @xt_console_error(i32, i64*)",
|
|
126
|
+
];
|
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Statement lowering: control flow, loops, switch and try/catch/finally.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
SyntaxKind,
|
|
7
|
+
type Block,
|
|
8
|
+
type CaseClause,
|
|
9
|
+
type DoStatement,
|
|
10
|
+
type Expression,
|
|
11
|
+
type ForInStatement,
|
|
12
|
+
type ForOfStatement,
|
|
13
|
+
type ForStatement,
|
|
14
|
+
type Identifier,
|
|
15
|
+
type IfStatement,
|
|
16
|
+
type ReturnStatement,
|
|
17
|
+
type Statement,
|
|
18
|
+
type SwitchStatement,
|
|
19
|
+
type ThrowStatement,
|
|
20
|
+
type TryStatement,
|
|
21
|
+
type VariableDeclarationList,
|
|
22
|
+
type VariableStatement,
|
|
23
|
+
type WhileStatement,
|
|
24
|
+
} from "../../ast/nodes.js";
|
|
25
|
+
import { i64, numberLiteral, XT_UNDEFINED } from "../values.js";
|
|
26
|
+
import type { Generator } from "./generator.js";
|
|
27
|
+
|
|
28
|
+
export interface StatementMethods {
|
|
29
|
+
emitStatements(this: Generator, statements: readonly Statement[]): void;
|
|
30
|
+
emitStatement(this: Generator, statement: Statement): void;
|
|
31
|
+
emitVariableStatement(this: Generator, statement: VariableStatement): void;
|
|
32
|
+
emitIf(this: Generator, statement: IfStatement): void;
|
|
33
|
+
emitWhile(this: Generator, statement: WhileStatement): void;
|
|
34
|
+
emitDo(this: Generator, statement: DoStatement): void;
|
|
35
|
+
emitFor(this: Generator, statement: ForStatement): void;
|
|
36
|
+
emitForOf(this: Generator, statement: ForOfStatement | ForInStatement): void;
|
|
37
|
+
bindLoopVariable(this: Generator, initializer: VariableDeclarationList | Expression, value: string): void;
|
|
38
|
+
emitSwitch(this: Generator, statement: SwitchStatement): void;
|
|
39
|
+
emitTry(this: Generator, statement: TryStatement): void;
|
|
40
|
+
bindCatchVariable(this: Generator, variable: Identifier | undefined, value: string): void;
|
|
41
|
+
emitReturn(this: Generator, statement: ReturnStatement): void;
|
|
42
|
+
popTryFramesTo(this: Generator, depth: number): void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const statementMethods: StatementMethods = {
|
|
46
|
+
emitStatements(statements: readonly Statement[]): void {
|
|
47
|
+
for (const statement of statements) {
|
|
48
|
+
if (this.current.terminated) break;
|
|
49
|
+
this.emitStatement(statement);
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
emitStatement(statement: Statement): void {
|
|
54
|
+
switch (statement.kind) {
|
|
55
|
+
case SyntaxKind.ExpressionStatement:
|
|
56
|
+
this.emitExpression((statement as { expression: Expression }).expression);
|
|
57
|
+
return;
|
|
58
|
+
case SyntaxKind.VariableStatement:
|
|
59
|
+
this.emitVariableStatement(statement as VariableStatement);
|
|
60
|
+
return;
|
|
61
|
+
case SyntaxKind.Block:
|
|
62
|
+
this.emitStatements((statement as Block).statements);
|
|
63
|
+
return;
|
|
64
|
+
case SyntaxKind.EmptyStatement:
|
|
65
|
+
case SyntaxKind.DebuggerStatement:
|
|
66
|
+
case SyntaxKind.FunctionDeclaration:
|
|
67
|
+
case SyntaxKind.InterfaceDeclaration:
|
|
68
|
+
case SyntaxKind.TypeAliasDeclaration:
|
|
69
|
+
return; // functions are emitted at module scope
|
|
70
|
+
case SyntaxKind.IfStatement:
|
|
71
|
+
this.emitIf(statement as IfStatement);
|
|
72
|
+
return;
|
|
73
|
+
case SyntaxKind.WhileStatement:
|
|
74
|
+
this.emitWhile(statement as WhileStatement);
|
|
75
|
+
return;
|
|
76
|
+
case SyntaxKind.DoStatement:
|
|
77
|
+
this.emitDo(statement as DoStatement);
|
|
78
|
+
return;
|
|
79
|
+
case SyntaxKind.ForStatement:
|
|
80
|
+
this.emitFor(statement as ForStatement);
|
|
81
|
+
return;
|
|
82
|
+
case SyntaxKind.ForOfStatement:
|
|
83
|
+
case SyntaxKind.ForInStatement:
|
|
84
|
+
this.emitForOf(statement as ForOfStatement | ForInStatement);
|
|
85
|
+
return;
|
|
86
|
+
case SyntaxKind.SwitchStatement:
|
|
87
|
+
this.emitSwitch(statement as SwitchStatement);
|
|
88
|
+
return;
|
|
89
|
+
case SyntaxKind.TryStatement:
|
|
90
|
+
this.emitTry(statement as TryStatement);
|
|
91
|
+
return;
|
|
92
|
+
case SyntaxKind.ReturnStatement:
|
|
93
|
+
this.emitReturn(statement as ReturnStatement);
|
|
94
|
+
return;
|
|
95
|
+
case SyntaxKind.BreakStatement: {
|
|
96
|
+
const loop = this.current.loops[this.current.loops.length - 1];
|
|
97
|
+
if (loop) {
|
|
98
|
+
this.popTryFramesTo(loop.tryDepth ?? 0);
|
|
99
|
+
this.terminate(`br label %${loop.breakLabel}`);
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
case SyntaxKind.ContinueStatement: {
|
|
104
|
+
const loop = this.current.loops[this.current.loops.length - 1];
|
|
105
|
+
if (loop) {
|
|
106
|
+
this.popTryFramesTo(loop.tryDepth ?? 0);
|
|
107
|
+
this.terminate(`br label %${loop.continueLabel}`);
|
|
108
|
+
}
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
case SyntaxKind.ThrowStatement: {
|
|
112
|
+
const value = this.emitExpression((statement as ThrowStatement).expression);
|
|
113
|
+
this.emit(` call void @xt_throw(i64 ${value})`);
|
|
114
|
+
this.terminate("unreachable");
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
case SyntaxKind.ExportDeclaration: {
|
|
118
|
+
const declaration = (statement as { declaration?: Statement }).declaration;
|
|
119
|
+
if (declaration) this.emitStatement(declaration);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
default:
|
|
123
|
+
this.unsupported(statement, "statement");
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
emitVariableStatement(statement: VariableStatement): void {
|
|
128
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
129
|
+
const symbol = this.binding.symbolOfDeclaration.get(declaration);
|
|
130
|
+
if (!symbol) continue;
|
|
131
|
+
if (this.current.slots.has(symbol.id)) continue; // hoisted `var`
|
|
132
|
+
const initial = declaration.initializer
|
|
133
|
+
? this.emitExpression(declaration.initializer)
|
|
134
|
+
: i64(XT_UNDEFINED);
|
|
135
|
+
this.declareSlot(symbol, initial);
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
emitIf(statement: IfStatement): void {
|
|
140
|
+
const thenLabel = this.label("if.then");
|
|
141
|
+
const elseLabel = statement.elseStatement ? this.label("if.else") : undefined;
|
|
142
|
+
const endLabel = this.label("if.end");
|
|
143
|
+
const condition = this.emitExpression(statement.condition);
|
|
144
|
+
const truthy = this.reg();
|
|
145
|
+
this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${condition})`);
|
|
146
|
+
const nonzero = this.reg();
|
|
147
|
+
this.emit(` ${nonzero} = icmp ne i32 ${truthy}, 0`);
|
|
148
|
+
this.terminate(`br i1 ${nonzero}, label %${thenLabel}, label %${elseLabel ?? endLabel}`);
|
|
149
|
+
|
|
150
|
+
this.startBlock(thenLabel);
|
|
151
|
+
this.emitStatement(statement.thenStatement);
|
|
152
|
+
if (!this.current.terminated) this.terminate(`br label %${endLabel}`);
|
|
153
|
+
|
|
154
|
+
if (elseLabel) {
|
|
155
|
+
this.startBlock(elseLabel);
|
|
156
|
+
this.emitStatement(statement.elseStatement!);
|
|
157
|
+
if (!this.current.terminated) this.terminate(`br label %${endLabel}`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
this.startBlock(endLabel);
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
emitWhile(statement: WhileStatement): void {
|
|
164
|
+
const condLabel = this.label("while.cond");
|
|
165
|
+
const bodyLabel = this.label("while.body");
|
|
166
|
+
const endLabel = this.label("while.end");
|
|
167
|
+
this.terminate(`br label %${condLabel}`);
|
|
168
|
+
this.startBlock(condLabel);
|
|
169
|
+
const condition = this.emitExpression(statement.condition);
|
|
170
|
+
const truthy = this.reg();
|
|
171
|
+
this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${condition})`);
|
|
172
|
+
const nonzero = this.reg();
|
|
173
|
+
this.emit(` ${nonzero} = icmp ne i32 ${truthy}, 0`);
|
|
174
|
+
this.terminate(`br i1 ${nonzero}, label %${bodyLabel}, label %${endLabel}`);
|
|
175
|
+
|
|
176
|
+
this.startBlock(bodyLabel);
|
|
177
|
+
this.current.loops.push({ breakLabel: endLabel, continueLabel: condLabel, tryDepth: this.current.tryFrames.length });
|
|
178
|
+
this.emitStatement(statement.statement);
|
|
179
|
+
this.current.loops.pop();
|
|
180
|
+
if (!this.current.terminated) this.terminate(`br label %${condLabel}`);
|
|
181
|
+
this.startBlock(endLabel);
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
emitDo(statement: DoStatement): void {
|
|
185
|
+
const bodyLabel = this.label("do.body");
|
|
186
|
+
const condLabel = this.label("do.cond");
|
|
187
|
+
const endLabel = this.label("do.end");
|
|
188
|
+
this.terminate(`br label %${bodyLabel}`);
|
|
189
|
+
this.startBlock(bodyLabel);
|
|
190
|
+
this.current.loops.push({ breakLabel: endLabel, continueLabel: condLabel, tryDepth: this.current.tryFrames.length });
|
|
191
|
+
this.emitStatement(statement.statement);
|
|
192
|
+
this.current.loops.pop();
|
|
193
|
+
if (!this.current.terminated) this.terminate(`br label %${condLabel}`);
|
|
194
|
+
|
|
195
|
+
this.startBlock(condLabel);
|
|
196
|
+
const condition = this.emitExpression(statement.condition);
|
|
197
|
+
const truthy = this.reg();
|
|
198
|
+
this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${condition})`);
|
|
199
|
+
const nonzero = this.reg();
|
|
200
|
+
this.emit(` ${nonzero} = icmp ne i32 ${truthy}, 0`);
|
|
201
|
+
this.terminate(`br i1 ${nonzero}, label %${bodyLabel}, label %${endLabel}`);
|
|
202
|
+
this.startBlock(endLabel);
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
emitFor(statement: ForStatement): void {
|
|
206
|
+
if (statement.initializer && statement.initializer.kind === SyntaxKind.VariableDeclarationList) {
|
|
207
|
+
this.emitVariableStatement({
|
|
208
|
+
kind: SyntaxKind.VariableStatement,
|
|
209
|
+
declarationList: statement.initializer as VariableDeclarationList,
|
|
210
|
+
modifiers: [],
|
|
211
|
+
start: statement.initializer.start,
|
|
212
|
+
end: statement.initializer.end,
|
|
213
|
+
});
|
|
214
|
+
} else if (statement.initializer) {
|
|
215
|
+
this.emitExpression(statement.initializer as Expression);
|
|
216
|
+
}
|
|
217
|
+
const condLabel = this.label("for.cond");
|
|
218
|
+
const bodyLabel = this.label("for.body");
|
|
219
|
+
const updateLabel = this.label("for.update");
|
|
220
|
+
const endLabel = this.label("for.end");
|
|
221
|
+
this.terminate(`br label %${condLabel}`);
|
|
222
|
+
|
|
223
|
+
this.startBlock(condLabel);
|
|
224
|
+
if (statement.condition) {
|
|
225
|
+
const condition = this.emitExpression(statement.condition);
|
|
226
|
+
const truthy = this.reg();
|
|
227
|
+
this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${condition})`);
|
|
228
|
+
const nonzero = this.reg();
|
|
229
|
+
this.emit(` ${nonzero} = icmp ne i32 ${truthy}, 0`);
|
|
230
|
+
this.terminate(`br i1 ${nonzero}, label %${bodyLabel}, label %${endLabel}`);
|
|
231
|
+
} else {
|
|
232
|
+
this.terminate(`br label %${bodyLabel}`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
this.startBlock(bodyLabel);
|
|
236
|
+
this.current.loops.push({ breakLabel: endLabel, continueLabel: updateLabel, tryDepth: this.current.tryFrames.length });
|
|
237
|
+
this.emitStatement(statement.statement);
|
|
238
|
+
this.current.loops.pop();
|
|
239
|
+
if (!this.current.terminated) this.terminate(`br label %${updateLabel}`);
|
|
240
|
+
|
|
241
|
+
this.startBlock(updateLabel);
|
|
242
|
+
if (statement.incrementor) this.emitExpression(statement.incrementor);
|
|
243
|
+
this.terminate(`br label %${condLabel}`);
|
|
244
|
+
this.startBlock(endLabel);
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
/** `for (const x of xs)` / `for (const k in obj)` over arrays, strings and objects. */
|
|
248
|
+
emitForOf(statement: ForOfStatement | ForInStatement): void {
|
|
249
|
+
const source = this.emitExpression(statement.expression);
|
|
250
|
+
const isForIn = statement.kind === SyntaxKind.ForInStatement;
|
|
251
|
+
// `for...in` iterates the enumerable keys (indices become strings);
|
|
252
|
+
// `for...of` iterates the values at each index.
|
|
253
|
+
const iterable = isForIn ? this.runtimeCall("xt_object_keys", [source]) : source;
|
|
254
|
+
const indexPtr = this.alloca();
|
|
255
|
+
this.emit(` store i64 ${numberLiteral(0)}, i64* ${indexPtr}`);
|
|
256
|
+
const lengthValue = this.reg();
|
|
257
|
+
this.emit(` ${lengthValue} = call i64 @xt_array_length(i64 ${iterable})`);
|
|
258
|
+
|
|
259
|
+
const condLabel = this.label("forof.cond");
|
|
260
|
+
const bodyLabel = this.label("forof.body");
|
|
261
|
+
const updateLabel = this.label("forof.update");
|
|
262
|
+
const endLabel = this.label("forof.end");
|
|
263
|
+
this.terminate(`br label %${condLabel}`);
|
|
264
|
+
|
|
265
|
+
this.startBlock(condLabel);
|
|
266
|
+
const index = this.reg();
|
|
267
|
+
this.emit(` ${index} = load i64, i64* ${indexPtr}`);
|
|
268
|
+
const inRange = this.reg();
|
|
269
|
+
this.emit(` ${inRange} = call i64 @xt_lt(i64 ${index}, i64 ${lengthValue})`);
|
|
270
|
+
const truthy = this.reg();
|
|
271
|
+
this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${inRange})`);
|
|
272
|
+
const nonzero = this.reg();
|
|
273
|
+
this.emit(` ${nonzero} = icmp ne i32 ${truthy}, 0`);
|
|
274
|
+
this.terminate(`br i1 ${nonzero}, label %${bodyLabel}, label %${endLabel}`);
|
|
275
|
+
|
|
276
|
+
this.startBlock(bodyLabel);
|
|
277
|
+
const element = this.reg();
|
|
278
|
+
this.emit(` ${element} = call i64 @xt_get(i64 ${iterable}, i64 ${index})`);
|
|
279
|
+
this.bindLoopVariable(statement.initializer, element);
|
|
280
|
+
this.current.loops.push({ breakLabel: endLabel, continueLabel: updateLabel, tryDepth: this.current.tryFrames.length });
|
|
281
|
+
this.emitStatement(statement.statement);
|
|
282
|
+
this.current.loops.pop();
|
|
283
|
+
if (!this.current.terminated) this.terminate(`br label %${updateLabel}`);
|
|
284
|
+
|
|
285
|
+
this.startBlock(updateLabel);
|
|
286
|
+
const next = this.reg();
|
|
287
|
+
this.emit(` ${next} = call i64 @xt_add(i64 ${index}, i64 ${numberLiteral(1)})`);
|
|
288
|
+
this.emit(` store i64 ${next}, i64* ${indexPtr}`);
|
|
289
|
+
this.terminate(`br label %${condLabel}`);
|
|
290
|
+
this.startBlock(endLabel);
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
bindLoopVariable(initializer: VariableDeclarationList | Expression, value: string): void {
|
|
294
|
+
if (initializer.kind === SyntaxKind.VariableDeclarationList) {
|
|
295
|
+
const declaration = initializer.declarations[0];
|
|
296
|
+
if (!declaration) return;
|
|
297
|
+
const symbol = this.binding.symbolOfDeclaration.get(declaration);
|
|
298
|
+
if (symbol) {
|
|
299
|
+
if (this.current.slots.has(symbol.id)) this.writeSlot(symbol, value);
|
|
300
|
+
else this.declareSlot(symbol, value);
|
|
301
|
+
}
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
this.emitAssignmentTarget(initializer as Expression, value);
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* JavaScript `switch`: test each `case` with strict equality, then run the
|
|
309
|
+
* matched clause and fall through into the following clauses until `break`.
|
|
310
|
+
*/
|
|
311
|
+
emitSwitch(statement: SwitchStatement): void {
|
|
312
|
+
const discriminant = this.emitExpression(statement.expression);
|
|
313
|
+
const endLabel = this.label("switch.end");
|
|
314
|
+
const clauses = statement.clauses;
|
|
315
|
+
const labels = clauses.map(() => this.label("switch.case"));
|
|
316
|
+
const testLabels = clauses.map((clause) =>
|
|
317
|
+
clause.kind === SyntaxKind.CaseClause ? this.label("switch.test") : undefined,
|
|
318
|
+
);
|
|
319
|
+
const defaultIndex = clauses.findIndex((clause) => clause.kind === SyntaxKind.DefaultClause);
|
|
320
|
+
const defaultLabel = defaultIndex >= 0 ? labels[defaultIndex]! : endLabel;
|
|
321
|
+
const caseIndexes = clauses
|
|
322
|
+
.map((clause, index) => ({ clause, index }))
|
|
323
|
+
.filter((entry) => entry.clause.kind === SyntaxKind.CaseClause);
|
|
324
|
+
|
|
325
|
+
this.terminate(`br label %${caseIndexes.length > 0 ? testLabels[caseIndexes[0]!.index]! : defaultLabel}`);
|
|
326
|
+
for (let test = 0; test < caseIndexes.length; test++) {
|
|
327
|
+
const entry = caseIndexes[test]!;
|
|
328
|
+
this.startBlock(testLabels[entry.index]!);
|
|
329
|
+
const caseValue = this.emitExpression((entry.clause as CaseClause).expression);
|
|
330
|
+
const equals = this.reg();
|
|
331
|
+
this.emit(` ${equals} = call i64 @xt_seq(i64 ${discriminant}, i64 ${caseValue})`);
|
|
332
|
+
const truthy = this.reg();
|
|
333
|
+
this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${equals})`);
|
|
334
|
+
const condition = this.reg();
|
|
335
|
+
this.emit(` ${condition} = icmp ne i32 ${truthy}, 0`);
|
|
336
|
+
const next = test + 1 < caseIndexes.length ? testLabels[caseIndexes[test + 1]!.index]! : defaultLabel;
|
|
337
|
+
this.terminate(`br i1 ${condition}, label %${labels[entry.index]}, label %${next}`);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
for (let index = 0; index < clauses.length; index++) {
|
|
341
|
+
this.startBlock(labels[index]!);
|
|
342
|
+
const enclosing = this.current.loops[this.current.loops.length - 1];
|
|
343
|
+
this.current.loops.push({ breakLabel: endLabel, continueLabel: enclosing?.continueLabel ?? endLabel, tryDepth: this.current.tryFrames.length });
|
|
344
|
+
for (const child of clauses[index]!.statements) {
|
|
345
|
+
if (this.current.terminated) break;
|
|
346
|
+
this.emitStatement(child);
|
|
347
|
+
}
|
|
348
|
+
this.current.loops.pop();
|
|
349
|
+
if (!this.current.terminated) {
|
|
350
|
+
const next = index + 1 < clauses.length ? labels[index + 1]! : endLabel;
|
|
351
|
+
this.terminate(`br label %${next}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
this.startBlock(endLabel);
|
|
355
|
+
},
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* `try`/`catch`/`finally` via a runtime setjmp frame. The runtime keeps a
|
|
359
|
+
* stack of frames; `xt_throw` longjmps into the innermost one. A `return`
|
|
360
|
+
* that exits the protected region skips `finally` (a known limitation).
|
|
361
|
+
*/
|
|
362
|
+
emitTry(statement: TryStatement): void {
|
|
363
|
+
this.current.usesTry = true;
|
|
364
|
+
const frameSlot = this.alloca();
|
|
365
|
+
const exceptionSlot = this.alloca();
|
|
366
|
+
const flagSlot = this.alloca();
|
|
367
|
+
const tryLabel = this.label("try.body");
|
|
368
|
+
const catchLabel = this.label("try.catch");
|
|
369
|
+
const exceptionLabel = this.label("try.exception");
|
|
370
|
+
const rethrowLabel = this.label("try.rethrow");
|
|
371
|
+
const finallyLabel = statement.finallyBlock ? this.label("try.finally") : undefined;
|
|
372
|
+
const endLabel = this.label("try.end");
|
|
373
|
+
const hasCatch = !!statement.catchClause;
|
|
374
|
+
|
|
375
|
+
const frame = this.reg();
|
|
376
|
+
this.emit(` ${frame} = call i8* @xt_try_enter()`);
|
|
377
|
+
this.emit(` store i8* ${frame}, i8** ${frameSlot}`);
|
|
378
|
+
this.emit(` store i64 0, i64* ${flagSlot}`);
|
|
379
|
+
const jump = this.reg();
|
|
380
|
+
this.emit(` ${jump} = call i32 @_setjmp(i8* ${frame})`);
|
|
381
|
+
const isThrow = this.reg();
|
|
382
|
+
this.emit(` ${isThrow} = icmp ne i32 ${jump}, 0`);
|
|
383
|
+
const exceptionTarget = hasCatch ? catchLabel : exceptionLabel;
|
|
384
|
+
this.terminate(`br i1 ${isThrow}, label %${exceptionTarget}, label %${tryLabel}`);
|
|
385
|
+
|
|
386
|
+
// Normal completion of the try block.
|
|
387
|
+
this.startBlock(tryLabel);
|
|
388
|
+
this.current.tryFrames.push(frameSlot);
|
|
389
|
+
this.emitStatements(statement.tryBlock.statements);
|
|
390
|
+
this.current.tryFrames.pop();
|
|
391
|
+
if (!this.current.terminated) {
|
|
392
|
+
const currentFrame = this.reg();
|
|
393
|
+
this.emit(` ${currentFrame} = load i8*, i8** ${frameSlot}`);
|
|
394
|
+
this.emit(` call void @xt_try_leave(i8* ${currentFrame})`);
|
|
395
|
+
this.terminate(`br label %${finallyLabel ?? endLabel}`);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (hasCatch) {
|
|
399
|
+
this.startBlock(catchLabel);
|
|
400
|
+
const currentFrame = this.reg();
|
|
401
|
+
this.emit(` ${currentFrame} = load i8*, i8** ${frameSlot}`);
|
|
402
|
+
const exception = this.reg();
|
|
403
|
+
this.emit(` ${exception} = call i64 @xt_try_exception(i8* ${currentFrame})`);
|
|
404
|
+
this.emit(` call void @xt_try_leave(i8* ${currentFrame})`);
|
|
405
|
+
this.bindCatchVariable(statement.catchClause!.variable, exception);
|
|
406
|
+
this.emitStatements(statement.catchClause!.block.statements);
|
|
407
|
+
if (!this.current.terminated) this.terminate(`br label %${finallyLabel ?? endLabel}`);
|
|
408
|
+
} else {
|
|
409
|
+
// Without a catch clause the exception is remembered, then rethrown
|
|
410
|
+
// after `finally` runs.
|
|
411
|
+
this.startBlock(exceptionLabel);
|
|
412
|
+
const currentFrame = this.reg();
|
|
413
|
+
this.emit(` ${currentFrame} = load i8*, i8** ${frameSlot}`);
|
|
414
|
+
const exception = this.reg();
|
|
415
|
+
this.emit(` ${exception} = call i64 @xt_try_exception(i8* ${currentFrame})`);
|
|
416
|
+
this.emit(` call void @xt_try_leave(i8* ${currentFrame})`);
|
|
417
|
+
this.emit(` store i64 ${exception}, i64* ${exceptionSlot}`);
|
|
418
|
+
this.emit(` store i64 1, i64* ${flagSlot}`);
|
|
419
|
+
this.terminate(`br label %${finallyLabel ?? rethrowLabel}`);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (finallyLabel) {
|
|
423
|
+
this.startBlock(finallyLabel);
|
|
424
|
+
this.emitStatements(statement.finallyBlock!.statements);
|
|
425
|
+
if (!this.current.terminated) {
|
|
426
|
+
if (hasCatch) {
|
|
427
|
+
this.terminate(`br label %${endLabel}`);
|
|
428
|
+
} else {
|
|
429
|
+
const flag = this.reg();
|
|
430
|
+
this.emit(` ${flag} = load i64, i64* ${flagSlot}`);
|
|
431
|
+
const truthy = this.reg();
|
|
432
|
+
this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${flag})`);
|
|
433
|
+
const shouldRethrow = this.reg();
|
|
434
|
+
this.emit(` ${shouldRethrow} = icmp ne i32 ${truthy}, 0`);
|
|
435
|
+
this.terminate(`br i1 ${shouldRethrow}, label %${rethrowLabel}, label %${endLabel}`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (!hasCatch) {
|
|
441
|
+
this.startBlock(rethrowLabel);
|
|
442
|
+
const exception = this.reg();
|
|
443
|
+
this.emit(` ${exception} = load i64, i64* ${exceptionSlot}`);
|
|
444
|
+
this.emit(` call void @xt_throw(i64 ${exception})`);
|
|
445
|
+
this.terminate("unreachable");
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
this.startBlock(endLabel);
|
|
449
|
+
},
|
|
450
|
+
|
|
451
|
+
bindCatchVariable(variable: Identifier | undefined, value: string): void {
|
|
452
|
+
if (!variable) return;
|
|
453
|
+
const symbol = this.binding.symbolOfDeclaration.get(variable);
|
|
454
|
+
if (!symbol) return;
|
|
455
|
+
if (this.current.slots.has(symbol.id)) this.writeSlot(symbol, value);
|
|
456
|
+
else this.declareSlot(symbol, value);
|
|
457
|
+
},
|
|
458
|
+
|
|
459
|
+
emitReturn(statement: ReturnStatement): void {
|
|
460
|
+
const value = statement.expression ? this.emitExpression(statement.expression) : i64(XT_UNDEFINED);
|
|
461
|
+
this.popTryFramesTo(0);
|
|
462
|
+
this.terminate(`ret i64 ${value}`);
|
|
463
|
+
},
|
|
464
|
+
|
|
465
|
+
/** Emit `xt_try_leave` for every active frame above `depth` (innermost first). */
|
|
466
|
+
popTryFramesTo(depth: number): void {
|
|
467
|
+
for (let index = this.current.tryFrames.length - 1; index >= depth; index--) {
|
|
468
|
+
const slot = this.current.tryFrames[index]!;
|
|
469
|
+
const frame = this.reg();
|
|
470
|
+
this.emit(` ${frame} = load i8*, i8** ${slot}`);
|
|
471
|
+
this.emit(` call void @xt_try_leave(i8* ${frame})`);
|
|
472
|
+
}
|
|
473
|
+
},
|
|
474
|
+
};
|