Skip to content

Commit

Permalink
Auto merge of rust-lang#113124 - nbdd0121:eh_frame, r=cjgillot
Browse files Browse the repository at this point in the history
Add MIR validation for unwind out from nounwind functions + fixes to make validation pass

`@Nilstrieb`  This is the MIR validation you asked in rust-lang#112403 (comment).

Two passes need to be fixed to get the validation to pass:
* `RemoveNoopLandingPads` currently unconditionally introduce a resume block (even there is none to begin with!), changed to not do that
* Generator state transform introduces a `assert` which may unwind, and its drop elaboration also introduces many new `UnwindAction`s, so in this case run the AbortUnwindingCalls after the transformation.

I believe this PR should also fix Rust-for-Linux/linux#1016, cc `@ojeda`

r? `@Nilstrieb`
  • Loading branch information
bors committed Aug 20, 2023
2 parents b6ab01a + 0a7202d commit ff55fa3
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 15 deletions.
50 changes: 42 additions & 8 deletions compiler/rustc_const_eval/src/transform/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand All @@ -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();
Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -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 => (),
Expand Down Expand Up @@ -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 => {
Expand Down Expand Up @@ -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 {
Expand Down
23 changes: 23 additions & 0 deletions compiler/rustc_mir_transform/src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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::{
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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(()));
}

Expand Down
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
Expand Up @@ -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 {
Expand Down Expand Up @@ -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();
Expand Down
11 changes: 10 additions & 1 deletion compiler/rustc_mir_transform/src/shim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
);

return body;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
2 changes: 1 addition & 1 deletion tests/run-make/panic-abort-eh_frame/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Expand All @@ -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));
}

0 comments on commit ff55fa3

Please sign in to comment.