From 1d007024efad2f9a4134ccaba5641c2eb910e002 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Mon, 19 Oct 2026 22:06:09 -0500 Subject: [PATCH] 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. --- .../compiler/src/backend/native/executable.ts | 378 +++++++++++------- 1 file changed, 230 insertions(+), 148 deletions(-) diff --git a/packages/compiler/src/backend/native/executable.ts b/packages/compiler/src/backend/native/executable.ts index e632795c9..5ebb4796e 100644 --- a/packages/compiler/src/backend/native/executable.ts +++ b/packages/compiler/src/backend/native/executable.ts @@ -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; + 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",