Skip to content

Add MIR validation for unwind out from nounwind functions + fixes to make validation pass #113124

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Aug 20, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 42 additions & 8 deletions compiler/rustc_const_eval/src/transform/validate.rs
Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@ use rustc_mir_dataflow::impls::MaybeStorageLive;
use rustc_mir_dataflow::storage::always_storage_live_locals;
use rustc_mir_dataflow::{Analysis, ResultsCursor};
use rustc_target::abi::{Size, FIRST_VARIANT};
use rustc_target::spec::abi::Abi;

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum EdgeKind {
@@ -58,6 +59,25 @@ impl<'tcx> MirPass<'tcx> for Validator {
.iterate_to_fixpoint()
.into_results_cursor(body);

let can_unwind = if mir_phase <= MirPhase::Runtime(RuntimePhase::Initial) {
// In this case `AbortUnwindingCalls` haven't yet been executed.
true
} else if !tcx.def_kind(def_id).is_fn_like() {
true
} else {
let body_ty = tcx.type_of(def_id).skip_binder();
let body_abi = match body_ty.kind() {
ty::FnDef(..) => body_ty.fn_sig(tcx).abi(),
ty::Closure(..) => Abi::RustCall,
ty::Generator(..) => Abi::Rust,
_ => {
span_bug!(body.span, "unexpected body ty: {:?} phase {:?}", body_ty, mir_phase)
}
};

ty::layout::fn_can_unwind(tcx, Some(def_id), body_abi)
};

let mut cfg_checker = CfgChecker {
when: &self.when,
body,
@@ -68,6 +88,7 @@ impl<'tcx> MirPass<'tcx> for Validator {
storage_liveness,
place_cache: FxHashSet::default(),
value_cache: FxHashSet::default(),
can_unwind,
};
cfg_checker.visit_body(body);
cfg_checker.check_cleanup_control_flow();
@@ -99,6 +120,9 @@ struct CfgChecker<'a, 'tcx> {
storage_liveness: ResultsCursor<'a, 'tcx, MaybeStorageLive<'static>>,
place_cache: FxHashSet<PlaceRef<'tcx>>,
value_cache: FxHashSet<u128>,
// If `false`, then the MIR must not contain `UnwindAction::Continue` or
// `TerminatorKind::Resume`.
can_unwind: bool,
}

impl<'a, 'tcx> CfgChecker<'a, 'tcx> {
@@ -237,13 +261,17 @@ impl<'a, 'tcx> CfgChecker<'a, 'tcx> {
match unwind {
UnwindAction::Cleanup(unwind) => {
if is_cleanup {
self.fail(location, "unwind on cleanup block");
self.fail(location, "`UnwindAction::Cleanup` in cleanup block");
}
self.check_edge(location, unwind, EdgeKind::Unwind);
}
UnwindAction::Continue => {
if is_cleanup {
self.fail(location, "unwind on cleanup block");
self.fail(location, "`UnwindAction::Continue` in cleanup block");
}

if !self.can_unwind {
self.fail(location, "`UnwindAction::Continue` in no-unwind function");
}
}
UnwindAction::Unreachable | UnwindAction::Terminate => (),
@@ -464,13 +492,19 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> {
);
}
}
TerminatorKind::Resume | TerminatorKind::Terminate => {
TerminatorKind::Resume => {
let bb = location.block;
if !self.body.basic_blocks[bb].is_cleanup {
self.fail(
location,
"Cannot `Resume` or `Terminate` from non-cleanup basic block",
)
self.fail(location, "Cannot `Resume` from non-cleanup basic block")
}
if !self.can_unwind {
self.fail(location, "Cannot `Resume` in a function that cannot unwind")
}
}
TerminatorKind::Terminate => {
let bb = location.block;
if !self.body.basic_blocks[bb].is_cleanup {
self.fail(location, "Cannot `Terminate` from non-cleanup basic block")
}
}
TerminatorKind::Return => {
@@ -665,7 +699,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
return;
};

f_ty.ty
ty::EarlyBinder::bind(f_ty.ty).instantiate(self.tcx, args)
} else {
let Some(&f_ty) = args.as_generator().prefix_tys().get(f.index())
else {
23 changes: 23 additions & 0 deletions compiler/rustc_mir_transform/src/generator.rs
Original file line number Diff line number Diff line change
@@ -50,8 +50,10 @@
//! For generators with state 1 (returned) and state 2 (poisoned) it does nothing.
//! Otherwise it drops all the values in scope at the last suspension point.
use crate::abort_unwinding_calls;
use crate::deref_separator::deref_finder;
use crate::errors;
use crate::pass_manager as pm;
use crate::simplify;
use crate::MirPass;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
@@ -64,6 +66,7 @@ use rustc_index::{Idx, IndexVec};
use rustc_middle::mir::dump_mir;
use rustc_middle::mir::visit::{MutVisitor, PlaceContext, Visitor};
use rustc_middle::mir::*;
use rustc_middle::ty::InstanceDef;
use rustc_middle::ty::{self, AdtDef, Ty, TyCtxt};
use rustc_middle::ty::{GeneratorArgs, GenericArgsRef};
use rustc_mir_dataflow::impls::{
@@ -1147,7 +1150,25 @@ fn create_generator_drop_shim<'tcx>(
// unrelated code from the resume part of the function
simplify::remove_dead_blocks(tcx, &mut body);

// Update the body's def to become the drop glue.
// This needs to be updated before the AbortUnwindingCalls pass.
let gen_instance = body.source.instance;
let drop_in_place = tcx.require_lang_item(LangItem::DropInPlace, None);
let drop_instance = InstanceDef::DropGlue(drop_in_place, Some(gen_ty));
body.source.instance = drop_instance;

pm::run_passes_no_validate(
tcx,
&mut body,
&[&abort_unwinding_calls::AbortUnwindingCalls],
None,
);

// Temporary change MirSource to generator's instance so that dump_mir produces more sensible
// filename.
body.source.instance = gen_instance;
dump_mir(tcx, false, "generator_drop", &0, &body, |_, _| Ok(()));
body.source.instance = drop_instance;

body
}
@@ -1317,6 +1338,8 @@ fn create_generator_resume_function<'tcx>(
// unrelated code from the drop part of the function
simplify::remove_dead_blocks(tcx, body);

pm::run_passes_no_validate(tcx, body, &[&abort_unwinding_calls::AbortUnwindingCalls], None);

dump_mir(tcx, false, "generator_resume", &0, body, |_, _| Ok(()));
}

16 changes: 13 additions & 3 deletions compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs
Original file line number Diff line number Diff line change
@@ -6,8 +6,8 @@ use rustc_middle::ty::TyCtxt;
use rustc_target::spec::PanicStrategy;

/// A pass that removes noop landing pads and replaces jumps to them with
/// `None`. This is important because otherwise LLVM generates terrible
/// code for these.
/// `UnwindAction::Continue`. This is important because otherwise LLVM generates
/// terrible code for these.
pub struct RemoveNoopLandingPads;

impl<'tcx> MirPass<'tcx> for RemoveNoopLandingPads {
@@ -84,7 +84,17 @@ impl RemoveNoopLandingPads {
fn remove_nop_landing_pads(&self, body: &mut Body<'_>) {
debug!("body: {:#?}", body);

// make sure there's a resume block
// Skip the pass if there are no blocks with a resume terminator.
let has_resume = body
.basic_blocks
.iter_enumerated()
.any(|(_bb, block)| matches!(block.terminator().kind, TerminatorKind::Resume));
if !has_resume {
debug!("remove_noop_landing_pads: no resume block in MIR");
return;
}

// make sure there's a resume block without any statements
let resume_block = {
let mut patch = MirPatch::new(body);
let resume_block = patch.resume_block();
11 changes: 10 additions & 1 deletion compiler/rustc_mir_transform/src/shim.rs
Original file line number Diff line number Diff line change
@@ -71,8 +71,17 @@ fn make_shim<'tcx>(tcx: TyCtxt<'tcx>, instance: ty::InstanceDef<'tcx>) -> Body<'
// of this function. Is this intentional?
if let Some(ty::Generator(gen_def_id, args, _)) = ty.map(Ty::kind) {
let body = tcx.optimized_mir(*gen_def_id).generator_drop().unwrap();
let body = EarlyBinder::bind(body.clone()).instantiate(tcx, args);
let mut body = EarlyBinder::bind(body.clone()).instantiate(tcx, args);
debug!("make_shim({:?}) = {:?}", instance, body);

// Run empty passes to mark phase change and perform validation.
pm::run_passes(
tcx,
&mut body,
&[],
Some(MirPhase::Runtime(RuntimePhase::Optimized)),
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be in generator.rs, where we construct this MIR? Where you run AbortUnwindingCalls.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validator wants to know about layouts, and the layouts require the lowering of resume, so a cycle can be created if validation is performed in generator.rs.


return body;
}

Original file line number Diff line number Diff line change
@@ -30,7 +30,7 @@ fn a::{closure#0}(_1: Pin<&mut [async fn body@$DIR/async_await.rs:11:14: 11:16]>
}

bb2: {
assert(const false, "`async fn` resumed after completion") -> [success: bb2, unwind continue];
assert(const false, "`async fn` resumed after completion") -> [success: bb2, unwind unreachable];
}

bb3: {
Original file line number Diff line number Diff line change
@@ -310,7 +310,7 @@ fn b::{closure#0}(_1: Pin<&mut [async fn body@$DIR/async_await.rs:14:18: 17:2]>,
}

bb28: {
assert(const false, "`async fn` resumed after completion") -> [success: bb28, unwind continue];
assert(const false, "`async fn` resumed after completion") -> [success: bb28, unwind unreachable];
}

bb29: {
2 changes: 1 addition & 1 deletion tests/run-make/panic-abort-eh_frame/Makefile
Original file line number Diff line number Diff line change
@@ -6,5 +6,5 @@
include ../tools.mk

all:
$(RUSTC) foo.rs --crate-type=lib --emit=obj=$(TMPDIR)/foo.o -Cpanic=abort
$(RUSTC) foo.rs --crate-type=lib --emit=obj=$(TMPDIR)/foo.o -Cpanic=abort --edition 2021 -Z validate-mir
objdump --dwarf=frames $(TMPDIR)/foo.o | $(CGREP) -v 'DW_CFA'
24 changes: 24 additions & 0 deletions tests/run-make/panic-abort-eh_frame/foo.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
#![no_std]

use core::future::Future;

pub struct NeedsDrop;

impl Drop for NeedsDrop {
fn drop(&mut self) {}
}

#[panic_handler]
fn handler(_: &core::panic::PanicInfo<'_>) -> ! {
loop {}
@@ -8,3 +16,19 @@ fn handler(_: &core::panic::PanicInfo<'_>) -> ! {
pub unsafe fn oops(x: *const u32) -> u32 {
*x
}

pub async fn foo(_: NeedsDrop) {
async fn bar() {}
bar().await;
}

pub fn poll_foo(x: &mut core::task::Context<'_>) {
let _g = NeedsDrop;
let mut p = core::pin::pin!(foo(NeedsDrop));
let _ = p.as_mut().poll(x);
let _ = p.as_mut().poll(x);
}

pub fn drop_foo() {
drop(foo(NeedsDrop));
}