blob: 9a94cae33828810d2174f9872dfd8ff48c074401 [file] [log] [blame]
//! Removes operations on ZST places, and convert ZST operands to constants.
use rustc_middle::mir::visit::*;
use rustc_middle::mir::*;
use rustc_middle::ty::{self, Ty, TyCtxt};
pub struct RemoveZsts;
impl<'tcx> MirPass<'tcx> for RemoveZsts {
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
sess.mir_opt_level() > 0
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
// Avoid query cycles (coroutines require optimized MIR for layout).
if tcx.type_of(body.source.def_id()).instantiate_identity().is_coroutine() {
return;
}
if !tcx.consider_optimizing(|| format!("RemoveZsts - {:?}", body.source.def_id())) {
return;
}
let param_env = tcx.param_env_reveal_all_normalized(body.source.def_id());
let local_decls = &body.local_decls;
let mut replacer = Replacer { tcx, param_env, local_decls };
for var_debug_info in &mut body.var_debug_info {
replacer.visit_var_debug_info(var_debug_info);
}
for (bb, data) in body.basic_blocks.as_mut_preserves_cfg().iter_enumerated_mut() {
replacer.visit_basic_block_data(bb, data);
}
}
}
struct Replacer<'a, 'tcx> {
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
local_decls: &'a LocalDecls<'tcx>,
}
/// A cheap, approximate check to avoid unnecessary `layout_of` calls.
fn maybe_zst(ty: Ty<'_>) -> bool {
match ty.kind() {
// maybe ZST (could be more precise)
ty::Adt(..)
| ty::Array(..)
| ty::Closure(..)
| ty::CoroutineClosure(..)
| ty::Tuple(..)
| ty::Alias(ty::Opaque, ..) => true,
// definitely ZST
ty::FnDef(..) | ty::Never => true,
// unreachable or can't be ZST
_ => false,
}
}
impl<'tcx> Replacer<'_, 'tcx> {
fn known_to_be_zst(&self, ty: Ty<'tcx>) -> bool {
if !maybe_zst(ty) {
return false;
}
let Ok(layout) = self.tcx.layout_of(self.param_env.and(ty)) else {
return false;
};
layout.is_zst()
}
fn make_zst(&self, ty: Ty<'tcx>) -> ConstOperand<'tcx> {
debug_assert!(self.known_to_be_zst(ty));
ConstOperand {
span: rustc_span::DUMMY_SP,
user_ty: None,
const_: Const::Val(ConstValue::ZeroSized, ty),
}
}
}
impl<'tcx> MutVisitor<'tcx> for Replacer<'_, 'tcx> {
fn tcx(&self) -> TyCtxt<'tcx> {
self.tcx
}
fn visit_var_debug_info(&mut self, var_debug_info: &mut VarDebugInfo<'tcx>) {
match var_debug_info.value {
VarDebugInfoContents::Const(_) => {}
VarDebugInfoContents::Place(place) => {
let place_ty = place.ty(self.local_decls, self.tcx).ty;
if self.known_to_be_zst(place_ty) {
var_debug_info.value = VarDebugInfoContents::Const(self.make_zst(place_ty))
}
}
}
}
fn visit_operand(&mut self, operand: &mut Operand<'tcx>, loc: Location) {
if let Operand::Constant(_) = operand {
return;
}
let op_ty = operand.ty(self.local_decls, self.tcx);
if self.known_to_be_zst(op_ty)
&& self.tcx.consider_optimizing(|| {
format!("RemoveZsts - Operand: {operand:?} Location: {loc:?}")
})
{
*operand = Operand::Constant(Box::new(self.make_zst(op_ty)))
}
}
fn visit_statement(&mut self, statement: &mut Statement<'tcx>, loc: Location) {
let place_for_ty = match statement.kind {
StatementKind::Assign(box (place, ref rvalue)) => {
rvalue.is_safe_to_remove().then_some(place)
}
StatementKind::Deinit(box place)
| StatementKind::SetDiscriminant { box place, variant_index: _ }
| StatementKind::AscribeUserType(box (place, _), _)
| StatementKind::Retag(_, box place)
| StatementKind::PlaceMention(box place)
| StatementKind::FakeRead(box (_, place)) => Some(place),
StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => {
Some(local.into())
}
StatementKind::Coverage(_)
| StatementKind::Intrinsic(_)
| StatementKind::Nop
| StatementKind::ConstEvalCounter => None,
};
if let Some(place_for_ty) = place_for_ty
&& let ty = place_for_ty.ty(self.local_decls, self.tcx).ty
&& self.known_to_be_zst(ty)
{
statement.make_nop();
} else {
self.super_statement(statement, loc);
}
}
}