Skip to content

Commit

Permalink
document & impl the transmutation modeled by BikeshedIntrinsicFrom
Browse files Browse the repository at this point in the history
Documents that `BikeshedIntrinsicFrom` models transmute-via-union,
which is slightly more expressive than the transmute-via-cast
implemented by `transmute_copy`. Additionally, we provide an
implementation of transmute-via-union as a method on the
`BikeshedIntrinsicFrom` trait with additional documentation on
the boundary between trait invariants and caller obligations.

Whether or not transmute-via-union is the right kind of transmute
to model remains up for discussion [1]. Regardless, it seems wise
to document the present behavior.

[1] https://rust-lang.zulipchat.com/#narrow/stream/216762-project-safe-transmute/topic/What.20'kind'.20of.20transmute.20to.20model.3F/near/426331967
  • Loading branch information
jswrenn committed Aug 19, 2024
1 parent 91376f4 commit 60cfa88
Show file tree
Hide file tree
Showing 19 changed files with 192 additions and 31 deletions.
5 changes: 5 additions & 0 deletions compiler/rustc_ty_utils/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,11 @@ fn resolve_associated_item<'tcx>(
tcx.item_name(trait_item_id)
),
}
} else if tcx.is_lang_item(trait_ref.def_id, LangItem::TransmuteTrait) {
let name = tcx.item_name(trait_item_id);
assert_eq!(name, sym::transmute);
let args = tcx.erase_regions(rcvr_args);
Some(ty::Instance::new(trait_item_id, args))
} else {
Instance::try_resolve_item_for_coroutine(tcx, trait_item_id, trait_id, rcvr_args)
}
Expand Down
167 changes: 153 additions & 14 deletions library/core/src/mem/transmutability.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,81 @@
use crate::marker::{ConstParamTy_, UnsizedConstParamTy};

