xbintsc 0.3.79 → 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 +13 -5
- package/README.zh-CN.md +13 -6
- 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 +49 -58
- 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/extensions/gui/index.js +10 -2
- package/dist/src/extensions/gui/index.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/gui-example-svg.test.d.ts +21 -0
- package/dist/tests/e2e/gui-example-svg.test.js +105 -0
- package/dist/tests/e2e/gui-example-svg.test.js.map +1 -0
- package/dist/tests/e2e/gui-transform.test.d.ts +7 -0
- package/dist/tests/e2e/gui-transform.test.js +198 -0
- package/dist/tests/e2e/gui-transform.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 +7 -0
- package/doc/ai/contributing.md +7 -1
- package/doc/ai/extensions.md +5 -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/extensions.md +5 -1
- package/doc/ai/zh-CN/language-support.md +4 -4
- package/doc/ai/zh-CN/troubleshooting.md +35 -2
- package/doc/gui/animation.md +53 -2
- package/doc/gui/api.md +1 -1
- package/doc/gui/dom.md +6 -1
- package/doc/gui/input.md +4 -1
- package/doc/gui/known-issues.md +16 -4
- package/doc/gui/layout.md +36 -0
- package/doc/gui/rendering.md +15 -0
- package/doc/gui.md +32 -3
- package/doc/implemented.md +4 -4
- package/doc/requirements.md +13 -4
- package/doc/unimplemented.md +11 -10
- package/doc/zh-CN/DESIGN.md +15 -9
- package/doc/zh-CN/gui/animation.md +49 -2
- package/doc/zh-CN/gui/api.md +1 -1
- package/doc/zh-CN/gui/dom.md +6 -1
- package/doc/zh-CN/gui/input.md +2 -0
- package/doc/zh-CN/gui/known-issues.md +12 -2
- package/doc/zh-CN/gui/layout.md +31 -0
- package/doc/zh-CN/gui/rendering.md +11 -0
- package/doc/zh-CN/gui.md +28 -3
- package/doc/zh-CN/implemented.md +4 -4
- package/doc/zh-CN/requirements.md +12 -3
- package/doc/zh-CN/unimplemented.md +10 -9
- package/package.json +3 -1
- package/runtime/ext_gui/document.cpp +21 -3
- package/runtime/ext_gui/document.h +4 -1
- package/runtime/ext_gui/dom_api_node.cpp +6 -4
- package/runtime/ext_gui/layout.cpp +78 -1
- package/runtime/ext_gui/layout.h +21 -0
- package/runtime/ext_gui/paint.cpp +48 -15
- package/runtime/ext_gui/paint.h +8 -2
- package/runtime/ext_gui/renderer.cpp +4 -2
- package/runtime/ext_gui/renderer_image.cpp +2 -1
- package/runtime/ext_gui/renderer_internal.h +6 -1
- package/runtime/ext_gui/renderer_text.cpp +37 -15
- package/runtime/ext_gui/style.h +10 -0
- package/runtime/ext_gui/style_print.cpp +6 -0
- package/runtime/ext_gui/style_values.cpp +43 -0
- package/runtime/ext_gui/transform.cpp +412 -0
- package/runtime/ext_gui/transform.h +112 -0
- package/runtime/ext_gui/window.cpp +6 -4
- 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 +68 -56
- package/src/driver/fingerprint.ts +85 -0
- package/src/driver/toolchain-provider.ts +17 -16
- package/src/driver/toolchain.ts +210 -21
- package/src/extensions/gui/index.ts +10 -2
- package/src/parser/expressions.ts +20 -1
|
@@ -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",
|
|
@@ -169,6 +169,32 @@ export function bundleModules(
|
|
|
169
169
|
|
|
170
170
|
// Phase 4: concatenate statements, dropping imports and pure export wrappers.
|
|
171
171
|
const merged: Statement[] = [];
|
|
172
|
+
/* Every node keeps the module it was parsed from, so a later codegen
|
|
173
|
+
* diagnostic can name the right file and line instead of pointing into the
|
|
174
|
+
* entry module (whose text happens to start at the same offsets). */
|
|
175
|
+
const origins = new Map<object, string>();
|
|
176
|
+
const trackOrigin = (record: ModuleRecord, root: Node): void => {
|
|
177
|
+
const visit = (node: Node): void => {
|
|
178
|
+
origins.set(node, record.path);
|
|
179
|
+
const fields = node as unknown as Record<string, unknown>;
|
|
180
|
+
for (const key of Object.keys(fields)) {
|
|
181
|
+
if (key === "kind" || key === "start" || key === "end" || key === "text" || key === "value" || key === "raw") {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const value = fields[key];
|
|
185
|
+
if (Array.isArray(value)) {
|
|
186
|
+
for (const child of value) if (isNode(child)) visit(child);
|
|
187
|
+
} else if (isNode(value)) {
|
|
188
|
+
visit(value);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
visit(root);
|
|
193
|
+
};
|
|
194
|
+
const push = (record: ModuleRecord, statement: Statement): void => {
|
|
195
|
+
trackOrigin(record, statement);
|
|
196
|
+
merged.push(statement);
|
|
197
|
+
};
|
|
172
198
|
const first = records[records.length - 1]!;
|
|
173
199
|
for (const record of records) {
|
|
174
200
|
for (const statement of record.sourceFile.statements) {
|
|
@@ -181,25 +207,25 @@ export function bundleModules(
|
|
|
181
207
|
externalSpecifiers,
|
|
182
208
|
);
|
|
183
209
|
if (resolution.kind === "external") {
|
|
184
|
-
|
|
210
|
+
push(record, statement);
|
|
185
211
|
} else {
|
|
186
212
|
const synthetic = namespaceStatements.get(statement);
|
|
187
|
-
if (synthetic)
|
|
213
|
+
if (synthetic) push(record, synthetic);
|
|
188
214
|
}
|
|
189
215
|
continue;
|
|
190
216
|
}
|
|
191
217
|
if (statement.kind === SyntaxKind.ExportDeclaration) {
|
|
192
218
|
const declaration = statement as ExportDeclaration;
|
|
193
219
|
const inner = (declaration as unknown as { declaration?: Statement }).declaration;
|
|
194
|
-
if (inner)
|
|
220
|
+
if (inner) push(record, inner);
|
|
195
221
|
continue;
|
|
196
222
|
}
|
|
197
223
|
if (statement.kind === SyntaxKind.ExportAssignment) {
|
|
198
224
|
const assignment = statement as ExportAssignment;
|
|
199
|
-
|
|
225
|
+
push(record, exportAssignmentToStatement(record, assignment));
|
|
200
226
|
continue;
|
|
201
227
|
}
|
|
202
|
-
|
|
228
|
+
push(record, statement);
|
|
203
229
|
}
|
|
204
230
|
}
|
|
205
231
|
|
|
@@ -217,6 +243,9 @@ export function bundleModules(
|
|
|
217
243
|
kind: SyntaxKind.SourceFile,
|
|
218
244
|
statements: merged,
|
|
219
245
|
fileName: first.path,
|
|
246
|
+
origins,
|
|
247
|
+
moduleTexts: new Map(records.map((record) => [record.path, record.text])),
|
|
248
|
+
modulePrefixes: records.map((record) => record.prefix),
|
|
220
249
|
text: records.map((record) => record.text).join("\n"),
|
|
221
250
|
start: 0,
|
|
222
251
|
end: 0,
|
|
@@ -248,6 +277,11 @@ function collectBindingIdentifiers(name: BindingName, out: string[]): void {
|
|
|
248
277
|
}
|
|
249
278
|
}
|
|
250
279
|
|
|
280
|
+
/** True for anything shaped like an AST node (constructed nodes carry no trivia). */
|
|
281
|
+
function isNode(value: unknown): value is Node {
|
|
282
|
+
return !!value && typeof value === "object" && "kind" in (value as object);
|
|
283
|
+
}
|
|
284
|
+
|
|
251
285
|
/** Lower `import * as ns from "./mod"` to `const m0_ns = { ...exports }`. */
|
|
252
286
|
function namespaceImportToStatement(
|
|
253
287
|
statement: Statement,
|