You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
"discriminant_value" intrinsic returns u64, cannot fit all discriminants #70509
Ever since we had enums with repr(u128)/repr(i128), the type of the discriminant_value intrinsic was no longer adequate: it returns a u64, which simply cannot fit all discriminant values.
@eddyb says the type of the intrinsic should be adjusted. This will affect both its codegen and Miri implementation.
cc @rust-lang/libs I wonder if we're safe to change std::mem::discriminant.
We might need a crater run as I suspect there are people who transmute the result to u64.
I wouldn't have any sympathy for people transmuting to u64.
If you want to be slick, you could make std::mem::Discriminant<T> have the same size as T's discriminant, rather than always 8 bytes. This supports enums with a 128 bit discriminant without increasing the size of Discriminant in the common case.
Reacted by lcnr, Mazdak Farrokhzad, Lukas Kalbertodt, scottmcm and Elichai Turkel
The Discriminant type was originally made opaque due to exactly this issue IIRC (and a couple other reasons). If only there was a way to prohibit transmuting it, or mark the type Unsized...
@lcnr There’s no point in making the intrinsic hide its implementation details, as the intrinsics are inherently unstable.
Rather than perma-unstable, I think the trait could be private to libcore. (The Discriminant struct doesn’t need a trait bound since the trait needs to be implemented for all types anyway: for example std::mem::discriminant(&true) compiles today.)
An associated type in a trait is required because that’s how the compiler supports having a field of different types in struct Discriminant<T> { … } depending on T, but these are implementation details that don’t need to be visible in any public API.
I realized that this breaks PartialEq for repr128 enums:
you meant that the failure to represent 128-bit discriminants breaks PartialEq, right? That is, it's not that the approach of using an associated type of a magic trait breaks PartialEq, but rather the underlying bug.
Yes, in the current state PartialEq can lead to undefined behavior as discriminant_value truncates 128 bit discriminants. #70705 correctly compares the untruncated 128 bit values and fixes this issue.
My understanding is that in current Nightly, intrinsics::discriminant_value returns a u64 so u64::max_value() as i128 + 1 is incorrectly truncated to zero. Thus __self_vi == __arg_1_vi is true even though the two values have different a variant, so control flow would go to the last branch of match but with intrinsics::unreachable we get UB.
One note would be that, eventually, it seems like we could stabilize the DiscriminantKind trait, though I would never permit user-provided implementations. It'd be nice to work towards a "minimal set of lang items" that we can use as the basis for the rest of the stdlib.
Although I guess that's an orthogonal thing -- i.e., in user code, Discriminant<T> is the stable way to access the "discriminant for this type". And the lang item itself is specific to how our stdlib impl works but not user exposed. Ok, never mind what I just wrote, except to note that exposing associated types of "magic traits" like T::Discriminant might be a nice option in the future.
So what is needed to unblock this? This PR is blocking some Miri cleanup of read_discriminant (that would be awkward to do right now because of the bug this fixes).
It seems like we're mostly good to go forward, there aren't (afaict) many public-facing implications of this beyond correctly handling larger discriminants.
Activity
eddyb commentedon Mar 28, 2020
cc @rust-lang/libs I wonder if we're safe to change
std::mem::discriminant.We might need a crater run as I suspect there are people who transmute the result to
u64.sfackler commentedon Mar 28, 2020
A crater run would be fine, but we should not block a change on people doing things like that IMO.
dtolnay commentedon Mar 28, 2020
I wouldn't have any sympathy for people transmuting to u64.
If you want to be slick, you could make std::mem::Discriminant<T> have the same size as T's discriminant, rather than always 8 bytes. This supports enums with a 128 bit discriminant without increasing the size of Discriminant in the common case.
lcnr commentedon Mar 28, 2020
Would the following work?
Add a (potentially hidden) permanently unstable trait
which is automatically implemented for every type and change
intrinsics::discriminantto returnDiscriminantKind::Discriminantinstead.mem::Discriminantshould now be able to use<T as DiscriminantKind>::Discriminantas its only field.This allows for
mem::Discriminantto be smaller in most cases while still being fairly simple to implement afaict.nagisa commentedon Mar 28, 2020
The
Discriminanttype was originally made opaque due to exactly this issue IIRC (and a couple other reasons). If only there was a way to prohibit transmuting it, or mark the type Unsized...@lcnr There’s no point in making the intrinsic hide its implementation details, as the intrinsics are inherently unstable.
lcnr commentedon Mar 28, 2020
The reason why I considered hiding it is to not pollute rustdoc by adding this (for the user irrelevant) impl to every type.
lcnr commentedon Apr 1, 2020
@rustbot claim
SimonSapin commentedon Apr 2, 2020
Rather than perma-unstable, I think the trait could be private to libcore. (The
Discriminantstruct doesn’t need a trait bound since the trait needs to be implemented for all types anyway: for examplestd::mem::discriminant(&true)compiles today.)An associated type in a trait is required because that’s how the compiler supports having a field of different types in
struct Discriminant<T> { … }depending onT, but these are implementation details that don’t need to be visible in any public API.lcnr commentedon Apr 2, 2020
@SimonSapin While it is not required as a bound for
Discriminant, it is required for the return type ofintrinsics::discriminant_valuewhich is public9 remaining items
nikomatsakis commentedon Apr 28, 2020
@lcnr to clarify, when you say
you meant that the failure to represent 128-bit discriminants breaks PartialEq, right? That is, it's not that the approach of using an associated type of a magic trait breaks PartialEq, but rather the underlying bug.
lcnr commentedon Apr 28, 2020
Yes, in the current state
PartialEqcan lead to undefined behavior asdiscriminant_valuetruncates 128 bit discriminants. #70705 correctly compares the untruncated 128 bit values and fixes this issue.lcnr commentedon Apr 28, 2020
can be thought of as
SimonSapin commentedon Apr 28, 2020
The output of
--pretty expandedfor #70509 (comment) includes:My understanding is that in current Nightly,
intrinsics::discriminant_valuereturns au64sou64::max_value() as i128 + 1is incorrectly truncated to zero. Thus__self_vi == __arg_1_viis true even though the two values have different a variant, so control flow would go to the last branch ofmatchbut withintrinsics::unreachablewe get UB.I believe #70705 would fix this.
nikomatsakis commentedon Apr 28, 2020
One note would be that, eventually, it seems like we could stabilize the
DiscriminantKindtrait, though I would never permit user-provided implementations. It'd be nice to work towards a "minimal set of lang items" that we can use as the basis for the rest of the stdlib.nikomatsakis commentedon Apr 28, 2020
Although I guess that's an orthogonal thing -- i.e., in user code,
Discriminant<T>is the stable way to access the "discriminant for this type". And the lang item itself is specific to how our stdlib impl works but not user exposed. Ok, never mind what I just wrote, except to note that exposing associated types of "magic traits" likeT::Discriminantmight be a nice option in the future.RalfJung commentedon May 2, 2020
So what is needed to unblock this? This PR is blocking some Miri cleanup of
read_discriminant(that would be awkward to do right now because of the bug this fixes).nikomatsakis commentedon May 4, 2020
@RalfJung by "this PR" you mean #70705 ?
nikomatsakis commentedon May 4, 2020
It seems like we're mostly good to go forward, there aren't (afaict) many public-facing implications of this beyond correctly handling larger discriminants.
RalfJung commentedon May 4, 2020
Sorry, I meant to post that in the PR #70705, yes.
Auto merge of rust-lang#86860 - fee1-dead:stabilize, r=LeSeulArtichaut