-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tracking issue for MIR-only RLIBs #38913
Comments
This is also potentially breaking people who are linking to rlibs expecting them to at least expose the extern I did that at least once before, though the application where I did it was already very hacky for other reasons and I do not think the project is around anymore. |
Another advantage I see is that pure MIR RLIBs effectively let us "recompile" Basically, Other case where one uses Xargo to recompile the cc @brson ^ pure MIR RLIBs would eliminate the need for std-aware Cargo and I expect the above will also make using sanitizers (cf #38699) cc @alexcrichton ^ relevant to sanitizer support
This would be required for the "easy sanitizers" scenario I'm describing above. Also, note that today one can build statically linked Rust programs using the |
@japaric that’s trickly advantage as it prevents us from adding any MIR optimisations that depend on the codegen options set :) We already have one which acts upon the |
@nagisa @japaric isn't platform independence listed in the issue description as non advantage?
I'd add to the advantages that it would add more parallelism, as the passes up to MIR being finished take less time than passes up to codegen being finished, and in combination with |
Could you elaborate on how it would prevent you from adding such optimizations? The way I see it is that the If you want the most optimized code possible then, yeah, you would have to use Xargo or std-aware Cargo to opt into MIR optimizations that depend on codegen options. While you are at it you can also throw in |
I agree that MIR optimisations don't really prevent you to have platform agnostic MIR. As both their input and output is MIR, those optimisations could be run in the leaf crates, once the target and other info is known. However, if earlier stages in the compiler depend on the target, which is the case with cfg, one would either have to refactor the entire compiler to understand cfg's in all later stages, or simulate compilation with all possible combinations of cfg's enabled/disabled (in the end cfg is an on/off question). The first approach will probably hugely bloat code complexity of the compiler, the second approach would bloat runtime complexity exponentially by the number of kinds of used cfg's. So MIR will probably stay platform dependent for some time. |
I'm not sure what are trying to get at? The |
@japaric Removing landing pads from MIR is already somewhat a problem since you cannot add them back after fact, so you already lose some of the so-called advantage by being unable to reverse that. Later on we might want to add something more invasive. For a completely hypothetical example consider something resembling autovectorisation which, again, is not exactly reversible and thus So, what I’m trying to say is that specifying codegen options on leaf crates only would still not be equivalent (and diverge more over time with extra hypothetical MIR opts) to specifying the codegen option(s) for every crate. You could (as @est31 did just now) argue for storing unoptimised MIR instead, but that, in addition to inreasing size of intermediate rlibs, serializes MIR opts.
Codegen options aren’t exactly related to platform independence in this context. |
I'm not sure if this can be listed as an advantage but pure MIR RLIBs would have prevented #38824. The TL;DR is that LLVM raises assertions when you try lower functions that take/return |
Ah, sorry, I've misread, you only talked about codegen options. |
I don't think it will affect const evaluation one way or another, but it would help us test Miri outside rustc to be able to easily build dependencies as MIR-only rlibs (with MIR for all items, not just generic/inline/constant ones like in the existing metadata). Largely, Miri is just like another backend in this context, so it is an instance of this previously mentioned advantage:
|
MSVC using |
Regarding (I would have more sympathy if someone could give a good reason for using rlibs as archives that isn't already covered by |
I'm very enthusiastic about this. I think separating the type checking and code generation into two phases is smart no matter exactly the strategy for when the MIR finally get translated. Gives us a lot of flexibility for coordinating the build. For example, we don't have to delay code generation until the final crate. Cargo itself could spawn parallel processes to do code generation for already-typechecked crates, while their downstreams continue type checking. By collapsing duplicate monomorphizations, I'm hopeful that this will lead to significant improvements to the major disadvantages of monomorphization, the bloat and the compile time. We could end up in a position where we can say, "the generics model is like C++, but more efficient". That could be a major advantage. One significant disadvantage with this model is link-time scalability. This will put massive memory pressure on the leaf crate builds, and that could bite us in the future as bigger projects are written in Rust. |
LTO is a downside too because of compile time. I'd expect we'd need a range of strategies for the actual codegen, to accomplish different goals in |
I’ve very worried about this for Servo. There’s currently 319 crates in the dependency graph, but after an initial build only a few of them are recompiled in the typical edit-build-test cycle. Even so, compile times are already pretty bad. Do MIR-only rlibs mean doing code generation for the entire dependency graph every time? This sounds like unacceptable explosion of compile times. |
@SimonSapin I see no point in experimenting with this on Servo's scale without enabling incremental recompilation (with ThinLTO in the future, too). Btw I hear @rkruppe is making good progress towards such a compilation mode. |
We discussed this in the last @rust-lang/tools meeting and the consensus was that this looks like a good idea in many ways but we will not pursue it as long as it would mean a significant compile regression. |
So then we'll be pursuing this as soon as Rust is able to fully take advantage of incremental compilation using ThinLTO? |
Given the recent work to make the compiler incremental, I wonder if it will be possible to perform incremental builds at the level of individual functions, caching anything that hasn't changed. That could allow amazing feats, such as executables that are incrementally updated as the user compiles their source code. |
Just jotting this down before I forget about it again: Currently, So it would be cleaner to also delay translation of statics to the final binary/staticlib/cdylib. This requires non-trivial refactoring though, as a lot of the current code is written under the assumption that all statics to translate come from the current crate (e.g., It also means metadata needs a way to enumerate all the statics and other collector roots (monomorphic functions, and some more things in "eager" mode) from other crates. The information is all there, but there's no efficient/easy way to enumerate them. |
uuh i'm scratching my head what i'm missing here: how is this gong to work with -C linker= ? We're relying on the fact that the hash of the input file to the linker is the same every invocation. If the objects get translated to native code before being passed to the linker, is the translation stable? Or will the targets system linker actually only ever see a single already relocated and re-ordered object file ? |
This issue only affects which Rust code (or monomorphization of generic Rust code) gets translated into which LLVM compilation unit. It doesn't affect what happens afterwards with these LLVM modules, the resulting object files, etc. — and while it's plausible that MIR-only rlibs would enable more innovation in the later stages of the backend, nothing along those lines has been proposed or even discussed as far as I remember. |
Another (marginal) benefit, assuming |
I've put together a proof-of-concept implementation of this in #48373. Although the implementation crashes for many crates, I was able to collect timings for a number of projects. The tables show the aggregate time spent for various tasks while compiling the whole crate graph. In many cases we do less work overall but due to worse parallelization, wall-clock time increases. I.e. everything seems to be bottlenecked on the MIR-to-LLVM translation in the leaf crates. To me this suggests that MIR-only RLIBs are blocked on the compiler internals being parallelized. ripgrep - cargo build
encoding-rs - cargo test --no-run
webrender - cargo build
futures-rs - cargo test --no-run
tokio-webpush-simple - cargo build
Number of LLVM function definitions generated for whole crate graph
|
What if we did this, but for (prompted by @dwijnand's comments on Discord about their workflow of changing EDIT: here's some data, since I wanted to replicate what @dwijnand was seeing:
Most of the time is spent building libstd, which should be improved once #53673 ends up in beta (perhaps at the cost of the |
Now that cargo passes |
It turns out I was confused - this issue is about never going through LLVM at all, while |
Visiting during T-compiler backlog bonanza meeting. We decided that this is at best S-tracking-unimplemented, but it also just represents an experimental idea that was being floated around, not something that anyone committed to. Thus, it doesn't deserve a tracking issue: It deserves a separate MCP at this point. Closing. |
There's been some talk about switching RLIBs to "MIR-only", that is, make RLIBs contain only the MIR representation of a program and not the LLVM IR and machine code as they do now. This issue will try to collect some advantages, disadvantages, and other concerns such an approach would entail:
Advantages
-C metadata
).libstd
is compiled with-Cdebuginfo=1
, which is good in general but as a side-effect increases the size of Rust binaries, even if they are built without debuginfo (because the debuginfo fromlibstd
gets statically linked into the binaries). This problem would not exist with MIR-only rlibs.libstd
(see LeakSanitizer, ThreadSanitizer, AddressSanitizer and MemorySanitizer support #38699), as @japaric points out.libstd
) can be compiled with-C target-cpu=native
, potentially resulting in better code, as @japaric points out.Disadvantages
pub #[no_mangle]
items being exported from RLIBs and link against them directly. This would not be possible anymore, as @nagisa points out.Non-Advantages
cfg
switches, MIR is not platform independent either.Mitigation strategies for disadvantages:
-C codegen-units
already, which provides a means of reducing super-linear optimizations.Open Questions
Please help collect more data on the viability of MIR-only RLIBs.
cc @rust-lang/core @rust-lang/compiler @rust-lang/tools @rkruppe
The text was updated successfully, but these errors were encountered: