Claude Code Plugins

Community-maintained marketplace

Feedback

analyze-rust-code-size

@bearcove/dodeca
94
0

Analyze Rust binary code size and identify bloat from monomorphization, large functions, and macro expansion using cargo-llvm-lines and rustc flags. Use when binary is too large or compile times are slow.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name analyze-rust-code-size
description Analyze Rust binary code size and identify bloat from monomorphization, large functions, and macro expansion using cargo-llvm-lines and rustc flags. Use when binary is too large or compile times are slow.

Analyze Rust Code Size & Monomorphization

Find code bloat, excessive monomorphization, and macro expansion issues that cause large binaries and slow compile times.

CRITICAL WORKFLOW RULE

ALWAYS capture full output to a file FIRST, THEN analyze it afterwards.

WRONG (wasteful, loses data if grep fails):

cargo llvm-lines --bin mybin | grep "something"  # BAD! Expensive command piped to grep
rustc -Z macro-stats ... 2>&1 | head -50         # BAD! You lose most of the data

CORRECT (efficient, preserves all data):

# Step 1: Capture EVERYTHING to a file
cargo llvm-lines --bin mybin > /tmp/llvm-lines.txt 2>&1

# Step 2: Analyze the file however you want
grep "picante" /tmp/llvm-lines.txt
head -100 /tmp/llvm-lines.txt
cat /tmp/llvm-lines.txt | whatever-analysis-you-want

This applies to ALL expensive commands: cargo builds, rustc invocations, profiling tools, etc.

Prerequisites

# Install cargo-llvm-lines
cargo install cargo-llvm-lines

# Nightly rust for -Z flags
rustup install nightly

Tool 1: cargo-llvm-lines (Find Monomorphization Bloat)

Shows which functions generate the most LLVM IR lines, indicating monomorphization bloat.

Usage

# Step 1: Capture to file (takes a while, needs full rebuild)
cargo llvm-lines --bin <binary-name> -p <package-name> > /tmp/llvm-lines-<binary>.txt 2>&1

# Step 2: Analyze the file
head -100 /tmp/llvm-lines-<binary>.txt
grep "picante" /tmp/llvm-lines-<binary>.txt
grep "tokio" /tmp/llvm-lines-<binary>.txt

Interpreting Results

Output format:

Lines                  Copies               Function name
-----                  ------               -------------
1952157                41947                (TOTAL)
 234836 (12.0%)    50 (0.1%)  picante::ingredient::derived::DerivedIngredient<DB,K,V>::access_scoped::{{closure}}
  • Lines: Total LLVM IR lines generated by all copies of this function
  • Copies: Number of monomorphized instances (different type parameters)
  • % of total: Percentage of total code size

Red flags:

  • High lines, low copies: One function generating tons of code (e.g., 234,836 lines from 50 copies = 4,700 lines each!)

    • Solution: Consider dyn Trait, split function into smaller pieces, or reduce generic complexity
  • High lines, high copies: Trait explosion (e.g., 100,000 lines from 5,000 copies)

    • Solution: Use dyn Trait, consolidate types, or reduce generic combinations
  • Closures with many lines: Often from large impl Future or complex generic contexts

    • Solution: Extract closure body into a separate non-generic function

Common Culprits

  • Picante/Salsa derived queries (generates lots of code per query type)
  • Tokio futures (each async fn with different concrete types)
  • Serde serialize/deserialize for many types
  • Iterator chains with many generic combinators

Tool 2: -Zmacro-stats (Find Macro Expansion Bloat)

Shows which macros expand to the most code.

Usage

# Step 1: Capture to file
RUSTFLAGS="-Zmacro-backtrace" \
  cargo +nightly build --bin <binary> -p <package> > /tmp/macro-stats.txt 2>&1

# Step 2: Look for macro stats in the output
grep -i "macro" /tmp/macro-stats.txt

Note: -Zmacro-stats is an older flag that may not work on newer nightly. Use -Zunpretty=expanded to see full expansion:

# Step 1: Capture expanded code
cargo +nightly rustc --bin <binary> -p <package> -- -Zunpretty=expanded > /tmp/expanded.rs 2>&1

# Step 2: Analyze
wc -l /tmp/expanded.rs  # Total lines after macro expansion
grep "picante" /tmp/expanded.rs | wc -l

Tool 3: -Zprint-type-sizes (Find Large Types)

Large types cause expensive memcpy operations and increase monomorphization cost.

Usage

# Step 1: Capture to file
cargo +nightly rustc --bin <binary> -p <package> -- -Zprint-type-sizes > /tmp/type-sizes.txt 2>&1

# Step 2: Analyze
head -100 /tmp/type-sizes.txt
grep "picante" /tmp/type-sizes.txt
sort -k2 -n /tmp/type-sizes.txt | tail -50  # Largest types

Interpreting Results

print-type-size type: `picante::ingredient::derived::Cell<...>`: 128 bytes

Red flags:

  • Types > 100 bytes (expensive to copy)
  • Deeply nested generic types (increase monomorphization)

Solutions:

  • Box large fields
  • Use references instead of owned types
  • Simplify nested generics

Tool 4: -Zprint-mono-items (See ALL Monomorphized Items)

Shows every single generic function instantiation. WARNING: Very verbose!

Usage

# Step 1: Capture to file (will be HUGE)
cargo +nightly rustc --bin <binary> -p <package> -- -Zprint-mono-items=eager > /tmp/mono-items.txt 2>&1

# Step 2: Analyze patterns
grep "picante" /tmp/mono-items.txt | wc -l
grep "picante::ingredient::derived" /tmp/mono-items.txt | sort | uniq | wc -l

This tells you exactly which type combinations are being monomorphized.

Workflow: Finding Compile Time Bloat

  1. Start with llvm-lines (fastest, most actionable):

    cargo llvm-lines --bin ddc -p dodeca > /tmp/llvm-lines.txt 2>&1
    head -50 /tmp/llvm-lines.txt
    
  2. Identify top bloaters (functions with high lines × copies)

  3. Check if it's from a specific crate:

    grep "picante" /tmp/llvm-lines.txt | head -20
    grep "tokio" /tmp/llvm-lines.txt | head -20
    grep "serde" /tmp/llvm-lines.txt | head -20
    
  4. For macro-heavy crates, check expansion:

    cargo +nightly rustc --bin ddc -- -Zunpretty=expanded > /tmp/expanded.rs 2>&1
    wc -l /tmp/expanded.rs
    
  5. Check type sizes if lots of memcpy/clone:

    cargo +nightly rustc --bin ddc -- -Zprint-type-sizes > /tmp/type-sizes.txt 2>&1
    sort -k2 -n /tmp/type-sizes.txt | tail -50
    

Solutions to Common Issues

Issue: Picante queries generate tons of code

Each #[derive(Query)] or #[salsa::query] generates lots of monomorphized code for runtime state, caching, etc.

Solutions:

  • Reduce number of query types (consolidate similar queries)
  • Use fewer concrete types with each query
  • Consider if some queries can use dyn Trait internally

Issue: Large async fn closures

Async functions become impl Future which monomorphize for each concrete type.

Solutions:

  • Extract logic into separate non-async helper functions
  • Use Box<dyn Future> for cold paths
  • Reduce generic parameters on async functions

Issue: Iterator chains

Each iterator combinator (.map().filter().collect()) with different types creates new monomorphizations.

Solutions:

  • Extract complex iterator chains into helper functions
  • Use explicit loops for cold paths
  • Consider Vec<Box<dyn Trait>> for heterogeneous collections

Summary

Golden rule: Capture → Analyze → Don't Waste Work

Tools in order of usefulness:

  1. cargo llvm-lines - Find monomorphization bloat
  2. -Zprint-type-sizes - Find large types
  3. -Zunpretty=expanded - See macro expansion
  4. -Zprint-mono-items - Full monomorphization dump (very verbose)

Always save output to files first!