Conversation
Signed-off-by: Xiangfei Ding <dingxiangfei2009@protonmail.ch>
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
Some changes occurred in cc @BoxyUwU Some changes occurred in match checking cc @Nadrieril This PR changes MIR cc @oli-obk, @RalfJung, @JakobDegen, @vakaras This PR changes rustc_public cc @oli-obk, @celinval, @ouz-a, @makai410 Some changes occurred to MIR optimizations cc @rust-lang/wg-mir-opt Some changes occurred to constck cc @fee1-dead Some changes occurred to the CTFE / Miri interpreter cc @rust-lang/miri Some changes occurred in compiler/rustc_codegen_cranelift cc @bjorn3 Some changes occurred to the CTFE machinery Some changes occurred in src/tools/clippy cc @rust-lang/clippy |
|
r? compiler |
| /// Wraps a value in an unsafe binder. | ||
| WrapUnsafeBinder(Operand<'tcx>, Ty<'tcx>), | ||
|
|
||
| /// A reborrowable type being reborrowed |
There was a problem hiding this comment.
Please explain the runtime behavior of this operation.
The interpreter implementation seems to indicate that this is equivalent to a normal assignment of Copy($place). Why is it even a separate statement?
There was a problem hiding this comment.
Right, my apologies, I definitely should've added more information around this: I added a link to the experiment tracking issue into the opening comment which contains some more information and links but basically: in the trivial case (#[repr(transparent)]-compatible memory layout of types and a single lifetime) Reborrow is a Copy plus extra borrow checking analysis to perform the same reborrowing operation as happens with &'a mut T, but now on some user-defined ADT struct Custom<'a>(..). CoerceShared is then the &'a mut T -> &'a T equivalent of this operation and thus also requires a user-defined target type.
This PR does not implement any non-trivial cases so in both cases the runtime effect is exactly equivalent to a Copy, but future expansions should make CoerceShared possible even if the layouts of the source and target types do not match, including the possibility for the target type to drop out some fields from the source type. (This has a requested use-case where a custom exclusive reference type has some write-time metadata which is then not necessary in a read-only reference.)
There was a problem hiding this comment.
I added a link to the experiment tracking issue
That link is broken btw.
future expansions should make CoerceShared possible even if the layouts of the source and target types do not match, including the possibility for the target type to drop out some fields from the source type.
That sounds like an operation that is too complicated to be a single MIR statement, it should instead be encoded as a sequence of simpler operations. Otherwise every MIR consumer has to re-implement all that logic, partially defeating the point of having an IR like MIR in the first place.
There was a problem hiding this comment.
Agh, thank you: fixed the link in the comment.
That sounds like an operation that is too complicated to be a single MIR statement, it should instead be encoded as a sequence of simpler operations. Otherwise every MIR consumer has to re-implement all that logic, partially defeating the point of having an IR like MIR in the first place.
This sounds reasonable to me; the "non-trivial" reborrow case where multiple exclusive references participate in the reborrow at the very least would make sense as a combination of trivial reborrows. How to design a good decomposition of CoerceShared is less clear to me. For eg. a CustomMut<'a>(*mut T, PhantomData<&'a mut ()>) to CustomRef<'a>(*const T, PhantomData<&'a ()>) should it be a (field-wise?) copy plus then an entirely separate MIR action which relates the two lifetimes? Or should it be a single MIR action that performs the copy and the lifetime relation at the same time, like it is now?
The first one seems overly complicated for this particular case, but the second case does not really work if CustomMut instead looks like CustomMut<'a>(*mut T, usize, PhantomData<..>).
There was a problem hiding this comment.
Yes, someone will have to work out a design for that and present it in a structured form -- maybe not an RFC since it's largely an internal compiler design choice, but something similar. Probably an MCP, since it seems like a fairly big change.
There was a problem hiding this comment.
Why does it need to be distinguished in MIR from a regular copy? To be special-cased in the borrow checker? Would making it an Rvalue also serve that goal?
There was a problem hiding this comment.
Exactly, it's for borrow checker special casing. It does also appear as an Rvalue::Reborrow but my understanding here is absolutely too shallow to really comment on whether that or the MIR operation or both serve the goal, or if one or the other is actually redundant and unnecessary.
There was a problem hiding this comment.
Definitely don't add two new operations. Please understand that any new MIR operation incurs a non-trivial cost on the rest of rustc and some of the surrounding ecosystem (e.g. Rust verification tools such as Verus or Kani). Be economical about adding new primitives.
There was a problem hiding this comment.
When you say "two new operations", do you specifically mean that there should either only be a reborrow/coerce shared MIR op XOR a Rvalue::Reborrow variant? As said, my understanding is shallow so I don't exactly know if both are strictly necessary or if we have simply made a judgement error in the implementation. A special MIR operation seems to make sense as we need to reborrow potentially through a PhantomData here, so that's definitely a new primitive sort of thing. Why the Rvalue was created I'm less clear on, though basically all of the actual operations are defined on it so it does seem relatively necessary in that sense, while the MIR operation is the one that actually produces the Rvalue.
But the aim absolutely is to produce something that is, as much as possible, trivial from a performance point of view, doesn't burden the ecosystem overmuch, and preferably is useful/powerful/restricted enough that it could be considered for replacing &mut at some later point in the future if so desired. But it definitely is also a new primitive (unfortunately) for now even if it were to replace the existing borrow one way down the line, since Rust currently has no way of reasoning about "reborrowing" a PhantomData<&mut ()> since of course that's just a Copy ZST and lifetime.
If you have any insight or suggestions on how to proceed to a more economical design here / what seems reasonable to you, I'd be really happy to hear absolutely any and all of them :) The possible paths that I see, including dumb ones, are:
- Current path with a MIR operation and Rvalue variant, working only on trivial treborrows (types with one lifetime, no changes in memory layout)
- Only MIR operation producing normal values (unclear where the current impl code moves but I assume it's possible?), only on trivial treborrows
- Only Rvalue (how do we produce this without a new MIR op?), only on trivial reborrows
- Special
PhantomExclusive(and its dualPhantomShared) type which is the only trivially reborrowable type (aside from&mut), possibly handled by the current Borrow Rvalue? - Complex reborrows: cram everything into the special MIR op, making it a very complex thing indeed
- Complex reborrows: produce these through effectively destructure+restructure, so a single complex Reborrow decomposes into a bunch of trivial ones and any associated data field copies as need be.
I'm absolutely trying to be all ears here: I want generic reborrowing in Rust, but I want it to be an unobtrusive expansion instead of an earth-shattering change and a horrible pain and annoyance on everyone working within the compiler and on the language. I don't want to muck this up in any way, shape, or form.
There was a problem hiding this comment.
Oh, this is a new Rvalue variant, not a new StatementKind. I had lost context some time since I started this thread. Sorry for the confusion.
I agree having that makes sense (but also see Oli's comment -- reusing an existing variant would be even better), but the variant should be documented a bit more. And if the op.sem of this variant becomes more complicated in the future, we may have to reconsider the best way to go about this.
| } | ||
|
|
||
| adjustment::Adjust::GenericReborrow(_reborrow) => { | ||
| // Do the magic! |
There was a problem hiding this comment.
Oh we should call delegates. See the ReborrowPin arm and that is basically what we want to do.
And eventually we should be able to subsume ReborrowPin case hopefully.
| // `&mut T: DerefMut` tho, so it's kinda moot. | ||
| } | ||
| Adjust::GenericReborrow(_) => { | ||
| // No effects to enforce here. |
There was a problem hiding this comment.
We should do something here. The type would change after a generic reborrow, ie the CoerceShared case.
|
Where can I read context on what CoerceShared is/why it exists, and what the design for reborrow traits is |
| block.and(PlaceBuilder::from(temp)) | ||
| } | ||
| ExprKind::Reborrow { source: _, mutability: _, ty: _ } => { | ||
| todo!(); |
There was a problem hiding this comment.
We should generate a temporary. Therefore, we can merge this case one above, in other words build a temporary place, so that the reborrowing receives its own region "token."
I added a link to the experiment tracking issue but put it very shortly: |
|
I think I'll just |
Thank you <3 my top secret plan (designed together with tmandry) was to get a compiler review and then reroll into types to that view as well so that works well :) |
Appreciated. |
| if let Err(terr) = | ||
| self.sub_types(rv_ty, place_ty, location.to_locations(), category) | ||
| // Note: we've checked Reborrow/CoerceShared type matches | ||
| // separately in fn visit_assign. |
There was a problem hiding this comment.
This being requires is another sign that this may not be the best representation.
From other use sites it seems to me like you'd want to generalize Rvalue::Ref to support more types than references
With this PR we now have basic functional Reborrow and CoerceShared traits. The current limitations are:
&mutwrappers is working (though I've not tested generic wrappers likeOption<&mut T>yet), but CoerceShared of&mutwrappers currently causes an ICE.The remaining tasks to complete before I'd consider this PR mergeable are:
Reborrow traits experiment: #145612