blob: 94f8cba6fb57869704f499852e98ff9e7d8aa284 [file] [log] [blame]
/// Functionality for terminators and helper types that appear in terminators.
use rustc_hir::LangItem;
use smallvec::SmallVec;
use super::TerminatorKind;
use rustc_data_structures::packed::Pu128;
use rustc_macros::HashStable;
use std::slice;
use super::*;
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<Item = (u128, BasicBlock)>, otherwise: BasicBlock) -> Self {
let (values, mut targets): (SmallVec<_>, SmallVec<_>) =
targets.map(|(v, t)| (Pu128(v), t)).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![Pu128(value)], targets: smallvec![then, else_] }
}
/// Inverse of `SwitchTargets::static_if`.
#[inline]
pub fn as_static_if(&self) -> Option<(u128, BasicBlock, BasicBlock)> {
if let &[value] = &self.values[..]
&& let &[then, else_] = &self.targets[..]
{
Some((value.get(), then, else_))
} else {
None
}
}
/// Returns the fallback target that is jumped to when none of the values match the operand.
#[inline]
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.
#[inline]
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).
#[inline]
pub fn all_targets(&self) -> &[BasicBlock] {
&self.targets
}
#[inline]
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.
#[inline]
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())
}
/// Adds a new target to the switch. But You cannot add an already present value.
#[inline]
pub fn add_target(&mut self, value: u128, bb: BasicBlock) {
let value = Pu128(value);
if self.values.contains(&value) {
bug!("target value {:?} already present", value);
}
self.values.push(value);
self.targets.insert(self.targets.len() - 1, bb);
}
}
pub struct SwitchTargetsIter<'a> {
inner: iter::Zip<slice::Iter<'a, Pu128>, slice::Iter<'a, BasicBlock>>,
}
impl<'a> Iterator for SwitchTargetsIter<'a> {
type Item = (u128, BasicBlock);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|(val, bb)| (val.get(), *bb))
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
impl<'a> ExactSizeIterator for SwitchTargetsIter<'a> {}
impl UnwindAction {
fn cleanup_block(self) -> Option<BasicBlock> {
match self {
UnwindAction::Cleanup(bb) => Some(bb),
UnwindAction::Continue | UnwindAction::Unreachable | UnwindAction::Terminate(_) => None,
}
}
}
impl UnwindTerminateReason {
pub fn as_str(self) -> &'static str {
// Keep this in sync with the messages in `core/src/panicking.rs`.
match self {
UnwindTerminateReason::Abi => "panic in a function that cannot unwind",
UnwindTerminateReason::InCleanup => "panic in a destructor during cleanup",
}
}
/// A short representation of this used for MIR printing.
pub fn as_short_str(self) -> &'static str {
match self {
UnwindTerminateReason::Abi => "abi",
UnwindTerminateReason::InCleanup => "cleanup",
}
}
pub fn lang_item(self) -> LangItem {
match self {
UnwindTerminateReason::Abi => LangItem::PanicCannotUnwind,
UnwindTerminateReason::InCleanup => LangItem::PanicInCleanup,
}
}
}
impl<O> AssertKind<O> {
/// 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(CoroutineKind::Coroutine(_)) => "coroutine resumed after completion",
ResumedAfterReturn(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)) => {
"`async fn` resumed after completion"
}
ResumedAfterReturn(CoroutineKind::Desugared(CoroutineDesugaring::AsyncGen, _)) => {
"`async gen fn` resumed after completion"
}
ResumedAfterReturn(CoroutineKind::Desugared(CoroutineDesugaring::Gen, _)) => {
"`gen fn` should just keep returning `None` after completion"
}
ResumedAfterPanic(CoroutineKind::Coroutine(_)) => "coroutine resumed after panicking",
ResumedAfterPanic(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)) => {
"`async fn` resumed after panicking"
}
ResumedAfterPanic(CoroutineKind::Desugared(CoroutineDesugaring::AsyncGen, _)) => {
"`async gen fn` resumed after panicking"
}
ResumedAfterPanic(CoroutineKind::Desugared(CoroutineDesugaring::Gen, _)) => {
"`gen fn` should just keep returning `None` 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<W: fmt::Write>(&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) -> DiagMessage {
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(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)) => {
middle_assert_async_resume_after_return
}
ResumedAfterReturn(CoroutineKind::Desugared(CoroutineDesugaring::AsyncGen, _)) => {
todo!()
}
ResumedAfterReturn(CoroutineKind::Desugared(CoroutineDesugaring::Gen, _)) => {
bug!("gen blocks can be resumed after they return and will keep returning `None`")
}
ResumedAfterReturn(CoroutineKind::Coroutine(_)) => {
middle_assert_coroutine_resume_after_return
}
ResumedAfterPanic(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)) => {
middle_assert_async_resume_after_panic
}
ResumedAfterPanic(CoroutineKind::Desugared(CoroutineDesugaring::AsyncGen, _)) => {
todo!()
}
ResumedAfterPanic(CoroutineKind::Desugared(CoroutineDesugaring::Gen, _)) => {
middle_assert_gen_resume_after_panic
}
ResumedAfterPanic(CoroutineKind::Coroutine(_)) => {
middle_assert_coroutine_resume_after_panic
}
MisalignedPointerDereference { .. } => middle_assert_misaligned_ptr_deref,
}
}
pub fn add_args(self, adder: &mut dyn FnMut(DiagArgName, DiagArgValue))
where
O: fmt::Debug,
{
use AssertKind::*;
macro_rules! add {
($name: expr, $value: expr) => {
adder($name.into(), $value.into_diag_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,
pub kind: TerminatorKind<'tcx>,
}
pub type Successors<'a> = impl DoubleEndedIterator<Item = BasicBlock> + 'a;
pub type SuccessorsMut<'a> = impl DoubleEndedIterator<Item = &'a mut BasicBlock> + 'a;
impl<'tcx> Terminator<'tcx> {
#[inline]
pub fn successors(&self) -> Successors<'_> {
self.kind.successors()
}
#[inline]
pub fn successors_mut(&mut self) -> SuccessorsMut<'_> {
self.kind.successors_mut()
}
#[inline]
pub fn unwind(&self) -> Option<&UnwindAction> {
self.kind.unwind()
}
#[inline]
pub fn unwind_mut(&mut self) -> Option<&mut UnwindAction> {
self.kind.unwind_mut()
}
}
impl<'tcx> TerminatorKind<'tcx> {
#[inline]
pub fn if_(cond: Operand<'tcx>, t: BasicBlock, f: BasicBlock) -> TerminatorKind<'tcx> {
TerminatorKind::SwitchInt { discr: cond, targets: SwitchTargets::static_if(0, f, t) }
}
#[inline]
pub fn successors(&self) -> Successors<'_> {
use self::TerminatorKind::*;
match *self {
Call { target: Some(ref t), unwind: UnwindAction::Cleanup(u), .. }
| Yield { resume: ref t, drop: Some(u), .. }
| Drop { target: ref t, unwind: UnwindAction::Cleanup(u), .. }
| Assert { target: ref t, unwind: UnwindAction::Cleanup(u), .. }
| FalseUnwind { real_target: ref t, unwind: UnwindAction::Cleanup(u) } => {
slice::from_ref(t).into_iter().copied().chain(Some(u))
}
Goto { target: ref t }
| Call { target: None, unwind: UnwindAction::Cleanup(ref t), .. }
| Call { target: Some(ref t), unwind: _, .. }
| Yield { resume: ref t, drop: None, .. }
| Drop { target: ref t, unwind: _, .. }
| Assert { target: ref t, unwind: _, .. }
| FalseUnwind { real_target: ref t, unwind: _ } => {
slice::from_ref(t).into_iter().copied().chain(None)
}
UnwindResume
| UnwindTerminate(_)
| CoroutineDrop
| Return
| Unreachable
| Call { target: None, unwind: _, .. } => (&[]).into_iter().copied().chain(None),
InlineAsm { ref targets, unwind: UnwindAction::Cleanup(u), .. } => {
targets.iter().copied().chain(Some(u))
}
InlineAsm { ref targets, unwind: _, .. } => targets.iter().copied().chain(None),
SwitchInt { ref targets, .. } => targets.targets.iter().copied().chain(None),
FalseEdge { ref real_target, imaginary_target } => {
slice::from_ref(real_target).into_iter().copied().chain(Some(imaginary_target))
}
}
}
#[inline]
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) } => {
slice::from_mut(t).into_iter().chain(Some(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: _ } => {
slice::from_mut(t).into_iter().chain(None)
}
UnwindResume
| UnwindTerminate(_)
| CoroutineDrop
| Return
| Unreachable
| Call { target: None, unwind: _, .. } => (&mut []).into_iter().chain(None),
InlineAsm { ref mut targets, unwind: UnwindAction::Cleanup(ref mut u), .. } => {
targets.iter_mut().chain(Some(u))
}
InlineAsm { ref mut targets, unwind: _, .. } => targets.iter_mut().chain(None),
SwitchInt { ref mut targets, .. } => targets.targets.iter_mut().chain(None),
FalseEdge { ref mut real_target, ref mut imaginary_target } => {
slice::from_mut(real_target).into_iter().chain(Some(imaginary_target))
}
}
}
#[inline]
pub fn unwind(&self) -> Option<&UnwindAction> {
match *self {
TerminatorKind::Goto { .. }
| TerminatorKind::UnwindResume
| TerminatorKind::UnwindTerminate(_)
| TerminatorKind::Return
| TerminatorKind::Unreachable
| TerminatorKind::CoroutineDrop
| 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),
}
}
#[inline]
pub fn unwind_mut(&mut self) -> Option<&mut UnwindAction> {
match *self {
TerminatorKind::Goto { .. }
| TerminatorKind::UnwindResume
| TerminatorKind::UnwindTerminate(_)
| TerminatorKind::Return
| TerminatorKind::Unreachable
| TerminatorKind::CoroutineDrop
| 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),
}
}
#[inline]
pub fn as_switch(&self) -> Option<(&Operand<'tcx>, &SwitchTargets)> {
match self {
TerminatorKind::SwitchInt { discr, targets } => Some((discr, targets)),
_ => None,
}
}
#[inline]
pub fn as_goto(&self) -> Option<BasicBlock> {
match self {
TerminatorKind::Goto { target } => Some(*target),
_ => None,
}
}
}
#[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_: &'mir [BasicBlock],
/// The cleanup block, if it exists.
cleanup: Option<BasicBlock>,
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 | UnwindResume | UnwindTerminate(_) | CoroutineDrop | 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: ref target, drop, resume_arg, value: _ } => {
TerminatorEdges::AssignOnReturn {
return_: slice::from_ref(target),
cleanup: drop,
place: CallReturnPlaces::Yield(resume_arg),
}
}
Call {
unwind,
destination,
ref target,
func: _,
args: _,
fn_span: _,
call_source: _,
} => TerminatorEdges::AssignOnReturn {
return_: target.as_ref().map(slice::from_ref).unwrap_or_default(),
cleanup: unwind.cleanup_block(),
place: CallReturnPlaces::Call(destination),
},
InlineAsm {
template: _,
ref operands,
options: _,
line_spans: _,
ref targets,
unwind,
} => TerminatorEdges::AssignOnReturn {
return_: targets,
cleanup: unwind.cleanup_block(),
place: CallReturnPlaces::InlineAsm(operands),
},
SwitchInt { ref targets, ref discr } => TerminatorEdges::SwitchInt { targets, discr },
}
}
}