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. /// /// The iterator may be empty, in which case the `SwitchInt` instruction is equivalent to /// `goto otherwise;`. pub fn new(targets: impl Iterator, otherwise: BasicBlock) -> Self { let (values, mut targets): (SmallVec<_>, SmallVec<_>) = targets.unzip(); targets.push(otherwise); Self { values, targets } } /// Builds a switch targets definition that jumps to `then` if the tested value equals `value`, /// and to `else_` if not. pub fn static_if(value: u128, then: BasicBlock, else_: BasicBlock) -> Self { Self { values: smallvec![value], targets: smallvec![then, else_] } } /// Returns the fallback target that is jumped to when none of the values match the operand. pub fn otherwise(&self) -> BasicBlock { *self.targets.last().unwrap() } /// Returns an iterator over the switch targets. /// /// The iterator will yield tuples containing the value and corresponding target to jump to, not /// including the `otherwise` fallback target. /// /// Note that this may yield 0 elements. Only the `otherwise` branch is mandatory. pub fn iter(&self) -> SwitchTargetsIter<'_> { SwitchTargetsIter { inner: iter::zip(&self.values, &self.targets) } } /// Returns a slice with all possible jump targets (including the fallback target). pub fn all_targets(&self) -> &[BasicBlock] { &self.targets } pub fn all_targets_mut(&mut self) -> &mut [BasicBlock] { &mut self.targets } /// Finds the `BasicBlock` to which this `SwitchInt` will branch given the /// specific value. This cannot fail, as it'll return the `otherwise` /// branch if there's not a specific match for the value. pub fn target_for_value(&self, value: u128) -> BasicBlock { self.iter().find_map(|(v, t)| (v == value).then_some(t)).unwrap_or_else(|| self.otherwise()) } } pub struct SwitchTargetsIter<'a> { inner: iter::Zip, slice::Iter<'a, BasicBlock>>, } impl<'a> Iterator for SwitchTargetsIter<'a> { type Item = (u128, BasicBlock); fn next(&mut self) -> Option { self.inner.next().map(|(val, bb)| (*val, *bb)) } fn size_hint(&self) -> (usize, Option) { self.inner.size_hint() } } impl<'a> ExactSizeIterator for SwitchTargetsIter<'a> {} #[derive(Clone, Debug, TyEncodable, TyDecodable, HashStable, TypeFoldable, TypeVisitable)] pub struct Terminator<'tcx> { pub source_info: SourceInfo, pub kind: TerminatorKind<'tcx>, } pub type Successors<'a> = impl DoubleEndedIterator + 'a; pub type SuccessorsMut<'a> = iter::Chain, slice::IterMut<'a, BasicBlock>>; impl<'tcx> Terminator<'tcx> { pub fn successors(&self) -> Successors<'_> { self.kind.successors() } pub fn successors_mut(&mut self) -> SuccessorsMut<'_> { self.kind.successors_mut() } pub fn unwind(&self) -> Option<&UnwindAction> { self.kind.unwind() } pub fn unwind_mut(&mut self) -> Option<&mut UnwindAction> { self.kind.unwind_mut() } } impl<'tcx> TerminatorKind<'tcx> { pub fn if_(cond: Operand<'tcx>, t: BasicBlock, f: BasicBlock) -> TerminatorKind<'tcx> { TerminatorKind::SwitchInt { discr: cond, targets: SwitchTargets::static_if(0, f, t) } } pub fn successors(&self) -> Successors<'_> { use self::TerminatorKind::*; match *self { Call { target: Some(t), unwind: UnwindAction::Cleanup(ref u), .. } | Yield { resume: t, drop: Some(ref u), .. } | Drop { target: t, unwind: UnwindAction::Cleanup(ref u), .. } | Assert { target: t, unwind: UnwindAction::Cleanup(ref u), .. } | FalseUnwind { real_target: t, unwind: UnwindAction::Cleanup(ref u) } | InlineAsm { destination: Some(t), unwind: UnwindAction::Cleanup(ref u), .. } => { Some(t).into_iter().chain(slice::from_ref(u).into_iter().copied()) } Goto { target: t } | Call { target: None, unwind: UnwindAction::Cleanup(t), .. } | Call { target: Some(t), unwind: _, .. } | Yield { resume: t, drop: None, .. } | Drop { target: t, unwind: _, .. } | Assert { target: t, unwind: _, .. } | FalseUnwind { real_target: t, unwind: _ } | InlineAsm { destination: None, unwind: UnwindAction::Cleanup(t), .. } | InlineAsm { destination: Some(t), unwind: _, .. } => { Some(t).into_iter().chain((&[]).into_iter().copied()) } Resume | Terminate | GeneratorDrop | Return | Unreachable | Call { target: None, unwind: _, .. } | InlineAsm { destination: None, unwind: _, .. } => { None.into_iter().chain((&[]).into_iter().copied()) } SwitchInt { ref targets, .. } => { None.into_iter().chain(targets.targets.iter().copied()) } FalseEdge { real_target, ref imaginary_target } => Some(real_target) .into_iter() .chain(slice::from_ref(imaginary_target).into_iter().copied()), } } pub fn successors_mut(&mut self) -> SuccessorsMut<'_> { use self::TerminatorKind::*; match *self { Call { target: Some(ref mut t), unwind: UnwindAction::Cleanup(ref mut u), .. } | Yield { resume: ref mut t, drop: Some(ref mut u), .. } | Drop { target: ref mut t, unwind: UnwindAction::Cleanup(ref mut u), .. } | Assert { target: ref mut t, unwind: UnwindAction::Cleanup(ref mut u), .. } | FalseUnwind { real_target: ref mut t, unwind: UnwindAction::Cleanup(ref mut u) } | InlineAsm { destination: Some(ref mut t), unwind: UnwindAction::Cleanup(ref mut u), .. } => Some(t).into_iter().chain(slice::from_mut(u)), Goto { target: ref mut t } | Call { target: None, unwind: UnwindAction::Cleanup(ref mut t), .. } | Call { target: Some(ref mut t), unwind: _, .. } | Yield { resume: ref mut t, drop: None, .. } | Drop { target: ref mut t, unwind: _, .. } | Assert { target: ref mut t, unwind: _, .. } | FalseUnwind { real_target: ref mut t, unwind: _ } | InlineAsm { destination: None, unwind: UnwindAction::Cleanup(ref mut t), .. } | InlineAsm { destination: Some(ref mut t), unwind: _, .. } => { Some(t).into_iter().chain(&mut []) } Resume | Terminate | GeneratorDrop | Return | Unreachable | Call { target: None, unwind: _, .. } | InlineAsm { destination: None, unwind: _, .. } => None.into_iter().chain(&mut []), SwitchInt { ref mut targets, .. } => None.into_iter().chain(&mut targets.targets), FalseEdge { ref mut real_target, ref mut imaginary_target } => { Some(real_target).into_iter().chain(slice::from_mut(imaginary_target)) } } } pub fn unwind(&self) -> Option<&UnwindAction> { match *self { TerminatorKind::Goto { .. } | TerminatorKind::Resume | TerminatorKind::Terminate | TerminatorKind::Return | TerminatorKind::Unreachable | TerminatorKind::GeneratorDrop | TerminatorKind::Yield { .. } | TerminatorKind::SwitchInt { .. } | TerminatorKind::FalseEdge { .. } => None, TerminatorKind::Call { ref unwind, .. } | TerminatorKind::Assert { ref unwind, .. } | TerminatorKind::Drop { ref unwind, .. } | TerminatorKind::FalseUnwind { ref unwind, .. } | TerminatorKind::InlineAsm { ref unwind, .. } => Some(unwind), } } pub fn unwind_mut(&mut self) -> Option<&mut UnwindAction> { match *self { TerminatorKind::Goto { .. } | TerminatorKind::Resume | TerminatorKind::Terminate | TerminatorKind::Return | TerminatorKind::Unreachable | TerminatorKind::GeneratorDrop | TerminatorKind::Yield { .. } | TerminatorKind::SwitchInt { .. } | TerminatorKind::FalseEdge { .. } => None, TerminatorKind::Call { ref mut unwind, .. } | TerminatorKind::Assert { ref mut unwind, .. } | TerminatorKind::Drop { ref mut unwind, .. } | TerminatorKind::FalseUnwind { ref mut unwind, .. } | TerminatorKind::InlineAsm { ref mut unwind, .. } => Some(unwind), } } pub fn as_switch(&self) -> Option<(&Operand<'tcx>, &SwitchTargets)> { match self { TerminatorKind::SwitchInt { discr, targets } => Some((discr, targets)), _ => None, } } pub fn as_goto(&self) -> Option { match self { TerminatorKind::Goto { target } => Some(*target), _ => None, } } } 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()); let unwind = match self.unwind() { // Not needed or included in successors None | Some(UnwindAction::Cleanup(_)) => None, Some(UnwindAction::Continue) => Some("unwind continue"), Some(UnwindAction::Unreachable) => Some("unwind unreachable"), Some(UnwindAction::Terminate) => Some("unwind terminate"), }; match (successor_count, unwind) { (0, None) => Ok(()), (0, Some(unwind)) => write!(fmt, " -> {unwind}"), (1, None) => 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 let Some(unwind) = unwind { write!(fmt, ", {unwind}")?; } 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"), Resume => write!(fmt, "resume"), Terminate => write!(fmt, "abort"), 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 | Resume | Terminate | 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`. None, /// For terminators that a single successor, like `goto`, and `assert` without cleanup block. Single(BasicBlock), /// For terminators that two successors, `assert` with cleanup block and `falseEdge`. Double(BasicBlock, BasicBlock), /// Special action for `Yield`, `Call` and `InlineAsm` terminators. AssignOnReturn { return_: Option, unwind: UnwindAction, place: CallReturnPlaces<'mir, 'tcx>, }, /// Special edge for `SwitchInt`. SwitchInt { targets: &'mir SwitchTargets, discr: &'mir Operand<'tcx> }, } /// List of places that are written to after a successful (non-unwind) return /// from a `Call`, `Yield` or `InlineAsm`. #[derive(Copy, Clone, Debug)] pub enum CallReturnPlaces<'a, 'tcx> { Call(Place<'tcx>), Yield(Place<'tcx>), InlineAsm(&'a [InlineAsmOperand<'tcx>]), } impl<'tcx> CallReturnPlaces<'_, 'tcx> { pub fn for_each(&self, mut f: impl FnMut(Place<'tcx>)) { match *self { Self::Call(place) | Self::Yield(place) => f(place), Self::InlineAsm(operands) => { for op in operands { match *op { InlineAsmOperand::Out { place: Some(place), .. } | InlineAsmOperand::InOut { out_place: Some(place), .. } => f(place), _ => {} } } } } } } impl<'tcx> Terminator<'tcx> { pub fn edges(&self) -> TerminatorEdges<'_, 'tcx> { self.kind.edges() } } impl<'tcx> TerminatorKind<'tcx> { pub fn edges(&self) -> TerminatorEdges<'_, 'tcx> { use TerminatorKind::*; match *self { Return | Resume | Terminate | GeneratorDrop | Unreachable => TerminatorEdges::None, Goto { target } => TerminatorEdges::Single(target), Assert { target, unwind, expected: _, msg: _, cond: _ } | Drop { target, unwind, place: _, replace: _ } | FalseUnwind { real_target: target, unwind } => match unwind { UnwindAction::Cleanup(unwind) => TerminatorEdges::Double(target, unwind), UnwindAction::Continue | UnwindAction::Terminate | UnwindAction::Unreachable => { TerminatorEdges::Single(target) } }, FalseEdge { real_target, imaginary_target } => { TerminatorEdges::Double(real_target, imaginary_target) } Yield { resume: target, drop, resume_arg, value: _ } => { TerminatorEdges::AssignOnReturn { return_: Some(target), unwind: drop.map_or(UnwindAction::Terminate, UnwindAction::Cleanup), place: CallReturnPlaces::Yield(resume_arg), } } Call { unwind, destination, target, func: _, args: _, fn_span: _, call_source: _ } => { TerminatorEdges::AssignOnReturn { return_: target, unwind, place: CallReturnPlaces::Call(destination), } } InlineAsm { template: _, ref operands, options: _, line_spans: _, destination, unwind, } => TerminatorEdges::AssignOnReturn { return_: destination, unwind, place: CallReturnPlaces::InlineAsm(operands), }, SwitchInt { ref targets, ref discr } => TerminatorEdges::SwitchInt { targets, discr }, } } }