diff --git a/compiler/rustc_mir_build/src/build/custom/mod.rs b/compiler/rustc_mir_build/src/build/custom/mod.rs
index 3de2f45ad9a..d302d538ad4 100644
--- a/compiler/rustc_mir_build/src/build/custom/mod.rs
+++ b/compiler/rustc_mir_build/src/build/custom/mod.rs
@@ -162,6 +162,19 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> {
             expected: expected.to_string(),
         }
     }
+
+    fn stmt_error(&self, stmt: StmtId, expected: &'static str) -> ParseError {
+        let stmt = &self.thir[stmt];
+        let span = match stmt.kind {
+            StmtKind::Expr { expr, .. } => self.thir[expr].span,
+            StmtKind::Let { span, .. } => span,
+        };
+        ParseError {
+            span,
+            item_description: format!("{:?}", stmt.kind),
+            expected: expected.to_string(),
+        }
+    }
 }
 
 type PResult<T> = Result<T, ParseError>;
diff --git a/compiler/rustc_mir_build/src/build/custom/parse.rs b/compiler/rustc_mir_build/src/build/custom/parse.rs
index e2ab2cb90c7..a6f9caada2d 100644
--- a/compiler/rustc_mir_build/src/build/custom/parse.rs
+++ b/compiler/rustc_mir_build/src/build/custom/parse.rs
@@ -27,10 +27,13 @@ macro_rules! parse_by_kind {
         $expr_name:pat,
         $expected:literal,
         $(
-            @call($name:literal, $args:ident) => $call_expr:expr,
+            @call($name:ident, $args:ident) => $call_expr:expr,
         )*
         $(
-            $pat:pat => $expr:expr,
+            @variant($adt:ident, $variant:ident) => $variant_expr:expr,
+        )*
+        $(
+            $pat:pat $(if $guard:expr)? => $expr:expr,
         )*
     ) => {{
         let expr_id = $self.preparse($expr_id);
@@ -42,14 +45,20 @@ macro_rules! parse_by_kind {
                 ExprKind::Call { ty, fun: _, args: $args, .. } if {
                     match ty.kind() {
                         ty::FnDef(did, _) => {
-                            $self.tcx.is_diagnostic_item(rustc_span::Symbol::intern($name), *did)
+                            $self.tcx.is_diagnostic_item(rustc_span::sym::$name, *did)
                         }
                         _ => false,
                     }
                 } => $call_expr,
             )*
             $(
-                $pat => $expr,
+                ExprKind::Adt(box AdtExpr { adt_def, variant_index, .. }) if {
+                    $self.tcx.is_diagnostic_item(rustc_span::sym::$adt, adt_def.did()) &&
+                    adt_def.variants()[*variant_index].name == rustc_span::sym::$variant
+                } => $variant_expr,
+            )*
+            $(
+                $pat $(if $guard)? => $expr,
             )*
             #[allow(unreachable_patterns)]
             _ => return Err($self.expr_error(expr_id, $expected))
@@ -172,7 +181,8 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> {
             ExprKind::Block { block } => &self.thir[*block].stmts,
         );
         for (i, block_def) in block_defs.iter().enumerate() {
-            let block = self.parse_block_def(self.statement_as_expr(*block_def)?)?;
+            let is_cleanup = self.body.basic_blocks_mut()[BasicBlock::from_usize(i)].is_cleanup;
+            let block = self.parse_block_def(self.statement_as_expr(*block_def)?, is_cleanup)?;
             self.body.basic_blocks_mut()[BasicBlock::from_usize(i)] = block;
         }
 
@@ -181,15 +191,28 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> {
 
     fn parse_block_decls(&mut self, stmts: impl Iterator<Item = StmtId>) -> PResult<()> {
         for stmt in stmts {
-            let (var, _, _) = self.parse_let_statement(stmt)?;
-            let data = BasicBlockData::new(None);
-            let block = self.body.basic_blocks_mut().push(data);
-            self.block_map.insert(var, block);
+            self.parse_basic_block_decl(stmt)?;
         }
-
         Ok(())
     }
 
+    fn parse_basic_block_decl(&mut self, stmt: StmtId) -> PResult<()> {
+        match &self.thir[stmt].kind {
+            StmtKind::Let { pattern, initializer: Some(initializer), .. } => {
+                let (var, ..) = self.parse_var(pattern)?;
+                let mut data = BasicBlockData::new(None);
+                data.is_cleanup = parse_by_kind!(self, *initializer, _, "basic block declaration",
+                    @variant(mir_basic_block, Normal) => false,
+                    @variant(mir_basic_block, Cleanup) => true,
+                );
+                let block = self.body.basic_blocks_mut().push(data);
+                self.block_map.insert(var, block);
+                Ok(())
+            }
+            _ => Err(self.stmt_error(stmt, "let statement with an initializer")),
+        }
+    }
+
     fn parse_local_decls(&mut self, mut stmts: impl Iterator<Item = StmtId>) -> PResult<()> {
         let (ret_var, ..) = self.parse_let_statement(stmts.next().unwrap())?;
         self.local_map.insert(ret_var, Local::from_u32(0));
@@ -219,7 +242,7 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> {
             };
             let span = self.thir[expr].span;
             let (name, operand) = parse_by_kind!(self, expr, _, "debuginfo",
-                @call("mir_debuginfo", args) => {
+                @call(mir_debuginfo, args) => {
                     (args[0], args[1])
                 },
             );
@@ -281,12 +304,13 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> {
         }
     }
 
-    fn parse_block_def(&self, expr_id: ExprId) -> PResult<BasicBlockData<'tcx>> {
+    fn parse_block_def(&self, expr_id: ExprId, is_cleanup: bool) -> PResult<BasicBlockData<'tcx>> {
         let block = parse_by_kind!(self, expr_id, _, "basic block",
             ExprKind::Block { block } => &self.thir[*block],
         );
 
         let mut data = BasicBlockData::new(None);
+        data.is_cleanup = is_cleanup;
         for stmt_id in &*block.stmts {
             let stmt = self.statement_as_expr(*stmt_id)?;
             let span = self.thir[stmt].span;
diff --git a/compiler/rustc_mir_build/src/build/custom/parse/instruction.rs b/compiler/rustc_mir_build/src/build/custom/parse/instruction.rs
index fd2c57a0a6f..4ce7f831c87 100644
--- a/compiler/rustc_mir_build/src/build/custom/parse/instruction.rs
+++ b/compiler/rustc_mir_build/src/build/custom/parse/instruction.rs
@@ -13,19 +13,19 @@ use super::{parse_by_kind, PResult, ParseCtxt};
 impl<'tcx, 'body> ParseCtxt<'tcx, 'body> {
     pub fn parse_statement(&self, expr_id: ExprId) -> PResult<StatementKind<'tcx>> {
         parse_by_kind!(self, expr_id, _, "statement",
-            @call("mir_storage_live", args) => {
+            @call(mir_storage_live, args) => {
                 Ok(StatementKind::StorageLive(self.parse_local(args[0])?))
             },
-            @call("mir_storage_dead", args) => {
+            @call(mir_storage_dead, args) => {
                 Ok(StatementKind::StorageDead(self.parse_local(args[0])?))
             },
-            @call("mir_deinit", args) => {
+            @call(mir_deinit, args) => {
                 Ok(StatementKind::Deinit(Box::new(self.parse_place(args[0])?)))
             },
-            @call("mir_retag", args) => {
+            @call(mir_retag, args) => {
                 Ok(StatementKind::Retag(RetagKind::Default, Box::new(self.parse_place(args[0])?)))
             },
-            @call("mir_set_discriminant", args) => {
+            @call(mir_set_discriminant, args) => {
                 let place = self.parse_place(args[0])?;
                 let var = self.parse_integer_literal(args[1])? as u32;
                 Ok(StatementKind::SetDiscriminant {
@@ -43,24 +43,30 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> {
 
     pub fn parse_terminator(&self, expr_id: ExprId) -> PResult<TerminatorKind<'tcx>> {
         parse_by_kind!(self, expr_id, expr, "terminator",
-            @call("mir_return", _args) => {
+            @call(mir_return, _args) => {
                 Ok(TerminatorKind::Return)
             },
-            @call("mir_goto", args) => {
+            @call(mir_goto, args) => {
                 Ok(TerminatorKind::Goto { target: self.parse_block(args[0])? } )
             },
-            @call("mir_unreachable", _args) => {
+            @call(mir_unreachable, _args) => {
                 Ok(TerminatorKind::Unreachable)
             },
-            @call("mir_drop", args) => {
+            @call(mir_unwind_resume, _args) => {
+                Ok(TerminatorKind::UnwindResume)
+            },
+            @call(mir_unwind_terminate, args) => {
+                Ok(TerminatorKind::UnwindTerminate(self.parse_unwind_terminate_reason(args[0])?))
+            },
+            @call(mir_drop, args) => {
                 Ok(TerminatorKind::Drop {
                     place: self.parse_place(args[0])?,
                     target: self.parse_block(args[1])?,
-                    unwind: UnwindAction::Continue,
+                    unwind: self.parse_unwind_action(args[2])?,
                     replace: false,
                 })
             },
-            @call("mir_call", args) => {
+            @call(mir_call, args) => {
                 self.parse_call(args)
             },
             ExprKind::Match { scrutinee, arms, .. } => {
@@ -70,6 +76,34 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> {
         )
     }
 
+    fn parse_unwind_terminate_reason(&self, expr_id: ExprId) -> PResult<UnwindTerminateReason> {
+        parse_by_kind!(self, expr_id, _, "unwind terminate reason",
+            @variant(mir_unwind_terminate_reason, Abi) => {
+                Ok(UnwindTerminateReason::Abi)
+            },
+            @variant(mir_unwind_terminate_reason, InCleanup) => {
+                Ok(UnwindTerminateReason::InCleanup)
+            },
+        )
+    }
+
+    fn parse_unwind_action(&self, expr_id: ExprId) -> PResult<UnwindAction> {
+        parse_by_kind!(self, expr_id, _, "unwind action",
+            @call(mir_unwind_continue, _args) => {
+                Ok(UnwindAction::Continue)
+            },
+            @call(mir_unwind_unreachable, _args) => {
+                Ok(UnwindAction::Unreachable)
+            },
+            @call(mir_unwind_terminate, args) => {
+                Ok(UnwindAction::Terminate(self.parse_unwind_terminate_reason(args[0])?))
+            },
+            @call(mir_unwind_cleanup, args) => {
+                Ok(UnwindAction::Cleanup(self.parse_block(args[0])?))
+            },
+        )
+    }
+
     fn parse_match(&self, arms: &[ArmId], span: Span) -> PResult<SwitchTargets> {
         let Some((otherwise, rest)) = arms.split_last() else {
             return Err(ParseError {
@@ -113,6 +147,7 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> {
         );
         let destination = self.parse_place(destination)?;
         let target = self.parse_block(args[1])?;
+        let unwind = self.parse_unwind_action(args[2])?;
 
         parse_by_kind!(self, call, _, "function call",
             ExprKind::Call { fun, args, from_hir_call, fn_span, .. } => {
@@ -126,7 +161,7 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> {
                     args,
                     destination,
                     target: Some(target),
-                    unwind: UnwindAction::Continue,
+                    unwind,
                     call_source: if *from_hir_call { CallSource::Normal } else {
                         CallSource::OverloadedOperator
                     },
@@ -138,25 +173,25 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> {
 
     fn parse_rvalue(&self, expr_id: ExprId) -> PResult<Rvalue<'tcx>> {
         parse_by_kind!(self, expr_id, expr, "rvalue",
-            @call("mir_discriminant", args) => self.parse_place(args[0]).map(Rvalue::Discriminant),
-            @call("mir_cast_transmute", args) => {
+            @call(mir_discriminant, args) => self.parse_place(args[0]).map(Rvalue::Discriminant),
+            @call(mir_cast_transmute, args) => {
                 let source = self.parse_operand(args[0])?;
                 Ok(Rvalue::Cast(CastKind::Transmute, source, expr.ty))
             },
-            @call("mir_checked", args) => {
+            @call(mir_checked, args) => {
                 parse_by_kind!(self, args[0], _, "binary op",
                     ExprKind::Binary { op, lhs, rhs } => Ok(Rvalue::CheckedBinaryOp(
                         *op, Box::new((self.parse_operand(*lhs)?, self.parse_operand(*rhs)?))
                     )),
                 )
             },
-            @call("mir_offset", args) => {
+            @call(mir_offset, args) => {
                 let ptr = self.parse_operand(args[0])?;
                 let offset = self.parse_operand(args[1])?;
                 Ok(Rvalue::BinaryOp(BinOp::Offset, Box::new((ptr, offset))))
             },
-            @call("mir_len", args) => Ok(Rvalue::Len(self.parse_place(args[0])?)),
-            @call("mir_copy_for_deref", args) => Ok(Rvalue::CopyForDeref(self.parse_place(args[0])?)),
+            @call(mir_len, args) => Ok(Rvalue::Len(self.parse_place(args[0])?)),
+            @call(mir_copy_for_deref, args) => Ok(Rvalue::CopyForDeref(self.parse_place(args[0])?)),
             ExprKind::Borrow { borrow_kind, arg } => Ok(
                 Rvalue::Ref(self.tcx.lifetimes.re_erased, *borrow_kind, self.parse_place(*arg)?)
             ),
@@ -206,9 +241,9 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> {
 
     pub fn parse_operand(&self, expr_id: ExprId) -> PResult<Operand<'tcx>> {
         parse_by_kind!(self, expr_id, expr, "operand",
-            @call("mir_move", args) => self.parse_place(args[0]).map(Operand::Move),
-            @call("mir_static", args) => self.parse_static(args[0]),
-            @call("mir_static_mut", args) => self.parse_static(args[0]),
+            @call(mir_move, args) => self.parse_place(args[0]).map(Operand::Move),
+            @call(mir_static, args) => self.parse_static(args[0]),
+            @call(mir_static_mut, args) => self.parse_static(args[0]),
             ExprKind::Literal { .. }
             | ExprKind::NamedConst { .. }
             | ExprKind::NonHirLiteral { .. }
@@ -229,7 +264,7 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> {
 
     fn parse_place_inner(&self, expr_id: ExprId) -> PResult<(Place<'tcx>, PlaceTy<'tcx>)> {
         let (parent, proj) = parse_by_kind!(self, expr_id, expr, "place",
-            @call("mir_field", args) => {
+            @call(mir_field, args) => {
                 let (parent, ty) = self.parse_place_inner(args[0])?;
                 let field = FieldIdx::from_u32(self.parse_integer_literal(args[1])? as u32);
                 let field_ty = ty.field_ty(self.tcx, field);
@@ -237,7 +272,7 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> {
                 let place = parent.project_deeper(&[proj], self.tcx);
                 return Ok((place, PlaceTy::from_ty(field_ty)));
             },
-            @call("mir_variant", args) => {
+            @call(mir_variant, args) => {
                 (args[0], PlaceElem::Downcast(
                     None,
                     VariantIdx::from_u32(self.parse_integer_literal(args[1])? as u32)
@@ -245,7 +280,7 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> {
             },
             ExprKind::Deref { arg } => {
                 parse_by_kind!(self, *arg, _, "does not matter",
-                    @call("mir_make_place", args) => return self.parse_place_inner(args[0]),
+                    @call(mir_make_place, args) => return self.parse_place_inner(args[0]),
                     _ => (*arg, PlaceElem::Deref),
                 )
             },
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index f287862cc23..302be85a429 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -124,6 +124,7 @@ symbols! {
     // There is currently no checking that all symbols are used; that would be
     // nice to have.
     Symbols {
+        Abi,
         AcqRel,
         Acquire,
         AddToDiagnostic,
@@ -166,6 +167,7 @@ symbols! {
         CString,
         Capture,
         Center,
+        Cleanup,
         Clone,
         Command,
         ConstParamTy,
@@ -215,6 +217,7 @@ symbols! {
         HashSet,
         Hasher,
         Implied,
+        InCleanup,
         IndexOutput,
         Input,
         Instant,
@@ -258,6 +261,7 @@ symbols! {
         NonZeroU8,
         NonZeroUsize,
         None,
+        Normal,
         Ok,
         Option,
         Ord,
@@ -1023,6 +1027,36 @@ symbols! {
         minnumf32,
         minnumf64,
         mips_target_feature,
+        mir_basic_block,
+        mir_call,
+        mir_cast_transmute,
+        mir_checked,
+        mir_copy_for_deref,
+        mir_debuginfo,
+        mir_deinit,
+        mir_discriminant,
+        mir_drop,
+        mir_field,
+        mir_goto,
+        mir_len,
+        mir_make_place,
+        mir_move,
+        mir_offset,
+        mir_retag,
+        mir_return,
+        mir_set_discriminant,
+        mir_static,
+        mir_static_mut,
+        mir_storage_dead,
+        mir_storage_live,
+        mir_unreachable,
+        mir_unwind_cleanup,
+        mir_unwind_continue,
+        mir_unwind_resume,
+        mir_unwind_terminate,
+        mir_unwind_terminate_reason,
+        mir_unwind_unreachable,
+        mir_variant,
         miri,
         misc,
         mmx_reg,
diff --git a/library/core/src/intrinsics/mir.rs b/library/core/src/intrinsics/mir.rs
index b26a17ec30e..0d8a306ace5 100644
--- a/library/core/src/intrinsics/mir.rs
+++ b/library/core/src/intrinsics/mir.rs
@@ -110,15 +110,15 @@
 //!         let popped;
 //!
 //!         {
-//!             Call(_unused = Vec::push(v, value), pop)
+//!             Call(_unused = Vec::push(v, value), pop, UnwindContinue())
 //!         }
 //!
 //!         pop = {
-//!             Call(popped = Vec::pop(v), drop)
+//!             Call(popped = Vec::pop(v), drop, UnwindContinue())
 //!         }
 //!
 //!         drop = {
-//!             Drop(popped, ret)
+//!             Drop(popped, ret, UnwindContinue())
 //!         }
 //!
 //!         ret = {
@@ -238,10 +238,6 @@
 //!
 //! #### Terminators
 //!
-//! Custom MIR does not currently support cleanup blocks or non-trivial unwind paths. As such, there
-//! are no resume and abort terminators, and terminators that might unwind do not have any way to
-//! indicate the unwind block.
-//!
 //!  - [`Goto`], [`Return`], [`Unreachable`] and [`Drop`](Drop()) have associated functions.
 //!  - `match some_int_operand` becomes a `SwitchInt`. Each arm should be `literal => basic_block`
 //!     - The exception is the last arm, which must be `_ => basic_block` and corresponds to the
@@ -260,7 +256,26 @@
 /// Type representing basic blocks.
 ///
 /// All terminators will have this type as a return type. It helps achieve some type safety.
-pub struct BasicBlock;
+#[rustc_diagnostic_item = "mir_basic_block"]
+pub enum BasicBlock {
+    /// A non-cleanup basic block.
+    Normal,
+    /// A basic block that lies on an unwind path.
+    Cleanup,
+}
+
+/// The reason we are terminating the process during unwinding.
+#[rustc_diagnostic_item = "mir_unwind_terminate_reason"]
+pub enum UnwindTerminateReason {
+    /// Unwinding is just not possible given the ABI of this function.
+    Abi,
+    /// We were already cleaning up for an ongoing unwind, and a *second*, *nested* unwind was
+    /// triggered by the drop glue.
+    InCleanup,
+}
+
+pub use UnwindTerminateReason::Abi as ReasonAbi;
+pub use UnwindTerminateReason::InCleanup as ReasonInCleanup;
 
 macro_rules! define {
     ($name:literal, $( #[ $meta:meta ] )* fn $($sig:tt)*) => {
@@ -271,11 +286,41 @@ macro_rules! define {
     }
 }
 
+// Unwind actions
+define!(
+    "mir_unwind_continue",
+    /// An unwind action that continues unwinding.
+    fn UnwindContinue()
+);
+define!(
+    "mir_unwind_unreachable",
+    /// An unwind action that triggers undefined behaviour.
+    fn UnwindUnreachable() -> BasicBlock
+);
+define!(
+    "mir_unwind_terminate",
+    /// An unwind action that terminates the execution.
+    ///
+    /// `UnwindTerminate` can also be used as a terminator.
+    fn UnwindTerminate(reason: UnwindTerminateReason)
+);
+define!(
+    "mir_unwind_cleanup",
+    /// An unwind action that continues execution in a given basic blok.
+    fn UnwindCleanup(goto: BasicBlock)
+);
+
+// Terminators
 define!("mir_return", fn Return() -> BasicBlock);
 define!("mir_goto", fn Goto(destination: BasicBlock) -> BasicBlock);
 define!("mir_unreachable", fn Unreachable() -> BasicBlock);
-define!("mir_drop", fn Drop<T>(place: T, goto: BasicBlock));
-define!("mir_call", fn Call(call: (), goto: BasicBlock));
+define!("mir_drop", fn Drop<T, U>(place: T, goto: BasicBlock, unwind_action: U));
+define!("mir_call", fn Call<U>(call: (), goto: BasicBlock, unwind_action: U));
+define!("mir_unwind_resume",
+    /// A terminator that resumes the unwinding.
+    fn UnwindResume()
+);
+
 define!("mir_storage_live", fn StorageLive<T>(local: T));
 define!("mir_storage_dead", fn StorageDead<T>(local: T));
 define!("mir_deinit", fn Deinit<T>(place: T));
@@ -382,16 +427,15 @@ pub macro mir {
         }
 
         $(
-            $block_name:ident = {
+            $block_name:ident $(($block_cleanup:ident))? = {
                 $($block:tt)*
             }
         )*
     ) => {{
         // First, we declare all basic blocks.
-        $(
-            let $block_name: ::core::intrinsics::mir::BasicBlock;
-        )*
-
+        __internal_declare_basic_blocks!($(
+            $block_name $(($block_cleanup))?
+        )*);
         {
             // Now all locals
             #[allow(non_snake_case)]
@@ -585,3 +629,17 @@ pub macro __internal_remove_let {
         }
     },
 }
+
+/// Helper macro that declares the basic blocks.
+#[doc(hidden)]
+pub macro __internal_declare_basic_blocks {
+    () => {},
+    ($name:ident (cleanup) $($rest:tt)*) => {
+        let $name = ::core::intrinsics::mir::BasicBlock::Cleanup;
+        __internal_declare_basic_blocks!($($rest)*)
+    },
+    ($name:ident $($rest:tt)*) => {
+        let $name = ::core::intrinsics::mir::BasicBlock::Normal;
+        __internal_declare_basic_blocks!($($rest)*)
+    },
+}
diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.rs b/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.rs
index d47af50d407..e79bd70e915 100644
--- a/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.rs
+++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.rs
@@ -14,7 +14,7 @@ fn main() {
             let ptr = std::ptr::addr_of_mut!(non_copy);
             // Inside `callee`, the first argument and `*ptr` are basically
             // aliasing places!
-            Call(_unit = callee(Move(*ptr), ptr), after_call)
+            Call(_unit = callee(Move(*ptr), ptr), after_call, UnwindContinue())
         }
         after_call = {
             Return()
diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.stack.stderr b/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.stack.stderr
index 381442e69b1..ccf9732ed07 100644
--- a/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.stack.stderr
+++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.stack.stderr
@@ -27,8 +27,8 @@ LL |     unsafe { ptr.write(S(0)) };
 note: inside `main`
   --> $DIR/arg_inplace_mutate.rs:LL:CC
    |
-LL |             Call(_unit = callee(Move(*ptr), ptr), after_call)
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |             Call(_unit = callee(Move(*ptr), ptr), after_call, UnwindContinue())
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = note: this error originates in the macro `::core::intrinsics::mir::__internal_remove_let` which comes from the expansion of the macro `mir` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.tree.stderr b/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.tree.stderr
index 3d8ba68547b..e7baf6e23db 100644
--- a/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.tree.stderr
+++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.tree.stderr
@@ -35,8 +35,8 @@ LL |     unsafe { ptr.write(S(0)) };
 note: inside `main`
   --> $DIR/arg_inplace_mutate.rs:LL:CC
    |
-LL |             Call(_unit = callee(Move(*ptr), ptr), after_call)
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |             Call(_unit = callee(Move(*ptr), ptr), after_call, UnwindContinue())
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = note: this error originates in the macro `::core::intrinsics::mir::__internal_remove_let` which comes from the expansion of the macro `mir` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_after.rs b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_after.rs
index ea773048dd4..e4c00fdd845 100644
--- a/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_after.rs
+++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_after.rs
@@ -11,7 +11,7 @@ fn main() {
         {
             let non_copy = S(42);
             // This could change `non_copy` in-place
-            Call(_unit = change_arg(Move(non_copy)), after_call)
+            Call(_unit = change_arg(Move(non_copy)), after_call, UnwindContinue())
         }
         after_call = {
             // So now we must not be allowed to observe non-copy again.
diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.none.stderr b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.none.stderr
index cba23c21d12..f8140d0236a 100644
--- a/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.none.stderr
+++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.none.stderr
@@ -11,8 +11,8 @@ LL |     unsafe { ptr.read() };
 note: inside `main`
   --> $DIR/arg_inplace_observe_during.rs:LL:CC
    |
-LL |             Call(_unit = change_arg(Move(*ptr), ptr), after_call)
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |             Call(_unit = change_arg(Move(*ptr), ptr), after_call, UnwindContinue())
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
 
diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.rs b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.rs
index 8c6a7df7a6d..517abd733a9 100644
--- a/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.rs
+++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.rs
@@ -14,7 +14,7 @@ fn main() {
             let non_copy = S(42);
             let ptr = std::ptr::addr_of_mut!(non_copy);
             // This could change `non_copy` in-place
-            Call(_unit = change_arg(Move(*ptr), ptr), after_call)
+            Call(_unit = change_arg(Move(*ptr), ptr), after_call, UnwindContinue())
         }
         after_call = {
             Return()
diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.stack.stderr b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.stack.stderr
index f8532186be2..c37e788e6b4 100644
--- a/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.stack.stderr
+++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.stack.stderr
@@ -27,8 +27,8 @@ LL |     x.0 = 0;
 note: inside `main`
   --> $DIR/arg_inplace_observe_during.rs:LL:CC
    |
-LL |             Call(_unit = change_arg(Move(*ptr), ptr), after_call)
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |             Call(_unit = change_arg(Move(*ptr), ptr), after_call, UnwindContinue())
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = note: this error originates in the macro `::core::intrinsics::mir::__internal_remove_let` which comes from the expansion of the macro `mir` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.tree.stderr b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.tree.stderr
index 7b1846a32db..7557d3710d1 100644
--- a/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.tree.stderr
+++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.tree.stderr
@@ -35,8 +35,8 @@ LL |     x.0 = 0;
 note: inside `main`
   --> $DIR/arg_inplace_observe_during.rs:LL:CC
    |
-LL |             Call(_unit = change_arg(Move(*ptr), ptr), after_call)
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |             Call(_unit = change_arg(Move(*ptr), ptr), after_call, UnwindContinue())
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = note: this error originates in the macro `::core::intrinsics::mir::__internal_remove_let` which comes from the expansion of the macro `mir` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.none.stderr b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.none.stderr
index 0a31adabf73..dd951066c32 100644
--- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.none.stderr
+++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.none.stderr
@@ -11,8 +11,8 @@ LL |     unsafe { ptr.read() };
 note: inside `main`
   --> $DIR/return_pointer_aliasing.rs:LL:CC
    |
-LL |             Call(*ptr = myfun(ptr), after_call)
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |             Call(*ptr = myfun(ptr), after_call, UnwindContinue())
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
 
diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.rs b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.rs
index 3d560af3d5e..23b1e38b99f 100644
--- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.rs
+++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.rs
@@ -15,7 +15,7 @@ pub fn main() {
             let ptr = &raw mut x;
             // We arrange for `myfun` to have a pointer that aliases
             // its return place. Even just reading from that pointer is UB.
-            Call(*ptr = myfun(ptr), after_call)
+            Call(*ptr = myfun(ptr), after_call, UnwindContinue())
         }
 
         after_call = {
diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.stack.stderr b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.stack.stderr
index 875cc5edad9..cf13be6da01 100644
--- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.stack.stderr
+++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.stack.stderr
@@ -27,8 +27,8 @@ LL |     unsafe { ptr.read() };
 note: inside `main`
   --> $DIR/return_pointer_aliasing.rs:LL:CC
    |
-LL |             Call(*ptr = myfun(ptr), after_call)
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |             Call(*ptr = myfun(ptr), after_call, UnwindContinue())
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = note: this error originates in the macro `::core::intrinsics::mir::__internal_remove_let` which comes from the expansion of the macro `mir` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.tree.stderr b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.tree.stderr
index deafbf02077..e16c4c0ebb6 100644
--- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.tree.stderr
+++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.tree.stderr
@@ -35,8 +35,8 @@ LL |     unsafe { ptr.read() };
 note: inside `main`
   --> $DIR/return_pointer_aliasing.rs:LL:CC
    |
-LL |             Call(*ptr = myfun(ptr), after_call)
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |             Call(*ptr = myfun(ptr), after_call, UnwindContinue())
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = note: this error originates in the macro `::core::intrinsics::mir::__internal_remove_let` which comes from the expansion of the macro `mir` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.rs b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.rs
index c1bbc748e1a..56706cdb63b 100644
--- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.rs
+++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.rs
@@ -15,7 +15,7 @@ pub fn main() {
             let ptr = &raw mut _x;
             // We arrange for `myfun` to have a pointer that aliases
             // its return place. Even just reading from that pointer is UB.
-            Call(_x = myfun(ptr), after_call)
+            Call(_x = myfun(ptr), after_call, UnwindContinue())
         }
 
         after_call = {
diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.stack.stderr b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.stack.stderr
index 0666db34fec..5d76d9eab67 100644
--- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.stack.stderr
+++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.stack.stderr
@@ -30,8 +30,8 @@ LL |     unsafe { ptr.write(0) };
 note: inside `main`
   --> $DIR/return_pointer_aliasing2.rs:LL:CC
    |
-LL |             Call(_x = myfun(ptr), after_call)
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |             Call(_x = myfun(ptr), after_call, UnwindContinue())
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = note: this error originates in the macro `::core::intrinsics::mir::__internal_remove_let` which comes from the expansion of the macro `mir` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.tree.stderr b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.tree.stderr
index e1b40a6bc18..e8165a73ff4 100644
--- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.tree.stderr
+++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.tree.stderr
@@ -35,8 +35,8 @@ LL |     unsafe { ptr.write(0) };
 note: inside `main`
   --> $DIR/return_pointer_aliasing2.rs:LL:CC
    |
-LL |             Call(_x = myfun(ptr), after_call)
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |             Call(_x = myfun(ptr), after_call, UnwindContinue())
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = note: this error originates in the macro `::core::intrinsics::mir::__internal_remove_let` which comes from the expansion of the macro `mir` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_on_unwind.rs b/src/tools/miri/tests/fail/function_calls/return_pointer_on_unwind.rs
index 79e29b79d6a..923c59e7429 100644
--- a/src/tools/miri/tests/fail/function_calls/return_pointer_on_unwind.rs
+++ b/src/tools/miri/tests/fail/function_calls/return_pointer_on_unwind.rs
@@ -14,7 +14,7 @@ struct S(i32, [u8; 128]);
 fn docall(out: &mut S) {
     mir! {
         {
-            Call(*out = callee(), after_call)
+            Call(*out = callee(), after_call, UnwindContinue())
         }
 
         after_call = {
@@ -37,7 +37,7 @@ fn callee() -> S {
             // become visible to the outside. In codegen we can see them
             // but Miri should detect this as UB!
             RET.0 = 42;
-            Call(_unit = startpanic(), after_call)
+            Call(_unit = startpanic(), after_call, UnwindContinue())
         }
 
         after_call = {
diff --git a/src/tools/miri/tests/fail/validity/cast_fn_ptr_invalid_caller_arg.rs b/src/tools/miri/tests/fail/validity/cast_fn_ptr_invalid_caller_arg.rs
index ee80186d4b5..9357b372505 100644
--- a/src/tools/miri/tests/fail/validity/cast_fn_ptr_invalid_caller_arg.rs
+++ b/src/tools/miri/tests/fail/validity/cast_fn_ptr_invalid_caller_arg.rs
@@ -20,7 +20,7 @@ fn call(f: fn(NonZeroU32)) {
             let tmp = ptr::addr_of!(c);
             let ptr = tmp as *const NonZeroU32;
             // The call site now is a NonZeroU32-to-u32 transmute.
-            Call(_res = f(*ptr), retblock) //~ERROR: expected something greater or equal to 1
+            Call(_res = f(*ptr), retblock, UnwindContinue()) //~ERROR: expected something greater or equal to 1
         }
         retblock = {
             Return()
diff --git a/src/tools/miri/tests/fail/validity/cast_fn_ptr_invalid_caller_arg.stderr b/src/tools/miri/tests/fail/validity/cast_fn_ptr_invalid_caller_arg.stderr
index 234c2804008..b40d99f7bc9 100644
--- a/src/tools/miri/tests/fail/validity/cast_fn_ptr_invalid_caller_arg.stderr
+++ b/src/tools/miri/tests/fail/validity/cast_fn_ptr_invalid_caller_arg.stderr
@@ -1,8 +1,8 @@
 error: Undefined Behavior: constructing invalid value: encountered 0, but expected something greater or equal to 1
   --> $DIR/cast_fn_ptr_invalid_caller_arg.rs:LL:CC
    |
-LL |             Call(_res = f(*ptr), retblock)
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered 0, but expected something greater or equal to 1
+LL |             Call(_res = f(*ptr), retblock, UnwindContinue())
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered 0, but expected something greater or equal to 1
    |
    = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
    = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
diff --git a/src/tools/miri/tests/pass/function_calls/return_place_on_heap.rs b/src/tools/miri/tests/pass/function_calls/return_place_on_heap.rs
index d410a875b1b..89ee689fabe 100644
--- a/src/tools/miri/tests/pass/function_calls/return_place_on_heap.rs
+++ b/src/tools/miri/tests/pass/function_calls/return_place_on_heap.rs
@@ -11,7 +11,7 @@ pub fn main() {
         {
             let x = 0;
             let ptr = &raw mut x;
-            Call(*ptr = myfun(), after_call)
+            Call(*ptr = myfun(), after_call, UnwindContinue())
         }
 
         after_call = {
diff --git a/tests/mir-opt/building/custom/terminators.rs b/tests/mir-opt/building/custom/terminators.rs
index 9e442e0f98a..a83a6c07461 100644
--- a/tests/mir-opt/building/custom/terminators.rs
+++ b/tests/mir-opt/building/custom/terminators.rs
@@ -13,7 +13,7 @@ fn ident<T>(t: T) -> T {
 fn direct_call(x: i32) -> i32 {
     mir!(
         {
-            Call(RET = ident(x), retblock)
+            Call(RET = ident(x), retblock, UnwindContinue())
         }
 
         retblock = {
@@ -27,7 +27,7 @@ fn direct_call(x: i32) -> i32 {
 fn indirect_call(x: i32, f: fn(i32) -> i32) -> i32 {
     mir!(
         {
-            Call(RET = f(x), retblock)
+            Call(RET = f(x), retblock, UnwindContinue())
         }
 
         retblock = {
@@ -49,7 +49,7 @@ impl<'a> Drop for WriteOnDrop<'a> {
 fn drop_first<'a>(a: WriteOnDrop<'a>, b: WriteOnDrop<'a>) {
     mir!(
         {
-            Drop(a, retblock)
+            Drop(a, retblock, UnwindContinue())
         }
 
         retblock = {
@@ -64,7 +64,7 @@ fn drop_first<'a>(a: WriteOnDrop<'a>, b: WriteOnDrop<'a>) {
 fn drop_second<'a>(a: WriteOnDrop<'a>, b: WriteOnDrop<'a>) {
     mir!(
         {
-            Drop(b, retblock)
+            Drop(b, retblock, UnwindContinue())
         }
 
         retblock = {
diff --git a/tests/mir-opt/building/custom/unwind_action.rs b/tests/mir-opt/building/custom/unwind_action.rs
new file mode 100644
index 00000000000..e3c4ffac358
--- /dev/null
+++ b/tests/mir-opt/building/custom/unwind_action.rs
@@ -0,0 +1,68 @@
+// compile-flags: --crate-type=lib
+// edition:2021
+// needs-unwind
+#![feature(custom_mir, core_intrinsics)]
+use core::intrinsics::mir::*;
+
+// CHECK-LABEL: fn a()
+// CHECK:       bb0: {
+// CHECK-NEXT:  a() -> [return: bb1, unwind unreachable];
+#[custom_mir(dialect = "runtime", phase = "optimized")]
+pub fn a() {
+    mir!(
+        {
+            Call(RET = a(), bb1, UnwindUnreachable())
+        }
+        bb1 = {
+            Return()
+        }
+    )
+}
+
+// CHECK-LABEL: fn b()
+// CHECK:       bb0: {
+// CHECK-NEXT:  b() -> [return: bb1, unwind continue];
+#[custom_mir(dialect = "runtime", phase = "optimized")]
+pub fn b() {
+    mir!(
+        {
+            Call(RET = b(), bb1, UnwindContinue())
+        }
+        bb1 = {
+            Return()
+        }
+    )
+}
+
+// CHECK-LABEL: fn c()
+// CHECK:       bb0: {
+// CHECK-NEXT:  c() -> [return: bb1, unwind terminate(abi)];
+#[custom_mir(dialect = "runtime", phase = "optimized")]
+pub fn c() {
+    mir!(
+        {
+            Call(RET = c(), bb1, UnwindTerminate(ReasonAbi))
+        }
+        bb1 = {
+            Return()
+        }
+    )
+}
+
+// CHECK-LABEL: fn d()
+// CHECK:       bb0: {
+// CHECK-NEXT:  d() -> [return: bb1, unwind: bb2];
+#[custom_mir(dialect = "runtime", phase = "optimized")]
+pub fn d() {
+    mir!(
+        {
+            Call(RET = d(), bb1, UnwindCleanup(bb2))
+        }
+        bb1 = {
+            Return()
+        }
+        bb2 (cleanup) = {
+            UnwindResume()
+        }
+    )
+}
diff --git a/tests/mir-opt/building/custom/unwind_terminate.rs b/tests/mir-opt/building/custom/unwind_terminate.rs
new file mode 100644
index 00000000000..efdf2ddb1d0
--- /dev/null
+++ b/tests/mir-opt/building/custom/unwind_terminate.rs
@@ -0,0 +1,34 @@
+// compile-flags: --crate-type=lib
+// edition:2021
+#![feature(custom_mir, core_intrinsics)]
+use core::intrinsics::mir::*;
+
+// CHECK-LABEL: fn f()
+// CHECK:       bb1 (cleanup): {
+// CHECK-NEXT:  terminate(abi);
+#[custom_mir(dialect = "runtime", phase = "optimized")]
+pub fn f() {
+    mir!(
+        {
+            Return()
+        }
+        bb1(cleanup) = {
+            UnwindTerminate(ReasonAbi)
+        }
+    )
+}
+
+// CHECK-LABEL: fn g()
+// CHECK:       bb1 (cleanup): {
+// CHECK-NEXT:  terminate(cleanup);
+#[custom_mir(dialect = "runtime", phase = "optimized")]
+pub fn g() {
+    mir!(
+        {
+            Return()
+        }
+        bb1(cleanup) = {
+            UnwindTerminate(ReasonInCleanup)
+        }
+    )
+}
diff --git a/tests/mir-opt/copy-prop/borrowed_local.rs b/tests/mir-opt/copy-prop/borrowed_local.rs
index c6b8ad3571f..a44e65164af 100644
--- a/tests/mir-opt/copy-prop/borrowed_local.rs
+++ b/tests/mir-opt/copy-prop/borrowed_local.rs
@@ -22,11 +22,11 @@ fn f() -> bool {
             let b = a;
             // We cannot propagate the place `a`.
             let r2 = &b;
-            Call(RET = cmp_ref(r1, r2), next)
+            Call(RET = cmp_ref(r1, r2), next, UnwindContinue())
         }
         next = {
             // But we can propagate the value `a`.
-            Call(RET = opaque(b), ret)
+            Call(RET = opaque(b), ret, UnwindContinue())
         }
         ret = {
             Return()
diff --git a/tests/mir-opt/copy-prop/calls.rs b/tests/mir-opt/copy-prop/calls.rs
index 2970f5f0b8d..bc6760707cc 100644
--- a/tests/mir-opt/copy-prop/calls.rs
+++ b/tests/mir-opt/copy-prop/calls.rs
@@ -26,7 +26,7 @@ fn multiple_edges(t: bool) -> u8 {
             match t { true => bbt, _ => ret }
         }
         bbt = {
-            Call(x = dummy(13), ret)
+            Call(x = dummy(13), ret, UnwindContinue())
         }
         ret = {
             // `x` is not assigned on the `bb0 -> ret` edge,
diff --git a/tests/mir-opt/copy-prop/custom_move_arg.rs b/tests/mir-opt/copy-prop/custom_move_arg.rs
index 2077874ee9a..8593d9fa9ab 100644
--- a/tests/mir-opt/copy-prop/custom_move_arg.rs
+++ b/tests/mir-opt/copy-prop/custom_move_arg.rs
@@ -14,11 +14,11 @@ struct NotCopy(bool);
 fn f(_1: NotCopy) {
     mir!({
         let _2 = _1;
-        Call(RET = opaque(Move(_1)), bb1)
+        Call(RET = opaque(Move(_1)), bb1, UnwindContinue())
     }
     bb1 = {
         let _3 = Move(_2);
-        Call(RET = opaque(_3), bb2)
+        Call(RET = opaque(_3), bb2, UnwindContinue())
     }
     bb2 = {
         Return()
diff --git a/tests/mir-opt/copy-prop/move_projection.rs b/tests/mir-opt/copy-prop/move_projection.rs
index 8629d535bcf..438a90dddd0 100644
--- a/tests/mir-opt/copy-prop/move_projection.rs
+++ b/tests/mir-opt/copy-prop/move_projection.rs
@@ -18,10 +18,10 @@ fn f(a: Foo) -> bool {
             let b = a;
             // This is a move out of a copy, so must become a copy of `a.0`.
             let c = Move(b.0);
-            Call(RET = opaque(Move(a)), bb1)
+            Call(RET = opaque(Move(a)), bb1, UnwindContinue())
         }
         bb1 = {
-            Call(RET = opaque(Move(c)), ret)
+            Call(RET = opaque(Move(c)), ret, UnwindContinue())
         }
         ret = {
             Return()
diff --git a/tests/mir-opt/dead-store-elimination/call_arg_copy.rs b/tests/mir-opt/dead-store-elimination/call_arg_copy.rs
index dcd15fb2b09..b2eb64756f9 100644
--- a/tests/mir-opt/dead-store-elimination/call_arg_copy.rs
+++ b/tests/mir-opt/dead-store-elimination/call_arg_copy.rs
@@ -28,7 +28,7 @@ struct Packed {
 fn move_packed(packed: Packed) {
     mir!(
         {
-            Call(RET = use_both(0, packed.y), ret)
+            Call(RET = use_both(0, packed.y), ret, UnwindContinue())
         }
         ret = {
             Return()
diff --git a/tests/mir-opt/dead-store-elimination/cycle.rs b/tests/mir-opt/dead-store-elimination/cycle.rs
index 8896f5ff345..c9ad06a9da2 100644
--- a/tests/mir-opt/dead-store-elimination/cycle.rs
+++ b/tests/mir-opt/dead-store-elimination/cycle.rs
@@ -20,7 +20,7 @@ fn cycle(mut x: i32, mut y: i32, mut z: i32) {
     mir!(
         let condition: bool;
         {
-            Call(condition = cond(), bb1)
+            Call(condition = cond(), bb1, UnwindContinue())
         }
         bb1 = {
             match condition { true => bb2, _ => ret }
@@ -30,7 +30,7 @@ fn cycle(mut x: i32, mut y: i32, mut z: i32) {
             z = y;
             y = x;
             x = temp;
-            Call(condition = cond(), bb1)
+            Call(condition = cond(), bb1, UnwindContinue())
         }
         ret = {
             Return()
diff --git a/tests/mir-opt/gvn.rs b/tests/mir-opt/gvn.rs
index 10a66ced026..6e082acdbd3 100644
--- a/tests/mir-opt/gvn.rs
+++ b/tests/mir-opt/gvn.rs
@@ -529,31 +529,31 @@ fn duplicate_slice() -> (bool, bool) {
             // CHECK: [[a:_.*]] = (const "a",);
             // CHECK: [[au:_.*]] = ([[a]].0: &str) as u128 (Transmute);
             let a = ("a",);
-            Call(au = transmute::<_, u128>(a.0), bb1)
+            Call(au = transmute::<_, u128>(a.0), bb1, UnwindContinue())
         }
         bb1 = {
             // CHECK: [[c:_.*]] = identity::<&str>(([[a]].0: &str))
-            Call(c = identity(a.0), bb2)
+            Call(c = identity(a.0), bb2, UnwindContinue())
         }
         bb2 = {
             // CHECK: [[cu:_.*]] = [[c]] as u128 (Transmute);
-            Call(cu = transmute::<_, u128>(c), bb3)
+            Call(cu = transmute::<_, u128>(c), bb3, UnwindContinue())
         }
         bb3 = {
             // This slice is different from `a.0`. Hence `bu` is not `au`.
             // CHECK: [[b:_.*]] = const "a";
             // CHECK: [[bu:_.*]] = [[b]] as u128 (Transmute);
             let b = "a";
-            Call(bu = transmute::<_, u128>(b), bb4)
+            Call(bu = transmute::<_, u128>(b), bb4, UnwindContinue())
         }
         bb4 = {
             // This returns a copy of `b`, which is not `a`.
             // CHECK: [[d:_.*]] = identity::<&str>([[b]])
-            Call(d = identity(b), bb5)
+            Call(d = identity(b), bb5, UnwindContinue())
         }
         bb5 = {
             // CHECK: [[du:_.*]] = [[d]] as u128 (Transmute);
-            Call(du = transmute::<_, u128>(d), bb6)
+            Call(du = transmute::<_, u128>(d), bb6, UnwindContinue())
         }
         bb6 = {
             // `direct` must not fold to `true`, as `indirect` will not.
diff --git a/tests/mir-opt/reference_prop.rs b/tests/mir-opt/reference_prop.rs
index 36134e019ad..1b9c8fe15c2 100644
--- a/tests/mir-opt/reference_prop.rs
+++ b/tests/mir-opt/reference_prop.rs
@@ -695,7 +695,7 @@ fn multiple_storage() {
             // As there are multiple `StorageLive` statements for `x`, we cannot know if this `z`'s
             // pointer address is the address of `x`, so do nothing.
             let y = *z;
-            Call(RET = opaque(y), retblock)
+            Call(RET = opaque(y), retblock, UnwindContinue())
         }
 
         retblock = {
@@ -723,7 +723,7 @@ fn dominate_storage() {
         }
         bb1 = {
             let c = *r;
-            Call(RET = opaque(c), bb2)
+            Call(RET = opaque(c), bb2, UnwindContinue())
         }
         bb2 = {
             StorageDead(x);
@@ -759,18 +759,18 @@ fn maybe_dead(m: bool) {
         bb1 = {
             StorageDead(x);
             StorageDead(y);
-            Call(RET = opaque(u), bb2)
+            Call(RET = opaque(u), bb2, UnwindContinue())
         }
         bb2 = {
             // As `x` may be `StorageDead`, `a` may be dangling, so we do nothing.
             let z = *a;
-            Call(RET = opaque(z), bb3)
+            Call(RET = opaque(z), bb3, UnwindContinue())
         }
         bb3 = {
             // As `y` may be `StorageDead`, `b` may be dangling, so we do nothing.
             // This implies that we also do not substitute `b` in `bb0`.
             let t = *b;
-            Call(RET = opaque(t), retblock)
+            Call(RET = opaque(t), retblock, UnwindContinue())
         }
         retblock = {
             Return()
diff --git a/tests/ui/mir/validate/noncleanup-cleanup.rs b/tests/ui/mir/validate/noncleanup-cleanup.rs
new file mode 100644
index 00000000000..0a1c4528aa6
--- /dev/null
+++ b/tests/ui/mir/validate/noncleanup-cleanup.rs
@@ -0,0 +1,21 @@
+// Check that validation rejects cleanup edge to a non-cleanup block.
+//
+// failure-status: 101
+// dont-check-compiler-stderr
+// error-pattern: cleanuppad mismatch
+#![feature(custom_mir, core_intrinsics)]
+extern crate core;
+use core::intrinsics::mir::*;
+
+#[custom_mir(dialect = "built")]
+pub fn main() {
+    mir!(
+        {
+            Call(RET = main(), block, UnwindCleanup(block))
+        }
+        block = {
+            Return()
+        }
+    )
+
+}
diff --git a/tests/ui/mir/validate/noncleanup-resume.rs b/tests/ui/mir/validate/noncleanup-resume.rs
new file mode 100644
index 00000000000..e80d09bc90a
--- /dev/null
+++ b/tests/ui/mir/validate/noncleanup-resume.rs
@@ -0,0 +1,17 @@
+// Check that validation rejects resume terminator in a non-cleanup block.
+//
+// failure-status: 101
+// dont-check-compiler-stderr
+// error-pattern: resume on non-cleanup block
+#![feature(custom_mir, core_intrinsics)]
+extern crate core;
+use core::intrinsics::mir::*;
+
+#[custom_mir(dialect = "built")]
+pub fn main() {
+    mir!(
+        {
+            UnwindResume()
+        }
+    )
+}
diff --git a/tests/ui/mir/validate/noncleanup-terminate.rs b/tests/ui/mir/validate/noncleanup-terminate.rs
new file mode 100644
index 00000000000..2a74668370d
--- /dev/null
+++ b/tests/ui/mir/validate/noncleanup-terminate.rs
@@ -0,0 +1,17 @@
+// Check that validation rejects terminate terminator in a non-cleanup block.
+//
+// failure-status: 101
+// dont-check-compiler-stderr
+// error-pattern: terminate on non-cleanup block
+#![feature(custom_mir, core_intrinsics)]
+extern crate core;
+use core::intrinsics::mir::*;
+
+#[custom_mir(dialect = "built")]
+pub fn main() {
+    mir!(
+        {
+            UnwindTerminate(ReasonAbi)
+        }
+    )
+}