/// Are values of a type transmutable into values of another type?
/// Marks that `Src` is transmutable into `Self`.
///
/// This trait is implemented on-the-fly by the compiler for types `Src` and `Self` when the bits of
/// any value of type `Self` are safely transmutable into a value of type `Dst`, in a given `Context`,
/// notwithstanding whatever safety checks you have asked the compiler to [`Assume`] are satisfied.
/// # Implementation
///
/// This trait cannot be implemented explicitly. It is implemented on-the-fly by
/// the compiler for all types `Src` and `Self` such that, given a set of safety
/// obligations on the programmer (see [`Assume`]), the compiler has proved that
/// the bits of a value of type `Src` can be soundly reinterpreted as a `Self`.
///
/// Specifically, this trait models (and
/// [implements](BikeshedIntrinsicFrom::transmute)) the semantics of
/// transmute-via-union; i.e.:
///
/// ```rust
/// pub unsafe fn transmute_via_union<Src, Dst>(src: Src) -> Dst {
/// use core::mem::ManuallyDrop;
///
/// #[repr(C)]
/// union Transmute<Src, Dst> {
/// src: ManuallyDrop<Src>,
/// dst: ManuallyDrop<Dst>,
/// }
///
/// let transmute = Transmute {
/// src: ManuallyDrop::new(src),
/// };
///
/// let dst = transmute.dst;
///
/// ManuallyDrop::into_inner(dst)
/// }
/// ```
///
/// Note that this construction supports some transmutations that are unsound
/// with [`mem::transmute_copy`](super::transmute_copy); namely, transmutations
/// that extend the bits of `Src` with trailing padding to fill trailing
/// uninitialized bytes of `Self`; e.g.:
///
#[cfg_attr(bootstrap, doc = "```rust,ignore not runnable on bootstrap")]
#[cfg_attr(not(bootstrap), doc = "```rust")]
/// #![feature(transmutability)]
///
/// use core::mem::{Assume, BikeshedIntrinsicFrom};
///
/// let src = 42u8; // size = 1
///
/// #[repr(C, align(2))]
/// struct Dst(u8); // size = 2
//
/// let _ = unsafe {
/// <Dst as BikeshedIntrinsicFrom<u8, { Assume::SAFETY }>>::transmute(src)
/// };
/// ```
///
/// ## Portability
///
/// Implementations of this trait do not provide any guarantee of portability
/// across toolchains, targets or compilations. This trait may be implemented
/// for certain combinations of `Src`, `Self` and `ASSUME` on some toolchains,
/// targets or compilations, but not others. For example, if the layouts of
/// `Src` or `Self` are non-deterministic, the presence or absence of an
/// implementation of this trait may also be non-deterministic. Even if `Src`
/// and `Self` have deterministic layouts (e.g., they are `repr(C)` structs),
/// Rust does not specify the alignments of its primitive integer types, and
/// layouts that involve these types may vary across toolchains, targets or
/// compilations.
///
/// ## Stability
///
/// Implementations of this trait do not provide any guarantee of SemVer
/// stability across the crate versions that define the `Src` and `Self` types.
/// If SemVer stability is crucial to your application, you must consult the
/// documentation of `Src` and `Self`s' defining crates. Note that the presence
/// of `repr(C)`, alone, does not carry a safety invariant of SemVer stability.
/// Furthermore, stability does not imply portability. For example, the size of
/// `usize` is stable, but not portable.
#[unstable(feature = "transmutability", issue = "99571")]
#[lang = "transmute_trait"]
#[rustc_deny_explicit_impl(implement_via_object = false)]
Expand All @@ -13,28 +84,96 @@ pub unsafe trait BikeshedIntrinsicFrom<Src, const ASSUME: Assume = { Assume::NOT
where
Src: ?Sized,
{
/// Transmutes a `Src` value into a `Self`.
///
/// # Safety
///
/// The safety obligations of the caller depend on the value of `ASSUME`:
/// - If [`ASSUME.alignment`](Assume::alignment), the caller must guarantee
/// that the addresses of references in the returned `Self` satisfy the
/// alignment requirements of their referent types.
/// - If [`ASSUME.lifetimes`](Assume::lifetimes), the caller must guarantee
/// that references in the returned `Self` will not outlive their
/// referents.
/// - If [`ASSUME.safety`](Assume::safety), the returned value might not
/// satisfy the library safety invariants of `Self`, and the caller must
/// guarantee that undefined behavior does not arise from uses of the
/// returned value.
/// - If [`ASSUME.validity`](Assume::validity), the caller must guarantee
/// that `src` is a bit-valid instance of `Self`.
///
/// When satisfying the above obligations (if any), the caller must *not*
/// assume that this trait provides any inherent guarantee of layout
/// [portability](#portability) or [stability](#stability).
unsafe fn transmute(src: Src) -> Self
where
Src: Sized,
Self: Sized,
{
use super::ManuallyDrop;

#[repr(C)]
union Transmute<Src, Dst> {
src: ManuallyDrop<Src>,
dst: ManuallyDrop<Dst>,
}

let transmute = Transmute { src: ManuallyDrop::new(src) };

// SAFETY: It is safe to reinterpret the bits of `src` as a value of
// type `Self`, because, by combination of invariant on this trait and
// contract on the caller, `src` has been proven to satisfy both the
// language and library invariants of `Self`. For all invariants not
// `ASSUME`'d by the caller, the safety obligation is supplied by the
// compiler. Conversely, for all invariants `ASSUME`'d by the caller,
// the safety obligation is supplied by contract on the caller.
let dst = unsafe { transmute.dst };

ManuallyDrop::into_inner(dst)
}
}

/// What transmutation safety conditions shall the compiler assume that *you* are checking?
/// Configurable proof assumptions of [`BikeshedIntrinsicFrom`].
///
/// When `false`, the respective proof obligation belongs to the compiler. When
/// `true`, the onus of the safety proof belongs to the programmer.
/// [`BikeshedIntrinsicFrom`].
#[unstable(feature = "transmutability", issue = "99571")]
#[lang = "transmute_opts"]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct Assume {
/// When `true`, the compiler assumes that *you* are ensuring (either dynamically or statically) that
/// destination referents do not have stricter alignment requirements than source referents.
/// When `false`, [`BikeshedIntrinsicFrom`] is not implemented for
/// transmutations that might violate the the alignment requirements of
/// references.
///
/// When `true`, [`BikeshedIntrinsicFrom`] assumes that *you* have ensured
/// that that references in the transmuted value satisfy the alignment
/// requirements of their referent types.
pub alignment: bool,

/// When `true`, the compiler assume that *you* are ensuring that lifetimes are not extended in a manner
/// that violates Rust's memory model.
/// When `false`, [`BikeshedIntrinsicFrom`] is not implemented for
/// transmutations that extend the lifetimes of references.
///
/// When `true`, [`BikeshedIntrinsicFrom`] assumes that *you* have ensured
/// that that references in the transmuted value do not outlive their
/// referents.
pub lifetimes: bool,

/// When `true`, the compiler assumes that *you* have ensured that no
/// unsoundness will arise from violating the safety invariants of the
/// destination type (and sometimes of the source type, too).
/// When `false`, [`BikeshedIntrinsicFrom`] is not implemented for
/// transmutations that might violate the library safety invariants of the
/// destination type.
///
/// When `true`, [`BikeshedIntrinsicFrom`] assumes that *you* have ensured
/// that undefined behavior does not arise from using the transmuted value.
pub safety: bool,

/// When `true`, the compiler assumes that *you* are ensuring that the source type is actually a valid
/// instance of the destination type.
/// When `false`, [`BikeshedIntrinsicFrom`] is not implemented for
/// transmutations that might violate the language-level bit-validity
/// invariant of the destination type.
///
/// When `true`, [`BikeshedIntrinsicFrom`] assumes that *you* have ensured
/// that the value being transmuted is a bit-valid instance of the
/// transmuted value.
pub validity: bool,
}

