@scriptc/compiler 0.0.26 → 0.0.27

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.
@@ -248,6 +248,25 @@ 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 (libc/libm, plus sanitizer ABI in instrumented
259
+ * builds) keep their global binding. Omitted = the classic archive,
260
+ * byte-for-byte. Host-native builds only. */
261
+ localizeSymbols?: readonly string[];
262
+ /** Thread-instanced state (the profile's abi.instance_per_thread): every
263
+ * TU of the archive compiles with -DSCR_THREAD_INSTANCES, moving the
264
+ * runtime units' mutable statics into thread-local storage (SCR_TL in
265
+ * scr_runtime.h) to match the program TU's thread-local globals — one
266
+ * complete instance per embedder thread. The define rides cflags, so
267
+ * every cache tier keys it automatically; omitted = the classic
268
+ * archive, byte-for-byte. */
269
+ threadInstances?: boolean;
251
270
  /** IR-detected link gates (the compileC precedent, refusal-narrowed). */
252
271
  regex?: boolean;
253
272
  assert?: boolean;
@@ -757,6 +757,7 @@ export async function compileLibArchive(opts) {
757
757
  "-fno-strict-aliasing", // the emitted object model type-puns — see compileC's buildArgs
758
758
  "-Wno-deprecated-declarations",
759
759
  "-DSCR_LIB",
760
+ ...(opts.threadInstances ? ["-DSCR_THREAD_INSTANCES"] : []),
760
761
  ...(opts.textDecoderLegacy ? ["-DSCR_TEXT_DECODER_LEGACY"] : []),
761
762
  "-I", rtDir,
762
763
  ...(regex ? ["-I", vendorEngineDir()] : []),
@@ -815,7 +816,12 @@ export async function compileLibArchive(opts) {
815
816
  }
816
817
  }
817
818
  const vendorBuildIdentity = await currentVendorCacheBuildIdentity(driver, `${toolchainEnv}\0${implicitToolchain ?? "<uncached>"}`);
818
- const cacheCompleteArchive = root !== null && await archiverSupportsPersistentCache(arArgv, driver);
819
+ // Runtime-localized archives skip the completed-archive tier: their bytes
820
+ // additionally depend on the host's ld/objcopy identities, which the
821
+ // archive key does not fingerprint. The runtime-object tier still serves
822
+ // them (localization consumes the same per-flavor objects).
823
+ const cacheCompleteArchive = opts.localizeSymbols === undefined &&
824
+ root !== null && await archiverSupportsPersistentCache(arArgv, driver);
819
825
  let cachedArchive = null;
820
826
  let compilerVersion = "";
821
827
  let archiverVersion = "";
@@ -968,17 +974,29 @@ export async function compileLibArchive(opts) {
968
974
  }
969
975
  }
970
976
  const objects = [programObject, ...runtimeObjects, ...lreObjects, ...zlibObjects];
971
- // A cacheable build owns a private archive from `ar` through publication.
972
- // The caller-visible output can be shared by another invocation without
973
- // letting that invocation's bytes poison this key.
974
- const archiveOutput = cachedArchive === null ? opts.outPath : join(buildDir, "artifact.lib.a");
977
+ // Multi-instance library mode: the archive's one member becomes the
978
+ // combined, symbol-localized object (cached vendor/runtime objects
979
+ // are read-only inputs here — the combine step never mutates them).
980
+ const archiveMembers = opts.localizeSymbols === undefined
981
+ ? objects
982
+ : [
983
+ await localizeLibraryObjects(driver, arArgv, buildDir, programObject, [...runtimeObjects, ...lreObjects, ...zlibObjects], opts.localizeSymbols, stem),
984
+ ];
985
+ // A cacheable or runtime-localized build owns a private archive from
986
+ // `ar` through publication. Localized archives deliberately bypass the
987
+ // completed-artifact cache, but still need atomic installation so two
988
+ // invocations sharing a caller-visible output cannot race `rm`/`ar` on
989
+ // that path.
990
+ const archiveOutput = cachedArchive === null && opts.localizeSymbols === undefined
991
+ ? opts.outPath
992
+ : join(buildDir, "artifact.lib.a");
975
993
  await rm(archiveOutput, { force: true }); // `ar r` would append into a stale archive
976
994
  await mkdir(dirname(archiveOutput), { recursive: true });
977
995
  await execFileAsync(arArgv[0] ?? "ar", [
978
996
  ...arArgv.slice(1),
979
997
  "rcs",
980
998
  archiveOutput,
981
- ...objects,
999
+ ...archiveMembers,
982
1000
  ]);
983
1001
  if (archiveOutput !== opts.outPath)
984
1002
  await installArtifact(archiveOutput, opts.outPath);
