xbintsc 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/ast/declarations.d.ts +20 -1
- package/dist/src/ast/expressions.d.ts +6 -2
- package/dist/src/ast/kinds.d.ts +6 -1
- package/dist/src/ast/kinds.js +5 -0
- package/dist/src/ast/kinds.js.map +1 -1
- package/dist/src/ast/statements.d.ts +2 -1
- package/dist/src/binder/binder.js +73 -9
- package/dist/src/binder/binder.js.map +1 -1
- package/dist/src/cli/main.js +2 -4
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/codegen/generator/calls.d.ts +1 -1
- package/dist/src/codegen/generator/calls.js +57 -14
- package/dist/src/codegen/generator/calls.js.map +1 -1
- package/dist/src/codegen/generator/context.d.ts +1 -1
- package/dist/src/codegen/generator/expressions.js +39 -3
- package/dist/src/codegen/generator/expressions.js.map +1 -1
- package/dist/src/codegen/generator/module.js +73 -7
- package/dist/src/codegen/generator/module.js.map +1 -1
- package/dist/src/codegen/generator/state.js +16 -0
- package/dist/src/codegen/generator/state.js.map +1 -1
- package/dist/src/codegen/generator/statements.d.ts +4 -1
- package/dist/src/codegen/generator/statements.js +141 -10
- package/dist/src/codegen/generator/statements.js.map +1 -1
- package/dist/src/codegen/generator/tables.d.ts +3 -0
- package/dist/src/codegen/generator/tables.js +13 -0
- package/dist/src/codegen/generator/tables.js.map +1 -1
- package/dist/src/codegen/values.d.ts +18 -11
- package/dist/src/codegen/values.js +92 -15
- package/dist/src/codegen/values.js.map +1 -1
- package/dist/src/diagnostics/source.d.ts +1 -1
- package/dist/src/diagnostics/source.js +12 -7
- package/dist/src/diagnostics/source.js.map +1 -1
- package/dist/src/driver/compiler.d.ts +5 -0
- package/dist/src/driver/compiler.js +42 -2
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/driver/modules.js +29 -4
- package/dist/src/driver/modules.js.map +1 -1
- package/dist/src/extensions/node/child_process/index.d.ts +7 -0
- package/dist/src/extensions/node/child_process/index.js +19 -0
- package/dist/src/extensions/node/child_process/index.js.map +1 -0
- package/dist/src/extensions/node/crypto/index.d.ts +6 -0
- package/dist/src/extensions/node/crypto/index.js +18 -0
- package/dist/src/extensions/node/crypto/index.js.map +1 -0
- package/dist/src/extensions/node/index.js +6 -0
- package/dist/src/extensions/node/index.js.map +1 -1
- package/dist/src/extensions/node/module.d.ts +9 -1
- package/dist/src/extensions/node/module.js +14 -1
- package/dist/src/extensions/node/module.js.map +1 -1
- package/dist/src/extensions/node/url/index.d.ts +7 -0
- package/dist/src/extensions/node/url/index.js +21 -0
- package/dist/src/extensions/node/url/index.js.map +1 -0
- package/dist/src/lexer/scanner.js +4 -5
- package/dist/src/lexer/scanner.js.map +1 -1
- package/dist/src/lexer/token.d.ts +10 -0
- package/dist/src/lexer/token.js +69 -0
- package/dist/src/lexer/token.js.map +1 -1
- package/dist/src/parser/context.js +4 -16
- package/dist/src/parser/context.js.map +1 -1
- package/dist/src/parser/declarations.d.ts +2 -0
- package/dist/src/parser/declarations.js +146 -33
- package/dist/src/parser/declarations.js.map +1 -1
- package/dist/src/parser/expressions.js +20 -3
- package/dist/src/parser/expressions.js.map +1 -1
- package/dist/src/parser/helpers.js +2 -0
- package/dist/src/parser/helpers.js.map +1 -1
- package/dist/src/parser/modules.js +7 -2
- package/dist/src/parser/modules.js.map +1 -1
- package/dist/src/parser/statements.js +28 -3
- package/dist/src/parser/statements.js.map +1 -1
- package/dist/src/parser/types.js +5 -0
- package/dist/src/parser/types.js.map +1 -1
- package/dist/tests/codegen/llvm.test.js +16 -0
- package/dist/tests/codegen/llvm.test.js.map +1 -1
- package/dist/tests/driver/compiler.test.js +13 -1
- package/dist/tests/driver/compiler.test.js.map +1 -1
- package/dist/tests/e2e/compile.test.js +57 -0
- package/dist/tests/e2e/compile.test.js.map +1 -1
- package/dist/tests/parser/parser.test.js +53 -0
- package/dist/tests/parser/parser.test.js.map +1 -1
- package/package.json +1 -1
- package/runtime/ext_node/child_process/child_process.c +213 -0
- package/runtime/ext_node/crypto/crypto.c +177 -0
- package/runtime/ext_node/process/process.c +37 -3
- package/runtime/ext_node/url/url.c +85 -0
- package/runtime/rt.h +20 -0
- package/runtime/rt_internal.h +11 -0
- package/runtime/xt_builtins.c +23 -0
- package/runtime/xt_containers.c +136 -10
- package/runtime/xt_io.c +53 -0
- package/runtime/xt_regex.h +16 -6
- package/runtime/xt_stdlib.c +11 -2
- package/runtime/xt_stdlib2.c +250 -13
- package/runtime/xt_values.c +2 -1
- package/src/ast/declarations.ts +26 -1
- package/src/ast/expressions.ts +7 -2
- package/src/ast/kinds.ts +5 -0
- package/src/ast/statements.ts +2 -1
- package/src/binder/binder.ts +75 -9
- package/src/cli/main.ts +2 -3
- package/src/codegen/generator/calls.ts +59 -16
- package/src/codegen/generator/context.ts +1 -1
- package/src/codegen/generator/expressions.ts +39 -3
- package/src/codegen/generator/module.ts +79 -7
- package/src/codegen/generator/state.ts +16 -0
- package/src/codegen/generator/statements.ts +139 -11
- package/src/codegen/generator/tables.ts +14 -0
- package/src/codegen/values.ts +85 -16
- package/src/diagnostics/source.ts +12 -7
- package/src/driver/compiler.ts +44 -2
- package/src/driver/modules.ts +28 -3
- package/src/extensions/node/child_process/index.ts +24 -0
- package/src/extensions/node/crypto/index.ts +23 -0
- package/src/extensions/node/index.ts +6 -0
- package/src/extensions/node/module.ts +13 -1
- package/src/extensions/node/url/index.ts +26 -0
- package/src/lexer/scanner.ts +4 -4
- package/src/lexer/token.ts +73 -0
- package/src/parser/context.ts +4 -15
- package/src/parser/declarations.ts +140 -29
- package/src/parser/expressions.ts +20 -4
- package/src/parser/helpers.ts +2 -0
- package/src/parser/modules.ts +7 -2
- package/src/parser/statements.ts +27 -3
- package/src/parser/types.ts +5 -0
|
@@ -4,15 +4,19 @@
|
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
SyntaxKind,
|
|
7
|
+
type ArrayBindingPattern,
|
|
8
|
+
type BindingName,
|
|
7
9
|
type Block,
|
|
8
10
|
type CaseClause,
|
|
9
11
|
type DoStatement,
|
|
12
|
+
type EnumDeclaration,
|
|
10
13
|
type Expression,
|
|
11
14
|
type ForInStatement,
|
|
12
15
|
type ForOfStatement,
|
|
13
16
|
type ForStatement,
|
|
14
17
|
type Identifier,
|
|
15
18
|
type IfStatement,
|
|
19
|
+
type ObjectBindingPattern,
|
|
16
20
|
type ReturnStatement,
|
|
17
21
|
type Statement,
|
|
18
22
|
type SwitchStatement,
|
|
@@ -23,12 +27,16 @@ import {
|
|
|
23
27
|
type WhileStatement,
|
|
24
28
|
} from "../../ast/nodes.js";
|
|
25
29
|
import { i64, numberLiteral, XT_UNDEFINED } from "../values.js";
|
|
30
|
+
import { propertyNameText } from "./tables.js";
|
|
26
31
|
import type { Generator } from "./generator.js";
|
|
27
32
|
|
|
28
33
|
export interface StatementMethods {
|
|
29
34
|
emitStatements(this: Generator, statements: readonly Statement[]): void;
|
|
30
35
|
emitStatement(this: Generator, statement: Statement): void;
|
|
31
36
|
emitVariableStatement(this: Generator, statement: VariableStatement): void;
|
|
37
|
+
emitBindingPattern(this: Generator, name: BindingName, value: string): void;
|
|
38
|
+
emitBindingDefault(this: Generator, value: string, initializer: Expression): string;
|
|
39
|
+
emitEnum(this: Generator, statement: EnumDeclaration): void;
|
|
32
40
|
emitIf(this: Generator, statement: IfStatement): void;
|
|
33
41
|
emitWhile(this: Generator, statement: WhileStatement): void;
|
|
34
42
|
emitDo(this: Generator, statement: DoStatement): void;
|
|
@@ -125,6 +133,9 @@ export const statementMethods: StatementMethods = {
|
|
|
125
133
|
if (declaration) this.emitStatement(declaration);
|
|
126
134
|
return;
|
|
127
135
|
}
|
|
136
|
+
case SyntaxKind.EnumDeclaration:
|
|
137
|
+
this.emitEnum(statement as EnumDeclaration);
|
|
138
|
+
return;
|
|
128
139
|
default:
|
|
129
140
|
this.unsupported(statement, "statement");
|
|
130
141
|
}
|
|
@@ -132,16 +143,130 @@ export const statementMethods: StatementMethods = {
|
|
|
132
143
|
|
|
133
144
|
emitVariableStatement(statement: VariableStatement): void {
|
|
134
145
|
for (const declaration of statement.declarationList.declarations) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
146
|
+
if (declaration.name.kind === SyntaxKind.Identifier) {
|
|
147
|
+
const symbol = this.binding.symbolOfDeclaration.get(declaration);
|
|
148
|
+
if (!symbol) {
|
|
149
|
+
if (declaration.initializer) this.emitExpression(declaration.initializer);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (this.current.slots.has(symbol.id)) {
|
|
153
|
+
// Hoisted `var` (or an already-declared slot): keep evaluating the
|
|
154
|
+
// initializer for its side effects, but do not redeclare the slot.
|
|
155
|
+
if (declaration.initializer) this.emitExpression(declaration.initializer);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (symbol.boxed) {
|
|
159
|
+
// A captured binding lives in a box. Create the box *before*
|
|
160
|
+
// evaluating the initializer so a self-referencing closure
|
|
161
|
+
// (`const f = () => f()`) captures the box, not a copy of the
|
|
162
|
+
// still-uninitialised value.
|
|
163
|
+
this.declareSlot(symbol, i64(XT_UNDEFINED));
|
|
164
|
+
if (declaration.initializer) this.writeSlot(symbol, this.emitExpression(declaration.initializer));
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const initial = declaration.initializer ? this.emitExpression(declaration.initializer) : i64(XT_UNDEFINED);
|
|
168
|
+
this.declareSlot(symbol, initial);
|
|
169
|
+
} else {
|
|
170
|
+
const initial = declaration.initializer ? this.emitExpression(declaration.initializer) : i64(XT_UNDEFINED);
|
|
171
|
+
this.emitBindingPattern(declaration.name, initial);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
/** Lower a destructuring binding pattern, declaring each bound identifier. */
|
|
177
|
+
emitBindingPattern(name: BindingName, value: string): void {
|
|
178
|
+
if (name.kind === SyntaxKind.Identifier) {
|
|
179
|
+
const symbol = this.binding.symbolOfDeclaration.get(name);
|
|
180
|
+
if (!symbol) return;
|
|
181
|
+
if (this.current.slots.has(symbol.id)) this.writeSlot(symbol, value);
|
|
182
|
+
else this.declareSlot(symbol, value);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (name.kind === SyntaxKind.ArrayBindingPattern) {
|
|
186
|
+
const pattern = name as ArrayBindingPattern;
|
|
187
|
+
for (let index = 0; index < pattern.elements.length; index++) {
|
|
188
|
+
const element = pattern.elements[index];
|
|
189
|
+
if (!element) continue;
|
|
190
|
+
let elementValue: string;
|
|
191
|
+
if (element.dotDotDotToken) {
|
|
192
|
+
const startPtr = this.alloca();
|
|
193
|
+
this.emit(` store i64 ${numberLiteral(index)}, i64* ${startPtr}`);
|
|
194
|
+
const sliceName = this.stringValue("slice");
|
|
195
|
+
elementValue = this.reg();
|
|
196
|
+
this.emit(` ${elementValue} = call i64 @xt_call_method(i64 ${value}, i64 ${sliceName}, i32 1, i64* ${startPtr})`);
|
|
197
|
+
} else {
|
|
198
|
+
elementValue = this.reg();
|
|
199
|
+
this.emit(` ${elementValue} = call i64 @xt_get(i64 ${value}, i64 ${numberLiteral(index)})`);
|
|
200
|
+
}
|
|
201
|
+
if (element.initializer) elementValue = this.emitBindingDefault(elementValue, element.initializer);
|
|
202
|
+
this.emitBindingPattern(element.name, elementValue);
|
|
203
|
+
}
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const pattern = name as ObjectBindingPattern;
|
|
207
|
+
for (const element of pattern.elements) {
|
|
208
|
+
const key = element.propertyName ?? (element.name.kind === SyntaxKind.Identifier ? element.name : undefined);
|
|
209
|
+
if (element.dotDotDotToken) {
|
|
210
|
+
this.unsupported(element, "object rest destructuring");
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const keyValue = this.stringValue(key ? propertyNameText(key) : "undefined");
|
|
214
|
+
let elementValue = this.reg();
|
|
215
|
+
this.emit(` ${elementValue} = call i64 @xt_get(i64 ${value}, i64 ${keyValue})`);
|
|
216
|
+
if (element.initializer) elementValue = this.emitBindingDefault(elementValue, element.initializer);
|
|
217
|
+
this.emitBindingPattern(element.name, elementValue);
|
|
142
218
|
}
|
|
143
219
|
},
|
|
144
220
|
|
|
221
|
+
/** `x = fallback` in a binding pattern: only apply when `x` is `undefined`. */
|
|
222
|
+
emitBindingDefault(value: string, initializer: Expression): string {
|
|
223
|
+
const fallback = this.emitExpression(initializer);
|
|
224
|
+
const isUndefined = this.reg();
|
|
225
|
+
this.emit(` ${isUndefined} = call i64 @xt_seq(i64 ${value}, i64 ${i64(XT_UNDEFINED)})`);
|
|
226
|
+
const truthy = this.reg();
|
|
227
|
+
this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${isUndefined})`);
|
|
228
|
+
const condition = this.reg();
|
|
229
|
+
this.emit(` ${condition} = icmp ne i32 ${truthy}, 0`);
|
|
230
|
+
const result = this.reg();
|
|
231
|
+
this.emit(` ${result} = select i1 ${condition}, i64 ${fallback}, i64 ${value}`);
|
|
232
|
+
return result;
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* `enum E { A, B }` (and `const enum`) lowers to a runtime object with both
|
|
237
|
+
* forward (`E.A`) and reverse (`E[0] === "A"`) numeric mappings.
|
|
238
|
+
*/
|
|
239
|
+
emitEnum(statement: EnumDeclaration): void {
|
|
240
|
+
const symbol = this.binding.symbolOfDeclaration.get(statement);
|
|
241
|
+
const object = this.reg();
|
|
242
|
+
this.emit(` ${object} = call i64 @xt_object_new()`);
|
|
243
|
+
let next = 0;
|
|
244
|
+
for (const member of statement.members) {
|
|
245
|
+
const name = propertyNameText(member.name);
|
|
246
|
+
const key = this.stringValue(name);
|
|
247
|
+
let value: string;
|
|
248
|
+
if (member.initializer) {
|
|
249
|
+
value = this.emitExpression(member.initializer);
|
|
250
|
+
if (member.initializer.kind === SyntaxKind.NumericLiteral) {
|
|
251
|
+
const numeric = (member.initializer as { value?: number }).value ?? 0;
|
|
252
|
+
next = numeric + 1;
|
|
253
|
+
} else {
|
|
254
|
+
next++;
|
|
255
|
+
}
|
|
256
|
+
} else {
|
|
257
|
+
value = numberLiteral(next);
|
|
258
|
+
next++;
|
|
259
|
+
}
|
|
260
|
+
this.emit(` call i64 @xt_set(i64 ${object}, i64 ${key}, i64 ${value})`);
|
|
261
|
+
// Reverse mapping (only meaningful for numeric members).
|
|
262
|
+
if (!member.initializer || member.initializer.kind === SyntaxKind.NumericLiteral) {
|
|
263
|
+
const reverseKey = this.stringValue(name);
|
|
264
|
+
this.emit(` call i64 @xt_set(i64 ${object}, i64 ${value}, i64 ${reverseKey})`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (symbol) this.declareSlot(symbol, object);
|
|
268
|
+
},
|
|
269
|
+
|
|
145
270
|
emitIf(statement: IfStatement): void {
|
|
146
271
|
const thenLabel = this.label("if.then");
|
|
147
272
|
const elseLabel = statement.elseStatement ? this.label("if.else") : undefined;
|
|
@@ -178,7 +303,6 @@ export const statementMethods: StatementMethods = {
|
|
|
178
303
|
const nonzero = this.reg();
|
|
179
304
|
this.emit(` ${nonzero} = icmp ne i32 ${truthy}, 0`);
|
|
180
305
|
this.terminate(`br i1 ${nonzero}, label %${bodyLabel}, label %${endLabel}`);
|
|
181
|
-
|
|
182
306
|
this.startBlock(bodyLabel);
|
|
183
307
|
this.current.loops.push({ breakLabel: endLabel, continueLabel: condLabel, tryDepth: this.current.tryFrames.length });
|
|
184
308
|
this.emitStatement(statement.statement);
|
|
@@ -260,7 +384,7 @@ export const statementMethods: StatementMethods = {
|
|
|
260
384
|
const indexPtr = this.alloca();
|
|
261
385
|
this.emit(` store i64 ${numberLiteral(0)}, i64* ${indexPtr}`);
|
|
262
386
|
const lengthValue = this.reg();
|
|
263
|
-
this.emit(` ${lengthValue} = call i64 @
|
|
387
|
+
this.emit(` ${lengthValue} = call i64 @xt_iter_length(i64 ${iterable})`);
|
|
264
388
|
|
|
265
389
|
const condLabel = this.label("forof.cond");
|
|
266
390
|
const bodyLabel = this.label("forof.body");
|
|
@@ -281,7 +405,7 @@ export const statementMethods: StatementMethods = {
|
|
|
281
405
|
|
|
282
406
|
this.startBlock(bodyLabel);
|
|
283
407
|
const element = this.reg();
|
|
284
|
-
this.emit(` ${element} = call i64 @
|
|
408
|
+
this.emit(` ${element} = call i64 @xt_iter_value(i64 ${iterable}, i64 ${index})`);
|
|
285
409
|
this.bindLoopVariable(statement.initializer, element);
|
|
286
410
|
this.current.loops.push({ breakLabel: endLabel, continueLabel: updateLabel, tryDepth: this.current.tryFrames.length });
|
|
287
411
|
this.emitStatement(statement.statement);
|
|
@@ -300,7 +424,11 @@ export const statementMethods: StatementMethods = {
|
|
|
300
424
|
if (initializer.kind === SyntaxKind.VariableDeclarationList) {
|
|
301
425
|
const declaration = initializer.declarations[0];
|
|
302
426
|
if (!declaration) return;
|
|
303
|
-
|
|
427
|
+
if (declaration.name.kind !== SyntaxKind.Identifier) {
|
|
428
|
+
this.emitBindingPattern(declaration.name, value);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const symbol = this.binding.symbolOfDeclaration.get(declaration) ?? this.binding.symbolOfDeclaration.get(declaration.name);
|
|
304
432
|
if (symbol) {
|
|
305
433
|
if (this.current.slots.has(symbol.id)) this.writeSlot(symbol, value);
|
|
306
434
|
else this.declareSlot(symbol, value);
|
|
@@ -79,6 +79,18 @@ export const GLOBAL_FUNCTIONS: Record<string, string> = {
|
|
|
79
79
|
Boolean: "xt_boolean_ctor",
|
|
80
80
|
};
|
|
81
81
|
|
|
82
|
+
/** Global functions that are also usable as first-class values, each mapped to
|
|
83
|
+
* a runtime trampoline with the closure ABI. */
|
|
84
|
+
export const BUILTIN_FUNCTION_VALUES: Record<string, string> = {
|
|
85
|
+
parseInt: "xt_builtin_value_parseInt",
|
|
86
|
+
parseFloat: "xt_builtin_value_parseFloat",
|
|
87
|
+
isNaN: "xt_builtin_value_isNaN",
|
|
88
|
+
isFinite: "xt_builtin_value_isFinite",
|
|
89
|
+
Number: "xt_builtin_value_number",
|
|
90
|
+
String: "xt_builtin_value_string",
|
|
91
|
+
Boolean: "xt_builtin_value_boolean",
|
|
92
|
+
};
|
|
93
|
+
|
|
82
94
|
/** Global namespaces whose static methods map to runtime dispatchers. */
|
|
83
95
|
export const NAMESPACE_STATICS: Record<string, string> = {
|
|
84
96
|
Math: "xt_math_call",
|
|
@@ -107,6 +119,7 @@ export const NAMESPACE_STATICS: Record<string, string> = {
|
|
|
107
119
|
/** Namespace identifiers whose property access maps to a runtime getter. */
|
|
108
120
|
export const NAMESPACE_PROPERTIES: Record<string, string> = {
|
|
109
121
|
process: "xt_process_get",
|
|
122
|
+
"import.meta": "xt_import_meta",
|
|
110
123
|
};
|
|
111
124
|
|
|
112
125
|
/** Global constructors called as `new X(...)` (all have signature `(i32, i64*)`). */
|
|
@@ -116,6 +129,7 @@ export const CTOR_FUNCTIONS: Record<string, string> = {
|
|
|
116
129
|
Set: "xt_set_ctor",
|
|
117
130
|
Date: "xt_date_ctor",
|
|
118
131
|
RegExp: "xt_regexp_ctor",
|
|
132
|
+
Error: "xt_error_ctor",
|
|
119
133
|
Promise: "xt_promise_ctor",
|
|
120
134
|
Buffer: "xt_buffer_ctor",
|
|
121
135
|
Readable: "xt_readable_ctor",
|
package/src/codegen/values.ts
CHANGED
|
@@ -5,32 +5,101 @@
|
|
|
5
5
|
* value uses a tag in the high 16 bits plus a 48-bit payload. The compiler
|
|
6
6
|
* emits these constants directly into LLVM IR, so this module (together with
|
|
7
7
|
* runtime/rt.h) is the single source of truth for the representation.
|
|
8
|
+
*
|
|
9
|
+
* The constants are written as pre-computed signed 64-bit decimal literals
|
|
10
|
+
* rather than BigInt expressions, and `numberLiteral` derives the raw bits of a
|
|
11
|
+
* double with plain arithmetic. That keeps this module free of BigInt and
|
|
12
|
+
* typed-array APIs so the compiler can be compiled by itself on every platform.
|
|
8
13
|
*/
|
|
9
14
|
|
|
10
|
-
|
|
11
|
-
export const
|
|
15
|
+
/** `0xffff000000000000` as a signed i64. */
|
|
16
|
+
export const XT_TAG_MASK = "-281474976710656";
|
|
17
|
+
/** `0xfff8000000000000` as a signed i64. */
|
|
18
|
+
export const XT_NUMBER_MASK = "-2251799813685248";
|
|
12
19
|
|
|
13
|
-
export const XT_UNDEFINED =
|
|
14
|
-
export const XT_NULL =
|
|
15
|
-
export const XT_FALSE =
|
|
16
|
-
export const XT_TRUE =
|
|
17
|
-
export const XT_STRING =
|
|
18
|
-
export const XT_OBJECT =
|
|
19
|
-
export const XT_ARRAY =
|
|
20
|
-
export const XT_FUNCTION =
|
|
20
|
+
export const XT_UNDEFINED = "-2251799813685248"; // 0xfff8000000000000
|
|
21
|
+
export const XT_NULL = "-1970324836974592"; // 0xfff9000000000000
|
|
22
|
+
export const XT_FALSE = "-1688849860263936"; // 0xfffa000000000000
|
|
23
|
+
export const XT_TRUE = "-1407374883553280"; // 0xfffb000000000000
|
|
24
|
+
export const XT_STRING = "-1125899906842624"; // 0xfffc000000000000
|
|
25
|
+
export const XT_OBJECT = "-844424930131968"; // 0xfffd000000000000
|
|
26
|
+
export const XT_ARRAY = "-562949953421312"; // 0xfffe000000000000
|
|
27
|
+
export const XT_FUNCTION = "-281474976710656"; // 0xffff000000000000
|
|
21
28
|
|
|
22
29
|
/** Render a 64-bit constant the way LLVM expects signed i64 literals. */
|
|
23
|
-
export function i64(value:
|
|
24
|
-
return
|
|
30
|
+
export function i64(value: string): string {
|
|
31
|
+
return value;
|
|
25
32
|
}
|
|
26
33
|
|
|
27
34
|
/** The raw 64 bits of an IEEE-754 double, as a signed i64 literal. */
|
|
28
35
|
export function numberLiteral(value: number): string {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
if (value !== value) return "9221120237041090560"; // 0x7ff8000000000000 (NaN)
|
|
37
|
+
const negative = value < 0 || (value === 0 && 1 / value < 0);
|
|
38
|
+
const abs = negative ? -value : value;
|
|
39
|
+
let high: number;
|
|
40
|
+
let low: number;
|
|
41
|
+
if (abs === 0) {
|
|
42
|
+
high = 0;
|
|
43
|
+
low = 0;
|
|
44
|
+
} else if (abs === Infinity) {
|
|
45
|
+
high = 0x7ff00000;
|
|
46
|
+
low = 0;
|
|
47
|
+
} else {
|
|
48
|
+
let exponent = Math.floor(Math.log2(abs));
|
|
49
|
+
while (exponent < 1023 && Math.pow(2, exponent + 1) <= abs) exponent++;
|
|
50
|
+
while (exponent > -1022 && Math.pow(2, exponent) > abs) exponent--;
|
|
51
|
+
let mantissa = Math.round((abs / Math.pow(2, exponent) - 1) * 4503599627370496);
|
|
52
|
+
let biased = exponent + 1023;
|
|
53
|
+
if (biased <= 0) {
|
|
54
|
+
mantissa = Math.round((abs / Math.pow(2, -1022)) * 4503599627370496);
|
|
55
|
+
biased = 0;
|
|
56
|
+
} else if (mantissa >= 4503599627370496) {
|
|
57
|
+
mantissa = 0;
|
|
58
|
+
biased += 1;
|
|
59
|
+
}
|
|
60
|
+
high = biased * 0x100000 + Math.floor(mantissa / 0x100000000);
|
|
61
|
+
low = mantissa % 0x100000000;
|
|
62
|
+
}
|
|
63
|
+
if (negative) high += 0x80000000;
|
|
64
|
+
return signedDecimal(high >>> 0, low >>> 0);
|
|
32
65
|
}
|
|
33
66
|
|
|
34
67
|
export function booleanLiteral(value: boolean): string {
|
|
35
|
-
return
|
|
68
|
+
return value ? XT_TRUE : XT_FALSE;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Two's-complement (high, low) 32-bit halves as a signed decimal string. */
|
|
72
|
+
function signedDecimal(high: number, low: number): string {
|
|
73
|
+
if (high >= 0x80000000) {
|
|
74
|
+
let h = (~high) >>> 0;
|
|
75
|
+
let l = (~low) >>> 0;
|
|
76
|
+
l = (l + 1) >>> 0;
|
|
77
|
+
if (l === 0) h = (h + 1) >>> 0;
|
|
78
|
+
return "-" + unsignedDecimal(h, l);
|
|
79
|
+
}
|
|
80
|
+
return unsignedDecimal(high, low);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Unsigned 64-bit `high * 2^32 + low` as a decimal string. */
|
|
84
|
+
function unsignedDecimal(high: number, low: number): string {
|
|
85
|
+
const digits: number[] = [];
|
|
86
|
+
let h = high;
|
|
87
|
+
if (h === 0) digits.push(0);
|
|
88
|
+
while (h > 0) {
|
|
89
|
+
digits.push(h % 10);
|
|
90
|
+
h = Math.floor(h / 10);
|
|
91
|
+
}
|
|
92
|
+
let carry = low;
|
|
93
|
+
for (let i = 0; i < digits.length; i++) {
|
|
94
|
+
const current = digits[i]! * 4294967296 + carry;
|
|
95
|
+
digits[i] = current % 10;
|
|
96
|
+
carry = Math.floor(current / 10);
|
|
97
|
+
}
|
|
98
|
+
while (carry > 0) {
|
|
99
|
+
digits.push(carry % 10);
|
|
100
|
+
carry = Math.floor(carry / 10);
|
|
101
|
+
}
|
|
102
|
+
let out = "";
|
|
103
|
+
for (let i = digits.length - 1; i >= 0; i--) out += digits[i]!;
|
|
104
|
+
return out;
|
|
36
105
|
}
|
|
@@ -80,15 +80,20 @@ export class SourceFile {
|
|
|
80
80
|
* and stable across platforms which is exactly what the incremental cache
|
|
81
81
|
* needs. Collision resistance is not a security property here.
|
|
82
82
|
*/
|
|
83
|
-
export function hashText(text: string, seed =
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
export function hashText(text: string, seed = 0): string {
|
|
84
|
+
// Two independent 32-bit FNV-1a lanes give a wide, stable fingerprint
|
|
85
|
+
// without relying on BigInt, which the compiler cannot assume at runtime.
|
|
86
|
+
let a = (0xcbf29ce4 ^ seed) >>> 0;
|
|
87
|
+
let b = (0x84222325 ^ seed) >>> 0;
|
|
88
|
+
const prime = 0x01000193;
|
|
87
89
|
for (let i = 0; i < text.length; i++) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
const code = text.charCodeAt(i);
|
|
91
|
+
a = Math.imul(a ^ (code & 0xff), prime) >>> 0;
|
|
92
|
+
a = Math.imul(a ^ (code >>> 8), prime) >>> 0;
|
|
93
|
+
b = Math.imul(b ^ (code >>> 8), prime) >>> 0;
|
|
94
|
+
b = Math.imul(b ^ (code & 0xff), prime) >>> 0;
|
|
90
95
|
}
|
|
91
|
-
return
|
|
96
|
+
return (a >>> 0).toString(16).padStart(8, "0") + (b >>> 0).toString(16).padStart(8, "0");
|
|
92
97
|
}
|
|
93
98
|
|
|
94
99
|
/** Combine several hashes/strings into one stable fingerprint. */
|
package/src/driver/compiler.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* nothing relevant changed.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
11
11
|
import { basename, dirname, extname, join, resolve } from "node:path";
|
|
12
12
|
import { DiagnosticBag, type Diagnostic } from "../diagnostics/diagnostic.js";
|
|
13
13
|
import { SourceFile } from "../diagnostics/source.js";
|
|
@@ -73,6 +73,35 @@ export function compileString(source: string, fileName = "input.ts", extensions?
|
|
|
73
73
|
return { ir, diagnostics: diagnostics.diagnostics };
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Compile a file to LLVM IR, bundling every reachable relative module first so
|
|
78
|
+
* that imports from other TypeScript sources resolve exactly like `build`.
|
|
79
|
+
*/
|
|
80
|
+
export function compileEntry(entryPath: string, extensions?: ExtensionRegistry): CompileStringResult {
|
|
81
|
+
const absoluteEntry = resolve(entryPath);
|
|
82
|
+
const sourceText = readFileSync(absoluteEntry, "utf8");
|
|
83
|
+
const file = new SourceFile(absoluteEntry, sourceText);
|
|
84
|
+
const diagnostics = new DiagnosticBag();
|
|
85
|
+
const parser = new Parser(file, diagnostics);
|
|
86
|
+
let sourceFile = parser.parseSourceFile();
|
|
87
|
+
const isModule = sourceFile.statements.some(
|
|
88
|
+
(statement) =>
|
|
89
|
+
statement.kind === SyntaxKind.ImportDeclaration ||
|
|
90
|
+
statement.kind === SyntaxKind.ExportDeclaration ||
|
|
91
|
+
statement.kind === SyntaxKind.ExportAssignment,
|
|
92
|
+
);
|
|
93
|
+
if (isModule) {
|
|
94
|
+
const bundled = bundleModules(absoluteEntry, diagnostics);
|
|
95
|
+
if (bundled) sourceFile = bundled.sourceFile;
|
|
96
|
+
}
|
|
97
|
+
const registry = extensions ?? createDefaultRegistry();
|
|
98
|
+
const { ir } = generate(sourceFile, diagnostics, {
|
|
99
|
+
builtins: registry.builtins(),
|
|
100
|
+
modules: registry.modules(),
|
|
101
|
+
});
|
|
102
|
+
return { ir, diagnostics: diagnostics.diagnostics };
|
|
103
|
+
}
|
|
104
|
+
|
|
76
105
|
function executableName(entry: string, outDir: string, explicit: string | undefined): string {
|
|
77
106
|
if (explicit) return resolve(explicit);
|
|
78
107
|
const base = basename(entry, extname(entry));
|
|
@@ -217,9 +246,22 @@ function ensureRuntimeObjects(
|
|
|
217
246
|
cacheDir: string,
|
|
218
247
|
registry: ExtensionRegistry,
|
|
219
248
|
): RuntimeObjects {
|
|
249
|
+
/* Runtime objects depend on the shared headers (rt.h/rt_internal.h/...),
|
|
250
|
+
so a header change must invalidate every cached object. Hash them all. */
|
|
251
|
+
const headerParts: string[] = [];
|
|
252
|
+
const collectHeaders = (dir: string): void => {
|
|
253
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
254
|
+
const full = join(dir, entry.name);
|
|
255
|
+
if (entry.isDirectory()) collectHeaders(full);
|
|
256
|
+
else if (entry.name.endsWith(".h")) headerParts.push(readFileSync(full, "utf8"));
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
collectHeaders(runtimeDir);
|
|
260
|
+
const headerStamp = hashParts(headerParts);
|
|
261
|
+
|
|
220
262
|
const compileOne = (sourcePath: string, tag: string): string => {
|
|
221
263
|
const body = readFileSync(sourcePath, "utf8");
|
|
222
|
-
const key = hashParts(["runtime", COMPILER_VERSION, tag, body, clang, process.platform]);
|
|
264
|
+
const key = hashParts(["runtime", COMPILER_VERSION, tag, headerStamp, body, clang, process.platform]);
|
|
223
265
|
const objectPath = join(cacheDir, `${tag}-${key}.o`);
|
|
224
266
|
if (!existsSync(objectPath)) {
|
|
225
267
|
compileC(runner, clang, sourcePath, objectPath, runtimeDir);
|
package/src/driver/modules.ts
CHANGED
|
@@ -65,6 +65,18 @@ function isExternalSpecifier(specifier: string): boolean {
|
|
|
65
65
|
|
|
66
66
|
function resolveModule(fromDir: string, specifier: string): string | undefined {
|
|
67
67
|
const base = resolve(fromDir, specifier);
|
|
68
|
+
// TypeScript sources are imported using their emitted `.js` extension
|
|
69
|
+
// (`import ... from "./foo.js"`), so map the extension back to the source
|
|
70
|
+
// file before probing the usual suffixes.
|
|
71
|
+
const candidates = [base];
|
|
72
|
+
const jsExtension = /\.(?:m|c)?jsx?$/.exec(base);
|
|
73
|
+
if (jsExtension) {
|
|
74
|
+
const stem = base.slice(0, jsExtension.index);
|
|
75
|
+
candidates.push(`${stem}.ts`, `${stem}.tsx`, `${stem}.mts`, `${stem}.cts`);
|
|
76
|
+
}
|
|
77
|
+
for (const candidate of candidates) {
|
|
78
|
+
if (existsSync(candidate) && statSync(candidate).isFile()) return candidate;
|
|
79
|
+
}
|
|
68
80
|
for (const suffix of RESOLVE_SUFFIXES) {
|
|
69
81
|
const candidate = base + suffix;
|
|
70
82
|
if (existsSync(candidate) && statSync(candidate).isFile()) return candidate;
|
|
@@ -86,8 +98,10 @@ function declarationName(node: Node): Identifier | undefined {
|
|
|
86
98
|
case SyntaxKind.TypeAliasDeclaration:
|
|
87
99
|
case SyntaxKind.ModuleDeclaration:
|
|
88
100
|
return (node as { name?: Identifier }).name;
|
|
89
|
-
case SyntaxKind.VariableDeclaration:
|
|
90
|
-
|
|
101
|
+
case SyntaxKind.VariableDeclaration: {
|
|
102
|
+
const name = (node as VariableDeclaration).name;
|
|
103
|
+
return name.kind === SyntaxKind.Identifier ? name : undefined;
|
|
104
|
+
}
|
|
91
105
|
default:
|
|
92
106
|
return undefined;
|
|
93
107
|
}
|
|
@@ -349,7 +363,7 @@ function exportedNames(statement: Statement): string[] {
|
|
|
349
363
|
const names: string[] = [];
|
|
350
364
|
if (statement.kind === SyntaxKind.VariableStatement) {
|
|
351
365
|
for (const declaration of (statement as VariableStatement).declarationList.declarations) {
|
|
352
|
-
|
|
366
|
+
collectBindingIdentifiers(declaration.name, names);
|
|
353
367
|
}
|
|
354
368
|
} else {
|
|
355
369
|
const name = declarationName(statement);
|
|
@@ -358,6 +372,17 @@ function exportedNames(statement: Statement): string[] {
|
|
|
358
372
|
return names;
|
|
359
373
|
}
|
|
360
374
|
|
|
375
|
+
function collectBindingIdentifiers(name: import("../ast/declarations.js").BindingName, out: string[]): void {
|
|
376
|
+
if (name.kind === SyntaxKind.Identifier) {
|
|
377
|
+
out.push(name.text);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
const elements = name.kind === SyntaxKind.ArrayBindingPattern ? name.elements : name.elements;
|
|
381
|
+
for (const element of elements) {
|
|
382
|
+
if (element) collectBindingIdentifiers(element.name, out);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
361
386
|
/** Turn `export default expr` into a synthetic `const` declaration. */
|
|
362
387
|
function exportAssignmentToStatement(record: ModuleRecord, assignment: ExportAssignment): Statement {
|
|
363
388
|
const name: Identifier = {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node's `child_process` module: `spawnSync(command, args, options)`, backed by
|
|
3
|
+
* `runtime/ext_node/child_process`. Captures stdout/stderr as UTF-8 strings and
|
|
4
|
+
* honours `options.cwd`, which is all the compiler's toolchain wrapper needs.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { NodeModule } from "../module.js";
|
|
8
|
+
import { resolveFrom } from "../module.js";
|
|
9
|
+
import type { BuiltinFunction, ModuleExports } from "../../registry.js";
|
|
10
|
+
|
|
11
|
+
const builtins: Record<string, BuiltinFunction> = {
|
|
12
|
+
spawnSync: { symbol: "xt_child_process_spawn_sync" },
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const exports: ModuleExports = {
|
|
16
|
+
spawnSync: { symbol: "xt_child_process_spawn_sync" },
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const childProcessModule: NodeModule = {
|
|
20
|
+
name: "child_process",
|
|
21
|
+
runtimeSources: () => [resolveFrom(import.meta.url, "../../../../runtime/ext_node/child_process/child_process.c")],
|
|
22
|
+
builtins: () => builtins,
|
|
23
|
+
exports: () => exports,
|
|
24
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node's `crypto` module: `createHash(...).update(...).digest("hex")`, backed
|
|
3
|
+
* by a self-contained SHA-256 in `runtime/ext_node/crypto`.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { NodeModule } from "../module.js";
|
|
7
|
+
import { resolveFrom } from "../module.js";
|
|
8
|
+
import type { BuiltinFunction, ModuleExports } from "../../registry.js";
|
|
9
|
+
|
|
10
|
+
const builtins: Record<string, BuiltinFunction> = {
|
|
11
|
+
createHash: { symbol: "xt_crypto_create_hash" },
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const exports: ModuleExports = {
|
|
15
|
+
createHash: { symbol: "xt_crypto_create_hash" },
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const cryptoModule: NodeModule = {
|
|
19
|
+
name: "crypto",
|
|
20
|
+
runtimeSources: () => [resolveFrom(import.meta.url, "../../../../runtime/ext_node/crypto/crypto.c")],
|
|
21
|
+
builtins: () => builtins,
|
|
22
|
+
exports: () => exports,
|
|
23
|
+
};
|
|
@@ -22,6 +22,9 @@ import { streamModule } from "./stream/index.js";
|
|
|
22
22
|
import { netModule } from "./net/index.js";
|
|
23
23
|
import { dgramModule } from "./dgram/index.js";
|
|
24
24
|
import { httpModule } from "./http/index.js";
|
|
25
|
+
import { urlModule } from "./url/index.js";
|
|
26
|
+
import { cryptoModule } from "./crypto/index.js";
|
|
27
|
+
import { childProcessModule } from "./child_process/index.js";
|
|
25
28
|
|
|
26
29
|
/** Every Node module the extension currently provides. */
|
|
27
30
|
const modules: readonly NodeModule[] = [
|
|
@@ -35,6 +38,9 @@ const modules: readonly NodeModule[] = [
|
|
|
35
38
|
netModule,
|
|
36
39
|
dgramModule,
|
|
37
40
|
httpModule,
|
|
41
|
+
urlModule,
|
|
42
|
+
cryptoModule,
|
|
43
|
+
childProcessModule,
|
|
38
44
|
];
|
|
39
45
|
|
|
40
46
|
export const nodeExtension: Extension = {
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { dirname, resolve } from "node:path";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { findRuntimeDir } from "../../driver/paths.js";
|
|
10
11
|
import type { BuiltinFunction, ModuleExports } from "../registry.js";
|
|
11
12
|
|
|
12
13
|
export interface NodeModule {
|
|
@@ -26,7 +27,18 @@ export interface NodeModule {
|
|
|
26
27
|
exports?(): ModuleExports;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Resolve a C runtime source that is written relative to an extension module
|
|
32
|
+
* (e.g. `../../../../runtime/ext_node/fs/read_file.c`).
|
|
33
|
+
*
|
|
34
|
+
* When running from source the page is resolved against the calling module's
|
|
35
|
+
* own URL, but inside a compiled xbintsc binary `import.meta.url` points at the
|
|
36
|
+
* executable, so the module-relative form no longer resolves. Anchor on the
|
|
37
|
+
* discovered runtime directory instead, which is correct in both worlds.
|
|
38
|
+
*/
|
|
30
39
|
export function resolveFrom(importMetaUrl: string, relative: string): string {
|
|
40
|
+
const marker = "runtime/";
|
|
41
|
+
const index = relative.indexOf(marker);
|
|
42
|
+
if (index >= 0) return resolve(findRuntimeDir(), relative.slice(index + marker.length));
|
|
31
43
|
return resolve(dirname(fileURLToPath(importMetaUrl)), relative);
|
|
32
44
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node's `url` module: `pathToFileURL` / `fileURLToPath`, backed by
|
|
3
|
+
* `runtime/ext_node/url`. These are the helpers the compiler uses to relate
|
|
4
|
+
* `import.meta.url`, `process.argv[1]` and filesystem paths.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { NodeModule } from "../module.js";
|
|
8
|
+
import { resolveFrom } from "../module.js";
|
|
9
|
+
import type { BuiltinFunction, ModuleExports } from "../../registry.js";
|
|
10
|
+
|
|
11
|
+
const builtins: Record<string, BuiltinFunction> = {
|
|
12
|
+
pathToFileURL: { symbol: "xt_url_path_to_file_url" },
|
|
13
|
+
fileURLToPath: { symbol: "xt_url_file_url_to_path" },
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const exports: ModuleExports = {
|
|
17
|
+
pathToFileURL: { symbol: "xt_url_path_to_file_url" },
|
|
18
|
+
fileURLToPath: { symbol: "xt_url_file_url_to_path" },
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const urlModule: NodeModule = {
|
|
22
|
+
name: "url",
|
|
23
|
+
runtimeSources: () => [resolveFrom(import.meta.url, "../../../../runtime/ext_node/url/url.c")],
|
|
24
|
+
builtins: () => builtins,
|
|
25
|
+
exports: () => exports,
|
|
26
|
+
};
|