-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Missed optimization: multiple instances of a small struct don't reuse the stack allocation #141649
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
Comments
Can you explain how? That issue is about a missing MIR optimization, and this issue is about an LLVM optimization. |
I thought it might be related to that issue because there's a "missing" |
I suspect the desired optimization depends on unresolved details of Rust's operational semantic (e.g., rust-lang/unsafe-code-guidelines#188). Does moving out of |
For example, miri (without any flags, at least) doesn't take any issue with this program that stashes a pointer to use std::ptr;
use std::cell::Cell;
thread_local !{
static LAST_X: Cell<*const u32> = const { Cell::new(ptr::null()) };
}
fn peek(x: &u32) -> u32 {
let last_x: *const u32 = LAST_X.get();
let result: u32 = if last_x.is_null() {
*x
} else {
unsafe { *last_x }
};
LAST_X.set(x);
result
}
fn main() {
let x1 = 5;
dbg!(peek(&x1));
let x2 = 8;
dbg!(peek(&x2));
} |
The discussion in #138544 about storage markers is not related. It is very easy to think you've found a pattern in MIR that can be optimized on with trivial analysis but write a pass that is unsound. I've done it too. To this specific issue, I think the storage markers are what we want in |
In the MIR I'm looking at the relevant locals in |
Ah! I was looking at the wrong locals. You are right. |
@hanna-kruppe that's a very surprising example 😮 I think there's something more going on. If I use blocks like this: pub fn offsets(buf: [u8; 16]) {
{
let w = WithOffset {
data: &buf,
offset: 0,
};
peek_w(&w);
use_w(w);
}
{
let w2 = WithOffset {
data: &buf,
offset: 1,
};
peek_w(&w2);
use_w(w2);
}
} There are still be two fn main() {
{
let x1 = 5;
dbg!(peek(&x1));
}
{
let x2 = 8;
dbg!(peek(&x2));
}
} so I maybe in this case the semantics are defined? |
Yeah, I haven't looked into it any further, but I would expect that adding blocks so that the first local is dead before the second one is introduced would allow overlapping their storage. |
In a variant with separate scopes #141649 (comment) overlapping storage of |
I don't think it's fair to call this just a mir-opt bug, because even without any optimizations we still generate MIR that is incompatible with the desired optimization. |
Uh oh!
There was an error while loading. Please reload this page.
When creating multiple instances of a small struct, each instance will be allocated separately on the stack even if they are known never to overlap.
Example: the following code will generate two
alloca
calls that are not optimized away by LLVM:(Godbolt)
LLVM IR:
It seems like a call to
@llvm.lifetime.{start,end}.p0
is missing. If we instead use:We do get them and the second
alloca
is optimized away (see the Godbolt link).I encountered this when working on memorysafety/rav1d#1402, where this misoptimization results in over 100 bytes of extra allocations in a specific function, which slows down the entire binary by ~0.5%.
This might also be related to #138544
The text was updated successfully, but these errors were encountered: