Skip to content

Commit

Permalink
Custom MIR: Support cleanup blocks
Browse files Browse the repository at this point in the history
Cleanup blocks are declared with `bb (cleanup) = { ... }`.

`Call` and `Drop` terminators take an additional argument describing the
unwind action, which is one of the following:

* `Continue()`
* `Unreachable()`
* `Termiante(reason)`, where reason is `Abi` or `InCleanup`
* `Cleanup(block)`

Also support resume and terminate terminators:

* `Resume()`
* `Terminate(reason)`
  • Loading branch information
tmiasko committed Oct 29, 2023
1 parent 7cc36de commit f54f88f
Show file tree
Hide file tree
Showing 35 changed files with 313 additions and 82 deletions.
44 changes: 29 additions & 15 deletions compiler/rustc_mir_build/src/build/custom/parse.rs
Expand Up @@ -30,7 +30,10 @@ macro_rules! parse_by_kind {
@call($name:literal, $args:ident) => $call_expr:expr,
)*
$(
$pat:pat => $expr:expr,
@variant($adt:literal, $variant:literal) => $variant_expr:expr,
)*
$(
$pat:pat $(if $guard:expr)? => $expr:expr,
)*
) => {{
let expr_id = $self.preparse($expr_id);
Expand All @@ -49,7 +52,13 @@ macro_rules! parse_by_kind {
} => $call_expr,
)*
$(
$pat => $expr,
ExprKind::Adt(box AdtExpr { adt_def, variant_index, .. }) if {
$self.tcx.is_diagnostic_item(rustc_span::Symbol::intern($adt), adt_def.did()) &&
adt_def.variants()[*variant_index].name == rustc_span::Symbol::intern($variant)
} => $variant_expr,
)*
$(
$pat $(if $guard)? => $expr,
)*
#[allow(unreachable_patterns)]
_ => return Err($self.expr_error(expr_id, $expected))
Expand Down Expand Up @@ -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;
}

Expand All @@ -181,8 +191,9 @@ 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 (var, _, _, initializer) = self.parse_let_statement(stmt)?;
let mut data = BasicBlockData::new(None);
data.is_cleanup = initializer.is_some();
let block = self.body.basic_blocks_mut().push(data);
self.block_map.insert(var, block);
}
Expand All @@ -195,7 +206,7 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> {
self.local_map.insert(ret_var, Local::from_u32(0));

for stmt in stmts {
let (var, ty, span) = self.parse_let_statement(stmt)?;
let (var, ty, span, _) = self.parse_let_statement(stmt)?;
let decl = LocalDecl::new(ty, span);
let local = self.body.local_decls.push(decl);
self.local_map.insert(var, local);
Expand Down Expand Up @@ -251,15 +262,17 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> {
Ok(())
}

fn parse_let_statement(&mut self, stmt_id: StmtId) -> PResult<(LocalVarId, Ty<'tcx>, Span)> {
let pattern = match &self.thir[stmt_id].kind {
StmtKind::Let { pattern, .. } => pattern,
StmtKind::Expr { expr, .. } => {
return Err(self.expr_error(*expr, "let statement"));
fn parse_let_statement(
&mut self,
stmt_id: StmtId,
) -> PResult<(LocalVarId, Ty<'tcx>, Span, Option<ExprId>)> {
match &self.thir[stmt_id].kind {
StmtKind::Let { pattern, initializer, .. } => {
let (var, ty, span) = self.parse_var(pattern)?;
Ok((var, ty, span, *initializer))
}
};

self.parse_var(pattern)
StmtKind::Expr { expr, .. } => Err(self.expr_error(*expr, "let statement")),
}
}

fn parse_var(&mut self, mut pat: &Pat<'tcx>) -> PResult<(LocalVarId, Ty<'tcx>, Span)> {
Expand All @@ -281,12 +294,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;
Expand Down
39 changes: 37 additions & 2 deletions compiler/rustc_mir_build/src/build/custom/parse/instruction.rs
Expand Up @@ -52,11 +52,17 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> {
@call("mir_unreachable", _args) => {
Ok(TerminatorKind::Unreachable)
},
@call("mir_resume", _args) => {
Ok(TerminatorKind::UnwindResume)
},
@call("mir_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,
})
},
Expand All @@ -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_continue", _args) => {
Ok(UnwindAction::Continue)
},
@call("mir_unreachable", _args) => {
Ok(UnwindAction::Unreachable)
},
@call("mir_terminate", args) => {
Ok(UnwindAction::Terminate(self.parse_unwind_terminate_reason(args[0])?))
},
@call("mir_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 {
Expand Down Expand Up @@ -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, .. } => {
Expand All @@ -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
},
Expand Down
56 changes: 41 additions & 15 deletions library/core/src/intrinsics/mir.rs
Expand Up @@ -110,15 +110,15 @@
//! let popped;
//!
//! {
//! Call(_unused = Vec::push(v, value), pop)
//! Call(_unused = Vec::push(v, value), pop, Continue())
//! }
//!
//! pop = {
//! Call(popped = Vec::pop(v), drop)
//! Call(popped = Vec::pop(v), drop, Continue())
//! }
//!
//! drop = {
//! Drop(popped, ret)
//! Drop(popped, ret, Continue())
//! }
//!
//! ret = {
Expand Down Expand Up @@ -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
Expand All @@ -262,6 +258,18 @@
/// All terminators will have this type as a return type. It helps achieve some type safety.
pub struct BasicBlock;

/// 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::*;

macro_rules! define {
($name:literal, $( #[ $meta:meta ] )* fn $($sig:tt)*) => {
#[rustc_diagnostic_item = $name]
Expand All @@ -270,12 +278,15 @@ macro_rules! define {
pub fn $($sig)* { panic!() }
}
}

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_continue", fn Continue());
define!("mir_resume", fn Resume());
define!("mir_terminate", fn Terminate(reason: UnwindTerminateReason));
define!("mir_cleanup", fn Cleanup(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_storage_live", fn StorageLive<T>(local: T));
define!("mir_storage_dead", fn StorageDead<T>(local: T));
define!("mir_deinit", fn Deinit<T>(place: T));
Expand Down Expand Up @@ -382,16 +393,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)]
Expand Down Expand Up @@ -585,3 +595,19 @@ pub macro __internal_remove_let {
}
},
}

/// Helper macro that declares the basic blocks.
///
/// The cleanup block are distinguished by an initializer.
#[doc(hidden)]
pub macro __internal_declare_basic_blocks {
() => {},
($name:ident (cleanup) $($rest:tt)*) => {
let $name = ::core::intrinsics::mir::BasicBlock;
__internal_declare_basic_blocks!($($rest)*)
},
($name:ident $($rest:tt)*) => {
let $name : ::core::intrinsics::mir::BasicBlock;
__internal_declare_basic_blocks!($($rest)*)
},
}
Expand Up @@ -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, Continue())
}
after_call = {
Return()
Expand Down
Expand Up @@ -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, Continue())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= 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
Expand Down
Expand Up @@ -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, Continue())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= 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
Expand Down
Expand Up @@ -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, Continue())
}
after_call = {
// So now we must not be allowed to observe non-copy again.
Expand Down
Expand Up @@ -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, Continue())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

Expand Down
Expand Up @@ -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, Continue())
}
after_call = {
Return()
Expand Down
Expand Up @@ -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, Continue())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= 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
Expand Down
Expand Up @@ -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, Continue())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= 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
Expand Down
Expand Up @@ -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, Continue())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

Expand Down
Expand Up @@ -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, Continue())
}

after_call = {
Expand Down
Expand Up @@ -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, Continue())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= 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
Expand Down

0 comments on commit f54f88f

Please sign in to comment.