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
|
@@ -54,11 +54,35 @@ export class GeneratorContext {
|
|
|
54
54
|
readonly importDefaults = new Map<number, ModuleExport>();
|
|
55
55
|
readonly globals: string[] = [];
|
|
56
56
|
readonly functions: string[] = [];
|
|
57
|
-
readonly strings = new Map<string, { label: string; length: number }>();
|
|
57
|
+
readonly strings = new Map<string, { label: string; length: number; slot: string }>();
|
|
58
58
|
readonly classGlobals = new Map<number, string>();
|
|
59
59
|
readonly moduleGlobals = new Map<number, string>();
|
|
60
60
|
readonly extraDeclarations = new Set<string>();
|
|
61
|
+
/** Static C strings used only as metadata (stack frame names/files). */
|
|
62
|
+
readonly staticStrings = new Map<string, string>();
|
|
63
|
+
/** Lazily built line-start indexes per source file, for stack frames. */
|
|
64
|
+
readonly lineStartsByFile = new Map<string, number[]>();
|
|
61
65
|
stringCounter = 0;
|
|
66
|
+
staticCounter = 0;
|
|
67
|
+
/**
|
|
68
|
+
* How many `UnsupportedFeature` diagnostics the function currently being
|
|
69
|
+
* emitted produced. Unsupported constructs can leave the generator with a
|
|
70
|
+
* partially-bound tree; reporting the resulting invariant failures as
|
|
71
|
+
* "Internal: …" only buries the actionable error, so they are suppressed
|
|
72
|
+
* while this is non-zero.
|
|
73
|
+
*/
|
|
74
|
+
unsupportedCount = 0;
|
|
75
|
+
/** Emit DWARF debug metadata (`--debug`). */
|
|
76
|
+
readonly debug: boolean;
|
|
77
|
+
/** Numbered metadata definitions, emitted after the functions. */
|
|
78
|
+
metadataCounter = 0;
|
|
79
|
+
readonly metadata: string[] = [];
|
|
80
|
+
readonly debugFiles = new Map<string, string>();
|
|
81
|
+
readonly debugSubprograms = new Map<number, string>();
|
|
82
|
+
readonly debugLocations = new Map<string, string>();
|
|
83
|
+
debugUnit?: string;
|
|
84
|
+
debugFlags: string[] = [];
|
|
85
|
+
debugSubroutineType?: string;
|
|
62
86
|
current!: FunctionState;
|
|
63
87
|
|
|
64
88
|
constructor(sourceFile: SourceFileNode, diagnostics: DiagnosticBag, options: CodegenOptions = {}) {
|
|
@@ -69,6 +93,7 @@ export class GeneratorContext {
|
|
|
69
93
|
this.modules = options.modules ?? {};
|
|
70
94
|
this.moduleHints = options.moduleHints ?? {};
|
|
71
95
|
this.target = options.target ?? { platform: process.platform, arch: process.arch };
|
|
96
|
+
this.debug = options.debug === true;
|
|
72
97
|
this.resolveImports();
|
|
73
98
|
}
|
|
74
99
|
|
|
@@ -120,7 +145,7 @@ export class GeneratorContext {
|
|
|
120
145
|
DiagnosticCode.ModuleNotFound,
|
|
121
146
|
`Module '"${specifier}"' has no exported member '${importedName}'`,
|
|
122
147
|
specifierNode.name,
|
|
123
|
-
this.
|
|
148
|
+
this.fileNameOf(specifierNode.name),
|
|
124
149
|
);
|
|
125
150
|
this.missingModuleSymbols.add(symbol.id);
|
|
126
151
|
}
|
|
@@ -148,7 +173,7 @@ export class GeneratorContext {
|
|
|
148
173
|
DiagnosticCode.ModuleNotFound,
|
|
149
174
|
`module '${specifier}' is provided by the '${provider}' extension; pass --ext ${provider}`,
|
|
150
175
|
declaration.moduleSpecifier,
|
|
151
|
-
this.
|
|
176
|
+
this.fileNameOf(declaration.moduleSpecifier),
|
|
152
177
|
);
|
|
153
178
|
this.markMissingModuleSymbols(declaration);
|
|
154
179
|
return;
|
|
@@ -165,7 +190,7 @@ export class GeneratorContext {
|
|
|
165
190
|
`module '${specifier}' is not supported: xbintsc can import built-in platform modules, ` +
|
|
166
191
|
"relative '.ts' files and ESM packages under node_modules; CommonJS packages are not supported",
|
|
167
192
|
declaration.moduleSpecifier,
|
|
168
|
-
this.
|
|
193
|
+
this.fileNameOf(declaration.moduleSpecifier),
|
|
169
194
|
);
|
|
170
195
|
for (const symbol of symbols) this.missingModuleSymbols.add(symbol.id);
|
|
171
196
|
}
|
|
@@ -230,11 +255,12 @@ export class GeneratorContext {
|
|
|
230
255
|
// -- emission primitives -------------------------------------------------
|
|
231
256
|
|
|
232
257
|
emit(line: string): void {
|
|
233
|
-
this.current.
|
|
258
|
+
const location = this.debug ? this.current.debugLocation : undefined;
|
|
259
|
+
this.current.buffer.push(location === undefined ? line : `${line}, !dbg ${location}`);
|
|
234
260
|
}
|
|
235
261
|
|
|
236
262
|
terminate(line: string): void {
|
|
237
|
-
this.
|
|
263
|
+
this.emit(` ${line}`);
|
|
238
264
|
this.current.terminated = true;
|
|
239
265
|
}
|
|
240
266
|
|
|
@@ -283,6 +309,19 @@ export class GeneratorContext {
|
|
|
283
309
|
return ptr;
|
|
284
310
|
}
|
|
285
311
|
|
|
312
|
+
/** Lay already-computed values out as a contiguous `i64*` argument buffer. */
|
|
313
|
+
emitValueArguments(values: readonly string[]): { argc: string; ptr: string } {
|
|
314
|
+
if (values.length === 0) return { argc: "0", ptr: "null" };
|
|
315
|
+
const ptr = `%args${this.current.allocas.length}`;
|
|
316
|
+
this.current.allocas.push(`${ptr} = alloca i64, i32 ${values.length}`);
|
|
317
|
+
for (let index = 0; index < values.length; index++) {
|
|
318
|
+
const slot = this.reg();
|
|
319
|
+
this.emit(` ${slot} = getelementptr i64, i64* ${ptr}, i32 ${index}`);
|
|
320
|
+
this.emit(` store i64 ${values[index]}, i64* ${slot}`);
|
|
321
|
+
}
|
|
322
|
+
return { argc: String(values.length), ptr };
|
|
323
|
+
}
|
|
324
|
+
|
|
286
325
|
const(value: bigint): string {
|
|
287
326
|
return i64(value);
|
|
288
327
|
}
|
|
@@ -333,11 +372,15 @@ export class GeneratorContext {
|
|
|
333
372
|
const slot = this.current.slots.get(symbol.id);
|
|
334
373
|
if (!slot) {
|
|
335
374
|
// Referenced from an inner function without a capture slot: this should
|
|
336
|
-
// be impossible once the binder has run, but stay defensive.
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
375
|
+
// be impossible once the binder has run, but stay defensive. After an
|
|
376
|
+
// unsupported construct the tree can be partially bound, so do not add
|
|
377
|
+
// an "Internal" error on top of the actionable one.
|
|
378
|
+
if (this.unsupportedCount === 0) {
|
|
379
|
+
this.diagnostics.error(
|
|
380
|
+
DiagnosticCode.CodegenError,
|
|
381
|
+
`Internal: no slot for '${symbol.name}' in ${this.current.fn.name}`,
|
|
382
|
+
);
|
|
383
|
+
}
|
|
341
384
|
return i64(XT_UNDEFINED);
|
|
342
385
|
}
|
|
343
386
|
const value = this.reg();
|
|
@@ -362,7 +405,9 @@ export class GeneratorContext {
|
|
|
362
405
|
}
|
|
363
406
|
const slot = this.current.slots.get(symbol.id);
|
|
364
407
|
if (!slot) {
|
|
365
|
-
this.
|
|
408
|
+
if (this.unsupportedCount === 0) {
|
|
409
|
+
this.diagnostics.error(DiagnosticCode.CodegenError, `Internal: no slot for '${symbol.name}'`);
|
|
410
|
+
}
|
|
366
411
|
return;
|
|
367
412
|
}
|
|
368
413
|
if (!slot.boxed) {
|
|
@@ -390,28 +435,94 @@ export class GeneratorContext {
|
|
|
390
435
|
stringValue(text: string): string {
|
|
391
436
|
const entry = this.internString(text);
|
|
392
437
|
const result = this.reg();
|
|
393
|
-
|
|
438
|
+
// The value is created once in `main` and shared by every use, so a string
|
|
439
|
+
// literal (especially a property name in a hot loop) no longer allocates on
|
|
440
|
+
// each evaluation.
|
|
441
|
+
this.emit(` ${result} = load i64, i64* ${entry.slot}`);
|
|
394
442
|
return result;
|
|
395
443
|
}
|
|
396
444
|
|
|
397
|
-
internString(text: string): { label: string; length: number } {
|
|
445
|
+
internString(text: string): { label: string; length: number; slot: string } {
|
|
398
446
|
const existing = this.strings.get(text);
|
|
399
447
|
if (existing) return existing;
|
|
400
|
-
const
|
|
448
|
+
const index = this.stringCounter++;
|
|
449
|
+
const label = `@.str.${index}`;
|
|
450
|
+
const slot = `@.strv.${index}`;
|
|
401
451
|
const bytes = utf8Bytes(text);
|
|
402
|
-
const entry = { label, length: bytes.length };
|
|
452
|
+
const entry = { label, length: bytes.length, slot };
|
|
403
453
|
this.strings.set(text, entry);
|
|
404
454
|
this.globals.push(`${label} = private unnamed_addr constant [${bytes.length + 1} x i8] c"${escapeBytes(bytes)}\\00"`);
|
|
455
|
+
// A GC-rooted global holding the runtime string value, initialised in main.
|
|
456
|
+
this.globals.push(`${slot} = internal global i64 0`);
|
|
405
457
|
return entry;
|
|
406
458
|
}
|
|
407
459
|
|
|
460
|
+
/**
|
|
461
|
+
* Intern a string purely as static module data (no `xt_string` value), for
|
|
462
|
+
* metadata such as shadow-stack frame names and file paths.
|
|
463
|
+
*/
|
|
464
|
+
internStatic(text: string): string {
|
|
465
|
+
const existing = this.staticStrings.get(text);
|
|
466
|
+
if (existing) return existing;
|
|
467
|
+
const label = `@.meta.${this.staticCounter++}`;
|
|
468
|
+
const bytes = utf8Bytes(text);
|
|
469
|
+
this.staticStrings.set(text, label);
|
|
470
|
+
this.globals.push(`${label} = private unnamed_addr constant [${bytes.length + 1} x i8] c"${escapeBytes(bytes)}\\00"`);
|
|
471
|
+
return label;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/** Strip a bundler-generated module prefix (`m1_`) from a display name. */
|
|
475
|
+
displayNameOf(name: string): string {
|
|
476
|
+
for (const prefix of this.sourceFile.modulePrefixes ?? []) {
|
|
477
|
+
if (name.startsWith(prefix) && name.length > prefix.length) return name.slice(prefix.length);
|
|
478
|
+
}
|
|
479
|
+
return name;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/** 1-based line and column of `node` in the module it came from. */
|
|
483
|
+
positionOf(node: Node): { line: number; column: number } {
|
|
484
|
+
const file = this.fileNameOf(node);
|
|
485
|
+
const text = this.sourceFile.moduleTexts?.get(file) ?? this.sourceFile.text;
|
|
486
|
+
let starts = this.lineStartsByFile.get(file);
|
|
487
|
+
if (!starts) {
|
|
488
|
+
starts = [0];
|
|
489
|
+
for (let index = 0; index < text.length; index++) {
|
|
490
|
+
if (text.charCodeAt(index) === 10) starts.push(index + 1);
|
|
491
|
+
}
|
|
492
|
+
this.lineStartsByFile.set(file, starts);
|
|
493
|
+
}
|
|
494
|
+
const offset = Math.max(0, Math.min(node.start, text.length));
|
|
495
|
+
let low = 0;
|
|
496
|
+
let high = starts.length - 1;
|
|
497
|
+
while (low < high) {
|
|
498
|
+
const middle = (low + high + 1) >> 1;
|
|
499
|
+
if (starts[middle]! <= offset) low = middle;
|
|
500
|
+
else high = middle - 1;
|
|
501
|
+
}
|
|
502
|
+
return { line: low + 1, column: offset - starts[low]! + 1 };
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/** 1-based line of `node` in the module it came from (for stack frames). */
|
|
506
|
+
lineOf(node: Node): number {
|
|
507
|
+
return this.positionOf(node).line;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/** The source file a node belongs to. Bundling merges every module into one
|
|
511
|
+
* `SourceFile` while keeping module-local offsets, so the origin map is what
|
|
512
|
+
* keeps diagnostics pointing at the file the user actually wrote.
|
|
513
|
+
*/
|
|
514
|
+
fileNameOf(node: Node): string {
|
|
515
|
+
return this.sourceFile.origins?.get(node) ?? this.sourceFile.fileName;
|
|
516
|
+
}
|
|
517
|
+
|
|
408
518
|
unsupported(node: Node, what: string): void {
|
|
409
519
|
const label = kindName(node.kind);
|
|
520
|
+
this.unsupportedCount++;
|
|
410
521
|
this.diagnostics.error(
|
|
411
522
|
DiagnosticCode.UnsupportedFeature,
|
|
412
523
|
`xbintsc does not yet support this ${what} (${label})`,
|
|
413
524
|
node,
|
|
414
|
-
this.
|
|
525
|
+
this.fileNameOf(node),
|
|
415
526
|
);
|
|
416
527
|
}
|
|
417
528
|
|
|
@@ -429,7 +540,7 @@ export class GeneratorContext {
|
|
|
429
540
|
DiagnosticCode.UnsupportedFeature,
|
|
430
541
|
`CommonJS \`require()\` is not supported; ${replacement}`,
|
|
431
542
|
node,
|
|
432
|
-
this.
|
|
543
|
+
this.fileNameOf(node),
|
|
433
544
|
);
|
|
434
545
|
}
|
|
435
546
|
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DWARF/CodeView metadata for `xbintsc build --debug`.
|
|
3
|
+
*
|
|
4
|
+
* The generated IR declares a compile unit, one `!DISubprogram` per compiled
|
|
5
|
+
* function and one cached `!DILocation` per statement position. With `-g`,
|
|
6
|
+
* clang turns these into DWARF (Linux/macOS) or CodeView (Windows), so a native
|
|
7
|
+
* debugger maps instructions back to the TypeScript source.
|
|
8
|
+
*
|
|
9
|
+
* Metadata is only emitted when debug is enabled: the nodes are large, and the
|
|
10
|
+
* default IR must stay byte-identical for the self-hosting fixpoint.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { Node } from "../../ast/nodes.js";
|
|
14
|
+
import type { FunctionInfo } from "../../binder/binder.js";
|
|
15
|
+
import { escapeBytes, utf8Bytes } from "./tables.js";
|
|
16
|
+
import type { Generator } from "./generator.js";
|
|
17
|
+
|
|
18
|
+
export interface DebugMethods {
|
|
19
|
+
/** Allocate a numbered metadata node (`!0`, `!1`, …). */
|
|
20
|
+
metadataId(this: Generator): string;
|
|
21
|
+
/** Allocate a metadata node and register its definition. */
|
|
22
|
+
addMetadata(this: Generator, definition: string): string;
|
|
23
|
+
/** `!DIFile` for a source path, normalising separators like `source_filename`. */
|
|
24
|
+
debugFileFor(this: Generator, fileName: string): string;
|
|
25
|
+
/** `!DISubprogram` for a compiled function (created on first use). */
|
|
26
|
+
debugSubprogramFor(this: Generator, fn: FunctionInfo): string | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Point subsequent instructions at `node`'s source position. Locations are
|
|
29
|
+
* cached per function/line/column so a statement emits one metadata node
|
|
30
|
+
* instead of one per instruction.
|
|
31
|
+
*/
|
|
32
|
+
setDebugLocation(this: Generator, node: Node): void;
|
|
33
|
+
/** Module-level metadata declarations and definitions (`--debug`). */
|
|
34
|
+
debugMetadataLines(this: Generator): string[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function escapeMetadata(text: string): string {
|
|
38
|
+
return escapeBytes(utf8Bytes(text));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Create the compile unit and module flags once. LLVM requires the `Debug Info
|
|
43
|
+
* Version` flag for any `!dbg` attachment, and clang only emits DWARF/CodeView
|
|
44
|
+
* for a module that declares a compile unit.
|
|
45
|
+
*/
|
|
46
|
+
function ensureDebugUnit(self: Generator): void {
|
|
47
|
+
if (self.debugUnit !== undefined) return;
|
|
48
|
+
const entryFile = self.debugFileFor(self.sourceFile.fileName);
|
|
49
|
+
const unit = self.addMetadata(
|
|
50
|
+
'distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: ' +
|
|
51
|
+
`${entryFile}, producer: "xbintsc", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)`,
|
|
52
|
+
);
|
|
53
|
+
self.debugUnit = unit;
|
|
54
|
+
self.debugFlags.push(
|
|
55
|
+
self.addMetadata('!{i32 7, !"Dwarf Version", i32 4}'),
|
|
56
|
+
self.addMetadata('!{i32 2, !"Debug Info Version", i32 3}'),
|
|
57
|
+
);
|
|
58
|
+
const types = self.addMetadata("!{null}");
|
|
59
|
+
self.debugSubroutineType = self.addMetadata(`!DISubroutineType(types: ${types})`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const debugMethods: DebugMethods = {
|
|
63
|
+
metadataId() {
|
|
64
|
+
return `!${this.metadataCounter++}`;
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
addMetadata(definition) {
|
|
68
|
+
const id = this.metadataId();
|
|
69
|
+
this.metadata.push(`${id} = ${definition}`);
|
|
70
|
+
return id;
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
debugFileFor(fileName) {
|
|
74
|
+
const normalized = fileName.split("\\").join("/");
|
|
75
|
+
const existing = this.debugFiles.get(normalized);
|
|
76
|
+
if (existing) return existing;
|
|
77
|
+
const slash = normalized.lastIndexOf("/");
|
|
78
|
+
const directory = slash >= 0 ? normalized.slice(0, slash) : ".";
|
|
79
|
+
const name = slash >= 0 ? normalized.slice(slash + 1) : normalized;
|
|
80
|
+
const id = this.addMetadata(
|
|
81
|
+
`!DIFile(filename: "${escapeMetadata(name)}", directory: "${escapeMetadata(directory)}")`,
|
|
82
|
+
);
|
|
83
|
+
this.debugFiles.set(normalized, id);
|
|
84
|
+
return id;
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
debugSubprogramFor(fn) {
|
|
88
|
+
if (!this.debug) return undefined;
|
|
89
|
+
const existing = this.debugSubprograms.get(fn.id);
|
|
90
|
+
if (existing) return existing;
|
|
91
|
+
ensureDebugUnit(this);
|
|
92
|
+
const file = this.debugFileFor(this.fileNameOf(fn.node));
|
|
93
|
+
const position = this.positionOf(fn.node);
|
|
94
|
+
const displayName = fn.isModule
|
|
95
|
+
? "module"
|
|
96
|
+
: fn.isConstructor && fn.classInfo
|
|
97
|
+
? `new ${this.displayNameOf(fn.classInfo.name)}`
|
|
98
|
+
: this.displayNameOf(fn.name.length > 0 ? fn.name : "anonymous");
|
|
99
|
+
const subprogram = this.addMetadata(
|
|
100
|
+
'distinct !DISubprogram(name: ' +
|
|
101
|
+
`"${escapeMetadata(displayName)}", scope: ${this.debugUnit}, file: ${file}, ` +
|
|
102
|
+
`line: ${position.line}, type: ${this.debugSubroutineType}, scopeLine: ${position.line}, ` +
|
|
103
|
+
`spFlags: DISPFlagDefinition, unit: ${this.debugUnit})`,
|
|
104
|
+
);
|
|
105
|
+
this.debugSubprograms.set(fn.id, subprogram);
|
|
106
|
+
return subprogram;
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
setDebugLocation(node) {
|
|
110
|
+
if (!this.debug || this.current.debugSubprogram === undefined) return;
|
|
111
|
+
const position = this.positionOf(node);
|
|
112
|
+
const key = `${this.current.debugSubprogram}:${position.line}:${position.column}`;
|
|
113
|
+
let location = this.debugLocations.get(key);
|
|
114
|
+
if (!location) {
|
|
115
|
+
location = this.addMetadata(
|
|
116
|
+
`!DILocation(line: ${position.line}, column: ${position.column}, scope: ${this.current.debugSubprogram})`,
|
|
117
|
+
);
|
|
118
|
+
this.debugLocations.set(key, location);
|
|
119
|
+
}
|
|
120
|
+
this.current.debugLocation = location;
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
debugMetadataLines() {
|
|
124
|
+
if (!this.debug || this.debugUnit === undefined) return [];
|
|
125
|
+
return [
|
|
126
|
+
`!llvm.dbg.cu = !{${this.debugUnit}}`,
|
|
127
|
+
`!llvm.module.flags = !{${this.debugFlags.join(", ")}}`,
|
|
128
|
+
...this.metadata,
|
|
129
|
+
"",
|
|
130
|
+
];
|
|
131
|
+
},
|
|
132
|
+
};
|
|
@@ -58,9 +58,7 @@ export const assignmentExpressionMethods: AssignmentExpressionMethods = {
|
|
|
58
58
|
this.unsupported(node, `assignment operator '${operator}'`);
|
|
59
59
|
return i64(XT_UNDEFINED);
|
|
60
60
|
}
|
|
61
|
-
|
|
62
|
-
this.emit(` ${result} = call i64 @${runtime}(i64 ${current}, i64 ${right})`);
|
|
63
|
-
value = result;
|
|
61
|
+
value = this.emitBinaryOperation(current, right, binaryOperator, runtime);
|
|
64
62
|
}
|
|
65
63
|
this.emitAssignmentTarget(target, value);
|
|
66
64
|
return value;
|
|
@@ -120,7 +118,7 @@ export const assignmentExpressionMethods: AssignmentExpressionMethods = {
|
|
|
120
118
|
? `Cannot assign to '${symbol.name}' because it is an import.`
|
|
121
119
|
: `Cannot assign to '${symbol.name}' because it is a constant.`,
|
|
122
120
|
identifier,
|
|
123
|
-
this.
|
|
121
|
+
this.fileNameOf(identifier),
|
|
124
122
|
);
|
|
125
123
|
return;
|
|
126
124
|
}
|
|
@@ -187,9 +185,13 @@ export const assignmentExpressionMethods: AssignmentExpressionMethods = {
|
|
|
187
185
|
|
|
188
186
|
/** `{ a, b } = value` / `for ({ a } of xs)`: assign each property of `value`. */
|
|
189
187
|
emitObjectAssignmentTarget(pattern: ObjectLiteralExpression, value: string): void {
|
|
188
|
+
const excludedKeys: string[] = [];
|
|
190
189
|
for (const property of pattern.properties) {
|
|
191
190
|
if (property.kind === SyntaxKind.SpreadElement) {
|
|
192
|
-
this.
|
|
191
|
+
const rest = this.reg();
|
|
192
|
+
const keys = this.emitValueArguments(excludedKeys);
|
|
193
|
+
this.emit(` ${rest} = call i64 @xt_object_rest(i64 ${value}, i32 ${keys.argc}, i64* ${keys.ptr})`);
|
|
194
|
+
this.emitAssignmentTarget((property as SpreadElement).expression, rest);
|
|
193
195
|
continue;
|
|
194
196
|
}
|
|
195
197
|
if (property.kind === SyntaxKind.PropertyAssignment) {
|
|
@@ -200,6 +202,7 @@ export const assignmentExpressionMethods: AssignmentExpressionMethods = {
|
|
|
200
202
|
} else {
|
|
201
203
|
key = this.stringValue(propertyNameText(assignment.name));
|
|
202
204
|
}
|
|
205
|
+
excludedKeys.push(key);
|
|
203
206
|
let target: Expression = assignment.initializer;
|
|
204
207
|
let fallback: Expression | undefined;
|
|
205
208
|
if (isPlainAssignment(target)) {
|
|
@@ -215,6 +218,7 @@ export const assignmentExpressionMethods: AssignmentExpressionMethods = {
|
|
|
215
218
|
}
|
|
216
219
|
const shorthand = property as ShorthandPropertyAssignment;
|
|
217
220
|
const key = this.stringValue(shorthand.name.text);
|
|
221
|
+
excludedKeys.push(key);
|
|
218
222
|
let elementValue = this.reg();
|
|
219
223
|
this.emit(` ${elementValue} = call i64 @xt_get(i64 ${value}, i64 ${key})`);
|
|
220
224
|
if (shorthand.initializer) elementValue = this.emitBindingDefault(elementValue, shorthand.initializer);
|
|
@@ -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
|
|