@scriptc/compiler 0.0.0 → 0.0.2
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/LICENSE +202 -0
- package/ambient/package.json +3 -0
- package/ambient/scriptc-node-fallback.d.ts +3001 -0
- package/ambient/scriptc-overrides.d.ts +95 -0
- package/ambient/scriptc.d.ts +74 -0
- package/dist/backend/cc.d.ts +169 -0
- package/dist/backend/cc.js +897 -0
- package/dist/backend/cc.js.map +1 -0
- package/dist/backend/emission/emit-async.d.ts +145 -0
- package/dist/backend/emission/emit-async.js +996 -0
- package/dist/backend/emission/emit-async.js.map +1 -0
- package/dist/backend/emission/emit-exprs.d.ts +3 -0
- package/dist/backend/emission/emit-exprs.js +5949 -0
- package/dist/backend/emission/emit-exprs.js.map +1 -0
- package/dist/backend/emission/emit-island.d.ts +45 -0
- package/dist/backend/emission/emit-island.js +271 -0
- package/dist/backend/emission/emit-island.js.map +1 -0
- package/dist/backend/emission/emit-shapes.d.ts +142 -0
- package/dist/backend/emission/emit-shapes.js +575 -0
- package/dist/backend/emission/emit-shapes.js.map +1 -0
- package/dist/backend/emission/emit-stmts.d.ts +78 -0
- package/dist/backend/emission/emit-stmts.js +960 -0
- package/dist/backend/emission/emit-stmts.js.map +1 -0
- package/dist/backend/emission/emit-types.d.ts +65 -0
- package/dist/backend/emission/emit-types.js +652 -0
- package/dist/backend/emission/emit-types.js.map +1 -0
- package/dist/backend/emission/emit-walkers.d.ts +154 -0
- package/dist/backend/emission/emit-walkers.js +1587 -0
- package/dist/backend/emission/emit-walkers.js.map +1 -0
- package/dist/backend/emission/emitter.d.ts +452 -0
- package/dist/backend/emission/emitter.js +1249 -0
- package/dist/backend/emission/emitter.js.map +1 -0
- package/dist/backend/emission/may-throw.d.ts +15 -0
- package/dist/backend/emission/may-throw.js +250 -0
- package/dist/backend/emission/may-throw.js.map +1 -0
- package/dist/backend/llvm/blocks.d.ts +21 -0
- package/dist/backend/llvm/blocks.js +68 -0
- package/dist/backend/llvm/blocks.js.map +1 -0
- package/dist/backend/llvm/classes.d.ts +84 -0
- package/dist/backend/llvm/classes.js +438 -0
- package/dist/backend/llvm/classes.js.map +1 -0
- package/dist/backend/llvm/dyn.d.ts +123 -0
- package/dist/backend/llvm/dyn.js +2624 -0
- package/dist/backend/llvm/dyn.js.map +1 -0
- package/dist/backend/llvm/emitter.d.ts +3 -0
- package/dist/backend/llvm/emitter.js +10102 -0
- package/dist/backend/llvm/emitter.js.map +1 -0
- package/dist/backend/llvm/shapes.d.ts +78 -0
- package/dist/backend/llvm/shapes.js +754 -0
- package/dist/backend/llvm/shapes.js.map +1 -0
- package/dist/backend/llvm/unsupported.d.ts +6 -0
- package/dist/backend/llvm/unsupported.js +12 -0
- package/dist/backend/llvm/unsupported.js.map +1 -0
- package/dist/backend/llvm/walkers.d.ts +58 -0
- package/dist/backend/llvm/walkers.js +793 -0
- package/dist/backend/llvm/walkers.js.map +1 -0
- package/dist/backend/mangle.d.ts +124 -0
- package/dist/backend/mangle.js +232 -0
- package/dist/backend/mangle.js.map +1 -0
- package/dist/coverage/report.d.ts +65 -0
- package/dist/coverage/report.js +238 -0
- package/dist/coverage/report.js.map +1 -0
- package/dist/diagnostics/diagnostic.d.ts +140 -0
- package/dist/diagnostics/diagnostic.js +458 -0
- package/dist/diagnostics/diagnostic.js.map +1 -0
- package/dist/diagnostics/render.d.ts +11 -0
- package/dist/diagnostics/render.js +58 -0
- package/dist/diagnostics/render.js.map +1 -0
- package/dist/frontend/cjs-lexer.d.ts +20 -0
- package/dist/frontend/cjs-lexer.js +813 -0
- package/dist/frontend/cjs-lexer.js.map +1 -0
- package/dist/frontend/lowering/http2-constants.d.ts +1 -0
- package/dist/frontend/lowering/http2-constants.js +251 -0
- package/dist/frontend/lowering/http2-constants.js.map +1 -0
- package/dist/frontend/lowering/lib-boundary.d.ts +6 -0
- package/dist/frontend/lowering/lib-boundary.js +143 -0
- package/dist/frontend/lowering/lib-boundary.js.map +1 -0
- package/dist/frontend/lowering/lower-assert.d.ts +18 -0
- package/dist/frontend/lowering/lower-assert.js +1269 -0
- package/dist/frontend/lowering/lower-assert.js.map +1 -0
- package/dist/frontend/lowering/lower-builtins.d.ts +511 -0
- package/dist/frontend/lowering/lower-builtins.js +5249 -0
- package/dist/frontend/lowering/lower-builtins.js.map +1 -0
- package/dist/frontend/lowering/lower-calls.d.ts +581 -0
- package/dist/frontend/lowering/lower-calls.js +6286 -0
- package/dist/frontend/lowering/lower-calls.js.map +1 -0
- package/dist/frontend/lowering/lower-classes.d.ts +727 -0
- package/dist/frontend/lowering/lower-classes.js +4701 -0
- package/dist/frontend/lowering/lower-classes.js.map +1 -0
- package/dist/frontend/lowering/lower-comptime.d.ts +53 -0
- package/dist/frontend/lowering/lower-comptime.js +244 -0
- package/dist/frontend/lowering/lower-comptime.js.map +1 -0
- package/dist/frontend/lowering/lower-containers.d.ts +486 -0
- package/dist/frontend/lowering/lower-containers.js +6345 -0
- package/dist/frontend/lowering/lower-containers.js.map +1 -0
- package/dist/frontend/lowering/lower-dgram.d.ts +20 -0
- package/dist/frontend/lowering/lower-dgram.js +319 -0
- package/dist/frontend/lowering/lower-dgram.js.map +1 -0
- package/dist/frontend/lowering/lower-emitter.d.ts +10 -0
- package/dist/frontend/lowering/lower-emitter.js +688 -0
- package/dist/frontend/lowering/lower-emitter.js.map +1 -0
- package/dist/frontend/lowering/lower-enums.d.ts +22 -0
- package/dist/frontend/lowering/lower-enums.js +235 -0
- package/dist/frontend/lowering/lower-enums.js.map +1 -0
- package/dist/frontend/lowering/lower-expando.d.ts +35 -0
- package/dist/frontend/lowering/lower-expando.js +276 -0
- package/dist/frontend/lowering/lower-expando.js.map +1 -0
- package/dist/frontend/lowering/lower-exprs.d.ts +524 -0
- package/dist/frontend/lowering/lower-exprs.js +8111 -0
- package/dist/frontend/lowering/lower-exprs.js.map +1 -0
- package/dist/frontend/lowering/lower-generators.d.ts +50 -0
- package/dist/frontend/lowering/lower-generators.js +421 -0
- package/dist/frontend/lowering/lower-generators.js.map +1 -0
- package/dist/frontend/lowering/lower-inspect.d.ts +20 -0
- package/dist/frontend/lowering/lower-inspect.js +1074 -0
- package/dist/frontend/lowering/lower-inspect.js.map +1 -0
- package/dist/frontend/lowering/lower-island.d.ts +108 -0
- package/dist/frontend/lowering/lower-island.js +650 -0
- package/dist/frontend/lowering/lower-island.js.map +1 -0
- package/dist/frontend/lowering/lower-mixins.d.ts +96 -0
- package/dist/frontend/lowering/lower-mixins.js +534 -0
- package/dist/frontend/lowering/lower-mixins.js.map +1 -0
- package/dist/frontend/lowering/lower-modules.d.ts +135 -0
- package/dist/frontend/lowering/lower-modules.js +1613 -0
- package/dist/frontend/lowering/lower-modules.js.map +1 -0
- package/dist/frontend/lowering/lower-namespaces.d.ts +168 -0
- package/dist/frontend/lowering/lower-namespaces.js +706 -0
- package/dist/frontend/lowering/lower-namespaces.js.map +1 -0
- package/dist/frontend/lowering/lower-server.d.ts +90 -0
- package/dist/frontend/lowering/lower-server.js +3471 -0
- package/dist/frontend/lowering/lower-server.js.map +1 -0
- package/dist/frontend/lowering/lower-stmts.d.ts +361 -0
- package/dist/frontend/lowering/lower-stmts.js +5341 -0
- package/dist/frontend/lowering/lower-stmts.js.map +1 -0
- package/dist/frontend/lowering/lower-stream.d.ts +88 -0
- package/dist/frontend/lowering/lower-stream.js +1533 -0
- package/dist/frontend/lowering/lower-stream.js.map +1 -0
- package/dist/frontend/lowering/lower-test.d.ts +27 -0
- package/dist/frontend/lowering/lower-test.js +353 -0
- package/dist/frontend/lowering/lower-test.js.map +1 -0
- package/dist/frontend/lowering/lowerer.d.ts +1748 -0
- package/dist/frontend/lowering/lowerer.js +6257 -0
- package/dist/frontend/lowering/lowerer.js.map +1 -0
- package/dist/frontend/lowering/surfaces.d.ts +272 -0
- package/dist/frontend/lowering/surfaces.js +1096 -0
- package/dist/frontend/lowering/surfaces.js.map +1 -0
- package/dist/frontend/npm-static.d.ts +55 -0
- package/dist/frontend/npm-static.js +300 -0
- package/dist/frontend/npm-static.js.map +1 -0
- package/dist/frontend/npm.d.ts +330 -0
- package/dist/frontend/npm.js +1259 -0
- package/dist/frontend/npm.js.map +1 -0
- package/dist/frontend/program.d.ts +186 -0
- package/dist/frontend/program.js +2255 -0
- package/dist/frontend/program.js.map +1 -0
- package/dist/frontend/provenance-registry.d.ts +48 -0
- package/dist/frontend/provenance-registry.js +87 -0
- package/dist/frontend/provenance-registry.js.map +1 -0
- package/dist/frontend/provenance.d.ts +6 -0
- package/dist/frontend/provenance.js +459 -0
- package/dist/frontend/provenance.js.map +1 -0
- package/dist/frontend/resolve.d.ts +56 -0
- package/dist/frontend/resolve.js +682 -0
- package/dist/frontend/resolve.js.map +1 -0
- package/dist/frontend/shared.d.ts +67 -0
- package/dist/frontend/shared.js +241 -0
- package/dist/frontend/shared.js.map +1 -0
- package/dist/frontend/ts7/adapter.d.ts +7 -0
- package/dist/frontend/ts7/adapter.js +54 -0
- package/dist/frontend/ts7/adapter.js.map +1 -0
- package/dist/frontend/ts7/ast.d.ts +50 -0
- package/dist/frontend/ts7/ast.js +211 -0
- package/dist/frontend/ts7/ast.js.map +1 -0
- package/dist/frontend/ts7/census-check.d.ts +1 -0
- package/dist/frontend/ts7/census-check.js +12 -0
- package/dist/frontend/ts7/census-check.js.map +1 -0
- package/dist/frontend/ts7/checker.d.ts +140 -0
- package/dist/frontend/ts7/checker.js +544 -0
- package/dist/frontend/ts7/checker.js.map +1 -0
- package/dist/frontend/ts7/enums.d.ts +18 -0
- package/dist/frontend/ts7/enums.js +43 -0
- package/dist/frontend/ts7/enums.js.map +1 -0
- package/dist/frontend/ts7/program.d.ts +99 -0
- package/dist/frontend/ts7/program.js +278 -0
- package/dist/frontend/ts7/program.js.map +1 -0
- package/dist/frontend/ts7/world-check.d.ts +3 -0
- package/dist/frontend/ts7/world-check.js +34 -0
- package/dist/frontend/ts7/world-check.js.map +1 -0
- package/dist/frontend/types.d.ts +205 -0
- package/dist/frontend/types.js +2485 -0
- package/dist/frontend/types.js.map +1 -0
- package/dist/index.d.ts +85 -0
- package/dist/index.js +429 -0
- package/dist/index.js.map +1 -0
- package/dist/ir/nodes.d.ts +4334 -0
- package/dist/ir/nodes.js +1906 -0
- package/dist/ir/nodes.js.map +1 -0
- package/dist/ir/serialize.d.ts +4 -0
- package/dist/ir/serialize.js +34 -0
- package/dist/ir/serialize.js.map +1 -0
- package/dist/ir/validate.d.ts +36 -0
- package/dist/ir/validate.js +4885 -0
- package/dist/ir/validate.js.map +1 -0
- package/package.json +30 -6
- package/README.md +0 -3
- package/index.js +0 -2
|
@@ -0,0 +1,897 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { chmod, copyFile, mkdir, mkdtemp, readdir, readFile, rename, rm, stat, unlink, utimes, writeFile } from "node:fs/promises";
|
|
5
|
+
import { availableParallelism, tmpdir } from "node:os";
|
|
6
|
+
import { basename, dirname, join } from "node:path";
|
|
7
|
+
import { promisify } from "node:util";
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
const RUNTIME_SOURCES = ["scr_number.c", "scr_string.c", "scr_array.c", "scr_bytes.c", "scr_bytes_io.c", "scr_map.c", "scr_closure.c", "scr_object.c", "scr_union.c", "scr_exception.c", "scr_error.c", "scr_console.c", "scr_lib.c", "scr_path.c", "scr_url.c", "scr_json.c", "scr_async.c", "scr_child.c", "scr_cycle.c"];
|
|
10
|
+
/** The pinned quickjs-ng snapshot under packages/runtime/vendor/quickjs-ng
|
|
11
|
+
* (see vendor/README.md — update both together). Keys the archive cache so
|
|
12
|
+
* a vendor bump can never reuse a stale engine build. */
|
|
13
|
+
const QJS_COMMIT = "3c8f3d68953955950074c41c6e4d999562ae82a7";
|
|
14
|
+
/** The pinned mbedTLS snapshot under packages/runtime/vendor/mbedtls (see
|
|
15
|
+
* vendor/README.md — update both together). Keys the archive cache so a
|
|
16
|
+
* vendor bump can never reuse a stale TLS build, and joins the runtime
|
|
17
|
+
* fingerprint so cached binaries re-key too. */
|
|
18
|
+
const MBEDTLS_VERSION = "3.6.7";
|
|
19
|
+
/** The pinned zlib snapshot under packages/runtime/vendor/zlib (see
|
|
20
|
+
* vendor/README.md — update both together). Keys the per-target object
|
|
21
|
+
* cache and joins the runtime fingerprint. Host builds never compile it —
|
|
22
|
+
* they keep the historical system `-lz` link; the vendored copy exists so
|
|
23
|
+
* zlib-using programs CROSS-compile (zig has no target sysroot libz). */
|
|
24
|
+
const ZLIB_VERSION = "1.3.1";
|
|
25
|
+
export function runtimeSrcDir() {
|
|
26
|
+
const require = createRequire(import.meta.url);
|
|
27
|
+
return join(dirname(require.resolve("@scriptc/runtime/package.json")), "src");
|
|
28
|
+
}
|
|
29
|
+
export function resolveCc(env = process.env) {
|
|
30
|
+
const cc = env["SCRIPTC_CC"] ?? "";
|
|
31
|
+
const target = env["SCRIPTC_TARGET"] ?? "";
|
|
32
|
+
if (cc === "" || cc === "clang") {
|
|
33
|
+
if (target !== "") {
|
|
34
|
+
throw new Error(`SCRIPTC_TARGET=${target} requires SCRIPTC_CC=zigcc — the default clang path has no cross-target sysroots.`);
|
|
35
|
+
}
|
|
36
|
+
return { argv: ["clang"], target: null, targetArgs: [] };
|
|
37
|
+
}
|
|
38
|
+
if (cc !== "zigcc") {
|
|
39
|
+
throw new Error(`unknown SCRIPTC_CC '${cc}' (supported: clang, zigcc)`);
|
|
40
|
+
}
|
|
41
|
+
if (target === "")
|
|
42
|
+
return { argv: ["zig", "cc"], target: null, targetArgs: [] };
|
|
43
|
+
return {
|
|
44
|
+
argv: ["zig", "cc"],
|
|
45
|
+
target,
|
|
46
|
+
targetArgs: ["-target", target, ...(target.includes("linux") ? ["-D_GNU_SOURCE"] : [])],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** The OS the produced binary runs on: the triple's OS under SCRIPTC_TARGET,
|
|
50
|
+
* the host's otherwise — so platform-conditional link flags follow the
|
|
51
|
+
* TARGET, not the machine running the compiler. Exported for compile()/
|
|
52
|
+
* analyze(): the FRONTEND consults it too (path.sep / os.EOL literals and
|
|
53
|
+
* the path-module binding follow the target — a win32 triple compiles
|
|
54
|
+
* Node-on-Windows semantics, path.win32 backing the bare module). */
|
|
55
|
+
export function targetPlatform(driver) {
|
|
56
|
+
if (driver.target === null)
|
|
57
|
+
return process.platform;
|
|
58
|
+
if (driver.target.includes("linux"))
|
|
59
|
+
return "linux";
|
|
60
|
+
if (driver.target.includes("windows"))
|
|
61
|
+
return "win32";
|
|
62
|
+
return driver.target.includes("macos") || driver.target.includes("darwin") ? "darwin" : "other";
|
|
63
|
+
}
|
|
64
|
+
function vendorEngineDir() {
|
|
65
|
+
return join(runtimeSrcDir(), "..", "vendor", "quickjs-ng");
|
|
66
|
+
}
|
|
67
|
+
async function fileExists(path) {
|
|
68
|
+
return stat(path).then((s) => s.isFile(), () => false);
|
|
69
|
+
}
|
|
70
|
+
/** The engine archive for one flavor, built lazily on the first --dynamic
|
|
71
|
+
* compile (~10s) and cached under vendor/.cache/<commit>-<flavor>/ — unlike
|
|
72
|
+
* the runtime's own sources (recompiled every build, ~100ms), the engine is
|
|
73
|
+
* far too big to rebuild per compile. The plain flavor is ALWAYS MinSizeRel
|
|
74
|
+
* regardless of the program's optimization level: measured binary delta is
|
|
75
|
+
* ~620KB vs ~900KB at -O2, and eval-in-an-island workloads don't earn the
|
|
76
|
+
* difference. The asan flavor (Debug + QJS_ENABLE_ASAN) exists so the
|
|
77
|
+
* sanitized test lane checks engine interop under instrumentation.
|
|
78
|
+
*
|
|
79
|
+
* Concurrency: parallel first builds (e.g. test workers) each build in a
|
|
80
|
+
* private temp dir and publish with an atomic rename — first one wins,
|
|
81
|
+
* losers discard their work and use the winner's archive.
|
|
82
|
+
*
|
|
83
|
+
* SCRIPTC_TARGET adds a per-target cache flavor built WITHOUT CMake (the
|
|
84
|
+
* mbedTLS recipe): the qjs library target is just four TUs
|
|
85
|
+
* (QJS_ENGINE_SOURCES) compiled with `zig cc -target <triple>` under the
|
|
86
|
+
* same flags CMake's MinSizeRel/Debug+ASan configurations apply, packed
|
|
87
|
+
* with `zig ar`. Host builds keep the exact historical CMake recipe. */
|
|
88
|
+
async function ensureEngineArchive(sanitize, driver) {
|
|
89
|
+
const flavor = `${sanitize ? "asan" : "plain"}${driver.target !== null ? `-${driver.target}` : ""}`;
|
|
90
|
+
const vendor = vendorEngineDir();
|
|
91
|
+
const cacheRoot = join(vendor, "..", ".cache");
|
|
92
|
+
const cacheDir = join(cacheRoot, `${QJS_COMMIT.slice(0, 12)}-${flavor}`);
|
|
93
|
+
const archive = join(cacheDir, "libqjs.a");
|
|
94
|
+
if (await fileExists(archive))
|
|
95
|
+
return archive;
|
|
96
|
+
if (driver.target !== null)
|
|
97
|
+
return buildEngineArchiveCross(sanitize, driver, cacheRoot, cacheDir);
|
|
98
|
+
await mkdir(cacheRoot, { recursive: true });
|
|
99
|
+
const buildDir = await mkdtemp(join(cacheRoot, `build-${flavor}-`));
|
|
100
|
+
const stageDir = `${buildDir}.stage`;
|
|
101
|
+
try {
|
|
102
|
+
const configureArgs = [
|
|
103
|
+
"-S", vendor,
|
|
104
|
+
"-B", buildDir,
|
|
105
|
+
...(sanitize
|
|
106
|
+
? ["-DCMAKE_BUILD_TYPE=Debug", "-DQJS_ENABLE_ASAN=ON"]
|
|
107
|
+
: ["-DCMAKE_BUILD_TYPE=MinSizeRel"]),
|
|
108
|
+
"-DQJS_BUILD_EXAMPLES=OFF",
|
|
109
|
+
"-DQJS_ENABLE_INSTALL=OFF",
|
|
110
|
+
];
|
|
111
|
+
try {
|
|
112
|
+
await execFileAsync("cmake", configureArgs);
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
if (err.code === "ENOENT") {
|
|
116
|
+
throw new Error("--dynamic builds the embedded engine once with CMake, which was not found on PATH.\n" +
|
|
117
|
+
"Install cmake (e.g. `brew install cmake`) and retry. Static builds do not need it.");
|
|
118
|
+
}
|
|
119
|
+
throw new Error(`cmake failed configuring the embedded engine (${vendor}).\n\n` +
|
|
120
|
+
`${err.stderr ?? String(err)}`);
|
|
121
|
+
}
|
|
122
|
+
await execFileAsync("cmake", [
|
|
123
|
+
"--build", buildDir,
|
|
124
|
+
"--target", "qjs",
|
|
125
|
+
`-j${availableParallelism()}`,
|
|
126
|
+
]);
|
|
127
|
+
// Publish only the archive, atomically: rename fails if a concurrent
|
|
128
|
+
// build won the race, and the winner's archive is just as good.
|
|
129
|
+
await mkdir(stageDir);
|
|
130
|
+
await copyFile(join(buildDir, "libqjs.a"), join(stageDir, "libqjs.a"));
|
|
131
|
+
await rename(stageDir, cacheDir).catch(() => undefined);
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
await rm(buildDir, { recursive: true, force: true });
|
|
135
|
+
await rm(stageDir, { recursive: true, force: true });
|
|
136
|
+
}
|
|
137
|
+
return archive;
|
|
138
|
+
}
|
|
139
|
+
/** The qjs library target's source list (CMakeLists.txt `qjs_sources` with
|
|
140
|
+
* QJS_BUILD_LIBC off — cutils is header-only): the cross-build recipe
|
|
141
|
+
* compiles exactly these. */
|
|
142
|
+
const QJS_ENGINE_SOURCES = ["dtoa.c", "libregexp.c", "libunicode.c", "quickjs.c"];
|
|
143
|
+
/** The engine archive for a CROSS target, per-TU `zig cc` compiles + `zig
|
|
144
|
+
* ar` (no CMake — the mbedTLS recipe). The flags mirror what the CMake
|
|
145
|
+
* configurations apply to the qjs library target: gnu11 (CMAKE_C_EXTENSIONS
|
|
146
|
+
* ON), hidden visibility, -funsigned-char, -DQUICKJS_NG_BUILD and
|
|
147
|
+
* -D_GNU_SOURCE (qjs_defines; linux targetArgs already carry the latter),
|
|
148
|
+
* then MinSizeRel (-Os -DNDEBUG) for plain or Debug+QJS_ENABLE_ASAN
|
|
149
|
+
* (-O0 -ggdb -fno-omit-frame-pointer -fsanitize=address
|
|
150
|
+
* -fno-sanitize-recover=all) for asan. Same atomic-rename publish as every
|
|
151
|
+
* vendor cache. */
|
|
152
|
+
async function buildEngineArchiveCross(sanitize, driver, cacheRoot, cacheDir) {
|
|
153
|
+
const vendor = vendorEngineDir();
|
|
154
|
+
const archive = join(cacheDir, "libqjs.a");
|
|
155
|
+
const arArgv = [...driver.argv.slice(0, 1), "ar"];
|
|
156
|
+
const cflags = [
|
|
157
|
+
"-std=gnu11",
|
|
158
|
+
...driver.targetArgs,
|
|
159
|
+
"-fvisibility=hidden",
|
|
160
|
+
"-funsigned-char",
|
|
161
|
+
"-DQUICKJS_NG_BUILD",
|
|
162
|
+
"-D_GNU_SOURCE",
|
|
163
|
+
// CMake's qjs_defines on WIN32 (quickjs.c's own _WIN32 arms cover the
|
|
164
|
+
// rest — timezoneapi, intrin, cutils.h's _msize usable-size probe).
|
|
165
|
+
...(targetPlatform(driver) === "win32" ? ["-DWIN32_LEAN_AND_MEAN", "-D_WIN32_WINNT=0x0601"] : []),
|
|
166
|
+
...(sanitize
|
|
167
|
+
? ["-O0", "-ggdb", "-fno-omit-frame-pointer", "-fsanitize=address", "-fno-sanitize-recover=all", "-DQJS_ENABLE_ASAN"]
|
|
168
|
+
: ["-Os", "-DNDEBUG"]),
|
|
169
|
+
"-I", vendor,
|
|
170
|
+
];
|
|
171
|
+
await mkdir(cacheRoot, { recursive: true });
|
|
172
|
+
const buildDir = await mkdtemp(join(cacheRoot, `build-qjs-${driver.target ?? "host"}-`));
|
|
173
|
+
try {
|
|
174
|
+
const width = Math.min(QJS_ENGINE_SOURCES.length, availableParallelism());
|
|
175
|
+
for (let i = 0; i < QJS_ENGINE_SOURCES.length; i += width) {
|
|
176
|
+
await Promise.all(QJS_ENGINE_SOURCES.slice(i, i + width).map((src) => execFileAsync(driver.argv[0] ?? "clang", [...driver.argv.slice(1), ...cflags, "-c", join(vendor, src), "-o", join(buildDir, `${basename(src, ".c")}.o`)], { cwd: buildDir })));
|
|
177
|
+
}
|
|
178
|
+
await execFileAsync(arArgv[0] ?? "ar", [...arArgv.slice(1), "rcs", join(buildDir, "libqjs.a"), ...QJS_ENGINE_SOURCES.map((s) => join(buildDir, `${basename(s, ".c")}.o`))]);
|
|
179
|
+
// Atomic publish: rename fails if a concurrent build won the race, and
|
|
180
|
+
// the winner's archive is just as good.
|
|
181
|
+
const stageDir = `${buildDir}.stage`;
|
|
182
|
+
await mkdir(stageDir);
|
|
183
|
+
await copyFile(join(buildDir, "libqjs.a"), join(stageDir, "libqjs.a"));
|
|
184
|
+
await rename(stageDir, cacheDir).catch(async () => {
|
|
185
|
+
await rm(stageDir, { recursive: true, force: true });
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
await rm(buildDir, { recursive: true, force: true });
|
|
190
|
+
}
|
|
191
|
+
return archive;
|
|
192
|
+
}
|
|
193
|
+
/** The vendor sources behind static-build regex support: quickjs-ng's
|
|
194
|
+
* libregexp and its unicode tables (cutils is header-only). Deliberately
|
|
195
|
+
* NOT the engine — a regex-using static binary links ~110KB of matcher,
|
|
196
|
+
* never the ~620KB island. */
|
|
197
|
+
const LRE_SOURCES = ["libregexp.c", "libunicode.c"];
|
|
198
|
+
/** The libregexp objects for one flavor, compiled lazily on the first
|
|
199
|
+
* regex-using static build (~1s) and cached like the engine archive —
|
|
200
|
+
* vendor/.cache/<commit>-lre-<flavor>/*.o — with the same atomic-rename
|
|
201
|
+
* publish (parallel first builds race safely; losers discard their work).
|
|
202
|
+
* Plain is -Os (size class matters: the regex fence pins regex-using
|
|
203
|
+
* binaries well under the engine class); asan matches the final link so
|
|
204
|
+
* the sanitized lane instruments the matcher too. Cross targets get their
|
|
205
|
+
* own flavor directory, compiled with the SCRIPTC_CC driver and its target
|
|
206
|
+
* args — libregexp/libunicode are plain C11, so the cross story is exactly
|
|
207
|
+
* the runtime sources' (no host-built inputs); vendored SOURCES are
|
|
208
|
+
* untouched, only the build wiring knows about targets. */
|
|
209
|
+
async function ensureLreObjects(sanitize, driver) {
|
|
210
|
+
// The flavor keys the DRIVER as well as the target: a zig-cc-built object
|
|
211
|
+
// set must never be handed to a clang link (or vice versa) off a shared
|
|
212
|
+
// cache directory — the historical `lre-plain`/`lre-asan` names stay
|
|
213
|
+
// clang's alone.
|
|
214
|
+
const flavor = (sanitize ? "asan" : "plain") +
|
|
215
|
+
(driver.argv.length === 1 && driver.argv[0] === "clang" ? "" : "-zigcc") +
|
|
216
|
+
(driver.target !== null ? `-${driver.target}` : "");
|
|
217
|
+
const vendor = vendorEngineDir();
|
|
218
|
+
const cacheRoot = join(vendor, "..", ".cache");
|
|
219
|
+
const cacheDir = join(cacheRoot, `${QJS_COMMIT.slice(0, 12)}-lre-${flavor}`);
|
|
220
|
+
const objects = LRE_SOURCES.map((f) => join(cacheDir, f.replace(/\.c$/, ".o")));
|
|
221
|
+
if ((await Promise.all(objects.map(fileExists))).every(Boolean))
|
|
222
|
+
return objects;
|
|
223
|
+
await mkdir(cacheRoot, { recursive: true });
|
|
224
|
+
const buildDir = await mkdtemp(join(cacheRoot, `build-lre-${flavor}-`));
|
|
225
|
+
try {
|
|
226
|
+
// One -c per invocation: zig's COFF driver rejects multiple -c inputs
|
|
227
|
+
// in a single command ("coff does not support linking multiple
|
|
228
|
+
// objects"); per-file compiles produce the identical objects on every
|
|
229
|
+
// target.
|
|
230
|
+
for (const f of LRE_SOURCES) {
|
|
231
|
+
await execFileAsync(driver.argv[0] ?? "clang", [
|
|
232
|
+
...driver.argv.slice(1),
|
|
233
|
+
"-std=c11",
|
|
234
|
+
...driver.targetArgs,
|
|
235
|
+
...(sanitize ? ["-O1", "-fsanitize=address"] : ["-Os"]),
|
|
236
|
+
"-I", vendor,
|
|
237
|
+
"-c", join(vendor, f),
|
|
238
|
+
"-o", join(buildDir, f.replace(/\.c$/, ".o")),
|
|
239
|
+
], { cwd: buildDir });
|
|
240
|
+
}
|
|
241
|
+
// Atomic publish: rename fails if a concurrent build won the race, and
|
|
242
|
+
// the winner's objects are just as good.
|
|
243
|
+
await rename(buildDir, cacheDir).catch(() => undefined);
|
|
244
|
+
}
|
|
245
|
+
finally {
|
|
246
|
+
await rm(buildDir, { recursive: true, force: true });
|
|
247
|
+
}
|
|
248
|
+
return objects;
|
|
249
|
+
}
|
|
250
|
+
function vendorZlibDir() {
|
|
251
|
+
return join(runtimeSrcDir(), "..", "vendor", "zlib");
|
|
252
|
+
}
|
|
253
|
+
/** The vendored zlib TUs behind CROSS-target zlib support: every root *.c
|
|
254
|
+
* except the gzFile file-I/O units (gz*.c — nothing in scr_zlib.c
|
|
255
|
+
* references the gzFile API, and those TUs alone want unistd/io headers).
|
|
256
|
+
* Host builds never touch this list — they link the system libz. */
|
|
257
|
+
const ZLIB_SOURCES = ["adler32.c", "compress.c", "crc32.c", "deflate.c", "infback.c", "inffast.c", "inflate.c", "inftrees.c", "trees.c", "uncompr.c", "zutil.c"];
|
|
258
|
+
/** The zlib objects for one flavor, compiled lazily on the first zlib-using
|
|
259
|
+
* CROSS build (~1s) and cached like the lre objects —
|
|
260
|
+
* vendor/.cache/zlib-<version>-<flavor>/*.o — with the same atomic-rename
|
|
261
|
+
* publish (parallel first builds race safely; losers discard their work).
|
|
262
|
+
* Plain is -Os, asan matches the final link so the sanitized lane
|
|
263
|
+
* instruments the codec too. The flavor keys the driver and target exactly
|
|
264
|
+
* like the lre flavor: only cross builds call this today, but the keying
|
|
265
|
+
* must never hand a zig-built object set to a clang link off a shared
|
|
266
|
+
* directory. */
|
|
267
|
+
async function ensureZlibObjects(sanitize, driver) {
|
|
268
|
+
const flavor = (sanitize ? "asan" : "plain") +
|
|
269
|
+
(driver.argv.length === 1 && driver.argv[0] === "clang" ? "" : "-zigcc") +
|
|
270
|
+
(driver.target !== null ? `-${driver.target}` : "");
|
|
271
|
+
const vendor = vendorZlibDir();
|
|
272
|
+
const cacheRoot = join(vendor, "..", ".cache");
|
|
273
|
+
const cacheDir = join(cacheRoot, `zlib-${ZLIB_VERSION}-${flavor}`);
|
|
274
|
+
const objects = ZLIB_SOURCES.map((f) => join(cacheDir, f.replace(/\.c$/, ".o")));
|
|
275
|
+
if ((await Promise.all(objects.map(fileExists))).every(Boolean))
|
|
276
|
+
return objects;
|
|
277
|
+
await mkdir(cacheRoot, { recursive: true });
|
|
278
|
+
const buildDir = await mkdtemp(join(cacheRoot, `build-zlib-${flavor}-`));
|
|
279
|
+
try {
|
|
280
|
+
// One -c per invocation, the lre recipe (zig's COFF driver rejects
|
|
281
|
+
// multiple -c inputs in a single command).
|
|
282
|
+
for (const f of ZLIB_SOURCES) {
|
|
283
|
+
await execFileAsync(driver.argv[0] ?? "clang", [
|
|
284
|
+
...driver.argv.slice(1),
|
|
285
|
+
"-std=c11",
|
|
286
|
+
...driver.targetArgs,
|
|
287
|
+
...(sanitize ? ["-O1", "-fsanitize=address"] : ["-Os"]),
|
|
288
|
+
"-I", vendor,
|
|
289
|
+
"-c", join(vendor, f),
|
|
290
|
+
"-o", join(buildDir, f.replace(/\.c$/, ".o")),
|
|
291
|
+
], { cwd: buildDir });
|
|
292
|
+
}
|
|
293
|
+
// Atomic publish: rename fails if a concurrent build won the race, and
|
|
294
|
+
// the winner's objects are just as good.
|
|
295
|
+
await rename(buildDir, cacheDir).catch(() => undefined);
|
|
296
|
+
}
|
|
297
|
+
finally {
|
|
298
|
+
await rm(buildDir, { recursive: true, force: true });
|
|
299
|
+
}
|
|
300
|
+
return objects;
|
|
301
|
+
}
|
|
302
|
+
function vendorCurlDir() {
|
|
303
|
+
return join(runtimeSrcDir(), "..", "vendor", "curl");
|
|
304
|
+
}
|
|
305
|
+
/** Every libcurl function scr_fetch_curl.c references — the generated import
|
|
306
|
+
* stub defines exactly these. A new curl call in scr_fetch_curl.c that is
|
|
307
|
+
* missing here fails the CROSS link immediately with the symbol's name
|
|
308
|
+
* (host builds resolve it from the real system libcurl and never look). */
|
|
309
|
+
const CURL_STUB_SYMBOLS = [
|
|
310
|
+
"curl_easy_cleanup", "curl_easy_getinfo", "curl_easy_init", "curl_easy_setopt", "curl_easy_strerror",
|
|
311
|
+
"curl_free", "curl_global_cleanup", "curl_global_init",
|
|
312
|
+
"curl_multi_add_handle", "curl_multi_cleanup", "curl_multi_info_read", "curl_multi_init",
|
|
313
|
+
"curl_multi_perform", "curl_multi_poll", "curl_multi_remove_handle",
|
|
314
|
+
"curl_slist_append", "curl_slist_free_all",
|
|
315
|
+
"curl_url", "curl_url_cleanup", "curl_url_get", "curl_url_set",
|
|
316
|
+
];
|
|
317
|
+
/** The libcurl import stub for one CROSS target, generated lazily on the
|
|
318
|
+
* first fetch-using cross build and cached like the zlib objects —
|
|
319
|
+
* vendor/.cache/curl-stub-<target>/libcurl.so, atomic-rename publish. The
|
|
320
|
+
* stub is empty definitions of CURL_STUB_SYMBOLS compiled `-shared` with
|
|
321
|
+
* `-soname libcurl.so.4`: linking against it satisfies scr_fetch_curl.c's
|
|
322
|
+
* references and records a plain `DT_NEEDED libcurl.so.4`, which the
|
|
323
|
+
* TARGET system's real libcurl satisfies at load time (curl's unversioned
|
|
324
|
+
* exports make the classic import-stub technique sound here — nothing
|
|
325
|
+
* from the stub's bodies ever ships in the binary). One stub per target,
|
|
326
|
+
* no sanitize flavor: the stub's code is never executed or instrumented,
|
|
327
|
+
* and asan links against plain shared libraries freely. */
|
|
328
|
+
async function ensureCurlStub(driver) {
|
|
329
|
+
const cacheRoot = join(vendorCurlDir(), "..", ".cache");
|
|
330
|
+
const cacheDir = join(cacheRoot, `curl-stub-${driver.target}`);
|
|
331
|
+
const lib = join(cacheDir, "libcurl.so");
|
|
332
|
+
if (await fileExists(lib))
|
|
333
|
+
return cacheDir;
|
|
334
|
+
await mkdir(cacheRoot, { recursive: true });
|
|
335
|
+
const buildDir = await mkdtemp(join(cacheRoot, `build-curl-stub-`));
|
|
336
|
+
try {
|
|
337
|
+
const src = join(buildDir, "stub.c");
|
|
338
|
+
await writeFile(src, CURL_STUB_SYMBOLS.map((s) => `void ${s}(void) {}\n`).join(""));
|
|
339
|
+
await execFileAsync(driver.argv[0] ?? "clang", [
|
|
340
|
+
...driver.argv.slice(1),
|
|
341
|
+
...driver.targetArgs,
|
|
342
|
+
"-shared", "-fPIC",
|
|
343
|
+
"-Wl,-soname,libcurl.so.4",
|
|
344
|
+
src,
|
|
345
|
+
"-o", join(buildDir, "libcurl.so"),
|
|
346
|
+
]);
|
|
347
|
+
// Atomic publish: rename fails if a concurrent build won the race, and
|
|
348
|
+
// the winner's stub is just as good.
|
|
349
|
+
const stageDir = `${buildDir}.stage`;
|
|
350
|
+
await mkdir(stageDir);
|
|
351
|
+
await copyFile(join(buildDir, "libcurl.so"), join(stageDir, "libcurl.so"));
|
|
352
|
+
await rename(stageDir, cacheDir).catch(async () => {
|
|
353
|
+
await rm(stageDir, { recursive: true, force: true });
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
finally {
|
|
357
|
+
await rm(buildDir, { recursive: true, force: true });
|
|
358
|
+
}
|
|
359
|
+
return cacheDir;
|
|
360
|
+
}
|
|
361
|
+
function vendorTlsDir() {
|
|
362
|
+
return join(runtimeSrcDir(), "..", "vendor", "mbedtls");
|
|
363
|
+
}
|
|
364
|
+
/** The mbedTLS archive for one flavor, compiled lazily on the first
|
|
365
|
+
* TLS-using build (~15s over ~110 TUs, parallelized) and cached like the
|
|
366
|
+
* engine archive — vendor/.cache/mbedtls-<version>-<flavor>/libmbedtls.a —
|
|
367
|
+
* with the same atomic-rename publish (parallel first builds race safely;
|
|
368
|
+
* losers discard their work). Plain is -Os (the TLS stack is link-gated
|
|
369
|
+
* but still wants its size class small); asan matches the final link so
|
|
370
|
+
* the sanitized lane instruments every TLS path too. No CMake: the stock
|
|
371
|
+
* config builds every library/*.c standalone with clang, so the recipe is
|
|
372
|
+
* per-TU `-c` compiles plus one `ar rcs` — vendored-build machinery the
|
|
373
|
+
* lre-objects cache already established.
|
|
374
|
+
*
|
|
375
|
+
* SCRIPTC_TARGET adds a per-target cache flavor (the lre-objects story):
|
|
376
|
+
* TUs compile with `zig cc -target <triple>` and the archive is packed
|
|
377
|
+
* with `zig ar` (llvm-ar — the host BSD ar has no business indexing ELF
|
|
378
|
+
* objects). Host builds keep the exact historical clang + ar recipe. */
|
|
379
|
+
async function ensureTlsArchive(sanitize, driver) {
|
|
380
|
+
const flavor = `${sanitize ? "asan" : "plain"}${driver.target !== null ? `-${driver.target}` : ""}`;
|
|
381
|
+
const compileArgv = driver.target !== null ? driver.argv : ["clang"];
|
|
382
|
+
const arArgv = driver.target !== null ? [...driver.argv.slice(0, 1), "ar"] : ["ar"];
|
|
383
|
+
const vendor = vendorTlsDir();
|
|
384
|
+
const cacheRoot = join(vendor, "..", ".cache");
|
|
385
|
+
const cacheDir = join(cacheRoot, `mbedtls-${MBEDTLS_VERSION}-${flavor}`);
|
|
386
|
+
const archive = join(cacheDir, "libmbedtls.a");
|
|
387
|
+
if (await fileExists(archive))
|
|
388
|
+
return archive;
|
|
389
|
+
await mkdir(cacheRoot, { recursive: true });
|
|
390
|
+
const buildDir = await mkdtemp(join(cacheRoot, `build-mbedtls-${flavor}-`));
|
|
391
|
+
try {
|
|
392
|
+
const sources = (await readdir(join(vendor, "library"))).filter((n) => n.endsWith(".c")).sort();
|
|
393
|
+
const cflags = [
|
|
394
|
+
"-std=c11",
|
|
395
|
+
...driver.targetArgs, // ALWAYS empty on host builds
|
|
396
|
+
...(sanitize ? ["-O1", "-fsanitize=address"] : ["-Os"]),
|
|
397
|
+
"-I", join(vendor, "include"),
|
|
398
|
+
"-I", join(vendor, "library"),
|
|
399
|
+
];
|
|
400
|
+
const width = availableParallelism();
|
|
401
|
+
for (let i = 0; i < sources.length; i += width) {
|
|
402
|
+
await Promise.all(sources.slice(i, i + width).map((src) => execFileAsync(compileArgv[0] ?? "clang", [...compileArgv.slice(1), ...cflags, "-c", join(vendor, "library", src), "-o", join(buildDir, `${basename(src, ".c")}.o`)])));
|
|
403
|
+
}
|
|
404
|
+
await execFileAsync(arArgv[0] ?? "ar", [...arArgv.slice(1), "rcs", join(buildDir, "libmbedtls.a"), ...sources.map((s) => join(buildDir, `${basename(s, ".c")}.o`))]);
|
|
405
|
+
// Atomic publish: rename fails if a concurrent build won the race, and
|
|
406
|
+
// the winner's archive is just as good.
|
|
407
|
+
const stageDir = `${buildDir}.stage`;
|
|
408
|
+
await mkdir(stageDir);
|
|
409
|
+
await copyFile(join(buildDir, "libmbedtls.a"), join(stageDir, "libmbedtls.a"));
|
|
410
|
+
await rename(stageDir, cacheDir).catch(async () => {
|
|
411
|
+
await rm(stageDir, { recursive: true, force: true });
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
finally {
|
|
415
|
+
await rm(buildDir, { recursive: true, force: true });
|
|
416
|
+
}
|
|
417
|
+
return archive;
|
|
418
|
+
}
|
|
419
|
+
/* ------------------- build cache (opt-in via SCRIPTC_CACHE_DIR) ----------------
|
|
420
|
+
* Content-addressed caches that let repeat builds of unchanged programs skip
|
|
421
|
+
* clang entirely — the test lanes' dominant cost. Two keyspaces under the
|
|
422
|
+
* cache root:
|
|
423
|
+
*
|
|
424
|
+
* bin/<key> — whole program binaries. key = sha256(clang version,
|
|
425
|
+
* runtime fingerprint (every .c/.h in the runtime src dir
|
|
426
|
+
* plus the vendor pin QJS_COMMIT), the FULL normalized
|
|
427
|
+
* command line, the emitted C bytes). Emitted C is
|
|
428
|
+
* byte-stable by project invariant, so unchanged programs
|
|
429
|
+
* hit; any flag difference — e.g. the sanitized lane's
|
|
430
|
+
* -O1/-fsanitize=address/-DSCR_RC_AUDIT — lands in a
|
|
431
|
+
* naturally distinct key. On a hit the cached binary is
|
|
432
|
+
* copied and atomically renamed onto outPath (never an
|
|
433
|
+
* in-place overwrite — see the hit path) and clang never
|
|
434
|
+
* runs; the binary is still EXECUTED live by whoever
|
|
435
|
+
* asked for it, so no comparison or sanitizer coverage
|
|
436
|
+
* is ever skipped.
|
|
437
|
+
*
|
|
438
|
+
* obj/<set>/<f>.o — per-flavor runtime objects for cache-miss builds. The
|
|
439
|
+
* historical single invocation recompiles every runtime
|
|
440
|
+
* TU per program (~1.3s at -O2); with cached objects a
|
|
441
|
+
* miss compiles ONLY the program's C and links (~0.15s).
|
|
442
|
+
* The clang driver hands every input the same option set,
|
|
443
|
+
* so per-TU `-c` compiles with those options plus a final
|
|
444
|
+
* link reproduce the single invocation exactly. Object
|
|
445
|
+
* compiles go through ccache when it is installed (silent
|
|
446
|
+
* fallback when not).
|
|
447
|
+
*
|
|
448
|
+
* Escape hatches, honored in BOTH directions (no reads, no writes): leave
|
|
449
|
+
* SCRIPTC_CACHE_DIR unset (the production default — the CLI never caches unless
|
|
450
|
+
* asked; vitest.config.ts sets it for the test lanes) or set SCRIPTC_NO_CACHE=1.
|
|
451
|
+
* Either way compileC issues the exact historical command line.
|
|
452
|
+
*
|
|
453
|
+
* Eviction: size-capped LRU over the whole cache root (SCRIPTC_CACHE_MAX_MB,
|
|
454
|
+
* default 4096) swept at most once per process after a write; reads bump
|
|
455
|
+
* mtimes. The harness's oracle cache lives under the same root and is swept
|
|
456
|
+
* by the same pass. Cache trouble is never a build failure — every cache
|
|
457
|
+
* error falls back to a real compile. */
|
|
458
|
+
function cacheRootDir() {
|
|
459
|
+
if (process.env["SCRIPTC_NO_CACHE"] === "1")
|
|
460
|
+
return null;
|
|
461
|
+
const dir = process.env["SCRIPTC_CACHE_DIR"];
|
|
462
|
+
return dir !== undefined && dir !== "" ? dir : null;
|
|
463
|
+
}
|
|
464
|
+
const ccVersionMemos = new Map();
|
|
465
|
+
function ccVersionOnce(argv) {
|
|
466
|
+
const key = argv.join("\x1f");
|
|
467
|
+
let memo = ccVersionMemos.get(key);
|
|
468
|
+
if (memo === undefined) {
|
|
469
|
+
// cwd: the version probe must not touch the caller's directory —
|
|
470
|
+
// `zig cc --version` (zig 0.16) drops an empty a.o wherever it runs.
|
|
471
|
+
memo = execFileAsync(argv[0] ?? "clang", [...argv.slice(1), "--version"], { cwd: tmpdir() }).then((r) => r.stdout.split("\n", 1)[0] ?? "");
|
|
472
|
+
ccVersionMemos.set(key, memo);
|
|
473
|
+
}
|
|
474
|
+
return memo;
|
|
475
|
+
}
|
|
476
|
+
let ccacheMemo = null;
|
|
477
|
+
function ccacheAvailable() {
|
|
478
|
+
ccacheMemo ??= execFileAsync("ccache", ["--version"]).then(() => true, () => false);
|
|
479
|
+
return ccacheMemo;
|
|
480
|
+
}
|
|
481
|
+
/** Content hash of every .c/.h in the runtime src dir plus the vendor pin —
|
|
482
|
+
* everything a binary links that the emitted C bytes don't already cover
|
|
483
|
+
* (npm-embedded C rides inside the emitted C; the engine archive and lre
|
|
484
|
+
* objects are pinned by QJS_COMMIT, the same trust their own caches use).
|
|
485
|
+
* Memoized behind a stat signature so watch-mode edits re-hash. */
|
|
486
|
+
let rtFingerprintMemo = null;
|
|
487
|
+
async function runtimeFingerprint(rtDir) {
|
|
488
|
+
const names = (await readdir(rtDir)).filter((n) => n.endsWith(".c") || n.endsWith(".h")).sort();
|
|
489
|
+
const stats = await Promise.all(names.map((n) => stat(join(rtDir, n))));
|
|
490
|
+
const sig = stats.map((s, i) => `${names[i] ?? ""}:${s.size}:${s.mtimeMs}`).join("|");
|
|
491
|
+
if (rtFingerprintMemo !== null && rtFingerprintMemo.sig === sig)
|
|
492
|
+
return rtFingerprintMemo.hash;
|
|
493
|
+
const h = createHash("sha256").update(QJS_COMMIT).update(MBEDTLS_VERSION).update(ZLIB_VERSION);
|
|
494
|
+
for (const n of names)
|
|
495
|
+
h.update(n).update("\0").update(await readFile(join(rtDir, n))).update("\0");
|
|
496
|
+
const hash = h.digest("hex");
|
|
497
|
+
rtFingerprintMemo = { sig, hash };
|
|
498
|
+
return hash;
|
|
499
|
+
}
|
|
500
|
+
/** The cached .o set for one flag flavor, compiled on first need. Concurrent
|
|
501
|
+
* first builds (parallel test workers on a cold cache) may duplicate work;
|
|
502
|
+
* per-file atomic renames make every winner equivalent. */
|
|
503
|
+
async function ensureRuntimeObjects(root, ccArgv, cflags, sources, keyPrefix) {
|
|
504
|
+
const setKey = createHash("sha256")
|
|
505
|
+
.update(keyPrefix)
|
|
506
|
+
.update(cflags.join("\x1f"))
|
|
507
|
+
.digest("hex")
|
|
508
|
+
.slice(0, 24);
|
|
509
|
+
const objDir = join(root, "obj", setKey);
|
|
510
|
+
const objOf = (src) => join(objDir, `${basename(src, ".c")}.o`);
|
|
511
|
+
const present = await Promise.all(sources.map((s) => fileExists(objOf(s))));
|
|
512
|
+
const missing = sources.filter((_, i) => !present[i]);
|
|
513
|
+
if (missing.length > 0) {
|
|
514
|
+
// ccache wraps only the default clang driver — multi-word drivers
|
|
515
|
+
// (`zig cc`) run bare; their object sets are keyed apart anyway.
|
|
516
|
+
const useCcache = ccArgv.length === 1 && ccArgv[0] === "clang" && (await ccacheAvailable());
|
|
517
|
+
await mkdir(objDir, { recursive: true });
|
|
518
|
+
const tmpDir = await mkdtemp(join(root, "obj", "build-"));
|
|
519
|
+
try {
|
|
520
|
+
// Modest parallelism: a flavor's objects build once, but several cold
|
|
521
|
+
// workers can race here — keep each build's CPU footprint small.
|
|
522
|
+
const width = 4;
|
|
523
|
+
for (let i = 0; i < missing.length; i += width) {
|
|
524
|
+
await Promise.all(missing.slice(i, i + width).map(async (src) => {
|
|
525
|
+
const tmpObj = join(tmpDir, `${basename(src, ".c")}.o`);
|
|
526
|
+
const argv = [...(useCcache ? ["ccache"] : []), ...ccArgv, ...cflags, "-c", src, "-o", tmpObj];
|
|
527
|
+
await execFileAsync(argv[0] ?? "clang", argv.slice(1));
|
|
528
|
+
await rename(tmpObj, objOf(src));
|
|
529
|
+
}));
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
finally {
|
|
533
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return new Map(sources.map((s) => [s, objOf(s)]));
|
|
537
|
+
}
|
|
538
|
+
/** Size-capped LRU sweep of the whole cache root, at most once per process
|
|
539
|
+
* and only after a write. Oldest-mtime files go first until the tree is back
|
|
540
|
+
* under 75% of the cap; read hits bump mtimes so hot entries survive. */
|
|
541
|
+
let pruneDone = false;
|
|
542
|
+
async function pruneCache(root) {
|
|
543
|
+
if (pruneDone)
|
|
544
|
+
return;
|
|
545
|
+
pruneDone = true;
|
|
546
|
+
const capBytes = Number(process.env["SCRIPTC_CACHE_MAX_MB"] ?? "4096") * 1024 * 1024;
|
|
547
|
+
if (!Number.isFinite(capBytes) || capBytes <= 0)
|
|
548
|
+
return;
|
|
549
|
+
const files = [];
|
|
550
|
+
const walk = async (dir) => {
|
|
551
|
+
const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
552
|
+
for (const ent of entries) {
|
|
553
|
+
const p = join(dir, ent.name);
|
|
554
|
+
if (ent.isDirectory())
|
|
555
|
+
await walk(p);
|
|
556
|
+
else if (ent.isFile()) {
|
|
557
|
+
const s = await stat(p).catch(() => null);
|
|
558
|
+
if (s !== null)
|
|
559
|
+
files.push({ path: p, size: s.size, mtimeMs: s.mtimeMs });
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
await walk(root);
|
|
564
|
+
let total = files.reduce((n, f) => n + f.size, 0);
|
|
565
|
+
if (total <= capBytes)
|
|
566
|
+
return;
|
|
567
|
+
files.sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
568
|
+
const recent = Date.now() - 60 * 60 * 1000; // never evict anything a live run may be using
|
|
569
|
+
for (const f of files) {
|
|
570
|
+
if (total <= capBytes * 0.75 || f.mtimeMs > recent)
|
|
571
|
+
break;
|
|
572
|
+
await unlink(f.path).catch(() => undefined);
|
|
573
|
+
total -= f.size;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
/** Compiles one C program together with the runtime sources.
|
|
577
|
+
* Without a cache dir (the production default), the runtime (a dozen small
|
|
578
|
+
* files) is recompiled on every build in one historical clang invocation —
|
|
579
|
+
* no cached-archive staleness bugs. --dynamic additionally compiles
|
|
580
|
+
* scr_island.c under SCR_DYNAMIC and links the cached engine archive (built
|
|
581
|
+
* lazily, see above); regex-using programs additionally compile scr_regex.c
|
|
582
|
+
* and link libregexp (the cached objects, or the archive's own copy under
|
|
583
|
+
* --dynamic). Without either, the command line is exactly the historical
|
|
584
|
+
* one — regex-free static builds must stay byte-identical.
|
|
585
|
+
*
|
|
586
|
+
* With SCRIPTC_CACHE_DIR set (the test lanes; see the cache block above),
|
|
587
|
+
* unchanged programs skip clang via the binary cache, and misses link the
|
|
588
|
+
* program's own TU against cached per-flavor runtime objects. */
|
|
589
|
+
export async function compileC(opts) {
|
|
590
|
+
const rtDir = runtimeSrcDir();
|
|
591
|
+
const dynamic = opts.dynamic ?? false;
|
|
592
|
+
const regex = opts.regex ?? false;
|
|
593
|
+
// fetch's implementation switch: the default is the NATIVE bridge
|
|
594
|
+
// (scr_fetch.c over scr_net + scr_tls + scr_http's client parser +
|
|
595
|
+
// zlib — no libcurl anywhere), which implies the socket units into the
|
|
596
|
+
// link. SCRIPTC_FETCH_CURL=1 keeps the retired curl reference
|
|
597
|
+
// (scr_fetch_curl.c + system libcurl / the linux soname stub)
|
|
598
|
+
// compilable for one release as the flip's reference.
|
|
599
|
+
const fetchOn = opts.fetch ?? false;
|
|
600
|
+
const curlFetch = fetchOn && process.env["SCRIPTC_FETCH_CURL"] === "1";
|
|
601
|
+
const nativeFetch = fetchOn && !curlFetch;
|
|
602
|
+
// The island's node:http/https client bridge: embedded graphs that
|
|
603
|
+
// import those builtins get working clients over the same socket units
|
|
604
|
+
// (native-fetch builds always carry it — scr_fetch_install registers it).
|
|
605
|
+
const netIsland = dynamic && ((opts.netIsland ?? false) || nativeFetch);
|
|
606
|
+
const net = (opts.net ?? false) || nativeFetch || netIsland;
|
|
607
|
+
const http = (opts.http ?? false) || nativeFetch || netIsland;
|
|
608
|
+
const tls = (opts.tls ?? false) || nativeFetch || netIsland;
|
|
609
|
+
const driver = resolveCc();
|
|
610
|
+
if (driver.target !== null) {
|
|
611
|
+
// See the resolveCc block: these inputs are built on and for the HOST
|
|
612
|
+
// (vendored archives, system libs). Regex, zlib, and the engine archive
|
|
613
|
+
// are NOT here: their vendored sources are plain C that ensureLreObjects
|
|
614
|
+
// / ensureZlibObjects / buildEngineArchiveCross compile per target with
|
|
615
|
+
// the driver itself — win32 included (the Windows lane runs the
|
|
616
|
+
// @dynamic corpus and the zlib program against the box's Node). The
|
|
617
|
+
// NATIVE fetch rides the socket units and cross-compiles with them —
|
|
618
|
+
// no gate; only the retired curl REFERENCE (SCRIPTC_FETCH_CURL=1)
|
|
619
|
+
// keeps a linux-only arm (the vendored curl headers + the generated
|
|
620
|
+
// soname stub, ensureCurlStub — win32 has no system libcurl contract
|
|
621
|
+
// to bind at load time). The event-loop units, tls, and dgram cross-compile to Linux
|
|
622
|
+
// AND Windows (scr_platform.h poller + per-target mbedTLS; the
|
|
623
|
+
// loop's win32 arm is WSAPoll, scr_loop_wsapoll.c, and the socket
|
|
624
|
+
// units' win32 arms respell winsock behind POSIX-errno wrappers —
|
|
625
|
+
// scr_net.c/scr_dgram.c/scr_tls.c). The events unit cross-compiles
|
|
626
|
+
// everywhere: its win32 arm is CRT signal() +
|
|
627
|
+
// PeekNamedPipe/WaitForSingleObject probes (scr_events.c), served by
|
|
628
|
+
// the loop's capped win32 idle sleep (scr_async.c).
|
|
629
|
+
const unsupported = [
|
|
630
|
+
// The NATIVE fetch cross-compiles wherever the socket units do
|
|
631
|
+
// (linux and win32 both); only the retired curl reference keeps
|
|
632
|
+
// its linux-only soname-stub arm.
|
|
633
|
+
["fetch (SCRIPTC_FETCH_CURL)", curlFetch && targetPlatform(driver) !== "linux"],
|
|
634
|
+
]
|
|
635
|
+
.filter(([, on]) => on)
|
|
636
|
+
.map(([name]) => name);
|
|
637
|
+
if (unsupported.length > 0) {
|
|
638
|
+
throw new Error(`SCRIPTC_TARGET=${driver.target}: ${unsupported.join(", ")} not supported under a cross target yet ` +
|
|
639
|
+
`(host-built vendor archives / system libs — see docs/linux-port.md).`);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
const engineArchive = dynamic ? await ensureEngineArchive(opts.sanitize ?? false, driver) : null;
|
|
643
|
+
const tlsArchive = tls ? await ensureTlsArchive(opts.sanitize ?? false, driver) : null;
|
|
644
|
+
// --dynamic + regex shares the archive's libregexp (its host hooks and
|
|
645
|
+
// ours would collide; see scr_regex.c) — the standalone objects are for
|
|
646
|
+
// static builds only.
|
|
647
|
+
const lreObjects = regex && !dynamic ? await ensureLreObjects(opts.sanitize ?? false, driver) : [];
|
|
648
|
+
// Vendored zlib is the CROSS story only — host builds keep the exact
|
|
649
|
+
// historical `-lz` system link (see CcOptions.zlib). The native fetch's
|
|
650
|
+
// gzip decoder rides the same objects/link.
|
|
651
|
+
const zlibObjects = ((opts.zlib ?? false) || nativeFetch) && driver.target !== null
|
|
652
|
+
? await ensureZlibObjects(opts.sanitize ?? false, driver)
|
|
653
|
+
: [];
|
|
654
|
+
// The libcurl import stub is likewise CROSS-only — host builds keep the
|
|
655
|
+
// exact historical system `-lcurl` link (see CcOptions.fetch). Curl
|
|
656
|
+
// reference builds only.
|
|
657
|
+
const curlStubDir = curlFetch && driver.target !== null ? await ensureCurlStub(driver) : null;
|
|
658
|
+
// rt() maps each runtime source's path on the command line: identity for
|
|
659
|
+
// the historical single invocation, cached-.o substitution on cache misses.
|
|
660
|
+
const buildArgs = (rt) => [
|
|
661
|
+
"-std=c11",
|
|
662
|
+
...driver.targetArgs, // ALWAYS empty on the default clang path
|
|
663
|
+
...(opts.sanitize
|
|
664
|
+
? ["-O1", "-fsanitize=address", "-DSCR_RC_AUDIT"]
|
|
665
|
+
: ["-O2"]),
|
|
666
|
+
"-fno-math-errno",
|
|
667
|
+
"-Wno-deprecated-declarations", // ucontext fibers (scr_async.c)
|
|
668
|
+
"-I", rtDir,
|
|
669
|
+
...RUNTIME_SOURCES.map((f) => rt(join(rtDir, f))),
|
|
670
|
+
// win32 targets compile the libc-shim TU (stpcpy, arc4random_buf,
|
|
671
|
+
// gmtime_r, strcasestr — the _WIN32 block in scr_runtime.h declares
|
|
672
|
+
// them) and link advapi32 (the CSPRNG RtlGenRandom/SystemFunction036,
|
|
673
|
+
// GetUserNameA), iphlpapi (GetAdaptersAddresses behind
|
|
674
|
+
// os.networkInterfaces), and ws2_32 (inet_ntop there; the socket
|
|
675
|
+
// units ride the same import). Never present on the default path, so
|
|
676
|
+
// the historical line cannot change.
|
|
677
|
+
...(targetPlatform(driver) === "win32"
|
|
678
|
+
? [rt(join(rtDir, "scr_win.c")), "-ladvapi32", "-liphlpapi", "-lws2_32"]
|
|
679
|
+
: []),
|
|
680
|
+
...(regex
|
|
681
|
+
? ["-I", vendorEngineDir(), rt(join(rtDir, "scr_regex.c")), ...lreObjects]
|
|
682
|
+
: []),
|
|
683
|
+
...(opts.assert || regex || opts.symbol ? [rt(join(rtDir, "scr_assert.c"))] : []),
|
|
684
|
+
...(opts.inspect ? [rt(join(rtDir, "scr_inspect.c"))] : []),
|
|
685
|
+
...(opts.dynInvoke ? [rt(join(rtDir, "scr_dyn_invoke.c"))] : []),
|
|
686
|
+
...(opts.dc ? [rt(join(rtDir, "scr_dc.c"))] : []),
|
|
687
|
+
...(opts.dynAsync || opts.dynInvoke || opts.dc ? [rt(join(rtDir, "scr_async_dyn.c"))] : []),
|
|
688
|
+
// The zlib UNIT (scr_zlib.c) gates on zlib.* IR use; the LINK (system
|
|
689
|
+
// libz on hosts, the vendored per-target objects on cross builds)
|
|
690
|
+
// also serves the native fetch's gzip decoder — spread exactly once.
|
|
691
|
+
...(opts.zlib
|
|
692
|
+
? driver.target !== null
|
|
693
|
+
? ["-I", vendorZlibDir(), rt(join(rtDir, "scr_zlib.c")), ...zlibObjects]
|
|
694
|
+
: [rt(join(rtDir, "scr_zlib.c")), "-lz"]
|
|
695
|
+
: nativeFetch
|
|
696
|
+
? driver.target !== null
|
|
697
|
+
? ["-I", vendorZlibDir(), ...zlibObjects]
|
|
698
|
+
: ["-lz"]
|
|
699
|
+
: []),
|
|
700
|
+
// The zlib ↔ island bridge: only when BOTH halves are in the build
|
|
701
|
+
// (the scr_inspect_island.c pattern) — the emitted main calls its
|
|
702
|
+
// installer exactly then.
|
|
703
|
+
...(opts.zlib && opts.dynamic ? [rt(join(rtDir, "scr_zlib_island.c"))] : []),
|
|
704
|
+
...(opts.events ? [rt(join(rtDir, "scr_events.c")), rt(join(rtDir, "scr_readline.c"))] : []),
|
|
705
|
+
...(opts.emitter ? [rt(join(rtDir, "scr_events_emitter.c"))] : []),
|
|
706
|
+
// The checked-dynamic HANDLE support unit (listener gate + runtime
|
|
707
|
+
// adapter closures): every referencing unit is one of the emitter or
|
|
708
|
+
// net families (http implies net), so handle-free binaries keep
|
|
709
|
+
// their exact size class.
|
|
710
|
+
...(opts.emitter || net ? [rt(join(rtDir, "scr_dyn_handle.c"))] : []),
|
|
711
|
+
...(opts.symbol ? [rt(join(rtDir, "scr_symbol.c"))] : []),
|
|
712
|
+
...(opts.searchParams ? [rt(join(rtDir, "scr_url_params.c"))] : []),
|
|
713
|
+
...(opts.stream ? [rt(join(rtDir, "scr_stream.c"))] : []),
|
|
714
|
+
// The readiness-poller backends (scr_platform.h): kqueue on macOS/BSD,
|
|
715
|
+
// epoll on Linux, WSAPoll on Windows — each TU is empty off its
|
|
716
|
+
// platform, so all three link whenever a poller-using unit does and
|
|
717
|
+
// the others cost nothing (ws2_32 rides the unconditional win32 libs
|
|
718
|
+
// above).
|
|
719
|
+
...(net || opts.dgram
|
|
720
|
+
? [
|
|
721
|
+
rt(join(rtDir, "scr_loop_kqueue.c")),
|
|
722
|
+
rt(join(rtDir, "scr_loop_epoll.c")),
|
|
723
|
+
rt(join(rtDir, "scr_loop_wsapoll.c")),
|
|
724
|
+
]
|
|
725
|
+
: []),
|
|
726
|
+
...(net ? [rt(join(rtDir, "scr_net.c"))] : []),
|
|
727
|
+
...(http ? [rt(join(rtDir, "scr_http.c"))] : []),
|
|
728
|
+
...(opts.http2 ?? false ? [rt(join(rtDir, "scr_http2.c"))] : []),
|
|
729
|
+
...(opts.dgram ? [rt(join(rtDir, "scr_dgram.c"))] : []),
|
|
730
|
+
...(opts.watch ? [rt(join(rtDir, "scr_watch.c"))] : []),
|
|
731
|
+
...(opts.nodeTest ? [rt(join(rtDir, "scr_test.c"))] : []),
|
|
732
|
+
...(tlsArchive
|
|
733
|
+
? [
|
|
734
|
+
"-I", join(vendorTlsDir(), "include"),
|
|
735
|
+
rt(join(rtDir, "scr_tls.c")),
|
|
736
|
+
tlsArchive,
|
|
737
|
+
// mbedTLS's win32 entropy poll is BCryptGenRandom (bcrypt.h) —
|
|
738
|
+
// an import the unconditional win32 libs above don't carry.
|
|
739
|
+
// Never present on the default path, so the historical TLS
|
|
740
|
+
// link line cannot change.
|
|
741
|
+
...(targetPlatform(driver) === "win32" ? ["-lbcrypt"] : []),
|
|
742
|
+
]
|
|
743
|
+
: []),
|
|
744
|
+
...(engineArchive
|
|
745
|
+
? [
|
|
746
|
+
"-DSCR_DYNAMIC",
|
|
747
|
+
"-I", vendorEngineDir(),
|
|
748
|
+
rt(join(rtDir, "scr_island.c")),
|
|
749
|
+
rt(join(rtDir, "scr_web.c")),
|
|
750
|
+
// The one unit referencing BOTH the island and the inspect
|
|
751
|
+
// engine (insp.jsval): linked exactly when both halves are.
|
|
752
|
+
...(opts.inspect ? [rt(join(rtDir, "scr_inspect_island.c"))] : []),
|
|
753
|
+
// fetch: the NATIVE bridge by default (its socket/tls/zlib
|
|
754
|
+
// dependencies joined the link above); the curl REFERENCE
|
|
755
|
+
// (SCRIPTC_FETCH_CURL=1) keeps the historical system -lcurl /
|
|
756
|
+
// linux soname-stub arms for one release.
|
|
757
|
+
...(nativeFetch ? [rt(join(rtDir, "scr_fetch.c"))] : []),
|
|
758
|
+
// The island's node:http/https CLIENT bridge (scr_net_island.c):
|
|
759
|
+
// the one TU referencing both the socket units and the engine —
|
|
760
|
+
// compiled beside the native fetch (scr_fetch_install registers
|
|
761
|
+
// it) and whenever the embedded graph imports node:http/https
|
|
762
|
+
// (the emitted main calls scr_net_island_install exactly then).
|
|
763
|
+
...(netIsland ? [rt(join(rtDir, "scr_net_island.c"))] : []),
|
|
764
|
+
...(curlFetch
|
|
765
|
+
? curlStubDir !== null
|
|
766
|
+
? ["-I", join(vendorCurlDir(), "include"), rt(join(rtDir, "scr_fetch_curl.c")), `-L${curlStubDir}`, "-lcurl"]
|
|
767
|
+
: [rt(join(rtDir, "scr_fetch_curl.c")), "-lcurl"]
|
|
768
|
+
: []),
|
|
769
|
+
engineArchive,
|
|
770
|
+
"-lm",
|
|
771
|
+
// ld64 dead-stripping claws back a chunk of the engine archive;
|
|
772
|
+
// harmless elsewhere but only spelled this way on macOS. Keyed on
|
|
773
|
+
// the TARGET platform (= the host on the default path, where this
|
|
774
|
+
// expression is byte-identical to the historical one).
|
|
775
|
+
...(targetPlatform(driver) === "darwin" ? ["-Wl,-dead_strip"] : []),
|
|
776
|
+
// The PE stack reserve, pinned to the 8MB POSIX main-stack
|
|
777
|
+
// geometry ISL_MAIN_STACK_BUDGET is sized against (4MB engine
|
|
778
|
+
// budget + 4MB excursion margin) — quickjs-ng's own CMake makes
|
|
779
|
+
// the same 8MB choice on Windows. Not left to the driver:
|
|
780
|
+
// classic mingw ld defaults to 2MB (which the budget would blow
|
|
781
|
+
// straight past); zig's lld happens to default to 16MB today,
|
|
782
|
+
// but that is nobody's contract.
|
|
783
|
+
...(targetPlatform(driver) === "win32" ? ["-Wl,--stack,8388608"] : []),
|
|
784
|
+
]
|
|
785
|
+
: []),
|
|
786
|
+
// A .ll program TU (the LLVM backend) deliberately carries no target
|
|
787
|
+
// triple (byte-stable output; clang supplies the host/SCRIPTC_TARGET
|
|
788
|
+
// triple exactly as it does for .c) — silence the -Woverride-module
|
|
789
|
+
// note about that. Never present for .c inputs, so the historical C
|
|
790
|
+
// command line cannot change by a byte.
|
|
791
|
+
...(opts.cPath.endsWith(".ll") ? ["-Wno-override-module"] : []),
|
|
792
|
+
opts.cPath,
|
|
793
|
+
"-o", opts.outPath,
|
|
794
|
+
];
|
|
795
|
+
const ccName = driver.argv.join(" ");
|
|
796
|
+
const runClang = async (args) => {
|
|
797
|
+
try {
|
|
798
|
+
await execFileAsync(driver.argv[0] ?? "clang", [...driver.argv.slice(1), ...args]);
|
|
799
|
+
}
|
|
800
|
+
catch (err) {
|
|
801
|
+
const stderr = err.stderr ?? String(err);
|
|
802
|
+
throw new Error(`${ccName} failed compiling ${opts.cPath}.\n` +
|
|
803
|
+
`This is a scriptc bug (generated C should always compile) unless ` +
|
|
804
|
+
`${ccName} itself is missing/broken.\n\n${stderr}`);
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
const root = cacheRootDir();
|
|
808
|
+
if (root === null) {
|
|
809
|
+
// The exact historical command line, byte for byte.
|
|
810
|
+
await runClang(buildArgs((p) => p));
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
const [cv, fingerprint, cBytes] = await Promise.all([
|
|
814
|
+
ccVersionOnce(driver.argv),
|
|
815
|
+
runtimeFingerprint(rtDir),
|
|
816
|
+
readFile(opts.cPath),
|
|
817
|
+
]);
|
|
818
|
+
// The key sees the full command line with the two program-specific paths
|
|
819
|
+
// normalized out (their CONTENT is what matters: the C bytes are hashed,
|
|
820
|
+
// the out path is where the result lands). Runtime and vendor paths stay
|
|
821
|
+
// verbatim — their contents are covered by the fingerprint and the pin.
|
|
822
|
+
const identityArgs = buildArgs((p) => p).map((a) => a === opts.cPath ? "<program.c>" : a === opts.outPath ? "<out>" : a);
|
|
823
|
+
const key = createHash("sha256")
|
|
824
|
+
.update("bin-v1\0")
|
|
825
|
+
// The driver spelling joins the version string: `zig cc --version`
|
|
826
|
+
// reports the clang underneath and could otherwise collide with a
|
|
827
|
+
// same-version host clang.
|
|
828
|
+
.update(ccName).update("\0")
|
|
829
|
+
.update(cv).update("\0")
|
|
830
|
+
.update(fingerprint).update("\0")
|
|
831
|
+
.update(identityArgs.join("\x1f")).update("\0")
|
|
832
|
+
.update(cBytes)
|
|
833
|
+
.digest("hex");
|
|
834
|
+
const binDir = join(root, "bin");
|
|
835
|
+
const cachedBin = join(binDir, key);
|
|
836
|
+
try {
|
|
837
|
+
// NEVER copy over outPath in place: overwriting an already-executed
|
|
838
|
+
// signed binary invalidates the kernel's per-vnode code-signature cache
|
|
839
|
+
// on macOS and the next exec dies with SIGKILL. Copy to a fresh inode
|
|
840
|
+
// and rename it into place instead.
|
|
841
|
+
const tmpOut = `${opts.outPath}.hit-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
842
|
+
await copyFile(cachedBin, tmpOut);
|
|
843
|
+
await chmod(tmpOut, 0o755);
|
|
844
|
+
await rename(tmpOut, opts.outPath);
|
|
845
|
+
const now = new Date();
|
|
846
|
+
await utimes(cachedBin, now, now).catch(() => undefined); // LRU bump
|
|
847
|
+
return; // hit: clang skipped entirely
|
|
848
|
+
}
|
|
849
|
+
catch {
|
|
850
|
+
/* miss — build below, then publish */
|
|
851
|
+
}
|
|
852
|
+
// Miss: link the program's own TU against cached per-flavor runtime
|
|
853
|
+
// objects. cflags reproduces exactly the option set every TU sees in the
|
|
854
|
+
// single invocation (the clang driver applies all options to all inputs).
|
|
855
|
+
const cflags = [
|
|
856
|
+
"-std=c11",
|
|
857
|
+
...driver.targetArgs,
|
|
858
|
+
...(opts.sanitize
|
|
859
|
+
? ["-O1", "-fsanitize=address", "-DSCR_RC_AUDIT"]
|
|
860
|
+
: ["-O2"]),
|
|
861
|
+
"-fno-math-errno",
|
|
862
|
+
"-Wno-deprecated-declarations",
|
|
863
|
+
"-I", rtDir,
|
|
864
|
+
...(regex || dynamic ? ["-I", vendorEngineDir()] : []),
|
|
865
|
+
...(zlibObjects.length > 0 ? ["-I", vendorZlibDir()] : []),
|
|
866
|
+
...(curlStubDir !== null ? ["-I", join(vendorCurlDir(), "include")] : []),
|
|
867
|
+
...(tlsArchive !== null ? ["-I", join(vendorTlsDir(), "include")] : []),
|
|
868
|
+
...(dynamic ? ["-DSCR_DYNAMIC"] : []),
|
|
869
|
+
];
|
|
870
|
+
// Collect the runtime sources this build actually compiles (the same
|
|
871
|
+
// conditionals as the command line, by construction).
|
|
872
|
+
const rtInputs = [];
|
|
873
|
+
buildArgs((p) => {
|
|
874
|
+
rtInputs.push(p);
|
|
875
|
+
return p;
|
|
876
|
+
});
|
|
877
|
+
let objects = null;
|
|
878
|
+
try {
|
|
879
|
+
objects = await ensureRuntimeObjects(root, driver.argv, cflags, rtInputs, `obj-v1\0${ccName}\0${cv}\0${fingerprint}\0`);
|
|
880
|
+
}
|
|
881
|
+
catch {
|
|
882
|
+
objects = null; // cache trouble is never a build failure
|
|
883
|
+
}
|
|
884
|
+
await runClang(buildArgs((p) => objects?.get(p) ?? p));
|
|
885
|
+
try {
|
|
886
|
+
await mkdir(binDir, { recursive: true });
|
|
887
|
+
const tmp = join(binDir, `.tmp-${key.slice(0, 8)}-${process.pid}-${Math.random().toString(36).slice(2)}`);
|
|
888
|
+
await copyFile(opts.outPath, tmp);
|
|
889
|
+
await chmod(tmp, 0o755);
|
|
890
|
+
await rename(tmp, cachedBin);
|
|
891
|
+
await pruneCache(root);
|
|
892
|
+
}
|
|
893
|
+
catch {
|
|
894
|
+
/* publishing is best-effort */
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
//# sourceMappingURL=cc.js.map
|