@@ -1029,6 +1047,73 @@ export async function compileLibArchive(opts) {
1029
1047
  if (root !== null)
1030
1048
  await pruneCache(root).catch(() => undefined);
1031
1049
  }
1050
+ /* Multi-instance library mode's localization step: combine the program
1051
+ * object with exactly the runtime/vendor members it reaches into ONE
1052
+ * relocatable object, then demote every scriptc external definition except
1053
+ * the profile-declared symbols to a local symbol. The internals are not
1054
+ * renamed apart — they stop being visible to the embedder's linker at all,
1055
+ * so a second archive built under a different prefix brings its own private
1056
+ * copy of the whole runtime (allocator, collector, arena, panic sink) into
1057
+ * the same process. Undefined references (libc/libm, plus sanitizer ABI in
1058
+ * instrumented builds) keep their global binding: the C library and
1059
+ * sanitizer are the embedder's, shared by design.
1060
+ *
1061
+ * Member selection matters: a classic archive's unused members (and their
1062
+ * undefined references to units library mode excludes, like the
1063
+ * fs-promises unit's fiber symbols) never reach an embedder's link. A
1064
+ * blind merge of every object would carry those references into the one
1065
+ * combined member. Staging the support objects into an intermediate
1066
+ * archive keeps the linker's own member semantics: `ld -r` pulls only the
1067
+ * members the program object transitively needs.
1068
+ *
1069
+ * darwin — one ld64 invocation: -r merges program + needed members,
1070
+ * -exported_symbols_list demotes every unlisted global to
1071
+ * private extern, and -r without -keep_private_externs writes
1072
+ * private externs out as non-external symbols. Apple ASan's
1073
+ * image-registration COMMON remains shared so the final Mach-O
1074
+ * image registers its globals once.
1075
+ * linux — ld -r merges with --force-group-allocation (ASan's
1076
+ * instrumented globals ride ELF section groups whose signatures
1077
+ * repeat across archives sharing runtime objects; resolving the
1078
+ * groups into the combined member keeps a later multi-archive
1079
+ * link from discarding one archive's copies), then binutils
1080
+ * objcopy --keep-global-symbols localizes every other DEFINED
1081
+ * global (objcopy leaves undefined symbols global by its own
1082
+ * rule).
1083
+ *
1084
+ * Host-native only: compileLibrary refuses localization for cross targets
1085
+ * before emission (this step runs the host's ld/objcopy over host-format
1086
+ * objects). */
1087
+ async function localizeLibraryObjects(driver, arArgv, buildDir, programObject, supportObjects, keepSymbols, stem) {
1088
+ const platform = targetPlatform(driver);
1089
+ const combined = join(buildDir, `${stem}.localized.o`);
1090
+ const staging = join(buildDir, `${stem}.localize-staging.a`);
1091
+ const keepFile = join(buildDir, "localize-keep.syms");
1092
+ const run = async (argv) => {
1093
+ try {
1094
+ await execFileAsync(argv[0], [...argv.slice(1)]);
1095
+ }
1096
+ catch (err) {
1097
+ const stderr = err.stderr ?? String(err);
1098
+ throw new Error(`${argv[0]} failed while localizing the library archive's runtime symbols (abi.localize_runtime).\n` +
1099
+ `Runtime symbol localization needs the host toolchain's ${platform === "darwin" ? "ld" : "ld and objcopy"} beside the C compiler.\n\n${stderr}`);
1100
+ }
1101
+ };
1102
+ await run([arArgv[0] ?? "ar", ...arArgv.slice(1), "rcs", staging, ...supportObjects]);
1103
+ if (platform === "darwin") {
1104
+ await writeFile(keepFile, keepSymbols.map((s) => `_${s}\n`).join(""));
1105
+ await run(["ld", "-r", programObject, staging, "-o", combined, "-exported_symbols_list", keepFile]);
1106
+ }
1107
+ else if (platform === "linux") {
1108
+ await writeFile(keepFile, keepSymbols.map((s) => `${s}\n`).join(""));
1109
+ await run(["ld", "-r", "--force-group-allocation", programObject, staging, "-o", combined]);
1110
+ await run(["objcopy", `--keep-global-symbols=${keepFile}`, combined]);
1111
+ }
1112
+ else {
1113
+ throw new Error(`runtime symbol localization (abi.localize_runtime) supports darwin and linux host builds; this build targets ${platform}`);
1114
+ }
1115
+ return combined;
1116
+ }
1032
1117
  /* -------------------------- persistent build cache ---------------------------
1033
1118
  * Content-addressed caches that let repeat builds of unchanged programs skip
1034
1119
  * payload code generation/linking — the test lanes' dominant cost. Executable