-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Do move forwarding on MIR #32966
Comments
What you describe seems to be "destination (back-)propagation" aka NRVO. let x = f();
if g() {
let y = x;
} Turns into: y = f();
if g() {
drop(y);
} else {
forget(y);
} |
I'm thinking about suggesting that @scottcarr takes this on. We have to be a bit careful because it interacts with the "memory model" discussion, but I imagine we can start by being conservative, and in particular trying to cleanly separate the part that decides whether or not an intervening write/read is a problem. |
We discussed this in the @rust-lang/compiler meeting yesterday. There was general consensus that there are in fact 3 related transformations we might perform:
The end-goal is to improve certain MIR builder patterns that we have to generate initially but which often introduce unnecessary temporaries, as well as for general optimization purposes. For example, one suboptimal pattern is around aggregates, where an expression like
assuming that the expressions don't look at
We might get there in one of two ways. The first might be to "de-aggregate" the aggregate expression, resulting in:
this would then be combined with destination propagation to yield the desired result. Another variation might be to have destination propagation understand aggregates, resulting in:
and then the final assignment can be recognized as a no-op. Personally the first route seems a bit simpler to me. (Note that we still need to generate the aggregates because they are important to borrowck; but after static analysis is done, they are not needed.) Another example of intermediate operands that are not needed are the arguments to binary expressions or calls. For example a call like
This can be significantly optimized to Finally there are some classic cases from C++ where destination propagation (aka NRVO) can help: fn foo() -> [u8; 1024] {
let x = [0; 1024];
x // will interact w/ debuginfo
} Here the goal would be to remove the variables and temporaries and just write the array directly into the return pointer. |
One complication: in the formulations above, you can see that I made reference to things like "... (does not access lvalue)". We need to decide what kind of code may legally "access" an lvalue, particularly around usafe/unsafe code. Given that we are actively debating this, the best thing is to try and isolate the code that makes these decisions so we can easily keep it up to date. For now, some simple rules might be to forego optimization in methods containing unsafe code. Some other simple possibilities for predicates:
Note that in safe code the borrow checker rules can help us as well. |
My suggestion around quantifying lvalue accesses is that we do not do anything special about unsafe code, but instead rely on the property that a local cannot be accessed indirectly without borrowing it. Most cases with moves that I can think of don't involve borrows, and the ones that do have to do with unsafe APIs such as tmp0 = &mut var0
tmp1 = None
tmp2 = &mut tmp1
tmp3 = tmp0 as *const T
tmp4 = ptr::read(tmp3)
tmp5 = tmp2 as *const T
tmp6 = tmp0 as *mut T
ptr::copy(tmp5, tmp6)
tmp7 = tmp2 as *mut T
ptr::write(tmp7, tmp4)
var1 = tmp1 After expanding intrinsics: tmp0 = &mut var0
tmp1 = None
tmp2 = &mut tmp1
tmp3 = tmp0 as *const T
tmp4 = *tmp3
tmp5 = tmp2 as *const T
tmp6 = tmp0 as *mut T
*tmp6 = *tmp5
tmp7 = tmp2 as *mut T
*tmp7 = tmp4
var1 = tmp1 With a bit of reaching definition analysis we can make those accesses direct, assuming we're not violating any MIR rules (the LLVM IR would be identical anyway, so this couldn't cause UB without another MIR pass that assumes otherwise): tmp0 = &mut var0
tmp1 = None
tmp2 = &mut tmp1
tmp3 = tmp0 as *const T
tmp4 = var0
tmp5 = tmp2 as *const T
tmp6 = tmp0 as *mut T
var0 = tmp1
tmp7 = tmp2 as *mut T
tmp1 = tmp4
var1 = tmp1 Now here's the cool part: we might not be able to do much becase of the borrows, but we can eliminate dead rvalues with no side-effects, first the casts: tmp0 = &mut var0
tmp1 = None
tmp2 = &mut tmp1
tmp4 = var0
var0 = tmp1
tmp1 = tmp4
var1 = tmp1 Then the actual borrows: tmp1 = None
tmp4 = var0
var0 = tmp1
tmp1 = tmp4
var1 = tmp1 Destination propagation on tmp1 = None
var1 = var0
var0 = tmp1 Source propagation on var1 = var0
var0 = None The last one is the only hard step IMO, although it might not be harder than destination propagation. |
If you put the "return an array" example into play you get the following MIR:
If we do the simple case and eliminate the temporary only (which avoids interaction with debuginfo, at least to start) then we'd expect to get:
|
I'm starting to think about implementing what @nikomatsakis called "destination propagation." I made a internals post about it here: https://internals.rust-lang.org/t/mir-move-up-propagation/3610 My current plan is to implement something pretty simple first and later iterate. Also I didn't read #34164 even though I am commenting after it. Sorry! I will read it soon to see if there is overlap. |
I've written a transform that can identify candidates for destination propagation. It would be nice to have some more examples (preferably in rust). My branch: https://github.com/scottcarr/rust/tree/move-up-propagation2 |
Triage: not aware of any movement on this issue. |
Just ran into (the lack of) this, as it made some embedded code use significantly more stack RAM than expected. I have a 1K buffer type that is returned down the call-stack, and ends up using a total of 4-5K maximum stack in the call chain (this is somewhat significant, as the device has a total of 64K of RAM). @pftbest put together a reasonable minified version of my problem, showing 2x stack usage over what is necessary with a single function call. It might be useful for anyone looking for a test case. pub fn bar() {
let x = BigTest::new();
}
pub struct BigTest {
arr: [u32; 128]
}
impl BigTest {
pub fn new() -> BigTest {
BigTest {
arr: [123; 128],
}
}
} example::bar:
sub rsp, 520
lea rdi, [rsp + 8]
call qword ptr [rip + example::BigTest::new@GOTPCREL]
add rsp, 520
ret
.LCPI1_0:
.long 123
.long 123
.long 123
.long 123
example::BigTest::new:
push rbx
sub rsp, 512
mov rbx, rdi
movaps xmm0, xmmword ptr [rip + .LCPI1_0]
movaps xmmword ptr [rsp], xmm0
movaps xmmword ptr [rsp + 16], xmm0
movaps xmmword ptr [rsp + 32], xmm0
movaps xmmword ptr [rsp + 48], xmm0
movaps xmmword ptr [rsp + 64], xmm0
movaps xmmword ptr [rsp + 80], xmm0
movaps xmmword ptr [rsp + 96], xmm0
movaps xmmword ptr [rsp + 112], xmm0
movaps xmmword ptr [rsp + 128], xmm0
movaps xmmword ptr [rsp + 144], xmm0
movaps xmmword ptr [rsp + 160], xmm0
movaps xmmword ptr [rsp + 176], xmm0
movaps xmmword ptr [rsp + 192], xmm0
movaps xmmword ptr [rsp + 208], xmm0
movaps xmmword ptr [rsp + 224], xmm0
movaps xmmword ptr [rsp + 240], xmm0
movaps xmmword ptr [rsp + 256], xmm0
movaps xmmword ptr [rsp + 272], xmm0
movaps xmmword ptr [rsp + 288], xmm0
movaps xmmword ptr [rsp + 304], xmm0
movaps xmmword ptr [rsp + 320], xmm0
movaps xmmword ptr [rsp + 336], xmm0
movaps xmmword ptr [rsp + 352], xmm0
movaps xmmword ptr [rsp + 368], xmm0
movaps xmmword ptr [rsp + 384], xmm0
movaps xmmword ptr [rsp + 400], xmm0
movaps xmmword ptr [rsp + 416], xmm0
movaps xmmword ptr [rsp + 432], xmm0
movaps xmmword ptr [rsp + 448], xmm0
movaps xmmword ptr [rsp + 464], xmm0
movaps xmmword ptr [rsp + 480], xmm0
movaps xmmword ptr [rsp + 496], xmm0
mov rsi, rsp
mov edx, 512
call qword ptr [rip + memcpy@GOTPCREL]
mov rax, rbx
add rsp, 512
pop rbx
ret Link on Godbolt: https://godbolt.org/z/AHzinO |
@jamesmunns I can't wait to get back to #47954, hopefully within the coming months. |
@jonas-schievink should this be |
It'd be cool to rewrite chains of the form "a moves to b, b moves to c" to "a moves to c".
The text was updated successfully, but these errors were encountered: