xbintsc 0.3.81 → 0.3.87
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 +12 -2
- package/README.zh-CN.md +12 -4
- package/dist/src/ast/common.d.ts +23 -0
- package/dist/src/cli/main.js +68 -24
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/codegen/generator/calls/invocation.js +5 -1
- package/dist/src/codegen/generator/calls/invocation.js.map +1 -1
- package/dist/src/codegen/generator/context.d.ts +50 -0
- package/dist/src/codegen/generator/context.js +124 -13
- package/dist/src/codegen/generator/context.js.map +1 -1
- package/dist/src/codegen/generator/debug.d.ts +33 -0
- package/dist/src/codegen/generator/debug.js +97 -0
- package/dist/src/codegen/generator/debug.js.map +1 -0
- package/dist/src/codegen/generator/expressions/assignment.js +9 -5
- package/dist/src/codegen/generator/expressions/assignment.js.map +1 -1
- package/dist/src/codegen/generator/expressions/operators.js +35 -9
- package/dist/src/codegen/generator/expressions/operators.js.map +1 -1
- package/dist/src/codegen/generator/expressions/primary.js +5 -2
- package/dist/src/codegen/generator/expressions/primary.js.map +1 -1
- package/dist/src/codegen/generator/generator.d.ts +3 -1
- package/dist/src/codegen/generator/generator.js +3 -1
- package/dist/src/codegen/generator/generator.js.map +1 -1
- package/dist/src/codegen/generator/module.d.ts +1 -0
- package/dist/src/codegen/generator/module.js +76 -6
- package/dist/src/codegen/generator/module.js.map +1 -1
- package/dist/src/codegen/generator/numeric.d.ts +26 -0
- package/dist/src/codegen/generator/numeric.js +127 -0
- package/dist/src/codegen/generator/numeric.js.map +1 -0
- package/dist/src/codegen/generator/state.d.ts +17 -0
- package/dist/src/codegen/generator/state.js +5 -0
- package/dist/src/codegen/generator/state.js.map +1 -1
- package/dist/src/codegen/generator/statements/control-flow.js +1 -1
- package/dist/src/codegen/generator/statements/control-flow.js.map +1 -1
- package/dist/src/codegen/generator/statements/dispatch.js +3 -0
- package/dist/src/codegen/generator/statements/dispatch.js.map +1 -1
- package/dist/src/codegen/generator/statements/variables.js +8 -2
- package/dist/src/codegen/generator/statements/variables.js.map +1 -1
- package/dist/src/codegen/generator/tables.d.ts +7 -0
- package/dist/src/codegen/generator/tables.js +25 -0
- package/dist/src/codegen/generator/tables.js.map +1 -1
- package/dist/src/driver/bundler/merge.js +41 -5
- package/dist/src/driver/bundler/merge.js.map +1 -1
- package/dist/src/driver/compiler.d.ts +22 -3
- package/dist/src/driver/compiler.js +43 -57
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/driver/fingerprint.d.ts +27 -0
- package/dist/src/driver/fingerprint.js +88 -0
- package/dist/src/driver/fingerprint.js.map +1 -0
- package/dist/src/driver/toolchain-provider.d.ts +9 -2
- package/dist/src/driver/toolchain-provider.js +15 -15
- package/dist/src/driver/toolchain-provider.js.map +1 -1
- package/dist/src/driver/toolchain.d.ts +58 -1
- package/dist/src/driver/toolchain.js +169 -22
- package/dist/src/driver/toolchain.js.map +1 -1
- package/dist/src/parser/expressions.js +20 -1
- package/dist/src/parser/expressions.js.map +1 -1
- package/dist/tests/cli/main.test.js +41 -1
- package/dist/tests/cli/main.test.js.map +1 -1
- package/dist/tests/codegen/expressions.test.js +14 -3
- package/dist/tests/codegen/expressions.test.js.map +1 -1
- package/dist/tests/codegen/llvm.test.js +23 -0
- package/dist/tests/codegen/llvm.test.js.map +1 -1
- package/dist/tests/driver/toolchain-provider.test.js +49 -2
- package/dist/tests/driver/toolchain-provider.test.js.map +1 -1
- package/dist/tests/driver/toolchain.test.js +62 -3
- package/dist/tests/driver/toolchain.test.js.map +1 -1
- package/dist/tests/e2e/child-process.test.d.ts +10 -0
- package/dist/tests/e2e/child-process.test.js +59 -0
- package/dist/tests/e2e/child-process.test.js.map +1 -0
- package/dist/tests/e2e/compile-objects-classes.test.js +47 -0
- package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
- package/dist/tests/e2e/error-stack.test.d.ts +9 -0
- package/dist/tests/e2e/error-stack.test.js +94 -0
- package/dist/tests/e2e/error-stack.test.js.map +1 -0
- package/dist/tests/e2e/string-concat.test.d.ts +9 -0
- package/dist/tests/e2e/string-concat.test.js +102 -0
- package/dist/tests/e2e/string-concat.test.js.map +1 -0
- package/dist/tests/helpers.d.ts +9 -0
- package/dist/tests/helpers.js +26 -3
- package/dist/tests/helpers.js.map +1 -1
- package/dist/tests/parser/expressions.test.js +7 -0
- package/dist/tests/parser/expressions.test.js.map +1 -1
- package/dist/tests/version.test.d.ts +1 -0
- package/dist/tests/version.test.js +24 -0
- package/dist/tests/version.test.js.map +1 -0
- package/doc/DESIGN.md +20 -12
- package/doc/ai/cli.md +2 -0
- package/doc/ai/contributing.md +7 -1
- package/doc/ai/language-support.md +4 -4
- package/doc/ai/troubleshooting.md +39 -2
- package/doc/ai/zh-CN/cli.md +2 -0
- package/doc/ai/zh-CN/contributing.md +6 -2
- package/doc/ai/zh-CN/language-support.md +4 -4
- package/doc/ai/zh-CN/troubleshooting.md +35 -2
- package/doc/implemented.md +3 -3
- package/doc/requirements.md +13 -4
- package/doc/unimplemented.md +11 -10
- package/doc/zh-CN/DESIGN.md +15 -9
- package/doc/zh-CN/implemented.md +3 -3
- package/doc/zh-CN/requirements.md +12 -3
- package/doc/zh-CN/unimplemented.md +10 -9
- package/package.json +3 -1
- package/runtime/ext_node/buffer/parts/construction.inc +2 -2
- package/runtime/ext_node/buffer/parts/methods.inc +1 -1
- package/runtime/ext_node/buffer/parts/search.inc +1 -1
- package/runtime/ext_node/child_process/child_process.c +25 -0
- package/runtime/ext_node/fs/fd_ops.c +1 -1
- package/runtime/rt.h +17 -1
- package/runtime/rt_internal.h +47 -4
- package/runtime/xt_alloc.c +14 -2
- package/runtime/xt_bigint/conversions.inc +1 -1
- package/runtime/xt_builtins.c +136 -14
- package/runtime/xt_containers/access.inc +17 -16
- package/runtime/xt_containers/objects.inc +4 -3
- package/runtime/xt_io/exception.inc +171 -2
- package/runtime/xt_io/inspect.inc +15 -13
- package/runtime/xt_io.c +5 -0
- package/runtime/xt_stdlib/arrays.inc +2 -2
- package/runtime/xt_stdlib/dispatch.inc +2 -2
- package/runtime/xt_stdlib/strings.inc +33 -21
- package/runtime/xt_stdlib2/array-statics.inc +2 -1
- package/runtime/xt_stdlib2/arrays.inc +1 -1
- package/runtime/xt_stdlib2/error.inc +22 -6
- package/runtime/xt_stdlib2/json.inc +3 -2
- package/runtime/xt_stdlib2/number-string-statics.inc +3 -1
- package/runtime/xt_stdlib2/regexp-match.inc +48 -33
- package/runtime/xt_stdlib2/strings.inc +23 -14
- package/runtime/xt_symbol.c +1 -1
- package/runtime/xt_values/comparison.inc +2 -2
- package/runtime/xt_values/constructors.inc +10 -5
- package/runtime/xt_values/conversions.inc +4 -3
- package/runtime/xt_values/strings.inc +78 -4
- package/scripts/bench.ts +172 -0
- package/scripts/build-runtime.ts +2 -1
- package/scripts/check-version.ts +53 -0
- package/src/ast/common.ts +23 -0
- package/src/cli/main.ts +68 -26
- package/src/codegen/generator/calls/invocation.ts +5 -0
- package/src/codegen/generator/context.ts +129 -18
- package/src/codegen/generator/debug.ts +132 -0
- package/src/codegen/generator/expressions/assignment.ts +9 -5
- package/src/codegen/generator/expressions/operators.ts +47 -8
- package/src/codegen/generator/expressions/primary.ts +5 -2
- package/src/codegen/generator/generator.ts +18 -2
- package/src/codegen/generator/module.ts +78 -6
- package/src/codegen/generator/numeric.ts +158 -0
- package/src/codegen/generator/state.ts +22 -0
- package/src/codegen/generator/statements/control-flow.ts +1 -1
- package/src/codegen/generator/statements/dispatch.ts +3 -0
- package/src/codegen/generator/statements/variables.ts +8 -2
- package/src/codegen/generator/tables.ts +26 -0
- package/src/driver/bundler/merge.ts +39 -5
- package/src/driver/compiler.ts +62 -55
- package/src/driver/fingerprint.ts +85 -0
- package/src/driver/toolchain-provider.ts +17 -16
- package/src/driver/toolchain.ts +210 -21
- package/src/parser/expressions.ts +20 -1
|
@@ -7,15 +7,34 @@ import {
|
|
|
7
7
|
BinaryOperator,
|
|
8
8
|
PostfixUnaryOperator,
|
|
9
9
|
PrefixUnaryOperator,
|
|
10
|
+
SyntaxKind,
|
|
10
11
|
type BinaryExpression,
|
|
11
12
|
type ConditionalExpression,
|
|
13
|
+
type Expression,
|
|
14
|
+
type Identifier,
|
|
12
15
|
type PostfixUnaryExpression,
|
|
13
16
|
type PrefixUnaryExpression,
|
|
14
17
|
} from "../../../ast/nodes.js";
|
|
15
18
|
import { i64, numberLiteral, XT_UNDEFINED } from "../../values.js";
|
|
16
|
-
import { BINARY_RUNTIME, isAssignmentOperator } from "../tables.js";
|
|
19
|
+
import { BINARY_RUNTIME, BUILTIN_INSTANCEOF, isAssignmentOperator } from "../tables.js";
|
|
17
20
|
import type { Generator } from "../generator.js";
|
|
18
21
|
|
|
22
|
+
/** True for an expression whose value is known to be a string. */
|
|
23
|
+
function isStringLike(node: Expression): boolean {
|
|
24
|
+
return node.kind === SyntaxKind.StringLiteral || node.kind === SyntaxKind.NoSubstitutionTemplateLiteral;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The built-in constructor a right-hand `instanceof` operand names, if any.
|
|
29
|
+
* A user binding with the same name shadows it.
|
|
30
|
+
*/
|
|
31
|
+
function builtinInstanceofId(generator: Generator, expression: Expression): number | undefined {
|
|
32
|
+
if (expression.kind !== SyntaxKind.Identifier) return undefined;
|
|
33
|
+
const identifier = expression as Identifier;
|
|
34
|
+
if (generator.binding.symbolOfIdentifier.get(identifier)) return undefined;
|
|
35
|
+
return BUILTIN_INSTANCEOF[identifier.text];
|
|
36
|
+
}
|
|
37
|
+
|
|
19
38
|
export interface OperatorExpressionMethods {
|
|
20
39
|
emitBinaryOrAssignment(this: Generator, node: BinaryExpression): string;
|
|
21
40
|
emitShortCircuit(this: Generator, node: BinaryExpression, kind: "and" | "or" | "nullish"): string;
|
|
@@ -44,6 +63,17 @@ export const operatorExpressionMethods: OperatorExpressionMethods = {
|
|
|
44
63
|
default:
|
|
45
64
|
break;
|
|
46
65
|
}
|
|
66
|
+
// `x instanceof Array` and friends: the built-in constructor is not a
|
|
67
|
+
// first-class value, so compare by the value's runtime kind instead.
|
|
68
|
+
if (operator === BinaryOperator.InstanceOf) {
|
|
69
|
+
const builtin = builtinInstanceofId(this, node.right);
|
|
70
|
+
if (builtin !== undefined) {
|
|
71
|
+
const leftValue = this.emitExpression(node.left);
|
|
72
|
+
const result = this.reg();
|
|
73
|
+
this.emit(` ${result} = call i64 @xt_instance_of_builtin(i64 ${leftValue}, i32 ${builtin})`);
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
47
77
|
const left = this.emitExpression(node.left);
|
|
48
78
|
const right = this.emitExpression(node.right);
|
|
49
79
|
const runtime = BINARY_RUNTIME[operator];
|
|
@@ -51,9 +81,10 @@ export const operatorExpressionMethods: OperatorExpressionMethods = {
|
|
|
51
81
|
this.unsupported(node, `operator '${operator}'`);
|
|
52
82
|
return i64(XT_UNDEFINED);
|
|
53
83
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
84
|
+
// `+` concatenates when either side is known to be a string; skip the
|
|
85
|
+
// numeric tag checks for that shape and call the runtime directly.
|
|
86
|
+
const knownStringConcat = operator === "+" && (isStringLike(node.left) || isStringLike(node.right));
|
|
87
|
+
return this.emitBinaryOperation(left, right, operator, runtime, !knownStringConcat);
|
|
57
88
|
},
|
|
58
89
|
|
|
59
90
|
emitShortCircuit(node: BinaryExpression, kind: "and" | "or" | "nullish"): string {
|
|
@@ -159,8 +190,12 @@ export const operatorExpressionMethods: OperatorExpressionMethods = {
|
|
|
159
190
|
const one = numberLiteral(1);
|
|
160
191
|
const current = this.emitExpression(node.operand);
|
|
161
192
|
const fn = node.operator === PrefixUnaryOperator.PlusPlus ? "xt_add" : "xt_sub";
|
|
162
|
-
const next = this.
|
|
163
|
-
|
|
193
|
+
const next = this.emitBinaryOperation(
|
|
194
|
+
current,
|
|
195
|
+
one,
|
|
196
|
+
node.operator === PrefixUnaryOperator.PlusPlus ? "+" : "-",
|
|
197
|
+
fn,
|
|
198
|
+
);
|
|
164
199
|
this.emitAssignmentTarget(node.operand, next);
|
|
165
200
|
return next;
|
|
166
201
|
}
|
|
@@ -174,8 +209,12 @@ export const operatorExpressionMethods: OperatorExpressionMethods = {
|
|
|
174
209
|
const one = numberLiteral(1);
|
|
175
210
|
const current = this.emitExpression(node.operand);
|
|
176
211
|
const fn = node.operator === PostfixUnaryOperator.PlusPlus ? "xt_add" : "xt_sub";
|
|
177
|
-
const next = this.
|
|
178
|
-
|
|
212
|
+
const next = this.emitBinaryOperation(
|
|
213
|
+
current,
|
|
214
|
+
one,
|
|
215
|
+
node.operator === PostfixUnaryOperator.PlusPlus ? "+" : "-",
|
|
216
|
+
fn,
|
|
217
|
+
);
|
|
179
218
|
this.emitAssignmentTarget(node.operand, next);
|
|
180
219
|
return current;
|
|
181
220
|
},
|
|
@@ -193,7 +193,7 @@ export const primaryExpressionMethods: PrimaryExpressionMethods = {
|
|
|
193
193
|
DiagnosticCode.CodegenError,
|
|
194
194
|
`Imported binding '${identifier.text}' cannot be used as a value`,
|
|
195
195
|
identifier,
|
|
196
|
-
this.
|
|
196
|
+
this.fileNameOf(identifier),
|
|
197
197
|
);
|
|
198
198
|
return i64(XT_UNDEFINED);
|
|
199
199
|
}
|
|
@@ -244,7 +244,7 @@ export const primaryExpressionMethods: PrimaryExpressionMethods = {
|
|
|
244
244
|
DiagnosticCode.CannotFindName,
|
|
245
245
|
`Cannot find name '${identifier.text}'`,
|
|
246
246
|
identifier,
|
|
247
|
-
this.
|
|
247
|
+
this.fileNameOf(identifier),
|
|
248
248
|
);
|
|
249
249
|
return i64(XT_UNDEFINED);
|
|
250
250
|
}
|
|
@@ -292,6 +292,9 @@ export const primaryExpressionMethods: PrimaryExpressionMethods = {
|
|
|
292
292
|
|
|
293
293
|
emitNew(node: NewExpression): string {
|
|
294
294
|
const callee = node.expression;
|
|
295
|
+
// A `new` expression is where the innermost frame's line should point for
|
|
296
|
+
// `Error.stack`; harmless for every other constructor.
|
|
297
|
+
this.emit(` call void @xt_stack_set_line(i32 ${this.lineOf(node)})`);
|
|
295
298
|
if (callee.kind === SyntaxKind.Identifier) {
|
|
296
299
|
const identifier = callee as Identifier;
|
|
297
300
|
const symbol = this.binding.symbolOfIdentifier.get(identifier);
|
|
@@ -14,13 +14,29 @@ import { moduleMethods, type ModuleMethods } from "./module.js";
|
|
|
14
14
|
import { statementMethods, type StatementMethods } from "./statements.js";
|
|
15
15
|
import { expressionMethods, type ExpressionMethods } from "./expressions.js";
|
|
16
16
|
import { callMethods, type CallMethods } from "./calls.js";
|
|
17
|
+
import { numericMethods, type NumericMethods } from "./numeric.js";
|
|
18
|
+
import { debugMethods, type DebugMethods } from "./debug.js";
|
|
17
19
|
import type { CodegenOptions, CodegenResult } from "./state.js";
|
|
18
20
|
|
|
19
21
|
export class Generator extends GeneratorContext {}
|
|
20
22
|
|
|
21
|
-
export interface Generator
|
|
23
|
+
export interface Generator
|
|
24
|
+
extends ModuleMethods,
|
|
25
|
+
StatementMethods,
|
|
26
|
+
ExpressionMethods,
|
|
27
|
+
CallMethods,
|
|
28
|
+
NumericMethods,
|
|
29
|
+
DebugMethods {}
|
|
22
30
|
|
|
23
|
-
Object.assign(
|
|
31
|
+
Object.assign(
|
|
32
|
+
Generator.prototype,
|
|
33
|
+
moduleMethods,
|
|
34
|
+
statementMethods,
|
|
35
|
+
expressionMethods,
|
|
36
|
+
callMethods,
|
|
37
|
+
numericMethods,
|
|
38
|
+
debugMethods,
|
|
39
|
+
);
|
|
24
40
|
|
|
25
41
|
export type { CodegenOptions, CodegenResult } from "./state.js";
|
|
26
42
|
|
|
@@ -29,9 +29,16 @@ function isParameterProperty(parameter: Parameter): boolean {
|
|
|
29
29
|
);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/** A readable, IR-symbol-safe suffix for a function name. */
|
|
33
|
+
function symbolSuffix(name: string): string {
|
|
34
|
+
if (name.length === 0 || name.startsWith("<")) return "";
|
|
35
|
+
return name.replace(/[^A-Za-z0-9_]/g, "_").replace(/^_+/, "").slice(0, 32);
|
|
36
|
+
}
|
|
37
|
+
|
|
32
38
|
export interface ModuleMethods {
|
|
33
39
|
run(this: Generator): string;
|
|
34
40
|
emitFunction(this: Generator, fn: FunctionInfo): void;
|
|
41
|
+
emitFunctionReturn(this: Generator, value: string): void;
|
|
35
42
|
emitMain(this: Generator): void;
|
|
36
43
|
functionName(this: Generator, fn: FunctionInfo): string;
|
|
37
44
|
wrapAsync(this: Generator, value: string): string;
|
|
@@ -77,13 +84,32 @@ export const moduleMethods: ModuleMethods = {
|
|
|
77
84
|
];
|
|
78
85
|
const runtimeDeclarations = new Set(RUNTIME_DECLARATIONS);
|
|
79
86
|
const extraDeclarations = [...this.extraDeclarations].filter((line) => !runtimeDeclarations.has(line));
|
|
80
|
-
return [
|
|
87
|
+
return [
|
|
88
|
+
...header,
|
|
89
|
+
...RUNTIME_DECLARATIONS,
|
|
90
|
+
...extraDeclarations,
|
|
91
|
+
"",
|
|
92
|
+
...this.globals,
|
|
93
|
+
"",
|
|
94
|
+
...this.functions,
|
|
95
|
+
"",
|
|
96
|
+
...this.debugMetadataLines(),
|
|
97
|
+
].join("\n");
|
|
81
98
|
},
|
|
82
99
|
|
|
83
100
|
// -- module level --------------------------------------------------------
|
|
84
101
|
|
|
85
102
|
emitFunction(fn: FunctionInfo): void {
|
|
103
|
+
this.unsupportedCount = 0;
|
|
104
|
+
// `async function*` would need an async iterator and a promise-returning
|
|
105
|
+
// `.next()`; the generator/async lowering below would silently produce a
|
|
106
|
+
// synchronous generator instead, so reject it rather than miscompile.
|
|
107
|
+
if (fn.isAsync && fn.isGenerator) {
|
|
108
|
+
this.unsupported(fn.node, "async generator function");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
86
111
|
const name = this.functionName(fn);
|
|
112
|
+
const debugSubprogram = this.debugSubprogramFor(fn);
|
|
87
113
|
const state: FunctionState = {
|
|
88
114
|
fn,
|
|
89
115
|
buffer: [],
|
|
@@ -98,10 +124,14 @@ export const moduleMethods: ModuleMethods = {
|
|
|
98
124
|
finallyStack: [],
|
|
99
125
|
escapePointers: [],
|
|
100
126
|
usesTry: false,
|
|
127
|
+
trackFrames: !fn.isGenerator,
|
|
128
|
+
...(debugSubprogram ? { debugSubprogram } : {}),
|
|
101
129
|
};
|
|
102
130
|
this.current = state;
|
|
131
|
+
if (debugSubprogram) this.setDebugLocation(fn.node);
|
|
103
132
|
|
|
104
|
-
const
|
|
133
|
+
const debugSuffix = debugSubprogram ? ` !dbg ${debugSubprogram}` : "";
|
|
134
|
+
const header = `define i64 @${name}(i64 %this, i64 %env, i32 %argc, i64* %argv)${debugSuffix} {`;
|
|
105
135
|
this.emit(`%saved.env = alloca i64`);
|
|
106
136
|
this.emit(`store i64 %env, i64* %saved.env`);
|
|
107
137
|
this.emit(`%saved.this = alloca i64`);
|
|
@@ -112,6 +142,23 @@ export const moduleMethods: ModuleMethods = {
|
|
|
112
142
|
this.emit(`%saved.argv = alloca i64*`);
|
|
113
143
|
this.emit(`store i64* %argv, i64** %saved.argv`);
|
|
114
144
|
|
|
145
|
+
// Shadow-stack frame: `Error.stack` and fatal-signal dumps walk this.
|
|
146
|
+
if (state.trackFrames) {
|
|
147
|
+
const displayName = fn.isModule
|
|
148
|
+
? "module"
|
|
149
|
+
: fn.isConstructor && fn.classInfo
|
|
150
|
+
? `new ${this.displayNameOf(fn.classInfo.name)}`
|
|
151
|
+
: this.displayNameOf(fn.name.length > 0 ? fn.name : "anonymous");
|
|
152
|
+
const namePointer = this.internStatic(displayName);
|
|
153
|
+
/* Normalise separators like `source_filename`: the compiler may run under
|
|
154
|
+
* Node (`\`) or as a self-hosted binary (`/`), and the IR must stay
|
|
155
|
+
* byte-identical across the two. */
|
|
156
|
+
const filePointer = this.internStatic(this.fileNameOf(fn.node).split("\\").join("/"));
|
|
157
|
+
this.emit(
|
|
158
|
+
` call void @xt_stack_push(i8* ${namePointer}, i8* ${filePointer}, i32 ${this.lineOf(fn.node)})`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
115
162
|
// Parameters.
|
|
116
163
|
const parameterNodes = (fn.node as { parameters?: Parameter[] }).parameters ?? [];
|
|
117
164
|
for (let index = 0; index < fn.params.length; index++) {
|
|
@@ -207,7 +254,7 @@ export const moduleMethods: ModuleMethods = {
|
|
|
207
254
|
this.emitStatements(this.sourceFile.statements);
|
|
208
255
|
} else if (fn.node.kind === SyntaxKind.ArrowFunction && (fn.node as ArrowFunction).body.kind !== SyntaxKind.Block) {
|
|
209
256
|
const value = this.emitExpression((fn.node as ArrowFunction).body as Expression);
|
|
210
|
-
this.
|
|
257
|
+
this.emitFunctionReturn(fn.isAsync ? this.wrapAsync(value) : value);
|
|
211
258
|
} else {
|
|
212
259
|
const body = (fn.node as { body: Block }).body;
|
|
213
260
|
if (body) this.emitStatements(body.statements);
|
|
@@ -215,7 +262,7 @@ export const moduleMethods: ModuleMethods = {
|
|
|
215
262
|
|
|
216
263
|
if (!this.current.terminated) {
|
|
217
264
|
const fallback = fn.isAsync ? this.wrapAsync(i64(XT_UNDEFINED)) : i64(XT_UNDEFINED);
|
|
218
|
-
this.
|
|
265
|
+
this.emitFunctionReturn(fallback);
|
|
219
266
|
}
|
|
220
267
|
|
|
221
268
|
const escapes = state.usesTry
|
|
@@ -229,8 +276,22 @@ export const moduleMethods: ModuleMethods = {
|
|
|
229
276
|
const moduleName = this.functionName(this.binding.moduleFunction);
|
|
230
277
|
// Module bindings and class constructors live in globals; register them as
|
|
231
278
|
// GC roots before the module body runs so a collection cannot free them.
|
|
232
|
-
const
|
|
279
|
+
const stringEntries = [...this.strings.values()];
|
|
280
|
+
const roots = [
|
|
281
|
+
...this.classGlobals.values(),
|
|
282
|
+
...this.moduleGlobals.values(),
|
|
283
|
+
...stringEntries.map((entry) => entry.slot),
|
|
284
|
+
];
|
|
233
285
|
const rootLines = roots.map((name) => ` call void @xt_gc_add_root(i64* ${name})`);
|
|
286
|
+
// Every interned string literal is materialised once here and shared by all
|
|
287
|
+
// uses, so property names and other literals stop allocating per access.
|
|
288
|
+
const stringInit = stringEntries.flatMap((entry) => {
|
|
289
|
+
const register = `%strv.${entry.slot.replace("@.strv.", "")}`;
|
|
290
|
+
return [
|
|
291
|
+
` ${register} = call i64 @xt_string_new(i8* ${entry.label}, i64 ${entry.length})`,
|
|
292
|
+
` store i64 ${register}, i64* ${entry.slot}`,
|
|
293
|
+
];
|
|
294
|
+
});
|
|
234
295
|
this.functions.push(
|
|
235
296
|
[
|
|
236
297
|
"define i32 @main(i32 %argc, i8** %argv) {",
|
|
@@ -239,6 +300,7 @@ export const moduleMethods: ModuleMethods = {
|
|
|
239
300
|
" call void @xt_gc_set_stack_base(i8* %stackbase.ptr)",
|
|
240
301
|
" call void @xt_gc_init()",
|
|
241
302
|
...rootLines,
|
|
303
|
+
...stringInit,
|
|
242
304
|
" call void @xt_gc_arm()",
|
|
243
305
|
" call void @xt_set_program_args(i32 %argc, i8** %argv)",
|
|
244
306
|
` %result = call i64 @${moduleName}(i64 ${i64(XT_UNDEFINED)}, i64 ${i64(XT_UNDEFINED)}, i32 0, i64* null)`,
|
|
@@ -252,7 +314,17 @@ export const moduleMethods: ModuleMethods = {
|
|
|
252
314
|
},
|
|
253
315
|
|
|
254
316
|
functionName(fn: FunctionInfo): string {
|
|
255
|
-
|
|
317
|
+
if (fn.isModule) return "xt_module";
|
|
318
|
+
/* Keep the original name in the symbol so native debuggers, crash
|
|
319
|
+
* backtraces and `--emit ir` are readable (`xt_fn_3_sum`). */
|
|
320
|
+
const suffix = symbolSuffix(this.displayNameOf(fn.name));
|
|
321
|
+
return suffix.length > 0 ? `xt_fn_${fn.id}_${suffix}` : `xt_fn_${fn.id}`;
|
|
322
|
+
},
|
|
323
|
+
|
|
324
|
+
/** Emit the function's return, dropping its shadow-stack frame first. */
|
|
325
|
+
emitFunctionReturn(value: string): void {
|
|
326
|
+
if (this.current.trackFrames) this.emit(" call void @xt_stack_pop()");
|
|
327
|
+
this.terminate(`ret i64 ${value}`);
|
|
256
328
|
},
|
|
257
329
|
|
|
258
330
|
/** Wrap a returned value in a resolved promise (for `async` functions). */
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inline fast paths for numeric operators.
|
|
3
|
+
*
|
|
4
|
+
* Every JavaScript operator used to be a runtime call (`xt_add`, `xt_lt`, …).
|
|
5
|
+
* That is correct but expensive in numeric code: a `for` loop paid three calls
|
|
6
|
+
* per iteration, and clang cannot inline across the generated IR / C runtime
|
|
7
|
+
* boundary. For two unboxed doubles the operation is just a hardware
|
|
8
|
+
* instruction, so emit that inline and keep the runtime call as the cold path
|
|
9
|
+
* for every other combination (strings, BigInt, coercion, …).
|
|
10
|
+
*
|
|
11
|
+
* Doubles are stored bit-for-bit in `xt_value`, so the tag check is an integer
|
|
12
|
+
* mask and the operation is `bitcast` → `fadd`/`fcmp` → `bitcast`.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { i64, XT_FALSE, XT_NUMBER_MASK, XT_TRUE } from "../values.js";
|
|
16
|
+
import type { Generator } from "./generator.js";
|
|
17
|
+
|
|
18
|
+
export interface NumericMethods {
|
|
19
|
+
/**
|
|
20
|
+
* Emit `left <operator> right`, using an inline double fast path when both
|
|
21
|
+
* operands turn out to be numbers and {@link allowFastPath} is set.
|
|
22
|
+
*/
|
|
23
|
+
emitBinaryOperation(
|
|
24
|
+
this: Generator,
|
|
25
|
+
left: string,
|
|
26
|
+
right: string,
|
|
27
|
+
operator: string,
|
|
28
|
+
runtime: string,
|
|
29
|
+
allowFastPath?: boolean,
|
|
30
|
+
): string;
|
|
31
|
+
emitNumericBinary(this: Generator, left: string, right: string, instruction: string, runtime: string): string;
|
|
32
|
+
emitNumericComparison(this: Generator, left: string, right: string, predicate: string, runtime: string): string;
|
|
33
|
+
/** Emit an i1 SSA value that is true when `value` is an unboxed double. */
|
|
34
|
+
isNumberValue(this: Generator, value: string): string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Canonical quiet NaN; the runtime uses the same pattern in `xt_from_double`. */
|
|
38
|
+
const CANONICAL_NAN = 0x7ff8000000000000n;
|
|
39
|
+
|
|
40
|
+
const NUMERIC_ARITHMETIC: Record<string, string | undefined> = {
|
|
41
|
+
"+": "fadd",
|
|
42
|
+
"-": "fsub",
|
|
43
|
+
"*": "fmul",
|
|
44
|
+
"/": "fdiv",
|
|
45
|
+
"%": "frem",
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const NUMERIC_COMPARISON: Record<string, string | undefined> = {
|
|
49
|
+
"<": "olt",
|
|
50
|
+
"<=": "ole",
|
|
51
|
+
">": "ogt",
|
|
52
|
+
">=": "oge",
|
|
53
|
+
"==": "oeq",
|
|
54
|
+
"!=": "une",
|
|
55
|
+
"===": "oeq",
|
|
56
|
+
"!==": "une",
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const numericMethods: NumericMethods = {
|
|
60
|
+
emitBinaryOperation(left, right, operator, runtime, allowFastPath = true) {
|
|
61
|
+
if (allowFastPath) {
|
|
62
|
+
const instruction = NUMERIC_ARITHMETIC[operator];
|
|
63
|
+
if (instruction) return this.emitNumericBinary(left, right, instruction, runtime);
|
|
64
|
+
const predicate = NUMERIC_COMPARISON[operator];
|
|
65
|
+
if (predicate) return this.emitNumericComparison(left, right, predicate, runtime);
|
|
66
|
+
}
|
|
67
|
+
const result = this.reg();
|
|
68
|
+
this.emit(` ${result} = call i64 @${runtime}(i64 ${left}, i64 ${right})`);
|
|
69
|
+
return result;
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
/** Emit an i1 SSA value that is true when `value` is an unboxed double. */
|
|
73
|
+
isNumberValue(value: string): string {
|
|
74
|
+
const masked = this.reg();
|
|
75
|
+
this.emit(` ${masked} = and i64 ${value}, ${i64(XT_NUMBER_MASK)}`);
|
|
76
|
+
const result = this.reg();
|
|
77
|
+
this.emit(` ${result} = icmp ne i64 ${masked}, ${i64(XT_NUMBER_MASK)}`);
|
|
78
|
+
return result;
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
emitNumericBinary(left, right, instruction, runtime) {
|
|
82
|
+
const result = this.alloca();
|
|
83
|
+
const leftIsNumber = this.isNumberValue(left);
|
|
84
|
+
const rightIsNumber = this.isNumberValue(right);
|
|
85
|
+
const bothNumbers = this.reg();
|
|
86
|
+
this.emit(` ${bothNumbers} = and i1 ${leftIsNumber}, ${rightIsNumber}`);
|
|
87
|
+
const fast = this.label("num.fast");
|
|
88
|
+
const slow = this.label("num.slow");
|
|
89
|
+
const end = this.label("num.end");
|
|
90
|
+
this.terminate(`br i1 ${bothNumbers}, label %${fast}, label %${slow}`);
|
|
91
|
+
|
|
92
|
+
this.startBlock(fast);
|
|
93
|
+
const leftDouble = this.reg();
|
|
94
|
+
this.emit(` ${leftDouble} = bitcast i64 ${left} to double`);
|
|
95
|
+
const rightDouble = this.reg();
|
|
96
|
+
this.emit(` ${rightDouble} = bitcast i64 ${right} to double`);
|
|
97
|
+
const computed = this.reg();
|
|
98
|
+
this.emit(` ${computed} = ${instruction} double ${leftDouble}, ${rightDouble}`);
|
|
99
|
+
// Hardware NaNs carry many payloads; the value model only reserves the
|
|
100
|
+
// canonical quiet NaN, and `-NaN` would otherwise collide with XT_UNDEFINED.
|
|
101
|
+
const bits = this.reg();
|
|
102
|
+
this.emit(` ${bits} = bitcast double ${computed} to i64`);
|
|
103
|
+
const unordered = this.reg();
|
|
104
|
+
this.emit(` ${unordered} = fcmp uno double ${computed}, ${computed}`);
|
|
105
|
+
const canonical = this.reg();
|
|
106
|
+
this.emit(` ${canonical} = select i1 ${unordered}, i64 ${i64(CANONICAL_NAN)}, i64 ${bits}`);
|
|
107
|
+
this.emit(` store i64 ${canonical}, i64* ${result}`);
|
|
108
|
+
this.terminate(`br label %${end}`);
|
|
109
|
+
|
|
110
|
+
this.startBlock(slow);
|
|
111
|
+
const called = this.reg();
|
|
112
|
+
this.emit(` ${called} = call i64 @${runtime}(i64 ${left}, i64 ${right})`);
|
|
113
|
+
this.emit(` store i64 ${called}, i64* ${result}`);
|
|
114
|
+
this.terminate(`br label %${end}`);
|
|
115
|
+
|
|
116
|
+
this.startBlock(end);
|
|
117
|
+
const value = this.reg();
|
|
118
|
+
this.emit(` ${value} = load i64, i64* ${result}`);
|
|
119
|
+
return value;
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
emitNumericComparison(left, right, predicate, runtime) {
|
|
123
|
+
const result = this.alloca();
|
|
124
|
+
const leftIsNumber = this.isNumberValue(left);
|
|
125
|
+
const rightIsNumber = this.isNumberValue(right);
|
|
126
|
+
const bothNumbers = this.reg();
|
|
127
|
+
this.emit(` ${bothNumbers} = and i1 ${leftIsNumber}, ${rightIsNumber}`);
|
|
128
|
+
const fast = this.label("cmp.fast");
|
|
129
|
+
const slow = this.label("cmp.slow");
|
|
130
|
+
const end = this.label("cmp.end");
|
|
131
|
+
this.terminate(`br i1 ${bothNumbers}, label %${fast}, label %${slow}`);
|
|
132
|
+
|
|
133
|
+
this.startBlock(fast);
|
|
134
|
+
const leftDouble = this.reg();
|
|
135
|
+
this.emit(` ${leftDouble} = bitcast i64 ${left} to double`);
|
|
136
|
+
const rightDouble = this.reg();
|
|
137
|
+
this.emit(` ${rightDouble} = bitcast i64 ${right} to double`);
|
|
138
|
+
// Ordered comparisons are false for NaN and unordered `!=` is true, which
|
|
139
|
+
// is exactly ECMAScript's behaviour for two numbers.
|
|
140
|
+
const compared = this.reg();
|
|
141
|
+
this.emit(` ${compared} = fcmp ${predicate} double ${leftDouble}, ${rightDouble}`);
|
|
142
|
+
const value = this.reg();
|
|
143
|
+
this.emit(` ${value} = select i1 ${compared}, i64 ${i64(XT_TRUE)}, i64 ${i64(XT_FALSE)}`);
|
|
144
|
+
this.emit(` store i64 ${value}, i64* ${result}`);
|
|
145
|
+
this.terminate(`br label %${end}`);
|
|
146
|
+
|
|
147
|
+
this.startBlock(slow);
|
|
148
|
+
const called = this.reg();
|
|
149
|
+
this.emit(` ${called} = call i64 @${runtime}(i64 ${left}, i64 ${right})`);
|
|
150
|
+
this.emit(` store i64 ${called}, i64* ${result}`);
|
|
151
|
+
this.terminate(`br label %${end}`);
|
|
152
|
+
|
|
153
|
+
this.startBlock(end);
|
|
154
|
+
const merged = this.reg();
|
|
155
|
+
this.emit(` ${merged} = load i64, i64* ${result}`);
|
|
156
|
+
return merged;
|
|
157
|
+
},
|
|
158
|
+
};
|
|
@@ -50,6 +50,16 @@ export interface FunctionState {
|
|
|
50
50
|
thisPtr?: string;
|
|
51
51
|
/** Set when the function contains a `try`, forcing locals to live in memory. */
|
|
52
52
|
usesTry: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* True when the function pushes/pops a shadow-stack frame. Generator bodies
|
|
55
|
+
* are excluded: they switch stacks and can be suspended, so a global shadow
|
|
56
|
+
* stack would attribute their frames to the wrong caller.
|
|
57
|
+
*/
|
|
58
|
+
trackFrames: boolean;
|
|
59
|
+
/** `!DISubprogram` for this function when DWARF emission is enabled. */
|
|
60
|
+
debugSubprogram?: string;
|
|
61
|
+
/** Current `!DILocation` attached to every emitted instruction. */
|
|
62
|
+
debugLocation?: string;
|
|
53
63
|
}
|
|
54
64
|
|
|
55
65
|
export interface CodegenResult {
|
|
@@ -74,6 +84,13 @@ export interface CodegenOptions {
|
|
|
74
84
|
* defaults to the running process; it is overridable for tests.
|
|
75
85
|
*/
|
|
76
86
|
readonly target?: { readonly platform: string; readonly arch: string };
|
|
87
|
+
/**
|
|
88
|
+
* Emit LLVM debug metadata (`!DICompileUnit`, `!DISubprogram`,
|
|
89
|
+
* `!DILocation`) so `clang -g` produces DWARF/CodeView for the generated
|
|
90
|
+
* program. Off by default: the metadata is large and only useful when
|
|
91
|
+
* debugging; `xbintsc build --debug` turns it on.
|
|
92
|
+
*/
|
|
93
|
+
readonly debug?: boolean;
|
|
77
94
|
}
|
|
78
95
|
|
|
79
96
|
export const RUNTIME_DECLARATIONS: readonly string[] = [
|
|
@@ -93,6 +110,7 @@ export const RUNTIME_DECLARATIONS: readonly string[] = [
|
|
|
93
110
|
"declare i64 @xt_this()",
|
|
94
111
|
"declare i64 @xt_new(i64, i32, i64*)",
|
|
95
112
|
"declare i64 @xt_instance_of(i64, i64)",
|
|
113
|
+
"declare i64 @xt_instance_of_builtin(i64, i32)",
|
|
96
114
|
"declare i64 @xt_function_set_prototype(i64, i64)",
|
|
97
115
|
"declare i64 @xt_function_get_prototype(i64)",
|
|
98
116
|
"declare i64 @xt_object_new_with_proto(i64)",
|
|
@@ -225,6 +243,7 @@ export const RUNTIME_DECLARATIONS: readonly string[] = [
|
|
|
225
243
|
"declare i64 @xt_object_entries(i64)",
|
|
226
244
|
"declare i64 @xt_object_assign(i32, i64*)",
|
|
227
245
|
"declare i64 @xt_object_spread(i64, i64)",
|
|
246
|
+
"declare i64 @xt_object_rest(i64, i32, i64*)",
|
|
228
247
|
"declare i64 @xt_call_method(i64, i64, i32, i64*)",
|
|
229
248
|
"declare i64 @xt_math_call(i64, i32, i64*)",
|
|
230
249
|
"declare i64 @xt_parse_int(i32, i64*)",
|
|
@@ -298,6 +317,9 @@ export const RUNTIME_DECLARATIONS: readonly string[] = [
|
|
|
298
317
|
"declare i8* @xt_try_enter()",
|
|
299
318
|
"declare i64 @xt_try_exception(i8*)",
|
|
300
319
|
"declare void @xt_try_leave(i8*)",
|
|
320
|
+
"declare void @xt_stack_push(i8*, i8*, i32)",
|
|
321
|
+
"declare void @xt_stack_pop()",
|
|
322
|
+
"declare void @xt_stack_set_line(i32)",
|
|
301
323
|
"declare void @xt_console_log(i32, i64*)",
|
|
302
324
|
"declare void @xt_console_info(i32, i64*)",
|
|
303
325
|
"declare void @xt_console_warn(i32, i64*)",
|
|
@@ -255,7 +255,7 @@ export const controlFlowStatementMethods: ControlFlowStatementMethods = {
|
|
|
255
255
|
this.runFinallysBeforeExit(0);
|
|
256
256
|
if (this.current.terminated) return;
|
|
257
257
|
this.popTryFramesTo(0);
|
|
258
|
-
this.
|
|
258
|
+
this.emitFunctionReturn(value);
|
|
259
259
|
},
|
|
260
260
|
|
|
261
261
|
/** Emit `xt_try_leave` for every active frame above `depth` (innermost first). */
|
|
@@ -41,6 +41,9 @@ export const statementDispatchMethods: StatementDispatchMethods = {
|
|
|
41
41
|
},
|
|
42
42
|
|
|
43
43
|
emitStatement(statement: Statement): void {
|
|
44
|
+
/* Every instruction emitted while lowering this statement (including
|
|
45
|
+
* nested expressions) points at its source position when DWARF is on. */
|
|
46
|
+
this.setDebugLocation(statement);
|
|
44
47
|
switch (statement.kind) {
|
|
45
48
|
case SyntaxKind.ExpressionStatement:
|
|
46
49
|
this.emitExpression((statement as { expression: Expression }).expression);
|
|
@@ -87,13 +87,19 @@ export const variableStatementMethods: VariableStatementMethods = {
|
|
|
87
87
|
return;
|
|
88
88
|
}
|
|
89
89
|
const pattern = name as ObjectBindingPattern;
|
|
90
|
+
/* Keys named by the pattern are excluded from `...rest`. */
|
|
91
|
+
const excludedKeys: string[] = [];
|
|
90
92
|
for (const element of pattern.elements) {
|
|
91
|
-
const key = element.propertyName ?? (element.name.kind === SyntaxKind.Identifier ? element.name : undefined);
|
|
92
93
|
if (element.dotDotDotToken) {
|
|
93
|
-
this.
|
|
94
|
+
const rest = this.reg();
|
|
95
|
+
const keys = this.emitValueArguments(excludedKeys);
|
|
96
|
+
this.emit(` ${rest} = call i64 @xt_object_rest(i64 ${value}, i32 ${keys.argc}, i64* ${keys.ptr})`);
|
|
97
|
+
this.emitBindingPattern(element.name, rest);
|
|
94
98
|
continue;
|
|
95
99
|
}
|
|
100
|
+
const key = element.propertyName ?? (element.name.kind === SyntaxKind.Identifier ? element.name : undefined);
|
|
96
101
|
const keyValue = this.stringValue(key ? propertyNameText(key) : "undefined");
|
|
102
|
+
excludedKeys.push(keyValue);
|
|
97
103
|
let elementValue = this.reg();
|
|
98
104
|
this.emit(` ${elementValue} = call i64 @xt_get(i64 ${value}, i64 ${keyValue})`);
|
|
99
105
|
if (element.initializer) elementValue = this.emitBindingDefault(elementValue, element.initializer);
|
|
@@ -22,6 +22,32 @@ export function requiresSetjmpex(platform: string, arch: string): boolean {
|
|
|
22
22
|
return platform === "win32" && arch === "arm64";
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Built-in constructors usable on the right of `instanceof`, mapped to the id
|
|
27
|
+
* `xt_instance_of_builtin` expects. Keep in sync with the `XT_INSTANCE_*` enum
|
|
28
|
+
* in `runtime/xt_builtins.c`; the typed-array ids follow the runtime's
|
|
29
|
+
* `XT_TA_*` order (Uint8Array first).
|
|
30
|
+
*/
|
|
31
|
+
export const BUILTIN_INSTANCEOF: Readonly<Record<string, number>> = {
|
|
32
|
+
Array: 0,
|
|
33
|
+
Object: 1,
|
|
34
|
+
Function: 2,
|
|
35
|
+
Map: 3,
|
|
36
|
+
Set: 4,
|
|
37
|
+
Date: 5,
|
|
38
|
+
RegExp: 6,
|
|
39
|
+
Promise: 7,
|
|
40
|
+
Uint8Array: 16,
|
|
41
|
+
Int8Array: 17,
|
|
42
|
+
Uint8ClampedArray: 18,
|
|
43
|
+
Uint16Array: 19,
|
|
44
|
+
Int16Array: 20,
|
|
45
|
+
Uint32Array: 21,
|
|
46
|
+
Int32Array: 22,
|
|
47
|
+
Float32Array: 23,
|
|
48
|
+
Float64Array: 24,
|
|
49
|
+
};
|
|
50
|
+
|
|
25
51
|
export const BINARY_RUNTIME: Record<string, string | undefined> = {
|
|
26
52
|
[BinaryOperator.Add]: "xt_add",
|
|
27
53
|
[BinaryOperator.Subtract]: "xt_sub",
|