List the executable's optional runtime units and defines in one table each

compileCInternal built the program's command line from a chain of
conditional spreads, one per optional runtime unit, and wrote the
optional -D flags out twice (the program build and the runtime-object
flags). RUNTIME_UNITS now lists the optional runtime inputs in link
order, one entry per unit: the sources and the condition that selects
them, or a small function for the entries that add vendor objects,
include directories or platform libraries. RUNTIME_DEFINES lists the
optional definitions once, and both flag lists use it. The command
lines keep their order, so the executables do not change.

The comment fragment before the process-events unit, left from a
removed zlib entry, is dropped.
This commit is contained in:
Chris Tate
2026-10-19 22:06:09 -05:00
parent 4bbf31b61f
commit 1d007024ef
+230 -148
View File
@@ -98,6 +98,218 @@ import { CacheInputsChangedError } from "./session.js";
import { ensureRuntimeObjects, stageRuntimeObjects, clearCcCaches } from "./runtime-objects.js";
import { localizeLibraryObjects } from "./object-merge.js";
/** What the runtime-unit table reads: the build options, the target driver,
* the units other options imply, and the vendor inputs resolved for this
* build. */
interface RuntimeUnitContext {
opts: CcOptions;
driver: ReturnType<typeof resolveCc>;
rt: (path: string) => string;
rtDir: string;
regex: boolean;
nativeFetch: boolean;
net: boolean;
http: boolean;
tls: boolean;
tlsCa: boolean;
cipher: boolean;
lreObjects: readonly string[];
zlibObjects: readonly string[];
tlsArchive: string | null;
}
/** An optional runtime input: runtime sources compiled when `when` holds,
* or command-line arguments computed from the build. */
interface RuntimeUnit {
when: (u: RuntimeUnitContext) => boolean | undefined;
sources: readonly string[];
args?: (u: RuntimeUnitContext) => readonly string[];
}
const unit = (when: RuntimeUnit["when"], ...sources: string[]): RuntimeUnit => ({ when, sources });
const unitArgs = (args: (u: RuntimeUnitContext) => readonly string[]): RuntimeUnit => ({
when: () => true,
sources: [],
args,
});
/** The optional runtime inputs, in link order. The order is part of the
* executable: a new unit goes next to the units it references. */
const RUNTIME_UNITS: readonly RuntimeUnit[] = [
unit(
(u) => u.opts.workers,
"scr_worker.c",
"scr_worker_events.c",
"scr_mailbox.c",
"scr_message.c",
),
unit((u) => u.opts.copying, "scr_copying.c"),
unit((u) => u.opts.fileHandle, "scr_file_handle.c"),
// win32 targets compile the libc-shim TU (stpcpy, arc4random_buf,
// gmtime_r, strcasestr — the _WIN32 block in scr_runtime.h declares
// them) and link advapi32 (the CSPRNG RtlGenRandom/SystemFunction036,
// GetUserNameA), iphlpapi (GetAdaptersAddresses behind
// os.networkInterfaces), and ws2_32 (inet_ntop there; the socket
// units ride the same import). Never present on the default path, so
// the historical line cannot change.
unitArgs((u) =>
targetPlatform(u.driver) === "win32"
? [u.rt(join(u.rtDir, "scr_win.c")), "-ladvapi32", "-liphlpapi", "-lws2_32"]
: [],
),
// musl deliberately has no libc-identification predefine; resolveCc's
// SCR_MUSL flag and this target-selected TU travel together.
unit((u) => isMuslTarget(u.driver), "scr_musl.c"),
// scr_glob.c (path.matchesGlob) compiles its segments through the
// regex unit and scr_idna.c (node:url's IDNA) normalizes through it,
// so both ride the same switch; section GC drops them from binaries
// that never use them.
unitArgs((u) =>
u.regex
? [
"-I",
vendorEngineDir(),
u.rt(join(u.rtDir, "scr_regex.c")),
u.rt(join(u.rtDir, "scr_glob.c")),
u.rt(join(u.rtDir, "scr_idna.c")),
...u.lreObjects,
]
: [],
),
unit((u) => u.opts.assert || u.regex || u.opts.symbol, "scr_assert.c"),
unit((u) => u.opts.inspect, "scr_inspect.c", "scr_console_native.c"),
unit((u) => u.opts.dynInvoke || u.opts.workers || u.nativeFetch, "scr_dyn_invoke.c"),
unit((u) => u.opts.dc, "scr_dc.c"),
unit((u) => u.opts.dynTier, "scr_js_value.c"),
unit((u) => u.opts.eventTarget || u.nativeFetch, "scr_event_target.c"),
unit(
(u) =>
u.opts.dynAsync ||
u.opts.dynInvoke ||
u.opts.workers ||
u.opts.dc ||
u.opts.fileHandle ||
u.nativeFetch,
"scr_async_dyn.c",
),
// The performance timeline (every perf_hooks user-timing call selects
// dynAsync, whose unit carries the timeline's buffer warning).
unit((u) => u.opts.dynAsync, "scr_perf.c"),
// Blob and File (every blob.* call selects dynAsync, whose unit boxes
// their reads as dyn promises).
unit((u) => u.opts.dynAsync, "scr_blob.c"),
// The zlib UNIT (scr_zlib.c) gates on zlib.* IR use; the LINK (system
// libz on the default host-clang build, vendored objects on Zig builds)
// also serves the native fetch's gzip decoder — spread exactly once.
unitArgs((u) =>
u.opts.zlib
? isZigDriver(u.driver)
? ["-I", vendorZlibDir(), u.rt(join(u.rtDir, "scr_zlib.c")), ...u.zlibObjects]
: [u.rt(join(u.rtDir, "scr_zlib.c"))]
: u.nativeFetch
? isZigDriver(u.driver)
? ["-I", vendorZlibDir(), ...u.zlibObjects]
: []
: [],
),
unit((u) => u.opts.events, "scr_events.c", "scr_readline.c"),
// readline's terminal mode: rl.terminal sets the regex unit it
// measures with.
unit((u) => u.opts.events && u.regex, "scr_readline_tty.c"),
unit((u) => u.opts.emitter || u.opts.workers, "scr_events_emitter.c"),
// The checked-dynamic HANDLE support unit (listener gate + runtime
// adapter closures): every referencing unit is one of the emitter or
// net families (http implies net), so handle-free binaries keep
// their exact size class.
unit((u) => u.opts.emitter || u.opts.workers || u.net, "scr_dyn_handle.c"),
unit((u) => u.opts.symbol, "scr_symbol.c"),
unit((u) => u.opts.assert && u.opts.bigint, "scr_bigint_assert.c"),
unit((u) => u.opts.qs, "scr_qs.c"),
// The legacy url unit references the regex, querystring and dyn-async
// units; the url.parse family's IR sets all three.
unit((u) => u.regex && u.opts.qs && u.opts.dynAsync, "scr_url_legacy.c"),
unit((u) => u.opts.parseArgs, "scr_util.c"),
unit((u) => u.opts.parseArgs && u.opts.symbol, "scr_util_compare.c"),
unit((u) => u.opts.parseArgs && u.opts.inspect && u.opts.dynAsync, "scr_util_style.c"),
unit(
(u) => u.opts.parseArgs && u.opts.inspect && u.opts.dynAsync && u.opts.dynInvoke && u.regex,
"scr_util_callable.c",
),
unit((u) => u.opts.stream, "scr_stream.c", "scr_fs_stream.c"),
// The zlib stream classes reference both the zlib engine and the
// stream unit.
unit((u) => u.opts.zlib && u.opts.stream, "scr_zlib_stream.c"),
unit((u) => u.opts.stream && u.opts.dynAsync, "scr_stream_ops.c"),
// FileHandle streams reference both the stream unit and FileHandle.
unit((u) => u.opts.stream && u.opts.fileHandle, "scr_fs_stream_fh.c"),
// The readiness-poller backends (scr_platform.h): kqueue on macOS/BSD,
// epoll on Linux, WSAPoll on Windows — each TU is empty off its
// platform, so all three link whenever a poller-using unit does and
// the others cost nothing (ws2_32 rides the unconditional win32 libs
// above).
unit((u) => u.net || u.opts.dgram, "scr_loop_kqueue.c", "scr_loop_epoll.c", "scr_loop_wsapoll.c"),
unit((u) => u.net, "scr_net.c"),
unit((u) => u.http, "scr_http.c"),
unit((u) => u.opts.http2, "scr_http2.c"),
unit((u) => u.opts.dgram, "scr_dgram.c"),
unit((u) => u.opts.watch, "scr_watch.c"),
unit((u) => u.opts.foreignFfi, "scr_ffi_queue.c"),
unit((u) => u.opts.nodeTest, "scr_test.c"),
unit((u) => u.opts.webcrypto, "scr_webcrypto.c"),
// The CA-store unit rides its own gate OR the tls one: scr_tls.c
// references its default-set override unconditionally.
unit((u) => u.tlsCa, "scr_tls_ca.c"),
unitArgs((u) =>
u.tlsArchive
? [
"-I",
join(vendorTlsDir(), "include"),
...(u.tls ? [u.rt(join(u.rtDir, "scr_tls.c"))] : []),
...(u.cipher ? [u.rt(join(u.rtDir, "scr_cipher.c"))] : []),
u.tlsArchive,
// mbedTLS's win32 entropy poll is BCryptGenRandom (bcrypt.h).
// The unconditional win32 libs above do not carry it.
// Never present on the default path, so the historical TLS
// link line cannot change.
...(targetPlatform(u.driver) === "win32" ? ["-lbcrypt"] : []),
]
: [],
),
// scr_tls_ca.c enumerates and PEM-encodes Windows system-store entries.
// This is independent of the mbedTLS archive: getCACertificates-only
// programs need crypt32 too, while TLS programs imply the CA unit.
unitArgs((u) => (u.tlsCa && targetPlatform(u.driver) === "win32" ? ["-lcrypt32"] : [])),
// Fetch uses the native network, TLS, and HTTP runtime.
unit((u) => u.nativeFetch, "scr_fetch.c"),
];
/** The arguments of the optional runtime inputs, in link order. */
function runtimeUnitArgs(u: RuntimeUnitContext): string[] {
return RUNTIME_UNITS.flatMap((entry) =>
entry.args
? [...entry.args(u)]
: entry.when(u)
? entry.sources.map((source) => u.rt(join(u.rtDir, source)))
: [],
);
}
/** The runtime's optional compile-time definitions, in command-line order.
* Both the program build and the runtime-object flags use this list. */
const RUNTIME_DEFINES: readonly {
define: string;
when: (opts: CcOptions) => boolean | undefined;
}[] = [
{ define: "SCR_TEXT_DECODER_LEGACY", when: (opts) => opts.textDecoderLegacy },
{ define: "SCR_WARNING_REPLACE", when: (opts) => opts.warningReplace },
{ define: "SCR_STDIO_EXTENDED", when: (opts) => opts.stdioExtended },
{ define: "SCR_YIN", when: (opts) => opts.youngNursery },
];
function runtimeDefineArgs(opts: CcOptions): string[] {
return RUNTIME_DEFINES.filter((d) => d.when(opts)).map((d) => `-D${d.define}`);
}
export async function compileCInternal(
opts: CcOptions,
cacheWarmOnly: boolean,
@@ -444,10 +656,7 @@ export async function compileCInternal(
? ["-O1", "-fsanitize=address", "-DSCR_RC_AUDIT"]
: [optimization === "dev" ? "-O0" : "-O2"]),
...executableSectionFlags.compile,
...(opts.textDecoderLegacy ? ["-DSCR_TEXT_DECODER_LEGACY"] : []),
...(opts.warningReplace ? ["-DSCR_WARNING_REPLACE"] : []),
...(opts.stdioExtended ? ["-DSCR_STDIO_EXTENDED"] : []),
...(opts.youngNursery ? ["-DSCR_YIN"] : []),
...runtimeDefineArgs(opts),
"-fno-math-errno",
// The runtime object model uses type-punned C: a hierarchy
// upcast is a raw pointer cast, so one object's header (rc, vt) and
@@ -465,146 +674,22 @@ export async function compileCInternal(
"-I",
rtDir,
...runtimeSources.map((f) => rt(join(rtDir, f))),
...(opts.workers
? ["scr_worker.c", "scr_worker_events.c", "scr_mailbox.c", "scr_message.c"].map((f) =>
rt(join(rtDir, f)),
)
: []),
...(opts.copying ? [rt(join(rtDir, "scr_copying.c"))] : []),
...(opts.fileHandle ? [rt(join(rtDir, "scr_file_handle.c"))] : []),
// win32 targets compile the libc-shim TU (stpcpy, arc4random_buf,
// gmtime_r, strcasestr — the _WIN32 block in scr_runtime.h declares
// them) and link advapi32 (the CSPRNG RtlGenRandom/SystemFunction036,
// GetUserNameA), iphlpapi (GetAdaptersAddresses behind
// os.networkInterfaces), and ws2_32 (inet_ntop there; the socket
// units ride the same import). Never present on the default path, so
// the historical line cannot change.
...(targetPlatform(driver) === "win32"
? [rt(join(rtDir, "scr_win.c")), "-ladvapi32", "-liphlpapi", "-lws2_32"]
: []),
// musl deliberately has no libc-identification predefine; resolveCc's
// SCR_MUSL flag and this target-selected TU travel together.
...(isMuslTarget(driver) ? [rt(join(rtDir, "scr_musl.c"))] : []),
// scr_glob.c (path.matchesGlob) compiles its segments through the
// regex unit and scr_idna.c (node:url's IDNA) normalizes through it,
// so both ride the same switch; section GC drops them from binaries
// that never use them.
...(regex
? [
"-I",
vendorEngineDir(),
rt(join(rtDir, "scr_regex.c")),
rt(join(rtDir, "scr_glob.c")),
rt(join(rtDir, "scr_idna.c")),
...lreObjects,
]
: []),
...(opts.assert || regex || opts.symbol ? [rt(join(rtDir, "scr_assert.c"))] : []),
...(opts.inspect
? [rt(join(rtDir, "scr_inspect.c")), rt(join(rtDir, "scr_console_native.c"))]
: []),
...(opts.dynInvoke || opts.workers || nativeFetch ? [rt(join(rtDir, "scr_dyn_invoke.c"))] : []),
...(opts.dc ? [rt(join(rtDir, "scr_dc.c"))] : []),
...(opts.dynTier === true ? [rt(join(rtDir, "scr_js_value.c"))] : []),
...(opts.eventTarget || nativeFetch ? [rt(join(rtDir, "scr_event_target.c"))] : []),
...(opts.dynAsync || opts.dynInvoke || opts.workers || opts.dc || opts.fileHandle || nativeFetch
? [rt(join(rtDir, "scr_async_dyn.c"))]
: []),
// The performance timeline (every perf_hooks user-timing call selects
// dynAsync, whose unit carries the timeline's buffer warning).
...(opts.dynAsync ? [rt(join(rtDir, "scr_perf.c"))] : []),
// Blob and File (every blob.* call selects dynAsync, whose unit boxes
// their reads as dyn promises).
...(opts.dynAsync ? [rt(join(rtDir, "scr_blob.c"))] : []),
// The zlib UNIT (scr_zlib.c) gates on zlib.* IR use; the LINK (system
// libz on the default host-clang build, vendored objects on Zig builds)
// also serves the native fetch's gzip decoder — spread exactly once.
...(opts.zlib
? isZigDriver(driver)
? ["-I", vendorZlibDir(), rt(join(rtDir, "scr_zlib.c")), ...zlibObjects]
: [rt(join(rtDir, "scr_zlib.c"))]
: nativeFetch
? isZigDriver(driver)
? ["-I", vendorZlibDir(), ...zlibObjects]
: []
: []),
// through the native installation hooks — the emitted main calls its
// installer exactly then.
...(opts.events ? [rt(join(rtDir, "scr_events.c")), rt(join(rtDir, "scr_readline.c"))] : []),
// readline's terminal mode: rl.terminal sets the regex unit it
// measures with.
...(opts.events && regex ? [rt(join(rtDir, "scr_readline_tty.c"))] : []),
...(opts.emitter || opts.workers ? [rt(join(rtDir, "scr_events_emitter.c"))] : []),
// The checked-dynamic HANDLE support unit (listener gate + runtime
// adapter closures): every referencing unit is one of the emitter or
// net families (http implies net), so handle-free binaries keep
// their exact size class.
...(opts.emitter || opts.workers || net ? [rt(join(rtDir, "scr_dyn_handle.c"))] : []),
...(opts.symbol ? [rt(join(rtDir, "scr_symbol.c"))] : []),
...(opts.assert && opts.bigint ? [rt(join(rtDir, "scr_bigint_assert.c"))] : []),
...(opts.qs ? [rt(join(rtDir, "scr_qs.c"))] : []),
// The legacy url unit references the regex, querystring and dyn-async
// units; the url.parse family's IR sets all three.
...(regex && opts.qs && opts.dynAsync ? [rt(join(rtDir, "scr_url_legacy.c"))] : []),
...(opts.parseArgs ? [rt(join(rtDir, "scr_util.c"))] : []),
...(opts.parseArgs && opts.symbol ? [rt(join(rtDir, "scr_util_compare.c"))] : []),
...(opts.parseArgs && opts.inspect && opts.dynAsync
? [rt(join(rtDir, "scr_util_style.c"))]
: []),
...(opts.parseArgs && opts.inspect && opts.dynAsync && opts.dynInvoke && regex
? [rt(join(rtDir, "scr_util_callable.c"))]
: []),
...(opts.stream ? [rt(join(rtDir, "scr_stream.c")), rt(join(rtDir, "scr_fs_stream.c"))] : []),
// The zlib stream classes reference both the zlib engine and the
// stream unit.
...(opts.zlib && opts.stream ? [rt(join(rtDir, "scr_zlib_stream.c"))] : []),
...(opts.stream && opts.dynAsync ? [rt(join(rtDir, "scr_stream_ops.c"))] : []),
// FileHandle streams reference both the stream unit and FileHandle.
...(opts.stream && opts.fileHandle ? [rt(join(rtDir, "scr_fs_stream_fh.c"))] : []),
// The readiness-poller backends (scr_platform.h): kqueue on macOS/BSD,
// epoll on Linux, WSAPoll on Windows — each TU is empty off its
// platform, so all three link whenever a poller-using unit does and
// the others cost nothing (ws2_32 rides the unconditional win32 libs
// above).
...(net || opts.dgram
? [
rt(join(rtDir, "scr_loop_kqueue.c")),
rt(join(rtDir, "scr_loop_epoll.c")),
rt(join(rtDir, "scr_loop_wsapoll.c")),
]
: []),
...(net ? [rt(join(rtDir, "scr_net.c"))] : []),
...(http ? [rt(join(rtDir, "scr_http.c"))] : []),
...((opts.http2 ?? false) ? [rt(join(rtDir, "scr_http2.c"))] : []),
...(opts.dgram ? [rt(join(rtDir, "scr_dgram.c"))] : []),
...(opts.watch ? [rt(join(rtDir, "scr_watch.c"))] : []),
...(opts.foreignFfi ? [rt(join(rtDir, "scr_ffi_queue.c"))] : []),
...(opts.nodeTest ? [rt(join(rtDir, "scr_test.c"))] : []),
...(opts.webcrypto ? [rt(join(rtDir, "scr_webcrypto.c"))] : []),
// The CA-store unit rides its own gate OR the tls one: scr_tls.c
// references its default-set override unconditionally.
...(tlsCa ? [rt(join(rtDir, "scr_tls_ca.c"))] : []),
...(tlsArchive
? [
"-I",
join(vendorTlsDir(), "include"),
...(tls ? [rt(join(rtDir, "scr_tls.c"))] : []),
...(cipher ? [rt(join(rtDir, "scr_cipher.c"))] : []),
tlsArchive,
// mbedTLS's win32 entropy poll is BCryptGenRandom (bcrypt.h).
// The unconditional win32 libs above do not carry it.
// Never present on the default path, so the historical TLS
// link line cannot change.
...(targetPlatform(driver) === "win32" ? ["-lbcrypt"] : []),
]
: []),
// scr_tls_ca.c enumerates and PEM-encodes Windows system-store entries.
// This is independent of the mbedTLS archive: getCACertificates-only
// programs need crypt32 too, while TLS programs imply the CA unit.
...(tlsCa && targetPlatform(driver) === "win32" ? ["-lcrypt32"] : []),
// Fetch uses the native network, TLS, and HTTP runtime.
...(nativeFetch ? [rt(join(rtDir, "scr_fetch.c"))] : []),
...runtimeUnitArgs({
opts,
driver,
rt,
rtDir,
regex,
nativeFetch,
net,
http,
tls,
tlsCa,
cipher,
lreObjects,
zlibObjects,
tlsArchive,
}),
// A .ll program TU (the LLVM backend) deliberately carries no target
// triple (byte-stable output; clang supplies the host/SCRIPTC_TARGET
// triple exactly as it does for .c) — silence the -Woverride-module
@@ -647,10 +732,7 @@ export async function compileCInternal(
? ["-O1", "-fsanitize=address", "-DSCR_RC_AUDIT"]
: [optimization === "dev" ? "-O0" : "-O2"]),
...executableSectionFlags.compile,
...(opts.textDecoderLegacy ? ["-DSCR_TEXT_DECODER_LEGACY"] : []),
...(opts.warningReplace ? ["-DSCR_WARNING_REPLACE"] : []),
...(opts.stdioExtended ? ["-DSCR_STDIO_EXTENDED"] : []),
...(opts.youngNursery ? ["-DSCR_YIN"] : []),
...runtimeDefineArgs(opts),
"-fno-math-errno",
"-fno-strict-aliasing", // the emitted object model type-puns — see buildArgs
"-Wno-deprecated-declarations",