From 094552c894501c8f4e42c21c54bdb7515f5a4222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 4 Aug 2026 09:07:30 +0200 Subject: [PATCH 1/2] perf(codegen): initialize only the two LLVM backends Perry can emit for `Target::initialize_all()` references every LLVM target's init symbol, so the static link pulls in all ~20 backends. Measured on the `perry` binary built with the llvm-inprocess feature: no feature 25.7 MB feature, initialize_all 185.9 MB (+160.1) feature, AArch64 + X86 98.9 MB (+73.2) -86.9 MB, 47% of the whole feature build, for backends nothing can reach. It was inkwell's convenient default in #7301 rather than a considered choice, and the feature was opt-in so nobody paid for it -- which matters now that making it standard is on the table. Perry's LLVM target surface is exactly two architectures: every triple the compile driver can produce is aarch64 (Apple platforms, Android, Linux gnu/musl/ohos, and watchOS's ILP32 arm64_32, still the AArch64 backend) or x86 (x86_64, x86_64h, i686). The lone riscv64gc string is a unit-test assertion in gc_map.rs, not an emission target, and wasm has its own crate that never reaches this backend. Fails loudly, never silently: an uninitialized triple errors at Target::from_triple. A new test asserts all ten driver-producible triples resolve, so adding an architecture without its backend fails in cargo-test rather than at a user's compile. Verified on the 81-module zod corpus: text, transport and native modes all compile, native output byte-identical to text, RS4GC probe 09 compiles. --- crates/perry-codegen/src/inprocess.rs | 52 ++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/crates/perry-codegen/src/inprocess.rs b/crates/perry-codegen/src/inprocess.rs index 8a1e4a912f..c48b73e058 100644 --- a/crates/perry-codegen/src/inprocess.rs +++ b/crates/perry-codegen/src/inprocess.rs @@ -41,7 +41,28 @@ static ANNOUNCE: Once = Once::new(); fn global_init(mllvm: &[String]) { LLVM_GLOBAL_INIT.call_once(|| { - Target::initialize_all(&InitializationConfig::default()); + // Only the backends Perry can actually emit for. `initialize_all()` + // references every LLVM target's init symbol, which makes the static + // link pull in all ~20 backends — measured at **+86.9 MB** on the + // `perry` binary (185.9 MB -> 98.9 MB), 47% of the whole feature + // build, for backends nothing can reach. It was inkwell's convenient + // default in #7301, not a considered choice; the feature was opt-in so + // nobody paid for it. + // + // Perry's LLVM target surface is exactly two architectures. Every + // triple the compile driver can produce is aarch64 (Apple platforms, + // Android, Linux gnu/musl/ohos, and watchOS's ILP32 `arm64_32`, which + // is still the AArch64 backend) or x86 (`x86_64`, `x86_64h`, `i686`). + // The lone `riscv64gc` string in the tree is a unit-test assertion in + // `gc_map.rs`, not an emission target, and wasm has its own crate + // (`perry-codegen-wasm`) that never reaches this backend. + // + // A triple outside this set fails loudly at `Target::from_triple` + // ("no LLVM target for ..."), so adding an architecture without + // initializing its backend is a hard error, never a silent fallback. + let cfg = InitializationConfig::default(); + Target::initialize_aarch64(&cfg); + Target::initialize_x86(&cfg); if !mllvm.is_empty() { let mut argv: Vec = vec![CString::new("perry-llvm-inprocess").unwrap()]; for flag in mllvm { @@ -382,6 +403,35 @@ entry: module.verify().expect("statepoint IR verifies"); } + /// The initialized backend set must cover every triple the compile driver + /// can produce. `initialize_all()` cost +86.9 MB of static link for ~18 + /// unreachable backends; this pins the replacement, so narrowing it + /// further — or adding a target without initializing its backend — fails + /// here rather than at a user's compile. + #[test] + fn every_supported_triple_resolves_to_an_initialized_backend() { + global_init(&[]); + for triple in [ + "arm64-apple-macosx", + "aarch64-apple-ios", + "aarch64-apple-watchos", + "aarch64-unknown-linux-gnu", + "aarch64-unknown-linux-musl", + "aarch64-linux-android", + "x86_64-apple-darwin", + "x86_64-unknown-linux-gnu", + "x86_64-pc-windows-msvc", + "i686-unknown-linux-gnu", + ] { + let t = TargetTriple::create(triple); + assert!( + Target::from_triple(&t).is_ok(), + "{triple} has no initialized LLVM backend — the compile driver \ + can emit this triple, so `global_init` must initialize it" + ); + } + } + /// #7327 CI regression: an empty CPU string makes LLVM pick `generic`, /// which on aarch64 is ARMv8.0 and has no FEAT_JSCVT — so the /// `llvm.aarch64.fjcvtzs` that codegen emits for any Apple arm64 triple From fe72135000a4b4b47e58f621daa43bb797d88fd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 4 Aug 2026 09:17:24 +0200 Subject: [PATCH 2/2] test: pin arm64_32-apple-watchos in the initialized-backend test CodeRabbit's catch. The comment claims watchOS's ILP32 arm64_32 is still the AArch64 backend and therefore covered, but the test never asserted it -- the one triple in the list whose coverage is not obvious from its name. Verified: it resolves. --- crates/perry-codegen/src/inprocess.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/perry-codegen/src/inprocess.rs b/crates/perry-codegen/src/inprocess.rs index c48b73e058..b838b66828 100644 --- a/crates/perry-codegen/src/inprocess.rs +++ b/crates/perry-codegen/src/inprocess.rs @@ -415,6 +415,7 @@ entry: "arm64-apple-macosx", "aarch64-apple-ios", "aarch64-apple-watchos", + "arm64_32-apple-watchos", "aarch64-unknown-linux-gnu", "aarch64-unknown-linux-musl", "aarch64-linux-android",