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
package/src/cli/main.ts
CHANGED
|
@@ -18,11 +18,11 @@ import { dirname, resolve } from "node:path";
|
|
|
18
18
|
import { pathToFileURL } from "node:url";
|
|
19
19
|
import { formatDiagnostic, type Diagnostic } from "../diagnostics/diagnostic.js";
|
|
20
20
|
import { SourceFile } from "../diagnostics/source.js";
|
|
21
|
-
import { build, compileEntry, COMPILER_VERSION, type EmitKind } from "../driver/compiler.js";
|
|
21
|
+
import { build, compileEntry, COMPILER_VERSION, type EmitKind, type PhaseTiming } from "../driver/compiler.js";
|
|
22
22
|
import { findRuntimeDir, platformSlug } from "../driver/paths.js";
|
|
23
23
|
import { runtimeLibDir } from "../driver/runtime-lib.js";
|
|
24
|
-
import { resolveToolchain } from "../driver/toolchain-provider.js";
|
|
25
|
-
import { realRunner, ToolchainError } from "../driver/toolchain.js";
|
|
24
|
+
import { resolveToolchain, type ResolvedToolchain } from "../driver/toolchain-provider.js";
|
|
25
|
+
import { MINIMUM_CLANG_MAJOR, probeClang, realRunner, ToolchainError } from "../driver/toolchain.js";
|
|
26
26
|
import {
|
|
27
27
|
DOC_CLI,
|
|
28
28
|
DOC_TROUBLESHOOTING,
|
|
@@ -154,23 +154,39 @@ function buildRegistry(extNames: readonly string[], nativeManifests: readonly st
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
function printDiagnostics(diagnostics: readonly Diagnostic[], fileName: string, io: CliIo): void {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
157
|
+
/* A diagnostic can come from any module in a bundled program, so load (and
|
|
158
|
+
* cache) the source of the file it names instead of always using the entry:
|
|
159
|
+
* reusing the entry's text is what used to print another file's line with a
|
|
160
|
+
* caret under unrelated code. */
|
|
161
|
+
const sources = new Map<string, SourceFile | undefined>();
|
|
162
|
+
const sourceFor = (name: string): SourceFile | undefined => {
|
|
163
|
+
if (!sources.has(name)) {
|
|
164
|
+
try {
|
|
165
|
+
sources.set(name, new SourceFile(name, readFileSync(name, "utf8")));
|
|
166
|
+
} catch {
|
|
167
|
+
sources.set(name, undefined);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return sources.get(name);
|
|
171
|
+
};
|
|
163
172
|
for (const diagnostic of diagnostics) {
|
|
164
173
|
// Driver-level diagnostics (icon, config, …) have no source location.
|
|
165
174
|
if (diagnostic.fileName === undefined && diagnostic.start === undefined) {
|
|
166
175
|
io.stderr(`error TS${diagnostic.code}: ${diagnostic.message}\n`);
|
|
167
176
|
continue;
|
|
168
177
|
}
|
|
178
|
+
const source = sourceFor(diagnostic.fileName ?? fileName);
|
|
169
179
|
io.stderr(formatDiagnostic(diagnostic, source) + "\n");
|
|
170
180
|
}
|
|
171
181
|
for (const hint of documentationHints(diagnostics)) emitHint(io, hint);
|
|
172
182
|
}
|
|
173
183
|
|
|
184
|
+
/** Print the phases collected by `--time`. */
|
|
185
|
+
function printTimings(timings: readonly PhaseTiming[], io: CliIo): void {
|
|
186
|
+
const text = timings.map((timing) => `${timing.phase} ${timing.ms}ms`).join(", ");
|
|
187
|
+
io.stdout(`xbintsc: timing (${text})\n`);
|
|
188
|
+
}
|
|
189
|
+
|
|
174
190
|
/** Print one `hint:` line unless the caller asked for plain output. */
|
|
175
191
|
function emitHint(io: CliIo, line: string): void {
|
|
176
192
|
if (hintsDisabled) return;
|
|
@@ -237,6 +253,8 @@ Options:
|
|
|
237
253
|
--app-id <id> macOS bundle identifier (e.g. com.example.demo)
|
|
238
254
|
--force Ignore the incremental cache
|
|
239
255
|
--verbose Print progress information
|
|
256
|
+
--time Print per-phase build timings
|
|
257
|
+
--debug Emit debug info (DWARF/CodeView) and keep frame pointers
|
|
240
258
|
--no-hints Do not append the "hint: … see <doc>" line to failures
|
|
241
259
|
|
|
242
260
|
xbintsc compiles a subset of TypeScript to a native binary. Types are erased
|
|
@@ -247,26 +265,47 @@ Docs: doc/ai/README.md (task guide) - doc/ai/language-support.md (subset) -
|
|
|
247
265
|
doc/ai/troubleshooting.md (failures) - llms.txt (index of every document)
|
|
248
266
|
`;
|
|
249
267
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
268
|
+
/**
|
|
269
|
+
* Report the resolved toolchain and runtime locations.
|
|
270
|
+
*
|
|
271
|
+
* Returns non-zero when the resolved compiler cannot build anything (missing,
|
|
272
|
+
* or older than {@link MINIMUM_CLANG_MAJOR}), so CI and scripts can use it as a
|
|
273
|
+
* preflight instead of discovering the problem halfway through a build.
|
|
274
|
+
*/
|
|
256
275
|
function doctor(io: CliIo): number {
|
|
257
276
|
io.stdout(`xbintsc ${COMPILER_VERSION}\n`);
|
|
258
277
|
io.stdout(`platform : ${platformSlug()}\n`);
|
|
259
278
|
|
|
279
|
+
let toolchain: ResolvedToolchain | undefined;
|
|
280
|
+
let usable = false;
|
|
260
281
|
try {
|
|
261
|
-
|
|
262
|
-
const
|
|
282
|
+
toolchain = resolveToolchain();
|
|
283
|
+
const probe = probeClang(realRunner, toolchain.clang, toolchain.env);
|
|
263
284
|
io.stdout(`toolchain : ${toolchain.clang} (${toolchain.source})\n`);
|
|
264
|
-
io.stdout(`compiler : ${
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
285
|
+
io.stdout(`compiler : ${probe.summary || "unknown"}\n`);
|
|
286
|
+
const version = toolchain.version ?? probe.version;
|
|
287
|
+
if (!version) {
|
|
288
|
+
// An explicit wrapper need not print a clang-style version; let it run.
|
|
289
|
+
usable = true;
|
|
290
|
+
io.stdout(`status : version unknown (requires clang >= ${MINIMUM_CLANG_MAJOR})\n`);
|
|
291
|
+
} else if (version.major >= MINIMUM_CLANG_MAJOR) {
|
|
292
|
+
usable = true;
|
|
293
|
+
io.stdout(`status : ok (requires clang >= ${MINIMUM_CLANG_MAJOR})\n`);
|
|
294
|
+
} else {
|
|
295
|
+
io.stdout(
|
|
296
|
+
`status : too old: clang ${version.major}.${version.minor}.${version.patch} ` +
|
|
297
|
+
`(requires >= ${MINIMUM_CLANG_MAJOR})\n`,
|
|
298
|
+
);
|
|
299
|
+
io.stdout(" set xbintsc_CLANG to a newer clang; on Windows Visual Studio ships one under\n");
|
|
300
|
+
io.stdout(" <VS install>\\VC\\Tools\\Llvm\\x64\\bin\\clang.exe. See doc/requirements.md.\n");
|
|
301
|
+
}
|
|
302
|
+
} catch (error) {
|
|
303
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
304
|
+
io.stdout("toolchain : not usable\n");
|
|
305
|
+
for (const line of message.split("\n")) {
|
|
306
|
+
if (line.trim().length > 0) io.stdout(` ${line.trim()}\n`);
|
|
307
|
+
}
|
|
308
|
+
io.stdout(" See doc/requirements.md for the per-platform install steps, or set xbintsc_CLANG.\n");
|
|
270
309
|
}
|
|
271
310
|
|
|
272
311
|
try {
|
|
@@ -282,13 +321,13 @@ function doctor(io: CliIo): number {
|
|
|
282
321
|
}
|
|
283
322
|
|
|
284
323
|
try {
|
|
285
|
-
|
|
324
|
+
if (!toolchain) throw new Error("no toolchain");
|
|
286
325
|
const resource = resolveResourceCompiler(realRunner, toolchain.clang);
|
|
287
326
|
io.stdout(`icon res : ${resource ? `${resource.tool} (${resource.kind})` : "none (.exe icon disabled)"}\n`);
|
|
288
327
|
} catch {
|
|
289
328
|
io.stdout("icon res : unknown\n");
|
|
290
329
|
}
|
|
291
|
-
return 0;
|
|
330
|
+
return usable ? 0 : 1;
|
|
292
331
|
}
|
|
293
332
|
|
|
294
333
|
type ConfigResolution =
|
|
@@ -388,7 +427,7 @@ function dispatch(argv: readonly string[], io: CliIo): number {
|
|
|
388
427
|
const extNames = listFlag(args.flags, "ext") ?? resolved.config.extensions ?? [];
|
|
389
428
|
const nativeManifests = listFlag(args.flags, "ext-native") ?? resolved.config.extNative ?? [];
|
|
390
429
|
const registry = buildRegistry(extNames, nativeManifests);
|
|
391
|
-
const { ir, diagnostics } = compileEntry(entry, registry);
|
|
430
|
+
const { ir, diagnostics } = compileEntry(entry, registry, { debug: Boolean(args.flags.get("debug")) });
|
|
392
431
|
if (diagnostics.some((d) => d.category === "error")) {
|
|
393
432
|
printDiagnostics(diagnostics, entry, io);
|
|
394
433
|
return 1;
|
|
@@ -423,6 +462,7 @@ function dispatch(argv: readonly string[], io: CliIo): number {
|
|
|
423
462
|
verbose: Boolean(args.flags.get("verbose")),
|
|
424
463
|
extensions: registry,
|
|
425
464
|
app: mergeAppConfig(args.flags, config),
|
|
465
|
+
debug: Boolean(args.flags.get("debug")),
|
|
426
466
|
});
|
|
427
467
|
|
|
428
468
|
if (result.diagnostics.some((d) => d.category === "error")) {
|
|
@@ -430,6 +470,8 @@ function dispatch(argv: readonly string[], io: CliIo): number {
|
|
|
430
470
|
return 1;
|
|
431
471
|
}
|
|
432
472
|
|
|
473
|
+
if (args.flags.has("time") && result.timings) printTimings(result.timings, io);
|
|
474
|
+
|
|
433
475
|
if (command === "build") {
|
|
434
476
|
const bundle = result.bundlePath ? ` (bundle: ${result.bundlePath})` : "";
|
|
435
477
|
io.stdout(`xbintsc: wrote ${result.outputPath}${result.cached ? " (cached)" : ""}${bundle}\n`);
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
BUILTIN_METHODS,
|
|
20
20
|
CONSOLE_METHODS,
|
|
21
21
|
CTOR_FUNCTIONS,
|
|
22
|
+
ERROR_CONSTRUCTORS,
|
|
22
23
|
GLOBAL_FUNCTIONS,
|
|
23
24
|
MATH_FUNCTIONS,
|
|
24
25
|
NAMESPACE_STATICS,
|
|
@@ -142,6 +143,10 @@ export const invocationCallMethods: InvocationCallMethods = {
|
|
|
142
143
|
}
|
|
143
144
|
const ctor = !symbol ? CTOR_FUNCTIONS[(callee as Identifier).text] : undefined;
|
|
144
145
|
if (ctor) {
|
|
146
|
+
// `Error("x")` without `new`: point the innermost frame at the call.
|
|
147
|
+
if (ERROR_CONSTRUCTORS[(callee as Identifier).text]) {
|
|
148
|
+
this.emit(` call void @xt_stack_set_line(i32 ${this.lineOf(node)})`);
|
|
149
|
+
}
|
|
145
150
|
const args = this.emitArguments(node.arguments);
|
|
146
151
|
const result = this.reg();
|
|
147
152
|
this.emit(` ${result} = call i64 @${ctor}(i32 ${args.argc}, i64* ${args.ptr})`);
|
|
@@ -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);
|