Repository navigation
Closed
Labels
C-enhancementCategory: An issue proposing an enhancement or a PR with one.Category: An issue proposing an enhancement or a PR with one.I-compiletimeIssue: Problems and improvements with respect to compile times.Issue: Problems and improvements with respect to compile times.I-heavyIssue: Problems and improvements with respect to binary size of generated code.Issue: Problems and improvements with respect to binary size of generated code.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.Relevant to the compiler team, which will review and decide on the PR/issue.
Description
@nnethercote asked me to write up a more detailed plan for getting rid of LLVM bitcode in RLIBs, so here goes:
Current situation
- We store LLVM bitcode in every RLIB and Rust dylib so that
rustccan
perform cross-crate LTO. - This has quite a bit of a cost in terms of compile times and file sizes (See here).
- Cross-crate LTO is a niche use case and the current approach makes that costlier
than necessary too (because then machine code is duplicated) - When doing xLTO the shortcomings of the current implementation become
especially obvious because then we have two basically identical LLVM bitcode
files for each module (modulo compression). - Embedding bitcode also causes the incremental compilation cache to be much
bigger because we have to keep object files and bitcode files in the cache.
Proposed Solution
The proposed solution is to follow Clang's model: When compiling for cross-crate
LTO, no machine code is generated and the .o file is actually LLVM bitcode.
There are special "fat" object files that contain regular machine code and
additionally, in a special section, uncompressed LLVM bitcode. These fat objects
would mainly be used for the standard library.
The consequences of this approach are:
- Code compiled by the user (i.e. everything except
libstd) would only be either
machine code or LLVM bitcode. - When compiling an RLIB
rustcneeds to know if it is intended to be used for
LTO or not. Thus Cargo needs to invokerustcdifferently. - We save quite a bit of space and time in the common case.
- Because the "fat" object files are a standard LLVM thing,
libstdcan partake
in LTO steps performed by the LLVM linker plugin.
Open questions
- Do we need to keep things backwards compatible by defaulting to "fat" object files? If the new default is to not store LLVM bitcode in RLIBs, then non-cargo build systems would start to silently not do LTO.
- My preference would be to make no-bitcode the new default, but make the compiler emit a warning if wants to do LTO but encounters rlibs without bitcode.
- Should rustc be able to transparently handle bitcode-only RLIBs in the non-LTO case?
- My preference would be yes. Possibly also add a warning that that is unexpected?
- LLVM's WASM implementation doesn't seem to support "fat" object files. I don't know if this is a fundamental restriction. I guess WASM projects are actually more likely to use LTO than regular projects. If WASM doesn't support custom sections, then we could also keep the current setup of having LLVM bitcode in a separate file in the RLIB archive. Maybe @alexcrichton or @fitzgen know more about WASM object files?
Steps to get there
That actually depends on how we resolve the open questions above.
cc #66598
Reacted by Nicholas Nethercote, est31, Josh Triplett, Victor Ding, Ryan Leckey and Felipe S. S. Schneider
Reacted by Hanna Kruppe, Victor Ding, Ryan Leckey, Hadrien G., Ohad Ravid, jyn and Felipe S. S. Schneider
Metadata
Metadata
Assignees
Labels
C-enhancementCategory: An issue proposing an enhancement or a PR with one.Category: An issue proposing an enhancement or a PR with one.I-compiletimeIssue: Problems and improvements with respect to compile times.Issue: Problems and improvements with respect to compile times.I-heavyIssue: Problems and improvements with respect to binary size of generated code.Issue: Problems and improvements with respect to binary size of generated code.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.Relevant to the compiler team, which will review and decide on the PR/issue.
Activity
alexcrichton commentedon Dec 2, 2019
Thanks for writing this up @michaelwoerister! This all sounds spot on to me, and my thoughts on the open questions would be:
I think we have a lot of liberties here to do whatever change we want. It's basically an implementation detail that compressed bitcode is in each rlib, so we basically need to ensure that use cases today continue to work tomorrow. Once we do that I think it's reasonably for us to both switch the defaults and to completely remove all support for compressed llvm bitcode.
One unfortunate consequence of this, though, will likely be that we need to default to emitting "fat" object files. For example today you can do:
and that "just works". If we were to omit bitcode entirely it wouldn't work as expected (would either not actually do LTO or would generate an error). So I think that the backwards compatible way to implement this change would be:
-C linker-plugin-ltoflag triggers object files in rlibs to be bitcode. No machine code is generated.-C llvm-bc-in-object=falseflag would be added to rustc. This would disable embedding LLVM bitcode in object files, aka not creating "fat" object files but rather just machine code ones.llvm-bc-in-object=falseby default, unless it thinks a dependency is going to be used for LTO. It could even get more clever and skip things like proc-macro LTO.linker-plugin-lto, but we're not actually performing linker-plugin-lto. I would need to investigate, but ideally rustc would literally passlinker-plugin-ltoto all dependencies, meaning rlibs only have*.ofiles that are actually LLVM bitcode. Iflinker-plugin-ltoon dependencies triggers other sorts of logic though we may need to add a dedicated-C object-is-llvm-bitcodeflag (or something like that).rustcitself is performing LTO it looks for*.ofiles isntead of*.bc.zfiles. This logic update would probably check crate metadata to see whether it's an llvm bitcode file or a fat object file.Given all that I think we can preserve everyone's use case today, while also switching all users to faster compilations by default. If you're not performing LTO, you never generate bitcode files. If you're performing LTO, you never generate machine code in intermediate rlibs.
I would personally weakly say "no" to be more strict, but I don't feel stringly either way. I feel like though there's not much reason to support this and it's easier to implement an error rather than codegen'ing more object files on the fly.
I do think it's pretty critical to keep LTO working for wasm. This is actually somewhat unfortunate though. In the meantime I think we'll either have to (a) work with LLVM to implement this because wasm does have custom sections, or (b) we'll need to keep logic for including
*.bcfiles in an rlib and a wasm-specific hook which says "go look for*.bcinstead of looking at*.o" if it's a "fat" object file.For LTO-ready rlibs on wasm though the
*.ofiles would actually be*.bc, it's only the "fat object file" rlibs, like libstd, which would have this extra*.bcfile.RalfJung commentedon Mar 25, 2020
@michaelwoerister is there a link missing here?
michaelwoerister commentedon Mar 25, 2020
@nnethercote's original PR (linked above) has some quite compelling numbers: #66598 (comment)
Rollup merge of rust-lang#70384 - nnethercote:refactor-object-file-ha…
Rollup merge of rust-lang#70384 - nnethercote:refactor-object-file-ha…
nnethercote commentedon Apr 2, 2020
#70458 is for the rustc side of things. rust-lang/cargo#8066 is for the Cargo side of things.
nnethercote commentedon Apr 21, 2020
#70458 turned out to be over-ambitious, and has been closed. #71323 is the more restrained alternative.
nnethercote commentedon Apr 22, 2020
#71323 has landed.