Auto merge of #108442 - scottmcm:mir-transmute, r=oli-obk
Add `CastKind::Transmute` to MIR ~~Nothing actually produces it in this commit, so I don't know how to test it, but it also means it shouldn't be possible for it to break anything.~~ Includes lowering `transmute` calls to it, so it's used. Zulip Conversation: <https://rust-lang.zulipchat.com/#narrow/stream/189540-t-compiler.2Fwg-mir-opt/topic/Good.20first.20isssue/near/321849610>
This commit is contained in:
commit
e216300876
@ -2222,6 +2222,13 @@ fn check_rvalue(&mut self, body: &Body<'tcx>, rvalue: &Rvalue<'tcx>, location: L
|
||||
}
|
||||
}
|
||||
}
|
||||
CastKind::Transmute => {
|
||||
span_mirbug!(
|
||||
self,
|
||||
rvalue,
|
||||
"Unexpected CastKind::Transmute, which is not permitted in Analysis MIR",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -709,6 +709,10 @@ fn is_fat_ptr<'tcx>(fx: &FunctionCx<'_, '_, 'tcx>, ty: Ty<'tcx>) -> bool {
|
||||
let operand = codegen_operand(fx, operand);
|
||||
operand.coerce_dyn_star(fx, lval);
|
||||
}
|
||||
Rvalue::Cast(CastKind::Transmute, ref operand, _to_ty) => {
|
||||
let operand = codegen_operand(fx, operand);
|
||||
lval.write_cvalue_transmute(fx, operand);
|
||||
}
|
||||
Rvalue::Discriminant(place) => {
|
||||
let place = codegen_place(fx, place);
|
||||
let value = place.to_cvalue(fx);
|
||||
|
@ -557,16 +557,6 @@ fn codegen_regular_intrinsic_call<'tcx>(
|
||||
fx.bcx.ins().band(ptr, mask);
|
||||
}
|
||||
|
||||
sym::transmute => {
|
||||
intrinsic_args!(fx, args => (from); intrinsic);
|
||||
|
||||
if ret.layout().abi.is_uninhabited() {
|
||||
crate::base::codegen_panic(fx, "Transmuting to uninhabited type.", source_info);
|
||||
return;
|
||||
}
|
||||
|
||||
ret.write_cvalue_transmute(fx, from);
|
||||
}
|
||||
sym::write_bytes | sym::volatile_set_memory => {
|
||||
intrinsic_args!(fx, args => (dst, val, count); intrinsic);
|
||||
let val = val.load_scalar(fx);
|
||||
|
@ -378,7 +378,7 @@ fn codegen_intrinsic_call(
|
||||
}
|
||||
}
|
||||
|
||||
_ => bug!("unknown intrinsic '{}'", name),
|
||||
_ => bug!("unknown intrinsic '{}' -- should it have been lowered earlier?", name),
|
||||
};
|
||||
|
||||
if !fn_abi.ret.is_ignore() {
|
||||
|
@ -16,7 +16,7 @@
|
||||
use rustc_middle::mir::{self, AssertKind, SwitchTargets};
|
||||
use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf, ValidityRequirement};
|
||||
use rustc_middle::ty::print::{with_no_trimmed_paths, with_no_visible_paths};
|
||||
use rustc_middle::ty::{self, Instance, Ty, TypeVisitableExt};
|
||||
use rustc_middle::ty::{self, Instance, Ty};
|
||||
use rustc_session::config::OptLevel;
|
||||
use rustc_span::source_map::Span;
|
||||
use rustc_span::{sym, Symbol};
|
||||
@ -769,23 +769,6 @@ fn codegen_call_terminator(
|
||||
None => bx.fn_abi_of_fn_ptr(sig, extra_args),
|
||||
};
|
||||
|
||||
if intrinsic == Some(sym::transmute) {
|
||||
return if let Some(target) = target {
|
||||
self.codegen_transmute(bx, &args[0], destination);
|
||||
helper.funclet_br(self, bx, target, mergeable_succ)
|
||||
} else {
|
||||
// If we are trying to transmute to an uninhabited type,
|
||||
// it is likely there is no allotted destination. In fact,
|
||||
// transmuting to an uninhabited type is UB, which means
|
||||
// we can do what we like. Here, we declare that transmuting
|
||||
// into an uninhabited type is impossible, so anything following
|
||||
// it must be unreachable.
|
||||
assert_eq!(fn_abi.ret.layout.abi, abi::Abi::Uninhabited);
|
||||
bx.unreachable();
|
||||
MergingSucc::False
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(merging_succ) = self.codegen_panic_intrinsic(
|
||||
&helper,
|
||||
bx,
|
||||
@ -828,7 +811,6 @@ fn codegen_call_terminator(
|
||||
|
||||
match intrinsic {
|
||||
None | Some(sym::drop_in_place) => {}
|
||||
Some(sym::copy_nonoverlapping) => unreachable!(),
|
||||
Some(intrinsic) => {
|
||||
let dest = match ret_dest {
|
||||
_ if fn_abi.ret.is_indirect() => llargs[0],
|
||||
@ -1739,71 +1721,6 @@ fn make_return_dest(
|
||||
}
|
||||
}
|
||||
|
||||
fn codegen_transmute(&mut self, bx: &mut Bx, src: &mir::Operand<'tcx>, dst: mir::Place<'tcx>) {
|
||||
if let Some(index) = dst.as_local() {
|
||||
match self.locals[index] {
|
||||
LocalRef::Place(place) => self.codegen_transmute_into(bx, src, place),
|
||||
LocalRef::UnsizedPlace(_) => bug!("transmute must not involve unsized locals"),
|
||||
LocalRef::Operand(None) => {
|
||||
let dst_layout = bx.layout_of(self.monomorphized_place_ty(dst.as_ref()));
|
||||
assert!(!dst_layout.ty.has_erasable_regions());
|
||||
let place = PlaceRef::alloca(bx, dst_layout);
|
||||
place.storage_live(bx);
|
||||
self.codegen_transmute_into(bx, src, place);
|
||||
let op = bx.load_operand(place);
|
||||
place.storage_dead(bx);
|
||||
self.locals[index] = LocalRef::Operand(Some(op));
|
||||
self.debug_introduce_local(bx, index);
|
||||
}
|
||||
LocalRef::Operand(Some(op)) => {
|
||||
assert!(op.layout.is_zst(), "assigning to initialized SSAtemp");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let dst = self.codegen_place(bx, dst.as_ref());
|
||||
self.codegen_transmute_into(bx, src, dst);
|
||||
}
|
||||
}
|
||||
|
||||
fn codegen_transmute_into(
|
||||
&mut self,
|
||||
bx: &mut Bx,
|
||||
src: &mir::Operand<'tcx>,
|
||||
dst: PlaceRef<'tcx, Bx::Value>,
|
||||
) {
|
||||
let src = self.codegen_operand(bx, src);
|
||||
|
||||
// Special-case transmutes between scalars as simple bitcasts.
|
||||
match (src.layout.abi, dst.layout.abi) {
|
||||
(abi::Abi::Scalar(src_scalar), abi::Abi::Scalar(dst_scalar)) => {
|
||||
// HACK(eddyb) LLVM doesn't like `bitcast`s between pointers and non-pointers.
|
||||
let src_is_ptr = matches!(src_scalar.primitive(), abi::Pointer(_));
|
||||
let dst_is_ptr = matches!(dst_scalar.primitive(), abi::Pointer(_));
|
||||
if src_is_ptr == dst_is_ptr {
|
||||
assert_eq!(src.layout.size, dst.layout.size);
|
||||
|
||||
// NOTE(eddyb) the `from_immediate` and `to_immediate_scalar`
|
||||
// conversions allow handling `bool`s the same as `u8`s.
|
||||
let src = bx.from_immediate(src.immediate());
|
||||
// LLVM also doesn't like `bitcast`s between pointers in different address spaces.
|
||||
let src_as_dst = if src_is_ptr {
|
||||
bx.pointercast(src, bx.backend_type(dst.layout))
|
||||
} else {
|
||||
bx.bitcast(src, bx.backend_type(dst.layout))
|
||||
};
|
||||
Immediate(bx.to_immediate_scalar(src_as_dst, dst_scalar)).store(bx, dst);
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let llty = bx.backend_type(src.layout);
|
||||
let cast_ptr = bx.pointercast(dst.llval, bx.type_ptr_to(llty));
|
||||
let align = src.layout.align.abi.min(dst.align);
|
||||
src.val.store(bx, PlaceRef::new_sized_aligned(cast_ptr, src.layout, align));
|
||||
}
|
||||
|
||||
// Stores the return value of a function call into it's final location.
|
||||
fn store_return(
|
||||
&mut self,
|
||||
|
@ -13,7 +13,7 @@
|
||||
use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf};
|
||||
use rustc_middle::ty::{self, adjustment::PointerCast, Instance, Ty, TyCtxt};
|
||||
use rustc_span::source_map::{Span, DUMMY_SP};
|
||||
use rustc_target::abi::VariantIdx;
|
||||
use rustc_target::abi::{self, VariantIdx};
|
||||
|
||||
impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||
#[instrument(level = "trace", skip(self, bx))]
|
||||
@ -72,6 +72,11 @@ pub fn codegen_rvalue(
|
||||
}
|
||||
}
|
||||
|
||||
mir::Rvalue::Cast(mir::CastKind::Transmute, ref operand, _ty) => {
|
||||
let src = self.codegen_operand(bx, operand);
|
||||
self.codegen_transmute(bx, src, dest);
|
||||
}
|
||||
|
||||
mir::Rvalue::Repeat(ref elem, count) => {
|
||||
let cg_elem = self.codegen_operand(bx, elem);
|
||||
|
||||
@ -143,6 +148,52 @@ pub fn codegen_rvalue(
|
||||
}
|
||||
}
|
||||
|
||||
fn codegen_transmute(
|
||||
&mut self,
|
||||
bx: &mut Bx,
|
||||
src: OperandRef<'tcx, Bx::Value>,
|
||||
dst: PlaceRef<'tcx, Bx::Value>,
|
||||
) {
|
||||
// The MIR validator enforces no unsized transmutes.
|
||||
debug_assert!(src.layout.is_sized());
|
||||
debug_assert!(dst.layout.is_sized());
|
||||
|
||||
if src.layout.size != dst.layout.size
|
||||
|| src.layout.abi == abi::Abi::Uninhabited
|
||||
|| dst.layout.abi == abi::Abi::Uninhabited
|
||||
{
|
||||
// In all of these cases it's UB to run this transmute, but that's
|
||||
// known statically so might as well trap for it, rather than just
|
||||
// making it unreachable.
|
||||
bx.abort();
|
||||
return;
|
||||
}
|
||||
|
||||
let size_in_bytes = src.layout.size.bytes();
|
||||
if size_in_bytes == 0 {
|
||||
// Nothing to write
|
||||
return;
|
||||
}
|
||||
|
||||
match src.val {
|
||||
OperandValue::Ref(src_llval, meta, src_align) => {
|
||||
debug_assert_eq!(meta, None);
|
||||
// For a place-to-place transmute, call `memcpy` directly so that
|
||||
// both arguments get the best-available alignment information.
|
||||
let bytes = bx.cx().const_usize(size_in_bytes);
|
||||
let flags = MemFlags::empty();
|
||||
bx.memcpy(dst.llval, dst.align, src_llval, src_align, bytes, flags);
|
||||
}
|
||||
OperandValue::Immediate(_) | OperandValue::Pair(_, _) => {
|
||||
// When we have immediate(s), the alignment of the source is irrelevant,
|
||||
// so we can store them using the destination's alignment.
|
||||
let llty = bx.backend_type(src.layout);
|
||||
let cast_ptr = bx.pointercast(dst.llval, bx.type_ptr_to(llty));
|
||||
src.val.store(bx, PlaceRef::new_sized_aligned(cast_ptr, src.layout, dst.align));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn codegen_rvalue_unsized(
|
||||
&mut self,
|
||||
bx: &mut Bx,
|
||||
@ -344,6 +395,9 @@ pub fn codegen_rvalue_operand(
|
||||
};
|
||||
OperandValue::Immediate(newval)
|
||||
}
|
||||
mir::CastKind::Transmute => {
|
||||
bug!("Transmute operand {:?} in `codegen_rvalue_operand`", operand);
|
||||
}
|
||||
};
|
||||
OperandRef { val, layout: cast }
|
||||
}
|
||||
@ -673,6 +727,10 @@ pub fn codegen_scalar_checked_binop(
|
||||
impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||
pub fn rvalue_creates_operand(&self, rvalue: &mir::Rvalue<'tcx>, span: Span) -> bool {
|
||||
match *rvalue {
|
||||
mir::Rvalue::Cast(mir::CastKind::Transmute, ..) =>
|
||||
// FIXME: Now that transmute is an Rvalue, it would be nice if
|
||||
// it could create `Immediate`s for scalars, where possible.
|
||||
false,
|
||||
mir::Rvalue::Ref(..) |
|
||||
mir::Rvalue::CopyForDeref(..) |
|
||||
mir::Rvalue::AddressOf(..) |
|
||||
|
@ -133,6 +133,22 @@ pub fn cast(
|
||||
bug!()
|
||||
}
|
||||
}
|
||||
|
||||
Transmute => {
|
||||
assert!(src.layout.is_sized());
|
||||
assert!(dest.layout.is_sized());
|
||||
if src.layout.size != dest.layout.size {
|
||||
throw_ub_format!(
|
||||
"transmuting from {}-byte type to {}-byte type: `{}` -> `{}`",
|
||||
src.layout.size.bytes(),
|
||||
dest.layout.size.bytes(),
|
||||
src.layout.ty,
|
||||
dest.layout.ty,
|
||||
);
|
||||
}
|
||||
|
||||
self.copy_op(src, dest, /*allow_transmute*/ true)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -127,7 +127,6 @@ pub fn emulate_intrinsic(
|
||||
// First handle intrinsics without return place.
|
||||
let ret = match ret {
|
||||
None => match intrinsic_name {
|
||||
sym::transmute => throw_ub_format!("transmuting to uninhabited type"),
|
||||
sym::abort => M::abort(self, "the program aborted execution".to_owned())?,
|
||||
// Unsupported diverging intrinsic.
|
||||
_ => return Ok(false),
|
||||
@ -411,9 +410,6 @@ pub fn emulate_intrinsic(
|
||||
self.exact_div(&val, &size, dest)?;
|
||||
}
|
||||
|
||||
sym::transmute => {
|
||||
self.copy_op(&args[0], dest, /*allow_transmute*/ true)?;
|
||||
}
|
||||
sym::assert_inhabited
|
||||
| sym::assert_zero_valid
|
||||
| sym::assert_mem_uninitialized_valid => {
|
||||
|
@ -621,6 +621,33 @@ macro_rules! check_kinds {
|
||||
);
|
||||
}
|
||||
}
|
||||
CastKind::Transmute => {
|
||||
if let MirPhase::Runtime(..) = self.mir_phase {
|
||||
// Unlike `mem::transmute`, a MIR `Transmute` is well-formed
|
||||
// for any two `Sized` types, just potentially UB to run.
|
||||
|
||||
if !op_ty.is_sized(self.tcx, self.param_env) {
|
||||
self.fail(
|
||||
location,
|
||||
format!("Cannot transmute from non-`Sized` type {op_ty:?}"),
|
||||
);
|
||||
}
|
||||
if !target_type.is_sized(self.tcx, self.param_env) {
|
||||
self.fail(
|
||||
location,
|
||||
format!("Cannot transmute to non-`Sized` type {target_type:?}"),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
self.fail(
|
||||
location,
|
||||
format!(
|
||||
"Transmute is not supported in non-runtime phase {:?}.",
|
||||
self.mir_phase
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Rvalue::Repeat(_, _)
|
||||
|
@ -1967,7 +1967,8 @@ pub fn is_safe_to_remove(&self) -> bool {
|
||||
| CastKind::PtrToPtr
|
||||
| CastKind::Pointer(_)
|
||||
| CastKind::PointerFromExposedAddress
|
||||
| CastKind::DynStar,
|
||||
| CastKind::DynStar
|
||||
| CastKind::Transmute,
|
||||
_,
|
||||
_,
|
||||
)
|
||||
|
@ -1156,6 +1156,13 @@ pub enum CastKind {
|
||||
IntToFloat,
|
||||
PtrToPtr,
|
||||
FnPtrToPtr,
|
||||
/// Reinterpret the bits of the input as a different type.
|
||||
///
|
||||
/// MIR is well-formed if the input and output types have different sizes,
|
||||
/// but running a transmute between differently-sized types is UB.
|
||||
///
|
||||
/// Allowed only in [`MirPhase::Runtime`]; Earlier it's a [`TerminatorKind::Call`].
|
||||
Transmute,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, TyEncodable, TyDecodable, Hash, HashStable)]
|
||||
|
@ -137,6 +137,10 @@ fn parse_call(
|
||||
fn parse_rvalue(&self, expr_id: ExprId) -> PResult<Rvalue<'tcx>> {
|
||||
parse_by_kind!(self, expr_id, expr, "rvalue",
|
||||
@call("mir_discriminant", args) => self.parse_place(args[0]).map(Rvalue::Discriminant),
|
||||
@call("mir_cast_transmute", args) => {
|
||||
let source = self.parse_operand(args[0])?;
|
||||
Ok(Rvalue::Cast(CastKind::Transmute, source, expr.ty))
|
||||
},
|
||||
@call("mir_checked", args) => {
|
||||
parse_by_kind!(self, args[0], _, "binary op",
|
||||
ExprKind::Binary { op, lhs, rhs } => Ok(Rvalue::CheckedBinaryOp(
|
||||
|
@ -504,6 +504,15 @@ fn check_rvalue(&mut self, rvalue: &Rvalue<'tcx>) -> Option<()> {
|
||||
|
||||
return None;
|
||||
}
|
||||
// Do not try creating references, nor any types with potentially-complex
|
||||
// invariants. This avoids an issue where checking validity would do a
|
||||
// bunch of work generating a nice message about the invariant violation,
|
||||
// only to not show it to anyone (since this isn't the lint).
|
||||
Rvalue::Cast(CastKind::Transmute, op, dst_ty) if !dst_ty.is_primitive() => {
|
||||
trace!("skipping Transmute of {:?} to {:?}", op, dst_ty);
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
// There's no other checking to do at this time.
|
||||
Rvalue::Aggregate(..)
|
||||
|
@ -221,6 +221,32 @@ fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
|
||||
terminator.kind = TerminatorKind::Goto { target };
|
||||
}
|
||||
}
|
||||
sym::transmute => {
|
||||
let dst_ty = destination.ty(local_decls, tcx).ty;
|
||||
let Ok([arg]) = <[_; 1]>::try_from(std::mem::take(args)) else {
|
||||
span_bug!(
|
||||
terminator.source_info.span,
|
||||
"Wrong number of arguments for transmute intrinsic",
|
||||
);
|
||||
};
|
||||
|
||||
// Always emit the cast, even if we transmute to an uninhabited type,
|
||||
// because that lets CTFE and codegen generate better error messages
|
||||
// when such a transmute actually ends up reachable.
|
||||
block.statements.push(Statement {
|
||||
source_info: terminator.source_info,
|
||||
kind: StatementKind::Assign(Box::new((
|
||||
*destination,
|
||||
Rvalue::Cast(CastKind::Transmute, arg, dst_ty),
|
||||
))),
|
||||
});
|
||||
|
||||
if let Some(target) = *target {
|
||||
terminator.kind = TerminatorKind::Goto { target };
|
||||
} else {
|
||||
terminator.kind = TerminatorKind::Unreachable;
|
||||
}
|
||||
}
|
||||
_ if intrinsic_name.as_str().starts_with("simd_shuffle") => {
|
||||
validate_simd_shuffle(tcx, args, terminator.source_info.span);
|
||||
}
|
||||
|
@ -343,6 +343,14 @@ fn Field<F>(place: (), field: u32) -> F
|
||||
/// See [`Field`] for documentation.
|
||||
fn Variant<T>(place: T, index: u32) -> ()
|
||||
);
|
||||
define!(
|
||||
"mir_cast_transmute",
|
||||
/// Emits a `CastKind::Transmute` cast.
|
||||
///
|
||||
/// Needed to test the UB when `sizeof(T) != sizeof(U)`, which can't be
|
||||
/// generated via the normal `mem::transmute`.
|
||||
fn CastTransmute<T, U>(operand: T) -> U
|
||||
);
|
||||
define!(
|
||||
"mir_make_place",
|
||||
#[doc(hidden)]
|
||||
|
@ -176,6 +176,9 @@ fn check_rvalue<'tcx>(
|
||||
// FIXME(dyn-star)
|
||||
unimplemented!()
|
||||
},
|
||||
Rvalue::Cast(CastKind::Transmute, _, _) => {
|
||||
Err((span, "transmute can attempt to turn pointers into integers, so is unstable in const fn".into()))
|
||||
},
|
||||
// binops are fine on integers
|
||||
Rvalue::BinaryOp(_, box (lhs, rhs)) | Rvalue::CheckedBinaryOp(_, box (lhs, rhs)) => {
|
||||
check_operand(tcx, lhs, span, body)?;
|
||||
|
@ -7,6 +7,6 @@
|
||||
|
||||
fn main() {
|
||||
let _x: ! = unsafe {
|
||||
std::mem::transmute::<Human, !>(Human) //~ ERROR: transmuting to uninhabited
|
||||
std::mem::transmute::<Human, !>(Human) //~ ERROR: entering unreachable code
|
||||
};
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
error: Undefined Behavior: transmuting to uninhabited type
|
||||
error: Undefined Behavior: entering unreachable code
|
||||
--> $DIR/never_transmute_humans.rs:LL:CC
|
||||
|
|
||||
LL | std::mem::transmute::<Human, !>(Human)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ transmuting to uninhabited type
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ entering unreachable code
|
||||
|
|
||||
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
||||
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
||||
|
@ -10,11 +10,13 @@ enum VoidI {}
|
||||
pub struct Void(VoidI);
|
||||
|
||||
pub fn f(v: Void) -> ! {
|
||||
match v.0 {} //~ ERROR: entering unreachable code
|
||||
match v.0 {}
|
||||
//~^ ERROR: entering unreachable code
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let v = unsafe { std::mem::transmute::<(), m::Void>(()) };
|
||||
m::f(v); //~ NOTE: inside `main`
|
||||
m::f(v);
|
||||
//~^ NOTE: inside `main`
|
||||
}
|
||||
|
196
tests/codegen/intrinsics/transmute.rs
Normal file
196
tests/codegen/intrinsics/transmute.rs
Normal file
@ -0,0 +1,196 @@
|
||||
// compile-flags: -O -C no-prepopulate-passes
|
||||
// only-64bit (so I don't need to worry about usize)
|
||||
// min-llvm-version: 15.0 # this test assumes `ptr`s
|
||||
|
||||
#![crate_type = "lib"]
|
||||
#![feature(core_intrinsics)]
|
||||
#![feature(custom_mir)]
|
||||
#![feature(inline_const)]
|
||||
|
||||
use std::mem::transmute;
|
||||
|
||||
// Some of the cases here are statically rejected by `mem::transmute`, so
|
||||
// we need to generate custom MIR for those cases to get to codegen.
|
||||
use std::intrinsics::mir::*;
|
||||
|
||||
enum Never {}
|
||||
|
||||
#[repr(align(2))]
|
||||
pub struct BigNever(Never, u16, Never);
|
||||
|
||||
#[repr(align(8))]
|
||||
pub struct Scalar64(i64);
|
||||
|
||||
#[repr(C, align(4))]
|
||||
pub struct Aggregate64(u16, u8, i8, f32);
|
||||
|
||||
// CHECK-LABEL: @check_bigger_size(
|
||||
#[no_mangle]
|
||||
#[custom_mir(dialect = "runtime", phase = "initial")]
|
||||
pub unsafe fn check_bigger_size(x: u16) -> u32 {
|
||||
// CHECK: call void @llvm.trap
|
||||
mir!{
|
||||
{
|
||||
RET = CastTransmute(x);
|
||||
Return()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @check_smaller_size(
|
||||
#[no_mangle]
|
||||
#[custom_mir(dialect = "runtime", phase = "initial")]
|
||||
pub unsafe fn check_smaller_size(x: u32) -> u16 {
|
||||
// CHECK: call void @llvm.trap
|
||||
mir!{
|
||||
{
|
||||
RET = CastTransmute(x);
|
||||
Return()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @check_to_uninhabited(
|
||||
#[no_mangle]
|
||||
#[custom_mir(dialect = "runtime", phase = "initial")]
|
||||
pub unsafe fn check_to_uninhabited(x: u16) -> BigNever {
|
||||
// CHECK: call void @llvm.trap
|
||||
mir!{
|
||||
{
|
||||
RET = CastTransmute(x);
|
||||
Return()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @check_from_uninhabited(
|
||||
#[no_mangle]
|
||||
#[custom_mir(dialect = "runtime", phase = "initial")]
|
||||
pub unsafe fn check_from_uninhabited(x: BigNever) -> u16 {
|
||||
// CHECK: call void @llvm.trap
|
||||
mir!{
|
||||
{
|
||||
RET = CastTransmute(x);
|
||||
Return()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @check_to_newtype(
|
||||
#[no_mangle]
|
||||
pub unsafe fn check_to_newtype(x: u64) -> Scalar64 {
|
||||
// CHECK: %0 = alloca i64
|
||||
// CHECK: store i64 %x, ptr %0
|
||||
// CHECK: %1 = load i64, ptr %0
|
||||
// CHECK: ret i64 %1
|
||||
transmute(x)
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @check_from_newtype(
|
||||
#[no_mangle]
|
||||
pub unsafe fn check_from_newtype(x: Scalar64) -> u64 {
|
||||
// CHECK: %0 = alloca i64
|
||||
// CHECK: store i64 %x, ptr %0
|
||||
// CHECK: %1 = load i64, ptr %0
|
||||
// CHECK: ret i64 %1
|
||||
transmute(x)
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @check_to_pair(
|
||||
#[no_mangle]
|
||||
pub unsafe fn check_to_pair(x: u64) -> Option<i32> {
|
||||
// CHECK: %0 = alloca { i32, i32 }, align 4
|
||||
// CHECK: store i64 %x, ptr %0, align 4
|
||||
transmute(x)
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @check_from_pair(
|
||||
#[no_mangle]
|
||||
pub unsafe fn check_from_pair(x: Option<i32>) -> u64 {
|
||||
// The two arguments are of types that are only 4-aligned, but they're
|
||||
// immediates so we can write using the destination alloca's alignment.
|
||||
const { assert!(std::mem::align_of::<Option<i32>>() == 4) };
|
||||
|
||||
// CHECK: %0 = alloca i64, align 8
|
||||
// CHECK: store i32 %x.0, ptr %1, align 8
|
||||
// CHECK: store i32 %x.1, ptr %2, align 4
|
||||
// CHECK: %3 = load i64, ptr %0, align 8
|
||||
// CHECK: ret i64 %3
|
||||
transmute(x)
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @check_to_float(
|
||||
#[no_mangle]
|
||||
pub unsafe fn check_to_float(x: u32) -> f32 {
|
||||
// CHECK: %0 = alloca float
|
||||
// CHECK: store i32 %x, ptr %0
|
||||
// CHECK: %1 = load float, ptr %0
|
||||
// CHECK: ret float %1
|
||||
transmute(x)
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @check_from_float(
|
||||
#[no_mangle]
|
||||
pub unsafe fn check_from_float(x: f32) -> u32 {
|
||||
// CHECK: %0 = alloca i32
|
||||
// CHECK: store float %x, ptr %0
|
||||
// CHECK: %1 = load i32, ptr %0
|
||||
// CHECK: ret i32 %1
|
||||
transmute(x)
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @check_to_bytes(
|
||||
#[no_mangle]
|
||||
pub unsafe fn check_to_bytes(x: u32) -> [u8; 4] {
|
||||
// CHECK: %0 = alloca [4 x i8], align 1
|
||||
// CHECK: store i32 %x, ptr %0, align 1
|
||||
// CHECK: %1 = load i32, ptr %0, align 1
|
||||
// CHECK: ret i32 %1
|
||||
transmute(x)
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @check_from_bytes(
|
||||
#[no_mangle]
|
||||
pub unsafe fn check_from_bytes(x: [u8; 4]) -> u32 {
|
||||
// CHECK: %1 = alloca i32, align 4
|
||||
// CHECK: %x = alloca [4 x i8], align 1
|
||||
// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %1, ptr align 1 %x, i64 4, i1 false)
|
||||
// CHECK: %3 = load i32, ptr %1, align 4
|
||||
// CHECK: ret i32 %3
|
||||
transmute(x)
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @check_to_aggregate(
|
||||
#[no_mangle]
|
||||
pub unsafe fn check_to_aggregate(x: u64) -> Aggregate64 {
|
||||
// CHECK: %0 = alloca %Aggregate64, align 4
|
||||
// CHECK: store i64 %x, ptr %0, align 4
|
||||
// CHECK: %1 = load i64, ptr %0, align 4
|
||||
// CHECK: ret i64 %1
|
||||
transmute(x)
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @check_from_aggregate(
|
||||
#[no_mangle]
|
||||
pub unsafe fn check_from_aggregate(x: Aggregate64) -> u64 {
|
||||
// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %{{[0-9]+}}, ptr align 4 %x, i64 8, i1 false)
|
||||
transmute(x)
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @check_long_array_less_aligned(
|
||||
#[no_mangle]
|
||||
pub unsafe fn check_long_array_less_aligned(x: [u64; 100]) -> [u16; 400] {
|
||||
// CHECK-NEXT: start
|
||||
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 2 %0, ptr align 8 %x, i64 800, i1 false)
|
||||
// CHECK-NEXT: ret void
|
||||
transmute(x)
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @check_long_array_more_aligned(
|
||||
#[no_mangle]
|
||||
pub unsafe fn check_long_array_more_aligned(x: [u8; 100]) -> [u32; 25] {
|
||||
// CHECK-NEXT: start
|
||||
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %0, ptr align 1 %x, i64 100, i1 false)
|
||||
// CHECK-NEXT: ret void
|
||||
transmute(x)
|
||||
}
|
@ -1,13 +1,19 @@
|
||||
// compile-flags: -O -C no-prepopulate-passes
|
||||
// min-llvm-version: 15.0 # this test assumes `ptr`s and thus no `pointercast`s
|
||||
|
||||
#![crate_type = "lib"]
|
||||
|
||||
// FIXME(eddyb) all of these tests show memory stores and loads, even after a
|
||||
// scalar `bitcast`, more special-casing is required to remove `alloca` usage.
|
||||
// With opaque ptrs in LLVM, `transmute` can load/store any `alloca` as any type,
|
||||
// without needing to pointercast, and SRoA will turn that into a `bitcast`.
|
||||
// As such, there's no longer special-casing in `transmute` to attempt to
|
||||
// generate `bitcast` ourselves, as that just made the IR longer.
|
||||
|
||||
// FIXME: That said, `bitcast`s could still be a valuable addition if they could
|
||||
// be done in `rvalue_creates_operand`, and thus avoid the `alloca`s entirely.
|
||||
|
||||
// CHECK-LABEL: define{{.*}}i32 @f32_to_bits(float noundef %x)
|
||||
// CHECK: store i32 %{{.*}}, {{.*}} %0
|
||||
// CHECK-NEXT: %[[RES:.*]] = load i32, {{.*}} %0
|
||||
// CHECK: store float %{{.*}}, ptr %0
|
||||
// CHECK-NEXT: %[[RES:.*]] = load i32, ptr %0
|
||||
// CHECK: ret i32 %[[RES]]
|
||||
#[no_mangle]
|
||||
pub fn f32_to_bits(x: f32) -> u32 {
|
||||
@ -25,12 +31,10 @@ pub fn bool_to_byte(b: bool) -> u8 {
|
||||
}
|
||||
|
||||
// CHECK-LABEL: define{{.*}}noundef zeroext i1 @byte_to_bool(i8 noundef %byte)
|
||||
// CHECK: %1 = trunc i8 %byte to i1
|
||||
// CHECK-NEXT: %2 = zext i1 %1 to i8
|
||||
// CHECK-NEXT: store i8 %2, {{.*}} %0
|
||||
// CHECK-NEXT: %3 = load i8, {{.*}} %0
|
||||
// CHECK-NEXT: %4 = trunc i8 %3 to i1
|
||||
// CHECK: ret i1 %4
|
||||
// CHECK: store i8 %byte, ptr %0
|
||||
// CHECK-NEXT: %1 = load i8, {{.*}} %0
|
||||
// CHECK-NEXT: %2 = trunc i8 %1 to i1
|
||||
// CHECK: ret i1 %2
|
||||
#[no_mangle]
|
||||
pub unsafe fn byte_to_bool(byte: u8) -> bool {
|
||||
std::mem::transmute(byte)
|
||||
@ -45,20 +49,8 @@ pub fn ptr_to_ptr(p: *mut u16) -> *mut u8 {
|
||||
unsafe { std::mem::transmute(p) }
|
||||
}
|
||||
|
||||
// HACK(eddyb) scalar `transmute`s between pointers and non-pointers are
|
||||
// currently not special-cased like other scalar `transmute`s, because
|
||||
// LLVM requires specifically `ptrtoint`/`inttoptr` instead of `bitcast`.
|
||||
//
|
||||
// Tests below show the non-special-cased behavior (with the possible
|
||||
// future special-cased instructions in the "NOTE(eddyb)" comments).
|
||||
|
||||
// CHECK: define{{.*}}[[USIZE:i[0-9]+]] @ptr_to_int({{i16\*|ptr}} noundef %p)
|
||||
|
||||
// NOTE(eddyb) see above, the following two CHECK lines should ideally be this:
|
||||
// %2 = ptrtoint i16* %p to [[USIZE]]
|
||||
// store [[USIZE]] %2, [[USIZE]]* %0
|
||||
// CHECK: store {{i16\*|ptr}} %p, {{.*}}
|
||||
|
||||
// CHECK-NEXT: %[[RES:.*]] = load [[USIZE]], {{.*}} %0
|
||||
// CHECK: ret [[USIZE]] %[[RES]]
|
||||
#[no_mangle]
|
||||
@ -67,12 +59,7 @@ pub fn ptr_to_int(p: *mut u16) -> usize {
|
||||
}
|
||||
|
||||
// CHECK: define{{.*}}{{i16\*|ptr}} @int_to_ptr([[USIZE]] noundef %i)
|
||||
|
||||
// NOTE(eddyb) see above, the following two CHECK lines should ideally be this:
|
||||
// %2 = inttoptr [[USIZE]] %i to i16*
|
||||
// store i16* %2, i16** %0
|
||||
// CHECK: store [[USIZE]] %i, {{.*}}
|
||||
|
||||
// CHECK-NEXT: %[[RES:.*]] = load {{i16\*|ptr}}, {{.*}} %0
|
||||
// CHECK: ret {{i16\*|ptr}} %[[RES]]
|
||||
#[no_mangle]
|
||||
|
15
tests/mir-opt/const_prop/transmute.from_char.ConstProp.diff
Normal file
15
tests/mir-opt/const_prop/transmute.from_char.ConstProp.diff
Normal file
@ -0,0 +1,15 @@
|
||||
- // MIR for `from_char` before ConstProp
|
||||
+ // MIR for `from_char` after ConstProp
|
||||
|
||||
fn from_char() -> i32 {
|
||||
let mut _0: i32; // return place in scope 0 at $DIR/transmute.rs:+0:23: +0:26
|
||||
scope 1 {
|
||||
}
|
||||
|
||||
bb0: {
|
||||
- _0 = const 'R' as i32 (Transmute); // scope 1 at $DIR/transmute.rs:+1:14: +1:28
|
||||
+ _0 = const 82_i32; // scope 1 at $DIR/transmute.rs:+1:14: +1:28
|
||||
return; // scope 0 at $DIR/transmute.rs:+2:2: +2:2
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,14 @@
|
||||
- // MIR for `invalid_bool` before ConstProp
|
||||
+ // MIR for `invalid_bool` after ConstProp
|
||||
|
||||
fn invalid_bool() -> bool {
|
||||
let mut _0: bool; // return place in scope 0 at $DIR/transmute.rs:+0:33: +0:37
|
||||
scope 1 {
|
||||
}
|
||||
|
||||
bb0: {
|
||||
_0 = const -1_i8 as bool (Transmute); // scope 1 at $DIR/transmute.rs:+1:14: +1:30
|
||||
return; // scope 0 at $DIR/transmute.rs:+2:2: +2:2
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,14 @@
|
||||
- // MIR for `invalid_char` before ConstProp
|
||||
+ // MIR for `invalid_char` after ConstProp
|
||||
|
||||
fn invalid_char() -> char {
|
||||
let mut _0: char; // return place in scope 0 at $DIR/transmute.rs:+0:33: +0:37
|
||||
scope 1 {
|
||||
}
|
||||
|
||||
bb0: {
|
||||
_0 = const _ as char (Transmute); // scope 1 at $DIR/transmute.rs:+1:14: +1:33
|
||||
return; // scope 0 at $DIR/transmute.rs:+2:2: +2:2
|
||||
}
|
||||
}
|
||||
|
23
tests/mir-opt/const_prop/transmute.less_as_i8.ConstProp.diff
Normal file
23
tests/mir-opt/const_prop/transmute.less_as_i8.ConstProp.diff
Normal file
@ -0,0 +1,23 @@
|
||||
- // MIR for `less_as_i8` before ConstProp
|
||||
+ // MIR for `less_as_i8` after ConstProp
|
||||
|
||||
fn less_as_i8() -> i8 {
|
||||
let mut _0: i8; // return place in scope 0 at $DIR/transmute.rs:+0:24: +0:26
|
||||
let mut _1: std::cmp::Ordering; // in scope 0 at $DIR/transmute.rs:+1:24: +1:48
|
||||
scope 1 {
|
||||
}
|
||||
|
||||
bb0: {
|
||||
StorageLive(_1); // scope 1 at $DIR/transmute.rs:+1:24: +1:48
|
||||
- _1 = Less; // scope 1 at $DIR/transmute.rs:+1:24: +1:48
|
||||
- _0 = move _1 as i8 (Transmute); // scope 1 at $DIR/transmute.rs:+1:14: +1:49
|
||||
+ _1 = const Less; // scope 1 at $DIR/transmute.rs:+1:24: +1:48
|
||||
+ // mir::Constant
|
||||
+ // + span: no-location
|
||||
+ // + literal: Const { ty: std::cmp::Ordering, val: Value(Scalar(0xff)) }
|
||||
+ _0 = const -1_i8; // scope 1 at $DIR/transmute.rs:+1:14: +1:49
|
||||
StorageDead(_1); // scope 1 at $DIR/transmute.rs:+1:48: +1:49
|
||||
return; // scope 0 at $DIR/transmute.rs:+2:2: +2:2
|
||||
}
|
||||
}
|
||||
|
61
tests/mir-opt/const_prop/transmute.rs
Normal file
61
tests/mir-opt/const_prop/transmute.rs
Normal file
@ -0,0 +1,61 @@
|
||||
// unit-test: ConstProp
|
||||
// compile-flags: -O --crate-type=lib
|
||||
|
||||
use std::mem::transmute;
|
||||
|
||||
// EMIT_MIR transmute.less_as_i8.ConstProp.diff
|
||||
pub fn less_as_i8() -> i8 {
|
||||
unsafe { transmute(std::cmp::Ordering::Less) }
|
||||
}
|
||||
|
||||
// EMIT_MIR transmute.from_char.ConstProp.diff
|
||||
pub fn from_char() -> i32 {
|
||||
unsafe { transmute('R') }
|
||||
}
|
||||
|
||||
// EMIT_MIR transmute.valid_char.ConstProp.diff
|
||||
pub fn valid_char() -> char {
|
||||
unsafe { transmute(0x52_u32) }
|
||||
}
|
||||
|
||||
// EMIT_MIR transmute.invalid_char.ConstProp.diff
|
||||
pub unsafe fn invalid_char() -> char {
|
||||
unsafe { transmute(i32::MAX) }
|
||||
}
|
||||
|
||||
// EMIT_MIR transmute.invalid_bool.ConstProp.diff
|
||||
pub unsafe fn invalid_bool() -> bool {
|
||||
unsafe { transmute(-1_i8) }
|
||||
}
|
||||
|
||||
// EMIT_MIR transmute.undef_union_as_integer.ConstProp.diff
|
||||
pub unsafe fn undef_union_as_integer() -> u32 {
|
||||
union Union32 { value: u32, unit: () }
|
||||
unsafe { transmute(Union32 { unit: () }) }
|
||||
}
|
||||
|
||||
// EMIT_MIR transmute.unreachable_direct.ConstProp.diff
|
||||
pub unsafe fn unreachable_direct() -> ! {
|
||||
let x: Never = unsafe { transmute(()) };
|
||||
match x {}
|
||||
}
|
||||
|
||||
// EMIT_MIR transmute.unreachable_ref.ConstProp.diff
|
||||
pub unsafe fn unreachable_ref() -> ! {
|
||||
let x: &Never = unsafe { transmute(1_usize) };
|
||||
match *x {}
|
||||
}
|
||||
|
||||
// EMIT_MIR transmute.unreachable_mut.ConstProp.diff
|
||||
pub unsafe fn unreachable_mut() -> ! {
|
||||
let x: &mut Never = unsafe { transmute(1_usize) };
|
||||
match *x {}
|
||||
}
|
||||
|
||||
// EMIT_MIR transmute.unreachable_box.ConstProp.diff
|
||||
pub unsafe fn unreachable_box() -> ! {
|
||||
let x: Box<Never> = unsafe { transmute(1_usize) };
|
||||
match *x {}
|
||||
}
|
||||
|
||||
enum Never {}
|
@ -0,0 +1,22 @@
|
||||
- // MIR for `undef_union_as_integer` before ConstProp
|
||||
+ // MIR for `undef_union_as_integer` after ConstProp
|
||||
|
||||
fn undef_union_as_integer() -> u32 {
|
||||
let mut _0: u32; // return place in scope 0 at $DIR/transmute.rs:+0:43: +0:46
|
||||
let mut _1: undef_union_as_integer::Union32; // in scope 0 at $DIR/transmute.rs:+2:24: +2:44
|
||||
let mut _2: (); // in scope 0 at $DIR/transmute.rs:+2:40: +2:42
|
||||
scope 1 {
|
||||
}
|
||||
|
||||
bb0: {
|
||||
StorageLive(_1); // scope 1 at $DIR/transmute.rs:+2:24: +2:44
|
||||
StorageLive(_2); // scope 1 at $DIR/transmute.rs:+2:40: +2:42
|
||||
_2 = (); // scope 1 at $DIR/transmute.rs:+2:40: +2:42
|
||||
_1 = Union32 { value: move _2 }; // scope 1 at $DIR/transmute.rs:+2:24: +2:44
|
||||
StorageDead(_2); // scope 1 at $DIR/transmute.rs:+2:43: +2:44
|
||||
_0 = move _1 as u32 (Transmute); // scope 1 at $DIR/transmute.rs:+2:14: +2:45
|
||||
StorageDead(_1); // scope 1 at $DIR/transmute.rs:+2:44: +2:45
|
||||
return; // scope 0 at $DIR/transmute.rs:+3:2: +3:2
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,23 @@
|
||||
- // MIR for `unreachable_box` before ConstProp
|
||||
+ // MIR for `unreachable_box` after ConstProp
|
||||
|
||||
fn unreachable_box() -> ! {
|
||||
let mut _0: !; // return place in scope 0 at $DIR/transmute.rs:+0:36: +0:37
|
||||
let mut _1: !; // in scope 0 at $DIR/transmute.rs:+0:38: +3:2
|
||||
let _2: std::boxed::Box<Never>; // in scope 0 at $DIR/transmute.rs:+1:9: +1:10
|
||||
let mut _3: !; // in scope 0 at $DIR/transmute.rs:+2:5: +2:16
|
||||
scope 1 {
|
||||
debug x => _2; // in scope 1 at $DIR/transmute.rs:+1:9: +1:10
|
||||
}
|
||||
scope 2 {
|
||||
}
|
||||
|
||||
bb0: {
|
||||
StorageLive(_1); // scope 0 at $DIR/transmute.rs:+0:38: +3:2
|
||||
StorageLive(_2); // scope 0 at $DIR/transmute.rs:+1:9: +1:10
|
||||
_2 = const 1_usize as std::boxed::Box<Never> (Transmute); // scope 2 at $DIR/transmute.rs:+1:34: +1:52
|
||||
StorageLive(_3); // scope 1 at $DIR/transmute.rs:+2:5: +2:16
|
||||
unreachable; // scope 1 at $DIR/transmute.rs:+2:11: +2:13
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,25 @@
|
||||
- // MIR for `unreachable_direct` before ConstProp
|
||||
+ // MIR for `unreachable_direct` after ConstProp
|
||||
|
||||
fn unreachable_direct() -> ! {
|
||||
let mut _0: !; // return place in scope 0 at $DIR/transmute.rs:+0:39: +0:40
|
||||
let mut _1: !; // in scope 0 at $DIR/transmute.rs:+0:41: +3:2
|
||||
let _2: Never; // in scope 0 at $DIR/transmute.rs:+1:9: +1:10
|
||||
let mut _3: (); // in scope 0 at $DIR/transmute.rs:+1:39: +1:41
|
||||
let mut _4: !; // in scope 0 at $DIR/transmute.rs:+2:5: +2:15
|
||||
scope 1 {
|
||||
debug x => _2; // in scope 1 at $DIR/transmute.rs:+1:9: +1:10
|
||||
}
|
||||
scope 2 {
|
||||
}
|
||||
|
||||
bb0: {
|
||||
StorageLive(_1); // scope 0 at $DIR/transmute.rs:+0:41: +3:2
|
||||
StorageLive(_2); // scope 0 at $DIR/transmute.rs:+1:9: +1:10
|
||||
StorageLive(_3); // scope 2 at $DIR/transmute.rs:+1:39: +1:41
|
||||
_3 = (); // scope 2 at $DIR/transmute.rs:+1:39: +1:41
|
||||
_2 = move _3 as Never (Transmute); // scope 2 at $DIR/transmute.rs:+1:29: +1:42
|
||||
unreachable; // scope 2 at $DIR/transmute.rs:+1:29: +1:42
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,27 @@
|
||||
- // MIR for `unreachable_mut` before ConstProp
|
||||
+ // MIR for `unreachable_mut` after ConstProp
|
||||
|
||||
fn unreachable_mut() -> ! {
|
||||
let mut _0: !; // return place in scope 0 at $DIR/transmute.rs:+0:36: +0:37
|
||||
let mut _1: !; // in scope 0 at $DIR/transmute.rs:+0:38: +3:2
|
||||
let _2: &mut Never; // in scope 0 at $DIR/transmute.rs:+1:9: +1:10
|
||||
let mut _3: &mut Never; // in scope 0 at $DIR/transmute.rs:+1:34: +1:52
|
||||
let mut _4: !; // in scope 0 at $DIR/transmute.rs:+2:5: +2:16
|
||||
scope 1 {
|
||||
debug x => _2; // in scope 1 at $DIR/transmute.rs:+1:9: +1:10
|
||||
}
|
||||
scope 2 {
|
||||
}
|
||||
|
||||
bb0: {
|
||||
StorageLive(_1); // scope 0 at $DIR/transmute.rs:+0:38: +3:2
|
||||
StorageLive(_2); // scope 0 at $DIR/transmute.rs:+1:9: +1:10
|
||||
StorageLive(_3); // scope 0 at $DIR/transmute.rs:+1:34: +1:52
|
||||
_3 = const 1_usize as &mut Never (Transmute); // scope 2 at $DIR/transmute.rs:+1:34: +1:52
|
||||
_2 = &mut (*_3); // scope 0 at $DIR/transmute.rs:+1:34: +1:52
|
||||
StorageDead(_3); // scope 0 at $DIR/transmute.rs:+1:54: +1:55
|
||||
StorageLive(_4); // scope 1 at $DIR/transmute.rs:+2:5: +2:16
|
||||
unreachable; // scope 1 at $DIR/transmute.rs:+2:11: +2:13
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,23 @@
|
||||
- // MIR for `unreachable_ref` before ConstProp
|
||||
+ // MIR for `unreachable_ref` after ConstProp
|
||||
|
||||
fn unreachable_ref() -> ! {
|
||||
let mut _0: !; // return place in scope 0 at $DIR/transmute.rs:+0:36: +0:37
|
||||
let mut _1: !; // in scope 0 at $DIR/transmute.rs:+0:38: +3:2
|
||||
let _2: &Never; // in scope 0 at $DIR/transmute.rs:+1:9: +1:10
|
||||
let mut _3: !; // in scope 0 at $DIR/transmute.rs:+2:5: +2:16
|
||||
scope 1 {
|
||||
debug x => _2; // in scope 1 at $DIR/transmute.rs:+1:9: +1:10
|
||||
}
|
||||
scope 2 {
|
||||
}
|
||||
|
||||
bb0: {
|
||||
StorageLive(_1); // scope 0 at $DIR/transmute.rs:+0:38: +3:2
|
||||
StorageLive(_2); // scope 0 at $DIR/transmute.rs:+1:9: +1:10
|
||||
_2 = const 1_usize as &Never (Transmute); // scope 2 at $DIR/transmute.rs:+1:30: +1:48
|
||||
StorageLive(_3); // scope 1 at $DIR/transmute.rs:+2:5: +2:16
|
||||
unreachable; // scope 1 at $DIR/transmute.rs:+2:11: +2:13
|
||||
}
|
||||
}
|
||||
|
15
tests/mir-opt/const_prop/transmute.valid_char.ConstProp.diff
Normal file
15
tests/mir-opt/const_prop/transmute.valid_char.ConstProp.diff
Normal file
@ -0,0 +1,15 @@
|
||||
- // MIR for `valid_char` before ConstProp
|
||||
+ // MIR for `valid_char` after ConstProp
|
||||
|
||||
fn valid_char() -> char {
|
||||
let mut _0: char; // return place in scope 0 at $DIR/transmute.rs:+0:24: +0:28
|
||||
scope 1 {
|
||||
}
|
||||
|
||||
bb0: {
|
||||
- _0 = const 82_u32 as char (Transmute); // scope 1 at $DIR/transmute.rs:+1:14: +1:33
|
||||
+ _0 = const 'R'; // scope 1 at $DIR/transmute.rs:+1:14: +1:33
|
||||
return; // scope 0 at $DIR/transmute.rs:+2:2: +2:2
|
||||
}
|
||||
}
|
||||
|
@ -24,61 +24,49 @@
|
||||
StorageLive(_2); // scope 0 at $DIR/issue_75439.rs:+2:9: +2:15
|
||||
StorageLive(_3); // scope 2 at $DIR/issue_75439.rs:+2:47: +2:52
|
||||
_3 = _1; // scope 2 at $DIR/issue_75439.rs:+2:47: +2:52
|
||||
_2 = transmute::<[u8; 16], [u32; 4]>(move _3) -> bb1; // scope 2 at $DIR/issue_75439.rs:+2:37: +2:53
|
||||
// mir::Constant
|
||||
// + span: $DIR/issue_75439.rs:8:37: 8:46
|
||||
// + literal: Const { ty: unsafe extern "rust-intrinsic" fn([u8; 16]) -> [u32; 4] {transmute::<[u8; 16], [u32; 4]>}, val: Value(<ZST>) }
|
||||
_2 = move _3 as [u32; 4] (Transmute); // scope 2 at $DIR/issue_75439.rs:+2:37: +2:53
|
||||
StorageDead(_3); // scope 2 at $DIR/issue_75439.rs:+2:52: +2:53
|
||||
switchInt(_2[0 of 4]) -> [0: bb1, otherwise: bb6]; // scope 3 at $DIR/issue_75439.rs:+4:12: +4:30
|
||||
}
|
||||
|
||||
bb1: {
|
||||
StorageDead(_3); // scope 2 at $DIR/issue_75439.rs:+2:52: +2:53
|
||||
switchInt(_2[0 of 4]) -> [0: bb2, otherwise: bb8]; // scope 3 at $DIR/issue_75439.rs:+4:12: +4:30
|
||||
switchInt(_2[1 of 4]) -> [0: bb2, otherwise: bb6]; // scope 3 at $DIR/issue_75439.rs:+4:12: +4:30
|
||||
}
|
||||
|
||||
bb2: {
|
||||
switchInt(_2[1 of 4]) -> [0: bb3, otherwise: bb8]; // scope 3 at $DIR/issue_75439.rs:+4:12: +4:30
|
||||
switchInt(_2[2 of 4]) -> [0: bb4, 4294901760: bb5, otherwise: bb6]; // scope 3 at $DIR/issue_75439.rs:+4:12: +4:30
|
||||
}
|
||||
|
||||
bb3: {
|
||||
switchInt(_2[2 of 4]) -> [0: bb5, 4294901760: bb6, otherwise: bb8]; // scope 3 at $DIR/issue_75439.rs:+4:12: +4:30
|
||||
}
|
||||
|
||||
bb4: {
|
||||
StorageLive(_5); // scope 3 at $DIR/issue_75439.rs:+5:14: +5:38
|
||||
StorageLive(_6); // scope 4 at $DIR/issue_75439.rs:+5:33: +5:35
|
||||
_6 = _4; // scope 4 at $DIR/issue_75439.rs:+5:33: +5:35
|
||||
_5 = transmute::<u32, [u8; 4]>(move _6) -> bb7; // scope 4 at $DIR/issue_75439.rs:+5:23: +5:36
|
||||
// mir::Constant
|
||||
// + span: $DIR/issue_75439.rs:11:23: 11:32
|
||||
// + literal: Const { ty: unsafe extern "rust-intrinsic" fn(u32) -> [u8; 4] {transmute::<u32, [u8; 4]>}, val: Value(<ZST>) }
|
||||
_5 = move _6 as [u8; 4] (Transmute); // scope 4 at $DIR/issue_75439.rs:+5:23: +5:36
|
||||
StorageDead(_6); // scope 4 at $DIR/issue_75439.rs:+5:35: +5:36
|
||||
_0 = Option::<[u8; 4]>::Some(move _5); // scope 3 at $DIR/issue_75439.rs:+5:9: +5:39
|
||||
StorageDead(_5); // scope 3 at $DIR/issue_75439.rs:+5:38: +5:39
|
||||
StorageDead(_4); // scope 1 at $DIR/issue_75439.rs:+6:5: +6:6
|
||||
goto -> bb7; // scope 1 at $DIR/issue_75439.rs:+4:5: +8:6
|
||||
}
|
||||
|
||||
bb4: {
|
||||
StorageLive(_4); // scope 3 at $DIR/issue_75439.rs:+4:27: +4:29
|
||||
_4 = _2[3 of 4]; // scope 3 at $DIR/issue_75439.rs:+4:27: +4:29
|
||||
goto -> bb3; // scope 3 at $DIR/issue_75439.rs:+4:12: +4:30
|
||||
}
|
||||
|
||||
bb5: {
|
||||
StorageLive(_4); // scope 3 at $DIR/issue_75439.rs:+4:27: +4:29
|
||||
_4 = _2[3 of 4]; // scope 3 at $DIR/issue_75439.rs:+4:27: +4:29
|
||||
goto -> bb4; // scope 3 at $DIR/issue_75439.rs:+4:12: +4:30
|
||||
goto -> bb3; // scope 3 at $DIR/issue_75439.rs:+4:12: +4:30
|
||||
}
|
||||
|
||||
bb6: {
|
||||
StorageLive(_4); // scope 3 at $DIR/issue_75439.rs:+4:27: +4:29
|
||||
_4 = _2[3 of 4]; // scope 3 at $DIR/issue_75439.rs:+4:27: +4:29
|
||||
goto -> bb4; // scope 3 at $DIR/issue_75439.rs:+4:12: +4:30
|
||||
_0 = Option::<[u8; 4]>::None; // scope 1 at $DIR/issue_75439.rs:+7:9: +7:13
|
||||
goto -> bb7; // scope 1 at $DIR/issue_75439.rs:+4:5: +8:6
|
||||
}
|
||||
|
||||
bb7: {
|
||||
StorageDead(_6); // scope 4 at $DIR/issue_75439.rs:+5:35: +5:36
|
||||
_0 = Option::<[u8; 4]>::Some(move _5); // scope 3 at $DIR/issue_75439.rs:+5:9: +5:39
|
||||
StorageDead(_5); // scope 3 at $DIR/issue_75439.rs:+5:38: +5:39
|
||||
StorageDead(_4); // scope 1 at $DIR/issue_75439.rs:+6:5: +6:6
|
||||
goto -> bb9; // scope 1 at $DIR/issue_75439.rs:+4:5: +8:6
|
||||
}
|
||||
|
||||
bb8: {
|
||||
_0 = Option::<[u8; 4]>::None; // scope 1 at $DIR/issue_75439.rs:+7:9: +7:13
|
||||
goto -> bb9; // scope 1 at $DIR/issue_75439.rs:+4:5: +8:6
|
||||
}
|
||||
|
||||
bb9: {
|
||||
StorageDead(_2); // scope 0 at $DIR/issue_75439.rs:+9:1: +9:2
|
||||
return; // scope 0 at $DIR/issue_75439.rs:+9:2: +9:2
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
StorageLive(_1); // scope 1 at $DIR/lower_intrinsics.rs:+2:9: +2:38
|
||||
- _1 = std::intrinsics::assume(const true) -> bb1; // scope 1 at $DIR/lower_intrinsics.rs:+2:9: +2:38
|
||||
- // mir::Constant
|
||||
- // + span: $DIR/lower_intrinsics.rs:72:9: 72:32
|
||||
- // + span: $DIR/lower_intrinsics.rs:105:9: 105:32
|
||||
- // + literal: Const { ty: unsafe extern "rust-intrinsic" fn(bool) {std::intrinsics::assume}, val: Value(<ZST>) }
|
||||
+ assume(const true); // scope 1 at $DIR/lower_intrinsics.rs:+2:9: +2:38
|
||||
+ goto -> bb1; // scope 1 at $DIR/lower_intrinsics.rs:+2:9: +2:38
|
||||
|
@ -31,7 +31,7 @@
|
||||
_3 = &(*_4); // scope 0 at $DIR/lower_intrinsics.rs:+1:42: +1:44
|
||||
- _2 = discriminant_value::<T>(move _3) -> bb1; // scope 0 at $DIR/lower_intrinsics.rs:+1:5: +1:45
|
||||
- // mir::Constant
|
||||
- // + span: $DIR/lower_intrinsics.rs:49:5: 49:41
|
||||
- // + span: $DIR/lower_intrinsics.rs:82:5: 82:41
|
||||
- // + literal: Const { ty: for<'a> extern "rust-intrinsic" fn(&'a T) -> <T as DiscriminantKind>::Discriminant {discriminant_value::<T>}, val: Value(<ZST>) }
|
||||
+ _2 = discriminant((*_3)); // scope 0 at $DIR/lower_intrinsics.rs:+1:5: +1:45
|
||||
+ goto -> bb1; // scope 0 at $DIR/lower_intrinsics.rs:+1:5: +1:45
|
||||
@ -46,13 +46,13 @@
|
||||
StorageLive(_7); // scope 0 at $DIR/lower_intrinsics.rs:+2:42: +2:44
|
||||
_19 = const _; // scope 0 at $DIR/lower_intrinsics.rs:+2:42: +2:44
|
||||
// mir::Constant
|
||||
// + span: $DIR/lower_intrinsics.rs:50:42: 50:44
|
||||
// + span: $DIR/lower_intrinsics.rs:83:42: 83:44
|
||||
// + literal: Const { ty: &i32, val: Unevaluated(discriminant, [T], Some(promoted[2])) }
|
||||
_7 = &(*_19); // scope 0 at $DIR/lower_intrinsics.rs:+2:42: +2:44
|
||||
_6 = &(*_7); // scope 0 at $DIR/lower_intrinsics.rs:+2:42: +2:44
|
||||
- _5 = discriminant_value::<i32>(move _6) -> bb2; // scope 0 at $DIR/lower_intrinsics.rs:+2:5: +2:45
|
||||
- // mir::Constant
|
||||
- // + span: $DIR/lower_intrinsics.rs:50:5: 50:41
|
||||
- // + span: $DIR/lower_intrinsics.rs:83:5: 83:41
|
||||
- // + literal: Const { ty: for<'a> extern "rust-intrinsic" fn(&'a i32) -> <i32 as DiscriminantKind>::Discriminant {discriminant_value::<i32>}, val: Value(<ZST>) }
|
||||
+ _5 = discriminant((*_6)); // scope 0 at $DIR/lower_intrinsics.rs:+2:5: +2:45
|
||||
+ goto -> bb2; // scope 0 at $DIR/lower_intrinsics.rs:+2:5: +2:45
|
||||
@ -67,13 +67,13 @@
|
||||
StorageLive(_11); // scope 0 at $DIR/lower_intrinsics.rs:+3:42: +3:45
|
||||
_18 = const _; // scope 0 at $DIR/lower_intrinsics.rs:+3:42: +3:45
|
||||
// mir::Constant
|
||||
// + span: $DIR/lower_intrinsics.rs:51:42: 51:45
|
||||
// + span: $DIR/lower_intrinsics.rs:84:42: 84:45
|
||||
// + literal: Const { ty: &(), val: Unevaluated(discriminant, [T], Some(promoted[1])) }
|
||||
_11 = &(*_18); // scope 0 at $DIR/lower_intrinsics.rs:+3:42: +3:45
|
||||
_10 = &(*_11); // scope 0 at $DIR/lower_intrinsics.rs:+3:42: +3:45
|
||||
- _9 = discriminant_value::<()>(move _10) -> bb3; // scope 0 at $DIR/lower_intrinsics.rs:+3:5: +3:46
|
||||
- // mir::Constant
|
||||
- // + span: $DIR/lower_intrinsics.rs:51:5: 51:41
|
||||
- // + span: $DIR/lower_intrinsics.rs:84:5: 84:41
|
||||
- // + literal: Const { ty: for<'a> extern "rust-intrinsic" fn(&'a ()) -> <() as DiscriminantKind>::Discriminant {discriminant_value::<()>}, val: Value(<ZST>) }
|
||||
+ _9 = discriminant((*_10)); // scope 0 at $DIR/lower_intrinsics.rs:+3:5: +3:46
|
||||
+ goto -> bb3; // scope 0 at $DIR/lower_intrinsics.rs:+3:5: +3:46
|
||||
@ -88,13 +88,13 @@
|
||||
StorageLive(_15); // scope 0 at $DIR/lower_intrinsics.rs:+4:42: +4:47
|
||||
_17 = const _; // scope 0 at $DIR/lower_intrinsics.rs:+4:42: +4:47
|
||||
// mir::Constant
|
||||
// + span: $DIR/lower_intrinsics.rs:52:42: 52:47
|
||||
// + span: $DIR/lower_intrinsics.rs:85:42: 85:47
|
||||
// + literal: Const { ty: &E, val: Unevaluated(discriminant, [T], Some(promoted[0])) }
|
||||
_15 = &(*_17); // scope 0 at $DIR/lower_intrinsics.rs:+4:42: +4:47
|
||||
_14 = &(*_15); // scope 0 at $DIR/lower_intrinsics.rs:+4:42: +4:47
|
||||
- _13 = discriminant_value::<E>(move _14) -> bb4; // scope 0 at $DIR/lower_intrinsics.rs:+4:5: +4:48
|
||||
- // mir::Constant
|
||||
- // + span: $DIR/lower_intrinsics.rs:52:5: 52:41
|
||||
- // + span: $DIR/lower_intrinsics.rs:85:5: 85:41
|
||||
- // + literal: Const { ty: for<'a> extern "rust-intrinsic" fn(&'a E) -> <E as DiscriminantKind>::Discriminant {discriminant_value::<E>}, val: Value(<ZST>) }
|
||||
+ _13 = discriminant((*_14)); // scope 0 at $DIR/lower_intrinsics.rs:+4:5: +4:48
|
||||
+ goto -> bb4; // scope 0 at $DIR/lower_intrinsics.rs:+4:5: +4:48
|
||||
|
@ -49,7 +49,7 @@
|
||||
StorageDead(_9); // scope 3 at $DIR/lower_intrinsics.rs:+4:90: +4:91
|
||||
- _3 = copy_nonoverlapping::<i32>(move _4, move _8, const 0_usize) -> bb1; // scope 3 at $DIR/lower_intrinsics.rs:+4:9: +4:95
|
||||
- // mir::Constant
|
||||
- // + span: $DIR/lower_intrinsics.rs:65:9: 65:28
|
||||
- // + span: $DIR/lower_intrinsics.rs:98:9: 98:28
|
||||
- // + literal: Const { ty: unsafe extern "rust-intrinsic" fn(*const i32, *mut i32, usize) {copy_nonoverlapping::<i32>}, val: Value(<ZST>) }
|
||||
+ copy_nonoverlapping(dst = move _8, src = move _4, count = const 0_usize); // scope 3 at $DIR/lower_intrinsics.rs:+4:9: +4:95
|
||||
+ goto -> bb1; // scope 3 at $DIR/lower_intrinsics.rs:+4:9: +4:95
|
||||
|
@ -24,7 +24,7 @@
|
||||
_4 = &raw const (*_1); // scope 1 at $DIR/lower_intrinsics.rs:+2:55: +2:56
|
||||
- _3 = option_payload_ptr::<usize>(move _4) -> bb1; // scope 1 at $DIR/lower_intrinsics.rs:+2:18: +2:57
|
||||
- // mir::Constant
|
||||
- // + span: $DIR/lower_intrinsics.rs:99:18: 99:54
|
||||
- // + span: $DIR/lower_intrinsics.rs:132:18: 132:54
|
||||
- // + literal: Const { ty: unsafe extern "rust-intrinsic" fn(*const Option<usize>) -> *const usize {option_payload_ptr::<usize>}, val: Value(<ZST>) }
|
||||
+ _3 = &raw const (((*_4) as Some).0: usize); // scope 1 at $DIR/lower_intrinsics.rs:+2:18: +2:57
|
||||
+ goto -> bb1; // scope 1 at $DIR/lower_intrinsics.rs:+2:18: +2:57
|
||||
@ -37,7 +37,7 @@
|
||||
_6 = &raw const (*_2); // scope 2 at $DIR/lower_intrinsics.rs:+3:55: +3:56
|
||||
- _5 = option_payload_ptr::<String>(move _6) -> bb2; // scope 2 at $DIR/lower_intrinsics.rs:+3:18: +3:57
|
||||
- // mir::Constant
|
||||
- // + span: $DIR/lower_intrinsics.rs:100:18: 100:54
|
||||
- // + span: $DIR/lower_intrinsics.rs:133:18: 133:54
|
||||
- // + literal: Const { ty: unsafe extern "rust-intrinsic" fn(*const Option<String>) -> *const String {option_payload_ptr::<String>}, val: Value(<ZST>) }
|
||||
+ _5 = &raw const (((*_6) as Some).0: std::string::String); // scope 2 at $DIR/lower_intrinsics.rs:+3:18: +3:57
|
||||
+ goto -> bb2; // scope 2 at $DIR/lower_intrinsics.rs:+3:18: +3:57
|
||||
|
@ -13,7 +13,7 @@
|
||||
_2 = &raw const (*_1); // scope 1 at $DIR/lower_intrinsics.rs:+1:46: +1:47
|
||||
- _0 = read_via_copy::<i32>(move _2) -> bb1; // scope 1 at $DIR/lower_intrinsics.rs:+1:14: +1:48
|
||||
- // mir::Constant
|
||||
- // + span: $DIR/lower_intrinsics.rs:85:14: 85:45
|
||||
- // + span: $DIR/lower_intrinsics.rs:118:14: 118:45
|
||||
- // + literal: Const { ty: unsafe extern "rust-intrinsic" fn(*const i32) -> i32 {read_via_copy::<i32>}, val: Value(<ZST>) }
|
||||
+ _0 = (*_2); // scope 1 at $DIR/lower_intrinsics.rs:+1:14: +1:48
|
||||
+ goto -> bb1; // scope 1 at $DIR/lower_intrinsics.rs:+1:14: +1:48
|
||||
|
@ -13,7 +13,7 @@
|
||||
_2 = &raw const (*_1); // scope 1 at $DIR/lower_intrinsics.rs:+1:46: +1:47
|
||||
- _0 = read_via_copy::<Never>(move _2); // scope 1 at $DIR/lower_intrinsics.rs:+1:14: +1:48
|
||||
- // mir::Constant
|
||||
- // + span: $DIR/lower_intrinsics.rs:90:14: 90:45
|
||||
- // + span: $DIR/lower_intrinsics.rs:123:14: 123:45
|
||||
- // + literal: Const { ty: unsafe extern "rust-intrinsic" fn(*const Never) -> Never {read_via_copy::<Never>}, val: Value(<ZST>) }
|
||||
+ unreachable; // scope 1 at $DIR/lower_intrinsics.rs:+1:14: +1:48
|
||||
}
|
||||
|
@ -38,6 +38,39 @@ pub fn non_const<T>() -> usize {
|
||||
size_of_t()
|
||||
}
|
||||
|
||||
// EMIT_MIR lower_intrinsics.transmute_inhabited.LowerIntrinsics.diff
|
||||
pub fn transmute_inhabited(c: std::cmp::Ordering) -> i8 {
|
||||
unsafe { std::mem::transmute(c) }
|
||||
}
|
||||
|
||||
// EMIT_MIR lower_intrinsics.transmute_uninhabited.LowerIntrinsics.diff
|
||||
pub unsafe fn transmute_uninhabited(u: ()) -> Never {
|
||||
unsafe { std::mem::transmute::<(), Never>(u) }
|
||||
}
|
||||
|
||||
// EMIT_MIR lower_intrinsics.transmute_ref_dst.LowerIntrinsics.diff
|
||||
pub unsafe fn transmute_ref_dst<T: ?Sized>(u: &T) -> *const T {
|
||||
unsafe { std::mem::transmute(u) }
|
||||
}
|
||||
|
||||
// EMIT_MIR lower_intrinsics.transmute_to_ref_uninhabited.LowerIntrinsics.diff
|
||||
pub unsafe fn transmute_to_ref_uninhabited() -> ! {
|
||||
let x: &Never = std::mem::transmute(1usize);
|
||||
match *x {}
|
||||
}
|
||||
|
||||
// EMIT_MIR lower_intrinsics.transmute_to_mut_uninhabited.LowerIntrinsics.diff
|
||||
pub unsafe fn transmute_to_mut_uninhabited() -> ! {
|
||||
let x: &mut Never = std::mem::transmute(1usize);
|
||||
match *x {}
|
||||
}
|
||||
|
||||
// EMIT_MIR lower_intrinsics.transmute_to_box_uninhabited.LowerIntrinsics.diff
|
||||
pub unsafe fn transmute_to_box_uninhabited() -> ! {
|
||||
let x: Box<Never> = std::mem::transmute(1usize);
|
||||
match *x {}
|
||||
}
|
||||
|
||||
pub enum E {
|
||||
A,
|
||||
B,
|
||||
|
@ -0,0 +1,27 @@
|
||||
- // MIR for `transmute_inhabited` before LowerIntrinsics
|
||||
+ // MIR for `transmute_inhabited` after LowerIntrinsics
|
||||
|
||||
fn transmute_inhabited(_1: std::cmp::Ordering) -> i8 {
|
||||
debug c => _1; // in scope 0 at $DIR/lower_intrinsics.rs:+0:28: +0:29
|
||||
let mut _0: i8; // return place in scope 0 at $DIR/lower_intrinsics.rs:+0:54: +0:56
|
||||
let mut _2: std::cmp::Ordering; // in scope 0 at $DIR/lower_intrinsics.rs:+1:34: +1:35
|
||||
scope 1 {
|
||||
}
|
||||
|
||||
bb0: {
|
||||
StorageLive(_2); // scope 1 at $DIR/lower_intrinsics.rs:+1:34: +1:35
|
||||
_2 = _1; // scope 1 at $DIR/lower_intrinsics.rs:+1:34: +1:35
|
||||
- _0 = transmute::<std::cmp::Ordering, i8>(move _2) -> bb1; // scope 1 at $DIR/lower_intrinsics.rs:+1:14: +1:36
|
||||
- // mir::Constant
|
||||
- // + span: $DIR/lower_intrinsics.rs:43:14: 43:33
|
||||
- // + literal: Const { ty: unsafe extern "rust-intrinsic" fn(std::cmp::Ordering) -> i8 {transmute::<std::cmp::Ordering, i8>}, val: Value(<ZST>) }
|
||||
+ _0 = move _2 as i8 (Transmute); // scope 1 at $DIR/lower_intrinsics.rs:+1:14: +1:36
|
||||
+ goto -> bb1; // scope 1 at $DIR/lower_intrinsics.rs:+1:14: +1:36
|
||||
}
|
||||
|
||||
bb1: {
|
||||
StorageDead(_2); // scope 1 at $DIR/lower_intrinsics.rs:+1:35: +1:36
|
||||
return; // scope 0 at $DIR/lower_intrinsics.rs:+2:2: +2:2
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,27 @@
|
||||
- // MIR for `transmute_ref_dst` before LowerIntrinsics
|
||||
+ // MIR for `transmute_ref_dst` after LowerIntrinsics
|
||||
|
||||
fn transmute_ref_dst(_1: &T) -> *const T {
|
||||
debug u => _1; // in scope 0 at $DIR/lower_intrinsics.rs:+0:44: +0:45
|
||||
let mut _0: *const T; // return place in scope 0 at $DIR/lower_intrinsics.rs:+0:54: +0:62
|
||||
let mut _2: &T; // in scope 0 at $DIR/lower_intrinsics.rs:+1:34: +1:35
|
||||
scope 1 {
|
||||
}
|
||||
|
||||
bb0: {
|
||||
StorageLive(_2); // scope 1 at $DIR/lower_intrinsics.rs:+1:34: +1:35
|
||||
_2 = _1; // scope 1 at $DIR/lower_intrinsics.rs:+1:34: +1:35
|
||||
- _0 = transmute::<&T, *const T>(move _2) -> bb1; // scope 1 at $DIR/lower_intrinsics.rs:+1:14: +1:36
|
||||
- // mir::Constant
|
||||
- // + span: $DIR/lower_intrinsics.rs:53:14: 53:33
|
||||
- // + literal: Const { ty: unsafe extern "rust-intrinsic" fn(&T) -> *const T {transmute::<&T, *const T>}, val: Value(<ZST>) }
|
||||
+ _0 = move _2 as *const T (Transmute); // scope 1 at $DIR/lower_intrinsics.rs:+1:14: +1:36
|
||||
+ goto -> bb1; // scope 1 at $DIR/lower_intrinsics.rs:+1:14: +1:36
|
||||
}
|
||||
|
||||
bb1: {
|
||||
StorageDead(_2); // scope 1 at $DIR/lower_intrinsics.rs:+1:35: +1:36
|
||||
return; // scope 0 at $DIR/lower_intrinsics.rs:+2:2: +2:2
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,29 @@
|
||||
- // MIR for `transmute_to_box_uninhabited` before LowerIntrinsics
|
||||
+ // MIR for `transmute_to_box_uninhabited` after LowerIntrinsics
|
||||
|
||||
fn transmute_to_box_uninhabited() -> ! {
|
||||
let mut _0: !; // return place in scope 0 at $DIR/lower_intrinsics.rs:+0:49: +0:50
|
||||
let mut _1: !; // in scope 0 at $DIR/lower_intrinsics.rs:+0:51: +3:2
|
||||
let _2: std::boxed::Box<Never>; // in scope 0 at $DIR/lower_intrinsics.rs:+1:9: +1:10
|
||||
let mut _3: !; // in scope 0 at $DIR/lower_intrinsics.rs:+2:5: +2:16
|
||||
scope 1 {
|
||||
debug x => _2; // in scope 1 at $DIR/lower_intrinsics.rs:+1:9: +1:10
|
||||
}
|
||||
|
||||
bb0: {
|
||||
StorageLive(_1); // scope 0 at $DIR/lower_intrinsics.rs:+0:51: +3:2
|
||||
StorageLive(_2); // scope 0 at $DIR/lower_intrinsics.rs:+1:9: +1:10
|
||||
- _2 = transmute::<usize, Box<Never>>(const 1_usize) -> bb1; // scope 0 at $DIR/lower_intrinsics.rs:+1:25: +1:52
|
||||
- // mir::Constant
|
||||
- // + span: $DIR/lower_intrinsics.rs:70:25: 70:44
|
||||
- // + literal: Const { ty: unsafe extern "rust-intrinsic" fn(usize) -> Box<Never> {transmute::<usize, Box<Never>>}, val: Value(<ZST>) }
|
||||
+ _2 = const 1_usize as std::boxed::Box<Never> (Transmute); // scope 0 at $DIR/lower_intrinsics.rs:+1:25: +1:52
|
||||
+ goto -> bb1; // scope 0 at $DIR/lower_intrinsics.rs:+1:25: +1:52
|
||||
}
|
||||
|
||||
bb1: {
|
||||
StorageLive(_3); // scope 1 at $DIR/lower_intrinsics.rs:+2:5: +2:16
|
||||
unreachable; // scope 1 at $DIR/lower_intrinsics.rs:+2:11: +2:13
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,29 @@
|
||||
- // MIR for `transmute_to_mut_uninhabited` before LowerIntrinsics
|
||||
+ // MIR for `transmute_to_mut_uninhabited` after LowerIntrinsics
|
||||
|
||||
fn transmute_to_mut_uninhabited() -> ! {
|
||||
let mut _0: !; // return place in scope 0 at $DIR/lower_intrinsics.rs:+0:49: +0:50
|
||||
let mut _1: !; // in scope 0 at $DIR/lower_intrinsics.rs:+0:51: +3:2
|
||||
let _2: &mut Never; // in scope 0 at $DIR/lower_intrinsics.rs:+1:9: +1:10
|
||||
let mut _3: !; // in scope 0 at $DIR/lower_intrinsics.rs:+2:5: +2:16
|
||||
scope 1 {
|
||||
debug x => _2; // in scope 1 at $DIR/lower_intrinsics.rs:+1:9: +1:10
|
||||
}
|
||||
|
||||
bb0: {
|
||||
StorageLive(_1); // scope 0 at $DIR/lower_intrinsics.rs:+0:51: +3:2
|
||||
StorageLive(_2); // scope 0 at $DIR/lower_intrinsics.rs:+1:9: +1:10
|
||||
- _2 = transmute::<usize, &mut Never>(const 1_usize) -> bb1; // scope 0 at $DIR/lower_intrinsics.rs:+1:25: +1:52
|
||||
- // mir::Constant
|
||||
- // + span: $DIR/lower_intrinsics.rs:64:25: 64:44
|
||||
- // + literal: Const { ty: unsafe extern "rust-intrinsic" fn(usize) -> &mut Never {transmute::<usize, &mut Never>}, val: Value(<ZST>) }
|
||||
+ _2 = const 1_usize as &mut Never (Transmute); // scope 0 at $DIR/lower_intrinsics.rs:+1:25: +1:52
|
||||
+ goto -> bb1; // scope 0 at $DIR/lower_intrinsics.rs:+1:25: +1:52
|
||||
}
|
||||
|
||||
bb1: {
|
||||
StorageLive(_3); // scope 1 at $DIR/lower_intrinsics.rs:+2:5: +2:16
|
||||
unreachable; // scope 1 at $DIR/lower_intrinsics.rs:+2:11: +2:13
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,29 @@
|
||||
- // MIR for `transmute_to_ref_uninhabited` before LowerIntrinsics
|
||||
+ // MIR for `transmute_to_ref_uninhabited` after LowerIntrinsics
|
||||
|
||||
fn transmute_to_ref_uninhabited() -> ! {
|
||||
let mut _0: !; // return place in scope 0 at $DIR/lower_intrinsics.rs:+0:49: +0:50
|
||||
let mut _1: !; // in scope 0 at $DIR/lower_intrinsics.rs:+0:51: +3:2
|
||||
let _2: &Never; // in scope 0 at $DIR/lower_intrinsics.rs:+1:9: +1:10
|
||||
let mut _3: !; // in scope 0 at $DIR/lower_intrinsics.rs:+2:5: +2:16
|
||||
scope 1 {
|
||||
debug x => _2; // in scope 1 at $DIR/lower_intrinsics.rs:+1:9: +1:10
|
||||
}
|
||||
|
||||
bb0: {
|
||||
StorageLive(_1); // scope 0 at $DIR/lower_intrinsics.rs:+0:51: +3:2
|
||||
StorageLive(_2); // scope 0 at $DIR/lower_intrinsics.rs:+1:9: +1:10
|
||||
- _2 = transmute::<usize, &Never>(const 1_usize) -> bb1; // scope 0 at $DIR/lower_intrinsics.rs:+1:21: +1:48
|
||||
- // mir::Constant
|
||||
- // + span: $DIR/lower_intrinsics.rs:58:21: 58:40
|
||||
- // + literal: Const { ty: unsafe extern "rust-intrinsic" fn(usize) -> &Never {transmute::<usize, &Never>}, val: Value(<ZST>) }
|
||||
+ _2 = const 1_usize as &Never (Transmute); // scope 0 at $DIR/lower_intrinsics.rs:+1:21: +1:48
|
||||
+ goto -> bb1; // scope 0 at $DIR/lower_intrinsics.rs:+1:21: +1:48
|
||||
}
|
||||
|
||||
bb1: {
|
||||
StorageLive(_3); // scope 1 at $DIR/lower_intrinsics.rs:+2:5: +2:16
|
||||
unreachable; // scope 1 at $DIR/lower_intrinsics.rs:+2:11: +2:13
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,22 @@
|
||||
- // MIR for `transmute_uninhabited` before LowerIntrinsics
|
||||
+ // MIR for `transmute_uninhabited` after LowerIntrinsics
|
||||
|
||||
fn transmute_uninhabited(_1: ()) -> Never {
|
||||
debug u => _1; // in scope 0 at $DIR/lower_intrinsics.rs:+0:37: +0:38
|
||||
let mut _0: Never; // return place in scope 0 at $DIR/lower_intrinsics.rs:+0:47: +0:52
|
||||
let mut _2: (); // in scope 0 at $DIR/lower_intrinsics.rs:+1:47: +1:48
|
||||
scope 1 {
|
||||
}
|
||||
|
||||
bb0: {
|
||||
StorageLive(_2); // scope 1 at $DIR/lower_intrinsics.rs:+1:47: +1:48
|
||||
_2 = _1; // scope 1 at $DIR/lower_intrinsics.rs:+1:47: +1:48
|
||||
- _0 = transmute::<(), Never>(move _2); // scope 1 at $DIR/lower_intrinsics.rs:+1:14: +1:49
|
||||
- // mir::Constant
|
||||
- // + span: $DIR/lower_intrinsics.rs:48:14: 48:46
|
||||
- // + literal: Const { ty: unsafe extern "rust-intrinsic" fn(()) -> Never {transmute::<(), Never>}, val: Value(<ZST>) }
|
||||
+ _0 = move _2 as Never (Transmute); // scope 1 at $DIR/lower_intrinsics.rs:+1:14: +1:49
|
||||
+ unreachable; // scope 1 at $DIR/lower_intrinsics.rs:+1:14: +1:49
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@
|
||||
_5 = _2; // scope 0 at $DIR/lower_intrinsics.rs:+1:53: +1:54
|
||||
- _3 = add_with_overflow::<i32>(move _4, move _5) -> bb1; // scope 0 at $DIR/lower_intrinsics.rs:+1:14: +1:55
|
||||
- // mir::Constant
|
||||
- // + span: $DIR/lower_intrinsics.rs:78:14: 78:49
|
||||
- // + span: $DIR/lower_intrinsics.rs:111:14: 111:49
|
||||
- // + literal: Const { ty: extern "rust-intrinsic" fn(i32, i32) -> (i32, bool) {add_with_overflow::<i32>}, val: Value(<ZST>) }
|
||||
+ _3 = CheckedAdd(move _4, move _5); // scope 0 at $DIR/lower_intrinsics.rs:+1:14: +1:55
|
||||
+ goto -> bb1; // scope 0 at $DIR/lower_intrinsics.rs:+1:14: +1:55
|
||||
@ -48,7 +48,7 @@
|
||||
_8 = _2; // scope 1 at $DIR/lower_intrinsics.rs:+2:53: +2:54
|
||||
- _6 = sub_with_overflow::<i32>(move _7, move _8) -> bb2; // scope 1 at $DIR/lower_intrinsics.rs:+2:14: +2:55
|
||||
- // mir::Constant
|
||||
- // + span: $DIR/lower_intrinsics.rs:79:14: 79:49
|
||||
- // + span: $DIR/lower_intrinsics.rs:112:14: 112:49
|
||||
- // + literal: Const { ty: extern "rust-intrinsic" fn(i32, i32) -> (i32, bool) {sub_with_overflow::<i32>}, val: Value(<ZST>) }
|
||||
+ _6 = CheckedSub(move _7, move _8); // scope 1 at $DIR/lower_intrinsics.rs:+2:14: +2:55
|
||||
+ goto -> bb2; // scope 1 at $DIR/lower_intrinsics.rs:+2:14: +2:55
|
||||
@ -64,7 +64,7 @@
|
||||
_11 = _2; // scope 2 at $DIR/lower_intrinsics.rs:+3:53: +3:54
|
||||
- _9 = mul_with_overflow::<i32>(move _10, move _11) -> bb3; // scope 2 at $DIR/lower_intrinsics.rs:+3:14: +3:55
|
||||
- // mir::Constant
|
||||
- // + span: $DIR/lower_intrinsics.rs:80:14: 80:49
|
||||
- // + span: $DIR/lower_intrinsics.rs:113:14: 113:49
|
||||
- // + literal: Const { ty: extern "rust-intrinsic" fn(i32, i32) -> (i32, bool) {mul_with_overflow::<i32>}, val: Value(<ZST>) }
|
||||
+ _9 = CheckedMul(move _10, move _11); // scope 2 at $DIR/lower_intrinsics.rs:+3:14: +3:55
|
||||
+ goto -> bb3; // scope 2 at $DIR/lower_intrinsics.rs:+3:14: +3:55
|
||||
|
@ -11,5 +11,5 @@ impl PrintName {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let _ = PrintName::VOID; //~ constant
|
||||
let _ = PrintName::VOID; //~ erroneous constant used
|
||||
}
|
||||
|
24
tests/ui/consts/const-eval/transmute-size-mismatch.rs
Normal file
24
tests/ui/consts/const-eval/transmute-size-mismatch.rs
Normal file
@ -0,0 +1,24 @@
|
||||
#![feature(core_intrinsics)]
|
||||
#![feature(custom_mir)]
|
||||
|
||||
// These cases are statically rejected by `mem::transmute`, so we need custom
|
||||
// MIR to be able to get to constant evaluation.
|
||||
use std::intrinsics::mir::*;
|
||||
|
||||
#[custom_mir(dialect = "runtime", phase = "initial")]
|
||||
const unsafe fn mir_transmute<T, U>(x: T) -> U {
|
||||
mir!{
|
||||
{
|
||||
RET = CastTransmute(x);
|
||||
//~^ ERROR evaluation of constant value failed
|
||||
//~| ERROR evaluation of constant value failed
|
||||
Return()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const FROM_BIGGER: u16 = unsafe { mir_transmute(123_i32) };
|
||||
|
||||
const FROM_SMALLER: u32 = unsafe { mir_transmute(123_i16) };
|
||||
|
||||
fn main() {}
|
37
tests/ui/consts/const-eval/transmute-size-mismatch.stderr
Normal file
37
tests/ui/consts/const-eval/transmute-size-mismatch.stderr
Normal file
@ -0,0 +1,37 @@
|
||||
error[E0080]: evaluation of constant value failed
|
||||
--> $DIR/transmute-size-mismatch.rs:12:13
|
||||
|
|
||||
LL | RET = CastTransmute(x);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ transmuting from 4-byte type to 2-byte type: `i32` -> `u16`
|
||||
|
|
||||
note: inside `mir_transmute::<i32, u16>`
|
||||
--> $DIR/transmute-size-mismatch.rs:12:13
|
||||
|
|
||||
LL | RET = CastTransmute(x);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
note: inside `FROM_BIGGER`
|
||||
--> $DIR/transmute-size-mismatch.rs:20:35
|
||||
|
|
||||
LL | const FROM_BIGGER: u16 = unsafe { mir_transmute(123_i32) };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error[E0080]: evaluation of constant value failed
|
||||
--> $DIR/transmute-size-mismatch.rs:12:13
|
||||
|
|
||||
LL | RET = CastTransmute(x);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ transmuting from 2-byte type to 4-byte type: `i16` -> `u32`
|
||||
|
|
||||
note: inside `mir_transmute::<i16, u32>`
|
||||
--> $DIR/transmute-size-mismatch.rs:12:13
|
||||
|
|
||||
LL | RET = CastTransmute(x);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
note: inside `FROM_SMALLER`
|
||||
--> $DIR/transmute-size-mismatch.rs:22:36
|
||||
|
|
||||
LL | const FROM_SMALLER: u32 = unsafe { mir_transmute(123_i16) };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: aborting due to 2 previous errors
|
||||
|
||||
For more information about this error, try `rustc --explain E0080`.
|
@ -108,13 +108,13 @@ error[E0080]: evaluation of constant value failed
|
||||
--> $DIR/ub-enum.rs:96:77
|
||||
|
|
||||
LL | const BAD_UNINHABITED_WITH_DATA1: Result<(i32, Never), (i32, !)> = unsafe { mem::transmute(0u64) };
|
||||
| ^^^^^^^^^^^^^^^^^^^^ transmuting to uninhabited type
|
||||
| ^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .<enum-variant(Ok)>.0.1: encountered a value of uninhabited type Never
|
||||
|
||||
error[E0080]: evaluation of constant value failed
|
||||
--> $DIR/ub-enum.rs:98:77
|
||||
|
|
||||
LL | const BAD_UNINHABITED_WITH_DATA2: Result<(i32, !), (i32, Never)> = unsafe { mem::transmute(0u64) };
|
||||
| ^^^^^^^^^^^^^^^^^^^^ transmuting to uninhabited type
|
||||
| ^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .<enum-variant(Ok)>.0.1: encountered a value of the never type `!`
|
||||
|
||||
error: aborting due to 13 previous errors
|
||||
|
||||
|
@ -108,13 +108,13 @@ error[E0080]: evaluation of constant value failed
|
||||
--> $DIR/ub-enum.rs:96:77
|
||||
|
|
||||
LL | const BAD_UNINHABITED_WITH_DATA1: Result<(i32, Never), (i32, !)> = unsafe { mem::transmute(0u64) };
|
||||
| ^^^^^^^^^^^^^^^^^^^^ transmuting to uninhabited type
|
||||
| ^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .<enum-variant(Ok)>.0.1: encountered a value of uninhabited type Never
|
||||
|
||||
error[E0080]: evaluation of constant value failed
|
||||
--> $DIR/ub-enum.rs:98:77
|
||||
|
|
||||
LL | const BAD_UNINHABITED_WITH_DATA2: Result<(i32, !), (i32, Never)> = unsafe { mem::transmute(0u64) };
|
||||
| ^^^^^^^^^^^^^^^^^^^^ transmuting to uninhabited type
|
||||
| ^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .<enum-variant(Ok)>.0.1: encountered a value of the never type `!`
|
||||
|
||||
error: aborting due to 13 previous errors
|
||||
|
||||
|
@ -11,7 +11,7 @@ error[E0080]: evaluation of constant value failed
|
||||
--> $DIR/validate_uninhabited_zsts.rs:4:14
|
||||
|
|
||||
LL | unsafe { std::mem::transmute(()) }
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ transmuting to uninhabited type
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered a value of the never type `!`
|
||||
|
|
||||
note: inside `foo`
|
||||
--> $DIR/validate_uninhabited_zsts.rs:4:14
|
||||
|
@ -11,7 +11,7 @@ error[E0080]: evaluation of constant value failed
|
||||
--> $DIR/validate_uninhabited_zsts.rs:4:14
|
||||
|
|
||||
LL | unsafe { std::mem::transmute(()) }
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ transmuting to uninhabited type
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered a value of the never type `!`
|
||||
|
|
||||
note: inside `foo`
|
||||
--> $DIR/validate_uninhabited_zsts.rs:4:14
|
||||
|
@ -47,7 +47,7 @@ error[E0080]: could not evaluate static initializer
|
||||
--> $DIR/uninhabited-static.rs:12:31
|
||||
|
|
||||
LL | static VOID2: Void = unsafe { std::mem::transmute(()) };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ transmuting to uninhabited type
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered a value of uninhabited type Void
|
||||
|
||||
warning: the type `Void` does not permit zero-initialization
|
||||
--> $DIR/uninhabited-static.rs:12:31
|
||||
@ -66,7 +66,7 @@ error[E0080]: could not evaluate static initializer
|
||||
--> $DIR/uninhabited-static.rs:16:32
|
||||
|
|
||||
LL | static NEVER2: Void = unsafe { std::mem::transmute(()) };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ transmuting to uninhabited type
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered a value of uninhabited type Void
|
||||
|
||||
warning: the type `Void` does not permit zero-initialization
|
||||
--> $DIR/uninhabited-static.rs:16:32
|
||||
|
Loading…
Reference in New Issue
Block a user