//! Lowers intrinsic calls use crate::{errors, MirPass}; use rustc_middle::mir::*; use rustc_middle::ty::subst::SubstsRef; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_span::symbol::{sym, Symbol}; use rustc_span::Span; use rustc_target::abi::{FieldIdx, VariantIdx}; pub struct LowerIntrinsics; impl<'tcx> MirPass<'tcx> for LowerIntrinsics { fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { let local_decls = &body.local_decls; for block in body.basic_blocks.as_mut() { let terminator = block.terminator.as_mut().unwrap(); if let TerminatorKind::Call { func, args, destination, target, .. } = &mut terminator.kind { let func_ty = func.ty(local_decls, tcx); let Some((intrinsic_name, substs)) = resolve_rust_intrinsic(tcx, func_ty) else { continue; }; match intrinsic_name { sym::unreachable => { terminator.kind = TerminatorKind::Unreachable; } sym::forget => { if let Some(target) = *target { block.statements.push(Statement { source_info: terminator.source_info, kind: StatementKind::Assign(Box::new(( *destination, Rvalue::Use(Operand::Constant(Box::new(Constant { span: terminator.source_info.span, user_ty: None, literal: ConstantKind::zero_sized(tcx.types.unit), }))), ))), }); terminator.kind = TerminatorKind::Goto { target }; } } sym::copy_nonoverlapping => { let target = target.unwrap(); let mut args = args.drain(..); block.statements.push(Statement { source_info: terminator.source_info, kind: StatementKind::Intrinsic(Box::new( NonDivergingIntrinsic::CopyNonOverlapping( rustc_middle::mir::CopyNonOverlapping { src: args.next().unwrap(), dst: args.next().unwrap(), count: args.next().unwrap(), }, ), )), }); assert_eq!( args.next(), None, "Extra argument for copy_non_overlapping intrinsic" ); drop(args); terminator.kind = TerminatorKind::Goto { target }; } sym::assume => { let target = target.unwrap(); let mut args = args.drain(..); block.statements.push(Statement { source_info: terminator.source_info, kind: StatementKind::Intrinsic(Box::new( NonDivergingIntrinsic::Assume(args.next().unwrap()), )), }); assert_eq!( args.next(), None, "Extra argument for copy_non_overlapping intrinsic" ); drop(args); terminator.kind = TerminatorKind::Goto { target }; } sym::wrapping_add | sym::wrapping_sub | sym::wrapping_mul => { if let Some(target) = *target { let lhs; let rhs; { let mut args = args.drain(..); lhs = args.next().unwrap(); rhs = args.next().unwrap(); } let bin_op = match intrinsic_name { sym::wrapping_add => BinOp::Add, sym::wrapping_sub => BinOp::Sub, sym::wrapping_mul => BinOp::Mul, _ => bug!("unexpected intrinsic"), }; block.statements.push(Statement { source_info: terminator.source_info, kind: StatementKind::Assign(Box::new(( *destination, Rvalue::BinaryOp(bin_op, Box::new((lhs, rhs))), ))), }); terminator.kind = TerminatorKind::Goto { target }; } } sym::add_with_overflow | sym::sub_with_overflow | sym::mul_with_overflow => { if let Some(target) = *target { let lhs; let rhs; { let mut args = args.drain(..); lhs = args.next().unwrap(); rhs = args.next().unwrap(); } let bin_op = match intrinsic_name { sym::add_with_overflow => BinOp::Add, sym::sub_with_overflow => BinOp::Sub, sym::mul_with_overflow => BinOp::Mul, _ => bug!("unexpected intrinsic"), }; block.statements.push(Statement { source_info: terminator.source_info, kind: StatementKind::Assign(Box::new(( *destination, Rvalue::CheckedBinaryOp(bin_op, Box::new((lhs, rhs))), ))), }); terminator.kind = TerminatorKind::Goto { target }; } } sym::size_of | sym::min_align_of => { if let Some(target) = *target { let tp_ty = substs.type_at(0); let null_op = match intrinsic_name { sym::size_of => NullOp::SizeOf, sym::min_align_of => NullOp::AlignOf, _ => bug!("unexpected intrinsic"), }; block.statements.push(Statement { source_info: terminator.source_info, kind: StatementKind::Assign(Box::new(( *destination, Rvalue::NullaryOp(null_op, tp_ty), ))), }); terminator.kind = TerminatorKind::Goto { target }; } } sym::read_via_copy => { let [arg] = args.as_slice() else { span_bug!(terminator.source_info.span, "Wrong number of arguments"); }; let derefed_place = if let Some(place) = arg.place() && let Some(local) = place.as_local() { tcx.mk_place_deref(local.into()) } else { span_bug!(terminator.source_info.span, "Only passing a local is supported"); }; terminator.kind = match *target { None => { // No target means this read something uninhabited, // so it must be unreachable, and we don't need to // preserve the assignment either. TerminatorKind::Unreachable } Some(target) => { block.statements.push(Statement { source_info: terminator.source_info, kind: StatementKind::Assign(Box::new(( *destination, Rvalue::Use(Operand::Copy(derefed_place)), ))), }); TerminatorKind::Goto { target } } } } sym::write_via_move => { let target = target.unwrap(); let Ok([ptr, val]) = <[_; 2]>::try_from(std::mem::take(args)) else { span_bug!( terminator.source_info.span, "Wrong number of arguments for write_via_move intrinsic", ); }; let derefed_place = if let Some(place) = ptr.place() && let Some(local) = place.as_local() { tcx.mk_place_deref(local.into()) } else { span_bug!(terminator.source_info.span, "Only passing a local is supported"); }; block.statements.push(Statement { source_info: terminator.source_info, kind: StatementKind::Assign(Box::new(( derefed_place, Rvalue::Use(val), ))), }); terminator.kind = TerminatorKind::Goto { target }; } sym::discriminant_value => { if let (Some(target), Some(arg)) = (*target, args[0].place()) { let arg = tcx.mk_place_deref(arg); block.statements.push(Statement { source_info: terminator.source_info, kind: StatementKind::Assign(Box::new(( *destination, Rvalue::Discriminant(arg), ))), }); terminator.kind = TerminatorKind::Goto { target }; } } sym::offset => { let target = target.unwrap(); let Ok([ptr, delta]) = <[_; 2]>::try_from(std::mem::take(args)) else { span_bug!( terminator.source_info.span, "Wrong number of arguments for offset intrinsic", ); }; block.statements.push(Statement { source_info: terminator.source_info, kind: StatementKind::Assign(Box::new(( *destination, Rvalue::BinaryOp(BinOp::Offset, Box::new((ptr, delta))), ))), }); terminator.kind = TerminatorKind::Goto { target }; } sym::option_payload_ptr => { if let (Some(target), Some(arg)) = (*target, args[0].place()) { let ty::RawPtr(ty::TypeAndMut { ty: dest_ty, .. }) = destination.ty(local_decls, tcx).ty.kind() else { bug!(); }; block.statements.push(Statement { source_info: terminator.source_info, kind: StatementKind::Assign(Box::new(( *destination, Rvalue::AddressOf( Mutability::Not, arg.project_deeper( &[ PlaceElem::Deref, PlaceElem::Downcast( Some(sym::Some), VariantIdx::from_u32(1), ), PlaceElem::Field(FieldIdx::from_u32(0), *dest_ty), ], tcx, ), ), ))), }); terminator.kind = TerminatorKind::Goto { target }; } } sym::transmute | sym::transmute_unchecked => { 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); } _ => {} } } } } } fn resolve_rust_intrinsic<'tcx>( tcx: TyCtxt<'tcx>, func_ty: Ty<'tcx>, ) -> Option<(Symbol, SubstsRef<'tcx>)> { if let ty::FnDef(def_id, substs) = *func_ty.kind() { if tcx.is_intrinsic(def_id) { return Some((tcx.item_name(def_id), substs)); } } None } fn validate_simd_shuffle<'tcx>(tcx: TyCtxt<'tcx>, args: &[Operand<'tcx>], span: Span) { if !matches!(args[2], Operand::Constant(_)) { tcx.sess.emit_err(errors::SimdShuffleLastConst { span }); } }