@scriptc/compiler 0.0.26 → 0.0.28
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.d.ts +22 -0
- package/dist/backend/cc.js +147 -9
- package/dist/backend/cc.js.map +1 -1
- package/dist/backend/emission/emitter.js +9 -2
- package/dist/backend/emission/emitter.js.map +1 -1
- package/dist/backend/llvm/emitter.js +12 -4
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/object-localize.d.ts +16 -0
- package/dist/backend/object-localize.js +936 -0
- package/dist/backend/object-localize.js.map +1 -0
- package/dist/backend/object-localize.test.d.ts +1 -0
- package/dist/backend/object-localize.test.js +555 -0
- package/dist/backend/object-localize.test.js.map +1 -0
- package/dist/index.js +57 -0
- package/dist/index.js.map +1 -1
- package/dist/ir/nodes.d.ts +6 -0
- package/dist/ir/nodes.js.map +1 -1
- package/dist/library/profile.d.ts +11 -0
- package/dist/library/profile.js +79 -2
- package/dist/library/profile.js.map +1 -1
- package/package.json +2 -2
- package/surface-manifest.json +1 -1
package/dist/backend/cc.d.ts
CHANGED
|
@@ -248,6 +248,28 @@ export interface LibArchiveOptions {
|
|
|
248
248
|
* matching compileC's arbitrary-input safety boundary. */
|
|
249
249
|
cacheIdentity?: string;
|
|
250
250
|
sanitize?: boolean;
|
|
251
|
+
/** Multi-instance library mode (the profile's abi.localize_runtime): the
|
|
252
|
+
* external symbols to KEEP global — every other scriptc external
|
|
253
|
+
* definition in the archive (the runtime's internals, the program TU's
|
|
254
|
+
* mangled functions and globals, vendor objects) is demoted to a local
|
|
255
|
+
* symbol. Toolchain sanitizer ABI remains external as required,
|
|
256
|
+
* so N archives built under pairwise-distinct prefixes link into one
|
|
257
|
+
* process with no symbol collisions and no shared mutable runtime state.
|
|
258
|
+
* Undefined references (the target C/math runtime and system APIs, plus
|
|
259
|
+
* sanitizer ABI in instrumented builds) keep their global binding. Windows
|
|
260
|
+
* embedders additionally link advapi32, iphlpapi, and ws2_32. Omitted = the
|
|
261
|
+
* classic archive, byte-for-byte. Admitted for darwin/linux/win32 native
|
|
262
|
+
* hosts, linux and windows cross triples from any host, and macos cross
|
|
263
|
+
* triples from a darwin host (compileLibrary owns the refusal fence). */
|
|
264
|
+
localizeSymbols?: readonly string[];
|
|
265
|
+
/** Thread-instanced state (the profile's abi.instance_per_thread): every
|
|
266
|
+
* TU of the archive compiles with -DSCR_THREAD_INSTANCES, moving the
|
|
267
|
+
* runtime units' mutable statics into thread-local storage (SCR_TL in
|
|
268
|
+
* scr_runtime.h) to match the program TU's thread-local globals — one
|
|
269
|
+
* complete instance per embedder thread. The define rides cflags, so
|
|
270
|
+
* every cache tier keys it automatically; omitted = the classic
|
|
271
|
+
* archive, byte-for-byte. */
|
|
272
|
+
threadInstances?: boolean;
|
|
251
273
|
/** IR-detected link gates (the compileC precedent, refusal-narrowed). */
|
|
252
274
|
regex?: boolean;
|
|
253
275
|
assert?: boolean;
|
package/dist/backend/cc.js
CHANGED
|
@@ -6,6 +6,7 @@ import { access, chmod, copyFile, link, mkdir, mkdtemp, readdir, readFile, realp
|
|
|
6
6
|
import { availableParallelism, homedir, tmpdir } from "node:os";
|
|
7
7
|
import { basename, delimiter, dirname, extname, isAbsolute, join, resolve } from "node:path";
|
|
8
8
|
import { promisify } from "node:util";
|
|
9
|
+
import { localizeElfObject, mergeAndLocalizeCoffObjects } from "./object-localize.js";
|
|
9
10
|
const execFileAsync = promisify(execFile);
|
|
10
11
|
/** Test lanes run against one immutable checkout and one immutable toolchain
|
|
11
12
|
* for the lifetime of each Vitest worker. Production deliberately rediscovers
|
|
@@ -712,9 +713,12 @@ async function ensureTlsArchive(sanitize, driver, buildIdentity, cacheRoot = ven
|
|
|
712
713
|
* units a library may reach are the pure-data ones (regex + the vendored matcher, assert,
|
|
713
714
|
* inspect, symbol, searchParams, emitter+dyn_handle, zlib); every
|
|
714
715
|
* loop-hooked or ambient unit was refused at SC4005 before emission.
|
|
715
|
-
* External-symbol contract: undefined references only to
|
|
716
|
-
*
|
|
717
|
-
*
|
|
716
|
+
* External-symbol contract: undefined references only to the target's C/math
|
|
717
|
+
* runtime and system APIs. Windows embedders additionally link advapi32,
|
|
718
|
+
* iphlpapi, and ws2_32; the platform driver supplies its ordinary CRT and
|
|
719
|
+
* kernel imports. Zlib rides the VENDORED per-flavor objects here even on
|
|
720
|
+
* hosts because the executable lane's system `-lz` cannot ride inside an
|
|
721
|
+
* archive. */
|
|
718
722
|
/** The library base: the executable lane's unconditional sources minus the
|
|
719
723
|
* fiber/loop and child-process units, plus the library-mode TU. */
|
|
720
724
|
const LIB_RUNTIME_SOURCES = [
|
|
@@ -757,6 +761,7 @@ export async function compileLibArchive(opts) {
|
|
|
757
761
|
"-fno-strict-aliasing", // the emitted object model type-puns — see compileC's buildArgs
|
|
758
762
|
"-Wno-deprecated-declarations",
|
|
759
763
|
"-DSCR_LIB",
|
|
764
|
+
...(opts.threadInstances ? ["-DSCR_THREAD_INSTANCES"] : []),
|
|
760
765
|
...(opts.textDecoderLegacy ? ["-DSCR_TEXT_DECODER_LEGACY"] : []),
|
|
761
766
|
"-I", rtDir,
|
|
762
767
|
...(regex ? ["-I", vendorEngineDir()] : []),
|
|
@@ -815,7 +820,13 @@ export async function compileLibArchive(opts) {
|
|
|
815
820
|
}
|
|
816
821
|
}
|
|
817
822
|
const vendorBuildIdentity = await currentVendorCacheBuildIdentity(driver, `${toolchainEnv}\0${implicitToolchain ?? "<uncached>"}`);
|
|
818
|
-
|
|
823
|
+
// Runtime-localized archives skip the completed-archive tier: their bytes
|
|
824
|
+
// additionally depend on the localization toolchain's identity (host ld/
|
|
825
|
+
// objcopy or the cross driver's lld), which the archive key does not
|
|
826
|
+
// fingerprint. The runtime-object tier still serves them (localization
|
|
827
|
+
// consumes the same per-flavor objects).
|
|
828
|
+
const cacheCompleteArchive = opts.localizeSymbols === undefined &&
|
|
829
|
+
root !== null && await archiverSupportsPersistentCache(arArgv, driver);
|
|
819
830
|
let cachedArchive = null;
|
|
820
831
|
let compilerVersion = "";
|
|
821
832
|
let archiverVersion = "";
|
|
@@ -968,17 +979,29 @@ export async function compileLibArchive(opts) {
|
|
|
968
979
|
}
|
|
969
980
|
}
|
|
970
981
|
const objects = [programObject, ...runtimeObjects, ...lreObjects, ...zlibObjects];
|
|
971
|
-
//
|
|
972
|
-
//
|
|
973
|
-
//
|
|
974
|
-
const
|
|
982
|
+
// Multi-instance library mode: the archive's one member becomes the
|
|
983
|
+
// combined, symbol-localized object (cached vendor/runtime objects
|
|
984
|
+
// are read-only inputs here — the combine step never mutates them).
|
|
985
|
+
const archiveMembers = opts.localizeSymbols === undefined
|
|
986
|
+
? objects
|
|
987
|
+
: [
|
|
988
|
+
await localizeLibraryObjects(driver, arArgv, buildDir, programObject, [...runtimeObjects, ...lreObjects, ...zlibObjects], opts.localizeSymbols, stem),
|
|
989
|
+
];
|
|
990
|
+
// A cacheable or runtime-localized build owns a private archive from
|
|
991
|
+
// `ar` through publication. Localized archives deliberately bypass the
|
|
992
|
+
// completed-artifact cache, but still need atomic installation so two
|
|
993
|
+
// invocations sharing a caller-visible output cannot race `rm`/`ar` on
|
|
994
|
+
// that path.
|
|
995
|
+
const archiveOutput = cachedArchive === null && opts.localizeSymbols === undefined
|
|
996
|
+
? opts.outPath
|
|
997
|
+
: join(buildDir, "artifact.lib.a");
|
|
975
998
|
await rm(archiveOutput, { force: true }); // `ar r` would append into a stale archive
|
|
976
999
|
await mkdir(dirname(archiveOutput), { recursive: true });
|
|
977
1000
|
await execFileAsync(arArgv[0] ?? "ar", [
|
|
978
1001
|
...arArgv.slice(1),
|
|
979
1002
|
"rcs",
|
|
980
1003
|
archiveOutput,
|
|
981
|
-
...
|
|
1004
|
+
...archiveMembers,
|
|
982
1005
|
]);
|
|
983
1006
|
if (archiveOutput !== opts.outPath)
|
|
984
1007
|
await installArtifact(archiveOutput, opts.outPath);
|
|
@@ -1029,6 +1052,121 @@ export async function compileLibArchive(opts) {
|
|
|
1029
1052
|
if (root !== null)
|
|
1030
1053
|
await pruneCache(root).catch(() => undefined);
|
|
1031
1054
|
}
|
|
1055
|
+
/* Multi-instance library mode's localization step: combine the program
|
|
1056
|
+
* object with exactly the runtime/vendor members it reaches into ONE
|
|
1057
|
+
* relocatable object, then demote every scriptc external definition except
|
|
1058
|
+
* the profile-declared symbols to a local symbol. The internals are not
|
|
1059
|
+
* renamed apart — they stop being visible to the embedder's linker at all,
|
|
1060
|
+
* so a second archive built under a different prefix brings its own private
|
|
1061
|
+
* copy of the whole runtime (allocator, collector, arena, panic sink) into
|
|
1062
|
+
* the same process. Undefined references (the target C/math runtime and
|
|
1063
|
+
* system APIs, plus sanitizer ABI in instrumented builds) keep their global
|
|
1064
|
+
* binding: those platform services and the sanitizer are the embedder's,
|
|
1065
|
+
* shared by design. Windows embedders additionally link advapi32, iphlpapi,
|
|
1066
|
+
* and ws2_32.
|
|
1067
|
+
*
|
|
1068
|
+
* Member selection matters: a classic archive's unused members (and their
|
|
1069
|
+
* undefined references to units library mode excludes, like the
|
|
1070
|
+
* fs-promises unit's fiber symbols) never reach an embedder's link. A
|
|
1071
|
+
* blind merge of every object would carry those references into the one
|
|
1072
|
+
* combined member. Staging the support objects into an intermediate
|
|
1073
|
+
* archive keeps the linker's own member semantics: `ld -r` pulls only the
|
|
1074
|
+
* members the program object transitively needs (the COFF arm implements
|
|
1075
|
+
* the same member semantics in process).
|
|
1076
|
+
*
|
|
1077
|
+
* Mach-O — one host-ld64 invocation: -r merges program + needed members,
|
|
1078
|
+
* -exported_symbols_list demotes every unlisted global to
|
|
1079
|
+
* private extern, and -r without -keep_private_externs writes
|
|
1080
|
+
* private externs out as non-external symbols. Apple ASan's
|
|
1081
|
+
* image-registration COMMON remains shared so the final Mach-O
|
|
1082
|
+
* image registers its globals once. ld64 reads every macOS
|
|
1083
|
+
* architecture, so darwin-host macos cross triples ride the
|
|
1084
|
+
* same arm; compileLibrary refuses macos targets elsewhere.
|
|
1085
|
+
* ELF, native linux host — ld -r merges with --force-group-allocation
|
|
1086
|
+
* (ASan's instrumented globals ride ELF section groups whose
|
|
1087
|
+
* signatures repeat across archives sharing runtime objects;
|
|
1088
|
+
* resolving the groups into the combined member keeps a later
|
|
1089
|
+
* multi-archive link from discarding one archive's copies),
|
|
1090
|
+
* then binutils objcopy --keep-global-symbols localizes every
|
|
1091
|
+
* other DEFINED global (objcopy leaves undefined symbols
|
|
1092
|
+
* global by its own rule). The exact historical recipe.
|
|
1093
|
+
* ELF, cross triples from any host — `zig cc -target <triple> -r`
|
|
1094
|
+
* merges (zig is already the cross driver's hard requirement),
|
|
1095
|
+
* then localizeElfObject demotes in process and resolves
|
|
1096
|
+
* section groups the way --force-group-allocation does, with
|
|
1097
|
+
* no host binutils/llvm-objcopy dependency.
|
|
1098
|
+
* COFF, native win32 hosts and windows cross triples from any host —
|
|
1099
|
+
* mergeAndLocalizeCoffObjects performs member selection, the
|
|
1100
|
+
* merge, cross-object symbol resolution, and demotion in
|
|
1101
|
+
* process: no linker offers a COFF relocatable mode (lld-link
|
|
1102
|
+
* mirrors MSVC link.exe; zig's COFF driver refuses multi-object
|
|
1103
|
+
* merges) and llvm-objcopy rejects symbol-scope flags for
|
|
1104
|
+
* COFF, so no tool pairing exists to shell out to. */
|
|
1105
|
+
async function localizeLibraryObjects(driver, arArgv, buildDir, programObject, supportObjects, keepSymbols, stem) {
|
|
1106
|
+
const platform = targetPlatform(driver);
|
|
1107
|
+
const combined = join(buildDir, `${stem}.localized.o`);
|
|
1108
|
+
const staging = join(buildDir, `${stem}.localize-staging.a`);
|
|
1109
|
+
const keepFile = join(buildDir, "localize-keep.syms");
|
|
1110
|
+
const run = async (argv) => {
|
|
1111
|
+
try {
|
|
1112
|
+
await execFileAsync(argv[0], [...argv.slice(1)]);
|
|
1113
|
+
}
|
|
1114
|
+
catch (err) {
|
|
1115
|
+
const stderr = err.stderr ?? String(err);
|
|
1116
|
+
throw new Error(`${argv[0]} failed while localizing the library archive's runtime symbols (abi.localize_runtime).\n` +
|
|
1117
|
+
`Runtime symbol localization needs the ${platform === "darwin" ? "host toolchain's ld" : driver.target === null ? "host toolchain's ld and objcopy" : "cross driver's relocatable link"} beside the C compiler.\n\n${stderr}`);
|
|
1118
|
+
}
|
|
1119
|
+
};
|
|
1120
|
+
if (platform === "win32") {
|
|
1121
|
+
// COFF has no relocatable-link tool to stage through; the member
|
|
1122
|
+
// selection and combine+demote happen in process over the object bytes.
|
|
1123
|
+
const [program, ...support] = await Promise.all([programObject, ...supportObjects].map((path) => readFile(path)));
|
|
1124
|
+
try {
|
|
1125
|
+
await writeFile(combined, mergeAndLocalizeCoffObjects(program, support, new Set(keepSymbols), {
|
|
1126
|
+
program: basename(programObject),
|
|
1127
|
+
support: supportObjects.map((path) => basename(path)),
|
|
1128
|
+
}));
|
|
1129
|
+
}
|
|
1130
|
+
catch (err) {
|
|
1131
|
+
throw new Error(`COFF symbol localization failed while localizing the library archive's runtime symbols (abi.localize_runtime).\n\n${err.message}`);
|
|
1132
|
+
}
|
|
1133
|
+
return combined;
|
|
1134
|
+
}
|
|
1135
|
+
await run([arArgv[0] ?? "ar", ...arArgv.slice(1), "rcs", staging, ...supportObjects]);
|
|
1136
|
+
if (platform === "darwin") {
|
|
1137
|
+
await writeFile(keepFile, keepSymbols.map((s) => `_${s}\n`).join(""));
|
|
1138
|
+
await run(["ld", "-r", programObject, staging, "-o", combined, "-exported_symbols_list", keepFile]);
|
|
1139
|
+
}
|
|
1140
|
+
else if (platform === "linux" && driver.target === null) {
|
|
1141
|
+
await writeFile(keepFile, keepSymbols.map((s) => `${s}\n`).join(""));
|
|
1142
|
+
await run(["ld", "-r", "--force-group-allocation", programObject, staging, "-o", combined]);
|
|
1143
|
+
await run(["objcopy", `--keep-global-symbols=${keepFile}`, combined]);
|
|
1144
|
+
}
|
|
1145
|
+
else if (platform === "linux") {
|
|
1146
|
+
// Cross ELF: the cross driver's own lld performs the relocatable merge
|
|
1147
|
+
// (with the staging archive's member semantics); demotion and section-
|
|
1148
|
+
// group resolution happen in process. -nostdlib keeps zig from feeding
|
|
1149
|
+
// libc/compiler-rt inputs into the merge.
|
|
1150
|
+
await run([
|
|
1151
|
+
driver.argv[0] ?? "zig",
|
|
1152
|
+
...driver.argv.slice(1),
|
|
1153
|
+
"-target", driver.target,
|
|
1154
|
+
"-nostdlib",
|
|
1155
|
+
"-r", programObject, staging,
|
|
1156
|
+
"-o", combined,
|
|
1157
|
+
]);
|
|
1158
|
+
try {
|
|
1159
|
+
await writeFile(combined, localizeElfObject(await readFile(combined), new Set(keepSymbols)));
|
|
1160
|
+
}
|
|
1161
|
+
catch (err) {
|
|
1162
|
+
throw new Error(`ELF symbol localization failed while localizing the library archive's runtime symbols (abi.localize_runtime).\n\n${err.message}`);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
else {
|
|
1166
|
+
throw new Error(`runtime symbol localization (abi.localize_runtime) has no ${platform} arm; compileLibrary admits darwin, linux, and win32 builds only`);
|
|
1167
|
+
}
|
|
1168
|
+
return combined;
|
|
1169
|
+
}
|
|
1032
1170
|
/* -------------------------- persistent build cache ---------------------------
|
|
1033
1171
|
* Content-addressed caches that let repeat builds of unchanged programs skip
|
|
1034
1172
|
* payload code generation/linking — the test lanes' dominant cost. Executable
|