Repository navigation
consteval: Rework caller location handling - #163746
Conversation
a401e74 to
48523ba
Compare
| // Figure out if the callee receives a caller location argument. | ||
| // In the case where we are calling the fallback body of | ||
| // a `#[rustc_intrinsic] #[track_caller] fn`, we pretend that | ||
| // the caller doesn't pass a caller location argument (for checking ABI), | ||
| // but the caller receives a caller location argument from thin air anyway. | ||
| let callee_receives_caller_location = with_caller_location | ||
| || (instance.def.requires_caller_location(*self.tcx) | ||
| && matches!(instance.def, InstanceKind::Item(def_id) if self.tcx.intrinsic(def_id).is_some())); | ||
|
|
There was a problem hiding this comment.
This is extremely hacky, but I don't have a better idea how to handle this.
There was a problem hiding this comment.
Yeah this is very strange. What problem is this even solving?
There was a problem hiding this comment.
For a #[rustc_intrinsic] #[track_caller] fn, there are two Instances involved.
- An
InstanceKind::Intrinsic. This is what the caller of the intrinsic "sees".requires_caller_locationreturnsfalse. - An
InstanceKind::Item. This is the fallback body, and is therefore what the callee "sees".requires_caller_locationreturnstrue.
In Machine::call_intrinsic, when such an intrinsic is called, "replaces" the former Instance with the latter Instance. However, it does not recheck to see if the new Instance needs a caller location.
callee_receives_caller_location is whether the callee receives a caller location argument, regardless of whether the caller passes one. These two things can mismatch for #[rustc_intrinsic] #[track_caller] fn.
Now that I think about it again, I probably should move this hack to next to the Machine::call_intrinsic call.
There was a problem hiding this comment.
For a #[rustc_intrinsic] #[track_caller] fn
I am not sure that's a combination anyone ever thought about. But having two difference instances like that with different signature sounds like a bug. Cc @oli-obk
There was a problem hiding this comment.
Some more context at #t-compiler/help > Help with `async_fn_track_caller` @ 💬
IIRC, codegen explicitly handles this case by replacing a call to the InstanceKind::Intrinsic with a call to the InstanceKind::Item, and the replacement happens before the decision on whether to pass an extra caller location argument.
There was a problem hiding this comment.
Intrinsics in the stdlib that are currently #[track_caller]: unchecked_disjoint_bitor, unchecked_funnel_shl, unchecked_funnel_shr
There was a problem hiding this comment.
Sigh, what a mess.
For us, that replacement happens around here:
rust/compiler/rustc_const_eval/src/interpret/call.rs
Lines 769 to 779 in 693bf50
So we could probably update with_caller_location there?
There was a problem hiding this comment.
Yeah, that's what I meant by "I probably should move this hack to next to the Machine::call_intrinsic call."
This comment has been minimized.
This comment has been minimized.
48523ba to
e0647c4
Compare
This comment has been minimized.
This comment has been minimized.
e0647c4 to
ab6523e
Compare
|
r? @RalfJung |
|
|
|
Some changes occurred to the CTFE machinery
cc @rust-lang/miri Some changes occurred to the CTFE / Miri interpreter cc @rust-lang/miri |
ab6523e to
e620c7f
Compare
| let _trace = enter_trace_span!(M, step::init_stack_frame, %instance, tracing_separate_thread = Empty); | ||
| let def_id = instance.def_id(); | ||
|
|
||
| assert!(!with_caller_location || callee_receives_caller_location); |
There was a problem hiding this comment.
Why are these two separate flags and what is the point of this assertion?
| .collect::<Vec<_>>() | ||
| ); | ||
|
|
||
| self.frame_mut().track_caller_arg = caller_location; |
There was a problem hiding this comment.
| self.frame_mut().track_caller_arg = caller_location; | |
| // Initialize special caller_location "local", if this function has it. | |
| self.frame_mut().track_caller_arg = caller_location; |
| let def_id = instance.def_id(); | ||
|
|
||
| assert!(!with_caller_location || callee_receives_caller_location); | ||
| let caller_location = callee_receives_caller_location.then(|| self.caller_location()); |
There was a problem hiding this comment.
Doesn't the Instance tell us whether the callee will get a caller location?
| // we will not pass normally; our `caller_location` intrinsic implementation handles | ||
| // that separately). | ||
| assert_eq!( | ||
| args.len() + if with_caller_location { 1 } else { 0 }, | ||
| caller_fn_abi.args.len(), | ||
| "mismatch between caller ABI and caller arguments", | ||
| ); |
There was a problem hiding this comment.
So now we need caller location hacks in two places. If we could instead integrate this with the argument passing we'd reduce the number of hacks here rather than increasing them -- that would be a lot nicer I think.
| // Figure out if the callee receives a caller location argument. | ||
| // In the case where we are calling the fallback body of | ||
| // a `#[rustc_intrinsic] #[track_caller] fn`, we pretend that | ||
| // the caller doesn't pass a caller location argument (for checking ABI), | ||
| // but the caller receives a caller location argument from thin air anyway. | ||
| let callee_receives_caller_location = with_caller_location | ||
| || (instance.def.requires_caller_location(*self.tcx) | ||
| && matches!(instance.def, InstanceKind::Item(def_id) if self.tcx.intrinsic(def_id).is_some())); | ||
|
|
There was a problem hiding this comment.
Yeah this is very strange. What problem is this even solving?
| assert_eq!( | ||
| frame.track_caller_arg.is_some(), | ||
| frame.instance.def.requires_caller_location(*self.tcx) | ||
| ); |
There was a problem hiding this comment.
Strange to check this here rather than during frame initialization.
There was a problem hiding this comment.
By the way, I just found out that this assertion fails on both of the code snippets in rust-lang/miri#5351
There was a problem hiding this comment.
Those snippets should cause UB before ever reaching the assertion, shouldn't they? So I think the assertion should be put back. The PR also still has a comment on the track_caller_arg field claiming this assertion as an invariant.
There was a problem hiding this comment.
The updated PR now detects UB at the point the mismatched function call happens.
There was a problem hiding this comment.
Yes. But asserting the invariant here would still be useful IMO. We have a lot of assertions in the interpreter because it's so easy to accidentally invalidate an invariant.
| /// frame is popped. | ||
| pub(super) va_list: Vec<MPlaceTy<'tcx, Prov>>, | ||
|
|
||
| /// The implicit caller location argument that is passed by this function's caller, if any. |
There was a problem hiding this comment.
| /// The implicit caller location argument that is passed by this function's caller, if any. | |
| /// The implicit caller location argument that is passed by this function's caller, if any. | |
| /// This is `Some` if and only if `instance.def.requires_caller_location` returns `true`. |
| } | ||
|
|
||
| span_bug!(self.cur_span(), "no non-`#[track_caller]` frame found") | ||
| frame.body.caller_location_span(source_info, frame.track_caller_arg, *self.tcx, identity) |
There was a problem hiding this comment.
| frame.body.caller_location_span(source_info, frame.track_caller_arg, *self.tcx, identity) | |
| // Directly returning `frame.track_caller_arg` would be wrong if we are | |
| // in code that got inlined from another function. `caller_location_span` | |
| // handles that for us. | |
| frame.body.caller_location_span(source_info, frame.track_caller_arg, *self.tcx, identity) |
|
@rustbot author |
|
Reminder, once the PR becomes ready for a review, use |
|
@rustbot ready This PR now also fixes rust-lang/miri#5351 I'll squash the commits into one before merging. |
This comment has been minimized.
This comment has been minimized.
|
what on earth is going on with #131787 lol |
|
Calling caller_location, instead of removing it as in 974786e?
|
| // they go to. (For the former, we expect the caller to do any special handling | ||
| // that might be required here (e.g. for untupling)). |
There was a problem hiding this comment.
Why is this a parenthetical now?
The new wording isn't worse than the old one but also not better IMO so I am surprised to see a diff here.
There was a problem hiding this comment.
This was moved from being part of a separate paragraph into being in the same paragraph. I think it's clearer that the text is about a new "topic" if it's in parentheses.
There was a problem hiding this comment.
The old structure of the comment was as follows:
- There are two iterators
"where they come from" part is easy, we expect the caller to do any special handlingbegins setting up the first iteratorNow we have to spread them out across the callee's locals,begins setting up the 2nd iterator
I can see how that was not very clear, but your changes make it even worse IMO. I would instead suggest making the two sections this is delineating more explicit. (Happy to draft a commit for that if you prefer.)
| // the two iterators should again have the same length. | ||
| // | ||
| // We could have an assertion that checks the iterator lengths, but plumbing through | ||
| // the necessary information is annoying, so we don't bother. |
There was a problem hiding this comment.
I am very unhappy about losing this assertion. Checking the number of arguments is a pretty basic sanity check.
It is also very concerning that we'd be using the wrong caller ABI here. That's something we should fix, not sweep under the rug.
There was a problem hiding this comment.
Do you mean that something in the original code is incorrect, and we shouldn't need to add 1 for the caller location argument in the assertion?
There was a problem hiding this comment.
Or maybe.... I should re-compute the function ABI when I do the intrinsic fallback?
There was a problem hiding this comment.
I think I figured out a better way of doing this.
There was a problem hiding this comment.
Do you mean that something in the original code is incorrect, and we shouldn't need to add 1 for the caller location argument in the assertion?
Yeah. Apparently the ABI can change when swapping an intrinsic for its fallback, and the caller will actually use the updated ABI, so we have to re-compute the caller ABI.
| assert_eq!( | ||
| frame.track_caller_arg.is_some(), | ||
| frame.instance.def.requires_caller_location(*self.tcx) | ||
| ); |
There was a problem hiding this comment.
Those snippets should cause UB before ever reaching the assertion, shouldn't they? So I think the assertion should be put back. The PR also still has a comment on the track_caller_arg field claiming this assertion as an invariant.
|
Hm, this seems to have tested something that's not specific to static / const-eval at all, so the way it ICEs now is new. @JonathanBrouwer is there another way to test #105594 that doesn't rely on bogus MIR being passed to const-eval? |
| // Note: Promoteds don't require a caller location, | ||
| // even when inside an `Instance` with `#[track_caller]` | ||
| assert_eq!(body.arg_count, 0); | ||
| assert!(cid.promoted.is_some() || !cid.instance.def.requires_caller_location(tcx)); |
There was a problem hiding this comment.
Maybe an instead of an assertion we can make this a delay_bug, with a FIXME pointing at #131787 saying that it should be an assertion? The delay_bug gives us a ErrorGuaranteed, we can then call something like throw_inval!(AlreadyReported(ReportedErrorInfo::non_const_eval_error)) to avoid executing the bogus MIR.
There was a problem hiding this comment.
Done. However, this seems to have gotten rid of the ICE in #131787 entirely. What should I now do with tests/crashes/131787.rs?
There was a problem hiding this comment.
Both of these tests do almost the same thing, don't they? I'd suggest adding a panic to the body of the static in issue-105594-invalid-attr-validation.rs, then deleting the other test as a duplicate.
There was a problem hiding this comment.
Done. This PR should be ready to merge now.
This comment has been minimized.
This comment has been minimized.
|
I can't immediately think of anything, ideally the code from the current test should just not pass bogus MIR to const eval at all ofcourse |
45058f9 to
28f4050
Compare
Instead of walking up the call stack each time we need the caller location, we emulate the run time behavior of passing an implicit caller location argument in #[track_caller] functions. We record a representation of that argument in each frame. For inlined functions though, we still do a "stack walk" within that one frame.
28f4050 to
ba7e70d
Compare
|
Next try... |
|
⌛ Testing commit ba7e70d with merge 5055b09... Workflow: https://github.com/rust-lang/rust/actions/runs/39154127389 |
consteval: Rework caller location handling Instead of walking up the call stack each time we need the caller location, we emulate the run time behavior of passing an implicit caller location argument in `#[track_caller]` functions. We record a representation of that argument in each frame. For inlined functions though, we still do a "stack walk" within that one frame. This PR is a prerequisite for #163396 Fixes rust-lang/miri#5351
…on, r=RalfJung consteval: Rework caller location handling Instead of walking up the call stack each time we need the caller location, we emulate the run time behavior of passing an implicit caller location argument in `#[track_caller]` functions. We record a representation of that argument in each frame. For inlined functions though, we still do a "stack walk" within that one frame. This PR is a prerequisite for rust-lang#163396 Fixes rust-lang/miri#5351
|
@bors yield |
|
Auto build was cancelled. Cancelled workflows: The next pull request likely to be tested is #164531. |
Rollup merge of #163746 - theemathas:consteval-caller_location, r=RalfJung consteval: Rework caller location handling Instead of walking up the call stack each time we need the caller location, we emulate the run time behavior of passing an implicit caller location argument in `#[track_caller]` functions. We record a representation of that argument in each frame. For inlined functions though, we still do a "stack walk" within that one frame. This PR is a prerequisite for #163396 Fixes rust-lang/miri#5351
…uwer Rollup of 4 pull requests Successful merges: - rust-lang/rust#160912 (Rename `bstr` module/feature to `byte_str`) - rust-lang/rust#164463 (perf: inline `Clause::map_bound`) - rust-lang/rust#163746 (consteval: Rework caller location handling) - rust-lang/rust#163611 (add core::process::resumable_breakpoint)
View all comments
Instead of walking up the call stack each time we need the caller location, we emulate the run time behavior of passing an implicit caller location argument in
#[track_caller]functions. We record a representation of that argument in each frame. For inlined functions though, we still do a "stack walk" within that one frame.This PR is a prerequisite for #163396
Fixes rust-lang/miri#5351