Expand Down
1 change: 1 addition & 0 deletions library/core/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
#![feature(strict_provenance_atomic_ptr)]
#![feature(test)]
#![feature(trait_upcasting)]
#![feature(transmutability)]
#![feature(trusted_len)]
#![feature(trusted_random_access)]
#![feature(try_blocks)]
Expand Down
16 changes: 16 additions & 0 deletions library/core/tests/mem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -790,3 +790,19 @@ fn const_maybe_uninit_zeroed() {

assert_eq!(unsafe { (*UNINIT.0.cast::<[[u8; SIZE]; 1]>())[0] }, [0u8; SIZE]);
}

#[cfg(not(bootstrap))]
#[test]
fn transmute() {
use core::mem::{Assume, BikeshedIntrinsicFrom};

let src = 42u8; // size = 1

#[repr(C, align(2))]
struct Dst(u8); // size = 2

let Dst(dst) =
unsafe { <Dst as BikeshedIntrinsicFrom<u8, { Assume::SAFETY }>>::transmute(src) };

assert_eq!(src, dst);
}
2 changes: 1 addition & 1 deletion tests/mir-opt/issue_72181_1.main.built.after.mir
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ fn main() -> () {
StorageLive(_2);
StorageLive(_3);
_3 = ();
_2 = transmute::<(), Void>(move _3) -> bb4;
_2 = std::intrinsics::transmute::<(), Void>(move _3) -> bb4;
}

