Define new runtime globals with initializers so library localization hides them (#992)

This commit is contained in:
Chris Tate
2026-10-14 21:04:33 -05:00
committed by GitHub
3 changed files with 67 additions and 4 deletions
+4 -2
View File
@@ -266,8 +266,10 @@ SCR_TL size_t scr_cyc_live = 0;
SCR_TL size_t scr_cyc_old_freed = 0;
#endif
/* Descriptor copies by registry id. Id 0 stays empty (an unregistered type). */
ScrCycType scr_cyc_types[SCR_CYC_MAX_TYPES];
/* Descriptor copies by registry id. Id 0 stays empty (an unregistered type).
* Explicitly initialized so library localization can demote it: a tentative
* definition is a COMMON symbol on Mach-O, and COMMON symbols stay global. */
ScrCycType scr_cyc_types[SCR_CYC_MAX_TYPES] = {0};
static uint32_t scr_cyc_ntypes = 1;
/* Assign `type` its id on its first allocation; any thread may get there
+5 -2
View File
@@ -96,8 +96,10 @@ typedef struct ScrSidx {
/* Keep short-string cursor traffic out of the sparse cache. A short-lived
* one-byte receiver can be far more common than a large indexed one; sharing
* the round-robin slots would otherwise rebuild a warm index every few calls.
* Explicitly initialized so library localization can demote it: a tentative
* definition is a COMMON symbol on Mach-O, and COMMON symbols stay global.
*/
SCR_TL ScrStrScan scr_str_scan;
SCR_TL ScrStrScan scr_str_scan = {0};
static SCR_TL ScrSidx scr_sidx_sparse_tab[SCR_SIDX_N];
static SCR_TL ScrSidx scr_sidx_cursor_tab[SCR_SIDX_N];
static SCR_TL unsigned scr_sidx_sparse_clock;
@@ -936,7 +938,8 @@ static inline uint32_t scr_utf8_decode(const char *p, size_t *adv) {
/* ── WTF-8 lone surrogates (scr_runtime.h) ────────────────────────── */
bool scr_wtf8_lone;
/* Explicitly initialized for library localization (see scr_str_scan). */
bool scr_wtf8_lone = false;
void scr_wtf8_note(void) {
if (!scr_wtf8_any()) __atomic_store_n(&scr_wtf8_lone, true, __ATOMIC_RELAXED);
@@ -0,0 +1,58 @@
import { execFile } from "node:child_process";
import { mkdir, readdir, readFile } from "node:fs/promises";
import { availableParallelism } from "node:os";
import { join } from "node:path";
import { promisify } from "node:util";
import { expect, test } from "vitest";
const execFileAsync = promisify(execFile);
const testDir = import.meta.dirname;
const srcDir = join(testDir, "../src");
const vendorDir = join(testDir, "../vendor");
const buildDir = join(testDir, "build", "common-symbols");
// Library localization (abi.localize_runtime) keeps COMMON symbols global,
// so a runtime global written as a tentative definition (`T x;` at file
// scope with no initializer) is exported from every localized library
// archive on toolchains that emit tentative definitions as COMMON symbols,
// such as the macOS runtime build. Linux clang defaults to -fno-common, where
// the same declaration is an ordinary definition, so the library-multi
// symbol checks only catch it on macOS. This test compiles every runtime
// source with -fcommon, in the plain and worker configurations, and expects
// no COMMON symbols: every external runtime global needs an initializer.
test("runtime globals are not tentative definitions", async () => {
await mkdir(buildDir, { recursive: true });
const sources = (await readdir(srcDir))
.filter((name) => name.endsWith(".c") && !name.endsWith(".test.c"))
.sort();
const jobs = sources.flatMap((name) =>
[[], ["-DSCR_WORKERS"]].map((defines) => ({ name, defines })),
);
const common: string[] = [];
let next = 0;
const worker = async (): Promise<void> => {
while (next < jobs.length) {
const { name, defines } = jobs[next++]!;
const output = join(buildDir, `${name}${defines.join("")}.ll`);
await execFileAsync("clang", [
"-std=c11",
"-O0",
"-fcommon",
"-S",
"-emit-llvm",
...(process.platform === "linux" ? ["-D_GNU_SOURCE"] : []),
...defines,
`-I${join(vendorDir, "quickjs-ng")}`,
`-I${join(vendorDir, "mbedtls", "include")}`,
"-o",
output,
join(srcDir, name),
]);
for (const match of (await readFile(output, "utf8")).matchAll(/^@(\S+) = common /gm)) {
common.push(`${name}${defines.length > 0 ? ` (${defines.join(" ")})` : ""}: ${match[1]}`);
}
}
};
await Promise.all(Array.from({ length: Math.min(8, availableParallelism()) }, worker));
expect(common.sort()).toEqual([]);
});