-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Implement pointee metadata unsizing via a TypedMetadata<T> container #97052
Conversation
Some changes occured to the CTFE / Miri engine cc @rust-lang/miri Hey! It looks like you've submitted a new PR for the library teams! If this PR contains changes to any Examples of
|
r? @cjgillot (rust-highfive has picked a reviewer for you, use r? to override) |
r? rust-lang/compiler @rustbot label +T-libs-api -T-libs +T-compiler I think that's the correct tags/team |
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.
looks good to me from the perspective of typeck and middle, i cannot vouch for the const-eval and codegen parts but they don't look unreasonable either.
library/core/src/ptr/metadata.rs
Outdated
/// assert_eq!(unsized_metadata.0, 5); | ||
/// ``` | ||
#[cfg_attr(not(bootstrap), lang = "just_metadata")] | ||
pub struct JustMetadata<T: ?Sized>(pub <T as Pointee>::Metadata); |
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 am not T-libs, but perhaps this could use some helper methods like from_raw_pointer(self, *const ()) -> *const T
which does ptr::from_raw_parts
. I don't know though.
Also, should this derive Copy
and other common traits?
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.
Deriving would use the wrong bounds (on T
instead of <T as Pointee>::Metadata
), but yes, this should probably get the other common traits. Mostly I just wanted to open the PR as soon as I saw it working.
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.
no worries, it doesn't need to be done in this PR.
This comment has been minimized.
This comment has been minimized.
@@ -194,21 +195,21 @@ pub fn unsize_ptr<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( | |||
src_ty: Ty<'tcx>, | |||
dst_ty: Ty<'tcx>, | |||
old_info: Option<Bx::Value>, | |||
) -> (Bx::Value, Bx::Value) { | |||
) -> OperandValue<Bx::Value> { |
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 will need to be implemented in cg_clif too. I can do this myself once this PR is merged.
@@ -342,7 +344,7 @@ pub fn coerce_unsized_info<'tcx>(tcx: TyCtxt<'tcx>, impl_did: DefId) -> CoerceUn | |||
let param_env = tcx.param_env(impl_did); | |||
assert!(!source.has_escaping_bound_vars()); | |||
|
|||
let err_info = CoerceUnsizedInfo { custom_kind: None }; | |||
let err_info = CoerceUnsizedKind::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.
Do we want to return something like None
(i.e. make this function return an Option
or Result
? I think CoerceUnsizedInfo { custom_kind: None }
was used here as both like CoerceUnsizedKind::Ptr
and the "uhh i don't know" coercion kind.
Either that, or we should make a CoerceUnsizedKind::Error
, though I'm not a fan of that.
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.
Like it seems the code below is returning err_info
on the error path (e.g. when we try to coerce two ADTs with different def_id
s).
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.
First of all, this PR definitely doesn't make the situation worse, since the same amount of information is produced. It seems at least somewhat consistent to "treat this as if a builtin pointer cast" (for the purpose of continuing reporting errors) if the coercion is bad (since we won't get to codegen anyway).
It's also worth noting where this is actually used, which seems to always be for side effects or go through custom_coerce_unsize_info
in practice, and CoerceUnsizedKind::Ptr
is only used as an error in find_vtable_types_for_unsizing
anyway
This comment has been minimized.
This comment has been minimized.
This error doesn't occur on x86_64 windows so I don't know how to go about debugging it. It was caused by 751e5e960f1388963e9e080fd12b6c014daa5ef1 deduplicating the unsize logic between operating on pointer |
Can you run a 32bit Windows target on a 64bit Windows host? That is probably enough to trigger the problem, if it is the usual pointer size mismatch kind of issue. |
Thanks for the PR, @CAD97. Is there some prior discussion about this somewhere? Is this something the lang-team needs to sign off on? |
T-lang should probably sign off on this direction being the chosen solution (rather than e.g. making Some recent discussion with @compiler-errors here (https://rust-lang.zulipchat.com/#narrow/stream/213817-t-lang/topic/unsizing.20pointee.20metadata); I know this has been generally desired (and @compiler-errors had an experiment at the same) but don't have any references at the moment, unfortunately. I should probably write down the issues I had trying to implement |
This comment has been minimized.
This comment has been minimized.
Realistic example: Consider a type |
That won't work anyway due to |
That coercion could work if Writing that out: this coercion itself actually does only need the ( |
I'm still not clear on why we want this to be a coercion. Is there a use case or snippet of code that shows why that would be needed? |
Here is an actual pseudo-worked example of a polymorphically typed generational arena. Details have been elided to better focus on the point of type Memory = [MaybeUninit<u8>];
struct Arena {
memory: Vec<(Generation, Vec<u8>)>,
/* elided: synchronization, ref-arena matching */
}
#[derive(Copy, Clone)]
type Generation = usize;
#[derive(Copy, Clone)]
struct GenerationalIndex {
index: usize,
generation: Generation,
}
#[derive(Copy, Clone)]
struct ArenaRef<T: ?Sized> {
index: GenerationalIndex,
meta: TypedMetadata<T>,
/* elided: ref-arena matching */
}
impl Arena {
fn index(&self, ix: GenerationalIndex) -> &Memory { /* elided */ }
fn resolve<T>(&self, ix: ArenaRef<T>) -> &T {
assert!(self.contains(ix));
let memory = self.index(ix.index);
unsafe { &*ptr::from_raw_parts(memory.as_ptr().cast(), ix.meta) }
}
/* elided: insertion, mutable access, other utilities */
}
trait Entity {
/* elided: generic behavior on any entity */
}
impl Entity for Player {}
struct Player {
/* elided */
}
impl Player {
fn attack(&self, ctx: &Arena, other: ArenaRef<dyn Entity>) { /* elided */ }
}
fn pvp(ctx: &Arena, player_1: ArenaRef<Player>, player_2: ArenaRef<Player>) {
ctx.resolve(player_1).attack(ctx, player_2);
ctx.resolve(player_2).attack(ctx, player_1);
} If you would like a more fully worked example, I can make a running example using explicit unsizing instead. |
☔ The latest upstream changes (presumably #98206) made this pull request unmergeable. Please resolve the merge conflicts. |
@CAD97 I'm not trying to be obtuse, but I think I'm still failing to follow how this relates to why we need to require coercion (as opposed to, for instance, an invoked operation). |
If we're okay with But unsizing for custom user types is desirable enough to have a conversion polyfill and The ultimate thing that makes this an actual requirement though is that I want So yes, the reasoning is the kind of circular "we need to be able to coerce metadata not on a pointer because (I believe) we want to be able to coerce metadata not on a pointer". This is essentially saying it's possible to implement But it's worth making explicit: while this PR is motivated by my Footnotes
|
FWIW I kind of liked The funny part is that it's basically just an alias for |
The one thing I worry about is that Perhaps if |
Based on @rustbot label S-blocked |
(This is specifically blocked on me reading over the proposal and the comment thread to better understand the problem being solved and the merits of the proposed solution.) |
I submitted a libs-api ACP about the The intent there is to cover the libs portion of what embracing this type would look like. Using this type as the type returned by |
I'm closing this for now as stale. I still hold that a |
TL;DR:
See src/test/ui/coercion/coerce-just-metadata.rs for an example of this working.
The motivating example is being able to spell a type like
and implement
CoerceUnsized
for it. This is shown to work in src/test/ui/coercion/silly-box.rs.It may be desirable to support coercing
DynMetadata<T>
in the future (e.g.DynMetadata<dyn Trait + Sync> -> DynMetadata<dyn Trait>
). This PR does not add this, but it could be added fairly easily by adding aCoerceUnsized
impl forDynMetadata
and allowingDynMetadata
whereTypedMetadata
is checked for here.FAQ
Isn't
TypedMetadata<T>
just a type alias for<T as Pointee>::Metadata
?Yes and no. The main problem at hand is that
<T as Pointee>::Metadata
no longer remembers whatT
is once the projection has been resolved.TypedMetadata<T>
then is a "strongly typed alias" which reintroducesT
and provides the ability to implementCoerceUnsized
, which cannot be directly implemented on a resolved<T as Pointee>::Metadata
.There is a potential alternative -- instead of getting
impl CoerceUnsized
to work forTypedMetadata<T>
as a builtin and then using the existingstruct
behavior forimpl CoerceUnsized
, we could teach coercion (andimpl CoerceUnsized
) to recognize the<T as Pointee>::Metadata
projection and directly coerce that (as is currently done inTypedMetadata<T>
) rather than delegating to the field'sCoerceUnsized
implementation. (However, this may be impractical, as type projections tend to get normalized away.)This would make
TypedMetadata
a pure library type, but it would still be necessary if wanting to coerce/convert<T as Pointee>::Metadata
in a function body or for some concreteT
, rather than as a generic struct field.Why a coercion instead of just a conversion?
Such that
SillyBox<T>
can be coerced. (See also the next question.)A conversion is fairly simple to write: just
ptr::from_raw_parts
a null data pointer and the metadata, then coerce that.Why not use
*mut T
instead of(*mut (), <T as Pointee>::Metadata)
?The short version: because you might not always have a pointer.
There are a few reasons you might want to elide storing the actual pointer-to-
T
, or store something other than a pointer alongside the pointee metadata.One such case is if you have some allocation at a statically known location; in this case it could make sense to have a
StaticRef
which just stores the pointee metadata and implementsDeref
by constructing a normal reference from the raw parts of the static location and the stored metadata. Normally such usage would necessarily also know the full concrete type and thus not need to use non-()
metadata, but situations such as using linker functionality to place a slice at a (linker-known) static address could want to haveStaticRef<[T]>
to avoid re-deriving the linker-provided length multiple times.Another such case is the use of something like an arena allocator, or a compacting allocator, or any storage allocation which doesn't hand out raw pointers and pin the allocated memory. Here, your
ArenaBox<T>
/ArenaRef<T>
would store the index into the arena (and perhaps additional metadata like a generation) and the pointee metadata, and then you ask the arena to temporarily resolve theArenaRef<T>
into a normal&T
.Yet another is
StackBox<T>
, which stores a potentially unsized pointee in its own inline memory, allowing for the use of unsized types (such asdyn Trait
) without indirection by always reserving a fixed max size/align for them on the stack.All of these could be serviced by storing the metadata on a dangling pointer; however, this extra stored pointer exists solely to appease the compiler, and not for any actual benefit to the program.