xbintsc 0.3.81 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +8 -4
- package/README.md +16 -5
- package/README.zh-CN.md +14 -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/access.js +6 -2
- package/dist/src/codegen/generator/calls/access.js.map +1 -1
- package/dist/src/codegen/generator/calls/closures.js +4 -0
- package/dist/src/codegen/generator/calls/closures.js.map +1 -1
- package/dist/src/codegen/generator/calls/invocation.js +9 -4
- 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 +11 -3
- 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 -1
- package/dist/src/codegen/generator/module.js +82 -12
- 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 +9 -0
- package/dist/src/codegen/generator/state.js.map +1 -1
- package/dist/src/codegen/generator/statements/control-flow.js +2 -4
- 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 +16 -0
- package/dist/src/codegen/generator/tables.js +99 -2
- 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 +23 -4
- package/dist/src/driver/compiler.js +46 -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/closures.test.js +8 -0
- package/dist/tests/codegen/closures.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/async-await.test.d.ts +10 -0
- package/dist/tests/e2e/async-await.test.js +247 -0
- package/dist/tests/e2e/async-await.test.js.map +1 -0
- 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/runtime-coverage-globals.test.js +46 -0
- package/dist/tests/e2e/runtime-coverage-globals.test.js.map +1 -1
- 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/e2e/string-utf16.test.d.ts +6 -0
- package/dist/tests/e2e/string-utf16.test.js +133 -0
- package/dist/tests/e2e/string-utf16.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 +16 -8
- package/doc/ai/extensions.md +3 -2
- package/doc/ai/language-support.md +15 -12
- package/doc/ai/troubleshooting.md +43 -5
- package/doc/ai/zh-CN/cli.md +2 -0
- package/doc/ai/zh-CN/contributing.md +11 -7
- package/doc/ai/zh-CN/extensions.md +2 -1
- package/doc/ai/zh-CN/language-support.md +15 -12
- package/doc/ai/zh-CN/troubleshooting.md +38 -4
- package/doc/implemented.md +16 -13
- package/doc/requirements.md +13 -4
- package/doc/unimplemented.md +20 -18
- package/doc/zh-CN/DESIGN.md +15 -9
- package/doc/zh-CN/implemented.md +15 -12
- package/doc/zh-CN/requirements.md +12 -3
- package/doc/zh-CN/unimplemented.md +19 -17
- 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 +49 -1
- package/runtime/rt_internal.h +136 -4
- package/runtime/xt_alloc.c +20 -2
- package/runtime/xt_async.c +197 -0
- package/runtime/xt_bigint/conversions.inc +1 -1
- package/runtime/xt_builtins.c +169 -43
- package/runtime/xt_constructors.c +389 -0
- package/runtime/xt_containers/access.inc +61 -24
- package/runtime/xt_containers/arrays.inc +26 -2
- package/runtime/xt_containers/objects.inc +206 -8
- package/runtime/xt_coroutine.c +212 -0
- package/runtime/xt_generator.c +31 -162
- package/runtime/xt_io/exception.inc +171 -2
- package/runtime/xt_io/inspect.inc +48 -13
- package/runtime/xt_io.c +5 -0
- package/runtime/xt_promise.c +62 -17
- package/runtime/xt_stdlib/arrays.inc +2 -2
- package/runtime/xt_stdlib/dispatch.inc +20 -4
- package/runtime/xt_stdlib/strings.inc +98 -29
- package/runtime/xt_stdlib2/array-statics.inc +3 -2
- package/runtime/xt_stdlib2/arrays.inc +1 -1
- package/runtime/xt_stdlib2/error.inc +33 -6
- package/runtime/xt_stdlib2/json.inc +46 -16
- package/runtime/xt_stdlib2/number-string-statics.inc +24 -13
- package/runtime/xt_stdlib2/objects.inc +1 -1
- package/runtime/xt_stdlib2/regexp-match.inc +50 -35
- package/runtime/xt_stdlib2/set.inc +6 -0
- package/runtime/xt_stdlib2/strings.inc +81 -42
- package/runtime/xt_symbol.c +1 -1
- package/runtime/xt_typed_array/construction.inc +43 -2
- package/runtime/xt_typed_array.c +4 -0
- package/runtime/xt_values/comparison.inc +2 -2
- package/runtime/xt_values/constructors.inc +18 -5
- package/runtime/xt_values/conversions.inc +4 -3
- package/runtime/xt_values/strings.inc +94 -4
- package/runtime/xt_values/utf16.inc +340 -0
- package/runtime/xt_values.c +1 -0
- 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/access.ts +7 -2
- package/src/codegen/generator/calls/closures.ts +3 -0
- package/src/codegen/generator/calls/invocation.ts +9 -3
- 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 +11 -3
- package/src/codegen/generator/generator.ts +18 -2
- package/src/codegen/generator/module.ts +83 -13
- package/src/codegen/generator/numeric.ts +158 -0
- package/src/codegen/generator/state.ts +26 -0
- package/src/codegen/generator/statements/control-flow.ts +2 -3
- package/src/codegen/generator/statements/dispatch.ts +3 -0
- package/src/codegen/generator/statements/variables.ts +8 -2
- package/src/codegen/generator/tables.ts +110 -2
- package/src/driver/bundler/merge.ts +39 -5
- package/src/driver/compiler.ts +65 -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
|
@@ -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,
|
package/src/driver/compiler.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { SyntaxKind } from "../ast/nodes.js";
|
|
|
18
18
|
import { bundleModules } from "./modules.js";
|
|
19
19
|
import { createDefaultRegistry, type ExtensionRegistry } from "../extensions/registry.js";
|
|
20
20
|
import { BuildCache, hashParts } from "./cache.js";
|
|
21
|
+
import { compilerFingerprint, registryFingerprint, runtimeFingerprint } from "./fingerprint.js";
|
|
21
22
|
import { findRuntimeDir } from "./paths.js";
|
|
22
23
|
import { findRuntimeLibrary } from "./runtime-lib.js";
|
|
23
24
|
import { resolveToolchain } from "./toolchain-provider.js";
|
|
@@ -31,10 +32,17 @@ import {
|
|
|
31
32
|
compileIr,
|
|
32
33
|
link,
|
|
33
34
|
realRunner,
|
|
35
|
+
RUNTIME_CFLAGS,
|
|
34
36
|
type Runner,
|
|
35
37
|
} from "./toolchain.js";
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
/**
|
|
40
|
+
* The compiler's own version. It is part of the incremental cache key, so it
|
|
41
|
+
* must be bumped for every release (a test enforces that it matches
|
|
42
|
+
* `package.json`); a stale version would let an upgraded compiler reuse binaries
|
|
43
|
+
* produced by the previous one.
|
|
44
|
+
*/
|
|
45
|
+
export const COMPILER_VERSION = "0.5.6";
|
|
38
46
|
|
|
39
47
|
export type EmitKind = "exe" | "obj" | "ir";
|
|
40
48
|
|
|
@@ -57,6 +65,14 @@ export interface BuildOptions {
|
|
|
57
65
|
readonly icon?: string;
|
|
58
66
|
/** Application metadata: name, icon and macOS bundle settings. */
|
|
59
67
|
readonly app?: AppConfig;
|
|
68
|
+
/** Emit LLVM debug metadata so `-g` produces DWARF/CodeView (default false). */
|
|
69
|
+
readonly debug?: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** One phase of a build, in milliseconds (reported by `--time`). */
|
|
73
|
+
export interface PhaseTiming {
|
|
74
|
+
readonly phase: "frontend" | "codegen" | "runtime" | "clang" | "link";
|
|
75
|
+
readonly ms: number;
|
|
60
76
|
}
|
|
61
77
|
|
|
62
78
|
export interface BuildResult {
|
|
@@ -67,6 +83,8 @@ export interface BuildResult {
|
|
|
67
83
|
readonly ir?: string;
|
|
68
84
|
/** macOS `.app` bundle produced by `app.bundle` (darwin only). */
|
|
69
85
|
readonly bundlePath?: string;
|
|
86
|
+
/** Per-phase timings, collected for `xbintsc build --time`. */
|
|
87
|
+
readonly timings?: readonly PhaseTiming[];
|
|
70
88
|
}
|
|
71
89
|
|
|
72
90
|
export interface CompileStringResult {
|
|
@@ -75,7 +93,12 @@ export interface CompileStringResult {
|
|
|
75
93
|
}
|
|
76
94
|
|
|
77
95
|
/** Compile TypeScript source text to LLVM IR without touching the toolchain. */
|
|
78
|
-
export function compileString(
|
|
96
|
+
export function compileString(
|
|
97
|
+
source: string,
|
|
98
|
+
fileName = "input.ts",
|
|
99
|
+
extensions?: ExtensionRegistry,
|
|
100
|
+
options: { readonly debug?: boolean } = {},
|
|
101
|
+
): CompileStringResult {
|
|
79
102
|
const file = new SourceFile(fileName, source);
|
|
80
103
|
const diagnostics = new DiagnosticBag();
|
|
81
104
|
const parser = new Parser(file, diagnostics);
|
|
@@ -85,6 +108,7 @@ export function compileString(source: string, fileName = "input.ts", extensions?
|
|
|
85
108
|
builtins: registry.builtins(),
|
|
86
109
|
modules: registry.modules(),
|
|
87
110
|
moduleHints: registry.moduleHints(),
|
|
111
|
+
debug: options.debug === true,
|
|
88
112
|
});
|
|
89
113
|
return { ir, diagnostics: diagnostics.diagnostics };
|
|
90
114
|
}
|
|
@@ -106,7 +130,11 @@ function externalModuleSpecifiers(registry: ExtensionRegistry): Set<string> {
|
|
|
106
130
|
* Compile a file to LLVM IR, bundling every reachable relative module first so
|
|
107
131
|
* that imports from other TypeScript sources resolve exactly like `build`.
|
|
108
132
|
*/
|
|
109
|
-
export function compileEntry(
|
|
133
|
+
export function compileEntry(
|
|
134
|
+
entryPath: string,
|
|
135
|
+
extensions?: ExtensionRegistry,
|
|
136
|
+
options: { readonly debug?: boolean } = {},
|
|
137
|
+
): CompileStringResult {
|
|
110
138
|
const absoluteEntry = resolve(entryPath);
|
|
111
139
|
const sourceText = sourceTextBytes(readFileSync(absoluteEntry, "utf8"));
|
|
112
140
|
const file = new SourceFile(absoluteEntry, sourceText);
|
|
@@ -128,6 +156,7 @@ export function compileEntry(entryPath: string, extensions?: ExtensionRegistry):
|
|
|
128
156
|
builtins: registry.builtins(),
|
|
129
157
|
modules: registry.modules(),
|
|
130
158
|
moduleHints: registry.moduleHints(),
|
|
159
|
+
debug: options.debug === true,
|
|
131
160
|
});
|
|
132
161
|
return { ir, diagnostics: diagnostics.diagnostics };
|
|
133
162
|
}
|
|
@@ -173,6 +202,8 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
173
202
|
|
|
174
203
|
const absoluteEntry = resolve(entryPath);
|
|
175
204
|
const sourceText = sourceTextBytes(readFileSync(absoluteEntry, "utf8"));
|
|
205
|
+
const buildStarted = Date.now();
|
|
206
|
+
const timings: PhaseTiming[] = [];
|
|
176
207
|
|
|
177
208
|
const baseName = basename(absoluteEntry, extname(absoluteEntry));
|
|
178
209
|
const outputPath =
|
|
@@ -203,6 +234,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
203
234
|
cacheText = bundled.text;
|
|
204
235
|
}
|
|
205
236
|
}
|
|
237
|
+
timings.push({ phase: "frontend", ms: Date.now() - buildStarted });
|
|
206
238
|
|
|
207
239
|
const app = options.app ?? {};
|
|
208
240
|
const iconPath = options.icon ?? app.icon;
|
|
@@ -222,6 +254,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
222
254
|
const runtimeDir = findRuntimeDir();
|
|
223
255
|
const cacheKey = hashParts([
|
|
224
256
|
COMPILER_VERSION,
|
|
257
|
+
compilerFingerprint(),
|
|
225
258
|
cacheText,
|
|
226
259
|
emit,
|
|
227
260
|
optimize,
|
|
@@ -232,6 +265,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
232
265
|
app.name ?? "",
|
|
233
266
|
String(app.bundle ?? false),
|
|
234
267
|
app.bundleId ?? "",
|
|
268
|
+
String(options.debug === true),
|
|
235
269
|
]);
|
|
236
270
|
|
|
237
271
|
// A `.app` bundle is deterministic from the output path and name, so it can be
|
|
@@ -243,21 +277,24 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
243
277
|
const cache = new BuildCache(cacheDir);
|
|
244
278
|
const outputs = bundle ? [outputPath, bundle] : [outputPath];
|
|
245
279
|
if (!options.force && !diagnostics.hasErrors && cache.isFresh(cacheKey, outputs)) {
|
|
246
|
-
return { outputPath, cached: true, diagnostics: [], ...(bundle ? { bundlePath: bundle } : {}) };
|
|
280
|
+
return { outputPath, cached: true, diagnostics: [], timings, ...(bundle ? { bundlePath: bundle } : {}) };
|
|
247
281
|
}
|
|
248
282
|
|
|
283
|
+
const codegenStarted = Date.now();
|
|
249
284
|
const { ir } = generate(sourceFile, diagnostics, {
|
|
250
285
|
builtins: registry.builtins(),
|
|
251
286
|
modules: registry.modules(),
|
|
252
287
|
moduleHints: registry.moduleHints(),
|
|
288
|
+
debug: options.debug === true,
|
|
253
289
|
});
|
|
290
|
+
timings.push({ phase: "codegen", ms: Date.now() - codegenStarted });
|
|
254
291
|
|
|
255
292
|
if (options.verbose) {
|
|
256
293
|
process.stderr.write(`xbintsc: generated ${ir.length} bytes of LLVM IR\n`);
|
|
257
294
|
}
|
|
258
295
|
|
|
259
296
|
if (diagnostics.hasErrors) {
|
|
260
|
-
return { outputPath, cached: false, diagnostics: diagnostics.diagnostics, ir };
|
|
297
|
+
return { outputPath, cached: false, diagnostics: diagnostics.diagnostics, ir, timings };
|
|
261
298
|
}
|
|
262
299
|
|
|
263
300
|
mkdirSync(outDir, { recursive: true });
|
|
@@ -272,10 +309,11 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
272
309
|
if (emit === "ir") {
|
|
273
310
|
cache.record(cacheKey, outputs);
|
|
274
311
|
cache.save();
|
|
275
|
-
return { outputPath, irPath, cached: false, diagnostics: [], ir };
|
|
312
|
+
return { outputPath, irPath, cached: false, diagnostics: [], ir, timings };
|
|
276
313
|
}
|
|
277
314
|
|
|
278
315
|
// -- toolchain -----------------------------------------------------------
|
|
316
|
+
const clangStarted = Date.now();
|
|
279
317
|
const toolchain = options.clang
|
|
280
318
|
? { clang: options.clang, linkerArgs: [] as string[], env: {} as Record<string, string> }
|
|
281
319
|
: resolveToolchain(runner);
|
|
@@ -283,14 +321,16 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
283
321
|
mkdirSync(cacheDir, { recursive: true });
|
|
284
322
|
|
|
285
323
|
const objectPath = emit === "obj" ? outputPath : resolve(outDir, baseName + ".o");
|
|
286
|
-
compileIr(runner, { clang, irPath, objectPath, optimize, env: toolchain.env });
|
|
324
|
+
compileIr(runner, { clang, irPath, objectPath, optimize, env: toolchain.env, debug: options.debug === true });
|
|
325
|
+
timings.push({ phase: "clang", ms: Date.now() - clangStarted });
|
|
287
326
|
|
|
288
327
|
if (emit === "obj") {
|
|
289
328
|
cache.record(cacheKey, outputs);
|
|
290
329
|
cache.save();
|
|
291
|
-
return { outputPath, irPath, cached: false, diagnostics: [], ir };
|
|
330
|
+
return { outputPath, irPath, cached: false, diagnostics: [], ir, timings };
|
|
292
331
|
}
|
|
293
332
|
|
|
333
|
+
const runtimeStarted = Date.now();
|
|
294
334
|
const { runtimeObjects, extensionObjects, nativeObjects } = ensureRuntimeObjects(
|
|
295
335
|
runner,
|
|
296
336
|
clang,
|
|
@@ -300,6 +340,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
300
340
|
resolvePreferPrebuilt(options.preferPrebuilt),
|
|
301
341
|
toolchain.env,
|
|
302
342
|
);
|
|
343
|
+
timings.push({ phase: "runtime", ms: Date.now() - runtimeStarted });
|
|
303
344
|
|
|
304
345
|
/* On Windows a GUI program links as a **Windows-subsystem** executable, so
|
|
305
346
|
* launching it does not flash a console window. That subsystem's C runtime
|
|
@@ -336,6 +377,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
336
377
|
}
|
|
337
378
|
}
|
|
338
379
|
|
|
380
|
+
const linkStarted = Date.now();
|
|
339
381
|
link(runner, {
|
|
340
382
|
clang,
|
|
341
383
|
objectPaths: [
|
|
@@ -360,6 +402,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
360
402
|
optimize,
|
|
361
403
|
env: toolchain.env,
|
|
362
404
|
});
|
|
405
|
+
timings.push({ phase: "link", ms: Date.now() - linkStarted });
|
|
363
406
|
|
|
364
407
|
const bundlePath =
|
|
365
408
|
bundle !== undefined
|
|
@@ -375,7 +418,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
375
418
|
|
|
376
419
|
cache.record(cacheKey, outputs);
|
|
377
420
|
cache.save();
|
|
378
|
-
return { outputPath, irPath, cached: false, diagnostics: [], ir, ...(bundlePath ? { bundlePath } : {}) };
|
|
421
|
+
return { outputPath, irPath, cached: false, diagnostics: [], ir, timings, ...(bundlePath ? { bundlePath } : {}) };
|
|
379
422
|
}
|
|
380
423
|
|
|
381
424
|
/** Default `CFBundleIdentifier` for a bundle when none is configured. */
|
|
@@ -401,59 +444,17 @@ export const RUNTIME_SOURCES = [
|
|
|
401
444
|
"xt_stdlib.c",
|
|
402
445
|
"xt_stdlib2.c",
|
|
403
446
|
"xt_promise.c",
|
|
447
|
+
"xt_async.c",
|
|
404
448
|
"xt_loop.c",
|
|
405
449
|
"xt_generator.c",
|
|
450
|
+
"xt_coroutine.c",
|
|
406
451
|
"xt_fetch.c",
|
|
407
452
|
"xt_symbol.c",
|
|
408
453
|
"xt_builtins.c",
|
|
454
|
+
"xt_constructors.c",
|
|
409
455
|
"xt_io.c",
|
|
410
456
|
] as const;
|
|
411
457
|
|
|
412
|
-
/**
|
|
413
|
-
* Fingerprint every runtime artifact that can change a linked executable: the
|
|
414
|
-
* C sources, shared headers and `#include`d `.inc` fragments plus the shipped
|
|
415
|
-
* prebuilt archives. It feeds the executable cache key, so a runtime change
|
|
416
|
-
* invalidates a cached binary even when the entry source is untouched — the
|
|
417
|
-
* per-object cache cannot express that, because the freshness check runs before
|
|
418
|
-
* any compilation.
|
|
419
|
-
*/
|
|
420
|
-
function runtimeFingerprint(runtimeDir: string): string {
|
|
421
|
-
if (!existsSync(runtimeDir)) return "";
|
|
422
|
-
const parts: string[] = [];
|
|
423
|
-
const walk = (dir: string): void => {
|
|
424
|
-
for (const entry of readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
425
|
-
const full = join(dir, entry.name);
|
|
426
|
-
if (entry.isDirectory()) {
|
|
427
|
-
walk(full);
|
|
428
|
-
} else if (/\.(c|h|inc)$/.test(entry.name)) {
|
|
429
|
-
parts.push(entry.name, readFileSync(full, "utf8"));
|
|
430
|
-
} else {
|
|
431
|
-
parts.push(entry.name, readFileSync(full).toString("base64"));
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
};
|
|
435
|
-
walk(runtimeDir);
|
|
436
|
-
return hashParts(parts);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* Fingerprint everything an extension contributes that can change the linked
|
|
441
|
-
* executable but does not live under `runtime/`: its name, linker flags and the
|
|
442
|
-
* contents of any pre-built C++/Rust objects. Keeps a cached binary fresh when
|
|
443
|
-
* a native extension is rebuilt, or when `--ext-native` flags change.
|
|
444
|
-
*/
|
|
445
|
-
function registryFingerprint(registry: ExtensionRegistry): string {
|
|
446
|
-
const parts: string[] = [];
|
|
447
|
-
for (const extension of registry.all()) {
|
|
448
|
-
parts.push(extension.name);
|
|
449
|
-
parts.push(...(extension.linkerFlags?.() ?? []));
|
|
450
|
-
for (const object of extension.nativeObjects?.() ?? []) {
|
|
451
|
-
parts.push(object, existsSync(object) ? readFileSync(object).toString("base64") : "");
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
return hashParts(parts);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
458
|
/**
|
|
458
459
|
* Compile the core runtime and every extension source, reusing cached object
|
|
459
460
|
* files keyed on the C source hash. Returns the object paths to link.
|
|
@@ -487,7 +488,16 @@ function ensureRuntimeObjects(
|
|
|
487
488
|
|
|
488
489
|
const compileOne = (sourcePath: string, tag: string): string => {
|
|
489
490
|
const body = readFileSync(sourcePath, "utf8");
|
|
490
|
-
const key = hashParts([
|
|
491
|
+
const key = hashParts([
|
|
492
|
+
"runtime",
|
|
493
|
+
COMPILER_VERSION,
|
|
494
|
+
RUNTIME_CFLAGS.join(" "),
|
|
495
|
+
tag,
|
|
496
|
+
headerStamp,
|
|
497
|
+
body,
|
|
498
|
+
clang,
|
|
499
|
+
process.platform,
|
|
500
|
+
]);
|
|
491
501
|
const objectPath = join(cacheDir, `${tag}-${key}.o`);
|
|
492
502
|
if (!existsSync(objectPath)) {
|
|
493
503
|
compileC(runner, clang, sourcePath, objectPath, runtimeDir, env);
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content fingerprints for the incremental cache.
|
|
3
|
+
*
|
|
4
|
+
* The executable cache is keyed on everything that can change the linked
|
|
5
|
+
* binary but is not part of the entry source: the C runtime, the compiler's own
|
|
6
|
+
* sources, and whatever a native extension contributes. The per-object cache
|
|
7
|
+
* cannot express these, because the freshness check runs before compilation.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
11
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
import type { ExtensionRegistry } from "../extensions/registry.js";
|
|
14
|
+
import { hashParts } from "./cache.js";
|
|
15
|
+
|
|
16
|
+
/** Runtime sources, headers and `#include`d `.inc` fragments, plus prebuilt archives. */
|
|
17
|
+
export function runtimeFingerprint(runtimeDir: string): string {
|
|
18
|
+
if (!existsSync(runtimeDir)) return "";
|
|
19
|
+
const parts: string[] = [];
|
|
20
|
+
const walk = (dir: string): void => {
|
|
21
|
+
for (const entry of readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
22
|
+
const full = join(dir, entry.name);
|
|
23
|
+
if (entry.isDirectory()) {
|
|
24
|
+
walk(full);
|
|
25
|
+
} else if (/\.(c|h|inc)$/.test(entry.name)) {
|
|
26
|
+
parts.push(entry.name, readFileSync(full, "utf8"));
|
|
27
|
+
} else {
|
|
28
|
+
parts.push(entry.name, readFileSync(full).toString("base64"));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
walk(runtimeDir);
|
|
33
|
+
return hashParts(parts);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Content hash of the compiler's own TypeScript sources, when they are present.
|
|
38
|
+
*
|
|
39
|
+
* `COMPILER_VERSION` is bumped per release, but a developer editing codegen
|
|
40
|
+
* between releases would otherwise reuse a binary produced by the previous
|
|
41
|
+
* compiler for the same input. Hashing `src/` closes that hole in a source
|
|
42
|
+
* checkout. A released archive ships only `bin/` and `runtime/`, so the marker
|
|
43
|
+
* check fails there and the version alone keys the cache — which is correct,
|
|
44
|
+
* because release automation keeps it in sync with package.json.
|
|
45
|
+
*/
|
|
46
|
+
export function compilerFingerprint(): string {
|
|
47
|
+
try {
|
|
48
|
+
// In a checkout this module is `<root>/src/driver/fingerprint.ts`.
|
|
49
|
+
const sourceRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
50
|
+
if (!existsSync(join(sourceRoot, "codegen", "llvm.ts"))) return "";
|
|
51
|
+
if (!existsSync(join(sourceRoot, "driver", "compiler.ts"))) return "";
|
|
52
|
+
const parts: string[] = [];
|
|
53
|
+
const walk = (dir: string): void => {
|
|
54
|
+
for (const entry of readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
55
|
+
const full = join(dir, entry.name);
|
|
56
|
+
if (entry.isDirectory()) {
|
|
57
|
+
walk(full);
|
|
58
|
+
} else if (entry.name.endsWith(".ts")) {
|
|
59
|
+
parts.push(relative(sourceRoot, full).replace(/\\/g, "/"), readFileSync(full, "utf8"));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
walk(sourceRoot);
|
|
64
|
+
return hashParts(parts);
|
|
65
|
+
} catch {
|
|
66
|
+
// A self-hosted or packaged compiler may not expose its sources.
|
|
67
|
+
return "";
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Everything an extension contributes besides its C sources: name, linker flags
|
|
73
|
+
* and the contents of pre-built C++/Rust objects.
|
|
74
|
+
*/
|
|
75
|
+
export function registryFingerprint(registry: ExtensionRegistry): string {
|
|
76
|
+
const parts: string[] = [];
|
|
77
|
+
for (const extension of registry.all()) {
|
|
78
|
+
parts.push(extension.name);
|
|
79
|
+
parts.push(...(extension.linkerFlags?.() ?? []));
|
|
80
|
+
for (const object of extension.nativeObjects?.() ?? []) {
|
|
81
|
+
parts.push(object, existsSync(object) ? readFileSync(object).toString("base64") : "");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return hashParts(parts);
|
|
85
|
+
}
|
|
@@ -6,18 +6,23 @@
|
|
|
6
6
|
* *which* driver to use, in a fixed priority order:
|
|
7
7
|
*
|
|
8
8
|
* 1. an explicit `xbintsc_CLANG` / `xbintsc_TOOLCHAIN`
|
|
9
|
-
* 2. the system `PATH` (`findClang`)
|
|
9
|
+
* 2. the system `PATH` (`findClang`), preferring a clang >= 16
|
|
10
|
+
* 3. the LLVM build bundled with Visual Studio on Windows
|
|
10
11
|
*
|
|
11
12
|
* xbintsc does not ship a compiler. Every host must provide a clang-compatible
|
|
12
13
|
* toolchain (see `doc/requirements.md`): the Xcode Command Line Tools on macOS,
|
|
13
14
|
* the distribution's clang/lld on Linux, and LLVM plus the Visual Studio C++
|
|
14
15
|
* build tools (the MSVC ABI) on Windows.
|
|
15
16
|
*
|
|
17
|
+
* The version is checked here, not left to the first failed C compile: the
|
|
18
|
+
* emitted IR needs LLVM 16+ (opaque pointers), and a "clang 15 is too old"
|
|
19
|
+
* error up front is far more actionable than a wall of IR type errors later.
|
|
20
|
+
*
|
|
16
21
|
* Extra linker arguments (for example `-fuse-ld=lld`) can be supplied through
|
|
17
22
|
* `xbintsc_LINKER_ARGS`.
|
|
18
23
|
*/
|
|
19
24
|
|
|
20
|
-
import {
|
|
25
|
+
import { locateClang, realRunner, type ClangVersion, type Runner } from "./toolchain.js";
|
|
21
26
|
|
|
22
27
|
export type ToolchainSource = "env" | "system";
|
|
23
28
|
|
|
@@ -26,6 +31,8 @@ export interface ResolvedToolchain {
|
|
|
26
31
|
readonly clang: string;
|
|
27
32
|
/** Where the driver came from. */
|
|
28
33
|
readonly source: ToolchainSource;
|
|
34
|
+
/** Parsed `clang --version`, when the driver prints one. */
|
|
35
|
+
readonly version?: ClangVersion;
|
|
29
36
|
/** Extra arguments to pass to the link step. */
|
|
30
37
|
readonly linkerArgs: readonly string[];
|
|
31
38
|
/** Extra environment for the toolchain subprocesses, merged over the current
|
|
@@ -33,14 +40,6 @@ export interface ResolvedToolchain {
|
|
|
33
40
|
readonly env: Record<string, string>;
|
|
34
41
|
}
|
|
35
42
|
|
|
36
|
-
function works(runner: Runner, command: string): boolean {
|
|
37
|
-
try {
|
|
38
|
-
return runner.run(command, ["--version"]).status === 0;
|
|
39
|
-
} catch {
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
43
|
function linkerArgs(fallback: readonly string[]): string[] {
|
|
45
44
|
const raw = process.env.xbintsc_LINKER_ARGS;
|
|
46
45
|
if (raw) return raw.split(" ").filter((part) => part.length > 0);
|
|
@@ -49,10 +48,12 @@ function linkerArgs(fallback: readonly string[]): string[] {
|
|
|
49
48
|
|
|
50
49
|
/** Resolve the toolchain that `build` should use. See the module docblock. */
|
|
51
50
|
export function resolveToolchain(runner: Runner = realRunner): ResolvedToolchain {
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
51
|
+
const location = locateClang(runner);
|
|
52
|
+
return {
|
|
53
|
+
clang: location.command,
|
|
54
|
+
source: location.source,
|
|
55
|
+
...(location.version ? { version: location.version } : {}),
|
|
56
|
+
linkerArgs: linkerArgs([]),
|
|
57
|
+
env: {},
|
|
58
|
+
};
|
|
58
59
|
}
|