-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Reading from the return place is fine #71005
Conversation
cc @rust-lang/wg-const-eval |
Ah, this is an ooooold assertion in Miri, and I wondered about it multiple times. But I am also a bit worried that there might be some things Miri is doing that actually rely on this being UB... For once, in a function call Also, if the return type is a ZST, is it okay for the return place to be "dangling" or even NULL? (Miri currently avoids actually allocating memory for the return place in some situations where we know the return type to be |
@RalfJung My understand is that something like That is, a miri can do the same thing:
|
But what about let _ptr = &raw mut _7;
_7 = foo(ptr); What exactly is (All of this will be a nightmare to test as I assume we cannot even generate such MIR.)
That sounds like a special hack needs to be added to a bunch of places. So far, Miri has exactly one explicit check for whether things overlap, and that is to validate I feel something is very deeply wrong with any proposal that requires explicit overlap checks. The easiest answer, I think, is to say that However, I do not know whether this accurately models our LLVM codegen. |
If the return value can't be returned in one or more registers, the a pointer to the return place is passed to the function as argument and then any writes to Function prologue: rust/src/librustc_codegen_ssa/mir/mod.rs Lines 204 to 208 in 1f3b659
rust/src/librustc_codegen_ssa/mir/block.rs Lines 1171 to 1183 in 1f3b659
rust/src/librustc_codegen_ssa/mir/block.rs Lines 257 to 260 in 1f3b659
|
The goal of NRVO is to optimize this to pass the return value pointer to // Something large.
struct Buf([u8; 128]);
fn fill(buf: &mut Buf) {...}
fn make() -> Buf {
let mut buf = Buf::new();
fill(&mut buf);
buf
} |
@RalfJung I think a good way to (conservatively) model our codegen is to |
Arguments are operands, not places, so I don't know what it means the However, I am somewhat dissatisfied with how much of a special-case-hack this is -- typically we make reborrowing explicit in MIR, but here, that is not possible because w can only access the return place, but never access it as a reference. |
An alternative would be to actually do this "allocate a dedicated return place" dance, consistently with any other local. You said yourself The question then is how to justify codegen. Basically codegen would have to ensure that the temporary that is used for the return place never had its address taken. Currently that is the case, but I am not sure if it always will be. |
|
Wait, are you saying that |
Yuuupp, although not necessarily in a way in which the caller can observe changes to I have been annoyed by Remember #68364? These two are basically equivalent (assuming dest = move src; dest = call identity(move src); In that for non- |
For MIR optimizations like NRVO it's important that calls can write the output directly into some variable, that may be borrowed before/after the call (checking the borrows don't overlap the calls at optimization time, not at codegen time). |
Oookay... I think we need an issue to figure out the semantics of MIR function calls, because I think Miri does not currently do what you say the semantics are.
Sure, but that's an optimization. There is no problem with this as long as it behaves observably entirely equivalent to reserving some dedicated memory for the return place, and copying later. |
I understand that's what they lower to in LLVM, but I am not convinced that they should actually be equivalent operations on the MIR level. Now this makes me wonder if we could also use such a "mutable reborrow" trick for assignments to avoid an explicit overlap check? (However, |
Yes, I was talking specifically about the restriction you suggested, and that I quoted, being undesirable. |
Complicating MIR semantics is also undesirable though, at least if we want to also use this to precisely capture Rust semantics. I am beginning to wonder if we should have "simple MIR" and "optimized MIR" with two distinct semantics, where the former is designed for simplicity and used as the target of MIR building, and then we prove that for what MIR building creates, both semantics are equivalent, and then go on using optimized MIR semantics with awful things like sharing the return place between caller and callee. |
Yeah we can be conservative and treat overlaps are errors in miri even codegen wouldn't introduce UB. |
Let us continue the semantic discussion in #71117. @jonas-schievink for your PR, I think if we want to make I left a note in the linked issue for how that could be done:
I think that would be preferable over the approach you are currently pursuing. Let me know if you need any help. There is a chance of this costing performance, so we should perf-test it -- but in terms of code simplification I think this would totally be worth it. |
Not sure if this is what you had in mind (it doesn't get rid of |
This comment has been minimized.
This comment has been minimized.
No that's not what I had in mind -- but it is an interesting idea. This now does both Stacked-Borrows-style exclusive reservation of the caller-provided return place, and a separate copy. The separate copy helps simplify Miri but reserving the caller-provided return place should mean that eliding the copy during lowering should always be sound... @eddyb what do you think? |
Those disappeared after a rebase, which I don't quite understand. It does seem to work fine now though, yeah. |
☀️ Try build successful - checks-azure |
Queued ab35b24df7cbdea1c9d064420530a62e86efacaf with parent 339a938, future comparison URL. |
Finished benchmarking try commit ab35b24df7cbdea1c9d064420530a62e86efacaf, comparison URL. |
Hmm, looks like ctfe-stress-4 regresses by up to 8%. This might be acceptable since it's a stress test, but it would be nice to improve that somehow. However, since I expect this to be caused by the fact that we always allocate at least one local now, I'm not quite sure how (ideas welcome!). |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Copy its value to the `return_place` upon leaving a call frame
Mostly renamed allocations, but I'm not sure about the const prop tests
@jonas-schievink so here's one idea, or a direction at least. With this PR, we actually allocate the return place for every constant twice: once at
and once in push_stack_frame . We should find a way to avoid that.
|
// Copy the return value to the caller's stack frame. | ||
if let Some(return_place) = frame.return_place { | ||
let op = self.access_local(&frame, mir::RETURN_PLACE, None)?; | ||
self.copy_op_transmute(op, return_place)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if we can pull this off, but maybe we can (for non-immediates) make the return place alias with the frame's RETURN_PLACE
local. This would be doable by changing the LocalState
of the RETURN_PLACE
to Live(Operand::Indirect(return_place))
(well you still need to convert the https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/interpret/struct.PlaceTy.html to an MPlaceTy, but that should be doable (and where not, we have an immediate, which is cheap and we can just keep copying it here)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I was wondering the same thing. We need to be careful then when popping the frame to not do copy_op
(as that would overlap itself) but still do validation.
The main subtlety here is making sure this is a Miri-internal optimization that cannot be observed by the program. The fact that we reborrow the return place should exclude any observations by accessing the return place directly... so I think the only remaining possible observation is through address equality tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So... about this PR. I'd be fine merging it with the 8% regression on the stress tests (with an open issue for fixing it again). Then we can experiment with this local aliasing trick in a PR that does nothing but the optimization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would be nice. I don't think I have the bandwidth for these deeper changes to const eval.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that something we should nominate for T-compiler decision?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think @rust-lang/wg-const-eval can make this decision on our own, since it's just a stress test being regressed. Regressing the test is not an issue, we just have the test to make sure we don't do so accidentally
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, fine for me. @ecstatic-morse what do you think?
@jonas-schievink can you create an issue describing the perf regression and linking to this discussion?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Opened #71463
📌 Commit 415fd0c has been approved by |
Rollup of 6 pull requests Successful merges: - rust-lang#71005 (Reading from the return place is fine) - rust-lang#71198 (Const check/promotion cleanup and sanity assertion) - rust-lang#71396 (Improve E0308 error message wording again) - rust-lang#71452 (Remove outdated reference to interpreter snapshotting) - rust-lang#71454 (Inline some function docs in `core::ptr`) - rust-lang#71461 (Improve E0567 explanation) Failed merges: r? @ghost
…chievink add back Scalar::null_ptr We were a bit overeager with removing this in rust-lang#71005, Miri uses this function quite a bit. The important part is that the `Place::null` methods are gone. :) r? @jonas-schievink @oli-obk Fixes rust-lang#71474
Const eval thinks that reading from local
_0
is UB, but it isn't._0
is just a normal local like any other, and codegen handles it that way too. The only special thing is that theReturn
terminator will read from it.I've hit these errors while working on an NRVO pass that can merge other locals with
_0
in #71003.r? @oli-obk