diff --git a/changelog.d/7010-proc-ipc-gate.md b/changelog.d/7010-proc-ipc-gate.md new file mode 100644 index 0000000000..51de5b09ff --- /dev/null +++ b/changelog.d/7010-proc-ipc-gate.md @@ -0,0 +1 @@ +**Binary size:** `process.send`/`disconnect`/`connected`/`channel` now sit behind a default-on `proc-ipc` feature the compiler enables on any of those property tokens. The IPC send path JSON-serializes every message, so the always-live process-namespace property lookup was statically pinning the whole JSON serializer into binaries that never touch IPC. Detection is deliberately broad (any `send`/`disconnect`/`connected`/`channel` mention enables it) because a miss would leave `process.send` undefined at runtime. Hello world: −16.5 KB (4,245,216 → 4,228,680 bytes). diff --git a/crates/perry-runtime/Cargo.toml b/crates/perry-runtime/Cargo.toml index 5bdeecd7e3..513446f015 100644 --- a/crates/perry-runtime/Cargo.toml +++ b/crates/perry-runtime/Cargo.toml @@ -22,7 +22,7 @@ crate-type = ["rlib"] # actually needs (see optimized_libs.rs), so the heavy subsystems below # (regex engine, Temporal, URL/IDNA, normalize, segmenter) are *opt-in per # app* and absent from binaries that never use them. -default = ["full", "regex-engine", "temporal", "url-engine", "string-normalize", "intl-segmenter", "intl-namespace", "global-math", "global-json", "global-reflect", "global-atomics", "global-url", "global-text", "global-websocket", "global-webcrypto", "global-webfetch", "intl-locale", "intl-datetime", "diagnostics", "mod-dgram", "mod-http2-constants", "dyn-eval", "keepalive-anchors", "alloc-mimalloc"] +default = ["full", "regex-engine", "temporal", "url-engine", "string-normalize", "intl-segmenter", "intl-namespace", "global-math", "global-json", "global-reflect", "global-atomics", "global-url", "global-text", "global-websocket", "global-webcrypto", "global-webfetch", "proc-ipc", "intl-locale", "intl-datetime", "diagnostics", "mod-dgram", "mod-http2-constants", "dyn-eval", "keepalive-anchors", "alloc-mimalloc"] # Compile the ~490 `#[used]` keep-alive anchor statics (`KEEP_*`, `keep!`, # `K*`/`G*` fn-pointer statics) that pin codegen-facing `#[no_mangle]` entry # points as dead-strip roots. They exist for the whole-program bitcode-LTO @@ -155,6 +155,13 @@ global-text = [] global-websocket = [] global-webcrypto = [] global-webfetch = [] +# `process.send` / `disconnect` / `connected` / `channel` — the child-process +# IPC channel. The send path JSON-serializes every message, so the always-live +# process-namespace property lookup statically pinned the whole `crate::json` +# serializer (~84 KB) into binaries that never touch IPC. The compiler enables +# this on any of the four property tokens (over-approximate: a missed +# detection would make `process.send` undefined, so err toward enabling). +proc-ipc = [] # `Intl.getCanonicalLocales` / `*.supportedLocalesOf` BCP-47 (UTS #35) language-tag # canonicalization via `icu_locale_core` (the data-free structural parser — case # normalization, variant ordering, extension well-formedness, UTS35 rejection of diff --git a/crates/perry-runtime/src/process/ipc.rs b/crates/perry-runtime/src/process/ipc.rs index 2a938cfbad..f133e83118 100644 --- a/crates/perry-runtime/src/process/ipc.rs +++ b/crates/perry-runtime/src/process/ipc.rs @@ -202,6 +202,17 @@ fn ipc_is_connected() -> bool { state.available && state.connected } +/// IPC-backed `process` properties. Behind `proc-ipc` (default-on, compiler +/// detected): with the feature off the channel can never be used by this +/// program, so returning `None` keeps the always-live process-namespace +/// lookup from statically pinning the IPC send path and, through it, the JSON +/// serializer. +#[cfg(not(feature = "proc-ipc"))] +pub(crate) fn process_ipc_property(_name: &str) -> Option { + None +} + +#[cfg(feature = "proc-ipc")] pub(crate) fn process_ipc_property(name: &str) -> Option { match name { "send" | "disconnect" | "connected" | "channel" => {} diff --git a/crates/perry/src/commands/compile/collect_modules/feature_detect.rs b/crates/perry/src/commands/compile/collect_modules/feature_detect.rs index 9f939f6b81..2c9fd1ce0b 100644 --- a/crates/perry/src/commands/compile/collect_modules/feature_detect.rs +++ b/crates/perry/src/commands/compile/collect_modules/feature_detect.rs @@ -363,6 +363,18 @@ pub(super) fn detect_optional_feature_usage( { ctx.uses_global_webfetch = true; } + // `process` IPC channel properties. Bare-token matching on purpose: + // the property name reaches the runtime as a string, so any `send` / + // `disconnect` / `connected` / `channel` mention enables the path. A + // miss would make `process.send` undefined at runtime, so this errs + // heavily toward enabling. + if hir_debug.contains("\"send\"") + || hir_debug.contains("\"disconnect\"") + || hir_debug.contains("\"connected\"") + || hir_debug.contains("\"channel\"") + { + ctx.uses_proc_ipc = true; + } // `Intl.getCanonicalLocales(...)` / `Intl.*.supportedLocalesOf(...)` gate // `perry-runtime/intl-locale` (`icu_locale_core` BCP-47 canonicalization). // Both lower with the method name as a `property` token. diff --git a/crates/perry/src/commands/compile/optimized_libs/freshness.rs b/crates/perry/src/commands/compile/optimized_libs/freshness.rs index cf2470591b..c4e3e7e2be 100644 --- a/crates/perry/src/commands/compile/optimized_libs/freshness.rs +++ b/crates/perry/src/commands/compile/optimized_libs/freshness.rs @@ -88,7 +88,7 @@ pub(crate) fn auto_optimized_cache_key( ) -> String { let target_str = target.unwrap_or("host"); format!( - "{}|{}|{}|wasm={}|regex={}|temporal={}|ee={}|url={}|norm={}|seg={}|loc={}|intlns={}|gns={}{}{}{}{}{}{}{}{}|diag={}|dgram={}|http2={}|dyneval={}|sizeopt={}|anchors={}|v={}", + "{}|{}|{}|wasm={}|regex={}|temporal={}|ee={}|url={}|norm={}|seg={}|loc={}|intlns={}|gns={}{}{}{}{}{}{}{}{}{}|diag={}|dgram={}|http2={}|dyneval={}|sizeopt={}|anchors={}|v={}", feature_arg, panic_abort_safe, target_str, @@ -110,6 +110,7 @@ pub(crate) fn auto_optimized_cache_key( ctx.uses_global_websocket, ctx.uses_global_webcrypto, ctx.uses_global_webfetch, + ctx.uses_proc_ipc, ctx.uses_diagnostics, ctx.uses_dgram, // HTTP/2 imports and dynamic builtin resolution pull in @@ -196,6 +197,7 @@ pub(crate) fn auto_optimized_cross_features( (ctx.uses_global_websocket, "global-websocket"), (ctx.uses_global_webcrypto, "global-webcrypto"), (ctx.uses_global_webfetch, "global-webfetch"), + (ctx.uses_proc_ipc, "proc-ipc"), ] { if used || dynamic_code { cross_features.push(format!("perry-runtime/{feat}")); diff --git a/crates/perry/src/commands/compile/types.rs b/crates/perry/src/commands/compile/types.rs index 5ce05b181a..0723c87a1f 100644 --- a/crates/perry/src/commands/compile/types.rs +++ b/crates/perry/src/commands/compile/types.rs @@ -700,6 +700,8 @@ pub struct CompilationContext { pub uses_global_websocket: bool, pub uses_global_webcrypto: bool, pub uses_global_webfetch: bool, + /// `process.send`/`disconnect`/`connected`/`channel` — gates `proc-ipc`. + pub uses_proc_ipc: bool, pub uses_intl_locale: bool, /// Whether any TS module localizes a date/time — `Intl.DateTimeFormat`, or /// `Date.prototype.toLocale{,Date,Time}String`. Gates @@ -1058,6 +1060,7 @@ impl CompilationContext { uses_global_websocket: false, uses_global_webcrypto: false, uses_global_webfetch: false, + uses_proc_ipc: false, uses_intl_locale: false, uses_intl_datetime: false, uses_diagnostics: false,