@scriptc/compiler 0.0.33 → 0.0.35
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/dist/backend/cc-cache.test.js +1091 -7
- package/dist/backend/cc-cache.test.js.map +1 -1
- package/dist/backend/cc.d.ts +109 -18
- package/dist/backend/cc.js +1057 -289
- package/dist/backend/cc.js.map +1 -1
- package/dist/backend/emission/emit-exprs.js +26 -1
- package/dist/backend/emission/emit-exprs.js.map +1 -1
- package/dist/backend/emission/emit-shapes.js +37 -4
- package/dist/backend/emission/emit-shapes.js.map +1 -1
- package/dist/backend/emission/emitter.d.ts +11 -2
- package/dist/backend/emission/emitter.js +11 -5
- package/dist/backend/emission/emitter.js.map +1 -1
- package/dist/backend/library-identity.d.ts +22 -0
- package/dist/backend/library-identity.js +103 -0
- package/dist/backend/library-identity.js.map +1 -0
- package/dist/backend/llvm/emitter.d.ts +3 -0
- package/dist/backend/llvm/emitter.js +35 -5
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/shapes.d.ts +1 -0
- package/dist/backend/llvm/shapes.js +25 -1
- package/dist/backend/llvm/shapes.js.map +1 -1
- package/dist/backend/llvm/split.d.ts +43 -0
- package/dist/backend/llvm/split.js +227 -0
- package/dist/backend/llvm/split.js.map +1 -0
- package/dist/backend/llvm/split.test.d.ts +1 -0
- package/dist/backend/llvm/split.test.js +102 -0
- package/dist/backend/llvm/split.test.js.map +1 -0
- package/dist/backend/mangle.d.ts +1 -0
- package/dist/backend/mangle.js +3 -0
- package/dist/backend/mangle.js.map +1 -1
- package/dist/backend/object-localize.d.ts +5 -5
- package/dist/backend/object-localize.js +19 -16
- package/dist/backend/object-localize.js.map +1 -1
- package/dist/backend/object-localize.test.js +57 -13
- package/dist/backend/object-localize.test.js.map +1 -1
- package/dist/executable/early-cache.d.ts +86 -0
- package/dist/executable/early-cache.js +460 -0
- package/dist/executable/early-cache.js.map +1 -0
- package/dist/executable/early-cache.test.d.ts +1 -0
- package/dist/executable/early-cache.test.js +300 -0
- package/dist/executable/early-cache.test.js.map +1 -0
- package/dist/ffi/profile.d.ts +7 -2
- package/dist/ffi/profile.js +1 -0
- package/dist/ffi/profile.js.map +1 -1
- package/dist/frontend/input-tracker.d.ts +82 -0
- package/dist/frontend/input-tracker.js +380 -0
- package/dist/frontend/input-tracker.js.map +1 -0
- package/dist/frontend/input-tracker.test.d.ts +1 -0
- package/dist/frontend/input-tracker.test.js +137 -0
- package/dist/frontend/input-tracker.test.js.map +1 -0
- package/dist/frontend/lowering/lower-builtins.js +4 -10
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.js +169 -144
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.js +3 -0
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.js +66 -32
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.js +89 -11
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-inspect.js +45 -26
- package/dist/frontend/lowering/lower-inspect.js.map +1 -1
- package/dist/frontend/lowering/lower-island.js +3 -3
- package/dist/frontend/lowering/lower-island.js.map +1 -1
- package/dist/frontend/lowering/lower-mixins.js +1 -1
- package/dist/frontend/lowering/lower-mixins.js.map +1 -1
- package/dist/frontend/lowering/lower-modules.js +1 -1
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +49 -4
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +73 -29
- package/dist/frontend/lowering/lowerer.js +588 -116
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/surfaces.js +1 -1
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/npm-static-rewrite.js +25 -26
- package/dist/frontend/npm-static-rewrite.js.map +1 -1
- package/dist/frontend/npm-static.js +29 -30
- package/dist/frontend/npm-static.js.map +1 -1
- package/dist/frontend/npm-static.test.d.ts +1 -0
- package/dist/frontend/npm-static.test.js +15 -0
- package/dist/frontend/npm-static.test.js.map +1 -0
- package/dist/frontend/npm.js +5 -17
- package/dist/frontend/npm.js.map +1 -1
- package/dist/frontend/program.js +44 -19
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/resolve.js +8 -29
- package/dist/frontend/resolve.js.map +1 -1
- package/dist/frontend/ts7/checker.d.ts +40 -2
- package/dist/frontend/ts7/checker.js +194 -32
- package/dist/frontend/ts7/checker.js.map +1 -1
- package/dist/frontend/ts7/program.js +17 -18
- package/dist/frontend/ts7/program.js.map +1 -1
- package/dist/frontend/types.d.ts +33 -0
- package/dist/frontend/types.js +134 -45
- package/dist/frontend/types.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +458 -141
- package/dist/index.js.map +1 -1
- package/dist/ir/nodes.d.ts +27 -4
- package/dist/ir/nodes.js +28 -0
- package/dist/ir/nodes.js.map +1 -1
- package/dist/ir/serialize.d.ts +1 -1
- package/dist/ir/serialize.js +1 -1
- package/dist/ir/validate.js +37 -1
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/early-cache.d.ts +65 -0
- package/dist/library/early-cache.js +361 -0
- package/dist/library/early-cache.js.map +1 -0
- package/dist/library/early-cache.test.d.ts +1 -0
- package/dist/library/early-cache.test.js +528 -0
- package/dist/library/early-cache.test.js.map +1 -0
- package/dist/library/implementation-identity.d.ts +23 -0
- package/dist/library/implementation-identity.js +84 -0
- package/dist/library/implementation-identity.js.map +1 -0
- package/dist/library/int-infer.js +14 -0
- package/dist/library/int-infer.js.map +1 -1
- package/dist/library/int-infer.test.js +21 -4
- package/dist/library/int-infer.test.js.map +1 -1
- package/dist/library/semantic-source.d.ts +13 -0
- package/dist/library/semantic-source.js +257 -0
- package/dist/library/semantic-source.js.map +1 -0
- package/dist/library/semantic-source.test.d.ts +1 -0
- package/dist/library/semantic-source.test.js +113 -0
- package/dist/library/semantic-source.test.js.map +1 -0
- package/dist/library/sidecar.d.ts +6 -0
- package/dist/library/sidecar.js +11 -0
- package/dist/library/sidecar.js.map +1 -1
- package/dist/startup-cache.d.ts +6 -0
- package/dist/startup-cache.js +6 -0
- package/dist/startup-cache.js.map +1 -0
- package/package.json +3 -2
- package/surface-manifest.json +1 -1
|
@@ -5,7 +5,8 @@ import { chmod, cp, mkdir, mkdtemp, readdir, readFile, rm, stat, symlink, utimes
|
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { delimiter, join } from "node:path";
|
|
7
7
|
import { afterAll, afterEach, expect, test as vitestTest } from "vitest";
|
|
8
|
-
import { cacheTargetIdentity, ccVersionOnce, compileC, compileLibArchive, implicitDependencyProbeIncludes, parseLinkTraceFiles, resolveBuildCacheRoot, runtimeFingerprint, runtimeSrcDir, stageRuntimeObjects, toolchainEnvironmentCachePolicy, toolchainEnvironmentFingerprint, vendorCacheBuildIdentity, vendorCacheTargetFlavor, } from "./cc.js";
|
|
8
|
+
import { cacheTargetIdentity, ccVersionOnce, compileC, compileLibArchive, executableNativeEnvironmentFingerprint, implicitDependencyProbeIncludes, parseLinkTraceFiles, resolveBuildCacheRoot, runtimeFingerprint, runtimeSrcDir, stageRuntimeObjects, supportedNativeCacheWarmProfiles, toolchainEnvironmentCachePolicy, toolchainEnvironmentFingerprint, vendorCacheBuildIdentity, vendorCacheTargetFlavor, warmNativeCaches, } from "./cc.js";
|
|
9
|
+
import { splitLlvmProgram } from "./llvm/split.js";
|
|
9
10
|
const scratch = [];
|
|
10
11
|
const TEST_CACHE_IDENTITY = "cc-cache-tests-v1";
|
|
11
12
|
const originalStableToolchain = process.env["SCRIPTC_TEST_STABLE_TOOLCHAIN"];
|
|
@@ -41,10 +42,20 @@ const test = Object.assign((name, ...args) => Reflect.apply(cacheTestSelected(na
|
|
|
41
42
|
const trustInstrumentedCompilerWrapper = () => {
|
|
42
43
|
process.env["SCRIPTC_TEST_TRUST_COMPILER_WRAPPER"] = "1";
|
|
43
44
|
};
|
|
44
|
-
const
|
|
45
|
+
const executableOnPath = (name) => (process.env["PATH"] ?? "")
|
|
45
46
|
.split(delimiter)
|
|
46
|
-
.map((entry) => join(entry === "" ? process.cwd() : entry,
|
|
47
|
+
.map((entry) => join(entry === "" ? process.cwd() : entry, name))
|
|
47
48
|
.find((candidate) => existsSync(candidate));
|
|
49
|
+
const zigExecutable = executableOnPath("zig");
|
|
50
|
+
const clangExecutable = executableOnPath("clang");
|
|
51
|
+
const arExecutable = executableOnPath("ar");
|
|
52
|
+
const ldExecutable = executableOnPath("ld");
|
|
53
|
+
const objcopyExecutable = executableOnPath("objcopy");
|
|
54
|
+
const nativeShardMergeAvailable = process.platform === "darwin"
|
|
55
|
+
? ldExecutable !== undefined
|
|
56
|
+
: process.platform === "linux"
|
|
57
|
+
? ldExecutable !== undefined && objcopyExecutable !== undefined
|
|
58
|
+
: process.platform === "win32" && process.arch === "x64";
|
|
48
59
|
const completeArtifacts = async (root, kind) => (await readdir(join(root, kind))).filter((name) => !name.endsWith(".sha256"));
|
|
49
60
|
const cacheTreeBytes = async (root) => {
|
|
50
61
|
let total = 0;
|
|
@@ -85,6 +96,36 @@ test("the production cache root follows overrides, platform defaults, and the ha
|
|
|
85
96
|
expect(resolveBuildCacheRoot({}, "darwin", "/Users/tester")).toBe("/Users/tester/Library/Caches/scriptc/build");
|
|
86
97
|
expect(resolveBuildCacheRoot({ LOCALAPPDATA: "/Users/tester/AppData/Local" }, "win32", "/Users/tester")).toBe("/Users/tester/AppData/Local/scriptc/cache/build");
|
|
87
98
|
});
|
|
99
|
+
test.skipIf(process.platform === "win32")("the early executable identity follows a compiler selected behind a stable driver", async () => {
|
|
100
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-effective-compiler-"));
|
|
101
|
+
scratch.push(dir);
|
|
102
|
+
const binDir = join(dir, "bin");
|
|
103
|
+
const selector = join(dir, "selected");
|
|
104
|
+
const firstCompiler = join(dir, "clang-first");
|
|
105
|
+
const secondCompiler = join(dir, "clang-second");
|
|
106
|
+
await mkdir(binDir);
|
|
107
|
+
await Promise.all([
|
|
108
|
+
writeFile(firstCompiler, "#!/bin/sh\nexit 0\n"),
|
|
109
|
+
writeFile(secondCompiler, "#!/bin/sh\nexit 0\n"),
|
|
110
|
+
writeFile(join(binDir, "clang"), "#!/bin/sh\nselected=$(cat \"$SCRIPTC_TEST_EFFECTIVE_COMPILER\")\nprintf '\"%s\" \"-cc1\"\\n' \"$selected\" >&2\n"),
|
|
111
|
+
writeFile(selector, `${firstCompiler}\n`),
|
|
112
|
+
]);
|
|
113
|
+
await Promise.all([
|
|
114
|
+
chmod(firstCompiler, 0o755),
|
|
115
|
+
chmod(secondCompiler, 0o755),
|
|
116
|
+
chmod(join(binDir, "clang"), 0o755),
|
|
117
|
+
]);
|
|
118
|
+
const env = {
|
|
119
|
+
...process.env,
|
|
120
|
+
PATH: `${binDir}${delimiter}${process.env["PATH"] ?? ""}`,
|
|
121
|
+
SCRIPTC_TEST_EFFECTIVE_COMPILER: selector,
|
|
122
|
+
};
|
|
123
|
+
const first = await executableNativeEnvironmentFingerprint(env);
|
|
124
|
+
expect(await executableNativeEnvironmentFingerprint(env)).toBe(first);
|
|
125
|
+
await writeFile(selector, `${secondCompiler}\n`);
|
|
126
|
+
const second = await executableNativeEnvironmentFingerprint(env);
|
|
127
|
+
expect(second).not.toBe(first);
|
|
128
|
+
});
|
|
88
129
|
test("native cache identities separate host architectures while cross targets remain explicit", () => {
|
|
89
130
|
expect(cacheTargetIdentity({ target: null }, "darwin", "arm64")).toBe("native:darwin:arm64");
|
|
90
131
|
expect(cacheTargetIdentity({ target: null }, "darwin", "x64")).toBe("native:darwin:x64");
|
|
@@ -142,9 +183,11 @@ test("the toolchain environment joins cache identities", () => {
|
|
|
142
183
|
completeArtifacts: false,
|
|
143
184
|
runtimeObjects: false,
|
|
144
185
|
});
|
|
186
|
+
// scriptc invokes its compiler and archiver directly; conventional build-
|
|
187
|
+
// system variables do not alter those commands.
|
|
145
188
|
expect(toolchainEnvironmentCachePolicy({ CFLAGS: "-I/headers" })).toEqual({
|
|
146
|
-
completeArtifacts:
|
|
147
|
-
runtimeObjects:
|
|
189
|
+
completeArtifacts: true,
|
|
190
|
+
runtimeObjects: true,
|
|
148
191
|
});
|
|
149
192
|
expect(toolchainEnvironmentCachePolicy({ ZIG_LIB_DIR: "/zig/lib" })).toEqual({
|
|
150
193
|
completeArtifacts: false,
|
|
@@ -154,8 +197,9 @@ test("the toolchain environment joins cache identities", () => {
|
|
|
154
197
|
completeArtifacts: false,
|
|
155
198
|
runtimeObjects: false,
|
|
156
199
|
});
|
|
157
|
-
expect(vendorCacheBuildIdentity(base, "compiler-one")).not.toBe(vendorCacheBuildIdentity(base, "compiler-two"));
|
|
158
|
-
expect(vendorCacheBuildIdentity(base, "compiler-one")).not.toBe(vendorCacheBuildIdentity(toolchainEnvironmentFingerprint({ MACOSX_DEPLOYMENT_TARGET: "11.0" }), "compiler-one"));
|
|
200
|
+
expect(vendorCacheBuildIdentity(base, "compiler-one", "sources-one")).not.toBe(vendorCacheBuildIdentity(base, "compiler-two", "sources-one"));
|
|
201
|
+
expect(vendorCacheBuildIdentity(base, "compiler-one", "sources-one")).not.toBe(vendorCacheBuildIdentity(toolchainEnvironmentFingerprint({ MACOSX_DEPLOYMENT_TARGET: "11.0" }), "compiler-one", "sources-one"));
|
|
202
|
+
expect(vendorCacheBuildIdentity(base, "compiler-one", "sources-one")).not.toBe(vendorCacheBuildIdentity(base, "compiler-one", "sources-two"));
|
|
159
203
|
});
|
|
160
204
|
test.skipIf(process.platform === "win32")("compiler version probes use and remove a private working directory", async () => {
|
|
161
205
|
const dir = await mkdtemp(join(tmpdir(), "scriptc-version-probe-"));
|
|
@@ -1682,6 +1726,339 @@ test("the hard disable bypasses vendor prerequisite caches", async () => {
|
|
|
1682
1726
|
process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"] = oldVendorCacheDir;
|
|
1683
1727
|
}
|
|
1684
1728
|
});
|
|
1729
|
+
test.skipIf(process.platform === "win32")("shared vendor prerequisites re-key when vendored source bytes change", async () => {
|
|
1730
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-vendor-source-identity-"));
|
|
1731
|
+
scratch.push(dir);
|
|
1732
|
+
const cacheRoot = join(dir, "cache");
|
|
1733
|
+
const fakeRuntimeRoot = join(dir, "runtime");
|
|
1734
|
+
const fakeRuntime = join(fakeRuntimeRoot, "src");
|
|
1735
|
+
const originalRuntime = runtimeSrcDir();
|
|
1736
|
+
const cPath = join(dir, "program.c");
|
|
1737
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
1738
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
1739
|
+
const oldRuntimeDir = process.env["SCRIPTC_TEST_RUNTIME_SRC_DIR"];
|
|
1740
|
+
const oldVendorCacheDir = process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"];
|
|
1741
|
+
try {
|
|
1742
|
+
await Promise.all([
|
|
1743
|
+
cp(originalRuntime, fakeRuntime, { recursive: true }),
|
|
1744
|
+
mkdir(join(fakeRuntimeRoot, "vendor"), { recursive: true }).then(() => Promise.all(["quickjs-ng", "ryu"].map((name) => cp(join(originalRuntime, "..", "vendor", name), join(fakeRuntimeRoot, "vendor", name), { recursive: true })))),
|
|
1745
|
+
]);
|
|
1746
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
1747
|
+
process.env["SCRIPTC_TEST_RUNTIME_SRC_DIR"] = fakeRuntime;
|
|
1748
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1749
|
+
delete process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"];
|
|
1750
|
+
await writeFile(cPath, "int main(void) { return 0; }\n");
|
|
1751
|
+
await compileC({
|
|
1752
|
+
cPath,
|
|
1753
|
+
outPath: join(dir, "first"),
|
|
1754
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
1755
|
+
regex: true,
|
|
1756
|
+
});
|
|
1757
|
+
const vendorRoot = join(cacheRoot, "vendor");
|
|
1758
|
+
expect((await readdir(vendorRoot)).filter((name) => name.includes("-lre-"))).toHaveLength(1);
|
|
1759
|
+
const libregexp = join(fakeRuntimeRoot, "vendor", "quickjs-ng", "libregexp.c");
|
|
1760
|
+
await writeFile(libregexp, `${await readFile(libregexp, "utf8")}\n/* cache identity probe */\n`);
|
|
1761
|
+
await writeFile(cPath, "int main(void) { return 0; } /* second */\n");
|
|
1762
|
+
await compileC({
|
|
1763
|
+
cPath,
|
|
1764
|
+
outPath: join(dir, "second"),
|
|
1765
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
1766
|
+
regex: true,
|
|
1767
|
+
});
|
|
1768
|
+
expect((await readdir(vendorRoot)).filter((name) => name.includes("-lre-"))).toHaveLength(2);
|
|
1769
|
+
}
|
|
1770
|
+
finally {
|
|
1771
|
+
if (oldCacheDir === undefined)
|
|
1772
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
1773
|
+
else
|
|
1774
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
1775
|
+
if (oldNoCache === undefined)
|
|
1776
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1777
|
+
else
|
|
1778
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
1779
|
+
if (oldRuntimeDir === undefined)
|
|
1780
|
+
delete process.env["SCRIPTC_TEST_RUNTIME_SRC_DIR"];
|
|
1781
|
+
else
|
|
1782
|
+
process.env["SCRIPTC_TEST_RUNTIME_SRC_DIR"] = oldRuntimeDir;
|
|
1783
|
+
if (oldVendorCacheDir === undefined)
|
|
1784
|
+
delete process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"];
|
|
1785
|
+
else
|
|
1786
|
+
process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"] = oldVendorCacheDir;
|
|
1787
|
+
}
|
|
1788
|
+
}, 120_000);
|
|
1789
|
+
test.skipIf(process.platform === "win32")("uncached arbitrary C keeps vendor prerequisites outside a read-only runtime package", async () => {
|
|
1790
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-readonly-runtime-"));
|
|
1791
|
+
scratch.push(dir);
|
|
1792
|
+
const fakeRuntimeRoot = join(dir, "runtime");
|
|
1793
|
+
const fakeRuntime = join(fakeRuntimeRoot, "src");
|
|
1794
|
+
const fakeVendor = join(fakeRuntimeRoot, "vendor");
|
|
1795
|
+
const originalRuntime = runtimeSrcDir();
|
|
1796
|
+
const cPath = join(dir, "program.c");
|
|
1797
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
1798
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
1799
|
+
const oldRuntimeDir = process.env["SCRIPTC_TEST_RUNTIME_SRC_DIR"];
|
|
1800
|
+
const oldVendorCacheDir = process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"];
|
|
1801
|
+
try {
|
|
1802
|
+
await Promise.all([
|
|
1803
|
+
cp(originalRuntime, fakeRuntime, { recursive: true }),
|
|
1804
|
+
mkdir(fakeVendor, { recursive: true }).then(() => Promise.all(["quickjs-ng", "ryu"].map((name) => cp(join(originalRuntime, "..", "vendor", name), join(fakeVendor, name), {
|
|
1805
|
+
recursive: true,
|
|
1806
|
+
})))),
|
|
1807
|
+
]);
|
|
1808
|
+
process.env["SCRIPTC_CACHE_DIR"] = join(dir, "cache");
|
|
1809
|
+
process.env["SCRIPTC_TEST_RUNTIME_SRC_DIR"] = fakeRuntime;
|
|
1810
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1811
|
+
delete process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"];
|
|
1812
|
+
await writeFile(cPath, "int main(void) { return 0; }\n");
|
|
1813
|
+
await chmod(fakeVendor, 0o555);
|
|
1814
|
+
const outPath = join(dir, "program");
|
|
1815
|
+
await compileC({ cPath, outPath, regex: true });
|
|
1816
|
+
expect((await stat(outPath)).isFile()).toBe(true);
|
|
1817
|
+
await expect(stat(join(fakeVendor, ".cache"))).rejects.toMatchObject({ code: "ENOENT" });
|
|
1818
|
+
}
|
|
1819
|
+
finally {
|
|
1820
|
+
await chmod(fakeVendor, 0o755).catch(() => undefined);
|
|
1821
|
+
if (oldCacheDir === undefined)
|
|
1822
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
1823
|
+
else
|
|
1824
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
1825
|
+
if (oldNoCache === undefined)
|
|
1826
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1827
|
+
else
|
|
1828
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
1829
|
+
if (oldRuntimeDir === undefined)
|
|
1830
|
+
delete process.env["SCRIPTC_TEST_RUNTIME_SRC_DIR"];
|
|
1831
|
+
else
|
|
1832
|
+
process.env["SCRIPTC_TEST_RUNTIME_SRC_DIR"] = oldRuntimeDir;
|
|
1833
|
+
if (oldVendorCacheDir === undefined)
|
|
1834
|
+
delete process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"];
|
|
1835
|
+
else
|
|
1836
|
+
process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"] = oldVendorCacheDir;
|
|
1837
|
+
}
|
|
1838
|
+
}, 120_000);
|
|
1839
|
+
test("native cache warming seeds exact runtime and vendor families without complete binaries", async () => {
|
|
1840
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-cache-warm-"));
|
|
1841
|
+
scratch.push(dir);
|
|
1842
|
+
const cacheRoot = join(dir, "cache");
|
|
1843
|
+
const vendorCacheRoot = join(cacheRoot, "vendor");
|
|
1844
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
1845
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
1846
|
+
const oldVendorCacheDir = process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"];
|
|
1847
|
+
try {
|
|
1848
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
1849
|
+
delete process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"];
|
|
1850
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1851
|
+
const warmed = await warmNativeCaches({ profiles: ["runtime", "tls", "dynamic"] });
|
|
1852
|
+
expect(warmed.cacheRoot).toBe(cacheRoot);
|
|
1853
|
+
expect(warmed.profiles.map(({ profile }) => profile)).toEqual(["runtime", "tls", "dynamic"]);
|
|
1854
|
+
expect(warmed.profiles.every(({ elapsedMs }) => elapsedMs >= 0)).toBe(true);
|
|
1855
|
+
expect((await readdir(join(cacheRoot, "obj"), { withFileTypes: true })).filter((e) => e.isDirectory())).toHaveLength(3);
|
|
1856
|
+
expect(await readdir(join(cacheRoot, "bin")).catch(() => [])).toEqual([]);
|
|
1857
|
+
const vendorEntries = await readdir(vendorCacheRoot);
|
|
1858
|
+
const tlsEntry = vendorEntries.find((name) => name.startsWith("mbedtls-"));
|
|
1859
|
+
const engineEntry = vendorEntries.find((name) => /^3c8f3d689539-plain-/.test(name));
|
|
1860
|
+
expect(tlsEntry).toBeDefined();
|
|
1861
|
+
expect(engineEntry).toBeDefined();
|
|
1862
|
+
expect((await stat(join(vendorCacheRoot, tlsEntry, "libmbedtls.a.sha256"))).isFile()).toBe(true);
|
|
1863
|
+
expect((await stat(join(vendorCacheRoot, engineEntry, "libqjs.a.sha256"))).isFile()).toBe(true);
|
|
1864
|
+
}
|
|
1865
|
+
finally {
|
|
1866
|
+
if (oldCacheDir === undefined)
|
|
1867
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
1868
|
+
else
|
|
1869
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
1870
|
+
if (oldNoCache === undefined)
|
|
1871
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1872
|
+
else
|
|
1873
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
1874
|
+
if (oldVendorCacheDir === undefined)
|
|
1875
|
+
delete process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"];
|
|
1876
|
+
else
|
|
1877
|
+
process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"] = oldVendorCacheDir;
|
|
1878
|
+
}
|
|
1879
|
+
}, 120_000);
|
|
1880
|
+
test("damaged shared vendor archives are rejected and rebuilt before linking", async () => {
|
|
1881
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-vendor-integrity-"));
|
|
1882
|
+
scratch.push(dir);
|
|
1883
|
+
const cacheRoot = join(dir, "cache");
|
|
1884
|
+
const cPath = join(dir, "program.c");
|
|
1885
|
+
const outPath = join(dir, "program");
|
|
1886
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
1887
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
1888
|
+
const oldVendorCacheDir = process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"];
|
|
1889
|
+
const corruption = Buffer.from("scriptc-corrupt-vendor-archive\n");
|
|
1890
|
+
try {
|
|
1891
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
1892
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1893
|
+
delete process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"];
|
|
1894
|
+
await writeFile(cPath, "int main(void) { return 0; }\n");
|
|
1895
|
+
await warmNativeCaches({ profiles: ["dynamic"] });
|
|
1896
|
+
const vendorRoot = join(cacheRoot, "vendor");
|
|
1897
|
+
const engineDir = (await readdir(vendorRoot)).find((name) => /^3c8f3d689539-plain-/.test(name));
|
|
1898
|
+
expect(engineDir).toBeDefined();
|
|
1899
|
+
const engineArchive = join(vendorRoot, engineDir, "libqjs.a");
|
|
1900
|
+
expect((await stat(`${engineArchive}.sha256`)).isFile()).toBe(true);
|
|
1901
|
+
await writeFile(engineArchive, corruption);
|
|
1902
|
+
await compileC({
|
|
1903
|
+
cPath,
|
|
1904
|
+
outPath,
|
|
1905
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
1906
|
+
dynamic: true,
|
|
1907
|
+
});
|
|
1908
|
+
expect((await stat(outPath)).isFile()).toBe(true);
|
|
1909
|
+
const repaired = await readFile(engineArchive);
|
|
1910
|
+
expect(repaired).not.toEqual(corruption);
|
|
1911
|
+
expect((await readFile(`${engineArchive}.sha256`, "utf8")).trim()).toBe(createHash("sha256").update(repaired).digest("hex"));
|
|
1912
|
+
}
|
|
1913
|
+
finally {
|
|
1914
|
+
if (oldCacheDir === undefined)
|
|
1915
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
1916
|
+
else
|
|
1917
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
1918
|
+
if (oldNoCache === undefined)
|
|
1919
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1920
|
+
else
|
|
1921
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
1922
|
+
if (oldVendorCacheDir === undefined)
|
|
1923
|
+
delete process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"];
|
|
1924
|
+
else
|
|
1925
|
+
process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"] = oldVendorCacheDir;
|
|
1926
|
+
}
|
|
1927
|
+
}, 120_000);
|
|
1928
|
+
test("dynamic builds promote staged vendor archives before bounded LRU eviction", async () => {
|
|
1929
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-vendor-lru-"));
|
|
1930
|
+
scratch.push(dir);
|
|
1931
|
+
const cacheRoot = join(dir, "cache");
|
|
1932
|
+
const cPath = join(dir, "program.c");
|
|
1933
|
+
const outPath = join(dir, "program");
|
|
1934
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
1935
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
1936
|
+
const oldMax = process.env["SCRIPTC_CACHE_MAX_MB"];
|
|
1937
|
+
const oldVendorCacheDir = process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"];
|
|
1938
|
+
try {
|
|
1939
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
1940
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1941
|
+
delete process.env["SCRIPTC_CACHE_MAX_MB"];
|
|
1942
|
+
delete process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"];
|
|
1943
|
+
await writeFile(cPath, "int main(void) { return 0; }\n");
|
|
1944
|
+
await warmNativeCaches({ profiles: ["dynamic"] });
|
|
1945
|
+
const vendorRoot = join(cacheRoot, "vendor");
|
|
1946
|
+
const engineDir = (await readdir(vendorRoot)).find((name) => /^3c8f3d689539-plain-/.test(name));
|
|
1947
|
+
expect(engineDir).toBeDefined();
|
|
1948
|
+
const engineArchive = join(vendorRoot, engineDir, "libqjs.a");
|
|
1949
|
+
const initialBytes = await cacheTreeBytes(cacheRoot);
|
|
1950
|
+
const capMb = Math.max(4, Math.ceil(initialBytes * 2 / (1024 * 1024)));
|
|
1951
|
+
const filler = join(cacheRoot, "filler.bin");
|
|
1952
|
+
const staleArchiveTime = new Date("2000-01-01T00:00:00.000Z");
|
|
1953
|
+
const fillerTime = new Date("2001-01-01T00:00:00.000Z");
|
|
1954
|
+
await writeFile(filler, Buffer.alloc(capMb * 1024 * 1024));
|
|
1955
|
+
await utimes(engineArchive, staleArchiveTime, staleArchiveTime);
|
|
1956
|
+
await utimes(filler, fillerTime, fillerTime);
|
|
1957
|
+
process.env["SCRIPTC_CACHE_MAX_MB"] = String(capMb);
|
|
1958
|
+
await compileC({
|
|
1959
|
+
cPath,
|
|
1960
|
+
outPath,
|
|
1961
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
1962
|
+
dynamic: true,
|
|
1963
|
+
});
|
|
1964
|
+
expect((await stat(outPath)).isFile()).toBe(true);
|
|
1965
|
+
expect((await stat(engineArchive)).mtimeMs).toBeGreaterThan(fillerTime.getTime());
|
|
1966
|
+
expect(await cacheTreeBytes(cacheRoot)).toBeLessThanOrEqual(capMb * 1024 * 1024);
|
|
1967
|
+
}
|
|
1968
|
+
finally {
|
|
1969
|
+
if (oldCacheDir === undefined)
|
|
1970
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
1971
|
+
else
|
|
1972
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
1973
|
+
if (oldNoCache === undefined)
|
|
1974
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1975
|
+
else
|
|
1976
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
1977
|
+
if (oldMax === undefined)
|
|
1978
|
+
delete process.env["SCRIPTC_CACHE_MAX_MB"];
|
|
1979
|
+
else
|
|
1980
|
+
process.env["SCRIPTC_CACHE_MAX_MB"] = oldMax;
|
|
1981
|
+
if (oldVendorCacheDir === undefined)
|
|
1982
|
+
delete process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"];
|
|
1983
|
+
else
|
|
1984
|
+
process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"] = oldVendorCacheDir;
|
|
1985
|
+
}
|
|
1986
|
+
}, 120_000);
|
|
1987
|
+
test("native cache warming follows the hard cache disable", async () => {
|
|
1988
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
1989
|
+
try {
|
|
1990
|
+
process.env["SCRIPTC_NO_CACHE"] = "1";
|
|
1991
|
+
await expect(warmNativeCaches({ profiles: ["runtime"] })).rejects.toThrow("native build cache is disabled");
|
|
1992
|
+
}
|
|
1993
|
+
finally {
|
|
1994
|
+
if (oldNoCache === undefined)
|
|
1995
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1996
|
+
else
|
|
1997
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
1998
|
+
}
|
|
1999
|
+
});
|
|
2000
|
+
test("native cache warming refuses environments that disable persistent objects", async () => {
|
|
2001
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-cache-warm-disabled-"));
|
|
2002
|
+
scratch.push(dir);
|
|
2003
|
+
const cacheRoot = join(dir, "cache");
|
|
2004
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
2005
|
+
const oldCpath = process.env["CPATH"];
|
|
2006
|
+
try {
|
|
2007
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
2008
|
+
process.env["CPATH"] = dir;
|
|
2009
|
+
await expect(warmNativeCaches({ profiles: ["runtime"] })).rejects.toThrow("requires a persistently cacheable compiler environment");
|
|
2010
|
+
expect(await readdir(cacheRoot)).toEqual([]);
|
|
2011
|
+
}
|
|
2012
|
+
finally {
|
|
2013
|
+
if (oldCacheDir === undefined)
|
|
2014
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
2015
|
+
else
|
|
2016
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
2017
|
+
if (oldCpath === undefined)
|
|
2018
|
+
delete process.env["CPATH"];
|
|
2019
|
+
else
|
|
2020
|
+
process.env["CPATH"] = oldCpath;
|
|
2021
|
+
}
|
|
2022
|
+
});
|
|
2023
|
+
test("native cache warm profiles follow target capabilities", () => {
|
|
2024
|
+
expect(supportedNativeCacheWarmProfiles({
|
|
2025
|
+
argv: ["zig", "cc"],
|
|
2026
|
+
target: "wasm32-wasi",
|
|
2027
|
+
zigTarget: "wasm32-wasi",
|
|
2028
|
+
targetArgs: [],
|
|
2029
|
+
linkArgs: [],
|
|
2030
|
+
})).toEqual([]);
|
|
2031
|
+
expect(supportedNativeCacheWarmProfiles({
|
|
2032
|
+
argv: ["zig", "cc"],
|
|
2033
|
+
target: "aarch64-apple-ios",
|
|
2034
|
+
zigTarget: "aarch64-ios.15.0",
|
|
2035
|
+
targetArgs: [],
|
|
2036
|
+
linkArgs: [],
|
|
2037
|
+
})).toEqual([]);
|
|
2038
|
+
});
|
|
2039
|
+
test("parallel native cache warming fails cleanly when the bounded cache cannot retain its profiles", async () => {
|
|
2040
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-cache-warm-cap-"));
|
|
2041
|
+
scratch.push(dir);
|
|
2042
|
+
const cacheRoot = join(dir, "cache");
|
|
2043
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
2044
|
+
const oldMax = process.env["SCRIPTC_CACHE_MAX_MB"];
|
|
2045
|
+
try {
|
|
2046
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
2047
|
+
process.env["SCRIPTC_CACHE_MAX_MB"] = "2.5";
|
|
2048
|
+
await expect(warmNativeCaches()).rejects.toThrow("SCRIPTC_CACHE_MAX_MB is too small to retain the requested native cache warm profiles");
|
|
2049
|
+
expect(await readdir(cacheRoot)).not.toEqual([]);
|
|
2050
|
+
}
|
|
2051
|
+
finally {
|
|
2052
|
+
if (oldCacheDir === undefined)
|
|
2053
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
2054
|
+
else
|
|
2055
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
2056
|
+
if (oldMax === undefined)
|
|
2057
|
+
delete process.env["SCRIPTC_CACHE_MAX_MB"];
|
|
2058
|
+
else
|
|
2059
|
+
process.env["SCRIPTC_CACHE_MAX_MB"] = oldMax;
|
|
2060
|
+
}
|
|
2061
|
+
}, 120_000);
|
|
1685
2062
|
test.skipIf(process.platform !== "darwin")("vendor object caches separate deployment-target environments", async () => {
|
|
1686
2063
|
const dir = await mkdtemp(join(tmpdir(), "scriptc-vendor-environment-"));
|
|
1687
2064
|
scratch.push(dir);
|
|
@@ -2437,6 +2814,44 @@ test("frontend-generated same-output builds no-op only while output and dependen
|
|
|
2437
2814
|
process.umask(oldUmask);
|
|
2438
2815
|
}
|
|
2439
2816
|
});
|
|
2817
|
+
test("artifact-ready callbacks expose native dependencies on builds and validated hits", async () => {
|
|
2818
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-artifact-ready-"));
|
|
2819
|
+
scratch.push(dir);
|
|
2820
|
+
const cacheRoot = join(dir, "cache");
|
|
2821
|
+
const cPath = join(dir, "program.c");
|
|
2822
|
+
const outPath = join(dir, "program");
|
|
2823
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
2824
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
2825
|
+
try {
|
|
2826
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
2827
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
2828
|
+
await writeFile(cPath, "int main(void) { return 0; }\n");
|
|
2829
|
+
const observations = [];
|
|
2830
|
+
const build = () => compileC({
|
|
2831
|
+
cPath,
|
|
2832
|
+
outPath,
|
|
2833
|
+
cacheIdentity: "scriptc-generated-v1",
|
|
2834
|
+
onArtifactReady: async ({ dependencies }) => {
|
|
2835
|
+
observations.push(dependencies.map((dependency) => dependency.path));
|
|
2836
|
+
},
|
|
2837
|
+
});
|
|
2838
|
+
await build();
|
|
2839
|
+
await build();
|
|
2840
|
+
expect(observations).toHaveLength(2);
|
|
2841
|
+
expect(observations[0].length).toBeGreaterThan(0);
|
|
2842
|
+
expect(observations[1]).toEqual(observations[0]);
|
|
2843
|
+
}
|
|
2844
|
+
finally {
|
|
2845
|
+
if (oldCacheDir === undefined)
|
|
2846
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
2847
|
+
else
|
|
2848
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
2849
|
+
if (oldNoCache === undefined)
|
|
2850
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
2851
|
+
else
|
|
2852
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
2853
|
+
}
|
|
2854
|
+
});
|
|
2440
2855
|
test.skipIf(process.platform === "win32")("output-local hits follow symlinked header targets", async () => {
|
|
2441
2856
|
const dir = await mkdtemp(join(tmpdir(), "scriptc-local-artifact-symlink-"));
|
|
2442
2857
|
scratch.push(dir);
|
|
@@ -2811,4 +3226,673 @@ test("library archives hit by content, invalidate on edits, and reuse runtime ob
|
|
|
2811
3226
|
process.env["PATH"] = oldPath;
|
|
2812
3227
|
}
|
|
2813
3228
|
});
|
|
3229
|
+
test("library identity edits reuse the cached large program object", async () => {
|
|
3230
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-lib-program-object-"));
|
|
3231
|
+
scratch.push(dir);
|
|
3232
|
+
const cacheRoot = join(dir, "cache");
|
|
3233
|
+
const cPath = join(dir, "program.c");
|
|
3234
|
+
const outPath = join(dir, "program.lib.a");
|
|
3235
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
3236
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
3237
|
+
try {
|
|
3238
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
3239
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
3240
|
+
await mkdir(cacheRoot, { mode: 0o700 });
|
|
3241
|
+
const programSource = "int scriptc_large_program_value(void) { return 7; }\n";
|
|
3242
|
+
await writeFile(cPath, `${programSource}unsigned long long scriptc_build_id(void) { return 1; }\n`);
|
|
3243
|
+
await compileLibArchive({
|
|
3244
|
+
cPath,
|
|
3245
|
+
programSource,
|
|
3246
|
+
identityCSource: "unsigned long long scriptc_build_id(void) { return 1; }\n",
|
|
3247
|
+
outPath,
|
|
3248
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
3249
|
+
});
|
|
3250
|
+
const [objectName] = (await readdir(join(cacheRoot, "program-obj")))
|
|
3251
|
+
.filter((name) => !name.endsWith(".sha256"));
|
|
3252
|
+
expect(objectName).toBeDefined();
|
|
3253
|
+
const objectPath = join(cacheRoot, "program-obj", objectName);
|
|
3254
|
+
const objectDigest = await readFile(`${objectPath}.sha256`, "utf8");
|
|
3255
|
+
const old = new Date("2000-01-01T00:00:00.000Z");
|
|
3256
|
+
await utimes(objectPath, old, old);
|
|
3257
|
+
await writeFile(cPath, `${programSource}unsigned long long scriptc_build_id(void) { return 2; }\n`);
|
|
3258
|
+
await compileLibArchive({
|
|
3259
|
+
cPath,
|
|
3260
|
+
programSource,
|
|
3261
|
+
identityCSource: "unsigned long long scriptc_build_id(void) { return 2; }\n",
|
|
3262
|
+
outPath,
|
|
3263
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
3264
|
+
});
|
|
3265
|
+
expect(await readFile(`${objectPath}.sha256`, "utf8")).toBe(objectDigest);
|
|
3266
|
+
expect((await stat(objectPath)).mtimeMs).toBeGreaterThan(old.getTime());
|
|
3267
|
+
expect((await readdir(join(cacheRoot, "program-obj"))).filter((name) => !name.endsWith(".sha256"))).toEqual([objectName]);
|
|
3268
|
+
const probeSource = join(dir, "probe.c");
|
|
3269
|
+
const probe = join(dir, "probe");
|
|
3270
|
+
await writeFile(probeSource, "#include <stdio.h>\nint scriptc_large_program_value(void);\nunsigned long long scriptc_build_id(void);\nint main(void) { printf(\"%d %llu\\n\", scriptc_large_program_value(), scriptc_build_id()); }\n");
|
|
3271
|
+
execFileSync("clang", [probeSource, outPath, "-lm", "-o", probe]);
|
|
3272
|
+
expect(execFileSync(probe, { encoding: "utf8" })).toBe("7 2\n");
|
|
3273
|
+
}
|
|
3274
|
+
finally {
|
|
3275
|
+
if (oldCacheDir === undefined)
|
|
3276
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
3277
|
+
else
|
|
3278
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
3279
|
+
if (oldNoCache === undefined)
|
|
3280
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
3281
|
+
else
|
|
3282
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
3283
|
+
}
|
|
3284
|
+
});
|
|
3285
|
+
test.skipIf(!nativeShardMergeAvailable)("LLVM library shards reuse unaffected program objects after a localized edit", async () => {
|
|
3286
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-lib-program-shards-"));
|
|
3287
|
+
scratch.push(dir);
|
|
3288
|
+
const cacheRoot = join(dir, "cache");
|
|
3289
|
+
const cPath = join(dir, "program.ll");
|
|
3290
|
+
const outPath = join(dir, "program.lib.a");
|
|
3291
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
3292
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
3293
|
+
const source = (value) => `%Pair = type { i64, i64 }
|
|
3294
|
+
@private_value = internal global i64 ${value}
|
|
3295
|
+
|
|
3296
|
+
define internal i64 @left() {
|
|
3297
|
+
entry:
|
|
3298
|
+
%v = load i64, ptr @private_value
|
|
3299
|
+
ret i64 %v
|
|
3300
|
+
}
|
|
3301
|
+
|
|
3302
|
+
define internal i64 @right() {
|
|
3303
|
+
entry:
|
|
3304
|
+
%v = call i64 @left()
|
|
3305
|
+
ret i64 %v
|
|
3306
|
+
}
|
|
3307
|
+
|
|
3308
|
+
define i64 @scriptc_public_value() {
|
|
3309
|
+
entry:
|
|
3310
|
+
%v = call i64 @right()
|
|
3311
|
+
ret i64 %v
|
|
3312
|
+
}
|
|
3313
|
+
`;
|
|
3314
|
+
try {
|
|
3315
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
3316
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
3317
|
+
await mkdir(cacheRoot, { mode: 0o700 });
|
|
3318
|
+
const build = async (value) => {
|
|
3319
|
+
const programSource = source(value);
|
|
3320
|
+
const split = splitLlvmProgram(programSource, { minimumBytes: 0, targetBytes: 64 * 1024 });
|
|
3321
|
+
expect(split?.shards).toHaveLength(3);
|
|
3322
|
+
await writeFile(cPath, programSource);
|
|
3323
|
+
await compileLibArchive({
|
|
3324
|
+
cPath,
|
|
3325
|
+
programSource,
|
|
3326
|
+
programShards: split.shards,
|
|
3327
|
+
programPublicSymbols: split.publicSymbols,
|
|
3328
|
+
outPath,
|
|
3329
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
3330
|
+
optimization: "dev",
|
|
3331
|
+
});
|
|
3332
|
+
};
|
|
3333
|
+
await build(7);
|
|
3334
|
+
const initial = (await readdir(join(cacheRoot, "program-shard")))
|
|
3335
|
+
.filter((name) => !name.endsWith(".sha256"));
|
|
3336
|
+
expect(initial).toHaveLength(3);
|
|
3337
|
+
await build(9);
|
|
3338
|
+
const after = (await readdir(join(cacheRoot, "program-shard")))
|
|
3339
|
+
.filter((name) => !name.endsWith(".sha256"));
|
|
3340
|
+
expect(after).toHaveLength(4);
|
|
3341
|
+
expect(initial.filter((name) => after.includes(name))).toHaveLength(3);
|
|
3342
|
+
const old = new Date("2000-01-01T00:00:00.000Z");
|
|
3343
|
+
await Promise.all(after.map((name) => utimes(join(cacheRoot, "program-shard", name), old, old)));
|
|
3344
|
+
await build(9);
|
|
3345
|
+
for (const name of after) {
|
|
3346
|
+
// The completed archive hit returns before shard lookup/merge; exact
|
|
3347
|
+
// repeats do not even touch the per-bucket object cache.
|
|
3348
|
+
expect((await stat(join(cacheRoot, "program-shard", name))).mtimeMs).toBe(old.getTime());
|
|
3349
|
+
}
|
|
3350
|
+
const probeSource = join(dir, "probe.c");
|
|
3351
|
+
const probe = join(dir, "probe");
|
|
3352
|
+
await writeFile(probeSource, "#include <stdio.h>\nlong long scriptc_public_value(void);\nint main(void) { printf(\"%lld\\n\", scriptc_public_value()); }\n");
|
|
3353
|
+
execFileSync("clang", [probeSource, outPath, "-lm", "-o", probe]);
|
|
3354
|
+
expect(execFileSync(probe, { encoding: "utf8" })).toBe("9\n");
|
|
3355
|
+
}
|
|
3356
|
+
finally {
|
|
3357
|
+
if (oldCacheDir === undefined)
|
|
3358
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
3359
|
+
else
|
|
3360
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
3361
|
+
if (oldNoCache === undefined)
|
|
3362
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
3363
|
+
else
|
|
3364
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
3365
|
+
}
|
|
3366
|
+
});
|
|
3367
|
+
test.skipIf(!nativeShardMergeAvailable)("LLVM executable dev shards reuse unaffected program objects after a localized edit", async () => {
|
|
3368
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-exe-program-shards-"));
|
|
3369
|
+
scratch.push(dir);
|
|
3370
|
+
const cacheRoot = join(dir, "cache");
|
|
3371
|
+
const cPath = join(dir, "program.ll");
|
|
3372
|
+
const outPath = join(dir, "program");
|
|
3373
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
3374
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
3375
|
+
const source = (value) => `@private_value = internal global i64 ${value}
|
|
3376
|
+
|
|
3377
|
+
define internal i64 @left() {
|
|
3378
|
+
entry:
|
|
3379
|
+
%v = load i64, ptr @private_value
|
|
3380
|
+
ret i64 %v
|
|
3381
|
+
}
|
|
3382
|
+
|
|
3383
|
+
define internal i64 @right() {
|
|
3384
|
+
entry:
|
|
3385
|
+
%v = call i64 @left()
|
|
3386
|
+
ret i64 %v
|
|
3387
|
+
}
|
|
3388
|
+
|
|
3389
|
+
define i32 @main() {
|
|
3390
|
+
entry:
|
|
3391
|
+
%v = call i64 @right()
|
|
3392
|
+
%exit = trunc i64 %v to i32
|
|
3393
|
+
ret i32 %exit
|
|
3394
|
+
}
|
|
3395
|
+
`;
|
|
3396
|
+
try {
|
|
3397
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
3398
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
3399
|
+
await mkdir(cacheRoot, { mode: 0o700 });
|
|
3400
|
+
const build = async (value) => {
|
|
3401
|
+
const programSource = source(value);
|
|
3402
|
+
const split = splitLlvmProgram(programSource, { minimumBytes: 0, targetBytes: 64 * 1024 });
|
|
3403
|
+
expect(split?.shards).toHaveLength(3);
|
|
3404
|
+
await writeFile(cPath, programSource);
|
|
3405
|
+
await compileC({
|
|
3406
|
+
cPath,
|
|
3407
|
+
programShards: split.shards,
|
|
3408
|
+
programPublicSymbols: split.publicSymbols,
|
|
3409
|
+
outPath,
|
|
3410
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
3411
|
+
optimization: "dev",
|
|
3412
|
+
});
|
|
3413
|
+
};
|
|
3414
|
+
await build(7);
|
|
3415
|
+
const initial = (await readdir(join(cacheRoot, "program-shard")))
|
|
3416
|
+
.filter((name) => !name.endsWith(".sha256"));
|
|
3417
|
+
expect(initial).toHaveLength(3);
|
|
3418
|
+
expect(spawnSync(outPath).status).toBe(7);
|
|
3419
|
+
await build(9);
|
|
3420
|
+
const after = (await readdir(join(cacheRoot, "program-shard")))
|
|
3421
|
+
.filter((name) => !name.endsWith(".sha256"));
|
|
3422
|
+
expect(after).toHaveLength(4);
|
|
3423
|
+
expect(initial.filter((name) => after.includes(name))).toHaveLength(3);
|
|
3424
|
+
expect(spawnSync(outPath).status).toBe(9);
|
|
3425
|
+
const old = new Date("2000-01-01T00:00:00.000Z");
|
|
3426
|
+
await Promise.all(after.map((name) => utimes(join(cacheRoot, "program-shard", name), old, old)));
|
|
3427
|
+
await build(9);
|
|
3428
|
+
for (const name of after) {
|
|
3429
|
+
// The completed executable hit returns before shard lookup/merge.
|
|
3430
|
+
expect((await stat(join(cacheRoot, "program-shard", name))).mtimeMs).toBe(old.getTime());
|
|
3431
|
+
}
|
|
3432
|
+
}
|
|
3433
|
+
finally {
|
|
3434
|
+
if (oldCacheDir === undefined)
|
|
3435
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
3436
|
+
else
|
|
3437
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
3438
|
+
if (oldNoCache === undefined)
|
|
3439
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
3440
|
+
else
|
|
3441
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
3442
|
+
}
|
|
3443
|
+
});
|
|
3444
|
+
test.skipIf(process.platform === "win32" || clangExecutable === undefined ||
|
|
3445
|
+
ldExecutable === undefined || (process.platform === "linux" && objcopyExecutable === undefined))("LLVM executable shards fall back without publishing a shard-derived binary", async () => {
|
|
3446
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-exe-program-shard-fallback-"));
|
|
3447
|
+
scratch.push(dir);
|
|
3448
|
+
const binDir = join(dir, "bin");
|
|
3449
|
+
const cacheRoot = join(dir, "cache");
|
|
3450
|
+
const cPath = join(dir, "program.ll");
|
|
3451
|
+
const outPath = join(dir, "program");
|
|
3452
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
3453
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
3454
|
+
const oldPath = process.env["PATH"];
|
|
3455
|
+
const oldRealLd = process.env["SCRIPTC_TEST_REAL_LD"];
|
|
3456
|
+
const programSource = [
|
|
3457
|
+
"define i32 @main() {",
|
|
3458
|
+
"entry:",
|
|
3459
|
+
" ret i32 7",
|
|
3460
|
+
"}",
|
|
3461
|
+
"",
|
|
3462
|
+
"define internal i64 @other_value() {",
|
|
3463
|
+
"entry:",
|
|
3464
|
+
" ret i64 9",
|
|
3465
|
+
"}",
|
|
3466
|
+
"",
|
|
3467
|
+
].join("\n");
|
|
3468
|
+
try {
|
|
3469
|
+
await Promise.all([mkdir(binDir), mkdir(cacheRoot, { mode: 0o700 })]);
|
|
3470
|
+
await Promise.all([
|
|
3471
|
+
symlink(clangExecutable, join(binDir, "clang")),
|
|
3472
|
+
...(process.platform === "linux"
|
|
3473
|
+
? [symlink(objcopyExecutable, join(binDir, "objcopy"))]
|
|
3474
|
+
: []),
|
|
3475
|
+
writeFile(join(binDir, "ld"), "#!/bin/sh\nfor arg in \"$@\"; do if [ \"$arg\" = -r ]; then exit 1; fi; done\nexec \"$SCRIPTC_TEST_REAL_LD\" \"$@\"\n"),
|
|
3476
|
+
writeFile(cPath, programSource),
|
|
3477
|
+
]);
|
|
3478
|
+
await chmod(join(binDir, "ld"), 0o755);
|
|
3479
|
+
process.env["PATH"] = binDir;
|
|
3480
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
3481
|
+
process.env["SCRIPTC_TEST_REAL_LD"] = ldExecutable;
|
|
3482
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
3483
|
+
await compileC({
|
|
3484
|
+
cPath,
|
|
3485
|
+
programShards: [
|
|
3486
|
+
{
|
|
3487
|
+
name: "program-f000.ll",
|
|
3488
|
+
source: "define i32 @main() {\nentry:\n ret i32 7\n}\ndeclare hidden i64 @other_value()\n",
|
|
3489
|
+
},
|
|
3490
|
+
{
|
|
3491
|
+
name: "program-f001.ll",
|
|
3492
|
+
source: "declare i32 @main()\ndefine hidden i64 @other_value() {\nentry:\n ret i64 9\n}\n",
|
|
3493
|
+
},
|
|
3494
|
+
],
|
|
3495
|
+
programPublicSymbols: ["main"],
|
|
3496
|
+
outPath,
|
|
3497
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
3498
|
+
optimization: "dev",
|
|
3499
|
+
});
|
|
3500
|
+
expect(spawnSync(outPath).status).toBe(7);
|
|
3501
|
+
// The canonical retry is valid output, but cannot occupy a key describing
|
|
3502
|
+
// a successful shard merge. Repairing the merge tool must retry the mode.
|
|
3503
|
+
expect(await readdir(join(cacheRoot, "bin")).catch(() => [])).toEqual([]);
|
|
3504
|
+
expect(await readdir(join(cacheRoot, "program-shard")).catch(() => [])).toEqual([]);
|
|
3505
|
+
}
|
|
3506
|
+
finally {
|
|
3507
|
+
if (oldCacheDir === undefined)
|
|
3508
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
3509
|
+
else
|
|
3510
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
3511
|
+
if (oldNoCache === undefined)
|
|
3512
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
3513
|
+
else
|
|
3514
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
3515
|
+
if (oldPath === undefined)
|
|
3516
|
+
delete process.env["PATH"];
|
|
3517
|
+
else
|
|
3518
|
+
process.env["PATH"] = oldPath;
|
|
3519
|
+
if (oldRealLd === undefined)
|
|
3520
|
+
delete process.env["SCRIPTC_TEST_REAL_LD"];
|
|
3521
|
+
else
|
|
3522
|
+
process.env["SCRIPTC_TEST_REAL_LD"] = oldRealLd;
|
|
3523
|
+
}
|
|
3524
|
+
});
|
|
3525
|
+
test.skipIf(!nativeShardMergeAvailable)("LLVM merged-object and archive caches separate shard projections and public-symbol keep sets", async () => {
|
|
3526
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-lib-program-shard-abi-"));
|
|
3527
|
+
scratch.push(dir);
|
|
3528
|
+
const cacheRoot = join(dir, "cache");
|
|
3529
|
+
const cPath = join(dir, "program.ll");
|
|
3530
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
3531
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
3532
|
+
const programSource = [
|
|
3533
|
+
"define i64 @foo() {",
|
|
3534
|
+
"entry:",
|
|
3535
|
+
" ret i64 1",
|
|
3536
|
+
"}",
|
|
3537
|
+
"",
|
|
3538
|
+
"define i64 @bar() {",
|
|
3539
|
+
"entry:",
|
|
3540
|
+
" ret i64 2",
|
|
3541
|
+
"}",
|
|
3542
|
+
"",
|
|
3543
|
+
].join("\n");
|
|
3544
|
+
const programShards = [
|
|
3545
|
+
{
|
|
3546
|
+
name: "program-f000.ll",
|
|
3547
|
+
source: "define i64 @foo() {\nentry:\n ret i64 1\n}\ndeclare i64 @bar()\n",
|
|
3548
|
+
},
|
|
3549
|
+
{
|
|
3550
|
+
name: "program-f001.ll",
|
|
3551
|
+
source: "declare i64 @foo()\ndefine i64 @bar() {\nentry:\n ret i64 2\n}\n",
|
|
3552
|
+
},
|
|
3553
|
+
];
|
|
3554
|
+
try {
|
|
3555
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
3556
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
3557
|
+
await mkdir(cacheRoot, { mode: 0o700 });
|
|
3558
|
+
await writeFile(cPath, programSource);
|
|
3559
|
+
const build = async (symbol, outPath, shards = programShards) => {
|
|
3560
|
+
await compileLibArchive({
|
|
3561
|
+
cPath,
|
|
3562
|
+
programSource,
|
|
3563
|
+
programShards: shards,
|
|
3564
|
+
programPublicSymbols: [symbol],
|
|
3565
|
+
outPath,
|
|
3566
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
3567
|
+
optimization: "dev",
|
|
3568
|
+
});
|
|
3569
|
+
};
|
|
3570
|
+
await build("foo", join(dir, "foo.lib.a"));
|
|
3571
|
+
const barArchive = join(dir, "bar.lib.a");
|
|
3572
|
+
await build("bar", barArchive);
|
|
3573
|
+
expect(await completeArtifacts(cacheRoot, "lib")).toHaveLength(2);
|
|
3574
|
+
expect((await readdir(join(cacheRoot, "program-obj"))).filter((name) => !name.endsWith(".sha256"))).toHaveLength(2);
|
|
3575
|
+
const probeSource = join(dir, "probe.c");
|
|
3576
|
+
const probe = join(dir, "probe");
|
|
3577
|
+
await writeFile(probeSource, "long long bar(void);\nint main(void) { return bar() != 2; }\n");
|
|
3578
|
+
execFileSync("clang", [probeSource, barArchive, "-lm", "-o", probe]);
|
|
3579
|
+
expect(execFileSync(probe, { encoding: "utf8" })).toBe("");
|
|
3580
|
+
// The canonical TU and ABI stay fixed, but the exact shard projection
|
|
3581
|
+
// changes. The merged-object tier must not reuse the previous layout.
|
|
3582
|
+
await build("bar", join(dir, "bar-commented.lib.a"), [
|
|
3583
|
+
{ ...programShards[0], source: `; projection edit\n${programShards[0].source}` },
|
|
3584
|
+
programShards[1],
|
|
3585
|
+
]);
|
|
3586
|
+
expect(await completeArtifacts(cacheRoot, "lib")).toHaveLength(3);
|
|
3587
|
+
expect((await readdir(join(cacheRoot, "program-obj"))).filter((name) => !name.endsWith(".sha256"))).toHaveLength(3);
|
|
3588
|
+
}
|
|
3589
|
+
finally {
|
|
3590
|
+
if (oldCacheDir === undefined)
|
|
3591
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
3592
|
+
else
|
|
3593
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
3594
|
+
if (oldNoCache === undefined)
|
|
3595
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
3596
|
+
else
|
|
3597
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
3598
|
+
}
|
|
3599
|
+
});
|
|
3600
|
+
test.skipIf(process.platform === "win32" || clangExecutable === undefined || arExecutable === undefined ||
|
|
3601
|
+
ldExecutable === undefined || (process.platform === "linux" && objcopyExecutable === undefined))("LLVM merged-object caches follow in-place shard merge-tool replacements", async () => {
|
|
3602
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-lib-program-shard-merge-tool-"));
|
|
3603
|
+
scratch.push(dir);
|
|
3604
|
+
const binDir = join(dir, "bin");
|
|
3605
|
+
const cacheRoot = join(dir, "cache");
|
|
3606
|
+
const mergeLog = join(dir, "merge.log");
|
|
3607
|
+
const cPath = join(dir, "program.ll");
|
|
3608
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
3609
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
3610
|
+
const oldPath = process.env["PATH"];
|
|
3611
|
+
const oldRealLd = process.env["SCRIPTC_TEST_REAL_LD"];
|
|
3612
|
+
const oldMergeLog = process.env["SCRIPTC_TEST_MERGE_LOG"];
|
|
3613
|
+
const programSource = [
|
|
3614
|
+
"define i64 @foo() {",
|
|
3615
|
+
"entry:",
|
|
3616
|
+
" ret i64 1",
|
|
3617
|
+
"}",
|
|
3618
|
+
"",
|
|
3619
|
+
"define i64 @bar() {",
|
|
3620
|
+
"entry:",
|
|
3621
|
+
" ret i64 2",
|
|
3622
|
+
"}",
|
|
3623
|
+
"",
|
|
3624
|
+
].join("\n");
|
|
3625
|
+
const programShards = [
|
|
3626
|
+
{
|
|
3627
|
+
name: "program-f000.ll",
|
|
3628
|
+
source: "define i64 @foo() {\nentry:\n ret i64 1\n}\ndeclare i64 @bar()\n",
|
|
3629
|
+
},
|
|
3630
|
+
{
|
|
3631
|
+
name: "program-f001.ll",
|
|
3632
|
+
source: "declare i64 @foo()\ndefine i64 @bar() {\nentry:\n ret i64 2\n}\n",
|
|
3633
|
+
},
|
|
3634
|
+
];
|
|
3635
|
+
const ldWrapper = (generation) => `#!/bin/sh
|
|
3636
|
+
printf '${generation}\\n' >> "$SCRIPTC_TEST_MERGE_LOG"
|
|
3637
|
+
exec "$SCRIPTC_TEST_REAL_LD" "$@"
|
|
3638
|
+
`;
|
|
3639
|
+
try {
|
|
3640
|
+
await Promise.all([mkdir(binDir), mkdir(cacheRoot, { mode: 0o700 })]);
|
|
3641
|
+
await Promise.all([
|
|
3642
|
+
symlink(clangExecutable, join(binDir, "clang")),
|
|
3643
|
+
symlink(arExecutable, join(binDir, "ar")),
|
|
3644
|
+
...(process.platform === "linux"
|
|
3645
|
+
? [symlink(objcopyExecutable, join(binDir, "objcopy"))]
|
|
3646
|
+
: []),
|
|
3647
|
+
writeFile(join(binDir, "ld"), ldWrapper("first")),
|
|
3648
|
+
writeFile(cPath, programSource),
|
|
3649
|
+
]);
|
|
3650
|
+
await chmod(join(binDir, "ld"), 0o755);
|
|
3651
|
+
process.env["PATH"] = binDir;
|
|
3652
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
3653
|
+
process.env["SCRIPTC_TEST_REAL_LD"] = ldExecutable;
|
|
3654
|
+
process.env["SCRIPTC_TEST_MERGE_LOG"] = mergeLog;
|
|
3655
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
3656
|
+
const build = (outPath) => compileLibArchive({
|
|
3657
|
+
cPath,
|
|
3658
|
+
programSource,
|
|
3659
|
+
programShards,
|
|
3660
|
+
programPublicSymbols: ["foo", "bar"],
|
|
3661
|
+
outPath,
|
|
3662
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
3663
|
+
optimization: "dev",
|
|
3664
|
+
});
|
|
3665
|
+
await build(join(dir, "first.lib.a"));
|
|
3666
|
+
await writeFile(join(binDir, "ld"), ldWrapper("second"));
|
|
3667
|
+
await chmod(join(binDir, "ld"), 0o755);
|
|
3668
|
+
await build(join(dir, "second.lib.a"));
|
|
3669
|
+
expect((await readFile(mergeLog, "utf8")).trim().split("\n")).toEqual(["first", "second"]);
|
|
3670
|
+
expect(await completeArtifacts(cacheRoot, "lib")).toHaveLength(2);
|
|
3671
|
+
expect((await readdir(join(cacheRoot, "program-obj"))).filter((name) => !name.endsWith(".sha256"))).toHaveLength(2);
|
|
3672
|
+
// The compiler and shard IR did not change; only merged outputs split by
|
|
3673
|
+
// localization-tool identity.
|
|
3674
|
+
expect((await readdir(join(cacheRoot, "program-shard"))).filter((name) => !name.endsWith(".sha256"))).toHaveLength(2);
|
|
3675
|
+
}
|
|
3676
|
+
finally {
|
|
3677
|
+
if (oldCacheDir === undefined)
|
|
3678
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
3679
|
+
else
|
|
3680
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
3681
|
+
if (oldNoCache === undefined)
|
|
3682
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
3683
|
+
else
|
|
3684
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
3685
|
+
if (oldPath === undefined)
|
|
3686
|
+
delete process.env["PATH"];
|
|
3687
|
+
else
|
|
3688
|
+
process.env["PATH"] = oldPath;
|
|
3689
|
+
if (oldRealLd === undefined)
|
|
3690
|
+
delete process.env["SCRIPTC_TEST_REAL_LD"];
|
|
3691
|
+
else
|
|
3692
|
+
process.env["SCRIPTC_TEST_REAL_LD"] = oldRealLd;
|
|
3693
|
+
if (oldMergeLog === undefined)
|
|
3694
|
+
delete process.env["SCRIPTC_TEST_MERGE_LOG"];
|
|
3695
|
+
else
|
|
3696
|
+
process.env["SCRIPTC_TEST_MERGE_LOG"] = oldMergeLog;
|
|
3697
|
+
}
|
|
3698
|
+
});
|
|
3699
|
+
test.skipIf(process.platform === "win32" || clangExecutable === undefined || arExecutable === undefined)("LLVM library shards fall back to the canonical TU without host merge tools", async () => {
|
|
3700
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-lib-program-shard-fallback-"));
|
|
3701
|
+
scratch.push(dir);
|
|
3702
|
+
const binDir = join(dir, "bin");
|
|
3703
|
+
const cPath = join(dir, "program.ll");
|
|
3704
|
+
const outPath = join(dir, "program.lib.a");
|
|
3705
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
3706
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
3707
|
+
const oldPath = process.env["PATH"];
|
|
3708
|
+
const programSource = "define i64 @public_value() {\nentry:\n ret i64 7\n}\n";
|
|
3709
|
+
try {
|
|
3710
|
+
await mkdir(binDir);
|
|
3711
|
+
await Promise.all([
|
|
3712
|
+
symlink(clangExecutable, join(binDir, "clang")),
|
|
3713
|
+
symlink(arExecutable, join(binDir, "ar")),
|
|
3714
|
+
]);
|
|
3715
|
+
process.env["PATH"] = binDir;
|
|
3716
|
+
process.env["SCRIPTC_NO_CACHE"] = "1";
|
|
3717
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
3718
|
+
await writeFile(cPath, programSource);
|
|
3719
|
+
await compileLibArchive({
|
|
3720
|
+
cPath,
|
|
3721
|
+
programSource,
|
|
3722
|
+
// If compilation reaches either shard, this build fails. Missing ld
|
|
3723
|
+
// (and objcopy on Linux) must select the valid canonical TU instead.
|
|
3724
|
+
programShards: [
|
|
3725
|
+
{ name: "program-f000.ll", source: "not valid LLVM IR\n" },
|
|
3726
|
+
{ name: "program-f001.ll", source: "also not valid LLVM IR\n" },
|
|
3727
|
+
],
|
|
3728
|
+
programPublicSymbols: ["public_value"],
|
|
3729
|
+
outPath,
|
|
3730
|
+
optimization: "dev",
|
|
3731
|
+
});
|
|
3732
|
+
expect(await readFile(outPath)).not.toHaveLength(0);
|
|
3733
|
+
}
|
|
3734
|
+
finally {
|
|
3735
|
+
if (oldCacheDir === undefined)
|
|
3736
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
3737
|
+
else
|
|
3738
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
3739
|
+
if (oldNoCache === undefined)
|
|
3740
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
3741
|
+
else
|
|
3742
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
3743
|
+
if (oldPath === undefined)
|
|
3744
|
+
delete process.env["PATH"];
|
|
3745
|
+
else
|
|
3746
|
+
process.env["PATH"] = oldPath;
|
|
3747
|
+
}
|
|
3748
|
+
});
|
|
3749
|
+
test.skipIf(process.platform === "win32" || clangExecutable === undefined || arExecutable === undefined ||
|
|
3750
|
+
(process.platform === "linux" && objcopyExecutable === undefined))("LLVM library shards fall back when an available host merge tool fails", async () => {
|
|
3751
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-lib-program-shard-merge-failure-"));
|
|
3752
|
+
scratch.push(dir);
|
|
3753
|
+
const binDir = join(dir, "bin");
|
|
3754
|
+
const cacheRoot = join(dir, "cache");
|
|
3755
|
+
const cPath = join(dir, "program.ll");
|
|
3756
|
+
const outPath = join(dir, "program.lib.a");
|
|
3757
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
3758
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
3759
|
+
const oldPath = process.env["PATH"];
|
|
3760
|
+
const programSource = [
|
|
3761
|
+
"define i64 @public_value() {",
|
|
3762
|
+
"entry:",
|
|
3763
|
+
" ret i64 7",
|
|
3764
|
+
"}",
|
|
3765
|
+
"",
|
|
3766
|
+
"define i64 @other_value() {",
|
|
3767
|
+
"entry:",
|
|
3768
|
+
" ret i64 9",
|
|
3769
|
+
"}",
|
|
3770
|
+
"",
|
|
3771
|
+
].join("\n");
|
|
3772
|
+
try {
|
|
3773
|
+
await Promise.all([mkdir(binDir), mkdir(cacheRoot, { mode: 0o700 })]);
|
|
3774
|
+
await Promise.all([
|
|
3775
|
+
symlink(clangExecutable, join(binDir, "clang")),
|
|
3776
|
+
symlink(arExecutable, join(binDir, "ar")),
|
|
3777
|
+
...(process.platform === "linux"
|
|
3778
|
+
? [symlink(objcopyExecutable, join(binDir, "objcopy"))]
|
|
3779
|
+
: []),
|
|
3780
|
+
writeFile(join(binDir, "ld"), "#!/bin/sh\nexit 1\n"),
|
|
3781
|
+
writeFile(cPath, programSource),
|
|
3782
|
+
]);
|
|
3783
|
+
await chmod(join(binDir, "ld"), 0o755);
|
|
3784
|
+
process.env["PATH"] = binDir;
|
|
3785
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
3786
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
3787
|
+
await compileLibArchive({
|
|
3788
|
+
cPath,
|
|
3789
|
+
programSource,
|
|
3790
|
+
programShards: [
|
|
3791
|
+
{
|
|
3792
|
+
name: "program-f000.ll",
|
|
3793
|
+
source: "define i64 @public_value() {\nentry:\n ret i64 7\n}\ndeclare i64 @other_value()\n",
|
|
3794
|
+
},
|
|
3795
|
+
{
|
|
3796
|
+
name: "program-f001.ll",
|
|
3797
|
+
source: "declare i64 @public_value()\ndefine i64 @other_value() {\nentry:\n ret i64 9\n}\n",
|
|
3798
|
+
},
|
|
3799
|
+
],
|
|
3800
|
+
programPublicSymbols: ["public_value", "other_value"],
|
|
3801
|
+
outPath,
|
|
3802
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
3803
|
+
optimization: "dev",
|
|
3804
|
+
});
|
|
3805
|
+
const probeSource = join(dir, "probe.c");
|
|
3806
|
+
const probe = join(dir, "probe");
|
|
3807
|
+
await writeFile(probeSource, "long long public_value(void);\nint main(void) { return public_value() != 7; }\n");
|
|
3808
|
+
execFileSync(clangExecutable, [probeSource, outPath, "-lm", "-o", probe]);
|
|
3809
|
+
expect(execFileSync(probe, { encoding: "utf8" })).toBe("");
|
|
3810
|
+
// The canonical retry is valid for this invocation, but must not occupy a
|
|
3811
|
+
// key describing merged shard bytes. A repaired merge tool retries the
|
|
3812
|
+
// optimization instead of receiving a poisoned completed/object hit.
|
|
3813
|
+
expect(await readdir(join(cacheRoot, "lib")).catch(() => [])).toEqual([]);
|
|
3814
|
+
expect(await readdir(join(cacheRoot, "program-obj")).catch(() => [])).toEqual([]);
|
|
3815
|
+
expect(await readdir(join(cacheRoot, "program-shard")).catch(() => [])).toEqual([]);
|
|
3816
|
+
}
|
|
3817
|
+
finally {
|
|
3818
|
+
if (oldCacheDir === undefined)
|
|
3819
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
3820
|
+
else
|
|
3821
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
3822
|
+
if (oldNoCache === undefined)
|
|
3823
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
3824
|
+
else
|
|
3825
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
3826
|
+
if (oldPath === undefined)
|
|
3827
|
+
delete process.env["PATH"];
|
|
3828
|
+
else
|
|
3829
|
+
process.env["PATH"] = oldPath;
|
|
3830
|
+
}
|
|
3831
|
+
});
|
|
3832
|
+
test.skipIf(process.platform === "win32" || zigExecutable === undefined)("cross-ELF localized archives retain unreferenced identity roots", async () => {
|
|
3833
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-lib-localized-identity-"));
|
|
3834
|
+
scratch.push(dir);
|
|
3835
|
+
const cPath = join(dir, "program.c");
|
|
3836
|
+
const probePath = join(dir, "probe.c");
|
|
3837
|
+
const archivePath = join(dir, "program.lib.a");
|
|
3838
|
+
const probeOutput = join(dir, "probe");
|
|
3839
|
+
const cacheRoot = join(dir, "cache");
|
|
3840
|
+
const target = "x86_64-linux-gnu.2.36";
|
|
3841
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
3842
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
3843
|
+
const oldCc = process.env["SCRIPTC_CC"];
|
|
3844
|
+
const oldTarget = process.env["SCRIPTC_TARGET"];
|
|
3845
|
+
try {
|
|
3846
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
3847
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
3848
|
+
process.env["SCRIPTC_CC"] = "zigcc";
|
|
3849
|
+
process.env["SCRIPTC_TARGET"] = target;
|
|
3850
|
+
await writeFile(cPath, "int scriptc_large_program_value(void) { return 7; }\n");
|
|
3851
|
+
await compileLibArchive({
|
|
3852
|
+
cPath,
|
|
3853
|
+
identityCSource: [
|
|
3854
|
+
"unsigned long long scriptc_build_id(void) { return 2; }",
|
|
3855
|
+
"unsigned scriptc_abi_version(void) { return 1; }",
|
|
3856
|
+
"",
|
|
3857
|
+
].join("\n"),
|
|
3858
|
+
outPath: archivePath,
|
|
3859
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
3860
|
+
localizeSymbols: [
|
|
3861
|
+
"scriptc_large_program_value",
|
|
3862
|
+
"scriptc_build_id",
|
|
3863
|
+
"scriptc_abi_version",
|
|
3864
|
+
],
|
|
3865
|
+
});
|
|
3866
|
+
await writeFile(probePath, [
|
|
3867
|
+
"int scriptc_large_program_value(void);",
|
|
3868
|
+
"unsigned long long scriptc_build_id(void);",
|
|
3869
|
+
"unsigned scriptc_abi_version(void);",
|
|
3870
|
+
"int main(void) {",
|
|
3871
|
+
" return scriptc_large_program_value() != 7 || scriptc_build_id() != 2 || scriptc_abi_version() != 1;",
|
|
3872
|
+
"}",
|
|
3873
|
+
"",
|
|
3874
|
+
].join("\n"));
|
|
3875
|
+
execFileSync(zigExecutable, [
|
|
3876
|
+
"cc", "-target", target, probePath, archivePath, "-lm", "-o", probeOutput,
|
|
3877
|
+
]);
|
|
3878
|
+
}
|
|
3879
|
+
finally {
|
|
3880
|
+
if (oldCacheDir === undefined)
|
|
3881
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
3882
|
+
else
|
|
3883
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
3884
|
+
if (oldNoCache === undefined)
|
|
3885
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
3886
|
+
else
|
|
3887
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
3888
|
+
if (oldCc === undefined)
|
|
3889
|
+
delete process.env["SCRIPTC_CC"];
|
|
3890
|
+
else
|
|
3891
|
+
process.env["SCRIPTC_CC"] = oldCc;
|
|
3892
|
+
if (oldTarget === undefined)
|
|
3893
|
+
delete process.env["SCRIPTC_TARGET"];
|
|
3894
|
+
else
|
|
3895
|
+
process.env["SCRIPTC_TARGET"] = oldTarget;
|
|
3896
|
+
}
|
|
3897
|
+
});
|
|
2814
3898
|
//# sourceMappingURL=cc-cache.test.js.map
|