├── .github ├── FUNDING.yml └── workflows │ ├── ci.yml │ └── install.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md └── src ├── cmd.rs ├── count.rs ├── error.rs ├── main.rs ├── opts.rs └── table.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: dtolnay 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | schedule: [cron: "40 1 * * *"] 8 | 9 | permissions: 10 | contents: read 11 | 12 | env: 13 | RUSTFLAGS: -Dwarnings 14 | 15 | jobs: 16 | pre_ci: 17 | uses: dtolnay/.github/.github/workflows/pre_ci.yml@master 18 | 19 | test: 20 | name: Rust ${{matrix.rust}} on ${{matrix.target || 'Linux'}} 21 | needs: pre_ci 22 | if: needs.pre_ci.outputs.continue 23 | runs-on: ${{matrix.os}}-latest 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | rust: [nightly, beta, stable, 1.74.0] 28 | os: [ubuntu] 29 | include: 30 | - rust: nightly 31 | target: Windows 32 | os: windows 33 | timeout-minutes: 45 34 | steps: 35 | - uses: actions/checkout@v4 36 | - uses: dtolnay/rust-toolchain@master 37 | with: 38 | toolchain: ${{matrix.rust}} 39 | - name: Enable type layout randomization 40 | run: echo RUSTFLAGS=${RUSTFLAGS}\ -Zrandomize-layout >> $GITHUB_ENV 41 | if: matrix.rust == 'nightly' 42 | shell: bash 43 | - run: cargo check --locked 44 | - run: cargo update 45 | - run: cargo run -- llvm-lines 46 | - run: cargo test 47 | - uses: actions/upload-artifact@v4 48 | if: matrix.os == 'ubuntu' && matrix.rust == 'nightly' && always() 49 | with: 50 | name: Cargo.lock 51 | path: Cargo.lock 52 | continue-on-error: true 53 | 54 | clippy: 55 | name: Clippy 56 | runs-on: ubuntu-latest 57 | if: github.event_name != 'pull_request' 58 | timeout-minutes: 45 59 | steps: 60 | - uses: actions/checkout@v4 61 | - uses: dtolnay/rust-toolchain@clippy 62 | - run: cargo update 63 | - run: cargo clippy -- -Dclippy::all -Dclippy::pedantic 64 | 65 | outdated: 66 | name: Outdated 67 | runs-on: ubuntu-latest 68 | if: github.event_name != 'pull_request' 69 | timeout-minutes: 45 70 | steps: 71 | - uses: actions/checkout@v4 72 | - uses: dtolnay/rust-toolchain@stable 73 | - uses: dtolnay/install@cargo-outdated 74 | - run: cargo update 75 | - run: cargo outdated --workspace --exit-code 1 76 | -------------------------------------------------------------------------------- /.github/workflows/install.yml: -------------------------------------------------------------------------------- 1 | name: Install 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: [cron: "40 1 * * *"] 6 | push: {tags: ['*']} 7 | 8 | permissions: {} 9 | 10 | env: 11 | RUSTFLAGS: -Dwarnings 12 | 13 | jobs: 14 | install: 15 | name: Install 16 | uses: dtolnay/.github/.github/workflows/check_install.yml@master 17 | with: 18 | crate: cargo-llvm-lines 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anstream" 7 | version = "0.6.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 10 | dependencies = [ 11 | "anstyle", 12 | "anstyle-parse", 13 | "anstyle-query", 14 | "anstyle-wincon", 15 | "colorchoice", 16 | "is_terminal_polyfill", 17 | "utf8parse", 18 | ] 19 | 20 | [[package]] 21 | name = "anstyle" 22 | version = "1.0.10" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 25 | 26 | [[package]] 27 | name = "anstyle-parse" 28 | version = "0.2.6" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 31 | dependencies = [ 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle-query" 37 | version = "1.1.2" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 40 | dependencies = [ 41 | "windows-sys", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-wincon" 46 | version = "3.0.6" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" 49 | dependencies = [ 50 | "anstyle", 51 | "windows-sys", 52 | ] 53 | 54 | [[package]] 55 | name = "bitflags" 56 | version = "2.6.0" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 59 | 60 | [[package]] 61 | name = "cargo-llvm-lines" 62 | version = "0.4.42" 63 | dependencies = [ 64 | "cargo-subcommand-metadata", 65 | "clap", 66 | "clap-cargo", 67 | "regex-lite", 68 | "rustc-demangle", 69 | "shlex", 70 | "tempfile", 71 | "termcolor", 72 | ] 73 | 74 | [[package]] 75 | name = "cargo-subcommand-metadata" 76 | version = "0.1.0" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "a33d3b80a8db16c4ad7676653766a8e59b5f95443c8823cb7cff587b90cb91ba" 79 | 80 | [[package]] 81 | name = "cfg-if" 82 | version = "1.0.0" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 85 | 86 | [[package]] 87 | name = "clap" 88 | version = "4.5.23" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" 91 | dependencies = [ 92 | "clap_builder", 93 | "clap_derive", 94 | ] 95 | 96 | [[package]] 97 | name = "clap-cargo" 98 | version = "0.15.1" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "5e3340d63f960ad376843dd750fb2aa4ac1b79e10775a070882bd135edfce303" 101 | dependencies = [ 102 | "anstyle", 103 | "clap", 104 | ] 105 | 106 | [[package]] 107 | name = "clap_builder" 108 | version = "4.5.23" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" 111 | dependencies = [ 112 | "anstream", 113 | "anstyle", 114 | "clap_lex", 115 | "strsim", 116 | "terminal_size", 117 | ] 118 | 119 | [[package]] 120 | name = "clap_derive" 121 | version = "4.5.18" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 124 | dependencies = [ 125 | "heck", 126 | "proc-macro2", 127 | "quote", 128 | "syn", 129 | ] 130 | 131 | [[package]] 132 | name = "clap_lex" 133 | version = "0.7.4" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 136 | 137 | [[package]] 138 | name = "colorchoice" 139 | version = "1.0.3" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 142 | 143 | [[package]] 144 | name = "errno" 145 | version = "0.3.10" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 148 | dependencies = [ 149 | "libc", 150 | "windows-sys", 151 | ] 152 | 153 | [[package]] 154 | name = "fastrand" 155 | version = "2.3.0" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 158 | 159 | [[package]] 160 | name = "getrandom" 161 | version = "0.2.15" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 164 | dependencies = [ 165 | "cfg-if", 166 | "libc", 167 | "wasi", 168 | ] 169 | 170 | [[package]] 171 | name = "heck" 172 | version = "0.5.0" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 175 | 176 | [[package]] 177 | name = "is_terminal_polyfill" 178 | version = "1.70.1" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 181 | 182 | [[package]] 183 | name = "libc" 184 | version = "0.2.169" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" 187 | 188 | [[package]] 189 | name = "linux-raw-sys" 190 | version = "0.4.14" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 193 | 194 | [[package]] 195 | name = "once_cell" 196 | version = "1.20.2" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 199 | 200 | [[package]] 201 | name = "proc-macro2" 202 | version = "1.0.92" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" 205 | dependencies = [ 206 | "unicode-ident", 207 | ] 208 | 209 | [[package]] 210 | name = "quote" 211 | version = "1.0.38" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" 214 | dependencies = [ 215 | "proc-macro2", 216 | ] 217 | 218 | [[package]] 219 | name = "regex-lite" 220 | version = "0.1.6" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" 223 | 224 | [[package]] 225 | name = "rustc-demangle" 226 | version = "0.1.24" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 229 | 230 | [[package]] 231 | name = "rustix" 232 | version = "0.38.42" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" 235 | dependencies = [ 236 | "bitflags", 237 | "errno", 238 | "libc", 239 | "linux-raw-sys", 240 | "windows-sys", 241 | ] 242 | 243 | [[package]] 244 | name = "shlex" 245 | version = "1.3.0" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 248 | 249 | [[package]] 250 | name = "strsim" 251 | version = "0.11.1" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 254 | 255 | [[package]] 256 | name = "syn" 257 | version = "2.0.94" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "987bc0be1cdea8b10216bd06e2ca407d40b9543468fafd3ddfb02f36e77f71f3" 260 | dependencies = [ 261 | "proc-macro2", 262 | "quote", 263 | "unicode-ident", 264 | ] 265 | 266 | [[package]] 267 | name = "tempfile" 268 | version = "3.15.0" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" 271 | dependencies = [ 272 | "cfg-if", 273 | "fastrand", 274 | "getrandom", 275 | "once_cell", 276 | "rustix", 277 | "windows-sys", 278 | ] 279 | 280 | [[package]] 281 | name = "termcolor" 282 | version = "1.4.1" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 285 | dependencies = [ 286 | "winapi-util", 287 | ] 288 | 289 | [[package]] 290 | name = "terminal_size" 291 | version = "0.4.1" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" 294 | dependencies = [ 295 | "rustix", 296 | "windows-sys", 297 | ] 298 | 299 | [[package]] 300 | name = "unicode-ident" 301 | version = "1.0.14" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 304 | 305 | [[package]] 306 | name = "utf8parse" 307 | version = "0.2.2" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 310 | 311 | [[package]] 312 | name = "wasi" 313 | version = "0.11.0+wasi-snapshot-preview1" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 316 | 317 | [[package]] 318 | name = "winapi-util" 319 | version = "0.1.9" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 322 | dependencies = [ 323 | "windows-sys", 324 | ] 325 | 326 | [[package]] 327 | name = "windows-sys" 328 | version = "0.59.0" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 331 | dependencies = [ 332 | "windows-targets", 333 | ] 334 | 335 | [[package]] 336 | name = "windows-targets" 337 | version = "0.52.6" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 340 | dependencies = [ 341 | "windows_aarch64_gnullvm", 342 | "windows_aarch64_msvc", 343 | "windows_i686_gnu", 344 | "windows_i686_gnullvm", 345 | "windows_i686_msvc", 346 | "windows_x86_64_gnu", 347 | "windows_x86_64_gnullvm", 348 | "windows_x86_64_msvc", 349 | ] 350 | 351 | [[package]] 352 | name = "windows_aarch64_gnullvm" 353 | version = "0.52.6" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 356 | 357 | [[package]] 358 | name = "windows_aarch64_msvc" 359 | version = "0.52.6" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 362 | 363 | [[package]] 364 | name = "windows_i686_gnu" 365 | version = "0.52.6" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 368 | 369 | [[package]] 370 | name = "windows_i686_gnullvm" 371 | version = "0.52.6" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 374 | 375 | [[package]] 376 | name = "windows_i686_msvc" 377 | version = "0.52.6" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 380 | 381 | [[package]] 382 | name = "windows_x86_64_gnu" 383 | version = "0.52.6" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 386 | 387 | [[package]] 388 | name = "windows_x86_64_gnullvm" 389 | version = "0.52.6" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 392 | 393 | [[package]] 394 | name = "windows_x86_64_msvc" 395 | version = "0.52.6" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 398 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cargo-llvm-lines" 3 | version = "0.4.42" 4 | authors = ["David Tolnay ", "Nicholas Nethercote "] 5 | categories = ["development-tools::cargo-plugins", "development-tools::debugging"] 6 | description = "Count the number of lines of LLVM IR across all instantiations of a generic function." 7 | documentation = "https://github.com/dtolnay/cargo-llvm-lines" 8 | edition = "2021" 9 | keywords = ["cargo", "subcommand"] 10 | license = "MIT OR Apache-2.0" 11 | repository = "https://github.com/dtolnay/cargo-llvm-lines" 12 | 13 | [dependencies] 14 | cargo-subcommand-metadata = "0.1" 15 | clap = { version = "4", features = ["deprecated", "derive", "wrap_help"] } 16 | clap-cargo = "0.15" 17 | regex = { version = "0.1", package = "regex-lite" } 18 | rustc-demangle = "0.1" 19 | shlex = "1.3" 20 | tempfile = "3.4" 21 | termcolor = "1.4" 22 | 23 | [package.metadata.docs.rs] 24 | targets = ["x86_64-unknown-linux-gnu"] 25 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cargo-llvm-lines 2 | 3 | [github](https://github.com/dtolnay/cargo-llvm-lines) 4 | [crates.io](https://crates.io/crates/cargo-llvm-lines) 5 | [build status](https://github.com/dtolnay/cargo-llvm-lines/actions?query=branch%3Amaster) 6 | 7 | This tool measures the number and size of instantiations of each generic 8 | function in a program, indicating which parts of your code offer the highest 9 | leverage in improving compilation metrics. 10 | 11 | Generic functions in Rust can be instantiated multiple times, so they can 12 | disproportionately affect compile time, compiler memory usage, and the size of 13 | compiled executables. 14 | 15 | ## Installation 16 | 17 | Install with `cargo install cargo-llvm-lines`. 18 | 19 | ## Output 20 | 21 | Example output from running `cargo llvm-lines` on its own codebase: 22 | 23 | ```console 24 | $ cargo llvm-lines | head -20 25 | 26 | Lines Copies Function name 27 | ----- ------ ------------- 28 | 51637 1222 (TOTAL) 29 | 2240 (4.3%, 4.3%) 1 (0.1%, 0.1%) ::augment_args 30 | 1190 (2.3%, 6.6%) 1 (0.1%, 0.2%) ::from_arg_matches_mut 31 | 1005 (1.9%, 8.6%) 3 (0.2%, 0.4%) alloc::raw_vec::RawVec::grow_amortized 32 | 973 (1.9%, 10.5%) 7 (0.6%, 1.0%) clap_builder::parser::matches::arg_matches::ArgMatches::try_remove_arg_t 33 | 939 (1.8%, 12.3%) 7 (0.6%, 1.6%) alloc::sync::Arc::try_unwrap 34 | 935 (1.8%, 14.1%) 6 (0.5%, 2.0%) as alloc::vec::spec_from_iter_nested::SpecFromIterNested>::from_iter 35 | 861 (1.7%, 15.8%) 7 (0.6%, 2.6%) alloc::sync::Arc::downcast 36 | 761 (1.5%, 17.2%) 5 (0.4%, 3.0%) alloc::vec::Vec::extend_desugared 37 | 638 (1.2%, 18.5%) 1 (0.1%, 3.1%) cargo_llvm_lines::table::print 38 | 599 (1.2%, 19.6%) 16 (1.3%, 4.4%) core::option::Option::ok_or_else 39 | 592 (1.1%, 20.8%) 2 (0.2%, 4.6%) core::slice::sort::merge 40 | 574 (1.1%, 21.9%) 2 (0.2%, 4.7%) core::slice::sort::merge_sort 41 | 561 (1.1%, 23.0%) 7 (0.6%, 5.3%) clap_builder::parser::matches::any_value::AnyValue::downcast_into 42 | 556 (1.1%, 24.1%) 4 (0.3%, 5.6%) as core::iter::traits::iterator::Iterator>::next 43 | 541 (1.0%, 25.1%) 16 (1.3%, 7.0%) core::option::Option::map 44 | 536 (1.0%, 26.1%) 8 (0.7%, 7.6%) as core::ops::drop::Drop>::drop 45 | 533 (1.0%, 27.2%) 1 (0.1%, 7.7%) core::str::pattern::simd_contains 46 | ``` 47 | 48 | There is one line per function with three columns of output: 49 | 50 | 1. Total number of lines of LLVM IR generated across all instantiations of the 51 | function (plus the percentage of the total and the cumulative percentage 52 | of all functions so far). 53 | 2. Number of instantiations of the function (plus the percentage of the total 54 | and the cumulative percentage of all functions so far). For a generic 55 | function, the number of instantiations is roughly the number of distinct 56 | combinations of generic type parameters it is called with. 57 | 3. Name of the function. 58 | 59 | ## Multicrate Projects 60 | 61 | Interpreting the output in the presence of multiple crates and generics can be 62 | tricky. `cargo llvm-lines` only shows the contribution of the root crate; 63 | dependencies are not included. To assess the contribution of an intermediate 64 | crate, use the `-p` flag: 65 | 66 | ```console 67 | $ cargo llvm-lines -p some-dependency 68 | ``` 69 | 70 | Note however, that Rust generics are monomorphised — a generic function 71 | will be accounted for in the crates that use it, rather than in the defining 72 | crate. 73 | 74 | There is a trick to get a holistic view: enabling link time optimization causes 75 | all code generation to happen in the root crate. So you can use the following 76 | invocation to get a full picture: 77 | 78 | ```console 79 | $ CARGO_PROFILE_RELEASE_LTO=fat cargo llvm-lines --release 80 | ``` 81 | 82 | ## Acknowledgements 83 | 84 | Based on a suggestion from **@eddyb** on how to count monomorphized functions 85 | in order to debug compiler memory usage, executable size and compile time. 86 | 87 | > **\** unoptimized LLVM IR
88 | > **\** first used grep '^define' to get only the lines defining function bodies
89 | > **\** then regex replace in my editor to remove everything before @ and everything after (
90 | > **\** then sort | uniq -c
91 | 92 |
93 | 94 | #### License 95 | 96 | 97 | Licensed under either of Apache License, Version 98 | 2.0 or MIT license at your option. 99 | 100 | 101 |
102 | 103 | 104 | Unless you explicitly state otherwise, any contribution intentionally submitted 105 | for inclusion in this crate by you, as defined in the Apache-2.0 license, shall 106 | be dual licensed as above, without any additional terms or conditions. 107 | 108 | -------------------------------------------------------------------------------- /src/cmd.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::OsStr; 2 | use std::process::Command; 3 | 4 | pub trait CommandExt { 5 | fn flag_value(&mut self, k: K, v: V) 6 | where 7 | K: AsRef, 8 | V: AsRef; 9 | } 10 | 11 | impl CommandExt for Command { 12 | fn flag_value(&mut self, k: K, v: V) 13 | where 14 | K: AsRef, 15 | V: AsRef, 16 | { 17 | let k = k.as_ref(); 18 | let v = v.as_ref(); 19 | if let Some(k) = k.to_str() { 20 | if let Some(v) = v.to_str() { 21 | self.arg(format!("{}={}", k, v)); 22 | return; 23 | } 24 | } 25 | self.arg(k); 26 | self.arg(v); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/count.rs: -------------------------------------------------------------------------------- 1 | use rustc_demangle::demangle; 2 | use std::collections::HashMap as Map; 3 | 4 | #[derive(Default)] 5 | pub struct Instantiations { 6 | pub copies: usize, 7 | pub total_lines: usize, 8 | } 9 | 10 | impl Instantiations { 11 | fn record_lines(&mut self, lines: usize) { 12 | self.copies += 1; 13 | self.total_lines += lines; 14 | } 15 | } 16 | 17 | pub fn count_lines(instantiations: &mut Map, ir: &[u8]) { 18 | let mut current_function = None; 19 | let mut count = 0; 20 | 21 | for line in String::from_utf8_lossy(ir).lines() { 22 | if line.starts_with("define ") { 23 | current_function = parse_function_name(line); 24 | } else if line == "}" { 25 | if let Some(name) = current_function.take() { 26 | instantiations 27 | .entry(name) 28 | .or_insert_with(Default::default) 29 | .record_lines(count); 30 | } 31 | count = 0; 32 | } else if line.starts_with(" ") && !line.starts_with(" ") { 33 | count += 1; 34 | } 35 | } 36 | } 37 | 38 | fn parse_function_name(line: &str) -> Option { 39 | let start = line.find('@')? + 1; 40 | let end = line[start..].find('(')?; 41 | let mangled = line[start..start + end].trim_matches('"'); 42 | let mut name = demangle(mangled).to_string(); 43 | if has_hash(&name) { 44 | let len = name.len() - 19; 45 | name.truncate(len); 46 | } 47 | Some(name) 48 | } 49 | 50 | fn has_hash(name: &str) -> bool { 51 | let mut bytes = name.bytes().rev(); 52 | for _ in 0..16 { 53 | if !bytes.next().is_some_and(is_ascii_hexdigit) { 54 | return false; 55 | } 56 | } 57 | bytes.next() == Some(b'h') && bytes.next() == Some(b':') && bytes.next() == Some(b':') 58 | } 59 | 60 | fn is_ascii_hexdigit(byte: u8) -> bool { 61 | byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte) 62 | } 63 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display}; 2 | use std::io; 3 | use std::path::PathBuf; 4 | 5 | pub type Result = std::result::Result; 6 | 7 | #[derive(Debug)] 8 | pub enum Error { 9 | Msg(&'static str), 10 | Io(io::Error), 11 | PathIo(PathBuf, io::Error), 12 | Quote(shlex::QuoteError), 13 | } 14 | 15 | impl From for Error { 16 | fn from(error: io::Error) -> Self { 17 | Error::Io(error) 18 | } 19 | } 20 | 21 | impl From for Error { 22 | fn from(error: shlex::QuoteError) -> Self { 23 | Error::Quote(error) 24 | } 25 | } 26 | 27 | impl Display for Error { 28 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 29 | match self { 30 | Error::Msg(msg) => formatter.write_str(msg), 31 | Error::Io(e) => Display::fmt(e, formatter), 32 | Error::PathIo(path, e) => write!(formatter, "{}: {}", path.display(), e), 33 | Error::Quote(e) => Display::fmt(e, formatter), 34 | } 35 | } 36 | } 37 | 38 | impl std::error::Error for Error { 39 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 40 | match self { 41 | Error::Msg(_) => None, 42 | Error::Io(e) => e.source(), 43 | Error::PathIo(_path, e) => e.source(), 44 | Error::Quote(e) => e.source(), 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | //! [![github]](https://github.com/dtolnay/cargo-llvm-lines) [![crates-io]](https://crates.io/crates/cargo-llvm-lines) [![docs-rs]](https://docs.rs/cargo-llvm-lines) 2 | //! 3 | //! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github 4 | //! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust 5 | //! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs 6 | 7 | #![allow( 8 | clippy::cast_precision_loss, 9 | clippy::let_underscore_untyped, 10 | clippy::module_name_repetitions, 11 | clippy::struct_excessive_bools, 12 | clippy::too_many_lines, 13 | clippy::uninlined_format_args, 14 | clippy::unnecessary_map_or, 15 | clippy::unseparated_literal_suffix, 16 | clippy::unwrap_or_default 17 | )] 18 | 19 | mod cmd; 20 | mod count; 21 | mod error; 22 | mod opts; 23 | mod table; 24 | 25 | use crate::cmd::CommandExt as _; 26 | use crate::count::{count_lines, Instantiations}; 27 | use crate::error::{Error, Result}; 28 | use crate::opts::{Coloring, LlvmLines, SortOrder, Subcommand}; 29 | use clap::{CommandFactory, Parser}; 30 | use regex::Regex; 31 | use std::collections::HashMap as Map; 32 | use std::env; 33 | use std::ffi::OsString; 34 | use std::fs; 35 | use std::io::{self, BufRead, IsTerminal, Write}; 36 | use std::path::{Path, PathBuf}; 37 | use std::process::{self, Command, Stdio}; 38 | use tempfile::TempDir; 39 | use termcolor::{Color::Green, ColorChoice, ColorSpec, StandardStream, WriteColor as _}; 40 | 41 | cargo_subcommand_metadata::description!( 42 | "Count the number of lines of LLVM IR across all instantiations of a generic function" 43 | ); 44 | 45 | fn main() { 46 | let Subcommand::LlvmLines(opts) = Subcommand::parse(); 47 | 48 | if opts.help { 49 | let _ = Subcommand::command() 50 | .get_subcommands_mut() 51 | .next() 52 | .unwrap() 53 | .print_help(); 54 | return; 55 | } 56 | 57 | if opts.version { 58 | let mut stdout = io::stdout(); 59 | let _ = stdout.write_all(Subcommand::command().render_version().as_bytes()); 60 | return; 61 | } 62 | 63 | let result = if opts.files.is_empty() { 64 | cargo_llvm_lines(&opts) 65 | } else { 66 | read_llvm_ir_from_paths(&opts.files, opts.sort, opts.filter.as_ref()) 67 | }; 68 | 69 | process::exit(match result { 70 | Ok(code) => code, 71 | Err(err) => { 72 | let _ = writeln!(io::stderr(), "{}", err); 73 | 1 74 | } 75 | }); 76 | } 77 | 78 | fn cargo_llvm_lines(opts: &LlvmLines) -> Result { 79 | let outdir = tempfile::Builder::new() 80 | .prefix("cargo-llvm-lines") 81 | .tempdir() 82 | .expect("failed to create tmp file"); 83 | let outfile = outdir.path().join("crate"); 84 | 85 | // If cargo-llvm-lines was invoked from cargo, use the cargo that invoked it. 86 | let cargo = env::var_os("CARGO").unwrap_or_else(|| OsString::from("cargo")); 87 | let mut cmd = Command::new(cargo); 88 | propagate_opts(&mut cmd, opts, &outfile); 89 | cmd.env("CARGO_INCREMENTAL", ""); 90 | cmd.stdout(Stdio::inherit()); 91 | 92 | if opts.verbose { 93 | let color = opts.color.unwrap_or(Coloring::Auto); 94 | print_command(&cmd, color)?; 95 | } 96 | 97 | let exit = filter_err(&mut cmd)?; 98 | if exit != 0 { 99 | return Ok(exit); 100 | } 101 | 102 | let ir = read_llvm_ir_from_dir(&outdir)?; 103 | let mut instantiations = Map::::new(); 104 | count_lines(&mut instantiations, &ir); 105 | table::print(instantiations, opts.sort, opts.filter.as_ref()); 106 | 107 | Ok(0) 108 | } 109 | 110 | fn read_llvm_ir_from_dir(outdir: &TempDir) -> Result> { 111 | for file in fs::read_dir(outdir)? { 112 | let path = file?.path(); 113 | if let Some(ext) = path.extension() { 114 | if ext == "ll" { 115 | let content = fs::read(path)?; 116 | return Ok(content); 117 | } 118 | } 119 | } 120 | 121 | Err(Error::Msg("Ran --emit=llvm-ir but did not find output IR")) 122 | } 123 | 124 | fn read_llvm_ir_from_paths( 125 | paths: &[PathBuf], 126 | sort_order: SortOrder, 127 | function_filter: Option<&Regex>, 128 | ) -> Result { 129 | let mut instantiations = Map::::new(); 130 | 131 | for path in paths { 132 | match fs::read(path) { 133 | Ok(ir) => count_lines(&mut instantiations, &ir), 134 | Err(err) => return Err(Error::PathIo(path.clone(), err)), 135 | } 136 | } 137 | 138 | table::print(instantiations, sort_order, function_filter); 139 | Ok(0) 140 | } 141 | 142 | fn propagate_opts(cmd: &mut Command, opts: &LlvmLines, outfile: &Path) { 143 | let LlvmLines { 144 | // Strip out options that are for cargo-llvm-lines itself. 145 | sort: _, 146 | filter: _, 147 | files: _, 148 | help: _, 149 | version: _, 150 | 151 | // Options to pass through to the cargo rustc invocation. 152 | verbose, 153 | quiet, 154 | color, 155 | ref config, 156 | ref nightly_only_flags, 157 | ref package, 158 | lib, 159 | ref bin, 160 | ref example, 161 | ref test, 162 | ref bench, 163 | ref features, 164 | all_features, 165 | no_default_features, 166 | jobs, 167 | release, 168 | ref profile, 169 | ref target, 170 | ref target_dir, 171 | ref manifest_path, 172 | frozen, 173 | locked, 174 | offline, 175 | ref rest, 176 | } = *opts; 177 | 178 | cmd.arg("rustc"); 179 | 180 | if verbose { 181 | cmd.arg("--verbose"); 182 | } 183 | 184 | if quiet { 185 | cmd.arg("--quiet"); 186 | } 187 | 188 | match color { 189 | Some(Coloring::Always) => cmd.flag_value("--color", "always"), 190 | Some(Coloring::Never) => cmd.flag_value("--color", "never"), 191 | None | Some(Coloring::Auto) => { 192 | if env::var_os("NO_COLOR").is_none() && io::stderr().is_terminal() { 193 | cmd.flag_value("--color", "always"); 194 | } else { 195 | cmd.flag_value("--color", "never"); 196 | } 197 | } 198 | } 199 | 200 | for kv in config { 201 | cmd.flag_value("--config", kv); 202 | } 203 | 204 | for flag in nightly_only_flags { 205 | cmd.arg(format!("-Z{}", flag)); 206 | } 207 | 208 | if let Some(package) = package { 209 | cmd.flag_value("--package", package); 210 | } 211 | 212 | if lib { 213 | cmd.arg("--lib"); 214 | } 215 | 216 | if let Some(bin) = bin { 217 | cmd.flag_value("--bin", bin); 218 | } 219 | 220 | if let Some(example) = example { 221 | cmd.flag_value("--example", example); 222 | } 223 | 224 | if let Some(test) = test { 225 | cmd.flag_value("--test", test); 226 | } 227 | 228 | if let Some(bench) = bench { 229 | cmd.flag_value("--bench", bench); 230 | } 231 | 232 | if let Some(features) = features { 233 | cmd.flag_value("--features", features); 234 | } 235 | 236 | if all_features { 237 | cmd.arg("--all-features"); 238 | } 239 | 240 | if no_default_features { 241 | cmd.arg("--no-default-features"); 242 | } 243 | 244 | if let Some(jobs) = jobs { 245 | cmd.flag_value("--jobs", jobs.to_string()); 246 | } 247 | 248 | if release { 249 | cmd.arg("--release"); 250 | } 251 | 252 | if let Some(profile) = profile { 253 | cmd.flag_value("--profile", profile); 254 | } 255 | 256 | if let Some(target) = target { 257 | cmd.flag_value("--target", target); 258 | } 259 | 260 | if let Some(target_dir) = target_dir { 261 | cmd.flag_value("--target-dir", target_dir); 262 | } 263 | 264 | if let Some(manifest_path) = manifest_path { 265 | cmd.flag_value("--manifest-path", manifest_path); 266 | } 267 | 268 | if frozen { 269 | cmd.arg("--frozen"); 270 | } 271 | 272 | if locked { 273 | cmd.arg("--locked"); 274 | } 275 | 276 | if offline { 277 | cmd.arg("--offline"); 278 | } 279 | 280 | // The `-Cno-prepopulate-passes` means we skip LLVM optimizations, which is 281 | // good because (a) we count the LLVM IR lines are sent to LLVM, not how 282 | // many there are after optimizations run, and (b) it's faster. 283 | // 284 | // The `-Cpasses=name-anon-globals` follows on: it's required to avoid the 285 | // following error on some programs: "The current compilation is going to 286 | // use thin LTO buffers without running LLVM's NameAnonGlobals pass. This 287 | // will likely cause errors in LLVM. Consider adding -C 288 | // passes=name-anon-globals to the compiler command line." 289 | cmd.arg("--"); 290 | cmd.flag_value("--emit", "llvm-ir"); 291 | cmd.arg("-Cno-prepopulate-passes"); 292 | cmd.arg("-Cpasses=name-anon-globals"); 293 | cmd.arg("-o"); 294 | cmd.arg(outfile); 295 | cmd.args(rest); 296 | } 297 | 298 | fn filter_err(cmd: &mut Command) -> Result { 299 | let mut child = cmd.stderr(Stdio::piped()).spawn()?; 300 | let mut stderr = io::BufReader::new(child.stderr.take().unwrap()); 301 | let mut line = String::new(); 302 | while let Ok(n) = stderr.read_line(&mut line) { 303 | if n == 0 { 304 | break; 305 | } 306 | if !ignore_cargo_err(&line) { 307 | let _ = write!(io::stderr(), "{}", line); 308 | } 309 | line.clear(); 310 | } 311 | let code = child.wait()?.code().unwrap_or(1); 312 | Ok(code) 313 | } 314 | 315 | fn ignore_cargo_err(line: &str) -> bool { 316 | if line.trim().is_empty() { 317 | return true; 318 | } 319 | 320 | let discarded_lines = [ 321 | "warnings emitted", 322 | "ignoring specified output filename because multiple outputs were \ 323 | requested", 324 | "ignoring specified output filename for 'link' output because multiple \ 325 | outputs were requested", 326 | "ignoring --out-dir flag due to -o flag", 327 | "due to multiple output types requested, the explicitly specified \ 328 | output file name will be adapted for each output type", 329 | "ignoring -C extra-filename flag due to -o flag", 330 | ]; 331 | for s in &discarded_lines { 332 | if line.contains(s) { 333 | return true; 334 | } 335 | } 336 | 337 | // warning: `cratename` (lib) generated 2 warnings 338 | if let Some(i) = line.find(") generated ") { 339 | let rest = &line[i + ") generated ".len()..]; 340 | let n = rest.bytes().take_while(u8::is_ascii_digit).count(); 341 | if n > 0 && rest[n..].starts_with(" warning") { 342 | return true; 343 | } 344 | } 345 | 346 | false 347 | } 348 | 349 | fn print_command(cmd: &Command, color: Coloring) -> Result<()> { 350 | let mut shell_words = String::new(); 351 | let quoter = shlex::Quoter::new().allow_nul(true); 352 | for arg in cmd.get_args() { 353 | let arg_lossy = arg.to_string_lossy(); 354 | shell_words.push(' '); 355 | match arg_lossy.split_once('=') { 356 | Some((flag, value)) if flag.starts_with('-') && flag == quoter.quote(flag)? => { 357 | shell_words.push_str(flag); 358 | shell_words.push('='); 359 | if !value.is_empty() { 360 | shell_words.push_str("er.quote(value)?); 361 | } 362 | } 363 | _ => shell_words.push_str("er.quote(&arg_lossy)?), 364 | } 365 | } 366 | 367 | let color_choice = match color { 368 | Coloring::Auto => ColorChoice::Auto, 369 | Coloring::Always => ColorChoice::Always, 370 | Coloring::Never => ColorChoice::Never, 371 | }; 372 | 373 | let mut stream = StandardStream::stderr(color_choice); 374 | let _ = stream.set_color(ColorSpec::new().set_bold(true).set_fg(Some(Green))); 375 | let _ = write!(stream, "{:>12}", "Running"); 376 | let _ = stream.reset(); 377 | let _ = writeln!(stream, " `cargo{}`", shell_words); 378 | Ok(()) 379 | } 380 | -------------------------------------------------------------------------------- /src/opts.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, ValueEnum}; 2 | use regex::Regex; 3 | use std::ffi::OsString; 4 | use std::path::PathBuf; 5 | 6 | const ABOUT: &str = " 7 | Print the number of lines of LLVM IR that is generated for the current project. 8 | 9 | Options shown below without an explanation mean the same thing as the 10 | corresponding option of `cargo build`."; 11 | 12 | const TEMPLATE: &str = "\ 13 | cargo-llvm-lines {version} 14 | {author} 15 | {about} 16 | 17 | {usage-heading} 18 | {usage} 19 | 20 | {all-args}"; 21 | 22 | // Help headings 23 | const PACKAGE_SELECTION: &str = "Package Selection"; 24 | const TARGET_SELECTION: &str = "Target Selection"; 25 | const FEATURE_SELECTION: &str = "Feature Selection"; 26 | const COMPILATION_OPTIONS: &str = "Compilation Options"; 27 | const MANIFEST_OPTIONS: &str = "Manifest Options"; 28 | 29 | #[derive(Parser, Debug)] 30 | #[command( 31 | name = "cargo-llvm-lines", 32 | bin_name = "cargo", 33 | author, 34 | version, 35 | disable_help_subcommand = true, 36 | styles = clap_cargo::style::CLAP_STYLING, 37 | )] 38 | #[allow(dead_code)] 39 | pub enum Subcommand { 40 | #[command( 41 | name = "llvm-lines", 42 | author, 43 | version, 44 | about = ABOUT, 45 | help_template = TEMPLATE, 46 | override_usage = "cargo llvm-lines [OPTIONS] -- [RUSTC OPTIONS]", 47 | disable_help_flag = true, 48 | disable_version_flag = true, 49 | )] 50 | LlvmLines(LlvmLines), 51 | } 52 | 53 | #[derive(Parser, Debug)] 54 | #[command(styles = clap_cargo::style::CLAP_STYLING)] 55 | pub struct LlvmLines { 56 | /// Set column by which to sort output table. 57 | #[arg( 58 | short, 59 | long, 60 | value_enum, 61 | value_name = "ORDER", 62 | default_value_t = SortOrder::Lines, 63 | )] 64 | pub sort: SortOrder, 65 | 66 | /// Display only functions matching the given regex. 67 | #[arg(long, value_name = "REGEX")] 68 | pub filter: Option, 69 | 70 | /// Analyze existing .ll files that were produced by e.g. 71 | /// `RUSTFLAGS="--emit=llvm-ir" ./x.py build --stage 0 compiler/rustc`. 72 | #[arg(short, long, value_name = "FILES")] 73 | pub files: Vec, 74 | 75 | // The following options are passed through to the cargo rustc invocation. 76 | #[arg(long)] 77 | pub verbose: bool, 78 | #[arg(short, long)] 79 | pub quiet: bool, 80 | #[arg(long, value_name = "WHEN", hide_possible_values = true)] 81 | pub color: Option, 82 | #[arg(long, value_name = "KEY=VALUE")] 83 | pub config: Vec, 84 | #[arg(short = 'Z', value_name = "FLAG")] 85 | pub nightly_only_flags: Vec, 86 | #[arg(short, long)] 87 | pub help: bool, 88 | #[arg(short = 'V', long)] 89 | pub version: bool, 90 | 91 | // Package selection 92 | #[arg(short, long, value_name = "SPEC", help_heading = PACKAGE_SELECTION)] 93 | pub package: Option, 94 | 95 | // Target selection 96 | #[arg(long, help_heading = TARGET_SELECTION)] 97 | pub lib: bool, 98 | #[arg(long, value_name = "NAME", help_heading = TARGET_SELECTION)] 99 | pub bin: Option, 100 | #[arg(long, value_name = "NAME", help_heading = TARGET_SELECTION)] 101 | pub example: Option, 102 | #[arg(long, value_name = "NAME", help_heading = TARGET_SELECTION)] 103 | pub test: Option, 104 | #[arg(long, value_name = "NAME", help_heading = TARGET_SELECTION)] 105 | pub bench: Option, 106 | 107 | // Feature selection 108 | #[arg(short = 'F', long, value_name = "FEATURES", help_heading = FEATURE_SELECTION)] 109 | pub features: Option, 110 | #[arg(long, help_heading = FEATURE_SELECTION)] 111 | pub all_features: bool, 112 | #[arg(long, help_heading = FEATURE_SELECTION)] 113 | pub no_default_features: bool, 114 | 115 | // Compilation options 116 | #[arg(short, long, value_name = "N", help_heading = COMPILATION_OPTIONS)] 117 | pub jobs: Option, 118 | #[arg(long, help_heading = COMPILATION_OPTIONS)] 119 | pub release: bool, 120 | #[arg(long, value_name = "PROFILE-NAME", help_heading = COMPILATION_OPTIONS)] 121 | pub profile: Option, 122 | #[arg(long, value_name = "TRIPLE", help_heading = COMPILATION_OPTIONS)] 123 | pub target: Option, 124 | #[arg(long, value_name = "DIRECTORY", help_heading = COMPILATION_OPTIONS)] 125 | pub target_dir: Option, 126 | 127 | // Manifest options 128 | #[arg(long, value_name = "PATH", help_heading = MANIFEST_OPTIONS)] 129 | pub manifest_path: Option, 130 | #[arg(long, help_heading = MANIFEST_OPTIONS)] 131 | pub frozen: bool, 132 | #[arg(long, help_heading = MANIFEST_OPTIONS)] 133 | pub locked: bool, 134 | #[arg(long, help_heading = MANIFEST_OPTIONS)] 135 | pub offline: bool, 136 | 137 | // Any additional flags for rustc taken after `--`. 138 | #[arg(last = true, hide = true)] 139 | pub rest: Vec, 140 | } 141 | 142 | #[derive(ValueEnum, Copy, Clone, Debug)] 143 | pub enum SortOrder { 144 | Lines, 145 | Copies, 146 | Name, 147 | } 148 | 149 | #[derive(ValueEnum, Debug, Clone, Copy)] 150 | pub enum Coloring { 151 | Auto, 152 | Always, 153 | Never, 154 | } 155 | 156 | #[test] 157 | fn test_cli() { 158 | ::command().debug_assert(); 159 | } 160 | -------------------------------------------------------------------------------- /src/table.rs: -------------------------------------------------------------------------------- 1 | use crate::opts::SortOrder; 2 | use crate::Instantiations; 3 | use regex::Regex; 4 | use std::collections::HashMap as Map; 5 | use std::io::{self, Write}; 6 | 7 | pub(crate) fn print( 8 | instantiations: Map, 9 | sort_order: SortOrder, 10 | function_filter: Option<&Regex>, 11 | ) { 12 | let mut data = instantiations.into_iter().collect::>(); 13 | 14 | let mut total = Instantiations { 15 | copies: 0, 16 | total_lines: 0, 17 | }; 18 | for row in &data { 19 | total.copies += row.1.copies; 20 | total.total_lines += row.1.total_lines; 21 | } 22 | 23 | match sort_order { 24 | SortOrder::Lines => { 25 | data.sort_by(|a, b| { 26 | let key_lo = (b.1.total_lines, b.1.copies, &a.0); 27 | let key_hi = (a.1.total_lines, a.1.copies, &b.0); 28 | key_lo.cmp(&key_hi) 29 | }); 30 | } 31 | SortOrder::Copies => { 32 | data.sort_by(|a, b| { 33 | let key_lo = (b.1.copies, b.1.total_lines, &a.0); 34 | let key_hi = (a.1.copies, a.1.total_lines, &b.0); 35 | key_lo.cmp(&key_hi) 36 | }); 37 | } 38 | SortOrder::Name => data.sort_by(|a, b| { 39 | let key_lo = (&a.0, b.1.copies, b.1.total_lines); 40 | let key_hi = (&b.0, a.1.copies, b.1.total_lines); 41 | key_lo.cmp(&key_hi) 42 | }), 43 | } 44 | 45 | let lines_width = total.total_lines.to_string().len(); 46 | let copies_width = total.copies.to_string().len(); 47 | 48 | let stdout = io::stdout(); 49 | let mut handle = stdout.lock(); 50 | let _ = writeln!( 51 | handle, 52 | " Lines{0:1$} Copies{0:2$} Function name", 53 | "", lines_width, copies_width, 54 | ); 55 | let _ = writeln!( 56 | handle, 57 | " -----{0:1$} ------{0:2$} -------------", 58 | "", lines_width, copies_width, 59 | ); 60 | let _ = writeln!( 61 | handle, 62 | " {0:1$} {2:3$} (TOTAL)", 63 | total.total_lines, lines_width, total.copies, copies_width, 64 | ); 65 | let mut cumul_lines = 0; 66 | let mut cumul_copies = 0; 67 | let perc = |m, cumul_m: &mut _, n| { 68 | *cumul_m += m; 69 | format!( 70 | "({:3.1}%,{:5.1}%)", 71 | m as f64 / n as f64 * 100f64, 72 | *cumul_m as f64 / n as f64 * 100f64, 73 | ) 74 | }; 75 | for row in data { 76 | if function_filter.map_or(true, |ff| ff.is_match(&row.0)) { 77 | let _ = writeln!( 78 | handle, 79 | " {0:1$} {2:<14} {3:4$} {5:<14} {6}", 80 | row.1.total_lines, 81 | lines_width, 82 | perc(row.1.total_lines, &mut cumul_lines, total.total_lines), 83 | row.1.copies, 84 | copies_width, 85 | perc(row.1.copies, &mut cumul_copies, total.copies), 86 | row.0, 87 | ); 88 | } 89 | } 90 | } 91 | --------------------------------------------------------------------------------