cargo-llvm-lines shows which generic function is eating your compile time
Count lines of LLVM IR per generic function
At a glance
- What is it?
- cargo-llvm-lines is a cargo subcommand that counts the LLVM IR lines produced by each generic function and the number of times it was instantiated. It is the tool you reach for when a Rust build got slow and the usual suspects are all generic helpers.
- Who is it for?
- cargo-llvm-lines suits a Rust engineer whose build has slowed down and whose suspicion is that a handful of heavily generic helpers are being monomorphised dozens of times. It does not suit a codebase whose bottleneck is type checking, macro expansion or dependency compilation rather than codegen, and the root-crate-only default makes its output misleading in a workspace until you use the -p flag or the LTO invocation.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 66 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One line per generic function, with an IR count and an instantiation count
The problem this tool addresses is specific to Rust. Generic functions in Rust can be instantiated multiple times, so a single generic helper can be copied into the final binary once per combination of type parameters it is called with. The project description says as much: those copies can disproportionately affect compile time, compiler memory usage, and the size of compiled executables.
cargo-llvm-lines measures the number and size of instantiations of each generic function, so you can see which parts of your code offer the biggest payoff in improving compilation metrics. The output is a table with one line per function and three columns.
The first column is the total number of lines of LLVM IR generated across all instantiations of the function, followed by that figure as a percentage of the total and the cumulative percentage of all functions so far. The second column is the number of instantiations of the function, again with a percentage and a cumulative percentage. For a generic function, the number of instantiations is roughly the number of distinct combinations of generic type parameters it is called with. The third column is the function name.
So the two numbers answer different questions. A function can have a large IR count because it was instantiated many times, or because one instantiation is very large, and only the pair of columns tells you which case you are looking at.
The report that ships with the tool is the tool's own example output
The documentation includes a run of the tool against its own codebase, and the numbers make the point about monomorphisation without needing any theory:
$ cargo llvm-lines | head -20
Lines Copies Function name
----- ------ -------------
51637 1222 (TOTAL)
2240 (4.3%, 4.3%) 1 (0.1%, 0.1%) <cargo_llvm_lines::opts::LlvmLines as clap_builder::derive::Args>::augment_args
1190 (2.3%, 6.6%) 1 (0.1%, 0.2%) <cargo_llvm_lines::opts::LlvmLines as clap_builder::derive::FromArgMatches>::from_arg_matches_mut
1005 (1.9%, 8.6%) 3 (0.2%, 0.4%) alloc::raw_vec::RawVec<T,A>::grow_amortized
973 (1.9%, 10.5%) 7 (0.6%, 1.0%) clap_builder::parser::matches::arg_matches::ArgMatches::try_remove_arg_t
939 (1.8%, 12.3%) 7 (0.6%, 1.6%) alloc::sync::Arc<T>::try_unwrap
935 (1.8%, 14.1%) 6 (0.5%, 2.0%) <alloc::vec::Vec<T> as alloc::vec::spec_from_iter_nested::SpecFromIterNested<T,I>>::from_iter
861 (1.7%, 15.8%) 7 (0.6%, 2.6%) alloc::sync::Arc<dyn core::any::Any+core::marker::Send+core::marker::Sync>::downcast
761 (1.5%, 17.2%) 5 (0.4%, 3.0%) alloc::vec::Vec<T,A>::extend_desugared
638 (1.2%, 18.5%) 1 (0.1%, 3.1%) cargo_llvm_lines::table::print
599 (1.2%, 19.6%) 16 (1.3%, 4.4%) core::option::Option<T>::ok_or_else
592 (1.1%, 20.8%) 2 (0.2%, 4.6%) core::slice::sort::merge
574 (1.1%, 21.9%) 2 (0.2%, 4.7%) core::slice::sort::merge_sort
561 (1.1%, 23.0%) 7 (0.6%, 5.3%) clap_builder::parser::matches::any_value::AnyValue::downcast_into
556 (1.1%, 24.1%) 4 (0.3%, 5.6%) <core::slice::iter::Iter<T> as core::iter::traits::iterator::Iterator>::next
541 (1.0%, 25.1%) 16 (1.3%, 7.0%) core::option::Option<T>::map
536 (1.0%, 26.1%) 8 (0.7%, 7.6%) <alloc::sync::Weak<T> as core::ops::drop::Drop>::drop
533 (1.0%, 27.2%) 1 (0.1%, 7.7%) core::str::pattern::simd_containsRead that top of the list carefully and the pattern becomes clear. The largest contributors in this sample are not exotic user generics but standard library helpers that are generic in practice: RawVec grow_amortized, Arc try_unwrap, Option map, Option ok_or_else, the slice sort functions, and the Drop impl for Weak. Option map has 541 IR lines but 16 instantiations, while augment_args has 2240 lines and a single instantiation, so it is large for a different reason. That distinction is exactly what the two columns are for.
The tool also demangles names, and the crate depends on rustc-demangle for that, so you see readable Rust paths rather than raw mangled symbols. The clap_builder and alloc entries are the giveaway that a derive-heavy argument parser and the standard allocator are contributing to the total.
Installing the subcommand and running it on your own crate
Installation is a single cargo command, and the documentation gives it in one line: install with `cargo install cargo-llvm-lines`.
Once installed, the plugin is invoked as a cargo subcommand, so the working command includes the cargo prefix:
$ cargo llvm-lines | head -20The tool prints its table to standard output, which is why piping it into head works the way the example shows. The manifest lists the crate under the development-tools::cargo-plugins and development-tools::debugging categories, with the keyword subcommand, so the intended workflow is as an add-on to an existing cargo command rather than a standalone binary.
The minimum supported Rust version is 1.86, declared as rust-version in the manifest, and the edition is 2021. The dependency list is small and tells you what the implementation is made of: cargo-subcommand-metadata to read the package metadata, clap with the derive feature for the argument parsing, clap-cargo for the cargo integration, regex (the regex-lite package) for pattern matching over the LLVM output, rustc-demangle for names, shlex and tempfile for handling the temporary build artefacts, and termcolor for the coloured table.
Dependencies are excluded by default, and monomorphisation makes the -p flag misleading
The most important caveat in the documentation is about multicrate projects. Interpreting the output in the presence of multiple crates and generics can be tricky. `cargo llvm-lines` only shows the contribution of the root crate, and dependencies are not included.
To assess the contribution of an intermediate crate, the documentation says to use the -p flag:
$ cargo llvm-lines -p some-dependencyBut here is the twist that the documentation spells out: Rust generics are monomorphised, and a generic function will be accounted for in the crates that use it rather than in the defining crate. So pointing -p at a library crate does not necessarily surface that library's own generic functions in the table. They show up in the crates that instantiate them.
That is not a bug so much as a consequence of how monomorphisation works, and it is the reason the tool is easy to misread. If you are chasing a generic helper that lives in a shared library, the copy in your binary is the one that costs you the IR lines.
The documentation also notes the ordering caveat of the table itself, which sorts by IR line count with cumulative percentages, so the first rows are the biggest contributors and the tail is a long list of small ones.
Link time optimization moves all codegen into the root crate
There is a trick in the documentation for getting a complete view: enabling link time optimization causes all code generation to happen in the root crate, so the whole picture can be captured in one run.
$ CARGO_PROFILE_RELEASE_LTO=fat cargo llvm-lines --releaseThe environment variable forces fat LTO for the release profile, and the release flag selects the release profile. The consequence is that the root-crate-only limitation described earlier stops being a limitation, because every function, including the ones originating in dependencies, is now generated in the root crate and therefore counted.
The cost is that you are no longer measuring what a normal debug or default build does. This is a diagnostic invocation, not a measurement of your shipping build. A build with LTO enabled is slower to compile and the codegen differs, so use it to answer the question of which generic function dominates the binary, not to estimate the cost of an incremental debug build.
Together with the -p flag, these two invocations cover the two questions you actually have. Use -p when you want to isolate a specific crate, and use the LTO form when you want the total picture across the dependency graph in one table.
Where cargo-llvm-lines is the wrong tool
The tool measures generated LLVM IR per generic function. It says nothing about the rest of the compile pipeline. If your build is slow because of type checking, borrow checking, macro expansion, proc macro execution or the time spent compiling dependencies, this table will not show it, and the top entries can be actively misleading you toward code that is not your bottleneck.
There is also a scope limit that is easy to miss. The tool counts generic functions and their instantiations, and the description frames it around generics specifically. A build whose cost is dominated by a single huge non-generic function, or by code generation for a large amount of straight-line application logic, is not what this report is designed to reveal.
The alternative approach for a different question is the compiler's own timing and size reporting. Rust has a built-in way to ask for per-crate timing and to print the size of each section of the binary, which tells you where the time and the bytes went without needing a plugin and without the monomorphisation accounting rules. The difference in approach is direct: the built-in flags summarise by crate and by output section, while cargo-llvm-lines attributes IR lines to individual generic functions and their instantiation counts, which is the level you need when the suspect is a type parameter that has exploded into a dozen copies.
The manual method the project acknowledges also deserves a mention. The acknowledgements credit a suggestion from eddyb on how to count monomorphized functions, describing a sequence of unoptimized LLVM IR, a grep for lines defining function bodies, an editor regex to strip the mangled parts, then a sort and a uniq -c. That is the manual version of what the tool automates, and it is a reasonable fallback if you cannot install a cargo plugin, but it is fiddly and the tool does it in one command.
A dual licence and a dependency list that keeps the plugin self-contained
Licensing is the permissive and familiar choice. The crate is licensed under either of the Apache License, Version 2.0 or the MIT license at your option, and the manifest records it as MIT OR Apache-2.0. The repository ships both LICENSE-APACHE and LICENSE-MIT, and the notice says that unless you explicitly state otherwise, any contribution intentionally submitted for inclusion is dual licensed the same way, without additional terms or conditions.
The manifest is worth reading for another reason. The version is 0.4.48 and the authors are David Tolnay and Nicholas Nethercote, and the package is marked private, which for a published library means it is not intended for direct use as a dependency. It is a binary tool. The README also shows a crates.io badge, so it is distributed through the registry like any other cargo install target.
On maintenance, the release history is dense and recent: 0.4.48 and 0.4.47 were both published on 2026-08-19, and 0.4.46 came on 2026-05-18. The last push to the repository was on 2026-08-19, and the default branch is master.
One operational detail affects how you should run it. The tool is a cargo subcommand, so it works inside an existing project directory and needs to build your crate to produce the IR it counts. In a large workspace that build is the slow part, which is worth remembering before you wire it into a loop.
Editorial conclusion
cargo-llvm-lines suits a Rust engineer whose build has slowed down and whose suspicion is that a handful of heavily generic helpers are being monomorphised dozens of times. It does not suit a codebase whose bottleneck is type checking, macro expansion or dependency compilation rather than codegen, and the root-crate-only default makes its output misleading in a workspace until you use the -p flag or the LTO invocation. Before adopting, run cargo llvm-lines -p on the crate you suspect and confirm that the top entries match a generic function you control, and note that the last push to the repository was on 2026-08-19.
Frequently asked questions
How do I install cargo-llvm-lines?
Run `cargo install cargo-llvm-lines`. The crate is published on crates.io and requires Rust 1.86 or newer.
What do the two numeric columns in cargo-llvm-lines mean?
The first column is the total number of LLVM IR lines across all instantiations of a function, and the second is the number of instantiations. For a generic function, the instantiation count is roughly the number of distinct combinations of generic type parameters it is called with.
Why does cargo-llvm-lines not show the dependencies in my report?
The tool only shows the contribution of the root crate by default, and the -p flag lets you look at an intermediate crate. Because generics are monomorphised, a generic function is accounted for in the crates that use it rather than in the defining crate.
How can I get a full picture across all crates with cargo-llvm-lines?
Enable link time optimization so that all code generation happens in the root crate, using `CARGO_PROFILE_RELEASE_LTO=fat cargo llvm-lines --release`.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/dtolnay-cargo-llvm-lines)Was this useful?