Skip to content

Commit

Permalink
Rollup merge of #108442 - scottmcm:mir-transmute, r=oli-obk
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
matthiaskrgr authored Mar 1, 2023
2 parents 75bd7cc + 6f49f85 commit b0e4d56
Show file tree
Hide file tree
Showing 27 changed files with 216 additions and 104 deletions.
7 changes: 7 additions & 0 deletions compiler/rustc_borrowck/src/type_check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2242,6 +2242,13 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
}
}
}
CastKind::Transmute => {
span_mirbug!(
self,
rvalue,
"Unexpected CastKind::Transmute, which is not permitted in Analysis MIR",
);
}
}
}

Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_codegen_cranelift/src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,10 @@ fn codegen_stmt<'tcx>(
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);
Expand Down
52 changes: 6 additions & 46 deletions compiler/rustc_codegen_ssa/src/mir/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use rustc_index::vec::Idx;
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};
Expand Down Expand Up @@ -765,6 +765,10 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
_ => None,
};

if let Some(intrinsic @ (sym::copy_nonoverlapping | sym::transmute)) = intrinsic {
bug!("Intrinsic {intrinsic} should have been lowered before codegen");
}

let extra_args = &args[sig.inputs().skip_binder().len()..];
let extra_args = bx.tcx().mk_type_list_from_iter(extra_args.iter().map(|op_arg| {
let op_ty = op_arg.ty(self.mir, bx.tcx());
Expand All @@ -776,23 +780,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
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,
Expand Down Expand Up @@ -835,7 +822,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {

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],
Expand Down Expand Up @@ -1746,33 +1732,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
}
}

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(
pub(crate) fn codegen_transmute_into(
&mut self,
bx: &mut Bx,
src: &mir::Operand<'tcx>,
Expand Down
10 changes: 10 additions & 0 deletions compiler/rustc_codegen_ssa/src/mir/rvalue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
dest.codegen_set_discr(bx, variant_index);
}

mir::Rvalue::Cast(mir::CastKind::Transmute, ref operand, _ty) => {
self.codegen_transmute_into(bx, operand, dest);
}

_ => {
assert!(self.rvalue_creates_operand(rvalue, DUMMY_SP));
let temp = self.codegen_rvalue_operand(bx, rvalue);
Expand Down Expand Up @@ -344,6 +348,9 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
};
OperandValue::Immediate(newval)
}
mir::CastKind::Transmute => {
bug!("Transmute operand {:?} in `codegen_rvalue_operand`", operand);
}
};
OperandRef { val, layout: cast }
}
Expand Down Expand Up @@ -684,6 +691,9 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
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, ..) =>
// sometimes this could, but for aggregates it can't
false,
mir::Rvalue::Ref(..) |
mir::Rvalue::CopyForDeref(..) |
mir::Rvalue::AddressOf(..) |
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_const_eval/src/interpret/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
bug!()
}
}

Transmute => {
self.copy_op(src, dest, /*allow_transmute*/ true)?;
}
}
Ok(())
}
Expand Down
8 changes: 4 additions & 4 deletions compiler/rustc_const_eval/src/interpret/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
let substs = instance.substs;
let intrinsic_name = self.tcx.item_name(instance.def_id());

if let sym::copy_nonoverlapping | sym::transmute = intrinsic_name {
bug!("Intrinsic {intrinsic_name} should have been lowered before interpreting MIR");
}

