xbintsc 0.1.6 → 0.1.9
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 -4
- 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/binder/binder.d.ts +35 -1
- package/dist/src/binder/binder.js +138 -0
- package/dist/src/binder/binder.js.map +1 -1
- package/dist/src/codegen/generator/calls.d.ts +26 -0
- package/dist/src/codegen/generator/calls.js +373 -0
- package/dist/src/codegen/generator/calls.js.map +1 -0
- package/dist/src/codegen/generator/context.d.ts +47 -0
- package/dist/src/codegen/generator/context.js +164 -0
- package/dist/src/codegen/generator/context.js.map +1 -0
- package/dist/src/codegen/generator/expressions.d.ts +25 -0
- package/dist/src/codegen/generator/expressions.js +454 -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 +19 -0
- package/dist/src/codegen/generator/module.js +214 -0
- package/dist/src/codegen/generator/module.js.map +1 -0
- package/dist/src/codegen/generator/state.d.ts +42 -0
- package/dist/src/codegen/generator/state.js +125 -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 +433 -0
- package/dist/src/codegen/generator/statements.js.map +1 -0
- package/dist/src/codegen/generator/tables.d.ts +23 -0
- package/dist/src/codegen/generator/tables.js +186 -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 -1537
- package/dist/src/codegen/llvm.js.map +1 -1
- package/dist/src/driver/compiler.js +36 -11
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/driver/modules.d.ts +24 -0
- package/dist/src/driver/modules.js +347 -0
- package/dist/src/driver/modules.js.map +1 -0
- 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 +440 -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 -2311
- 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/codegen/llvm.test.js +1 -1
- package/dist/tests/codegen/llvm.test.js.map +1 -1
- package/dist/tests/e2e/compile.test.js +86 -0
- package/dist/tests/e2e/compile.test.js.map +1 -1
- package/package.json +1 -1
- package/runtime/rt.h +55 -4
- package/runtime/rt_internal.h +169 -0
- package/runtime/xt_alloc.c +38 -0
- package/runtime/xt_builtins.c +238 -0
- package/runtime/xt_containers.c +312 -0
- package/runtime/xt_io.c +246 -0
- package/runtime/xt_promise.c +292 -0
- package/runtime/xt_stdlib.c +556 -0
- package/runtime/xt_stdlib2.c +1043 -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/binder/binder.ts +196 -0
- package/src/codegen/generator/calls.ts +433 -0
- package/src/codegen/generator/context.ts +190 -0
- package/src/codegen/generator/expressions.ts +508 -0
- package/src/codegen/generator/generator.ts +35 -0
- package/src/codegen/generator/module.ts +244 -0
- package/src/codegen/generator/state.ts +169 -0
- package/src/codegen/generator/statements.ts +480 -0
- package/src/codegen/generator/tables.ts +206 -0
- package/src/codegen/llvm.ts +4 -1720
- package/src/driver/compiler.ts +45 -12
- package/src/driver/modules.ts +377 -0
- 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 +468 -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 -2373
- 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 -1372
package/src/codegen/llvm.ts
CHANGED
|
@@ -15,1725 +15,9 @@
|
|
|
15
15
|
*
|
|
16
16
|
* Later pipeline stages (optimization passes, object emission) live in the
|
|
17
17
|
* driver; this module only produces IR text.
|
|
18
|
+
*
|
|
19
|
+
* The implementation is split by function under `./generator/`; this module is
|
|
20
|
+
* the stable entry point.
|
|
18
21
|
*/
|
|
19
22
|
|
|
20
|
-
|
|
21
|
-
SyntaxKind,
|
|
22
|
-
type Node,
|
|
23
|
-
type SourceFileNode,
|
|
24
|
-
type Expression,
|
|
25
|
-
type Statement,
|
|
26
|
-
type Identifier,
|
|
27
|
-
type CallExpression,
|
|
28
|
-
type PropertyAccessExpression,
|
|
29
|
-
type ElementAccessExpression,
|
|
30
|
-
type BinaryExpression,
|
|
31
|
-
type ConditionalExpression,
|
|
32
|
-
type ArrowFunction,
|
|
33
|
-
type FunctionExpression,
|
|
34
|
-
type FunctionDeclaration,
|
|
35
|
-
type VariableStatement,
|
|
36
|
-
type VariableDeclarationList,
|
|
37
|
-
type Block,
|
|
38
|
-
type IfStatement,
|
|
39
|
-
type WhileStatement,
|
|
40
|
-
type DoStatement,
|
|
41
|
-
type ForStatement,
|
|
42
|
-
type ForOfStatement,
|
|
43
|
-
type ForInStatement,
|
|
44
|
-
type SwitchStatement,
|
|
45
|
-
type CaseClause,
|
|
46
|
-
type TryStatement,
|
|
47
|
-
type DeleteExpression,
|
|
48
|
-
type ReturnStatement,
|
|
49
|
-
type ThrowStatement,
|
|
50
|
-
type ObjectLiteralExpression,
|
|
51
|
-
type ArrayLiteralExpression,
|
|
52
|
-
type TemplateLiteral,
|
|
53
|
-
type PrefixUnaryExpression,
|
|
54
|
-
type PostfixUnaryExpression,
|
|
55
|
-
type Parameter,
|
|
56
|
-
} from "../ast/nodes.js";
|
|
57
|
-
import {
|
|
58
|
-
AssignmentOperator,
|
|
59
|
-
BinaryOperator,
|
|
60
|
-
PrefixUnaryOperator,
|
|
61
|
-
PostfixUnaryOperator,
|
|
62
|
-
} from "../ast/nodes.js";
|
|
63
|
-
import {
|
|
64
|
-
bind,
|
|
65
|
-
type BindResult,
|
|
66
|
-
type FunctionInfo,
|
|
67
|
-
type SymbolInfo,
|
|
68
|
-
SymbolKind,
|
|
69
|
-
} from "../binder/binder.js";
|
|
70
|
-
import type { DiagnosticBag } from "../diagnostics/diagnostic.js";
|
|
71
|
-
import { DiagnosticCode } from "../diagnostics/diagnostic.js";
|
|
72
|
-
import type { BuiltinFunction } from "../extensions/registry.js";
|
|
73
|
-
import { i64, numberLiteral, XT_UNDEFINED, XT_NULL, XT_FALSE, XT_TRUE } from "./values.js";
|
|
74
|
-
|
|
75
|
-
interface Slot {
|
|
76
|
-
/** Register holding the `i64*` to the storage. */
|
|
77
|
-
readonly ptr: string;
|
|
78
|
-
readonly boxed: boolean;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
interface LoopLabels {
|
|
82
|
-
readonly breakLabel: string;
|
|
83
|
-
readonly continueLabel: string;
|
|
84
|
-
/** Number of enclosing `try` frames when the loop was entered. */
|
|
85
|
-
readonly tryDepth?: number;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
interface FunctionState {
|
|
89
|
-
readonly fn: FunctionInfo;
|
|
90
|
-
readonly buffer: string[];
|
|
91
|
-
readonly allocas: string[];
|
|
92
|
-
readonly slots: Map<number, Slot>;
|
|
93
|
-
reg: number;
|
|
94
|
-
label: number;
|
|
95
|
-
terminated: boolean;
|
|
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;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const RUNTIME_DECLARATIONS: readonly string[] = [
|
|
104
|
-
"declare i64 @xt_undefined()",
|
|
105
|
-
"declare i64 @xt_null()",
|
|
106
|
-
"declare i64 @xt_bool(i32)",
|
|
107
|
-
"declare i64 @xt_number(double)",
|
|
108
|
-
"declare i64 @xt_string_new(i8*, i64)",
|
|
109
|
-
"declare i64 @xt_string_from_cstr(i8*)",
|
|
110
|
-
"declare i64 @xt_arg(i32, i64*, i32)",
|
|
111
|
-
"declare i64 @xt_closure_new(i8*, i32, i64*)",
|
|
112
|
-
"declare i64 @xt_closure_call(i64, i32, i64*)",
|
|
113
|
-
"declare i64 @xt_closure_env(i64, i32)",
|
|
114
|
-
"declare i32 @xt_truthy(i64)",
|
|
115
|
-
"declare double @xt_to_number(i64)",
|
|
116
|
-
"declare i64 @xt_to_string(i64)",
|
|
117
|
-
"declare i64 @xt_typeof(i64)",
|
|
118
|
-
"declare i64 @xt_add(i64, i64)",
|
|
119
|
-
"declare i64 @xt_sub(i64, i64)",
|
|
120
|
-
"declare i64 @xt_mul(i64, i64)",
|
|
121
|
-
"declare i64 @xt_div(i64, i64)",
|
|
122
|
-
"declare i64 @xt_mod(i64, i64)",
|
|
123
|
-
"declare i64 @xt_pow(i64, i64)",
|
|
124
|
-
"declare i64 @xt_neg(i64)",
|
|
125
|
-
"declare i64 @xt_pos(i64)",
|
|
126
|
-
"declare i64 @xt_bit_and(i64, i64)",
|
|
127
|
-
"declare i64 @xt_bit_or(i64, i64)",
|
|
128
|
-
"declare i64 @xt_bit_xor(i64, i64)",
|
|
129
|
-
"declare i64 @xt_bit_not(i64)",
|
|
130
|
-
"declare i64 @xt_shl(i64, i64)",
|
|
131
|
-
"declare i64 @xt_shr(i64, i64)",
|
|
132
|
-
"declare i64 @xt_ushr(i64, i64)",
|
|
133
|
-
"declare i64 @xt_lt(i64, i64)",
|
|
134
|
-
"declare i64 @xt_le(i64, i64)",
|
|
135
|
-
"declare i64 @xt_gt(i64, i64)",
|
|
136
|
-
"declare i64 @xt_ge(i64, i64)",
|
|
137
|
-
"declare i64 @xt_eq(i64, i64)",
|
|
138
|
-
"declare i64 @xt_ne(i64, i64)",
|
|
139
|
-
"declare i64 @xt_seq(i64, i64)",
|
|
140
|
-
"declare i64 @xt_sne(i64, i64)",
|
|
141
|
-
"declare i64 @xt_not(i64)",
|
|
142
|
-
"declare i64 @xt_object_new()",
|
|
143
|
-
"declare i64 @xt_object_get_cstr(i64, i8*)",
|
|
144
|
-
"declare i64 @xt_object_set(i64, i64, i64)",
|
|
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)",
|
|
163
|
-
"declare i64 @xt_array_new(i32, i64*)",
|
|
164
|
-
"declare i64 @xt_array_push(i64, i64)",
|
|
165
|
-
"declare i64 @xt_array_length(i64)",
|
|
166
|
-
"declare i64 @xt_array_spread(i64, i64)",
|
|
167
|
-
"declare i64 @xt_get(i64, i64)",
|
|
168
|
-
"declare i64 @xt_set(i64, i64, i64)",
|
|
169
|
-
"declare i64 @xt_box_new(i64)",
|
|
170
|
-
"declare i64 @xt_box_get(i64)",
|
|
171
|
-
"declare i64 @xt_box_set(i64, i64)",
|
|
172
|
-
"declare i64 @xt_is_nullish(i64)",
|
|
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*)",
|
|
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*)",
|
|
182
|
-
];
|
|
183
|
-
|
|
184
|
-
export interface CodegenResult {
|
|
185
|
-
readonly ir: string;
|
|
186
|
-
readonly binding: BindResult;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
export interface CodegenOptions {
|
|
190
|
-
/** Global names supplied by registered extensions. */
|
|
191
|
-
readonly builtins?: Readonly<Record<string, BuiltinFunction>>;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
export function generate(
|
|
195
|
-
sourceFile: SourceFileNode,
|
|
196
|
-
diagnostics: DiagnosticBag,
|
|
197
|
-
options: CodegenOptions = {},
|
|
198
|
-
): CodegenResult {
|
|
199
|
-
const generator = new Generator(sourceFile, diagnostics, options);
|
|
200
|
-
return { ir: generator.run(), binding: generator.binding };
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
class Generator {
|
|
204
|
-
readonly binding: BindResult;
|
|
205
|
-
private readonly globals: string[] = [];
|
|
206
|
-
private readonly functions: string[] = [];
|
|
207
|
-
private readonly strings = new Map<string, { label: string; length: number }>();
|
|
208
|
-
private readonly extraDeclarations = new Set<string>();
|
|
209
|
-
private readonly builtins: Readonly<Record<string, BuiltinFunction>>;
|
|
210
|
-
private stringCounter = 0;
|
|
211
|
-
private current!: FunctionState;
|
|
212
|
-
|
|
213
|
-
constructor(
|
|
214
|
-
private readonly sourceFile: SourceFileNode,
|
|
215
|
-
private readonly diagnostics: DiagnosticBag,
|
|
216
|
-
options: CodegenOptions = {},
|
|
217
|
-
) {
|
|
218
|
-
this.binding = bind(sourceFile);
|
|
219
|
-
this.builtins = options.builtins ?? {};
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
run(): string {
|
|
223
|
-
for (const fn of this.binding.functions) this.emitFunction(fn);
|
|
224
|
-
this.emitMain();
|
|
225
|
-
const header = ["; ModuleID = 'xbintsc'", "source_filename = \"" + this.sourceFile.fileName + "\"", ""];
|
|
226
|
-
return [...header, ...RUNTIME_DECLARATIONS, ...this.extraDeclarations, "", ...this.globals, "", ...this.functions, ""].join("\n");
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// -- module level --------------------------------------------------------
|
|
230
|
-
|
|
231
|
-
private emitFunction(fn: FunctionInfo): void {
|
|
232
|
-
const name = this.functionName(fn);
|
|
233
|
-
const state: FunctionState = {
|
|
234
|
-
fn,
|
|
235
|
-
buffer: [],
|
|
236
|
-
allocas: [],
|
|
237
|
-
slots: new Map(),
|
|
238
|
-
reg: 0,
|
|
239
|
-
label: 0,
|
|
240
|
-
terminated: false,
|
|
241
|
-
loops: [],
|
|
242
|
-
tryFrames: [],
|
|
243
|
-
escapePointers: [],
|
|
244
|
-
usesTry: false,
|
|
245
|
-
};
|
|
246
|
-
this.current = state;
|
|
247
|
-
|
|
248
|
-
const header = `define i64 @${name}(i64 %env, i32 %argc, i64* %argv) {`;
|
|
249
|
-
this.emit(`%saved.env = alloca i64`);
|
|
250
|
-
this.emit(`store i64 %env, i64* %saved.env`);
|
|
251
|
-
this.emit(`%saved.argc = alloca i32`);
|
|
252
|
-
this.emit(`store i32 %argc, i32* %saved.argc`);
|
|
253
|
-
this.emit(`%saved.argv = alloca i64*`);
|
|
254
|
-
this.emit(`store i64* %argv, i64** %saved.argv`);
|
|
255
|
-
|
|
256
|
-
// Parameters.
|
|
257
|
-
const parameterNodes = (fn.node as { parameters?: Parameter[] }).parameters ?? [];
|
|
258
|
-
for (let index = 0; index < fn.params.length; index++) {
|
|
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
|
-
}
|
|
267
|
-
const value = this.reg();
|
|
268
|
-
this.emit(` ${value} = call i64 @xt_arg(i32 %argc, i64* %argv, i32 ${index})`);
|
|
269
|
-
this.declareSlot(symbol, value);
|
|
270
|
-
if (parameter?.initializer) this.emitDefaultParameter(symbol, value, parameter.initializer);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Captures threaded through the environment.
|
|
274
|
-
for (const symbol of fn.captures) {
|
|
275
|
-
const index = fn.captureIndex.get(symbol.id)!;
|
|
276
|
-
const value = this.reg();
|
|
277
|
-
this.emit(` ${value} = call i64 @xt_closure_env(i64 %env, i32 ${index})`);
|
|
278
|
-
this.defineCaptureSlot(symbol, value);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Body.
|
|
282
|
-
if (fn.node.kind === SyntaxKind.SourceFile) {
|
|
283
|
-
this.emitStatements(this.sourceFile.statements);
|
|
284
|
-
} else if (fn.node.kind === SyntaxKind.ArrowFunction && (fn.node as ArrowFunction).body.kind !== SyntaxKind.Block) {
|
|
285
|
-
const value = this.emitExpression((fn.node as ArrowFunction).body as Expression);
|
|
286
|
-
this.terminate(`ret i64 ${value}`);
|
|
287
|
-
} else {
|
|
288
|
-
const body = (fn.node as { body: Block }).body;
|
|
289
|
-
this.emitStatements(body.statements);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if (!this.current.terminated) this.terminate(`ret i64 ${i64(XT_UNDEFINED)}`);
|
|
293
|
-
|
|
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];
|
|
298
|
-
this.functions.push([header, ...lines, "}", ""].join("\n"));
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
private emitMain(): void {
|
|
302
|
-
const moduleName = this.functionName(this.binding.moduleFunction);
|
|
303
|
-
this.functions.push(
|
|
304
|
-
[
|
|
305
|
-
"define i32 @main(i32 %argc, i8** %argv) {",
|
|
306
|
-
` %result = call i64 @${moduleName}(i64 ${i64(XT_UNDEFINED)}, i32 0, i64* null)`,
|
|
307
|
-
" ret i32 0",
|
|
308
|
-
"}",
|
|
309
|
-
"",
|
|
310
|
-
].join("\n"),
|
|
311
|
-
);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
private functionName(fn: FunctionInfo): string {
|
|
315
|
-
return fn.isModule ? "xt_module" : `xt_fn_${fn.id}`;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// -- emission primitives -------------------------------------------------
|
|
319
|
-
|
|
320
|
-
private emit(line: string): void {
|
|
321
|
-
this.current.buffer.push(line);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
private terminate(line: string): void {
|
|
325
|
-
this.current.buffer.push(` ${line}`);
|
|
326
|
-
this.current.terminated = true;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
private reg(): string {
|
|
330
|
-
return `%r${this.current.reg++}`;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
private label(prefix: string): string {
|
|
334
|
-
return `${prefix}.${this.current.label++}`;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
private startBlock(label: string): void {
|
|
338
|
-
this.current.buffer.push(`${label}:`);
|
|
339
|
-
this.current.terminated = false;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
private alloca(): string {
|
|
343
|
-
const ptr = `%slot${this.current.allocas.length}`;
|
|
344
|
-
this.current.allocas.push(`${ptr} = alloca i64`);
|
|
345
|
-
this.current.escapePointers.push(ptr);
|
|
346
|
-
return ptr;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
private const(hex: bigint): string {
|
|
350
|
-
return i64(hex);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// -- variables -----------------------------------------------------------
|
|
354
|
-
|
|
355
|
-
private declareSlot(symbol: SymbolInfo, initial: string): void {
|
|
356
|
-
const ptr = this.alloca();
|
|
357
|
-
if (symbol.boxed) {
|
|
358
|
-
const box = this.reg();
|
|
359
|
-
this.emit(` ${box} = call i64 @xt_box_new(i64 ${initial})`);
|
|
360
|
-
this.emit(` store i64 ${box}, i64* ${ptr}`);
|
|
361
|
-
} else {
|
|
362
|
-
this.emit(` store i64 ${initial}, i64* ${ptr}`);
|
|
363
|
-
}
|
|
364
|
-
this.current.slots.set(symbol.id, { ptr, boxed: symbol.boxed });
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
private defineCaptureSlot(symbol: SymbolInfo, box: string): void {
|
|
368
|
-
const ptr = this.alloca();
|
|
369
|
-
this.emit(` store i64 ${box}, i64* ${ptr}`);
|
|
370
|
-
// A captured symbol is always boxed; the environment holds the box.
|
|
371
|
-
this.current.slots.set(symbol.id, { ptr, boxed: true });
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
private readSlot(symbol: SymbolInfo): string {
|
|
375
|
-
const slot = this.current.slots.get(symbol.id);
|
|
376
|
-
if (!slot) {
|
|
377
|
-
// Referenced from an inner function without a capture slot: this should
|
|
378
|
-
// be impossible once the binder has run, but stay defensive.
|
|
379
|
-
this.diagnostics.error(
|
|
380
|
-
DiagnosticCode.CodegenError,
|
|
381
|
-
`Internal: no slot for '${symbol.name}' in ${this.current.fn.name}`,
|
|
382
|
-
);
|
|
383
|
-
return i64(XT_UNDEFINED);
|
|
384
|
-
}
|
|
385
|
-
const value = this.reg();
|
|
386
|
-
this.emit(` ${value} = load i64, i64* ${slot.ptr}`);
|
|
387
|
-
if (!slot.boxed) return value;
|
|
388
|
-
const unboxed = this.reg();
|
|
389
|
-
this.emit(` ${unboxed} = call i64 @xt_box_get(i64 ${value})`);
|
|
390
|
-
return unboxed;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
private writeSlot(symbol: SymbolInfo, value: string): void {
|
|
394
|
-
const slot = this.current.slots.get(symbol.id);
|
|
395
|
-
if (!slot) {
|
|
396
|
-
this.diagnostics.error(DiagnosticCode.CodegenError, `Internal: no slot for '${symbol.name}'`);
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
if (!slot.boxed) {
|
|
400
|
-
this.emit(` store i64 ${value}, i64* ${slot.ptr}`);
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
const box = this.reg();
|
|
404
|
-
this.emit(` ${box} = load i64, i64* ${slot.ptr}`);
|
|
405
|
-
this.emit(` call i64 @xt_box_set(i64 ${box}, i64 ${value})`);
|
|
406
|
-
}
|
|
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
|
-
|
|
425
|
-
// -- statements ----------------------------------------------------------
|
|
426
|
-
|
|
427
|
-
private emitStatements(statements: readonly Statement[]): void {
|
|
428
|
-
for (const statement of statements) {
|
|
429
|
-
if (this.current.terminated) break;
|
|
430
|
-
this.emitStatement(statement);
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
private emitStatement(statement: Statement): void {
|
|
435
|
-
switch (statement.kind) {
|
|
436
|
-
case SyntaxKind.ExpressionStatement:
|
|
437
|
-
this.emitExpression((statement as { expression: Expression }).expression);
|
|
438
|
-
return;
|
|
439
|
-
case SyntaxKind.VariableStatement:
|
|
440
|
-
this.emitVariableStatement(statement as VariableStatement);
|
|
441
|
-
return;
|
|
442
|
-
case SyntaxKind.Block:
|
|
443
|
-
this.emitStatements((statement as Block).statements);
|
|
444
|
-
return;
|
|
445
|
-
case SyntaxKind.EmptyStatement:
|
|
446
|
-
case SyntaxKind.DebuggerStatement:
|
|
447
|
-
case SyntaxKind.FunctionDeclaration:
|
|
448
|
-
case SyntaxKind.InterfaceDeclaration:
|
|
449
|
-
case SyntaxKind.TypeAliasDeclaration:
|
|
450
|
-
return; // functions are emitted at module scope
|
|
451
|
-
case SyntaxKind.IfStatement:
|
|
452
|
-
this.emitIf(statement as IfStatement);
|
|
453
|
-
return;
|
|
454
|
-
case SyntaxKind.WhileStatement:
|
|
455
|
-
this.emitWhile(statement as WhileStatement);
|
|
456
|
-
return;
|
|
457
|
-
case SyntaxKind.DoStatement:
|
|
458
|
-
this.emitDo(statement as DoStatement);
|
|
459
|
-
return;
|
|
460
|
-
case SyntaxKind.ForStatement:
|
|
461
|
-
this.emitFor(statement as ForStatement);
|
|
462
|
-
return;
|
|
463
|
-
case SyntaxKind.ForOfStatement:
|
|
464
|
-
case SyntaxKind.ForInStatement:
|
|
465
|
-
this.emitForOf(statement as ForOfStatement | ForInStatement);
|
|
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;
|
|
473
|
-
case SyntaxKind.ReturnStatement:
|
|
474
|
-
this.emitReturn(statement as ReturnStatement);
|
|
475
|
-
return;
|
|
476
|
-
case SyntaxKind.BreakStatement: {
|
|
477
|
-
const loop = this.current.loops[this.current.loops.length - 1];
|
|
478
|
-
if (loop) {
|
|
479
|
-
this.popTryFramesTo(loop.tryDepth ?? 0);
|
|
480
|
-
this.terminate(`br label %${loop.breakLabel}`);
|
|
481
|
-
}
|
|
482
|
-
return;
|
|
483
|
-
}
|
|
484
|
-
case SyntaxKind.ContinueStatement: {
|
|
485
|
-
const loop = this.current.loops[this.current.loops.length - 1];
|
|
486
|
-
if (loop) {
|
|
487
|
-
this.popTryFramesTo(loop.tryDepth ?? 0);
|
|
488
|
-
this.terminate(`br label %${loop.continueLabel}`);
|
|
489
|
-
}
|
|
490
|
-
return;
|
|
491
|
-
}
|
|
492
|
-
case SyntaxKind.ThrowStatement: {
|
|
493
|
-
const value = this.emitExpression((statement as ThrowStatement).expression);
|
|
494
|
-
this.emit(` call void @xt_throw(i64 ${value})`);
|
|
495
|
-
this.terminate("unreachable");
|
|
496
|
-
return;
|
|
497
|
-
}
|
|
498
|
-
case SyntaxKind.ExportDeclaration: {
|
|
499
|
-
const declaration = (statement as { declaration?: Statement }).declaration;
|
|
500
|
-
if (declaration) this.emitStatement(declaration);
|
|
501
|
-
return;
|
|
502
|
-
}
|
|
503
|
-
default:
|
|
504
|
-
this.unsupported(statement, "statement");
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
private emitVariableStatement(statement: VariableStatement): void {
|
|
509
|
-
for (const declaration of statement.declarationList.declarations) {
|
|
510
|
-
const symbol = this.binding.symbolOfDeclaration.get(declaration);
|
|
511
|
-
if (!symbol) continue;
|
|
512
|
-
if (this.current.slots.has(symbol.id)) continue; // hoisted `var`
|
|
513
|
-
const initial = declaration.initializer
|
|
514
|
-
? this.emitExpression(declaration.initializer)
|
|
515
|
-
: i64(XT_UNDEFINED);
|
|
516
|
-
this.declareSlot(symbol, initial);
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
private emitIf(statement: IfStatement): void {
|
|
521
|
-
const thenLabel = this.label("if.then");
|
|
522
|
-
const elseLabel = statement.elseStatement ? this.label("if.else") : undefined;
|
|
523
|
-
const endLabel = this.label("if.end");
|
|
524
|
-
const condition = this.emitExpression(statement.condition);
|
|
525
|
-
const truthy = this.reg();
|
|
526
|
-
this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${condition})`);
|
|
527
|
-
const nonzero = this.reg();
|
|
528
|
-
this.emit(` ${nonzero} = icmp ne i32 ${truthy}, 0`);
|
|
529
|
-
this.terminate(`br i1 ${nonzero}, label %${thenLabel}, label %${elseLabel ?? endLabel}`);
|
|
530
|
-
|
|
531
|
-
this.startBlock(thenLabel);
|
|
532
|
-
this.emitStatement(statement.thenStatement);
|
|
533
|
-
if (!this.current.terminated) this.terminate(`br label %${endLabel}`);
|
|
534
|
-
|
|
535
|
-
if (elseLabel) {
|
|
536
|
-
this.startBlock(elseLabel);
|
|
537
|
-
this.emitStatement(statement.elseStatement!);
|
|
538
|
-
if (!this.current.terminated) this.terminate(`br label %${endLabel}`);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
this.startBlock(endLabel);
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
private emitWhile(statement: WhileStatement): void {
|
|
545
|
-
const condLabel = this.label("while.cond");
|
|
546
|
-
const bodyLabel = this.label("while.body");
|
|
547
|
-
const endLabel = this.label("while.end");
|
|
548
|
-
this.terminate(`br label %${condLabel}`);
|
|
549
|
-
this.startBlock(condLabel);
|
|
550
|
-
const condition = this.emitExpression(statement.condition);
|
|
551
|
-
const truthy = this.reg();
|
|
552
|
-
this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${condition})`);
|
|
553
|
-
const nonzero = this.reg();
|
|
554
|
-
this.emit(` ${nonzero} = icmp ne i32 ${truthy}, 0`);
|
|
555
|
-
this.terminate(`br i1 ${nonzero}, label %${bodyLabel}, label %${endLabel}`);
|
|
556
|
-
|
|
557
|
-
this.startBlock(bodyLabel);
|
|
558
|
-
this.current.loops.push({ breakLabel: endLabel, continueLabel: condLabel, tryDepth: this.current.tryFrames.length });
|
|
559
|
-
this.emitStatement(statement.statement);
|
|
560
|
-
this.current.loops.pop();
|
|
561
|
-
if (!this.current.terminated) this.terminate(`br label %${condLabel}`);
|
|
562
|
-
this.startBlock(endLabel);
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
private emitDo(statement: DoStatement): void {
|
|
566
|
-
const bodyLabel = this.label("do.body");
|
|
567
|
-
const condLabel = this.label("do.cond");
|
|
568
|
-
const endLabel = this.label("do.end");
|
|
569
|
-
this.terminate(`br label %${bodyLabel}`);
|
|
570
|
-
this.startBlock(bodyLabel);
|
|
571
|
-
this.current.loops.push({ breakLabel: endLabel, continueLabel: condLabel, tryDepth: this.current.tryFrames.length });
|
|
572
|
-
this.emitStatement(statement.statement);
|
|
573
|
-
this.current.loops.pop();
|
|
574
|
-
if (!this.current.terminated) this.terminate(`br label %${condLabel}`);
|
|
575
|
-
|
|
576
|
-
this.startBlock(condLabel);
|
|
577
|
-
const condition = this.emitExpression(statement.condition);
|
|
578
|
-
const truthy = this.reg();
|
|
579
|
-
this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${condition})`);
|
|
580
|
-
const nonzero = this.reg();
|
|
581
|
-
this.emit(` ${nonzero} = icmp ne i32 ${truthy}, 0`);
|
|
582
|
-
this.terminate(`br i1 ${nonzero}, label %${bodyLabel}, label %${endLabel}`);
|
|
583
|
-
this.startBlock(endLabel);
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
private emitFor(statement: ForStatement): void {
|
|
587
|
-
if (statement.initializer && statement.initializer.kind === SyntaxKind.VariableDeclarationList) {
|
|
588
|
-
this.emitVariableStatement({
|
|
589
|
-
kind: SyntaxKind.VariableStatement,
|
|
590
|
-
declarationList: statement.initializer as VariableDeclarationList,
|
|
591
|
-
modifiers: [],
|
|
592
|
-
start: statement.initializer.start,
|
|
593
|
-
end: statement.initializer.end,
|
|
594
|
-
});
|
|
595
|
-
} else if (statement.initializer) {
|
|
596
|
-
this.emitExpression(statement.initializer as Expression);
|
|
597
|
-
}
|
|
598
|
-
const condLabel = this.label("for.cond");
|
|
599
|
-
const bodyLabel = this.label("for.body");
|
|
600
|
-
const updateLabel = this.label("for.update");
|
|
601
|
-
const endLabel = this.label("for.end");
|
|
602
|
-
this.terminate(`br label %${condLabel}`);
|
|
603
|
-
|
|
604
|
-
this.startBlock(condLabel);
|
|
605
|
-
if (statement.condition) {
|
|
606
|
-
const condition = this.emitExpression(statement.condition);
|
|
607
|
-
const truthy = this.reg();
|
|
608
|
-
this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${condition})`);
|
|
609
|
-
const nonzero = this.reg();
|
|
610
|
-
this.emit(` ${nonzero} = icmp ne i32 ${truthy}, 0`);
|
|
611
|
-
this.terminate(`br i1 ${nonzero}, label %${bodyLabel}, label %${endLabel}`);
|
|
612
|
-
} else {
|
|
613
|
-
this.terminate(`br label %${bodyLabel}`);
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
this.startBlock(bodyLabel);
|
|
617
|
-
this.current.loops.push({ breakLabel: endLabel, continueLabel: updateLabel, tryDepth: this.current.tryFrames.length });
|
|
618
|
-
this.emitStatement(statement.statement);
|
|
619
|
-
this.current.loops.pop();
|
|
620
|
-
if (!this.current.terminated) this.terminate(`br label %${updateLabel}`);
|
|
621
|
-
|
|
622
|
-
this.startBlock(updateLabel);
|
|
623
|
-
if (statement.incrementor) this.emitExpression(statement.incrementor);
|
|
624
|
-
this.terminate(`br label %${condLabel}`);
|
|
625
|
-
this.startBlock(endLabel);
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
/** `for (const x of xs)` / `for (const k in obj)` over arrays, strings and objects. */
|
|
629
|
-
private emitForOf(statement: ForOfStatement | ForInStatement): void {
|
|
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;
|
|
635
|
-
const indexPtr = this.alloca();
|
|
636
|
-
this.emit(` store i64 ${numberLiteral(0)}, i64* ${indexPtr}`);
|
|
637
|
-
const lengthValue = this.reg();
|
|
638
|
-
this.emit(` ${lengthValue} = call i64 @xt_array_length(i64 ${iterable})`);
|
|
639
|
-
|
|
640
|
-
const condLabel = this.label("forof.cond");
|
|
641
|
-
const bodyLabel = this.label("forof.body");
|
|
642
|
-
const updateLabel = this.label("forof.update");
|
|
643
|
-
const endLabel = this.label("forof.end");
|
|
644
|
-
this.terminate(`br label %${condLabel}`);
|
|
645
|
-
|
|
646
|
-
this.startBlock(condLabel);
|
|
647
|
-
const index = this.reg();
|
|
648
|
-
this.emit(` ${index} = load i64, i64* ${indexPtr}`);
|
|
649
|
-
const inRange = this.reg();
|
|
650
|
-
this.emit(` ${inRange} = call i64 @xt_lt(i64 ${index}, i64 ${lengthValue})`);
|
|
651
|
-
const truthy = this.reg();
|
|
652
|
-
this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${inRange})`);
|
|
653
|
-
const nonzero = this.reg();
|
|
654
|
-
this.emit(` ${nonzero} = icmp ne i32 ${truthy}, 0`);
|
|
655
|
-
this.terminate(`br i1 ${nonzero}, label %${bodyLabel}, label %${endLabel}`);
|
|
656
|
-
|
|
657
|
-
this.startBlock(bodyLabel);
|
|
658
|
-
const element = this.reg();
|
|
659
|
-
this.emit(` ${element} = call i64 @xt_get(i64 ${iterable}, i64 ${index})`);
|
|
660
|
-
this.bindLoopVariable(statement.initializer, element);
|
|
661
|
-
this.current.loops.push({ breakLabel: endLabel, continueLabel: updateLabel, tryDepth: this.current.tryFrames.length });
|
|
662
|
-
this.emitStatement(statement.statement);
|
|
663
|
-
this.current.loops.pop();
|
|
664
|
-
if (!this.current.terminated) this.terminate(`br label %${updateLabel}`);
|
|
665
|
-
|
|
666
|
-
this.startBlock(updateLabel);
|
|
667
|
-
const next = this.reg();
|
|
668
|
-
this.emit(` ${next} = call i64 @xt_add(i64 ${index}, i64 ${numberLiteral(1)})`);
|
|
669
|
-
this.emit(` store i64 ${next}, i64* ${indexPtr}`);
|
|
670
|
-
this.terminate(`br label %${condLabel}`);
|
|
671
|
-
this.startBlock(endLabel);
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
private bindLoopVariable(initializer: VariableDeclarationList | Expression, value: string): void {
|
|
675
|
-
if (initializer.kind === SyntaxKind.VariableDeclarationList) {
|
|
676
|
-
const declaration = initializer.declarations[0];
|
|
677
|
-
if (!declaration) return;
|
|
678
|
-
const symbol = this.binding.symbolOfDeclaration.get(declaration);
|
|
679
|
-
if (symbol) {
|
|
680
|
-
if (this.current.slots.has(symbol.id)) this.writeSlot(symbol, value);
|
|
681
|
-
else this.declareSlot(symbol, value);
|
|
682
|
-
}
|
|
683
|
-
return;
|
|
684
|
-
}
|
|
685
|
-
this.emitAssignmentTarget(initializer as Expression, value);
|
|
686
|
-
}
|
|
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
|
-
|
|
840
|
-
private emitReturn(statement: ReturnStatement): void {
|
|
841
|
-
const value = statement.expression ? this.emitExpression(statement.expression) : i64(XT_UNDEFINED);
|
|
842
|
-
this.popTryFramesTo(0);
|
|
843
|
-
this.terminate(`ret i64 ${value}`);
|
|
844
|
-
}
|
|
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
|
-
|
|
856
|
-
// -- expressions ---------------------------------------------------------
|
|
857
|
-
|
|
858
|
-
private emitExpression(node: Expression): string {
|
|
859
|
-
switch (node.kind) {
|
|
860
|
-
case SyntaxKind.Identifier:
|
|
861
|
-
return this.emitIdentifier(node as Identifier);
|
|
862
|
-
case SyntaxKind.NumericLiteral: {
|
|
863
|
-
const value = (node as { value: number }).value;
|
|
864
|
-
return numberLiteral(value);
|
|
865
|
-
}
|
|
866
|
-
case SyntaxKind.BigIntLiteral:
|
|
867
|
-
return numberLiteral(Number((node as { value: bigint }).value));
|
|
868
|
-
case SyntaxKind.StringLiteral:
|
|
869
|
-
return this.stringValue((node as { value: string }).value);
|
|
870
|
-
case SyntaxKind.NoSubstitutionTemplateLiteral:
|
|
871
|
-
return this.stringValue((node as { value: string }).value);
|
|
872
|
-
case SyntaxKind.TemplateLiteral:
|
|
873
|
-
return this.emitTemplate(node as TemplateLiteral);
|
|
874
|
-
case SyntaxKind.TrueKeyword:
|
|
875
|
-
return i64(XT_TRUE);
|
|
876
|
-
case SyntaxKind.FalseKeyword:
|
|
877
|
-
return i64(XT_FALSE);
|
|
878
|
-
case SyntaxKind.NullKeyword:
|
|
879
|
-
return i64(XT_NULL);
|
|
880
|
-
case SyntaxKind.UndefinedKeyword:
|
|
881
|
-
return i64(XT_UNDEFINED);
|
|
882
|
-
case SyntaxKind.ParenthesizedExpression:
|
|
883
|
-
return this.emitExpression((node as { expression: Expression }).expression);
|
|
884
|
-
case SyntaxKind.AsExpression:
|
|
885
|
-
case SyntaxKind.SatisfiesExpression:
|
|
886
|
-
case SyntaxKind.NonNullExpression:
|
|
887
|
-
return this.emitExpression((node as { expression: Expression }).expression);
|
|
888
|
-
case SyntaxKind.DeleteExpression:
|
|
889
|
-
return this.emitDelete(node as DeleteExpression);
|
|
890
|
-
case SyntaxKind.BinaryExpression:
|
|
891
|
-
return this.emitBinaryOrAssignment(node as BinaryExpression);
|
|
892
|
-
case SyntaxKind.PrefixUnaryExpression:
|
|
893
|
-
return this.emitPrefix(node as PrefixUnaryExpression);
|
|
894
|
-
case SyntaxKind.PostfixUnaryExpression:
|
|
895
|
-
return this.emitPostfix(node as PostfixUnaryExpression);
|
|
896
|
-
case SyntaxKind.ConditionalExpression:
|
|
897
|
-
return this.emitConditional(node as ConditionalExpression);
|
|
898
|
-
case SyntaxKind.CallExpression:
|
|
899
|
-
return this.emitCall(node as CallExpression);
|
|
900
|
-
case SyntaxKind.PropertyAccessExpression:
|
|
901
|
-
return this.emitPropertyAccess(node as PropertyAccessExpression);
|
|
902
|
-
case SyntaxKind.ElementAccessExpression:
|
|
903
|
-
return this.emitElementAccess(node as ElementAccessExpression);
|
|
904
|
-
case SyntaxKind.ArrayLiteralExpression:
|
|
905
|
-
return this.emitArrayLiteral(node as ArrayLiteralExpression);
|
|
906
|
-
case SyntaxKind.ObjectLiteralExpression:
|
|
907
|
-
return this.emitObjectLiteral(node as ObjectLiteralExpression);
|
|
908
|
-
case SyntaxKind.ArrowFunction:
|
|
909
|
-
case SyntaxKind.FunctionExpression:
|
|
910
|
-
return this.emitClosure(node as ArrowFunction | FunctionExpression);
|
|
911
|
-
default:
|
|
912
|
-
this.unsupported(node, "expression");
|
|
913
|
-
return i64(XT_UNDEFINED);
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
private emitIdentifier(identifier: Identifier): string {
|
|
918
|
-
const symbol = this.binding.symbolOfIdentifier.get(identifier);
|
|
919
|
-
if (symbol) {
|
|
920
|
-
if (symbol.kind === SymbolKind.Function && !this.current.slots.has(symbol.id)) {
|
|
921
|
-
return this.emitFunctionValue(symbol);
|
|
922
|
-
}
|
|
923
|
-
return this.readSlot(symbol);
|
|
924
|
-
}
|
|
925
|
-
switch (identifier.text) {
|
|
926
|
-
case "undefined":
|
|
927
|
-
return i64(XT_UNDEFINED);
|
|
928
|
-
case "NaN":
|
|
929
|
-
return numberLiteral(NaN);
|
|
930
|
-
case "Infinity":
|
|
931
|
-
return numberLiteral(Infinity);
|
|
932
|
-
case "console":
|
|
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
|
-
}
|
|
943
|
-
default:
|
|
944
|
-
this.diagnostics.error(
|
|
945
|
-
DiagnosticCode.CannotFindName,
|
|
946
|
-
`Cannot find name '${identifier.text}'`,
|
|
947
|
-
identifier,
|
|
948
|
-
this.sourceFile.fileName,
|
|
949
|
-
);
|
|
950
|
-
return i64(XT_UNDEFINED);
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
private emitFunctionValue(symbol: SymbolInfo): string {
|
|
955
|
-
const declaration = symbol.declarations[0];
|
|
956
|
-
const fn = declaration ? this.binding.functionOfNode.get(declaration) : undefined;
|
|
957
|
-
if (!fn) return i64(XT_UNDEFINED);
|
|
958
|
-
const cast = this.reg();
|
|
959
|
-
this.emit(` ${cast} = bitcast i64 (i64, i32, i64*)* @${this.functionName(fn)} to i8*`);
|
|
960
|
-
const closure = this.reg();
|
|
961
|
-
this.emit(` ${closure} = call i64 @xt_closure_new(i8* ${cast}, i32 0, i64* null)`);
|
|
962
|
-
return closure;
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
private emitTemplate(node: TemplateLiteral): string {
|
|
966
|
-
let accumulator = this.stringValue(node.head);
|
|
967
|
-
for (const span of node.spans) {
|
|
968
|
-
const expression = this.emitExpression(span.expression);
|
|
969
|
-
const text = this.reg();
|
|
970
|
-
this.emit(` ${text} = call i64 @xt_to_string(i64 ${expression})`);
|
|
971
|
-
const joined = this.reg();
|
|
972
|
-
this.emit(` ${joined} = call i64 @xt_add(i64 ${accumulator}, i64 ${text})`);
|
|
973
|
-
accumulator = joined;
|
|
974
|
-
if (span.literal.length > 0) {
|
|
975
|
-
const literal = this.stringValue(span.literal);
|
|
976
|
-
const withLiteral = this.reg();
|
|
977
|
-
this.emit(` ${withLiteral} = call i64 @xt_add(i64 ${accumulator}, i64 ${literal})`);
|
|
978
|
-
accumulator = withLiteral;
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
return accumulator;
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
private emitBinaryOrAssignment(node: BinaryExpression): string {
|
|
985
|
-
const operator = node.operator as string;
|
|
986
|
-
if (isAssignmentOperator(operator)) {
|
|
987
|
-
return this.emitAssignment(node);
|
|
988
|
-
}
|
|
989
|
-
switch (operator) {
|
|
990
|
-
case BinaryOperator.AmpersandAmpersand:
|
|
991
|
-
return this.emitShortCircuit(node, "and");
|
|
992
|
-
case BinaryOperator.BarBar:
|
|
993
|
-
return this.emitShortCircuit(node, "or");
|
|
994
|
-
case BinaryOperator.QuestionQuestion:
|
|
995
|
-
return this.emitShortCircuit(node, "nullish");
|
|
996
|
-
case BinaryOperator.Comma: {
|
|
997
|
-
this.emitExpression(node.left);
|
|
998
|
-
return this.emitExpression(node.right);
|
|
999
|
-
}
|
|
1000
|
-
default:
|
|
1001
|
-
break;
|
|
1002
|
-
}
|
|
1003
|
-
const left = this.emitExpression(node.left);
|
|
1004
|
-
const right = this.emitExpression(node.right);
|
|
1005
|
-
const runtime = BINARY_RUNTIME[operator];
|
|
1006
|
-
if (!runtime) {
|
|
1007
|
-
this.unsupported(node, `operator '${operator}'`);
|
|
1008
|
-
return i64(XT_UNDEFINED);
|
|
1009
|
-
}
|
|
1010
|
-
const result = this.reg();
|
|
1011
|
-
this.emit(` ${result} = call i64 @${runtime}(i64 ${left}, i64 ${right})`);
|
|
1012
|
-
return result;
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
private emitShortCircuit(node: BinaryExpression, kind: "and" | "or" | "nullish"): string {
|
|
1016
|
-
const result = this.alloca();
|
|
1017
|
-
const left = this.emitExpression(node.left);
|
|
1018
|
-
this.emit(` store i64 ${left}, i64* ${result}`);
|
|
1019
|
-
const rightLabel = this.label("sc.right");
|
|
1020
|
-
const endLabel = this.label("sc.end");
|
|
1021
|
-
let condition: string;
|
|
1022
|
-
if (kind === "and") {
|
|
1023
|
-
const truthy = this.reg();
|
|
1024
|
-
this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${left})`);
|
|
1025
|
-
condition = this.reg();
|
|
1026
|
-
this.emit(` ${condition} = icmp ne i32 ${truthy}, 0`);
|
|
1027
|
-
} else if (kind === "or") {
|
|
1028
|
-
const truthy = this.reg();
|
|
1029
|
-
this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${left})`);
|
|
1030
|
-
const isTruthy = this.reg();
|
|
1031
|
-
this.emit(` ${isTruthy} = icmp ne i32 ${truthy}, 0`);
|
|
1032
|
-
condition = this.reg();
|
|
1033
|
-
this.emit(` ${condition} = xor i1 ${isTruthy}, true`);
|
|
1034
|
-
} else {
|
|
1035
|
-
const nullish = this.reg();
|
|
1036
|
-
this.emit(` ${nullish} = call i64 @xt_is_nullish(i64 ${left})`);
|
|
1037
|
-
const truthy = this.reg();
|
|
1038
|
-
this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${nullish})`);
|
|
1039
|
-
condition = this.reg();
|
|
1040
|
-
this.emit(` ${condition} = icmp ne i32 ${truthy}, 0`);
|
|
1041
|
-
}
|
|
1042
|
-
this.terminate(`br i1 ${condition}, label %${rightLabel}, label %${endLabel}`);
|
|
1043
|
-
this.startBlock(rightLabel);
|
|
1044
|
-
const right = this.emitExpression(node.right);
|
|
1045
|
-
this.emit(` store i64 ${right}, i64* ${result}`);
|
|
1046
|
-
this.terminate(`br label %${endLabel}`);
|
|
1047
|
-
this.startBlock(endLabel);
|
|
1048
|
-
const value = this.reg();
|
|
1049
|
-
this.emit(` ${value} = load i64, i64* ${result}`);
|
|
1050
|
-
return value;
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
private emitConditional(node: ConditionalExpression): string {
|
|
1054
|
-
const result = this.alloca();
|
|
1055
|
-
const condition = this.emitExpression(node.condition);
|
|
1056
|
-
const truthy = this.reg();
|
|
1057
|
-
this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${condition})`);
|
|
1058
|
-
const nonzero = this.reg();
|
|
1059
|
-
this.emit(` ${nonzero} = icmp ne i32 ${truthy}, 0`);
|
|
1060
|
-
const trueLabel = this.label("cond.true");
|
|
1061
|
-
const falseLabel = this.label("cond.false");
|
|
1062
|
-
const endLabel = this.label("cond.end");
|
|
1063
|
-
this.terminate(`br i1 ${nonzero}, label %${trueLabel}, label %${falseLabel}`);
|
|
1064
|
-
this.startBlock(trueLabel);
|
|
1065
|
-
const whenTrue = this.emitExpression(node.whenTrue);
|
|
1066
|
-
this.emit(` store i64 ${whenTrue}, i64* ${result}`);
|
|
1067
|
-
this.terminate(`br label %${endLabel}`);
|
|
1068
|
-
this.startBlock(falseLabel);
|
|
1069
|
-
const whenFalse = this.emitExpression(node.whenFalse);
|
|
1070
|
-
this.emit(` store i64 ${whenFalse}, i64* ${result}`);
|
|
1071
|
-
this.terminate(`br label %${endLabel}`);
|
|
1072
|
-
this.startBlock(endLabel);
|
|
1073
|
-
const value = this.reg();
|
|
1074
|
-
this.emit(` ${value} = load i64, i64* ${result}`);
|
|
1075
|
-
return value;
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
private emitPrefix(node: PrefixUnaryExpression): string {
|
|
1079
|
-
switch (node.operator) {
|
|
1080
|
-
case PrefixUnaryOperator.Minus: {
|
|
1081
|
-
const operand = this.emitExpression(node.operand);
|
|
1082
|
-
const result = this.reg();
|
|
1083
|
-
this.emit(` ${result} = call i64 @xt_neg(i64 ${operand})`);
|
|
1084
|
-
return result;
|
|
1085
|
-
}
|
|
1086
|
-
case PrefixUnaryOperator.Plus: {
|
|
1087
|
-
const operand = this.emitExpression(node.operand);
|
|
1088
|
-
const result = this.reg();
|
|
1089
|
-
this.emit(` ${result} = call i64 @xt_pos(i64 ${operand})`);
|
|
1090
|
-
return result;
|
|
1091
|
-
}
|
|
1092
|
-
case PrefixUnaryOperator.Exclamation: {
|
|
1093
|
-
const operand = this.emitExpression(node.operand);
|
|
1094
|
-
const result = this.reg();
|
|
1095
|
-
this.emit(` ${result} = call i64 @xt_not(i64 ${operand})`);
|
|
1096
|
-
return result;
|
|
1097
|
-
}
|
|
1098
|
-
case PrefixUnaryOperator.Tilde: {
|
|
1099
|
-
const operand = this.emitExpression(node.operand);
|
|
1100
|
-
const result = this.reg();
|
|
1101
|
-
this.emit(` ${result} = call i64 @xt_bit_not(i64 ${operand})`);
|
|
1102
|
-
return result;
|
|
1103
|
-
}
|
|
1104
|
-
case PrefixUnaryOperator.TypeOf: {
|
|
1105
|
-
const operand = this.emitExpression(node.operand);
|
|
1106
|
-
const result = this.reg();
|
|
1107
|
-
this.emit(` ${result} = call i64 @xt_typeof(i64 ${operand})`);
|
|
1108
|
-
return result;
|
|
1109
|
-
}
|
|
1110
|
-
case PrefixUnaryOperator.Void:
|
|
1111
|
-
if (node.operand) this.emitExpression(node.operand);
|
|
1112
|
-
return i64(XT_UNDEFINED);
|
|
1113
|
-
case PrefixUnaryOperator.PlusPlus:
|
|
1114
|
-
case PrefixUnaryOperator.MinusMinus: {
|
|
1115
|
-
const one = numberLiteral(1);
|
|
1116
|
-
const current = this.emitExpression(node.operand);
|
|
1117
|
-
const fn = node.operator === PrefixUnaryOperator.PlusPlus ? "xt_add" : "xt_sub";
|
|
1118
|
-
const next = this.reg();
|
|
1119
|
-
this.emit(` ${next} = call i64 @${fn}(i64 ${current}, i64 ${one})`);
|
|
1120
|
-
this.emitAssignmentTarget(node.operand, next);
|
|
1121
|
-
return next;
|
|
1122
|
-
}
|
|
1123
|
-
default:
|
|
1124
|
-
this.unsupported(node, `unary operator '${node.operator}'`);
|
|
1125
|
-
return i64(XT_UNDEFINED);
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
private emitPostfix(node: PostfixUnaryExpression): string {
|
|
1130
|
-
const one = numberLiteral(1);
|
|
1131
|
-
const current = this.emitExpression(node.operand);
|
|
1132
|
-
const fn = node.operator === PostfixUnaryOperator.PlusPlus ? "xt_add" : "xt_sub";
|
|
1133
|
-
const next = this.reg();
|
|
1134
|
-
this.emit(` ${next} = call i64 @${fn}(i64 ${current}, i64 ${one})`);
|
|
1135
|
-
this.emitAssignmentTarget(node.operand, next);
|
|
1136
|
-
return current;
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
private emitAssignment(node: BinaryExpression): string {
|
|
1140
|
-
const operator = node.operator as unknown as AssignmentOperator;
|
|
1141
|
-
const target = node.left;
|
|
1142
|
-
let value: string;
|
|
1143
|
-
if (operator === AssignmentOperator.Assign) {
|
|
1144
|
-
value = this.emitExpression(node.right);
|
|
1145
|
-
} else if (
|
|
1146
|
-
operator === AssignmentOperator.AmpersandAmpersandAssign ||
|
|
1147
|
-
operator === AssignmentOperator.BarBarAssign ||
|
|
1148
|
-
operator === AssignmentOperator.QuestionQuestionAssign
|
|
1149
|
-
) {
|
|
1150
|
-
value = this.emitLogicalAssignment(node);
|
|
1151
|
-
} else {
|
|
1152
|
-
const current = this.emitExpression(target);
|
|
1153
|
-
const right = this.emitExpression(node.right);
|
|
1154
|
-
const binaryOperator = compoundToBinary(operator);
|
|
1155
|
-
const runtime = BINARY_RUNTIME[binaryOperator];
|
|
1156
|
-
if (!runtime) {
|
|
1157
|
-
this.unsupported(node, `assignment operator '${operator}'`);
|
|
1158
|
-
return i64(XT_UNDEFINED);
|
|
1159
|
-
}
|
|
1160
|
-
const result = this.reg();
|
|
1161
|
-
this.emit(` ${result} = call i64 @${runtime}(i64 ${current}, i64 ${right})`);
|
|
1162
|
-
value = result;
|
|
1163
|
-
}
|
|
1164
|
-
this.emitAssignmentTarget(target, value);
|
|
1165
|
-
return value;
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
private emitLogicalAssignment(node: BinaryExpression): string {
|
|
1169
|
-
const operator = node.operator as unknown as AssignmentOperator;
|
|
1170
|
-
const target = node.left;
|
|
1171
|
-
const current = this.emitExpression(target);
|
|
1172
|
-
const result = this.alloca();
|
|
1173
|
-
this.emit(` store i64 ${current}, i64* ${result}`);
|
|
1174
|
-
const rightLabel = this.label("assign.right");
|
|
1175
|
-
const endLabel = this.label("assign.end");
|
|
1176
|
-
let condition: string;
|
|
1177
|
-
if (operator === AssignmentOperator.AmpersandAmpersandAssign) {
|
|
1178
|
-
const truthy = this.reg();
|
|
1179
|
-
this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${current})`);
|
|
1180
|
-
condition = this.reg();
|
|
1181
|
-
this.emit(` ${condition} = icmp ne i32 ${truthy}, 0`);
|
|
1182
|
-
} else if (operator === AssignmentOperator.BarBarAssign) {
|
|
1183
|
-
const truthy = this.reg();
|
|
1184
|
-
this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${current})`);
|
|
1185
|
-
const isTruthy = this.reg();
|
|
1186
|
-
this.emit(` ${isTruthy} = icmp ne i32 ${truthy}, 0`);
|
|
1187
|
-
condition = this.reg();
|
|
1188
|
-
this.emit(` ${condition} = xor i1 ${isTruthy}, true`);
|
|
1189
|
-
} else {
|
|
1190
|
-
const nullish = this.reg();
|
|
1191
|
-
this.emit(` ${nullish} = call i64 @xt_is_nullish(i64 ${current})`);
|
|
1192
|
-
const truthy = this.reg();
|
|
1193
|
-
this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${nullish})`);
|
|
1194
|
-
condition = this.reg();
|
|
1195
|
-
this.emit(` ${condition} = icmp ne i32 ${truthy}, 0`);
|
|
1196
|
-
}
|
|
1197
|
-
this.terminate(`br i1 ${condition}, label %${rightLabel}, label %${endLabel}`);
|
|
1198
|
-
this.startBlock(rightLabel);
|
|
1199
|
-
const right = this.emitExpression(node.right);
|
|
1200
|
-
this.emit(` store i64 ${right}, i64* ${result}`);
|
|
1201
|
-
this.terminate(`br label %${endLabel}`);
|
|
1202
|
-
this.startBlock(endLabel);
|
|
1203
|
-
const value = this.reg();
|
|
1204
|
-
this.emit(` ${value} = load i64, i64* ${result}`);
|
|
1205
|
-
this.emitAssignmentTarget(target, value);
|
|
1206
|
-
return value;
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
private emitAssignmentTarget(target: Expression, value: string): void {
|
|
1210
|
-
switch (target.kind) {
|
|
1211
|
-
case SyntaxKind.Identifier: {
|
|
1212
|
-
const symbol = this.binding.symbolOfIdentifier.get(target as Identifier);
|
|
1213
|
-
if (symbol) this.writeSlot(symbol, value);
|
|
1214
|
-
return;
|
|
1215
|
-
}
|
|
1216
|
-
case SyntaxKind.PropertyAccessExpression: {
|
|
1217
|
-
const access = target as PropertyAccessExpression;
|
|
1218
|
-
const object = this.emitExpression(access.expression);
|
|
1219
|
-
const key = this.stringValue(access.name.text);
|
|
1220
|
-
this.emit(` call i64 @xt_set(i64 ${object}, i64 ${key}, i64 ${value})`);
|
|
1221
|
-
return;
|
|
1222
|
-
}
|
|
1223
|
-
case SyntaxKind.ElementAccessExpression: {
|
|
1224
|
-
const access = target as ElementAccessExpression;
|
|
1225
|
-
const object = this.emitExpression(access.expression);
|
|
1226
|
-
const key = this.emitExpression(access.argumentExpression);
|
|
1227
|
-
this.emit(` call i64 @xt_set(i64 ${object}, i64 ${key}, i64 ${value})`);
|
|
1228
|
-
return;
|
|
1229
|
-
}
|
|
1230
|
-
case SyntaxKind.ParenthesizedExpression:
|
|
1231
|
-
this.emitAssignmentTarget((target as { expression: Expression }).expression, value);
|
|
1232
|
-
return;
|
|
1233
|
-
default:
|
|
1234
|
-
this.unsupported(target, "assignment target");
|
|
1235
|
-
}
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
|
-
private emitCall(node: CallExpression): string {
|
|
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
|
-
}
|
|
1271
|
-
if (callee.kind === SyntaxKind.PropertyAccessExpression) {
|
|
1272
|
-
const special = this.tryEmitBuiltinCall(node, callee as PropertyAccessExpression);
|
|
1273
|
-
if (special) return special;
|
|
1274
|
-
}
|
|
1275
|
-
if (callee.kind === SyntaxKind.Identifier) {
|
|
1276
|
-
const symbol = this.binding.symbolOfIdentifier.get(callee as Identifier);
|
|
1277
|
-
if (symbol && symbol.kind === SymbolKind.Function) {
|
|
1278
|
-
const declaration = symbol.declarations[0];
|
|
1279
|
-
const fn = declaration ? this.binding.functionOfNode.get(declaration) : undefined;
|
|
1280
|
-
if (fn) {
|
|
1281
|
-
const args = this.emitArguments(node.arguments);
|
|
1282
|
-
const result = this.reg();
|
|
1283
|
-
this.emit(
|
|
1284
|
-
` ${result} = call i64 @${this.functionName(fn)}(i64 ${i64(XT_UNDEFINED)}, i32 ${args.argc}, i64* ${args.ptr})`,
|
|
1285
|
-
);
|
|
1286
|
-
return result;
|
|
1287
|
-
}
|
|
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
|
-
}
|
|
1296
|
-
const builtin = this.builtins[(callee as Identifier).text];
|
|
1297
|
-
if (!symbol && builtin) {
|
|
1298
|
-
this.extraDeclarations.add(
|
|
1299
|
-
builtin.returnVoid ? `declare void @${builtin.symbol}(i32, i64*)` : `declare i64 @${builtin.symbol}(i32, i64*)`,
|
|
1300
|
-
);
|
|
1301
|
-
const args = this.emitArguments(node.arguments);
|
|
1302
|
-
if (builtin.returnVoid) {
|
|
1303
|
-
this.emit(` call void @${builtin.symbol}(i32 ${args.argc}, i64* ${args.ptr})`);
|
|
1304
|
-
return i64(XT_UNDEFINED);
|
|
1305
|
-
}
|
|
1306
|
-
const result = this.reg();
|
|
1307
|
-
this.emit(` ${result} = call i64 @${builtin.symbol}(i32 ${args.argc}, i64* ${args.ptr})`);
|
|
1308
|
-
return result;
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
const calleeValue = this.emitExpression(callee);
|
|
1312
|
-
const args = this.emitArguments(node.arguments);
|
|
1313
|
-
const result = this.reg();
|
|
1314
|
-
this.emit(` ${result} = call i64 @xt_closure_call(i64 ${calleeValue}, i32 ${args.argc}, i64* ${args.ptr})`);
|
|
1315
|
-
return result;
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
|
-
private tryEmitBuiltinCall(node: CallExpression, callee: PropertyAccessExpression): string | undefined {
|
|
1319
|
-
const target = callee.expression;
|
|
1320
|
-
const method = callee.name.text;
|
|
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);
|
|
1337
|
-
const args = this.emitArguments(node.arguments);
|
|
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;
|
|
1341
|
-
}
|
|
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") {
|
|
1350
|
-
const args = this.emitArguments(node.arguments);
|
|
1351
|
-
const result = this.reg();
|
|
1352
|
-
this.emit(` ${result} = call i64 @xt_object_assign(i32 ${args.argc}, i64* ${args.ptr})`);
|
|
1353
|
-
return result;
|
|
1354
|
-
}
|
|
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;
|
|
1367
|
-
}
|
|
1368
|
-
return undefined;
|
|
1369
|
-
}
|
|
1370
|
-
|
|
1371
|
-
private runtimeCall(name: string, args: readonly string[]): string {
|
|
1372
|
-
const result = this.reg();
|
|
1373
|
-
this.emit(` ${result} = call i64 @${name}(${args.map((a) => `i64 ${a}`).join(", ")})`);
|
|
1374
|
-
return result;
|
|
1375
|
-
}
|
|
1376
|
-
|
|
1377
|
-
private emitArguments(args: readonly Expression[]): { argc: number; ptr: string } {
|
|
1378
|
-
if (args.length === 0) return { argc: 0, ptr: "null" };
|
|
1379
|
-
const ptr = `%args${this.current.allocas.length}`;
|
|
1380
|
-
this.current.allocas.push(`${ptr} = alloca i64, i32 ${args.length}`);
|
|
1381
|
-
for (let index = 0; index < args.length; index++) {
|
|
1382
|
-
const value = this.emitExpression(args[index]!);
|
|
1383
|
-
const slot = this.reg();
|
|
1384
|
-
this.emit(` ${slot} = getelementptr i64, i64* ${ptr}, i32 ${index}`);
|
|
1385
|
-
this.emit(` store i64 ${value}, i64* ${slot}`);
|
|
1386
|
-
}
|
|
1387
|
-
return { argc: args.length, ptr };
|
|
1388
|
-
}
|
|
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
|
-
|
|
1407
|
-
private emitPropertyAccess(node: PropertyAccessExpression): string {
|
|
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]!);
|
|
1415
|
-
}
|
|
1416
|
-
const object = this.emitExpression(node.expression);
|
|
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();
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1431
|
-
private emitElementAccess(node: ElementAccessExpression): string {
|
|
1432
|
-
const object = this.emitExpression(node.expression);
|
|
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;
|
|
1466
|
-
}
|
|
1467
|
-
|
|
1468
|
-
private emitArrayLiteral(node: ArrayLiteralExpression): string {
|
|
1469
|
-
const hasSpread = node.elements.some((element) => element.kind === SyntaxKind.SpreadElement);
|
|
1470
|
-
if (!hasSpread) {
|
|
1471
|
-
const args = this.emitArguments(node.elements);
|
|
1472
|
-
const result = this.reg();
|
|
1473
|
-
this.emit(` ${result} = call i64 @xt_array_new(i32 ${args.argc}, i64* ${args.ptr})`);
|
|
1474
|
-
return result;
|
|
1475
|
-
}
|
|
1476
|
-
let array = this.runtimeCall("xt_array_new", [`0`, `null`]);
|
|
1477
|
-
for (const element of node.elements) {
|
|
1478
|
-
if (element.kind === SyntaxKind.SpreadElement) {
|
|
1479
|
-
const spread = this.emitExpression((element as { expression: Expression }).expression);
|
|
1480
|
-
array = this.runtimeCall("xt_array_spread", [array, spread]);
|
|
1481
|
-
} else {
|
|
1482
|
-
const value = this.emitExpression(element);
|
|
1483
|
-
array = this.runtimeCall("xt_array_push", [array, value]);
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
return array;
|
|
1487
|
-
}
|
|
1488
|
-
|
|
1489
|
-
private emitObjectLiteral(node: ObjectLiteralExpression): string {
|
|
1490
|
-
const object = this.reg();
|
|
1491
|
-
this.emit(` ${object} = call i64 @xt_object_new()`);
|
|
1492
|
-
for (const property of node.properties) {
|
|
1493
|
-
if (property.kind === SyntaxKind.PropertyAssignment) {
|
|
1494
|
-
const name = propertyNameText(property.name);
|
|
1495
|
-
const key = this.stringValue(name);
|
|
1496
|
-
const value = this.emitExpression(property.initializer);
|
|
1497
|
-
this.emit(` call i64 @xt_set(i64 ${object}, i64 ${key}, i64 ${value})`);
|
|
1498
|
-
} else if (property.kind === SyntaxKind.ShorthandPropertyAssignment) {
|
|
1499
|
-
const identifier = property.name;
|
|
1500
|
-
const key = this.stringValue(identifier.text);
|
|
1501
|
-
const value = this.emitIdentifier(identifier);
|
|
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})`);
|
|
1506
|
-
} else {
|
|
1507
|
-
this.unsupported(property, "object spread");
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
return object;
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
|
-
private emitClosure(node: ArrowFunction | FunctionExpression): string {
|
|
1514
|
-
const fn = this.binding.functionOfNode.get(node);
|
|
1515
|
-
if (!fn) {
|
|
1516
|
-
this.unsupported(node, "closure");
|
|
1517
|
-
return i64(XT_UNDEFINED);
|
|
1518
|
-
}
|
|
1519
|
-
let envPtr = "null";
|
|
1520
|
-
if (fn.captures.length > 0) {
|
|
1521
|
-
envPtr = `%env${this.current.allocas.length}`;
|
|
1522
|
-
this.current.allocas.push(`${envPtr} = alloca i64, i32 ${fn.captures.length}`);
|
|
1523
|
-
for (let index = 0; index < fn.captures.length; index++) {
|
|
1524
|
-
const symbol = fn.captures[index]!;
|
|
1525
|
-
// The current function must be able to read the capture: either from
|
|
1526
|
-
// its own slot (local) or from one of its own capture slots.
|
|
1527
|
-
const slot = this.current.slots.get(symbol.id);
|
|
1528
|
-
let raw: string;
|
|
1529
|
-
if (slot) {
|
|
1530
|
-
raw = this.reg();
|
|
1531
|
-
this.emit(` ${raw} = load i64, i64* ${slot.ptr}`);
|
|
1532
|
-
} else {
|
|
1533
|
-
const outer = this.binding.functionOfNode.get(node);
|
|
1534
|
-
void outer;
|
|
1535
|
-
raw = i64(XT_UNDEFINED);
|
|
1536
|
-
}
|
|
1537
|
-
const target = this.reg();
|
|
1538
|
-
this.emit(` ${target} = getelementptr i64, i64* ${envPtr}, i32 ${index}`);
|
|
1539
|
-
this.emit(` store i64 ${raw}, i64* ${target}`);
|
|
1540
|
-
}
|
|
1541
|
-
}
|
|
1542
|
-
const cast = this.reg();
|
|
1543
|
-
this.emit(` ${cast} = bitcast i64 (i64, i32, i64*)* @${this.functionName(fn)} to i8*`);
|
|
1544
|
-
const closure = this.reg();
|
|
1545
|
-
this.emit(` ${closure} = call i64 @xt_closure_new(i8* ${cast}, i32 ${fn.captures.length}, i64* ${envPtr})`);
|
|
1546
|
-
return closure;
|
|
1547
|
-
}
|
|
1548
|
-
|
|
1549
|
-
// -- string pool ---------------------------------------------------------
|
|
1550
|
-
|
|
1551
|
-
private stringValue(text: string): string {
|
|
1552
|
-
const entry = this.internString(text);
|
|
1553
|
-
const result = this.reg();
|
|
1554
|
-
this.emit(` ${result} = call i64 @xt_string_new(i8* ${entry.label}, i64 ${entry.length})`);
|
|
1555
|
-
return result;
|
|
1556
|
-
}
|
|
1557
|
-
|
|
1558
|
-
private internString(text: string): { label: string; length: number } {
|
|
1559
|
-
const existing = this.strings.get(text);
|
|
1560
|
-
if (existing) return existing;
|
|
1561
|
-
const label = `@.str.${this.stringCounter++}`;
|
|
1562
|
-
const bytes = utf8Bytes(text);
|
|
1563
|
-
const entry = { label, length: bytes.length };
|
|
1564
|
-
this.strings.set(text, entry);
|
|
1565
|
-
this.globals.push(`${label} = private unnamed_addr constant [${bytes.length + 1} x i8] c"${escapeBytes(bytes)}\\00"`);
|
|
1566
|
-
return entry;
|
|
1567
|
-
}
|
|
1568
|
-
|
|
1569
|
-
private unsupported(node: Node, what: string): void {
|
|
1570
|
-
const label = kindName(node.kind);
|
|
1571
|
-
this.diagnostics.error(
|
|
1572
|
-
DiagnosticCode.UnsupportedFeature,
|
|
1573
|
-
`xbintsc does not yet support this ${what} (${label})`,
|
|
1574
|
-
node,
|
|
1575
|
-
this.sourceFile.fileName,
|
|
1576
|
-
);
|
|
1577
|
-
}
|
|
1578
|
-
}
|
|
1579
|
-
|
|
1580
|
-
const BINARY_RUNTIME: Record<string, string | undefined> = {
|
|
1581
|
-
[BinaryOperator.Add]: "xt_add",
|
|
1582
|
-
[BinaryOperator.Subtract]: "xt_sub",
|
|
1583
|
-
[BinaryOperator.Multiply]: "xt_mul",
|
|
1584
|
-
[BinaryOperator.Divide]: "xt_div",
|
|
1585
|
-
[BinaryOperator.Remainder]: "xt_mod",
|
|
1586
|
-
[BinaryOperator.Exponent]: "xt_pow",
|
|
1587
|
-
[BinaryOperator.LessThan]: "xt_lt",
|
|
1588
|
-
[BinaryOperator.LessThanEquals]: "xt_le",
|
|
1589
|
-
[BinaryOperator.GreaterThan]: "xt_gt",
|
|
1590
|
-
[BinaryOperator.GreaterThanEquals]: "xt_ge",
|
|
1591
|
-
[BinaryOperator.EqualsEquals]: "xt_eq",
|
|
1592
|
-
[BinaryOperator.ExclamationEquals]: "xt_ne",
|
|
1593
|
-
[BinaryOperator.EqualsEqualsEquals]: "xt_seq",
|
|
1594
|
-
[BinaryOperator.ExclamationEqualsEquals]: "xt_sne",
|
|
1595
|
-
[BinaryOperator.Ampersand]: "xt_bit_and",
|
|
1596
|
-
[BinaryOperator.Bar]: "xt_bit_or",
|
|
1597
|
-
[BinaryOperator.Caret]: "xt_bit_xor",
|
|
1598
|
-
[BinaryOperator.LessThanLessThan]: "xt_shl",
|
|
1599
|
-
[BinaryOperator.GreaterThanGreaterThan]: "xt_shr",
|
|
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,
|
|
1625
|
-
};
|
|
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
|
-
|
|
1643
|
-
const ASSIGNMENT_OPERATORS = new Set<string>([
|
|
1644
|
-
"=",
|
|
1645
|
-
"+=",
|
|
1646
|
-
"-=",
|
|
1647
|
-
"*=",
|
|
1648
|
-
"/=",
|
|
1649
|
-
"%=",
|
|
1650
|
-
"**=",
|
|
1651
|
-
"<<=",
|
|
1652
|
-
">>=",
|
|
1653
|
-
">>>=",
|
|
1654
|
-
"&=",
|
|
1655
|
-
"|=",
|
|
1656
|
-
"^=",
|
|
1657
|
-
"&&=",
|
|
1658
|
-
"||=",
|
|
1659
|
-
"??=",
|
|
1660
|
-
]);
|
|
1661
|
-
|
|
1662
|
-
function isAssignmentOperator(operator: string): boolean {
|
|
1663
|
-
return ASSIGNMENT_OPERATORS.has(operator);
|
|
1664
|
-
}
|
|
1665
|
-
|
|
1666
|
-
function compoundToBinary(operator: AssignmentOperator): BinaryOperator {
|
|
1667
|
-
const text = operator.slice(0, -1);
|
|
1668
|
-
return text as BinaryOperator;
|
|
1669
|
-
}
|
|
1670
|
-
|
|
1671
|
-
function propertyNameText(name: Node): string {
|
|
1672
|
-
switch (name.kind) {
|
|
1673
|
-
case SyntaxKind.Identifier:
|
|
1674
|
-
case SyntaxKind.PrivateIdentifier:
|
|
1675
|
-
return (name as unknown as { text: string }).text;
|
|
1676
|
-
case SyntaxKind.StringLiteral:
|
|
1677
|
-
return (name as unknown as { value: string }).value;
|
|
1678
|
-
case SyntaxKind.NumericLiteral:
|
|
1679
|
-
return String((name as unknown as { value: number }).value);
|
|
1680
|
-
default:
|
|
1681
|
-
return "";
|
|
1682
|
-
}
|
|
1683
|
-
}
|
|
1684
|
-
|
|
1685
|
-
/** Human readable syntax kind for diagnostics (const enums have no reverse map). */
|
|
1686
|
-
function kindName(kind: number): string {
|
|
1687
|
-
const entry = KIND_NAMES.get(kind);
|
|
1688
|
-
return entry ?? String(kind);
|
|
1689
|
-
}
|
|
1690
|
-
|
|
1691
|
-
const KIND_NAMES = new Map<number, string>([
|
|
1692
|
-
[SyntaxKind.CallExpression, "call expression"],
|
|
1693
|
-
[SyntaxKind.NewExpression, "new expression"],
|
|
1694
|
-
[SyntaxKind.ClassDeclaration, "class declaration"],
|
|
1695
|
-
[SyntaxKind.ClassExpression, "class expression"],
|
|
1696
|
-
[SyntaxKind.EnumDeclaration, "enum declaration"],
|
|
1697
|
-
[SyntaxKind.SwitchStatement, "switch statement"],
|
|
1698
|
-
[SyntaxKind.TryStatement, "try statement"],
|
|
1699
|
-
[SyntaxKind.SpreadElement, "spread element"],
|
|
1700
|
-
[SyntaxKind.ThisKeyword, "this expression"],
|
|
1701
|
-
[SyntaxKind.RegularExpressionLiteral, "regular expression"],
|
|
1702
|
-
[SyntaxKind.TaggedTemplateExpression, "tagged template"],
|
|
1703
|
-
[SyntaxKind.DeleteExpression, "delete expression"],
|
|
1704
|
-
[SyntaxKind.AwaitExpression, "await expression"],
|
|
1705
|
-
[SyntaxKind.YieldExpression, "yield expression"],
|
|
1706
|
-
[SyntaxKind.SwitchStatement, "switch statement"],
|
|
1707
|
-
]);
|
|
1708
|
-
|
|
1709
|
-
function utf8Bytes(text: string): number[] {
|
|
1710
|
-
const bytes: number[] = [];
|
|
1711
|
-
for (const character of text) {
|
|
1712
|
-
const code = character.codePointAt(0)!;
|
|
1713
|
-
if (code < 0x80) bytes.push(code);
|
|
1714
|
-
else if (code < 0x800) {
|
|
1715
|
-
bytes.push(0xc0 | (code >> 6), 0x80 | (code & 0x3f));
|
|
1716
|
-
} else if (code < 0x10000) {
|
|
1717
|
-
bytes.push(0xe0 | (code >> 12), 0x80 | ((code >> 6) & 0x3f), 0x80 | (code & 0x3f));
|
|
1718
|
-
} else {
|
|
1719
|
-
bytes.push(
|
|
1720
|
-
0xf0 | (code >> 18),
|
|
1721
|
-
0x80 | ((code >> 12) & 0x3f),
|
|
1722
|
-
0x80 | ((code >> 6) & 0x3f),
|
|
1723
|
-
0x80 | (code & 0x3f),
|
|
1724
|
-
);
|
|
1725
|
-
}
|
|
1726
|
-
}
|
|
1727
|
-
return bytes;
|
|
1728
|
-
}
|
|
1729
|
-
|
|
1730
|
-
function escapeBytes(bytes: readonly number[]): string {
|
|
1731
|
-
return bytes
|
|
1732
|
-
.map((byte) => {
|
|
1733
|
-
if (byte === 0x22) return '\\22';
|
|
1734
|
-
if (byte === 0x5c) return '\\5C';
|
|
1735
|
-
if (byte >= 0x20 && byte < 0x7f) return String.fromCharCode(byte);
|
|
1736
|
-
return `\\${byte.toString(16).toUpperCase().padStart(2, "0")}`;
|
|
1737
|
-
})
|
|
1738
|
-
.join("");
|
|
1739
|
-
}
|
|
23
|
+
export { generate, type CodegenOptions, type CodegenResult } from "./generator/generator.js";
|