Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upDo move forwarding on MIR #32966
Comments
pcwalton
added
I-slow
C-enhancement
A-mir
labels
Apr 14, 2016
This comment has been minimized.
This comment has been minimized.
|
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);
} |
This comment has been minimized.
This comment has been minimized.
|
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. |
nikomatsakis
added
the
T-lang
label
Jun 3, 2016
This comment has been minimized.
This comment has been minimized.
|
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. |
This comment has been minimized.
This comment has been minimized.
|
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. |
This comment has been minimized.
This comment has been minimized.
|
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 = tmp1After 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 = tmp1With 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 = tmp1Now 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 = tmp1Then the actual borrows: tmp1 = None
tmp4 = var0
var0 = tmp1
tmp1 = tmp4
var1 = tmp1Destination propagation on tmp1 = None
var1 = var0
var0 = tmp1Source propagation on var1 = var0
var0 = NoneThe last one is the only hard step IMO, although it might not be harder than destination propagation. |
This comment has been minimized.
This comment has been minimized.
|
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:
|
pcwalton
referenced this issue
Jun 12, 2016
Closed
[WIP] [MIR] Generic lattice-based dataflow framework, rebased #34164
This comment has been minimized.
This comment has been minimized.
|
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. |
This comment has been minimized.
This comment has been minimized.
|
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 |
cmr
referenced this issue
Jul 21, 2016
Closed
implement named return value optimization in the frontend #788
pcwalton
referenced this issue
Aug 8, 2016
Open
`array.push(Foo { ... })` should construct `Foo` in-place #35531
jrmuizel
referenced this issue
Aug 3, 2017
Closed
Inefficient codegen for function returning String #34861
This comment has been minimized.
This comment has been minimized.
|
Triage: not aware of any movement on this issue. |
pcwalton commentedApr 14, 2016
It'd be cool to rewrite chains of the form "a moves to b, b moves to c" to "a moves to c".