// 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),
Expand Down Expand Up @@ -411,9 +414,6 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
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 => {
Expand Down
37 changes: 37 additions & 0 deletions compiler/rustc_const_eval/src/transform/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,43 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
);
}
}
CastKind::Transmute => {
if let MirPhase::Runtime(..) = self.mir_phase {
// `mem::transmute` currently requires types concrete enough
// to *know* that their sizes are the same, and we repeat
// that check here. This restriction may be lifted in future,
// should some optimizations need to it be more general.
// (It might just end up being UB if they don't match, say.)

let src_layout = self.tcx.layout_of(self.param_env.and(op_ty));
let dst_layout = self.tcx.layout_of(self.param_env.and(*target_type));
if let Err(e) = src_layout {
self.fail(
location,
format!(
"Unable to compute layout for source type {op_ty:?}: {e}"
),
);
}
if let Err(e) = dst_layout {
self.fail(location, format!("Unable to compute layout for destination type {target_type:?}: {e}"));
}

if let (Ok(src_layout), Ok(dst_layout)) = (src_layout, dst_layout) {
if src_layout.layout.size() != dst_layout.layout.size() {
self.fail(location, format!("Source and destination layouts have different sizes: {src_layout:?} vs {dst_layout:?}"));
}
}
} else {
self.fail(
location,
format!(
"Transmute is not supported in non-runtime phase {:?}.",
self.mir_phase
),
);
}
}
}
}
Rvalue::Repeat(_, _)
Expand Down
3 changes: 2 additions & 1 deletion compiler/rustc_middle/src/mir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1963,7 +1963,8 @@ impl<'tcx> Rvalue<'tcx> {
| CastKind::PtrToPtr
| CastKind::Pointer(_)
| CastKind::PointerFromExposedAddress
| CastKind::DynStar,
| CastKind::DynStar
| CastKind::Transmute,
_,
_,
)
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_middle/src/mir/syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ pub enum MirPhase {
/// access to. This occurs in generator bodies. Such locals do not behave like other locals,
/// because they eg may be aliased in surprising ways. Runtime MIR has no such special locals -
/// all generator bodies are lowered and so all places that look like locals really are locals.
/// - Intrinsics: In analysis MIR, intrinsics are typically represented as [`TerminatorKind::Call`]s
/// to their `extern "rust-intrinsic"` declarations. In runtime MIR, some intrinsics are lowered
/// to MIR constructs not used earlier, such as `mem::transmute` → [`CastKind::Transmute`].
///
/// Also note that the lint pass which reports eg `200_u8 + 200_u8` as an error is run as a part
/// of analysis to runtime MIR lowering. To ensure lints are reported reliably, this means that
Expand Down Expand Up @@ -1189,6 +1192,10 @@ pub enum CastKind {
IntToFloat,
PtrToPtr,
FnPtrToPtr,
/// Reinterpret the bits of the input as a different type.
///
/// Allowed only in [`MirPhase::Runtime`]; Earlier it's a [`TerminatorKind::Call`].
Transmute,
}

#[derive(Clone, Debug, PartialEq, Eq, TyEncodable, TyDecodable, Hash, HashStable)]
Expand Down
6 changes: 6 additions & 0 deletions compiler/rustc_mir_transform/src/check_unsafety.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,12 @@ impl<'tcx> Visitor<'tcx> for UnsafetyChecker<'_, 'tcx> {
self.register_violations(violations, used_unsafe_blocks.iter().copied());
}
},
Rvalue::Cast(CastKind::Transmute, ..) => {
self.require_unsafe(
UnsafetyViolationKind::General,
UnsafetyViolationDetails::CallToUnsafeFunction,
);
}
_ => {}
}
self.super_rvalue(rvalue, location);
Expand Down
25 changes: 25 additions & 0 deletions compiler/rustc_mir_transform/src/lower_intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
use crate::MirPass;
use rustc_middle::mir::*;
use rustc_middle::ty::inhabitedness::inhabited_predicate::InhabitedPredicate;
use rustc_middle::ty::subst::SubstsRef;
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_span::symbol::{sym, Symbol};
Expand Down Expand Up @@ -162,6 +163,30 @@ impl<'tcx> MirPass<'tcx> for LowerIntrinsics {
terminator.kind = TerminatorKind::Goto { target };
}
}
sym::transmute => {
let dst_ty = destination.ty(local_decls, tcx).ty;
if let Some(target) = *target {
let mut args = args.drain(..);
block.statements.push(Statement {
source_info: terminator.source_info,
kind: StatementKind::Assign(Box::new((
*destination,
Rvalue::Cast(CastKind::Transmute, args.next().unwrap(), dst_ty),
))),
});
assert_eq!(args.next(), None, "Extra argument for transmute intrinsic");
drop(args);
terminator.kind = TerminatorKind::Goto { target };
} else {
debug_assert!(!matches!(
dst_ty.inhabited_predicate(tcx),
InhabitedPredicate::True
));
// `transmute::<_, !>(x)` is UB for anything inhabited,
// and must be unreachable if `x` is uninhabited.
terminator.kind = TerminatorKind::Unreachable;
}
}
_ if intrinsic_name.as_str().starts_with("simd_shuffle") => {
validate_simd_shuffle(tcx, args, terminator.source_info.span);
}
Expand Down
3 changes: 3 additions & 0 deletions src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
Expand Down
2 changes: 1 addition & 1 deletion src/tools/miri/tests/fail/never_transmute_humans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ struct Human;

fn main() {
let _x: ! = unsafe {
std::mem::transmute::<Human, !>(Human) //~ ERROR: transmuting to uninhabited
std::mem::transmute::<Human, !>(Human) //~ ERROR: entering unreachable code
};
}
4 changes: 2 additions & 2 deletions src/tools/miri/tests/fail/never_transmute_humans.stderr
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Loading

0 comments on commit b0e4d56

Please sign in to comment.