-
Notifications
You must be signed in to change notification settings - Fork 13.3k
Allow calling *const methods on *mut values #82436
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
Conversation
r? @lcnr (rust-highfive has picked a reviewer for you, use r? to override) |
This comment has been minimized.
This comment has been minimized.
I'd like to get some reviews at this point. I didn't remove the log lines as I'll probably need them again. Who knows about method probing? Who should I ping? |
☔ The latest upstream changes (presumably #82530) made this pull request unmergeable. Please resolve the merge conflicts. |
r? @nikomatsakis maybe 🤔 |
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.
Looking good! Left some comments after a first pass.
assert!(pick.unsize.is_none()); | ||
|
||
if pick.to_const_ptr { |
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.
This could use a comment -- what case is it handling?
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.
to_const_ptr
is only set when the receiver object has type *mut
and the method's self is *const
. Quoting the documentation:
/// Only valid when the receiver type is a `*mut`: coerce the receiver to `*const`. If this it
/// `true` then `autoref` should be `None`.
pub to_const_ptr: bool,
I will add a comment here.
assert_eq!(*mutbl, hir::Mutability::Mut); | ||
self.tcx.mk_ptr(ty::TypeAndMut { mutbl: hir::Mutability::Not, ty }) | ||
} | ||
_ => panic!(), |
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.
Also, why is panic a reasonable thing to do 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.
If pick.to_const_ptr
is set then the receiver must be a *mut
and the method's self type must be *const
. to_const_ptr
basically says "coerce mut ptr to const ptr". So if it's set, then target must be RawPtr
, otherwise it's a bug and we shouldn't have set to_const_ptr = true
.
I will add a panic message.
@@ -170,6 +170,10 @@ pub struct Pick<'tcx> { | |||
/// B = A | &A | &mut A | |||
pub autoref: Option<hir::Mutability>, | |||
|
|||
/// Only valid when the self type is a `*mut`: coerce the receiver to `*const`. If this it | |||
/// `true` then `autoref` should be `None`. |
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.
Nice comment :)
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 reworded this slightly, saying "Only valid when the receiver type ..." instead of "... self type ...".
@@ -1143,6 +1160,33 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { | |||
}) | |||
} | |||
|
|||
/// If `self_ty` is `*mut T` then this picks `*const T` methods. The reason why we have a | |||
/// special case for this is because going from `*mut T` to `*const T` with autoderefs and | |||
/// autorefs would require dereferencing the pointer, which is not safe. |
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.
IIUC, it's not so much that the pointer would be dereferenced, but more that adding an autoref would create a safe reference, which we don't want to do.
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 just tried this:
struct Test {}
impl Test {
fn x(&self) {
println!("x called");
}
}
fn main() {
let mut test = Test {};
let mut test_mut_ref = &mut test;
test_mut_ref.x();
}
the relevant, unoptimized MIR:
_4 = &(*_2);
_3 = Test::x(move _4) -> [return: bb1, unwind: bb2];
I thought the *
dereferences, no? Similar code for the pointer version would also do &*
and dereference.
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.
You're not wrong, the *
does dereference -- it's just that, when you combine it with a &
, the net effect is kind of an "identity transform", at least at runtime. That is, &*x
is sort of equivalent to x
(the "address of the data that x
points to" is kind of just "x") -- but only sort of, because now we've made a shared reference, and that has implications (e.g., if x: &mut T
, it implies that *x
is frozen while this shared reference is live).
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 this the case even when result of &
is a pointer, rather than reference? I'm guessing the final assembly for &*
(for pointer-to-pointer conversions) may disappear, but I thought in MIR it's still dereferencing, and has the implications of *
operation (whatever those implications are).
Instead what we do here is more like x as *const T
(where x : *mut T
), which doesn't have unsafe dereferencing in MIR, and as a result would not have the same implications.
With the last commit I ended up rewriting most of it, sorry for that! The main change is, the pub struct Pick<'tcx> {
...
/// Indicates that we want to add an autoref (and maybe also unsize it), or if the receiver is
/// `*mut T`, convert it to `*const T`.
pub autoref_or_ptr_adjustment: Option<AutorefOrPtrAdjustment<'tcx>>,
} The /// When adjusting a receiver we often want to do one of
///
/// - Add a `&` (or `&mut`), converting the recevier from `T` to `&T` (or `&mut T`)
/// - If the receiver has type `*mut T`, convert it to `*const T`
///
/// This type tells us which one to do.
///
/// Note that in principle we could do both at the same time. For example, when the receiver has
/// type `T`, we could autoref it to `&T`, then convert to `*const T`. Or, when it has type `*mut
/// T`, we could convert it to `*const T`, then autoref to `&*const T`. However, currently we do
/// (at most) one of these. Either the type has `T` and we convert it to `&T` (or with `mut`), or
/// it has type `*mut T` and we convert it to `*const T`.
#[derive(Debug, PartialEq, Clone)]
pub enum AutorefOrPtrAdjustment<'tcx> {
/// Receiver has type `T`, add `&` or `&mut` (it `T` is `mut`), and maybe also "unsize" it.
/// Unsizing is used to convert a `[T; N]` to `[T]`, which only makes sense when autorefing.
Autoref {
mutbl: hir::Mutability,
/// Indicates that the source expression should be "unsized" to a target type. This should
/// probably eventually go away in favor of just coercing method receivers.
unsize: Option<Ty<'tcx>>,
},
/// Receiver has type `*mut T`, convert to `*const T`
ToConstPtr,
} With this type, the assertions in @nikomatsakis, sorry for the large refactoring. Another review would be very helpful now. Thanks! reviewing the comments would be especially helpful as I'm not sure if I'm 100% right in some places. I will squash the commits in the final version. |
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.
This looks good to me! I left a nit about an improved comment.
/// type `T`, we could autoref it to `&T`, then convert to `*const T`. Or, when it has type `*mut | ||
/// T`, we could convert it to `*const T`, then autoref to `&*const T`. However, currently we do | ||
/// (at most) one of these. Either the type has `T` and we convert it to `&T` (or with `mut`), or | ||
/// it has type `*mut T` and we convert it to `*const T`. |
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.
It may be worth adding a note that while we could do both at the same time, it would have a different meaning -- that is, converting to a &T
first would create a safe reference, which asserts (among other things) that the reference is aligned, etc.
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 what you're saying is, if we allowed both autoref'ing converting a ref/ptr to const ptr, the order we do those would matter. Obviously the type of the result will be different, other than that:
- Autoref, convert to const (
*const T
): The pointer will be aligned (I'm not sure if I get this part right, I don't know about reference semantics in detail), among other things (what other things? I'll ask on Zulip) - Convert to const first, then autoref (
&*T
): the pointer may not be aligned.
Did I get it right?
I'll try to update the comment.
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 sounds roughly correct, yes. One nit:
The pointer will be aligned
It's not that the pointer will be aligned, so much as that it would be UB if the pointer were not aligned (i.e., it'd be incorrect code, and the compiler might misoptimize it).
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 how to best document this, mostly because I don't know the semantics with references and pointers in detail.
If the comments starting with "Note that ..." is confusing currently I could delete them. Would that be an improvement @nikomatsakis ?
@osa1 feel free to squash the commits |
Thanks @nikomatsakis . I'll definitely squash the commits once all is done. |
@nikomatsakis I've squashed the commits. |
@bors r+ |
📌 Commit 98fbc09 has been approved by |
nice work, @osa1 ! |
Thanks @nikomatsakis ! I'm aware that some of my comments may not be too clear, I'm happy to update them now or later if you have any suggestions. |
☀️ Test successful - checks-actions |
This led to a number of sizeable - 5-8% - regressions on several crates. @nikomatsakis @osa1 do you think this is just the cost of adding this additional ability is just necessary? maybe there's some suboptimal bits in the code added that could be polished? |
FWIW it also looks like this was a regression in memory usage - https://perf.rust-lang.org/compare.html?start=4d76b4ca52a65d63ab83d82d6630f1df8ec05a93&end=f42888c15fd370b8bca4c5646ffc3aac3005dca8&stat=max-rss |
@Mark-Simulacrum Interesting. If this causes regression in max residency, then I think the new I'll debug this. In the meantime please feel free to revert this, I'll hopefully submit a new PR with lower RSS. |
To test that |
@osa1 you could also try boxing the |
Thanks @jyn514 . I'll try that once I figure out how to collect RSS metrics and compare results. |
I'm unable to replicate perf results locally, but I compared size of I'll try to reduce size of |
I'm still baffled. The new code (called in So in the benchmarks the new code should never be executed, and any increase in runtime, or instructions, should be because of the extra 8 byte allocation. Is |
I think I figured it out. PR coming. |
FTR, #83293 fixes the perf issues introduced with this patch. |
Does this need |
I think bug fixes don't appear in release notes, right? If that's the case then I guess this is a question of whether this is a change in the specification (in the language, or the I had checked the lang spec a while ago and couldn't see anything relevant to method calls on pointer types (which makes sense, this is an unstable extension). |
This allows
*const
methods to be called on*mut
values.TODOs:
Remove debug logsDone.I haven't tested, but I think this currently won't work when theThis works, because autoderefs are done before callingself
value has type like&&&&& *mut X
because I don't do any autoderefs when probing. To fix this the new code inrustc_typeck::check::method::probe
needs to reusepick_method
somehow as I think that's the function that autoderefs.pick_core
, inmethod_autoderef_steps
, called byprobe_op
.I should probably move the newDone.Pick
topick_autorefd_method
. If not, I should move it to its own function.Test this with aI think this case cannot happen, because we don't have any array methods withPick
withto_ptr = true
andunsize = true
.*mut [X]
receiver. I should confirm that this is true and document this. I've placed two assertions about this.Maybe giveI now have a(Mutability, bool)
a name and fieldsto_const_ptr
field inPick
.Changes inThere's still a special case foradjust_self_ty
is quite hacky. The problem is we can't deref a pointer, and even if we don't have an adjustment to get the address of a value, so to go from*mut
to*const
we need a special case.to_const_ptr
, but I'm not sure if we can avoid this.reached_raw_pointer
stuff is used. I suspect only for error messages.Fixes #80258