bb1: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
bb0: {
StorageLive(_2);
_2 = _1;
- _0 = transmute::<std::cmp::Ordering, i8>(move _2) -> [return: bb1, unwind unreachable];
- _0 = std::intrinsics::transmute::<std::cmp::Ordering, i8>(move _2) -> [return: bb1, unwind unreachable];
+ _0 = move _2 as i8 (Transmute);
+ goto -> bb1;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
bb0: {
StorageLive(_2);
_2 = _1;
- _0 = transmute::<std::cmp::Ordering, i8>(move _2) -> [return: bb1, unwind unreachable];
- _0 = std::intrinsics::transmute::<std::cmp::Ordering, i8>(move _2) -> [return: bb1, unwind unreachable];
+ _0 = move _2 as i8 (Transmute);
+ goto -> bb1;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
bb0: {
StorageLive(_2);
_2 = _1;
- _0 = transmute::<&T, *const T>(move _2) -> [return: bb1, unwind unreachable];
- _0 = std::intrinsics::transmute::<&T, *const T>(move _2) -> [return: bb1, unwind unreachable];
+ _0 = move _2 as *const T (Transmute);
+ goto -> bb1;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
bb0: {
StorageLive(_2);
_2 = _1;
- _0 = transmute::<&T, *const T>(move _2) -> [return: bb1, unwind unreachable];
- _0 = std::intrinsics::transmute::<&T, *const T>(move _2) -> [return: bb1, unwind unreachable];
+ _0 = move _2 as *const T (Transmute);
+ goto -> bb1;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

bb0: {
StorageLive(_1);
- _1 = transmute::<usize, Box<Never>>(const 1_usize) -> [return: bb1, unwind unreachable];
- _1 = std::intrinsics::transmute::<usize, Box<Never>>(const 1_usize) -> [return: bb1, unwind unreachable];
+ _1 = const 1_usize as std::boxed::Box<Never> (Transmute);
+ goto -> bb1;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

bb0: {
StorageLive(_1);
- _1 = transmute::<usize, Box<Never>>(const 1_usize) -> [return: bb1, unwind unreachable];
- _1 = std::intrinsics::transmute::<usize, Box<Never>>(const 1_usize) -> [return: bb1, unwind unreachable];
+ _1 = const 1_usize as std::boxed::Box<Never> (Transmute);
+ goto -> bb1;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

bb0: {
StorageLive(_1);
- _1 = transmute::<usize, &mut Never>(const 1_usize) -> [return: bb1, unwind unreachable];
- _1 = std::intrinsics::transmute::<usize, &mut Never>(const 1_usize) -> [return: bb1, unwind unreachable];
+ _1 = const 1_usize as &mut Never (Transmute);
+ goto -> bb1;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

bb0: {
StorageLive(_1);
- _1 = transmute::<usize, &mut Never>(const 1_usize) -> [return: bb1, unwind unreachable];
- _1 = std::intrinsics::transmute::<usize, &mut Never>(const 1_usize) -> [return: bb1, unwind unreachable];
+ _1 = const 1_usize as &mut Never (Transmute);
+ goto -> bb1;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

bb0: {
StorageLive(_1);
- _1 = transmute::<usize, &Never>(const 1_usize) -> [return: bb1, unwind unreachable];
- _1 = std::intrinsics::transmute::<usize, &Never>(const 1_usize) -> [return: bb1, unwind unreachable];
+ _1 = const 1_usize as &Never (Transmute);
+ goto -> bb1;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

bb0: {
StorageLive(_1);
- _1 = transmute::<usize, &Never>(const 1_usize) -> [return: bb1, unwind unreachable];
- _1 = std::intrinsics::transmute::<usize, &Never>(const 1_usize) -> [return: bb1, unwind unreachable];
+ _1 = const 1_usize as &Never (Transmute);
+ goto -> bb1;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
bb0: {
StorageLive(_2);
_2 = _1;
- _0 = transmute::<(), Never>(move _2) -> unwind unreachable;
- _0 = std::intrinsics::transmute::<(), Never>(move _2) -> unwind unreachable;
+ _0 = move _2 as Never (Transmute);
+ unreachable;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
bb0: {
StorageLive(_2);
_2 = _1;
- _0 = transmute::<(), Never>(move _2) -> unwind unreachable;
- _0 = std::intrinsics::transmute::<(), Never>(move _2) -> unwind unreachable;
+ _0 = move _2 as Never (Transmute);
+ unreachable;
}
Expand Down
4 changes: 2 additions & 2 deletions tests/ui/closures/coerce-unsafe-to-closure.stderr
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
error[E0277]: expected a `FnOnce(&str)` closure, found `unsafe extern "rust-intrinsic" fn(_) -> _ {transmute::<_, _>}`
error[E0277]: expected a `FnOnce(&str)` closure, found `unsafe extern "rust-intrinsic" fn(_) -> _ {std::intrinsics::transmute::<_, _>}`
--> $DIR/coerce-unsafe-to-closure.rs:2:44
|
LL | let x: Option<&[u8]> = Some("foo").map(std::mem::transmute);
| --- ^^^^^^^^^^^^^^^^^^^ call the function in a closure: `|| unsafe { /* code */ }`
| |
| required by a bound introduced by this call
|
= help: the trait `FnOnce(&str)` is not implemented for fn item `unsafe extern "rust-intrinsic" fn(_) -> _ {transmute::<_, _>}`
= help: the trait `FnOnce(&str)` is not implemented for fn item `unsafe extern "rust-intrinsic" fn(_) -> _ {std::intrinsics::transmute::<_, _>}`
= note: unsafe function cannot be called generically without an unsafe block
note: required by a bound in `Option::<T>::map`
--> $SRC_DIR/core/src/option.rs:LL:COL
Expand Down
4 changes: 2 additions & 2 deletions tests/ui/intrinsics/reify-intrinsic.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ LL | let _: unsafe extern "rust-intrinsic" fn(isize) -> usize = std::mem::tr
| expected due to this
|
= note: expected fn pointer `unsafe extern "rust-intrinsic" fn(isize) -> usize`
found fn item `unsafe extern "rust-intrinsic" fn(_) -> _ {transmute::<_, _>}`
found fn item `unsafe extern "rust-intrinsic" fn(_) -> _ {std::intrinsics::transmute::<_, _>}`

error[E0606]: casting `unsafe extern "rust-intrinsic" fn(_) -> _ {transmute::<_, _>}` as `unsafe extern "rust-intrinsic" fn(isize) -> usize` is invalid
error[E0606]: casting `unsafe extern "rust-intrinsic" fn(_) -> _ {std::intrinsics::transmute::<_, _>}` as `unsafe extern "rust-intrinsic" fn(isize) -> usize` is invalid
--> $DIR/reify-intrinsic.rs:11:13
|
LL | let _ = std::mem::transmute as unsafe extern "rust-intrinsic" fn(isize) -> usize;
Expand Down

0 comments on commit 60cfa88

Please sign in to comment.