diff --git a/CLAUDE.md b/CLAUDE.md index 61bf8dcebb..94d8b53bf4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co Perry is a native TypeScript compiler written in Rust that compiles TypeScript source code directly to native executables. It uses SWC for TypeScript parsing and LLVM for code generation. -**Current Version:** 0.5.1519 +**Current Version:** 0.5.1520 ## TypeScript Parity Status diff --git a/Cargo.lock b/Cargo.lock index ebcce9bbe9..2689c5821c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5598,7 +5598,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perry" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "anyhow", "base64 0.22.1", @@ -5660,7 +5660,7 @@ dependencies = [ [[package]] name = "perry-api-manifest" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "perry-dispatch", "serde", @@ -5668,7 +5668,7 @@ dependencies = [ [[package]] name = "perry-audio-miniaudio" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "cc", "libc", @@ -5677,7 +5677,7 @@ dependencies = [ [[package]] name = "perry-codegen" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "anyhow", "inkwell", @@ -5694,7 +5694,7 @@ dependencies = [ [[package]] name = "perry-codegen-arkts" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "anyhow", "perry-hir", @@ -5702,7 +5702,7 @@ dependencies = [ [[package]] name = "perry-codegen-glance" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "anyhow", "perry-hir", @@ -5710,7 +5710,7 @@ dependencies = [ [[package]] name = "perry-codegen-js" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "anyhow", "perry-dispatch", @@ -5719,7 +5719,7 @@ dependencies = [ [[package]] name = "perry-codegen-swiftui" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "anyhow", "perry-hir", @@ -5727,7 +5727,7 @@ dependencies = [ [[package]] name = "perry-codegen-wasm" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "anyhow", "base64 0.22.1", @@ -5739,7 +5739,7 @@ dependencies = [ [[package]] name = "perry-codegen-wear-tiles" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "anyhow", "perry-hir", @@ -5747,7 +5747,7 @@ dependencies = [ [[package]] name = "perry-container-compose" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "anyhow", "async-trait", @@ -5776,14 +5776,14 @@ dependencies = [ [[package]] name = "perry-container-e2e" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "anyhow", ] [[package]] name = "perry-diagnostics" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "serde", "serde_json", @@ -5791,7 +5791,7 @@ dependencies = [ [[package]] name = "perry-dispatch" -version = "0.5.1519" +version = "0.5.1520" [[package]] name = "perry-doc-fixture-my-bindings" @@ -5802,7 +5802,7 @@ dependencies = [ [[package]] name = "perry-doc-tests" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "anyhow", "clap", @@ -5817,7 +5817,7 @@ dependencies = [ [[package]] name = "perry-ext-ads" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "block2", "objc2", @@ -5827,7 +5827,7 @@ dependencies = [ [[package]] name = "perry-ext-argon2" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "argon2", "perry-ffi", @@ -5836,7 +5836,7 @@ dependencies = [ [[package]] name = "perry-ext-axios" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "perry-ffi", "reqwest", @@ -5845,7 +5845,7 @@ dependencies = [ [[package]] name = "perry-ext-bcrypt" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "bcrypt", "perry-ffi", @@ -5853,7 +5853,7 @@ dependencies = [ [[package]] name = "perry-ext-better-sqlite3" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "perry-ffi", "rusqlite", @@ -5861,7 +5861,7 @@ dependencies = [ [[package]] name = "perry-ext-cheerio" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "perry-ffi", "scraper", @@ -5869,7 +5869,7 @@ dependencies = [ [[package]] name = "perry-ext-commander" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "perry-ffi", "perry-runtime", @@ -5877,7 +5877,7 @@ dependencies = [ [[package]] name = "perry-ext-cron" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "chrono", "cron", @@ -5887,7 +5887,7 @@ dependencies = [ [[package]] name = "perry-ext-dayjs" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "chrono", "perry-ffi", @@ -5895,7 +5895,7 @@ dependencies = [ [[package]] name = "perry-ext-decimal" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "perry-ffi", "rust_decimal", @@ -5903,7 +5903,7 @@ dependencies = [ [[package]] name = "perry-ext-dotenv" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "perry-ffi", "serde_json", @@ -5911,7 +5911,7 @@ dependencies = [ [[package]] name = "perry-ext-ethers" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "perry-ffi", "rand 0.10.1", @@ -5919,7 +5919,7 @@ dependencies = [ [[package]] name = "perry-ext-events" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "perry-ffi", "perry-runtime", @@ -5927,14 +5927,14 @@ dependencies = [ [[package]] name = "perry-ext-exponential-backoff" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-fastify" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "bytes", "http-body-util", @@ -5952,7 +5952,7 @@ dependencies = [ [[package]] name = "perry-ext-fetch" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "bytes", "lazy_static", @@ -5965,7 +5965,7 @@ dependencies = [ [[package]] name = "perry-ext-http" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "base64 0.22.1", "bytes", @@ -5990,7 +5990,7 @@ dependencies = [ [[package]] name = "perry-ext-ioredis" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "lazy_static", "perry-ffi", @@ -6000,7 +6000,7 @@ dependencies = [ [[package]] name = "perry-ext-jsonwebtoken" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "base64 0.22.1", "jsonwebtoken", @@ -6011,7 +6011,7 @@ dependencies = [ [[package]] name = "perry-ext-lru-cache" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "lru", "perry-ffi", @@ -6020,7 +6020,7 @@ dependencies = [ [[package]] name = "perry-ext-moment" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "chrono", "perry-ffi", @@ -6028,7 +6028,7 @@ dependencies = [ [[package]] name = "perry-ext-mongodb" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "bson", "futures-util", @@ -6040,7 +6040,7 @@ dependencies = [ [[package]] name = "perry-ext-mysql2" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "chrono", "perry-ffi", @@ -6050,7 +6050,7 @@ dependencies = [ [[package]] name = "perry-ext-nanoid" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "nanoid", "perry-ffi", @@ -6059,7 +6059,7 @@ dependencies = [ [[package]] name = "perry-ext-net" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "bytes", "perry-ffi", @@ -6072,7 +6072,7 @@ dependencies = [ [[package]] name = "perry-ext-node-forge" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "const-oid 0.10.2", "der 0.8.0", @@ -6091,7 +6091,7 @@ dependencies = [ [[package]] name = "perry-ext-nodemailer" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "lettre", "perry-ffi", @@ -6101,7 +6101,7 @@ dependencies = [ [[package]] name = "perry-ext-parcel-watcher" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "fancy-regex", "notify", @@ -6113,7 +6113,7 @@ dependencies = [ [[package]] name = "perry-ext-pdf" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "perry-ffi", "printpdf", @@ -6121,7 +6121,7 @@ dependencies = [ [[package]] name = "perry-ext-pg" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "perry-ffi", "sqlx", @@ -6130,7 +6130,7 @@ dependencies = [ [[package]] name = "perry-ext-ratelimit" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "governor", "perry-ffi", @@ -6138,7 +6138,7 @@ dependencies = [ [[package]] name = "perry-ext-sharp" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "fast_image_resize", "image", @@ -6148,7 +6148,7 @@ dependencies = [ [[package]] name = "perry-ext-streams" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "lazy_static", "perry-ffi", @@ -6157,7 +6157,7 @@ dependencies = [ [[package]] name = "perry-ext-typescript" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "perry-ffi", "serde", @@ -6173,7 +6173,7 @@ dependencies = [ [[package]] name = "perry-ext-undici" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "perry-ffi", "perry-runtime", @@ -6182,7 +6182,7 @@ dependencies = [ [[package]] name = "perry-ext-uuid" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "perry-ffi", "uuid", @@ -6190,7 +6190,7 @@ dependencies = [ [[package]] name = "perry-ext-validator" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "perry-ffi", "regex", @@ -6200,7 +6200,7 @@ dependencies = [ [[package]] name = "perry-ext-ws" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "futures-util", "lazy_static", @@ -6213,7 +6213,7 @@ dependencies = [ [[package]] name = "perry-ext-zlib" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "brotli", "flate2", @@ -6223,7 +6223,7 @@ dependencies = [ [[package]] name = "perry-ffi" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "dashmap", "once_cell", @@ -6232,7 +6232,7 @@ dependencies = [ [[package]] name = "perry-hir" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "anyhow", "perry-api-manifest", @@ -6250,7 +6250,7 @@ dependencies = [ [[package]] name = "perry-parser" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "anyhow", "perry-diagnostics", @@ -6262,7 +6262,7 @@ dependencies = [ [[package]] name = "perry-runtime" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "anyhow", "base64 0.22.1", @@ -6304,14 +6304,14 @@ dependencies = [ [[package]] name = "perry-runtime-static" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "perry-runtime", ] [[package]] name = "perry-stdlib" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "aes 0.8.4", "aes 0.9.1", @@ -6406,14 +6406,14 @@ dependencies = [ [[package]] name = "perry-stdlib-static" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "perry-stdlib", ] [[package]] name = "perry-transform" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "anyhow", "perry-hir", @@ -6422,14 +6422,14 @@ dependencies = [ [[package]] name = "perry-ui" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "perry-ui-model", ] [[package]] name = "perry-ui-android" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "base64 0.22.1", "itoa", @@ -6447,7 +6447,7 @@ dependencies = [ [[package]] name = "perry-ui-geisterhand" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "rand 0.10.1", "serde", @@ -6457,7 +6457,7 @@ dependencies = [ [[package]] name = "perry-ui-gtk4" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "base64 0.22.1", "cairo-rs 0.22.0", @@ -6480,7 +6480,7 @@ dependencies = [ [[package]] name = "perry-ui-ios" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "base64 0.22.1", "block2", @@ -6497,7 +6497,7 @@ dependencies = [ [[package]] name = "perry-ui-macos" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "base64 0.22.1", "block2", @@ -6513,7 +6513,7 @@ dependencies = [ [[package]] name = "perry-ui-model" -version = "0.5.1519" +version = "0.5.1520" [[package]] name = "perry-ui-test" @@ -6524,11 +6524,11 @@ dependencies = [ [[package]] name = "perry-ui-testkit" -version = "0.5.1519" +version = "0.5.1520" [[package]] name = "perry-ui-tvos" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "base64 0.22.1", "block2", @@ -6545,7 +6545,7 @@ dependencies = [ [[package]] name = "perry-ui-visionos" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "base64 0.22.1", "block2", @@ -6562,7 +6562,7 @@ dependencies = [ [[package]] name = "perry-ui-watchos" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "block2", "libc", @@ -6576,7 +6576,7 @@ dependencies = [ [[package]] name = "perry-ui-windows" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "base64 0.22.1", "libc", @@ -6595,14 +6595,14 @@ dependencies = [ [[package]] name = "perry-ui-windows-winui" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "perry-ui-windows", ] [[package]] name = "perry-updater" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "anyhow", "base64 0.22.1", @@ -6618,7 +6618,7 @@ dependencies = [ [[package]] name = "perry-wasm-host" -version = "0.5.1519" +version = "0.5.1520" dependencies = [ "wasmi", ] diff --git a/Cargo.toml b/Cargo.toml index 2623e8a948..f6b934f2fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -316,7 +316,7 @@ codegen-units = 16 codegen-units = 16 [workspace.package] -version = "0.5.1519" +version = "0.5.1520" edition = "2021" license = "MIT" repository = "https://github.com/PerryTS/perry" diff --git a/changelog.d/8627-report-size-duplicate-body-accuracy.md b/changelog.d/8627-report-size-duplicate-body-accuracy.md new file mode 100644 index 0000000000..209c5ab2c8 --- /dev/null +++ b/changelog.d/8627-report-size-duplicate-body-accuracy.md @@ -0,0 +1,3 @@ +### Fixed + +- `perry compile --report-size` (from [#8579](https://github.com/PerryTS/perry/pull/8579)): duplicate function/static-data body detection now keys directly on the exact byte slice instead of an FNV-1a hash + size check — a hash collision at the same size could previously fabricate a false duplicate finding (caught in CodeRabbit review, fixed before this PR rather than just reworded, since the fix was as cheap as the hash was). Also corrected the "duplicate crate instance" finding's framing: verified directly (md5 + objdump on the extracted archive members) that while `perry-runtime`/`perry-stdlib` do redundantly compile some shared dependencies (`gimli` confirmed byte-identical across their two separate `.a` archives), a successful link only pulls one physical copy per symbol — so the attributed bytes are real, in-use code in the shipped binary, not a duplicate sitting in it twice. The report and its "Suggestions" section now say this explicitly (renamed `duplicate_crate_versions` to `duplicate_crate_instances` in the JSON schema, and `estimated_bytes` for this finding is `0` rather than a size claim) instead of overclaiming a shipped-binary-size win that isn't there. diff --git a/crates/perry/src/commands/compile/size_report.rs b/crates/perry/src/commands/compile/size_report.rs index 6bf5481927..9a7874878a 100644 --- a/crates/perry/src/commands/compile/size_report.rs +++ b/crates/perry/src/commands/compile/size_report.rs @@ -18,7 +18,7 @@ //! (static-archive compile, then a raw `cc`/`ld` link of those archives plus //! LLVM-emitted object code). This is a symbol-table-only view of whatever //! made it into the final link — code/data attribution, duplicate function -//! bodies, duplicate crate versions, generic-monomorphization cost, and a +//! bodies, duplicate crate instances, generic-monomorphization cost, and a //! few named cost patterns (panics, `Debug`/`Display` formatting, vtables). use std::collections::BTreeMap; @@ -67,13 +67,18 @@ struct DuplicateBody { symbols: Vec, } +/// Same crate name compiled independently more than once (proof, not +/// inference — a distinct v0-mangling disambiguator hash per build, unlike +/// reading `Cargo.lock`, which only proves a version is *resolvable*). +/// +/// This is a compile-time / intermediate-archive-size finding, not a +/// shipped-binary-size one: a successful link proves each hash's content is +/// linked at most once (the linker errors on a true duplicate-symbol +/// inclusion), so `total_bytes` is real, in-use code in the final binary — +/// not bytes recoverable by deduplicating it there. #[derive(Serialize)] -struct DuplicateCrateVersion { +struct DuplicateCrateInstance { crate_name: String, - /// The v0-mangling disambiguator hash for each distinct build of this - /// crate name actually linked into the binary — proof, not inference, - /// that more than one copy is present (unlike reading `Cargo.lock`, - /// which only proves more than one version is *resolvable*). hashes: Vec, total_bytes: u64, } @@ -105,7 +110,7 @@ struct SizeReport { largest: Vec, generic_families: Vec, duplicate_bodies: Vec, - duplicate_crate_versions: Vec, + duplicate_crate_instances: Vec, patterns: Vec, suggestions: Vec, } @@ -272,7 +277,7 @@ fn build_report(exe_path: &Path) -> anyhow::Result { let mut family_totals: BTreeMap<(String, String), (usize, u64)> = BTreeMap::new(); let mut crate_hashes: BTreeMap> = BTreeMap::new(); let mut crate_hash_bytes: BTreeMap<(String, String), u64> = BTreeMap::new(); - let mut body_hashes: BTreeMap> = BTreeMap::new(); // hash -> [(symbol, size)] + let mut body_bytes: BTreeMap<&[u8], Vec<(String, u64)>> = BTreeMap::new(); // exact bytes -> [(symbol, size)] let mut pattern_totals: BTreeMap<&'static str, (u64, usize)> = BTreeMap::new(); for sym in &raw { @@ -321,13 +326,12 @@ fn build_report(exe_path: &Path) -> anyhow::Result { if let Ok(section) = file.section_by_index(object::SectionIndex(sym.section as usize)) { if let Ok(Some(bytes)) = section.data_range(sym.address, sym.size) { - // FNV-1a: fast, dependency-free, and collisions here only cost - // a false "these might be duplicates" that the exact byte - // slices grouped under the same hash would still need to - // agree on — good enough for a diagnostic report. - let hash = fnv1a(bytes); - body_hashes - .entry(hash) + // Keyed on the exact byte slice (`&[u8]` is `Ord`), not a + // hash of it — a duplicate-body finding is a claim serious + // enough that a hash collision must not be able to fabricate + // one. + body_bytes + .entry(bytes) .or_default() .push((demangled.clone(), sym.size)); } @@ -359,13 +363,9 @@ fn build_report(exe_path: &Path) -> anyhow::Result { generic_families.sort_by_key(|a| std::cmp::Reverse(a.total_bytes)); generic_families.truncate(REPORT_TOP_FAMILIES); - let mut duplicate_bodies: Vec = body_hashes + let mut duplicate_bodies: Vec = body_bytes .into_values() .filter(|group| group.len() > 1) - // Same-hash groups can still differ in size if two DIFFERENT-length - // symbols' byte ranges happened to collide in the (rare) FNV-1a sense; - // require the sizes to actually match before calling it a duplicate. - .filter(|group| group.iter().all(|(_, size)| *size == group[0].1)) .map(|group| { let size = group[0].1; let copies = group.len(); @@ -380,7 +380,7 @@ fn build_report(exe_path: &Path) -> anyhow::Result { duplicate_bodies.sort_by_key(|a| std::cmp::Reverse(a.wasted_bytes)); duplicate_bodies.truncate(REPORT_TOP_DUPLICATES); - let mut duplicate_crate_versions: Vec = crate_hashes + let mut duplicate_crate_instances: Vec = crate_hashes .into_iter() .filter(|(_, hashes)| hashes.len() > 1) .map(|(crate_name, hashes)| { @@ -393,14 +393,14 @@ fn build_report(exe_path: &Path) -> anyhow::Result { .unwrap_or(0) }) .sum(); - DuplicateCrateVersion { + DuplicateCrateInstance { crate_name, hashes: hashes.into_iter().collect(), total_bytes, } }) .collect(); - duplicate_crate_versions.sort_by_key(|a| std::cmp::Reverse(a.total_bytes)); + duplicate_crate_instances.sort_by_key(|a| std::cmp::Reverse(a.total_bytes)); let mut patterns: Vec = pattern_totals .into_iter() @@ -409,7 +409,7 @@ fn build_report(exe_path: &Path) -> anyhow::Result { patterns.sort_by_key(|a| std::cmp::Reverse(a.bytes)); let suggestions = build_suggestions( - &duplicate_crate_versions, + &duplicate_crate_instances, &generic_families, &duplicate_bodies, &patterns, @@ -438,36 +438,47 @@ fn build_report(exe_path: &Path) -> anyhow::Result { largest: largest_all, generic_families, duplicate_bodies, - duplicate_crate_versions, + duplicate_crate_instances, patterns, suggestions, }) } fn build_suggestions( - duplicate_crate_versions: &[DuplicateCrateVersion], + duplicate_crate_instances: &[DuplicateCrateInstance], generic_families: &[GenericFamily], duplicate_bodies: &[DuplicateBody], patterns: &[PatternTotal], ) -> Vec { let mut out = Vec::new(); - for dup in duplicate_crate_versions { + for dup in duplicate_crate_instances { out.push(Suggestion { - kind: "duplicate-crate-instance", + kind: "duplicate-compile-crate-instance", summary: format!( - "`{}` is linked {} times under different builds ({}) — Cargo.lock likely already \ - agrees on one version; this is `perry-runtime`/`perry-stdlib` each independently \ - compiling their own copy as separate `cargo build` invocations, so identical code \ - doesn't dedupe across the resulting `.a` archives. Extending Perry's existing \ - archive-dedup pass (today scoped to `dedup_runtime_for_tier3`/`dedup_stdlib_for_tier3`) \ - to the default build path would recover up to {}", + "`{}` is compiled independently {} times ({}) — once each inside \ + `perry-runtime`'s and `perry-stdlib`'s separate `cargo build` invocations, not \ + a Cargo.lock version conflict. This is redundant COMPILE work and bloats the \ + intermediate `.a` archives; it is NOT necessarily {} of recoverable shipped-\ + binary size — a successful link proves each hash's content is linked at most \ + once (the linker errors on a true duplicate-symbol inclusion), so every byte \ + attributed here is real, in-use code in this binary, not waste sitting twice in \ + it. Extending Perry's existing archive-dedup pass (today scoped to \ + `dedup_runtime_for_tier3`/`dedup_stdlib_for_tier3`) to the default build path \ + would speed up incremental/auto-optimize builds and shrink the intermediate \ + archives; whether it also shrinks a given shipped binary depends on whether that \ + binary's link happens to need both hash-variants — a separate, per-binary claim \ + this report does not make.", dup.crate_name, dup.hashes.len(), dup.hashes.join(", "), human_bytes(dup.total_bytes), ), - estimated_bytes: dup.total_bytes, + // Deliberately not `dup.total_bytes`: that is real, in-use code + // in THIS binary (see summary), not a recoverable-bytes claim — + // giving it a nonzero estimate here would misrank it against + // suggestions that genuinely shrink the shipped binary. + estimated_bytes: 0, }); } @@ -558,17 +569,6 @@ const PATTERNS: &[PatternMatcher] = &[ }), ]; -/// FNV-1a — fast, dependency-free, good enough to bucket candidate duplicate -/// bodies before the exact-size check in `build_report` confirms them. -fn fnv1a(bytes: &[u8]) -> u64 { - let mut hash: u64 = 0xcbf29ce484222325; - for &b in bytes { - hash ^= b as u64; - hash = hash.wrapping_mul(0x100000001b3); - } - hash -} - /// Demangle a Rust symbol name. `rustc_demangle` returns non-Rust input /// unchanged — the normal case for libc/system symbols — and `crate_of` /// below buckets those as `native/other`. @@ -580,7 +580,7 @@ fn demangle(name: &str) -> String { /// segment) and, when present, the v0-mangling disambiguator hash right /// after it (`crate_name[16 hex digits]`). Two symbols from the SAME crate /// NAME but DIFFERENT hashes are proof two separate builds of that crate -/// both made it into the final link — see `DuplicateCrateVersion`. +/// both made it into the final link — see `DuplicateCrateInstance`. /// /// `::method` / `::method` associated-fn forms put the /// crate name one level in; the leading `<` is stripped before reading it. @@ -716,14 +716,17 @@ fn render_markdown(report: &SizeReport) -> String { )); } - if !report.duplicate_crate_versions.is_empty() { - out.push_str("\n## Duplicate crate versions\n\n"); + if !report.duplicate_crate_instances.is_empty() { + out.push_str("\n## Duplicate crate instances\n\n"); out.push_str( - "Same crate name linked more than once under a different build (proven from the \ - symbol table's own disambiguator hash, not inferred from `Cargo.lock`).\n\n", + "Same crate name compiled independently more than once (proven from the symbol \ + table's own disambiguator hash, not inferred from `Cargo.lock`). This is a \ + compile-time / intermediate-archive-size finding: a successful link proves each \ + hash's content is linked at most once, so the `Total` column is real, in-use code \ + in this binary — not bytes recoverable by deduplicating it here.\n\n", ); out.push_str("| Total | Copies | Crate |\n|---|---|---|\n"); - for dup in &report.duplicate_crate_versions { + for dup in &report.duplicate_crate_instances { out.push_str(&format!( "| {} | {} | `{}` |\n", human_bytes(dup.total_bytes), @@ -884,12 +887,6 @@ mod tests { ); } - #[test] - fn fnv1a_is_deterministic_and_distinguishes_different_bytes() { - assert_eq!(fnv1a(b"hello"), fnv1a(b"hello")); - assert_ne!(fnv1a(b"hello"), fnv1a(b"world")); - } - #[test] fn human_bytes_picks_the_right_unit() { assert_eq!(human_bytes(512), "512 B");