Skip to content

Commit e216300

Browse files
committed
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>
2 parents df7fd99 + 64cce5f commit e216300

File tree

55 files changed

+953
-188
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+953
-188
lines changed

compiler/rustc_borrowck/src/type_check/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -2222,6 +2222,13 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
22222222
}
22232223
}
22242224
}
2225+
CastKind::Transmute => {
2226+
span_mirbug!(
2227+
self,
2228+
rvalue,
2229+
"Unexpected CastKind::Transmute, which is not permitted in Analysis MIR",
2230+
);
2231+
}
22252232
}
22262233
}
22272234

compiler/rustc_codegen_cranelift/src/base.rs

+4
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,10 @@ fn codegen_stmt<'tcx>(
709709
let operand = codegen_operand(fx, operand);
710710
operand.coerce_dyn_star(fx, lval);
711711
}
712+
Rvalue::Cast(CastKind::Transmute, ref operand, _to_ty) => {
713+
let operand = codegen_operand(fx, operand);
714+
lval.write_cvalue_transmute(fx, operand);
715+
}
712716
Rvalue::Discriminant(place) => {
713717
let place = codegen_place(fx, place);
714718
let value = place.to_cvalue(fx);

compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs

-10
Original file line numberDiff line numberDiff line change
@@ -557,16 +557,6 @@ fn codegen_regular_intrinsic_call<'tcx>(
557557
fx.bcx.ins().band(ptr, mask);
558558
}
559559

560-
sym::transmute => {
561-
intrinsic_args!(fx, args => (from); intrinsic);
562-
563-
if ret.layout().abi.is_uninhabited() {
564-
crate::base::codegen_panic(fx, "Transmuting to uninhabited type.", source_info);
565-
return;
566-
}
567-
568-
ret.write_cvalue_transmute(fx, from);
569-
}
570560
sym::write_bytes | sym::volatile_set_memory => {
571561
intrinsic_args!(fx, args => (dst, val, count); intrinsic);
572562
let val = val.load_scalar(fx);

compiler/rustc_codegen_llvm/src/intrinsic.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ impl<'ll, 'tcx> IntrinsicCallMethods<'tcx> for Builder<'_, 'll, 'tcx> {
378378
}
379379
}
380380

381-
_ => bug!("unknown intrinsic '{}'", name),
381+
_ => bug!("unknown intrinsic '{}' -- should it have been lowered earlier?", name),
382382
};
383383

384384
if !fn_abi.ret.is_ignore() {

compiler/rustc_codegen_ssa/src/mir/block.rs

+1-84
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use rustc_index::vec::Idx;
1616
use rustc_middle::mir::{self, AssertKind, SwitchTargets};
1717
use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf, ValidityRequirement};
1818
use rustc_middle::ty::print::{with_no_trimmed_paths, with_no_visible_paths};
19-
use rustc_middle::ty::{self, Instance, Ty, TypeVisitableExt};
19+
use rustc_middle::ty::{self, Instance, Ty};
2020
use rustc_session::config::OptLevel;
2121
use rustc_span::source_map::Span;
2222
use rustc_span::{sym, Symbol};
@@ -769,23 +769,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
769769
None => bx.fn_abi_of_fn_ptr(sig, extra_args),
770770
};
771771

772-
if intrinsic == Some(sym::transmute) {
773-
return if let Some(target) = target {
774-
self.codegen_transmute(bx, &args[0], destination);
775-
helper.funclet_br(self, bx, target, mergeable_succ)
776-
} else {
777-
// If we are trying to transmute to an uninhabited type,
778-
// it is likely there is no allotted destination. In fact,
779-
// transmuting to an uninhabited type is UB, which means
780-
// we can do what we like. Here, we declare that transmuting
781-
// into an uninhabited type is impossible, so anything following
782-
// it must be unreachable.
783-
assert_eq!(fn_abi.ret.layout.abi, abi::Abi::Uninhabited);
784-
bx.unreachable();
785-
MergingSucc::False
786-
};
787-
}
788-
789772
if let Some(merging_succ) = self.codegen_panic_intrinsic(
790773
&helper,
791774
bx,
@@ -828,7 +811,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
828811

829812
match intrinsic {
830813
None | Some(sym::drop_in_place) => {}
831-
Some(sym::copy_nonoverlapping) => unreachable!(),
832814
Some(intrinsic) => {
833815
let dest = match ret_dest {
834816
_ if fn_abi.ret.is_indirect() => llargs[0],
@@ -1739,71 +1721,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
17391721
}
17401722
}
17411723

1742-
fn codegen_transmute(&mut self, bx: &mut Bx, src: &mir::Operand<'tcx>, dst: mir::Place<'tcx>) {
1743-
if let Some(index) = dst.as_local() {
1744-
match self.locals[index] {
1745-
LocalRef::Place(place) => self.codegen_transmute_into(bx, src, place),
1746-
LocalRef::UnsizedPlace(_) => bug!("transmute must not involve unsized locals"),
1747-
LocalRef::Operand(None) => {
1748-
let dst_layout = bx.layout_of(self.monomorphized_place_ty(dst.as_ref()));
1749-
assert!(!dst_layout.ty.has_erasable_regions());
1750-
let place = PlaceRef::alloca(bx, dst_layout);
1751-
place.storage_live(bx);
1752-
self.codegen_transmute_into(bx, src, place);
1753-
let op = bx.load_operand(place);
1754-
place.storage_dead(bx);
1755-
self.locals[index] = LocalRef::Operand(Some(op));
1756-
self.debug_introduce_local(bx, index);
1757-
}
1758-
LocalRef::Operand(Some(op)) => {
1759-
assert!(op.layout.is_zst(), "assigning to initialized SSAtemp");
1760-
}
1761-
}
1762-
} else {
1763-
let dst = self.codegen_place(bx, dst.as_ref());
1764-
self.codegen_transmute_into(bx, src, dst);
1765-
}
1766-
}
1767-
1768-
fn codegen_transmute_into(
1769-
&mut self,
1770-
bx: &mut Bx,
1771-
src: &mir::Operand<'tcx>,
1772-
dst: PlaceRef<'tcx, Bx::Value>,
1773-
) {
1774-
let src = self.codegen_operand(bx, src);
1775-
1776-
// Special-case transmutes between scalars as simple bitcasts.
1777-
match (src.layout.abi, dst.layout.abi) {
1778-
(abi::Abi::Scalar(src_scalar), abi::Abi::Scalar(dst_scalar)) => {
1779-
// HACK(eddyb) LLVM doesn't like `bitcast`s between pointers and non-pointers.
1780-
let src_is_ptr = matches!(src_scalar.primitive(), abi::Pointer(_));
1781-
let dst_is_ptr = matches!(dst_scalar.primitive(), abi::Pointer(_));
1782-
if src_is_ptr == dst_is_ptr {
1783-
assert_eq!(src.layout.size, dst.layout.size);
1784-
1785-
// NOTE(eddyb) the `from_immediate` and `to_immediate_scalar`
1786-
// conversions allow handling `bool`s the same as `u8`s.
1787-
let src = bx.from_immediate(src.immediate());
1788-
// LLVM also doesn't like `bitcast`s between pointers in different address spaces.
1789-
let src_as_dst = if src_is_ptr {
1790-
bx.pointercast(src, bx.backend_type(dst.layout))
1791-
} else {
1792-
bx.bitcast(src, bx.backend_type(dst.layout))
1793-
};
1794-
Immediate(bx.to_immediate_scalar(src_as_dst, dst_scalar)).store(bx, dst);
1795-
return;
1796-
}
1797-
}
1798-
_ => {}
1799-
}
1800-
1801-
let llty = bx.backend_type(src.layout);
1802-
let cast_ptr = bx.pointercast(dst.llval, bx.type_ptr_to(llty));
1803-
let align = src.layout.align.abi.min(dst.align);
1804-
src.val.store(bx, PlaceRef::new_sized_aligned(cast_ptr, src.layout, align));
1805-
}
1806-
18071724
// Stores the return value of a function call into it's final location.
18081725
fn store_return(
18091726
&mut self,

compiler/rustc_codegen_ssa/src/mir/rvalue.rs

+59-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use rustc_middle::ty::cast::{CastTy, IntTy};
1313
use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf};
1414
use rustc_middle::ty::{self, adjustment::PointerCast, Instance, Ty, TyCtxt};
1515
use rustc_span::source_map::{Span, DUMMY_SP};
16-
use rustc_target::abi::VariantIdx;
16+
use rustc_target::abi::{self, VariantIdx};
1717

1818
impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
1919
#[instrument(level = "trace", skip(self, bx))]
@@ -72,6 +72,11 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
7272
}
7373
}
7474

75+
mir::Rvalue::Cast(mir::CastKind::Transmute, ref operand, _ty) => {
76+
let src = self.codegen_operand(bx, operand);
77+
self.codegen_transmute(bx, src, dest);
78+
}
79+
7580
mir::Rvalue::Repeat(ref elem, count) => {
7681
let cg_elem = self.codegen_operand(bx, elem);
7782

@@ -143,6 +148,52 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
143148
}
144149
}
145150

151+
fn codegen_transmute(
152+
&mut self,
153+
bx: &mut Bx,
154+
src: OperandRef<'tcx, Bx::Value>,
155+
dst: PlaceRef<'tcx, Bx::Value>,
156+
) {
157+
// The MIR validator enforces no unsized transmutes.
158+
debug_assert!(src.layout.is_sized());
159+
debug_assert!(dst.layout.is_sized());
160+
161+
if src.layout.size != dst.layout.size
162+
|| src.layout.abi == abi::Abi::Uninhabited
163+
|| dst.layout.abi == abi::Abi::Uninhabited
164+
{
165+
// In all of these cases it's UB to run this transmute, but that's
166+
// known statically so might as well trap for it, rather than just
167+
// making it unreachable.
168+
bx.abort();
169+
return;
170+
}
171+
172+
let size_in_bytes = src.layout.size.bytes();
173+
if size_in_bytes == 0 {
174+
// Nothing to write
175+
return;
176+
}
177+
178+
match src.val {
179+
OperandValue::Ref(src_llval, meta, src_align) => {
180+
debug_assert_eq!(meta, None);
181+
// For a place-to-place transmute, call `memcpy` directly so that
182+
// both arguments get the best-available alignment information.
183+
let bytes = bx.cx().const_usize(size_in_bytes);
184+
let flags = MemFlags::empty();
185+
bx.memcpy(dst.llval, dst.align, src_llval, src_align, bytes, flags);
186+
}
187+
OperandValue::Immediate(_) | OperandValue::Pair(_, _) => {
188+
// When we have immediate(s), the alignment of the source is irrelevant,
189+
// so we can store them using the destination's alignment.
190+
let llty = bx.backend_type(src.layout);
191+
let cast_ptr = bx.pointercast(dst.llval, bx.type_ptr_to(llty));
192+
src.val.store(bx, PlaceRef::new_sized_aligned(cast_ptr, src.layout, dst.align));
193+
}
194+
}
195+
}
196+
146197
pub fn codegen_rvalue_unsized(
147198
&mut self,
148199
bx: &mut Bx,
@@ -344,6 +395,9 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
344395
};
345396
OperandValue::Immediate(newval)
346397
}
398+
mir::CastKind::Transmute => {
399+
bug!("Transmute operand {:?} in `codegen_rvalue_operand`", operand);
400+
}
347401
};
348402
OperandRef { val, layout: cast }
349403
}
@@ -673,6 +727,10 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
673727
impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
674728
pub fn rvalue_creates_operand(&self, rvalue: &mir::Rvalue<'tcx>, span: Span) -> bool {
675729
match *rvalue {
730+
mir::Rvalue::Cast(mir::CastKind::Transmute, ..) =>
731+
// FIXME: Now that transmute is an Rvalue, it would be nice if
732+
// it could create `Immediate`s for scalars, where possible.
733+
false,
676734
mir::Rvalue::Ref(..) |
677735
mir::Rvalue::CopyForDeref(..) |
678736
mir::Rvalue::AddressOf(..) |

compiler/rustc_const_eval/src/interpret/cast.rs

+16
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,22 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
133133
bug!()
134134
}
135135
}
136+
137+
Transmute => {
138+
assert!(src.layout.is_sized());
139+
assert!(dest.layout.is_sized());
140+
if src.layout.size != dest.layout.size {
141+
throw_ub_format!(
142+
"transmuting from {}-byte type to {}-byte type: `{}` -> `{}`",
143+
src.layout.size.bytes(),
144+
dest.layout.size.bytes(),
145+
src.layout.ty,
146+
dest.layout.ty,
147+
);
148+
}
149+
150+
self.copy_op(src, dest, /*allow_transmute*/ true)?;
151+
}
136152
}
137153
Ok(())
138154
}

compiler/rustc_const_eval/src/interpret/intrinsics.rs

-4
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,6 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
127127
// First handle intrinsics without return place.
128128
let ret = match ret {
129129
None => match intrinsic_name {
130-
sym::transmute => throw_ub_format!("transmuting to uninhabited type"),
131130
sym::abort => M::abort(self, "the program aborted execution".to_owned())?,
132131
// Unsupported diverging intrinsic.
133132
_ => return Ok(false),
@@ -411,9 +410,6 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
411410
self.exact_div(&val, &size, dest)?;
412411
}
413412

414-
sym::transmute => {
415-
self.copy_op(&args[0], dest, /*allow_transmute*/ true)?;
416-
}
417413
sym::assert_inhabited
418414
| sym::assert_zero_valid
419415
| sym::assert_mem_uninitialized_valid => {

compiler/rustc_const_eval/src/transform/validate.rs

+27
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,33 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
621621
);
622622
}
623623
}
624+
CastKind::Transmute => {
625+
if let MirPhase::Runtime(..) = self.mir_phase {
626+
// Unlike `mem::transmute`, a MIR `Transmute` is well-formed
627+
// for any two `Sized` types, just potentially UB to run.
628+
629+
if !op_ty.is_sized(self.tcx, self.param_env) {
630+
self.fail(
631+
location,
632+
format!("Cannot transmute from non-`Sized` type {op_ty:?}"),
633+
);
634+
}
635+
if !target_type.is_sized(self.tcx, self.param_env) {
636+
self.fail(
637+
location,
638+
format!("Cannot transmute to non-`Sized` type {target_type:?}"),
639+
);
640+
}
641+
} else {
642+
self.fail(
643+
location,
644+
format!(
645+
"Transmute is not supported in non-runtime phase {:?}.",
646+
self.mir_phase
647+
),
648+
);
649+
}
650+
}
624651
}
625652
}
626653
Rvalue::Repeat(_, _)

compiler/rustc_middle/src/mir/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1967,7 +1967,8 @@ impl<'tcx> Rvalue<'tcx> {
19671967
| CastKind::PtrToPtr
19681968
| CastKind::Pointer(_)
19691969
| CastKind::PointerFromExposedAddress
1970-
| CastKind::DynStar,
1970+
| CastKind::DynStar
1971+
| CastKind::Transmute,
19711972
_,
19721973
_,
19731974
)

compiler/rustc_middle/src/mir/syntax.rs

+7
Original file line numberDiff line numberDiff line change
@@ -1156,6 +1156,13 @@ pub enum CastKind {
11561156
IntToFloat,
11571157
PtrToPtr,
11581158
FnPtrToPtr,
1159+
/// Reinterpret the bits of the input as a different type.
1160+
///
1161+
/// MIR is well-formed if the input and output types have different sizes,
1162+
/// but running a transmute between differently-sized types is UB.
1163+
///
1164+
/// Allowed only in [`MirPhase::Runtime`]; Earlier it's a [`TerminatorKind::Call`].
1165+
Transmute,
11591166
}
11601167

11611168
#[derive(Clone, Debug, PartialEq, Eq, TyEncodable, TyDecodable, Hash, HashStable)]

compiler/rustc_mir_build/src/build/custom/parse/instruction.rs

+4
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> {
137137
fn parse_rvalue(&self, expr_id: ExprId) -> PResult<Rvalue<'tcx>> {
138138
parse_by_kind!(self, expr_id, expr, "rvalue",
139139
@call("mir_discriminant", args) => self.parse_place(args[0]).map(Rvalue::Discriminant),
140+
@call("mir_cast_transmute", args) => {
141+
let source = self.parse_operand(args[0])?;
142+
Ok(Rvalue::Cast(CastKind::Transmute, source, expr.ty))
143+
},
140144
@call("mir_checked", args) => {
141145
parse_by_kind!(self, args[0], _, "binary op",
142146
ExprKind::Binary { op, lhs, rhs } => Ok(Rvalue::CheckedBinaryOp(

compiler/rustc_mir_transform/src/const_prop.rs

+9
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,15 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
504504

505505
return None;
506506
}
507+
// Do not try creating references, nor any types with potentially-complex
508+
// invariants. This avoids an issue where checking validity would do a
509+
// bunch of work generating a nice message about the invariant violation,
510+
// only to not show it to anyone (since this isn't the lint).
511+
Rvalue::Cast(CastKind::Transmute, op, dst_ty) if !dst_ty.is_primitive() => {
512+
trace!("skipping Transmute of {:?} to {:?}", op, dst_ty);
513+
514+
return None;
515+
}
507516

508517
// There's no other checking to do at this time.
509518
Rvalue::Aggregate(..)

0 commit comments

Comments
 (0)