diff --git a/compiler/rustc_middle/src/mir/mod.rs b/compiler/rustc_middle/src/mir/mod.rs index be4eaac79ceb..77c2aa4d57eb 100644 --- a/compiler/rustc_middle/src/mir/mod.rs +++ b/compiler/rustc_middle/src/mir/mod.rs @@ -37,7 +37,7 @@ use either::Either; use std::borrow::Cow; use std::cell::RefCell; use std::collections::hash_map::Entry; -use std::fmt::{self, Debug, Formatter, Write}; +use std::fmt::{self, Debug, Formatter}; use std::ops::{Index, IndexMut}; use std::{iter, mem}; @@ -56,6 +56,7 @@ pub mod patch; pub mod pretty; mod query; pub mod spanview; +mod statement; mod syntax; pub mod tcx; mod terminator; @@ -71,6 +72,7 @@ pub use self::pretty::{ }; pub use consts::*; use pretty::pretty_print_const_value; +pub use statement::*; pub use syntax::*; pub use terminator::*; @@ -1122,20 +1124,6 @@ pub struct VarDebugInfo<'tcx> { pub argument_index: Option, } -impl Debug for VarDebugInfo<'_> { - fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { - if let Some(box VarDebugInfoFragment { ty, ref projection }) = self.composite { - pre_fmt_projection(&projection[..], fmt)?; - write!(fmt, "({}: {})", self.name, ty)?; - post_fmt_projection(&projection[..], fmt)?; - } else { - write!(fmt, "{}", self.name)?; - } - - write!(fmt, " => {:?}", self.value) - } -} - /////////////////////////////////////////////////////////////////////////// // BasicBlock @@ -1292,576 +1280,6 @@ impl<'tcx> BasicBlockData<'tcx> { } } -impl AssertKind { - /// Returns true if this an overflow checking assertion controlled by -C overflow-checks. - pub fn is_optional_overflow_check(&self) -> bool { - use AssertKind::*; - use BinOp::*; - matches!(self, OverflowNeg(..) | Overflow(Add | Sub | Mul | Shl | Shr, ..)) - } - - /// Get the message that is printed at runtime when this assertion fails. - /// - /// The caller is expected to handle `BoundsCheck` and `MisalignedPointerDereference` by - /// invoking the appropriate lang item (panic_bounds_check/panic_misaligned_pointer_dereference) - /// instead of printing a static message. - pub fn description(&self) -> &'static str { - use AssertKind::*; - match self { - Overflow(BinOp::Add, _, _) => "attempt to add with overflow", - Overflow(BinOp::Sub, _, _) => "attempt to subtract with overflow", - Overflow(BinOp::Mul, _, _) => "attempt to multiply with overflow", - Overflow(BinOp::Div, _, _) => "attempt to divide with overflow", - Overflow(BinOp::Rem, _, _) => "attempt to calculate the remainder with overflow", - OverflowNeg(_) => "attempt to negate with overflow", - Overflow(BinOp::Shr, _, _) => "attempt to shift right with overflow", - Overflow(BinOp::Shl, _, _) => "attempt to shift left with overflow", - Overflow(op, _, _) => bug!("{:?} cannot overflow", op), - DivisionByZero(_) => "attempt to divide by zero", - RemainderByZero(_) => "attempt to calculate the remainder with a divisor of zero", - ResumedAfterReturn(GeneratorKind::Gen) => "generator resumed after completion", - ResumedAfterReturn(GeneratorKind::Async(_)) => "`async fn` resumed after completion", - ResumedAfterPanic(GeneratorKind::Gen) => "generator resumed after panicking", - ResumedAfterPanic(GeneratorKind::Async(_)) => "`async fn` resumed after panicking", - BoundsCheck { .. } | MisalignedPointerDereference { .. } => { - bug!("Unexpected AssertKind") - } - } - } - - /// Format the message arguments for the `assert(cond, msg..)` terminator in MIR printing. - /// - /// Needs to be kept in sync with the run-time behavior (which is defined by - /// `AssertKind::description` and the lang items mentioned in its docs). - /// Note that we deliberately show more details here than we do at runtime, such as the actual - /// numbers that overflowed -- it is much easier to do so here than at runtime. - pub fn fmt_assert_args(&self, f: &mut W) -> fmt::Result - where - O: Debug, - { - use AssertKind::*; - match self { - BoundsCheck { ref len, ref index } => write!( - f, - "\"index out of bounds: the length is {{}} but the index is {{}}\", {len:?}, {index:?}" - ), - - OverflowNeg(op) => { - write!(f, "\"attempt to negate `{{}}`, which would overflow\", {op:?}") - } - DivisionByZero(op) => write!(f, "\"attempt to divide `{{}}` by zero\", {op:?}"), - RemainderByZero(op) => write!( - f, - "\"attempt to calculate the remainder of `{{}}` with a divisor of zero\", {op:?}" - ), - Overflow(BinOp::Add, l, r) => write!( - f, - "\"attempt to compute `{{}} + {{}}`, which would overflow\", {l:?}, {r:?}" - ), - Overflow(BinOp::Sub, l, r) => write!( - f, - "\"attempt to compute `{{}} - {{}}`, which would overflow\", {l:?}, {r:?}" - ), - Overflow(BinOp::Mul, l, r) => write!( - f, - "\"attempt to compute `{{}} * {{}}`, which would overflow\", {l:?}, {r:?}" - ), - Overflow(BinOp::Div, l, r) => write!( - f, - "\"attempt to compute `{{}} / {{}}`, which would overflow\", {l:?}, {r:?}" - ), - Overflow(BinOp::Rem, l, r) => write!( - f, - "\"attempt to compute the remainder of `{{}} % {{}}`, which would overflow\", {l:?}, {r:?}" - ), - Overflow(BinOp::Shr, _, r) => { - write!(f, "\"attempt to shift right by `{{}}`, which would overflow\", {r:?}") - } - Overflow(BinOp::Shl, _, r) => { - write!(f, "\"attempt to shift left by `{{}}`, which would overflow\", {r:?}") - } - MisalignedPointerDereference { required, found } => { - write!( - f, - "\"misaligned pointer dereference: address must be a multiple of {{}} but is {{}}\", {required:?}, {found:?}" - ) - } - _ => write!(f, "\"{}\"", self.description()), - } - } - - /// Format the diagnostic message for use in a lint (e.g. when the assertion fails during const-eval). - /// - /// Needs to be kept in sync with the run-time behavior (which is defined by - /// `AssertKind::description` and the lang items mentioned in its docs). - /// Note that we deliberately show more details here than we do at runtime, such as the actual - /// numbers that overflowed -- it is much easier to do so here than at runtime. - pub fn diagnostic_message(&self) -> DiagnosticMessage { - use crate::fluent_generated::*; - use AssertKind::*; - - match self { - BoundsCheck { .. } => middle_bounds_check, - Overflow(BinOp::Shl, _, _) => middle_assert_shl_overflow, - Overflow(BinOp::Shr, _, _) => middle_assert_shr_overflow, - Overflow(_, _, _) => middle_assert_op_overflow, - OverflowNeg(_) => middle_assert_overflow_neg, - DivisionByZero(_) => middle_assert_divide_by_zero, - RemainderByZero(_) => middle_assert_remainder_by_zero, - ResumedAfterReturn(GeneratorKind::Async(_)) => middle_assert_async_resume_after_return, - ResumedAfterReturn(GeneratorKind::Gen) => middle_assert_generator_resume_after_return, - ResumedAfterPanic(GeneratorKind::Async(_)) => middle_assert_async_resume_after_panic, - ResumedAfterPanic(GeneratorKind::Gen) => middle_assert_generator_resume_after_panic, - - MisalignedPointerDereference { .. } => middle_assert_misaligned_ptr_deref, - } - } - - pub fn add_args(self, adder: &mut dyn FnMut(Cow<'static, str>, DiagnosticArgValue<'static>)) - where - O: fmt::Debug, - { - use AssertKind::*; - - macro_rules! add { - ($name: expr, $value: expr) => { - adder($name.into(), $value.into_diagnostic_arg()); - }; - } - - match self { - BoundsCheck { len, index } => { - add!("len", format!("{len:?}")); - add!("index", format!("{index:?}")); - } - Overflow(BinOp::Shl | BinOp::Shr, _, val) - | DivisionByZero(val) - | RemainderByZero(val) - | OverflowNeg(val) => { - add!("val", format!("{val:#?}")); - } - Overflow(binop, left, right) => { - add!("op", binop.to_hir_binop().as_str()); - add!("left", format!("{left:#?}")); - add!("right", format!("{right:#?}")); - } - ResumedAfterReturn(_) | ResumedAfterPanic(_) => {} - MisalignedPointerDereference { required, found } => { - add!("required", format!("{required:#?}")); - add!("found", format!("{found:#?}")); - } - } - } -} - -/////////////////////////////////////////////////////////////////////////// -// Statements - -/// A statement in a basic block, including information about its source code. -#[derive(Clone, TyEncodable, TyDecodable, HashStable, TypeFoldable, TypeVisitable)] -pub struct Statement<'tcx> { - pub source_info: SourceInfo, - pub kind: StatementKind<'tcx>, -} - -impl Statement<'_> { - /// Changes a statement to a nop. This is both faster than deleting instructions and avoids - /// invalidating statement indices in `Location`s. - pub fn make_nop(&mut self) { - self.kind = StatementKind::Nop - } - - /// Changes a statement to a nop and returns the original statement. - #[must_use = "If you don't need the statement, use `make_nop` instead"] - pub fn replace_nop(&mut self) -> Self { - Statement { - source_info: self.source_info, - kind: mem::replace(&mut self.kind, StatementKind::Nop), - } - } -} - -impl Debug for Statement<'_> { - fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { - use self::StatementKind::*; - match self.kind { - Assign(box (ref place, ref rv)) => write!(fmt, "{place:?} = {rv:?}"), - FakeRead(box (ref cause, ref place)) => { - write!(fmt, "FakeRead({cause:?}, {place:?})") - } - Retag(ref kind, ref place) => write!( - fmt, - "Retag({}{:?})", - match kind { - RetagKind::FnEntry => "[fn entry] ", - RetagKind::TwoPhase => "[2phase] ", - RetagKind::Raw => "[raw] ", - RetagKind::Default => "", - }, - place, - ), - StorageLive(ref place) => write!(fmt, "StorageLive({place:?})"), - StorageDead(ref place) => write!(fmt, "StorageDead({place:?})"), - SetDiscriminant { ref place, variant_index } => { - write!(fmt, "discriminant({place:?}) = {variant_index:?}") - } - Deinit(ref place) => write!(fmt, "Deinit({place:?})"), - PlaceMention(ref place) => { - write!(fmt, "PlaceMention({place:?})") - } - AscribeUserType(box (ref place, ref c_ty), ref variance) => { - write!(fmt, "AscribeUserType({place:?}, {variance:?}, {c_ty:?})") - } - Coverage(box self::Coverage { ref kind, code_region: Some(ref rgn) }) => { - write!(fmt, "Coverage::{kind:?} for {rgn:?}") - } - Coverage(box ref coverage) => write!(fmt, "Coverage::{:?}", coverage.kind), - Intrinsic(box ref intrinsic) => write!(fmt, "{intrinsic}"), - ConstEvalCounter => write!(fmt, "ConstEvalCounter"), - Nop => write!(fmt, "nop"), - } - } -} - -impl<'tcx> StatementKind<'tcx> { - pub fn as_assign_mut(&mut self) -> Option<&mut (Place<'tcx>, Rvalue<'tcx>)> { - match self { - StatementKind::Assign(x) => Some(x), - _ => None, - } - } - - pub fn as_assign(&self) -> Option<&(Place<'tcx>, Rvalue<'tcx>)> { - match self { - StatementKind::Assign(x) => Some(x), - _ => None, - } - } -} - -/////////////////////////////////////////////////////////////////////////// -// Places - -impl ProjectionElem { - /// Returns `true` if the target of this projection may refer to a different region of memory - /// than the base. - fn is_indirect(&self) -> bool { - match self { - Self::Deref => true, - - Self::Field(_, _) - | Self::Index(_) - | Self::OpaqueCast(_) - | Self::ConstantIndex { .. } - | Self::Subslice { .. } - | Self::Downcast(_, _) => false, - } - } - - /// Returns `true` if the target of this projection always refers to the same memory region - /// whatever the state of the program. - pub fn is_stable_offset(&self) -> bool { - match self { - Self::Deref | Self::Index(_) => false, - Self::Field(_, _) - | Self::OpaqueCast(_) - | Self::ConstantIndex { .. } - | Self::Subslice { .. } - | Self::Downcast(_, _) => true, - } - } - - /// Returns `true` if this is a `Downcast` projection with the given `VariantIdx`. - pub fn is_downcast_to(&self, v: VariantIdx) -> bool { - matches!(*self, Self::Downcast(_, x) if x == v) - } - - /// Returns `true` if this is a `Field` projection with the given index. - pub fn is_field_to(&self, f: FieldIdx) -> bool { - matches!(*self, Self::Field(x, _) if x == f) - } - - /// Returns `true` if this is accepted inside `VarDebugInfoContents::Place`. - pub fn can_use_in_debuginfo(&self) -> bool { - match self { - Self::ConstantIndex { from_end: false, .. } - | Self::Deref - | Self::Downcast(_, _) - | Self::Field(_, _) => true, - Self::ConstantIndex { from_end: true, .. } - | Self::Index(_) - | Self::OpaqueCast(_) - | Self::Subslice { .. } => false, - } - } -} - -/// Alias for projections as they appear in `UserTypeProjection`, where we -/// need neither the `V` parameter for `Index` nor the `T` for `Field`. -pub type ProjectionKind = ProjectionElem<(), ()>; - -#[derive(Clone, Copy, PartialEq, Eq, Hash)] -pub struct PlaceRef<'tcx> { - pub local: Local, - pub projection: &'tcx [PlaceElem<'tcx>], -} - -// Once we stop implementing `Ord` for `DefId`, -// this impl will be unnecessary. Until then, we'll -// leave this impl in place to prevent re-adding a -// dependency on the `Ord` impl for `DefId` -impl<'tcx> !PartialOrd for PlaceRef<'tcx> {} - -impl<'tcx> Place<'tcx> { - // FIXME change this to a const fn by also making List::empty a const fn. - pub fn return_place() -> Place<'tcx> { - Place { local: RETURN_PLACE, projection: List::empty() } - } - - /// Returns `true` if this `Place` contains a `Deref` projection. - /// - /// If `Place::is_indirect` returns false, the caller knows that the `Place` refers to the - /// same region of memory as its base. - pub fn is_indirect(&self) -> bool { - self.projection.iter().any(|elem| elem.is_indirect()) - } - - /// Returns `true` if this `Place`'s first projection is `Deref`. - /// - /// This is useful because for MIR phases `AnalysisPhase::PostCleanup` and later, - /// `Deref` projections can only occur as the first projection. In that case this method - /// is equivalent to `is_indirect`, but faster. - pub fn is_indirect_first_projection(&self) -> bool { - self.as_ref().is_indirect_first_projection() - } - - /// Finds the innermost `Local` from this `Place`, *if* it is either a local itself or - /// a single deref of a local. - #[inline(always)] - pub fn local_or_deref_local(&self) -> Option { - self.as_ref().local_or_deref_local() - } - - /// If this place represents a local variable like `_X` with no - /// projections, return `Some(_X)`. - #[inline(always)] - pub fn as_local(&self) -> Option { - self.as_ref().as_local() - } - - #[inline] - pub fn as_ref(&self) -> PlaceRef<'tcx> { - PlaceRef { local: self.local, projection: &self.projection } - } - - /// Iterate over the projections in evaluation order, i.e., the first element is the base with - /// its projection and then subsequently more projections are added. - /// As a concrete example, given the place a.b.c, this would yield: - /// - (a, .b) - /// - (a.b, .c) - /// - /// Given a place without projections, the iterator is empty. - #[inline] - pub fn iter_projections( - self, - ) -> impl Iterator, PlaceElem<'tcx>)> + DoubleEndedIterator { - self.as_ref().iter_projections() - } - - /// Generates a new place by appending `more_projections` to the existing ones - /// and interning the result. - pub fn project_deeper(self, more_projections: &[PlaceElem<'tcx>], tcx: TyCtxt<'tcx>) -> Self { - if more_projections.is_empty() { - return self; - } - - self.as_ref().project_deeper(more_projections, tcx) - } -} - -impl From for Place<'_> { - #[inline] - fn from(local: Local) -> Self { - Place { local, projection: List::empty() } - } -} - -impl<'tcx> PlaceRef<'tcx> { - /// Finds the innermost `Local` from this `Place`, *if* it is either a local itself or - /// a single deref of a local. - pub fn local_or_deref_local(&self) -> Option { - match *self { - PlaceRef { local, projection: [] } - | PlaceRef { local, projection: [ProjectionElem::Deref] } => Some(local), - _ => None, - } - } - - /// Returns `true` if this `Place` contains a `Deref` projection. - /// - /// If `Place::is_indirect` returns false, the caller knows that the `Place` refers to the - /// same region of memory as its base. - pub fn is_indirect(&self) -> bool { - self.projection.iter().any(|elem| elem.is_indirect()) - } - - /// Returns `true` if this `Place`'s first projection is `Deref`. - /// - /// This is useful because for MIR phases `AnalysisPhase::PostCleanup` and later, - /// `Deref` projections can only occur as the first projection. In that case this method - /// is equivalent to `is_indirect`, but faster. - pub fn is_indirect_first_projection(&self) -> bool { - // To make sure this is not accidentally used in wrong mir phase - debug_assert!( - self.projection.is_empty() || !self.projection[1..].contains(&PlaceElem::Deref) - ); - self.projection.first() == Some(&PlaceElem::Deref) - } - - /// If this place represents a local variable like `_X` with no - /// projections, return `Some(_X)`. - #[inline] - pub fn as_local(&self) -> Option { - match *self { - PlaceRef { local, projection: [] } => Some(local), - _ => None, - } - } - - #[inline] - pub fn last_projection(&self) -> Option<(PlaceRef<'tcx>, PlaceElem<'tcx>)> { - if let &[ref proj_base @ .., elem] = self.projection { - Some((PlaceRef { local: self.local, projection: proj_base }, elem)) - } else { - None - } - } - - /// Iterate over the projections in evaluation order, i.e., the first element is the base with - /// its projection and then subsequently more projections are added. - /// As a concrete example, given the place a.b.c, this would yield: - /// - (a, .b) - /// - (a.b, .c) - /// - /// Given a place without projections, the iterator is empty. - #[inline] - pub fn iter_projections( - self, - ) -> impl Iterator, PlaceElem<'tcx>)> + DoubleEndedIterator { - self.projection.iter().enumerate().map(move |(i, proj)| { - let base = PlaceRef { local: self.local, projection: &self.projection[..i] }; - (base, *proj) - }) - } - - /// Generates a new place by appending `more_projections` to the existing ones - /// and interning the result. - pub fn project_deeper( - self, - more_projections: &[PlaceElem<'tcx>], - tcx: TyCtxt<'tcx>, - ) -> Place<'tcx> { - let mut v: Vec>; - - let new_projections = if self.projection.is_empty() { - more_projections - } else { - v = Vec::with_capacity(self.projection.len() + more_projections.len()); - v.extend(self.projection); - v.extend(more_projections); - &v - }; - - Place { local: self.local, projection: tcx.mk_place_elems(new_projections) } - } -} - -impl From for PlaceRef<'_> { - #[inline] - fn from(local: Local) -> Self { - PlaceRef { local, projection: &[] } - } -} - -impl Debug for Place<'_> { - fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { - self.as_ref().fmt(fmt) - } -} - -impl Debug for PlaceRef<'_> { - fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { - pre_fmt_projection(self.projection, fmt)?; - write!(fmt, "{:?}", self.local)?; - post_fmt_projection(self.projection, fmt) - } -} - -fn pre_fmt_projection(projection: &[PlaceElem<'_>], fmt: &mut Formatter<'_>) -> fmt::Result { - for &elem in projection.iter().rev() { - match elem { - ProjectionElem::OpaqueCast(_) - | ProjectionElem::Downcast(_, _) - | ProjectionElem::Field(_, _) => { - write!(fmt, "(").unwrap(); - } - ProjectionElem::Deref => { - write!(fmt, "(*").unwrap(); - } - ProjectionElem::Index(_) - | ProjectionElem::ConstantIndex { .. } - | ProjectionElem::Subslice { .. } => {} - } - } - - Ok(()) -} - -fn post_fmt_projection(projection: &[PlaceElem<'_>], fmt: &mut Formatter<'_>) -> fmt::Result { - for &elem in projection.iter() { - match elem { - ProjectionElem::OpaqueCast(ty) => { - write!(fmt, " as {ty})")?; - } - ProjectionElem::Downcast(Some(name), _index) => { - write!(fmt, " as {name})")?; - } - ProjectionElem::Downcast(None, index) => { - write!(fmt, " as variant#{index:?})")?; - } - ProjectionElem::Deref => { - write!(fmt, ")")?; - } - ProjectionElem::Field(field, ty) => { - with_no_trimmed_paths!(write!(fmt, ".{:?}: {})", field.index(), ty)?); - } - ProjectionElem::Index(ref index) => { - write!(fmt, "[{index:?}]")?; - } - ProjectionElem::ConstantIndex { offset, min_length, from_end: false } => { - write!(fmt, "[{offset:?} of {min_length:?}]")?; - } - ProjectionElem::ConstantIndex { offset, min_length, from_end: true } => { - write!(fmt, "[-{offset:?} of {min_length:?}]")?; - } - ProjectionElem::Subslice { from, to, from_end: true } if to == 0 => { - write!(fmt, "[{from:?}:]")?; - } - ProjectionElem::Subslice { from, to, from_end: true } if from == 0 => { - write!(fmt, "[:-{to:?}]")?; - } - ProjectionElem::Subslice { from, to, from_end: true } => { - write!(fmt, "[{from:?}:-{to:?}]")?; - } - ProjectionElem::Subslice { from, to, from_end: false } => { - write!(fmt, "[{from:?}..{to:?}]")?; - } - } - } - - Ok(()) -} - /////////////////////////////////////////////////////////////////////////// // Scopes @@ -1940,345 +1358,12 @@ pub struct SourceScopeLocalData { pub safety: Safety, } -/////////////////////////////////////////////////////////////////////////// -// Operands - -impl<'tcx> Debug for Operand<'tcx> { - fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { - use self::Operand::*; - match *self { - Constant(ref a) => write!(fmt, "{a:?}"), - Copy(ref place) => write!(fmt, "{place:?}"), - Move(ref place) => write!(fmt, "move {place:?}"), - } - } -} - -impl<'tcx> Operand<'tcx> { - /// Convenience helper to make a constant that refers to the fn - /// with given `DefId` and args. Since this is used to synthesize - /// MIR, assumes `user_ty` is None. - pub fn function_handle( - tcx: TyCtxt<'tcx>, - def_id: DefId, - args: impl IntoIterator>, - span: Span, - ) -> Self { - let ty = Ty::new_fn_def(tcx, def_id, args); - Operand::Constant(Box::new(Constant { - span, - user_ty: None, - literal: ConstantKind::Val(ConstValue::ZeroSized, ty), - })) - } - - pub fn is_move(&self) -> bool { - matches!(self, Operand::Move(..)) - } - - /// Convenience helper to make a literal-like constant from a given scalar value. - /// Since this is used to synthesize MIR, assumes `user_ty` is None. - pub fn const_from_scalar( - tcx: TyCtxt<'tcx>, - ty: Ty<'tcx>, - val: Scalar, - span: Span, - ) -> Operand<'tcx> { - debug_assert!({ - let param_env_and_ty = ty::ParamEnv::empty().and(ty); - let type_size = tcx - .layout_of(param_env_and_ty) - .unwrap_or_else(|e| panic!("could not compute layout for {ty:?}: {e:?}")) - .size; - let scalar_size = match val { - Scalar::Int(int) => int.size(), - _ => panic!("Invalid scalar type {val:?}"), - }; - scalar_size == type_size - }); - Operand::Constant(Box::new(Constant { - span, - user_ty: None, - literal: ConstantKind::Val(ConstValue::Scalar(val), ty), - })) - } - - pub fn to_copy(&self) -> Self { - match *self { - Operand::Copy(_) | Operand::Constant(_) => self.clone(), - Operand::Move(place) => Operand::Copy(place), - } - } - - /// Returns the `Place` that is the target of this `Operand`, or `None` if this `Operand` is a - /// constant. - pub fn place(&self) -> Option> { - match self { - Operand::Copy(place) | Operand::Move(place) => Some(*place), - Operand::Constant(_) => None, - } - } - - /// Returns the `Constant` that is the target of this `Operand`, or `None` if this `Operand` is a - /// place. - pub fn constant(&self) -> Option<&Constant<'tcx>> { - match self { - Operand::Constant(x) => Some(&**x), - Operand::Copy(_) | Operand::Move(_) => None, - } - } - - /// Gets the `ty::FnDef` from an operand if it's a constant function item. - /// - /// While this is unlikely in general, it's the normal case of what you'll - /// find as the `func` in a [`TerminatorKind::Call`]. - pub fn const_fn_def(&self) -> Option<(DefId, GenericArgsRef<'tcx>)> { - let const_ty = self.constant()?.literal.ty(); - if let ty::FnDef(def_id, args) = *const_ty.kind() { Some((def_id, args)) } else { None } - } -} - -/////////////////////////////////////////////////////////////////////////// -/// Rvalues - -impl<'tcx> Rvalue<'tcx> { - /// Returns true if rvalue can be safely removed when the result is unused. - #[inline] - pub fn is_safe_to_remove(&self) -> bool { - match self { - // Pointer to int casts may be side-effects due to exposing the provenance. - // While the model is undecided, we should be conservative. See - // - Rvalue::Cast(CastKind::PointerExposeAddress, _, _) => false, - - Rvalue::Use(_) - | Rvalue::CopyForDeref(_) - | Rvalue::Repeat(_, _) - | Rvalue::Ref(_, _, _) - | Rvalue::ThreadLocalRef(_) - | Rvalue::AddressOf(_, _) - | Rvalue::Len(_) - | Rvalue::Cast( - CastKind::IntToInt - | CastKind::FloatToInt - | CastKind::FloatToFloat - | CastKind::IntToFloat - | CastKind::FnPtrToPtr - | CastKind::PtrToPtr - | CastKind::PointerCoercion(_) - | CastKind::PointerFromExposedAddress - | CastKind::DynStar - | CastKind::Transmute, - _, - _, - ) - | Rvalue::BinaryOp(_, _) - | Rvalue::CheckedBinaryOp(_, _) - | Rvalue::NullaryOp(_, _) - | Rvalue::UnaryOp(_, _) - | Rvalue::Discriminant(_) - | Rvalue::Aggregate(_, _) - | Rvalue::ShallowInitBox(_, _) => true, - } - } -} - -impl BorrowKind { - pub fn mutability(&self) -> Mutability { - match *self { - BorrowKind::Shared | BorrowKind::Shallow => Mutability::Not, - BorrowKind::Mut { .. } => Mutability::Mut, - } - } - - pub fn allows_two_phase_borrow(&self) -> bool { - match *self { - BorrowKind::Shared - | BorrowKind::Shallow - | BorrowKind::Mut { kind: MutBorrowKind::Default | MutBorrowKind::ClosureCapture } => { - false - } - BorrowKind::Mut { kind: MutBorrowKind::TwoPhaseBorrow } => true, - } - } -} - -impl<'tcx> Debug for Rvalue<'tcx> { - fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { - use self::Rvalue::*; - - match *self { - Use(ref place) => write!(fmt, "{place:?}"), - Repeat(ref a, b) => { - write!(fmt, "[{a:?}; ")?; - pretty_print_const(b, fmt, false)?; - write!(fmt, "]") - } - Len(ref a) => write!(fmt, "Len({a:?})"), - Cast(ref kind, ref place, ref ty) => { - with_no_trimmed_paths!(write!(fmt, "{place:?} as {ty} ({kind:?})")) - } - BinaryOp(ref op, box (ref a, ref b)) => write!(fmt, "{op:?}({a:?}, {b:?})"), - CheckedBinaryOp(ref op, box (ref a, ref b)) => { - write!(fmt, "Checked{op:?}({a:?}, {b:?})") - } - UnaryOp(ref op, ref a) => write!(fmt, "{op:?}({a:?})"), - Discriminant(ref place) => write!(fmt, "discriminant({place:?})"), - NullaryOp(ref op, ref t) => { - let t = with_no_trimmed_paths!(format!("{}", t)); - match op { - NullOp::SizeOf => write!(fmt, "SizeOf({t})"), - NullOp::AlignOf => write!(fmt, "AlignOf({t})"), - NullOp::OffsetOf(fields) => write!(fmt, "OffsetOf({t}, {fields:?})"), - } - } - ThreadLocalRef(did) => ty::tls::with(|tcx| { - let muta = tcx.static_mutability(did).unwrap().prefix_str(); - write!(fmt, "&/*tls*/ {}{}", muta, tcx.def_path_str(did)) - }), - Ref(region, borrow_kind, ref place) => { - let kind_str = match borrow_kind { - BorrowKind::Shared => "", - BorrowKind::Shallow => "shallow ", - BorrowKind::Mut { .. } => "mut ", - }; - - // When printing regions, add trailing space if necessary. - let print_region = ty::tls::with(|tcx| { - tcx.sess.verbose() || tcx.sess.opts.unstable_opts.identify_regions - }); - let region = if print_region { - let mut region = region.to_string(); - if !region.is_empty() { - region.push(' '); - } - region - } else { - // Do not even print 'static - String::new() - }; - write!(fmt, "&{region}{kind_str}{place:?}") - } - - CopyForDeref(ref place) => write!(fmt, "deref_copy {place:#?}"), - - AddressOf(mutability, ref place) => { - let kind_str = match mutability { - Mutability::Mut => "mut", - Mutability::Not => "const", - }; - - write!(fmt, "&raw {kind_str} {place:?}") - } - - Aggregate(ref kind, ref places) => { - let fmt_tuple = |fmt: &mut Formatter<'_>, name: &str| { - let mut tuple_fmt = fmt.debug_tuple(name); - for place in places { - tuple_fmt.field(place); - } - tuple_fmt.finish() - }; - - match **kind { - AggregateKind::Array(_) => write!(fmt, "{places:?}"), - - AggregateKind::Tuple => { - if places.is_empty() { - write!(fmt, "()") - } else { - fmt_tuple(fmt, "") - } - } - - AggregateKind::Adt(adt_did, variant, args, _user_ty, _) => { - ty::tls::with(|tcx| { - let variant_def = &tcx.adt_def(adt_did).variant(variant); - let args = tcx.lift(args).expect("could not lift for printing"); - let name = FmtPrinter::new(tcx, Namespace::ValueNS) - .print_def_path(variant_def.def_id, args)? - .into_buffer(); - - match variant_def.ctor_kind() { - Some(CtorKind::Const) => fmt.write_str(&name), - Some(CtorKind::Fn) => fmt_tuple(fmt, &name), - None => { - let mut struct_fmt = fmt.debug_struct(&name); - for (field, place) in iter::zip(&variant_def.fields, places) { - struct_fmt.field(field.name.as_str(), place); - } - struct_fmt.finish() - } - } - }) - } - - AggregateKind::Closure(def_id, args) => ty::tls::with(|tcx| { - let name = if tcx.sess.opts.unstable_opts.span_free_formats { - let args = tcx.lift(args).unwrap(); - format!("[closure@{}]", tcx.def_path_str_with_args(def_id, args),) - } else { - let span = tcx.def_span(def_id); - format!( - "[closure@{}]", - tcx.sess.source_map().span_to_diagnostic_string(span) - ) - }; - let mut struct_fmt = fmt.debug_struct(&name); - - // FIXME(project-rfc-2229#48): This should be a list of capture names/places - if let Some(def_id) = def_id.as_local() - && let Some(upvars) = tcx.upvars_mentioned(def_id) - { - for (&var_id, place) in iter::zip(upvars.keys(), places) { - let var_name = tcx.hir().name(var_id); - struct_fmt.field(var_name.as_str(), place); - } - } else { - for (index, place) in places.iter().enumerate() { - struct_fmt.field(&format!("{index}"), place); - } - } - - struct_fmt.finish() - }), - - AggregateKind::Generator(def_id, _, _) => ty::tls::with(|tcx| { - let name = format!("[generator@{:?}]", tcx.def_span(def_id)); - let mut struct_fmt = fmt.debug_struct(&name); - - // FIXME(project-rfc-2229#48): This should be a list of capture names/places - if let Some(def_id) = def_id.as_local() - && let Some(upvars) = tcx.upvars_mentioned(def_id) - { - for (&var_id, place) in iter::zip(upvars.keys(), places) { - let var_name = tcx.hir().name(var_id); - struct_fmt.field(var_name.as_str(), place); - } - } else { - for (index, place) in places.iter().enumerate() { - struct_fmt.field(&format!("{index}"), place); - } - } - - struct_fmt.finish() - }), - } - } - - ShallowInitBox(ref place, ref ty) => { - with_no_trimmed_paths!(write!(fmt, "ShallowInitBox({place:?}, {ty})")) - } - } - } -} - /// A collection of projections into user types. /// /// They are projections because a binding can occur a part of a /// parent pattern that has been ascribed a type. /// -/// Its a collection because there can be multiple type ascriptions on +/// It's a collection because there can be multiple type ascriptions on /// the path from the root of the pattern down to the binding itself. /// /// An example: diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index 39af95d62f86..ce033f1bc545 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -1,13 +1,13 @@ use std::collections::BTreeSet; -use std::fmt::Display; -use std::fmt::Write as _; +use std::fmt::{self, Debug, Display, Write as _}; use std::fs; -use std::io::{self, Write}; +use std::io::{self, Write as _}; use std::path::{Path, PathBuf}; use super::graphviz::write_mir_fn_graphviz; use super::spanview::write_mir_fn_spanview; use either::Either; +use rustc_ast::InlineAsmTemplatePiece; use rustc_data_structures::fx::FxHashMap; use rustc_hir::def_id::DefId; use rustc_index::Idx; @@ -79,7 +79,7 @@ pub fn dump_mir<'tcx, F>( body: &Body<'tcx>, extra_data: F, ) where - F: FnMut(PassWhere, &mut dyn Write) -> io::Result<()>, + F: FnMut(PassWhere, &mut dyn io::Write) -> io::Result<()>, { if !dump_enabled(tcx, pass_name, body.source.def_id()) { return; @@ -116,7 +116,7 @@ fn dump_matched_mir_node<'tcx, F>( body: &Body<'tcx>, mut extra_data: F, ) where - F: FnMut(PassWhere, &mut dyn Write) -> io::Result<()>, + F: FnMut(PassWhere, &mut dyn io::Write) -> io::Result<()>, { let _: io::Result<()> = try { let mut file = create_dump_file(tcx, "mir", pass_num, pass_name, disambiguator, body)?; @@ -260,11 +260,14 @@ pub fn create_dump_file<'tcx>( ) } +/////////////////////////////////////////////////////////////////////////// +// Whole MIR bodies + /// Write out a human-readable textual representation for the given MIR. pub fn write_mir_pretty<'tcx>( tcx: TyCtxt<'tcx>, single: Option, - w: &mut dyn Write, + w: &mut dyn io::Write, ) -> io::Result<()> { writeln!(w, "// WARNING: This output format is intended for human consumers only")?; writeln!(w, "// and is subject to change without notice. Knock yourself out.")?; @@ -278,7 +281,7 @@ pub fn write_mir_pretty<'tcx>( writeln!(w)?; } - let render_body = |w: &mut dyn Write, body| -> io::Result<()> { + let render_body = |w: &mut dyn io::Write, body| -> io::Result<()> { write_mir_fn(tcx, body, &mut |_, _| Ok(()), w)?; for body in tcx.promoted_mir(def_id) { @@ -309,10 +312,10 @@ pub fn write_mir_fn<'tcx, F>( tcx: TyCtxt<'tcx>, body: &Body<'tcx>, extra_data: &mut F, - w: &mut dyn Write, + w: &mut dyn io::Write, ) -> io::Result<()> where - F: FnMut(PassWhere, &mut dyn Write) -> io::Result<()>, + F: FnMut(PassWhere, &mut dyn io::Write) -> io::Result<()>, { write_mir_intro(tcx, body, w)?; for block in body.basic_blocks.indices() { @@ -330,16 +333,268 @@ where Ok(()) } +/// Prints local variables in a scope tree. +fn write_scope_tree( + tcx: TyCtxt<'_>, + body: &Body<'_>, + scope_tree: &FxHashMap>, + w: &mut dyn io::Write, + parent: SourceScope, + depth: usize, +) -> io::Result<()> { + let indent = depth * INDENT.len(); + + // Local variable debuginfo. + for var_debug_info in &body.var_debug_info { + if var_debug_info.source_info.scope != parent { + // Not declared in this scope. + continue; + } + + let indented_debug_info = format!("{0:1$}debug {2:?};", INDENT, indent, var_debug_info); + + if tcx.sess.opts.unstable_opts.mir_include_spans { + writeln!( + w, + "{0:1$} // in {2}", + indented_debug_info, + ALIGN, + comment(tcx, var_debug_info.source_info), + )?; + } else { + writeln!(w, "{indented_debug_info}")?; + } + } + + // Local variable types. + for (local, local_decl) in body.local_decls.iter_enumerated() { + if (1..body.arg_count + 1).contains(&local.index()) { + // Skip over argument locals, they're printed in the signature. + continue; + } + + if local_decl.source_info.scope != parent { + // Not declared in this scope. + continue; + } + + let mut_str = local_decl.mutability.prefix_str(); + + let mut indented_decl = ty::print::with_no_trimmed_paths!(format!( + "{0:1$}let {2}{3:?}: {4}", + INDENT, indent, mut_str, local, local_decl.ty + )); + if let Some(user_ty) = &local_decl.user_ty { + for user_ty in user_ty.projections() { + write!(indented_decl, " as {user_ty:?}").unwrap(); + } + } + indented_decl.push(';'); + + let local_name = if local == RETURN_PLACE { " return place" } else { "" }; + + if tcx.sess.opts.unstable_opts.mir_include_spans { + writeln!( + w, + "{0:1$} //{2} in {3}", + indented_decl, + ALIGN, + local_name, + comment(tcx, local_decl.source_info), + )?; + } else { + writeln!(w, "{indented_decl}",)?; + } + } + + let Some(children) = scope_tree.get(&parent) else { + return Ok(()); + }; + + for &child in children { + let child_data = &body.source_scopes[child]; + assert_eq!(child_data.parent_scope, Some(parent)); + + let (special, span) = if let Some((callee, callsite_span)) = child_data.inlined { + ( + format!( + " (inlined {}{})", + if callee.def.requires_caller_location(tcx) { "#[track_caller] " } else { "" }, + callee + ), + Some(callsite_span), + ) + } else { + (String::new(), None) + }; + + let indented_header = format!("{0:1$}scope {2}{3} {{", "", indent, child.index(), special); + + if tcx.sess.opts.unstable_opts.mir_include_spans { + if let Some(span) = span { + writeln!( + w, + "{0:1$} // at {2}", + indented_header, + ALIGN, + tcx.sess.source_map().span_to_embeddable_string(span), + )?; + } else { + writeln!(w, "{indented_header}")?; + } + } else { + writeln!(w, "{indented_header}")?; + } + + write_scope_tree(tcx, body, scope_tree, w, child, depth + 1)?; + writeln!(w, "{0:1$}}}", "", depth * INDENT.len())?; + } + + Ok(()) +} + +impl Debug for VarDebugInfo<'_> { + fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { + if let Some(box VarDebugInfoFragment { ty, ref projection }) = self.composite { + pre_fmt_projection(&projection[..], fmt)?; + write!(fmt, "({}: {})", self.name, ty)?; + post_fmt_projection(&projection[..], fmt)?; + } else { + write!(fmt, "{}", self.name)?; + } + + write!(fmt, " => {:?}", self.value) + } +} + +/// Write out a human-readable textual representation of the MIR's `fn` type and the types of its +/// local variables (both user-defined bindings and compiler temporaries). +pub fn write_mir_intro<'tcx>( + tcx: TyCtxt<'tcx>, + body: &Body<'_>, + w: &mut dyn io::Write, +) -> io::Result<()> { + write_mir_sig(tcx, body, w)?; + writeln!(w, "{{")?; + + // construct a scope tree and write it out + let mut scope_tree: FxHashMap> = Default::default(); + for (index, scope_data) in body.source_scopes.iter().enumerate() { + if let Some(parent) = scope_data.parent_scope { + scope_tree.entry(parent).or_default().push(SourceScope::new(index)); + } else { + // Only the argument scope has no parent, because it's the root. + assert_eq!(index, OUTERMOST_SOURCE_SCOPE.index()); + } + } + + write_scope_tree(tcx, body, &scope_tree, w, OUTERMOST_SOURCE_SCOPE, 1)?; + + // Add an empty line before the first block is printed. + writeln!(w)?; + + Ok(()) +} + +fn write_mir_sig(tcx: TyCtxt<'_>, body: &Body<'_>, w: &mut dyn io::Write) -> io::Result<()> { + use rustc_hir::def::DefKind; + + trace!("write_mir_sig: {:?}", body.source.instance); + let def_id = body.source.def_id(); + let kind = tcx.def_kind(def_id); + let is_function = match kind { + DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(..) => true, + _ => tcx.is_closure(def_id), + }; + match (kind, body.source.promoted) { + (_, Some(i)) => write!(w, "{i:?} in ")?, + (DefKind::Const | DefKind::AssocConst, _) => write!(w, "const ")?, + (DefKind::Static(hir::Mutability::Not), _) => write!(w, "static ")?, + (DefKind::Static(hir::Mutability::Mut), _) => write!(w, "static mut ")?, + (_, _) if is_function => write!(w, "fn ")?, + (DefKind::AnonConst | DefKind::InlineConst, _) => {} // things like anon const, not an item + _ => bug!("Unexpected def kind {:?}", kind), + } + + ty::print::with_forced_impl_filename_line! { + // see notes on #41697 elsewhere + write!(w, "{}", tcx.def_path_str(def_id))? + } + + if body.source.promoted.is_none() && is_function { + write!(w, "(")?; + + // fn argument types. + for (i, arg) in body.args_iter().enumerate() { + if i != 0 { + write!(w, ", ")?; + } + write!(w, "{:?}: {}", Place::from(arg), body.local_decls[arg].ty)?; + } + + write!(w, ") -> {}", body.return_ty())?; + } else { + assert_eq!(body.arg_count, 0); + write!(w, ": {} =", body.return_ty())?; + } + + if let Some(yield_ty) = body.yield_ty() { + writeln!(w)?; + writeln!(w, "yields {yield_ty}")?; + } + + write!(w, " ")?; + // Next thing that gets printed is the opening { + + Ok(()) +} + +fn write_user_type_annotations( + tcx: TyCtxt<'_>, + body: &Body<'_>, + w: &mut dyn io::Write, +) -> io::Result<()> { + if !body.user_type_annotations.is_empty() { + writeln!(w, "| User Type Annotations")?; + } + for (index, annotation) in body.user_type_annotations.iter_enumerated() { + writeln!( + w, + "| {:?}: user_ty: {}, span: {}, inferred_ty: {}", + index.index(), + annotation.user_ty, + tcx.sess.source_map().span_to_embeddable_string(annotation.span), + with_no_trimmed_paths!(format!("{}", annotation.inferred_ty)), + )?; + } + if !body.user_type_annotations.is_empty() { + writeln!(w, "|")?; + } + Ok(()) +} + +pub fn dump_mir_def_ids(tcx: TyCtxt<'_>, single: Option) -> Vec { + if let Some(i) = single { + vec![i] + } else { + tcx.mir_keys(()).iter().map(|def_id| def_id.to_def_id()).collect() + } +} + +/////////////////////////////////////////////////////////////////////////// +// Basic blocks and their parts +// FIXME: we use `Debug` for pretty-printing here; should we implement `Display` instead? + /// Write out a human-readable textual representation for the given basic block. pub fn write_basic_block<'tcx, F>( tcx: TyCtxt<'tcx>, block: BasicBlock, body: &Body<'tcx>, extra_data: &mut F, - w: &mut dyn Write, + w: &mut dyn io::Write, ) -> io::Result<()> where - F: FnMut(PassWhere, &mut dyn Write) -> io::Result<()>, + F: FnMut(PassWhere, &mut dyn io::Write) -> io::Result<()>, { let data = &body[block]; @@ -390,20 +645,507 @@ where writeln!(w, "{indented_terminator}")?; } - write_extra(tcx, w, |visitor| { - visitor.visit_terminator(data.terminator(), current_location); - })?; + write_extra(tcx, w, |visitor| { + visitor.visit_terminator(data.terminator(), current_location); + })?; + + extra_data(PassWhere::AfterLocation(current_location), w)?; + extra_data(PassWhere::AfterTerminator(block), w)?; + + writeln!(w, "{INDENT}}}") +} + +impl Debug for Statement<'_> { + fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { + use self::StatementKind::*; + match self.kind { + Assign(box (ref place, ref rv)) => write!(fmt, "{place:?} = {rv:?}"), + FakeRead(box (ref cause, ref place)) => { + write!(fmt, "FakeRead({cause:?}, {place:?})") + } + Retag(ref kind, ref place) => write!( + fmt, + "Retag({}{:?})", + match kind { + RetagKind::FnEntry => "[fn entry] ", + RetagKind::TwoPhase => "[2phase] ", + RetagKind::Raw => "[raw] ", + RetagKind::Default => "", + }, + place, + ), + StorageLive(ref place) => write!(fmt, "StorageLive({place:?})"), + StorageDead(ref place) => write!(fmt, "StorageDead({place:?})"), + SetDiscriminant { ref place, variant_index } => { + write!(fmt, "discriminant({place:?}) = {variant_index:?}") + } + Deinit(ref place) => write!(fmt, "Deinit({place:?})"), + PlaceMention(ref place) => { + write!(fmt, "PlaceMention({place:?})") + } + AscribeUserType(box (ref place, ref c_ty), ref variance) => { + write!(fmt, "AscribeUserType({place:?}, {variance:?}, {c_ty:?})") + } + Coverage(box self::Coverage { ref kind, code_region: Some(ref rgn) }) => { + write!(fmt, "Coverage::{kind:?} for {rgn:?}") + } + Coverage(box ref coverage) => write!(fmt, "Coverage::{:?}", coverage.kind), + Intrinsic(box ref intrinsic) => write!(fmt, "{intrinsic}"), + ConstEvalCounter => write!(fmt, "ConstEvalCounter"), + Nop => write!(fmt, "nop"), + } + } +} + +impl<'tcx> Debug for TerminatorKind<'tcx> { + fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { + self.fmt_head(fmt)?; + let successor_count = self.successors().count(); + let labels = self.fmt_successor_labels(); + assert_eq!(successor_count, labels.len()); + + // `Cleanup` is already included in successors + let show_unwind = !matches!(self.unwind(), None | Some(UnwindAction::Cleanup(_))); + let fmt_unwind = |fmt: &mut Formatter<'_>| -> fmt::Result { + write!(fmt, "unwind ")?; + match self.unwind() { + // Not needed or included in successors + None | Some(UnwindAction::Cleanup(_)) => unreachable!(), + Some(UnwindAction::Continue) => write!(fmt, "continue"), + Some(UnwindAction::Unreachable) => write!(fmt, "unreachable"), + Some(UnwindAction::Terminate(reason)) => { + write!(fmt, "terminate({})", reason.as_short_str()) + } + } + }; + + match (successor_count, show_unwind) { + (0, false) => Ok(()), + (0, true) => { + write!(fmt, " -> ")?; + fmt_unwind(fmt) + } + (1, false) => write!(fmt, " -> {:?}", self.successors().next().unwrap()), + _ => { + write!(fmt, " -> [")?; + for (i, target) in self.successors().enumerate() { + if i > 0 { + write!(fmt, ", ")?; + } + write!(fmt, "{}: {:?}", labels[i], target)?; + } + if show_unwind { + write!(fmt, ", ")?; + fmt_unwind(fmt)?; + } + write!(fmt, "]") + } + } + } +} + +impl<'tcx> TerminatorKind<'tcx> { + /// Writes the "head" part of the terminator; that is, its name and the data it uses to pick the + /// successor basic block, if any. The only information not included is the list of possible + /// successors, which may be rendered differently between the text and the graphviz format. + pub fn fmt_head(&self, fmt: &mut W) -> fmt::Result { + use self::TerminatorKind::*; + match self { + Goto { .. } => write!(fmt, "goto"), + SwitchInt { discr, .. } => write!(fmt, "switchInt({discr:?})"), + Return => write!(fmt, "return"), + GeneratorDrop => write!(fmt, "generator_drop"), + UnwindResume => write!(fmt, "resume"), + UnwindTerminate(reason) => { + write!(fmt, "abort({})", reason.as_short_str()) + } + Yield { value, resume_arg, .. } => write!(fmt, "{resume_arg:?} = yield({value:?})"), + Unreachable => write!(fmt, "unreachable"), + Drop { place, .. } => write!(fmt, "drop({place:?})"), + Call { func, args, destination, .. } => { + write!(fmt, "{destination:?} = ")?; + write!(fmt, "{func:?}(")?; + for (index, arg) in args.iter().enumerate() { + if index > 0 { + write!(fmt, ", ")?; + } + write!(fmt, "{arg:?}")?; + } + write!(fmt, ")") + } + Assert { cond, expected, msg, .. } => { + write!(fmt, "assert(")?; + if !expected { + write!(fmt, "!")?; + } + write!(fmt, "{cond:?}, ")?; + msg.fmt_assert_args(fmt)?; + write!(fmt, ")") + } + FalseEdge { .. } => write!(fmt, "falseEdge"), + FalseUnwind { .. } => write!(fmt, "falseUnwind"), + InlineAsm { template, ref operands, options, .. } => { + write!(fmt, "asm!(\"{}\"", InlineAsmTemplatePiece::to_string(template))?; + for op in operands { + write!(fmt, ", ")?; + let print_late = |&late| if late { "late" } else { "" }; + match op { + InlineAsmOperand::In { reg, value } => { + write!(fmt, "in({reg}) {value:?}")?; + } + InlineAsmOperand::Out { reg, late, place: Some(place) } => { + write!(fmt, "{}out({}) {:?}", print_late(late), reg, place)?; + } + InlineAsmOperand::Out { reg, late, place: None } => { + write!(fmt, "{}out({}) _", print_late(late), reg)?; + } + InlineAsmOperand::InOut { + reg, + late, + in_value, + out_place: Some(out_place), + } => { + write!( + fmt, + "in{}out({}) {:?} => {:?}", + print_late(late), + reg, + in_value, + out_place + )?; + } + InlineAsmOperand::InOut { reg, late, in_value, out_place: None } => { + write!(fmt, "in{}out({}) {:?} => _", print_late(late), reg, in_value)?; + } + InlineAsmOperand::Const { value } => { + write!(fmt, "const {value:?}")?; + } + InlineAsmOperand::SymFn { value } => { + write!(fmt, "sym_fn {value:?}")?; + } + InlineAsmOperand::SymStatic { def_id } => { + write!(fmt, "sym_static {def_id:?}")?; + } + } + } + write!(fmt, ", options({options:?}))") + } + } + } + + /// Returns the list of labels for the edges to the successor basic blocks. + pub fn fmt_successor_labels(&self) -> Vec> { + use self::TerminatorKind::*; + match *self { + Return | UnwindResume | UnwindTerminate(_) | Unreachable | GeneratorDrop => vec![], + Goto { .. } => vec!["".into()], + SwitchInt { ref targets, .. } => targets + .values + .iter() + .map(|&u| Cow::Owned(u.to_string())) + .chain(iter::once("otherwise".into())) + .collect(), + Call { target: Some(_), unwind: UnwindAction::Cleanup(_), .. } => { + vec!["return".into(), "unwind".into()] + } + Call { target: Some(_), unwind: _, .. } => vec!["return".into()], + Call { target: None, unwind: UnwindAction::Cleanup(_), .. } => vec!["unwind".into()], + Call { target: None, unwind: _, .. } => vec![], + Yield { drop: Some(_), .. } => vec!["resume".into(), "drop".into()], + Yield { drop: None, .. } => vec!["resume".into()], + Drop { unwind: UnwindAction::Cleanup(_), .. } => vec!["return".into(), "unwind".into()], + Drop { unwind: _, .. } => vec!["return".into()], + Assert { unwind: UnwindAction::Cleanup(_), .. } => { + vec!["success".into(), "unwind".into()] + } + Assert { unwind: _, .. } => vec!["success".into()], + FalseEdge { .. } => vec!["real".into(), "imaginary".into()], + FalseUnwind { unwind: UnwindAction::Cleanup(_), .. } => { + vec!["real".into(), "unwind".into()] + } + FalseUnwind { unwind: _, .. } => vec!["real".into()], + InlineAsm { destination: Some(_), unwind: UnwindAction::Cleanup(_), .. } => { + vec!["return".into(), "unwind".into()] + } + InlineAsm { destination: Some(_), unwind: _, .. } => { + vec!["return".into()] + } + InlineAsm { destination: None, unwind: UnwindAction::Cleanup(_), .. } => { + vec!["unwind".into()] + } + InlineAsm { destination: None, unwind: _, .. } => vec![], + } + } +} + +impl<'tcx> Debug for Rvalue<'tcx> { + fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { + use self::Rvalue::*; + + match *self { + Use(ref place) => write!(fmt, "{place:?}"), + Repeat(ref a, b) => { + write!(fmt, "[{a:?}; ")?; + pretty_print_const(b, fmt, false)?; + write!(fmt, "]") + } + Len(ref a) => write!(fmt, "Len({a:?})"), + Cast(ref kind, ref place, ref ty) => { + with_no_trimmed_paths!(write!(fmt, "{place:?} as {ty} ({kind:?})")) + } + BinaryOp(ref op, box (ref a, ref b)) => write!(fmt, "{op:?}({a:?}, {b:?})"), + CheckedBinaryOp(ref op, box (ref a, ref b)) => { + write!(fmt, "Checked{op:?}({a:?}, {b:?})") + } + UnaryOp(ref op, ref a) => write!(fmt, "{op:?}({a:?})"), + Discriminant(ref place) => write!(fmt, "discriminant({place:?})"), + NullaryOp(ref op, ref t) => { + let t = with_no_trimmed_paths!(format!("{}", t)); + match op { + NullOp::SizeOf => write!(fmt, "SizeOf({t})"), + NullOp::AlignOf => write!(fmt, "AlignOf({t})"), + NullOp::OffsetOf(fields) => write!(fmt, "OffsetOf({t}, {fields:?})"), + } + } + ThreadLocalRef(did) => ty::tls::with(|tcx| { + let muta = tcx.static_mutability(did).unwrap().prefix_str(); + write!(fmt, "&/*tls*/ {}{}", muta, tcx.def_path_str(did)) + }), + Ref(region, borrow_kind, ref place) => { + let kind_str = match borrow_kind { + BorrowKind::Shared => "", + BorrowKind::Shallow => "shallow ", + BorrowKind::Mut { .. } => "mut ", + }; + + // When printing regions, add trailing space if necessary. + let print_region = ty::tls::with(|tcx| { + tcx.sess.verbose() || tcx.sess.opts.unstable_opts.identify_regions + }); + let region = if print_region { + let mut region = region.to_string(); + if !region.is_empty() { + region.push(' '); + } + region + } else { + // Do not even print 'static + String::new() + }; + write!(fmt, "&{region}{kind_str}{place:?}") + } + + CopyForDeref(ref place) => write!(fmt, "deref_copy {place:#?}"), + + AddressOf(mutability, ref place) => { + let kind_str = match mutability { + Mutability::Mut => "mut", + Mutability::Not => "const", + }; + + write!(fmt, "&raw {kind_str} {place:?}") + } + + Aggregate(ref kind, ref places) => { + let fmt_tuple = |fmt: &mut Formatter<'_>, name: &str| { + let mut tuple_fmt = fmt.debug_tuple(name); + for place in places { + tuple_fmt.field(place); + } + tuple_fmt.finish() + }; + + match **kind { + AggregateKind::Array(_) => write!(fmt, "{places:?}"), + + AggregateKind::Tuple => { + if places.is_empty() { + write!(fmt, "()") + } else { + fmt_tuple(fmt, "") + } + } + + AggregateKind::Adt(adt_did, variant, args, _user_ty, _) => { + ty::tls::with(|tcx| { + let variant_def = &tcx.adt_def(adt_did).variant(variant); + let args = tcx.lift(args).expect("could not lift for printing"); + let name = FmtPrinter::new(tcx, Namespace::ValueNS) + .print_def_path(variant_def.def_id, args)? + .into_buffer(); + + match variant_def.ctor_kind() { + Some(CtorKind::Const) => fmt.write_str(&name), + Some(CtorKind::Fn) => fmt_tuple(fmt, &name), + None => { + let mut struct_fmt = fmt.debug_struct(&name); + for (field, place) in iter::zip(&variant_def.fields, places) { + struct_fmt.field(field.name.as_str(), place); + } + struct_fmt.finish() + } + } + }) + } + + AggregateKind::Closure(def_id, args) => ty::tls::with(|tcx| { + let name = if tcx.sess.opts.unstable_opts.span_free_formats { + let args = tcx.lift(args).unwrap(); + format!("[closure@{}]", tcx.def_path_str_with_args(def_id, args),) + } else { + let span = tcx.def_span(def_id); + format!( + "[closure@{}]", + tcx.sess.source_map().span_to_diagnostic_string(span) + ) + }; + let mut struct_fmt = fmt.debug_struct(&name); + + // FIXME(project-rfc-2229#48): This should be a list of capture names/places + if let Some(def_id) = def_id.as_local() + && let Some(upvars) = tcx.upvars_mentioned(def_id) + { + for (&var_id, place) in iter::zip(upvars.keys(), places) { + let var_name = tcx.hir().name(var_id); + struct_fmt.field(var_name.as_str(), place); + } + } else { + for (index, place) in places.iter().enumerate() { + struct_fmt.field(&format!("{index}"), place); + } + } + + struct_fmt.finish() + }), + + AggregateKind::Generator(def_id, _, _) => ty::tls::with(|tcx| { + let name = format!("[generator@{:?}]", tcx.def_span(def_id)); + let mut struct_fmt = fmt.debug_struct(&name); + + // FIXME(project-rfc-2229#48): This should be a list of capture names/places + if let Some(def_id) = def_id.as_local() + && let Some(upvars) = tcx.upvars_mentioned(def_id) + { + for (&var_id, place) in iter::zip(upvars.keys(), places) { + let var_name = tcx.hir().name(var_id); + struct_fmt.field(var_name.as_str(), place); + } + } else { + for (index, place) in places.iter().enumerate() { + struct_fmt.field(&format!("{index}"), place); + } + } + + struct_fmt.finish() + }), + } + } + + ShallowInitBox(ref place, ref ty) => { + with_no_trimmed_paths!(write!(fmt, "ShallowInitBox({place:?}, {ty})")) + } + } + } +} + +impl<'tcx> Debug for Operand<'tcx> { + fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { + use self::Operand::*; + match *self { + Constant(ref a) => write!(fmt, "{a:?}"), + Copy(ref place) => write!(fmt, "{place:?}"), + Move(ref place) => write!(fmt, "move {place:?}"), + } + } +} + +impl Debug for Place<'_> { + fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { + self.as_ref().fmt(fmt) + } +} + +impl Debug for PlaceRef<'_> { + fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { + pre_fmt_projection(self.projection, fmt)?; + write!(fmt, "{:?}", self.local)?; + post_fmt_projection(self.projection, fmt) + } +} + +fn pre_fmt_projection(projection: &[PlaceElem<'_>], fmt: &mut Formatter<'_>) -> fmt::Result { + for &elem in projection.iter().rev() { + match elem { + ProjectionElem::OpaqueCast(_) + | ProjectionElem::Downcast(_, _) + | ProjectionElem::Field(_, _) => { + write!(fmt, "(").unwrap(); + } + ProjectionElem::Deref => { + write!(fmt, "(*").unwrap(); + } + ProjectionElem::Index(_) + | ProjectionElem::ConstantIndex { .. } + | ProjectionElem::Subslice { .. } => {} + } + } + + Ok(()) +} - extra_data(PassWhere::AfterLocation(current_location), w)?; - extra_data(PassWhere::AfterTerminator(block), w)?; +fn post_fmt_projection(projection: &[PlaceElem<'_>], fmt: &mut Formatter<'_>) -> fmt::Result { + for &elem in projection.iter() { + match elem { + ProjectionElem::OpaqueCast(ty) => { + write!(fmt, " as {ty})")?; + } + ProjectionElem::Downcast(Some(name), _index) => { + write!(fmt, " as {name})")?; + } + ProjectionElem::Downcast(None, index) => { + write!(fmt, " as variant#{index:?})")?; + } + ProjectionElem::Deref => { + write!(fmt, ")")?; + } + ProjectionElem::Field(field, ty) => { + with_no_trimmed_paths!(write!(fmt, ".{:?}: {})", field.index(), ty)?); + } + ProjectionElem::Index(ref index) => { + write!(fmt, "[{index:?}]")?; + } + ProjectionElem::ConstantIndex { offset, min_length, from_end: false } => { + write!(fmt, "[{offset:?} of {min_length:?}]")?; + } + ProjectionElem::ConstantIndex { offset, min_length, from_end: true } => { + write!(fmt, "[-{offset:?} of {min_length:?}]")?; + } + ProjectionElem::Subslice { from, to, from_end: true } if to == 0 => { + write!(fmt, "[{from:?}:]")?; + } + ProjectionElem::Subslice { from, to, from_end: true } if from == 0 => { + write!(fmt, "[:-{to:?}]")?; + } + ProjectionElem::Subslice { from, to, from_end: true } => { + write!(fmt, "[{from:?}:-{to:?}]")?; + } + ProjectionElem::Subslice { from, to, from_end: false } => { + write!(fmt, "[{from:?}..{to:?}]")?; + } + } + } - writeln!(w, "{INDENT}}}") + Ok(()) } /// After we print the main statement, we sometimes dump extra /// information. There's often a lot of little things "nuzzled up" in /// a statement. -fn write_extra<'tcx, F>(tcx: TyCtxt<'tcx>, write: &mut dyn Write, mut visit_op: F) -> io::Result<()> +fn write_extra<'tcx, F>( + tcx: TyCtxt<'tcx>, + write: &mut dyn io::Write, + mut visit_op: F, +) -> io::Result<()> where F: FnMut(&mut ExtraComments<'tcx>), { @@ -534,161 +1276,15 @@ fn comment(tcx: TyCtxt<'_>, SourceInfo { span, scope }: SourceInfo) -> String { format!("scope {} at {}", scope.index(), location,) } -/// Prints local variables in a scope tree. -fn write_scope_tree( - tcx: TyCtxt<'_>, - body: &Body<'_>, - scope_tree: &FxHashMap>, - w: &mut dyn Write, - parent: SourceScope, - depth: usize, -) -> io::Result<()> { - let indent = depth * INDENT.len(); - - // Local variable debuginfo. - for var_debug_info in &body.var_debug_info { - if var_debug_info.source_info.scope != parent { - // Not declared in this scope. - continue; - } - - let indented_debug_info = format!("{0:1$}debug {2:?};", INDENT, indent, var_debug_info); - - if tcx.sess.opts.unstable_opts.mir_include_spans { - writeln!( - w, - "{0:1$} // in {2}", - indented_debug_info, - ALIGN, - comment(tcx, var_debug_info.source_info), - )?; - } else { - writeln!(w, "{indented_debug_info}")?; - } - } - - // Local variable types. - for (local, local_decl) in body.local_decls.iter_enumerated() { - if (1..body.arg_count + 1).contains(&local.index()) { - // Skip over argument locals, they're printed in the signature. - continue; - } - - if local_decl.source_info.scope != parent { - // Not declared in this scope. - continue; - } - - let mut_str = local_decl.mutability.prefix_str(); - - let mut indented_decl = ty::print::with_no_trimmed_paths!(format!( - "{0:1$}let {2}{3:?}: {4}", - INDENT, indent, mut_str, local, local_decl.ty - )); - if let Some(user_ty) = &local_decl.user_ty { - for user_ty in user_ty.projections() { - write!(indented_decl, " as {user_ty:?}").unwrap(); - } - } - indented_decl.push(';'); - - let local_name = if local == RETURN_PLACE { " return place" } else { "" }; - - if tcx.sess.opts.unstable_opts.mir_include_spans { - writeln!( - w, - "{0:1$} //{2} in {3}", - indented_decl, - ALIGN, - local_name, - comment(tcx, local_decl.source_info), - )?; - } else { - writeln!(w, "{indented_decl}",)?; - } - } - - let Some(children) = scope_tree.get(&parent) else { - return Ok(()); - }; - - for &child in children { - let child_data = &body.source_scopes[child]; - assert_eq!(child_data.parent_scope, Some(parent)); - - let (special, span) = if let Some((callee, callsite_span)) = child_data.inlined { - ( - format!( - " (inlined {}{})", - if callee.def.requires_caller_location(tcx) { "#[track_caller] " } else { "" }, - callee - ), - Some(callsite_span), - ) - } else { - (String::new(), None) - }; - - let indented_header = format!("{0:1$}scope {2}{3} {{", "", indent, child.index(), special); - - if tcx.sess.opts.unstable_opts.mir_include_spans { - if let Some(span) = span { - writeln!( - w, - "{0:1$} // at {2}", - indented_header, - ALIGN, - tcx.sess.source_map().span_to_embeddable_string(span), - )?; - } else { - writeln!(w, "{indented_header}")?; - } - } else { - writeln!(w, "{indented_header}")?; - } - - write_scope_tree(tcx, body, scope_tree, w, child, depth + 1)?; - writeln!(w, "{0:1$}}}", "", depth * INDENT.len())?; - } - - Ok(()) -} - -/// Write out a human-readable textual representation of the MIR's `fn` type and the types of its -/// local variables (both user-defined bindings and compiler temporaries). -pub fn write_mir_intro<'tcx>( - tcx: TyCtxt<'tcx>, - body: &Body<'_>, - w: &mut dyn Write, -) -> io::Result<()> { - write_mir_sig(tcx, body, w)?; - writeln!(w, "{{")?; - - // construct a scope tree and write it out - let mut scope_tree: FxHashMap> = Default::default(); - for (index, scope_data) in body.source_scopes.iter().enumerate() { - if let Some(parent) = scope_data.parent_scope { - scope_tree.entry(parent).or_default().push(SourceScope::new(index)); - } else { - // Only the argument scope has no parent, because it's the root. - assert_eq!(index, OUTERMOST_SOURCE_SCOPE.index()); - } - } - - write_scope_tree(tcx, body, &scope_tree, w, OUTERMOST_SOURCE_SCOPE, 1)?; - - // Add an empty line before the first block is printed. - writeln!(w)?; - - Ok(()) -} +/////////////////////////////////////////////////////////////////////////// +// Allocations /// Find all `AllocId`s mentioned (recursively) in the MIR body and print their corresponding /// allocations. pub fn write_allocations<'tcx>( tcx: TyCtxt<'tcx>, body: &Body<'_>, - w: &mut dyn Write, + w: &mut dyn io::Write, ) -> io::Result<()> { fn alloc_ids_from_alloc( alloc: ConstAllocation<'_>, @@ -737,7 +1333,7 @@ pub fn write_allocations<'tcx>( let mut todo: Vec<_> = seen.iter().copied().collect(); while let Some(id) = todo.pop() { let mut write_allocation_track_relocs = - |w: &mut dyn Write, alloc: ConstAllocation<'tcx>| -> io::Result<()> { + |w: &mut dyn io::Write, alloc: ConstAllocation<'tcx>| -> io::Result<()> { // `.rev()` because we are popping them from the back of the `todo` vector. for id in alloc_ids_from_alloc(alloc).rev() { if seen.insert(id) { @@ -998,90 +1594,8 @@ pub fn write_allocation_bytes<'tcx, Prov: Provenance, Extra, Bytes: AllocBytes>( Ok(()) } -fn write_mir_sig(tcx: TyCtxt<'_>, body: &Body<'_>, w: &mut dyn Write) -> io::Result<()> { - use rustc_hir::def::DefKind; - - trace!("write_mir_sig: {:?}", body.source.instance); - let def_id = body.source.def_id(); - let kind = tcx.def_kind(def_id); - let is_function = match kind { - DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(..) => true, - _ => tcx.is_closure(def_id), - }; - match (kind, body.source.promoted) { - (_, Some(i)) => write!(w, "{i:?} in ")?, - (DefKind::Const | DefKind::AssocConst, _) => write!(w, "const ")?, - (DefKind::Static(hir::Mutability::Not), _) => write!(w, "static ")?, - (DefKind::Static(hir::Mutability::Mut), _) => write!(w, "static mut ")?, - (_, _) if is_function => write!(w, "fn ")?, - (DefKind::AnonConst | DefKind::InlineConst, _) => {} // things like anon const, not an item - _ => bug!("Unexpected def kind {:?}", kind), - } - - ty::print::with_forced_impl_filename_line! { - // see notes on #41697 elsewhere - write!(w, "{}", tcx.def_path_str(def_id))? - } - - if body.source.promoted.is_none() && is_function { - write!(w, "(")?; - - // fn argument types. - for (i, arg) in body.args_iter().enumerate() { - if i != 0 { - write!(w, ", ")?; - } - write!(w, "{:?}: {}", Place::from(arg), body.local_decls[arg].ty)?; - } - - write!(w, ") -> {}", body.return_ty())?; - } else { - assert_eq!(body.arg_count, 0); - write!(w, ": {} =", body.return_ty())?; - } - - if let Some(yield_ty) = body.yield_ty() { - writeln!(w)?; - writeln!(w, "yields {yield_ty}")?; - } - - write!(w, " ")?; - // Next thing that gets printed is the opening { - - Ok(()) -} - -fn write_user_type_annotations( - tcx: TyCtxt<'_>, - body: &Body<'_>, - w: &mut dyn Write, -) -> io::Result<()> { - if !body.user_type_annotations.is_empty() { - writeln!(w, "| User Type Annotations")?; - } - for (index, annotation) in body.user_type_annotations.iter_enumerated() { - writeln!( - w, - "| {:?}: user_ty: {}, span: {}, inferred_ty: {}", - index.index(), - annotation.user_ty, - tcx.sess.source_map().span_to_embeddable_string(annotation.span), - with_no_trimmed_paths!(format!("{}", annotation.inferred_ty)), - )?; - } - if !body.user_type_annotations.is_empty() { - writeln!(w, "|")?; - } - Ok(()) -} - -pub fn dump_mir_def_ids(tcx: TyCtxt<'_>, single: Option) -> Vec { - if let Some(i) = single { - vec![i] - } else { - tcx.mir_keys(()).iter().map(|def_id| def_id.to_def_id()).collect() - } -} +/////////////////////////////////////////////////////////////////////////// +// Constants fn pretty_print_byte_str(fmt: &mut Formatter<'_>, byte_str: &[u8]) -> fmt::Result { write!(fmt, "b\"{}\"", byte_str.escape_ascii()) @@ -1244,6 +1758,9 @@ pub(crate) fn pretty_print_const_value<'tcx>( }) } +/////////////////////////////////////////////////////////////////////////// +// Miscellaneous + /// Calc converted u64 decimal into hex and return it's length in chars /// /// ```ignore (cannot-test-private-function) diff --git a/compiler/rustc_middle/src/mir/statement.rs b/compiler/rustc_middle/src/mir/statement.rs new file mode 100644 index 000000000000..25534f469604 --- /dev/null +++ b/compiler/rustc_middle/src/mir/statement.rs @@ -0,0 +1,441 @@ +/// Functionality for statements, operands, places, and things that appear in them. +use super::*; + +/////////////////////////////////////////////////////////////////////////// +// Statements + +/// A statement in a basic block, including information about its source code. +#[derive(Clone, TyEncodable, TyDecodable, HashStable, TypeFoldable, TypeVisitable)] +pub struct Statement<'tcx> { + pub source_info: SourceInfo, + pub kind: StatementKind<'tcx>, +} + +impl Statement<'_> { + /// Changes a statement to a nop. This is both faster than deleting instructions and avoids + /// invalidating statement indices in `Location`s. + pub fn make_nop(&mut self) { + self.kind = StatementKind::Nop + } + + /// Changes a statement to a nop and returns the original statement. + #[must_use = "If you don't need the statement, use `make_nop` instead"] + pub fn replace_nop(&mut self) -> Self { + Statement { + source_info: self.source_info, + kind: mem::replace(&mut self.kind, StatementKind::Nop), + } + } +} + +impl<'tcx> StatementKind<'tcx> { + pub fn as_assign_mut(&mut self) -> Option<&mut (Place<'tcx>, Rvalue<'tcx>)> { + match self { + StatementKind::Assign(x) => Some(x), + _ => None, + } + } + + pub fn as_assign(&self) -> Option<&(Place<'tcx>, Rvalue<'tcx>)> { + match self { + StatementKind::Assign(x) => Some(x), + _ => None, + } + } +} + +/////////////////////////////////////////////////////////////////////////// +// Places + +impl ProjectionElem { + /// Returns `true` if the target of this projection may refer to a different region of memory + /// than the base. + fn is_indirect(&self) -> bool { + match self { + Self::Deref => true, + + Self::Field(_, _) + | Self::Index(_) + | Self::OpaqueCast(_) + | Self::ConstantIndex { .. } + | Self::Subslice { .. } + | Self::Downcast(_, _) => false, + } + } + + /// Returns `true` if the target of this projection always refers to the same memory region + /// whatever the state of the program. + pub fn is_stable_offset(&self) -> bool { + match self { + Self::Deref | Self::Index(_) => false, + Self::Field(_, _) + | Self::OpaqueCast(_) + | Self::ConstantIndex { .. } + | Self::Subslice { .. } + | Self::Downcast(_, _) => true, + } + } + + /// Returns `true` if this is a `Downcast` projection with the given `VariantIdx`. + pub fn is_downcast_to(&self, v: VariantIdx) -> bool { + matches!(*self, Self::Downcast(_, x) if x == v) + } + + /// Returns `true` if this is a `Field` projection with the given index. + pub fn is_field_to(&self, f: FieldIdx) -> bool { + matches!(*self, Self::Field(x, _) if x == f) + } + + /// Returns `true` if this is accepted inside `VarDebugInfoContents::Place`. + pub fn can_use_in_debuginfo(&self) -> bool { + match self { + Self::ConstantIndex { from_end: false, .. } + | Self::Deref + | Self::Downcast(_, _) + | Self::Field(_, _) => true, + Self::ConstantIndex { from_end: true, .. } + | Self::Index(_) + | Self::OpaqueCast(_) + | Self::Subslice { .. } => false, + } + } +} + +/// Alias for projections as they appear in `UserTypeProjection`, where we +/// need neither the `V` parameter for `Index` nor the `T` for `Field`. +pub type ProjectionKind = ProjectionElem<(), ()>; + +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct PlaceRef<'tcx> { + pub local: Local, + pub projection: &'tcx [PlaceElem<'tcx>], +} + +// Once we stop implementing `Ord` for `DefId`, +// this impl will be unnecessary. Until then, we'll +// leave this impl in place to prevent re-adding a +// dependency on the `Ord` impl for `DefId` +impl<'tcx> !PartialOrd for PlaceRef<'tcx> {} + +impl<'tcx> Place<'tcx> { + // FIXME change this to a const fn by also making List::empty a const fn. + pub fn return_place() -> Place<'tcx> { + Place { local: RETURN_PLACE, projection: List::empty() } + } + + /// Returns `true` if this `Place` contains a `Deref` projection. + /// + /// If `Place::is_indirect` returns false, the caller knows that the `Place` refers to the + /// same region of memory as its base. + pub fn is_indirect(&self) -> bool { + self.projection.iter().any(|elem| elem.is_indirect()) + } + + /// Returns `true` if this `Place`'s first projection is `Deref`. + /// + /// This is useful because for MIR phases `AnalysisPhase::PostCleanup` and later, + /// `Deref` projections can only occur as the first projection. In that case this method + /// is equivalent to `is_indirect`, but faster. + pub fn is_indirect_first_projection(&self) -> bool { + self.as_ref().is_indirect_first_projection() + } + + /// Finds the innermost `Local` from this `Place`, *if* it is either a local itself or + /// a single deref of a local. + #[inline(always)] + pub fn local_or_deref_local(&self) -> Option { + self.as_ref().local_or_deref_local() + } + + /// If this place represents a local variable like `_X` with no + /// projections, return `Some(_X)`. + #[inline(always)] + pub fn as_local(&self) -> Option { + self.as_ref().as_local() + } + + #[inline] + pub fn as_ref(&self) -> PlaceRef<'tcx> { + PlaceRef { local: self.local, projection: &self.projection } + } + + /// Iterate over the projections in evaluation order, i.e., the first element is the base with + /// its projection and then subsequently more projections are added. + /// As a concrete example, given the place a.b.c, this would yield: + /// - (a, .b) + /// - (a.b, .c) + /// + /// Given a place without projections, the iterator is empty. + #[inline] + pub fn iter_projections( + self, + ) -> impl Iterator, PlaceElem<'tcx>)> + DoubleEndedIterator { + self.as_ref().iter_projections() + } + + /// Generates a new place by appending `more_projections` to the existing ones + /// and interning the result. + pub fn project_deeper(self, more_projections: &[PlaceElem<'tcx>], tcx: TyCtxt<'tcx>) -> Self { + if more_projections.is_empty() { + return self; + } + + self.as_ref().project_deeper(more_projections, tcx) + } +} + +impl From for Place<'_> { + #[inline] + fn from(local: Local) -> Self { + Place { local, projection: List::empty() } + } +} + +impl<'tcx> PlaceRef<'tcx> { + /// Finds the innermost `Local` from this `Place`, *if* it is either a local itself or + /// a single deref of a local. + pub fn local_or_deref_local(&self) -> Option { + match *self { + PlaceRef { local, projection: [] } + | PlaceRef { local, projection: [ProjectionElem::Deref] } => Some(local), + _ => None, + } + } + + /// Returns `true` if this `Place` contains a `Deref` projection. + /// + /// If `Place::is_indirect` returns false, the caller knows that the `Place` refers to the + /// same region of memory as its base. + pub fn is_indirect(&self) -> bool { + self.projection.iter().any(|elem| elem.is_indirect()) + } + + /// Returns `true` if this `Place`'s first projection is `Deref`. + /// + /// This is useful because for MIR phases `AnalysisPhase::PostCleanup` and later, + /// `Deref` projections can only occur as the first projection. In that case this method + /// is equivalent to `is_indirect`, but faster. + pub fn is_indirect_first_projection(&self) -> bool { + // To make sure this is not accidentally used in wrong mir phase + debug_assert!( + self.projection.is_empty() || !self.projection[1..].contains(&PlaceElem::Deref) + ); + self.projection.first() == Some(&PlaceElem::Deref) + } + + /// If this place represents a local variable like `_X` with no + /// projections, return `Some(_X)`. + #[inline] + pub fn as_local(&self) -> Option { + match *self { + PlaceRef { local, projection: [] } => Some(local), + _ => None, + } + } + + #[inline] + pub fn last_projection(&self) -> Option<(PlaceRef<'tcx>, PlaceElem<'tcx>)> { + if let &[ref proj_base @ .., elem] = self.projection { + Some((PlaceRef { local: self.local, projection: proj_base }, elem)) + } else { + None + } + } + + /// Iterate over the projections in evaluation order, i.e., the first element is the base with + /// its projection and then subsequently more projections are added. + /// As a concrete example, given the place a.b.c, this would yield: + /// - (a, .b) + /// - (a.b, .c) + /// + /// Given a place without projections, the iterator is empty. + #[inline] + pub fn iter_projections( + self, + ) -> impl Iterator, PlaceElem<'tcx>)> + DoubleEndedIterator { + self.projection.iter().enumerate().map(move |(i, proj)| { + let base = PlaceRef { local: self.local, projection: &self.projection[..i] }; + (base, *proj) + }) + } + + /// Generates a new place by appending `more_projections` to the existing ones + /// and interning the result. + pub fn project_deeper( + self, + more_projections: &[PlaceElem<'tcx>], + tcx: TyCtxt<'tcx>, + ) -> Place<'tcx> { + let mut v: Vec>; + + let new_projections = if self.projection.is_empty() { + more_projections + } else { + v = Vec::with_capacity(self.projection.len() + more_projections.len()); + v.extend(self.projection); + v.extend(more_projections); + &v + }; + + Place { local: self.local, projection: tcx.mk_place_elems(new_projections) } + } +} + +impl From for PlaceRef<'_> { + #[inline] + fn from(local: Local) -> Self { + PlaceRef { local, projection: &[] } + } +} + +/////////////////////////////////////////////////////////////////////////// +// Operands + +impl<'tcx> Operand<'tcx> { + /// Convenience helper to make a constant that refers to the fn + /// with given `DefId` and args. Since this is used to synthesize + /// MIR, assumes `user_ty` is None. + pub fn function_handle( + tcx: TyCtxt<'tcx>, + def_id: DefId, + args: impl IntoIterator>, + span: Span, + ) -> Self { + let ty = Ty::new_fn_def(tcx, def_id, args); + Operand::Constant(Box::new(Constant { + span, + user_ty: None, + literal: ConstantKind::Val(ConstValue::ZeroSized, ty), + })) + } + + pub fn is_move(&self) -> bool { + matches!(self, Operand::Move(..)) + } + + /// Convenience helper to make a literal-like constant from a given scalar value. + /// Since this is used to synthesize MIR, assumes `user_ty` is None. + pub fn const_from_scalar( + tcx: TyCtxt<'tcx>, + ty: Ty<'tcx>, + val: Scalar, + span: Span, + ) -> Operand<'tcx> { + debug_assert!({ + let param_env_and_ty = ty::ParamEnv::empty().and(ty); + let type_size = tcx + .layout_of(param_env_and_ty) + .unwrap_or_else(|e| panic!("could not compute layout for {ty:?}: {e:?}")) + .size; + let scalar_size = match val { + Scalar::Int(int) => int.size(), + _ => panic!("Invalid scalar type {val:?}"), + }; + scalar_size == type_size + }); + Operand::Constant(Box::new(Constant { + span, + user_ty: None, + literal: ConstantKind::Val(ConstValue::Scalar(val), ty), + })) + } + + pub fn to_copy(&self) -> Self { + match *self { + Operand::Copy(_) | Operand::Constant(_) => self.clone(), + Operand::Move(place) => Operand::Copy(place), + } + } + + /// Returns the `Place` that is the target of this `Operand`, or `None` if this `Operand` is a + /// constant. + pub fn place(&self) -> Option> { + match self { + Operand::Copy(place) | Operand::Move(place) => Some(*place), + Operand::Constant(_) => None, + } + } + + /// Returns the `Constant` that is the target of this `Operand`, or `None` if this `Operand` is a + /// place. + pub fn constant(&self) -> Option<&Constant<'tcx>> { + match self { + Operand::Constant(x) => Some(&**x), + Operand::Copy(_) | Operand::Move(_) => None, + } + } + + /// Gets the `ty::FnDef` from an operand if it's a constant function item. + /// + /// While this is unlikely in general, it's the normal case of what you'll + /// find as the `func` in a [`TerminatorKind::Call`]. + pub fn const_fn_def(&self) -> Option<(DefId, GenericArgsRef<'tcx>)> { + let const_ty = self.constant()?.literal.ty(); + if let ty::FnDef(def_id, args) = *const_ty.kind() { Some((def_id, args)) } else { None } + } +} + +/////////////////////////////////////////////////////////////////////////// +/// Rvalues + +impl<'tcx> Rvalue<'tcx> { + /// Returns true if rvalue can be safely removed when the result is unused. + #[inline] + pub fn is_safe_to_remove(&self) -> bool { + match self { + // Pointer to int casts may be side-effects due to exposing the provenance. + // While the model is undecided, we should be conservative. See + // + Rvalue::Cast(CastKind::PointerExposeAddress, _, _) => false, + + Rvalue::Use(_) + | Rvalue::CopyForDeref(_) + | Rvalue::Repeat(_, _) + | Rvalue::Ref(_, _, _) + | Rvalue::ThreadLocalRef(_) + | Rvalue::AddressOf(_, _) + | Rvalue::Len(_) + | Rvalue::Cast( + CastKind::IntToInt + | CastKind::FloatToInt + | CastKind::FloatToFloat + | CastKind::IntToFloat + | CastKind::FnPtrToPtr + | CastKind::PtrToPtr + | CastKind::PointerCoercion(_) + | CastKind::PointerFromExposedAddress + | CastKind::DynStar + | CastKind::Transmute, + _, + _, + ) + | Rvalue::BinaryOp(_, _) + | Rvalue::CheckedBinaryOp(_, _) + | Rvalue::NullaryOp(_, _) + | Rvalue::UnaryOp(_, _) + | Rvalue::Discriminant(_) + | Rvalue::Aggregate(_, _) + | Rvalue::ShallowInitBox(_, _) => true, + } + } +} + +impl BorrowKind { + pub fn mutability(&self) -> Mutability { + match *self { + BorrowKind::Shared | BorrowKind::Shallow => Mutability::Not, + BorrowKind::Mut { .. } => Mutability::Mut, + } + } + + pub fn allows_two_phase_borrow(&self) -> bool { + match *self { + BorrowKind::Shared + | BorrowKind::Shallow + | BorrowKind::Mut { kind: MutBorrowKind::Default | MutBorrowKind::ClosureCapture } => { + false + } + BorrowKind::Mut { kind: MutBorrowKind::TwoPhaseBorrow } => true, + } + } +} diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index 79b64a491f4e..f99084d0eb7a 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -3,7 +3,7 @@ //! This is in a dedicated file so that changes to this file can be reviewed more carefully. //! The intention is that this file only contains datatype declarations, no code. -use super::{BasicBlock, Constant, Local, SwitchTargets, UserTypeProjection}; +use super::{BasicBlock, Constant, Local, UserTypeProjection}; use crate::mir::coverage::{CodeRegion, CoverageKind}; use crate::traits::Reveal; @@ -24,6 +24,7 @@ use rustc_span::def_id::LocalDefId; use rustc_span::symbol::Symbol; use rustc_span::Span; use rustc_target::asm::InlineAsmRegOrRegClass; +use smallvec::SmallVec; /// Represents the "flavors" of MIR. /// @@ -828,6 +829,27 @@ impl TerminatorKind<'_> { } } +#[derive(Debug, Clone, TyEncodable, TyDecodable, Hash, HashStable, PartialEq)] +pub struct SwitchTargets { + /// Possible values. The locations to branch to in each case + /// are found in the corresponding indices from the `targets` vector. + pub(super) values: SmallVec<[u128; 1]>, + + /// Possible branch sites. The last element of this vector is used + /// for the otherwise branch, so targets.len() == values.len() + 1 + /// should hold. + // + // This invariant is quite non-obvious and also could be improved. + // One way to make this invariant is to have something like this instead: + // + // branches: Vec<(ConstInt, BasicBlock)>, + // otherwise: Option // exhaustive if None + // + // However we’ve decided to keep this as-is until we figure a case + // where some other approach seems to be strictly better than other. + pub(super) targets: SmallVec<[BasicBlock; 2]>, +} + /// Action to be taken when a stack unwind happens. #[derive(Copy, Clone, Debug, PartialEq, Eq, TyEncodable, TyDecodable, Hash, HashStable)] #[derive(TypeFoldable, TypeVisitable)] diff --git a/compiler/rustc_middle/src/mir/terminator.rs b/compiler/rustc_middle/src/mir/terminator.rs index 7eddf13b3fb5..02aab4a892d0 100644 --- a/compiler/rustc_middle/src/mir/terminator.rs +++ b/compiler/rustc_middle/src/mir/terminator.rs @@ -1,39 +1,16 @@ +/// Functionality for terminators and helper types that appear in terminators. use rustc_hir::LangItem; use smallvec::SmallVec; use super::{BasicBlock, InlineAsmOperand, Operand, SourceInfo, TerminatorKind, UnwindAction}; -use rustc_ast::InlineAsmTemplatePiece; pub use rustc_ast::Mutability; use rustc_macros::HashStable; -use std::borrow::Cow; -use std::fmt::{self, Debug, Formatter, Write}; use std::iter; use std::slice; pub use super::query::*; use super::*; -#[derive(Debug, Clone, TyEncodable, TyDecodable, Hash, HashStable, PartialEq)] -pub struct SwitchTargets { - /// Possible values. The locations to branch to in each case - /// are found in the corresponding indices from the `targets` vector. - values: SmallVec<[u128; 1]>, - - /// Possible branch sites. The last element of this vector is used - /// for the otherwise branch, so targets.len() == values.len() + 1 - /// should hold. - // - // This invariant is quite non-obvious and also could be improved. - // One way to make this invariant is to have something like this instead: - // - // branches: Vec<(ConstInt, BasicBlock)>, - // otherwise: Option // exhaustive if None - // - // However we’ve decided to keep this as-is until we figure a case - // where some other approach seems to be strictly better than other. - targets: SmallVec<[BasicBlock; 2]>, -} - impl SwitchTargets { /// Creates switch targets from an iterator of values and target blocks. /// @@ -135,6 +112,168 @@ impl UnwindTerminateReason { } } +impl AssertKind { + /// Returns true if this an overflow checking assertion controlled by -C overflow-checks. + pub fn is_optional_overflow_check(&self) -> bool { + use AssertKind::*; + use BinOp::*; + matches!(self, OverflowNeg(..) | Overflow(Add | Sub | Mul | Shl | Shr, ..)) + } + + /// Get the message that is printed at runtime when this assertion fails. + /// + /// The caller is expected to handle `BoundsCheck` and `MisalignedPointerDereference` by + /// invoking the appropriate lang item (panic_bounds_check/panic_misaligned_pointer_dereference) + /// instead of printing a static message. + pub fn description(&self) -> &'static str { + use AssertKind::*; + match self { + Overflow(BinOp::Add, _, _) => "attempt to add with overflow", + Overflow(BinOp::Sub, _, _) => "attempt to subtract with overflow", + Overflow(BinOp::Mul, _, _) => "attempt to multiply with overflow", + Overflow(BinOp::Div, _, _) => "attempt to divide with overflow", + Overflow(BinOp::Rem, _, _) => "attempt to calculate the remainder with overflow", + OverflowNeg(_) => "attempt to negate with overflow", + Overflow(BinOp::Shr, _, _) => "attempt to shift right with overflow", + Overflow(BinOp::Shl, _, _) => "attempt to shift left with overflow", + Overflow(op, _, _) => bug!("{:?} cannot overflow", op), + DivisionByZero(_) => "attempt to divide by zero", + RemainderByZero(_) => "attempt to calculate the remainder with a divisor of zero", + ResumedAfterReturn(GeneratorKind::Gen) => "generator resumed after completion", + ResumedAfterReturn(GeneratorKind::Async(_)) => "`async fn` resumed after completion", + ResumedAfterPanic(GeneratorKind::Gen) => "generator resumed after panicking", + ResumedAfterPanic(GeneratorKind::Async(_)) => "`async fn` resumed after panicking", + BoundsCheck { .. } | MisalignedPointerDereference { .. } => { + bug!("Unexpected AssertKind") + } + } + } + + /// Format the message arguments for the `assert(cond, msg..)` terminator in MIR printing. + /// + /// Needs to be kept in sync with the run-time behavior (which is defined by + /// `AssertKind::description` and the lang items mentioned in its docs). + /// Note that we deliberately show more details here than we do at runtime, such as the actual + /// numbers that overflowed -- it is much easier to do so here than at runtime. + pub fn fmt_assert_args(&self, f: &mut W) -> fmt::Result + where + O: Debug, + { + use AssertKind::*; + match self { + BoundsCheck { ref len, ref index } => write!( + f, + "\"index out of bounds: the length is {{}} but the index is {{}}\", {len:?}, {index:?}" + ), + + OverflowNeg(op) => { + write!(f, "\"attempt to negate `{{}}`, which would overflow\", {op:?}") + } + DivisionByZero(op) => write!(f, "\"attempt to divide `{{}}` by zero\", {op:?}"), + RemainderByZero(op) => write!( + f, + "\"attempt to calculate the remainder of `{{}}` with a divisor of zero\", {op:?}" + ), + Overflow(BinOp::Add, l, r) => write!( + f, + "\"attempt to compute `{{}} + {{}}`, which would overflow\", {l:?}, {r:?}" + ), + Overflow(BinOp::Sub, l, r) => write!( + f, + "\"attempt to compute `{{}} - {{}}`, which would overflow\", {l:?}, {r:?}" + ), + Overflow(BinOp::Mul, l, r) => write!( + f, + "\"attempt to compute `{{}} * {{}}`, which would overflow\", {l:?}, {r:?}" + ), + Overflow(BinOp::Div, l, r) => write!( + f, + "\"attempt to compute `{{}} / {{}}`, which would overflow\", {l:?}, {r:?}" + ), + Overflow(BinOp::Rem, l, r) => write!( + f, + "\"attempt to compute the remainder of `{{}} % {{}}`, which would overflow\", {l:?}, {r:?}" + ), + Overflow(BinOp::Shr, _, r) => { + write!(f, "\"attempt to shift right by `{{}}`, which would overflow\", {r:?}") + } + Overflow(BinOp::Shl, _, r) => { + write!(f, "\"attempt to shift left by `{{}}`, which would overflow\", {r:?}") + } + MisalignedPointerDereference { required, found } => { + write!( + f, + "\"misaligned pointer dereference: address must be a multiple of {{}} but is {{}}\", {required:?}, {found:?}" + ) + } + _ => write!(f, "\"{}\"", self.description()), + } + } + + /// Format the diagnostic message for use in a lint (e.g. when the assertion fails during const-eval). + /// + /// Needs to be kept in sync with the run-time behavior (which is defined by + /// `AssertKind::description` and the lang items mentioned in its docs). + /// Note that we deliberately show more details here than we do at runtime, such as the actual + /// numbers that overflowed -- it is much easier to do so here than at runtime. + pub fn diagnostic_message(&self) -> DiagnosticMessage { + use crate::fluent_generated::*; + use AssertKind::*; + + match self { + BoundsCheck { .. } => middle_bounds_check, + Overflow(BinOp::Shl, _, _) => middle_assert_shl_overflow, + Overflow(BinOp::Shr, _, _) => middle_assert_shr_overflow, + Overflow(_, _, _) => middle_assert_op_overflow, + OverflowNeg(_) => middle_assert_overflow_neg, + DivisionByZero(_) => middle_assert_divide_by_zero, + RemainderByZero(_) => middle_assert_remainder_by_zero, + ResumedAfterReturn(GeneratorKind::Async(_)) => middle_assert_async_resume_after_return, + ResumedAfterReturn(GeneratorKind::Gen) => middle_assert_generator_resume_after_return, + ResumedAfterPanic(GeneratorKind::Async(_)) => middle_assert_async_resume_after_panic, + ResumedAfterPanic(GeneratorKind::Gen) => middle_assert_generator_resume_after_panic, + + MisalignedPointerDereference { .. } => middle_assert_misaligned_ptr_deref, + } + } + + pub fn add_args(self, adder: &mut dyn FnMut(Cow<'static, str>, DiagnosticArgValue<'static>)) + where + O: fmt::Debug, + { + use AssertKind::*; + + macro_rules! add { + ($name: expr, $value: expr) => { + adder($name.into(), $value.into_diagnostic_arg()); + }; + } + + match self { + BoundsCheck { len, index } => { + add!("len", format!("{len:?}")); + add!("index", format!("{index:?}")); + } + Overflow(BinOp::Shl | BinOp::Shr, _, val) + | DivisionByZero(val) + | RemainderByZero(val) + | OverflowNeg(val) => { + add!("val", format!("{val:#?}")); + } + Overflow(binop, left, right) => { + add!("op", binop.to_hir_binop().as_str()); + add!("left", format!("{left:#?}")); + add!("right", format!("{right:#?}")); + } + ResumedAfterReturn(_) | ResumedAfterPanic(_) => {} + MisalignedPointerDereference { required, found } => { + add!("required", format!("{required:#?}")); + add!("found", format!("{found:#?}")); + } + } + } +} + #[derive(Clone, Debug, TyEncodable, TyDecodable, HashStable, TypeFoldable, TypeVisitable)] pub struct Terminator<'tcx> { pub source_info: SourceInfo, @@ -299,187 +438,6 @@ impl<'tcx> TerminatorKind<'tcx> { } } -impl<'tcx> Debug for TerminatorKind<'tcx> { - fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { - self.fmt_head(fmt)?; - let successor_count = self.successors().count(); - let labels = self.fmt_successor_labels(); - assert_eq!(successor_count, labels.len()); - - // `Cleanup` is already included in successors - let show_unwind = !matches!(self.unwind(), None | Some(UnwindAction::Cleanup(_))); - let fmt_unwind = |fmt: &mut Formatter<'_>| -> fmt::Result { - write!(fmt, "unwind ")?; - match self.unwind() { - // Not needed or included in successors - None | Some(UnwindAction::Cleanup(_)) => unreachable!(), - Some(UnwindAction::Continue) => write!(fmt, "continue"), - Some(UnwindAction::Unreachable) => write!(fmt, "unreachable"), - Some(UnwindAction::Terminate(reason)) => { - write!(fmt, "terminate({})", reason.as_short_str()) - } - } - }; - - match (successor_count, show_unwind) { - (0, false) => Ok(()), - (0, true) => { - write!(fmt, " -> ")?; - fmt_unwind(fmt) - } - (1, false) => write!(fmt, " -> {:?}", self.successors().next().unwrap()), - _ => { - write!(fmt, " -> [")?; - for (i, target) in self.successors().enumerate() { - if i > 0 { - write!(fmt, ", ")?; - } - write!(fmt, "{}: {:?}", labels[i], target)?; - } - if show_unwind { - write!(fmt, ", ")?; - fmt_unwind(fmt)?; - } - write!(fmt, "]") - } - } - } -} - -impl<'tcx> TerminatorKind<'tcx> { - /// Writes the "head" part of the terminator; that is, its name and the data it uses to pick the - /// successor basic block, if any. The only information not included is the list of possible - /// successors, which may be rendered differently between the text and the graphviz format. - pub fn fmt_head(&self, fmt: &mut W) -> fmt::Result { - use self::TerminatorKind::*; - match self { - Goto { .. } => write!(fmt, "goto"), - SwitchInt { discr, .. } => write!(fmt, "switchInt({discr:?})"), - Return => write!(fmt, "return"), - GeneratorDrop => write!(fmt, "generator_drop"), - UnwindResume => write!(fmt, "resume"), - UnwindTerminate(reason) => { - write!(fmt, "abort({})", reason.as_short_str()) - } - Yield { value, resume_arg, .. } => write!(fmt, "{resume_arg:?} = yield({value:?})"), - Unreachable => write!(fmt, "unreachable"), - Drop { place, .. } => write!(fmt, "drop({place:?})"), - Call { func, args, destination, .. } => { - write!(fmt, "{destination:?} = ")?; - write!(fmt, "{func:?}(")?; - for (index, arg) in args.iter().enumerate() { - if index > 0 { - write!(fmt, ", ")?; - } - write!(fmt, "{arg:?}")?; - } - write!(fmt, ")") - } - Assert { cond, expected, msg, .. } => { - write!(fmt, "assert(")?; - if !expected { - write!(fmt, "!")?; - } - write!(fmt, "{cond:?}, ")?; - msg.fmt_assert_args(fmt)?; - write!(fmt, ")") - } - FalseEdge { .. } => write!(fmt, "falseEdge"), - FalseUnwind { .. } => write!(fmt, "falseUnwind"), - InlineAsm { template, ref operands, options, .. } => { - write!(fmt, "asm!(\"{}\"", InlineAsmTemplatePiece::to_string(template))?; - for op in operands { - write!(fmt, ", ")?; - let print_late = |&late| if late { "late" } else { "" }; - match op { - InlineAsmOperand::In { reg, value } => { - write!(fmt, "in({reg}) {value:?}")?; - } - InlineAsmOperand::Out { reg, late, place: Some(place) } => { - write!(fmt, "{}out({}) {:?}", print_late(late), reg, place)?; - } - InlineAsmOperand::Out { reg, late, place: None } => { - write!(fmt, "{}out({}) _", print_late(late), reg)?; - } - InlineAsmOperand::InOut { - reg, - late, - in_value, - out_place: Some(out_place), - } => { - write!( - fmt, - "in{}out({}) {:?} => {:?}", - print_late(late), - reg, - in_value, - out_place - )?; - } - InlineAsmOperand::InOut { reg, late, in_value, out_place: None } => { - write!(fmt, "in{}out({}) {:?} => _", print_late(late), reg, in_value)?; - } - InlineAsmOperand::Const { value } => { - write!(fmt, "const {value:?}")?; - } - InlineAsmOperand::SymFn { value } => { - write!(fmt, "sym_fn {value:?}")?; - } - InlineAsmOperand::SymStatic { def_id } => { - write!(fmt, "sym_static {def_id:?}")?; - } - } - } - write!(fmt, ", options({options:?}))") - } - } - } - - /// Returns the list of labels for the edges to the successor basic blocks. - pub fn fmt_successor_labels(&self) -> Vec> { - use self::TerminatorKind::*; - match *self { - Return | UnwindResume | UnwindTerminate(_) | Unreachable | GeneratorDrop => vec![], - Goto { .. } => vec!["".into()], - SwitchInt { ref targets, .. } => targets - .values - .iter() - .map(|&u| Cow::Owned(u.to_string())) - .chain(iter::once("otherwise".into())) - .collect(), - Call { target: Some(_), unwind: UnwindAction::Cleanup(_), .. } => { - vec!["return".into(), "unwind".into()] - } - Call { target: Some(_), unwind: _, .. } => vec!["return".into()], - Call { target: None, unwind: UnwindAction::Cleanup(_), .. } => vec!["unwind".into()], - Call { target: None, unwind: _, .. } => vec![], - Yield { drop: Some(_), .. } => vec!["resume".into(), "drop".into()], - Yield { drop: None, .. } => vec!["resume".into()], - Drop { unwind: UnwindAction::Cleanup(_), .. } => vec!["return".into(), "unwind".into()], - Drop { unwind: _, .. } => vec!["return".into()], - Assert { unwind: UnwindAction::Cleanup(_), .. } => { - vec!["success".into(), "unwind".into()] - } - Assert { unwind: _, .. } => vec!["success".into()], - FalseEdge { .. } => vec!["real".into(), "imaginary".into()], - FalseUnwind { unwind: UnwindAction::Cleanup(_), .. } => { - vec!["real".into(), "unwind".into()] - } - FalseUnwind { unwind: _, .. } => vec!["real".into()], - InlineAsm { destination: Some(_), unwind: UnwindAction::Cleanup(_), .. } => { - vec!["return".into(), "unwind".into()] - } - InlineAsm { destination: Some(_), unwind: _, .. } => { - vec!["return".into()] - } - InlineAsm { destination: None, unwind: UnwindAction::Cleanup(_), .. } => { - vec!["unwind".into()] - } - InlineAsm { destination: None, unwind: _, .. } => vec![], - } - } -} - #[derive(Copy, Clone, Debug)] pub enum TerminatorEdges<'mir, 'tcx> { /// For terminators that have no successor, like `return`.