Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit ff55fa3

Browse files
committedAug 20, 2023
Auto merge of #113124 - nbdd0121:eh_frame, r=cjgillot
Add MIR validation for unwind out from nounwind functions + fixes to make validation pass `@Nilstrieb` This is the MIR validation you asked in #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`
2 parents b6ab01a + 0a7202d commit ff55fa3

File tree

8 files changed

+115
-15
lines changed

8 files changed

+115
-15
lines changed
 

‎compiler/rustc_const_eval/src/transform/validate.rs

+42-8
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use rustc_mir_dataflow::impls::MaybeStorageLive;
1818
use rustc_mir_dataflow::storage::always_storage_live_locals;
1919
use rustc_mir_dataflow::{Analysis, ResultsCursor};
2020
use rustc_target::abi::{Size, FIRST_VARIANT};
21+
use rustc_target::spec::abi::Abi;
2122

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

62+
let can_unwind = if mir_phase <= MirPhase::Runtime(RuntimePhase::Initial) {
63+
// In this case `AbortUnwindingCalls` haven't yet been executed.
64+
true
65+
} else if !tcx.def_kind(def_id).is_fn_like() {
66+
true
67+
} else {
68+
let body_ty = tcx.type_of(def_id).skip_binder();
69+
let body_abi = match body_ty.kind() {
70+
ty::FnDef(..) => body_ty.fn_sig(tcx).abi(),
71+
ty::Closure(..) => Abi::RustCall,
72+
ty::Generator(..) => Abi::Rust,
73+
_ => {
74+
span_bug!(body.span, "unexpected body ty: {:?} phase {:?}", body_ty, mir_phase)
75+
}
76+
};
77+
78+
ty::layout::fn_can_unwind(tcx, Some(def_id), body_abi)
79+
};
80+
6181
let mut cfg_checker = CfgChecker {
6282
when: &self.when,
6383
body,
@@ -68,6 +88,7 @@ impl<'tcx> MirPass<'tcx> for Validator {
6888
storage_liveness,
6989
place_cache: FxHashSet::default(),
7090
value_cache: FxHashSet::default(),
91+
can_unwind,
7192
};
7293
cfg_checker.visit_body(body);
7394
cfg_checker.check_cleanup_control_flow();
@@ -99,6 +120,9 @@ struct CfgChecker<'a, 'tcx> {
99120
storage_liveness: ResultsCursor<'a, 'tcx, MaybeStorageLive<'static>>,
100121
place_cache: FxHashSet<PlaceRef<'tcx>>,
101122
value_cache: FxHashSet<u128>,
123+
// If `false`, then the MIR must not contain `UnwindAction::Continue` or
124+
// `TerminatorKind::Resume`.
125+
can_unwind: bool,
102126
}
103127

104128
impl<'a, 'tcx> CfgChecker<'a, 'tcx> {
@@ -237,13 +261,17 @@ impl<'a, 'tcx> CfgChecker<'a, 'tcx> {
237261
match unwind {
238262
UnwindAction::Cleanup(unwind) => {
239263
if is_cleanup {
240-
self.fail(location, "unwind on cleanup block");
264+
self.fail(location, "`UnwindAction::Cleanup` in cleanup block");
241265
}
242266
self.check_edge(location, unwind, EdgeKind::Unwind);
243267
}
244268
UnwindAction::Continue => {
245269
if is_cleanup {
246-
self.fail(location, "unwind on cleanup block");
270+
self.fail(location, "`UnwindAction::Continue` in cleanup block");
271+
}
272+
273+
if !self.can_unwind {
274+
self.fail(location, "`UnwindAction::Continue` in no-unwind function");
247275
}
248276
}
249277
UnwindAction::Unreachable | UnwindAction::Terminate => (),
@@ -464,13 +492,19 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> {
464492
);
465493
}
466494
}
467-
TerminatorKind::Resume | TerminatorKind::Terminate => {
495+
TerminatorKind::Resume => {
468496
let bb = location.block;
469497
if !self.body.basic_blocks[bb].is_cleanup {
470-
self.fail(
471-
location,
472-
"Cannot `Resume` or `Terminate` from non-cleanup basic block",
473-
)
498+
self.fail(location, "Cannot `Resume` from non-cleanup basic block")
499+
}
500+
if !self.can_unwind {
501+
self.fail(location, "Cannot `Resume` in a function that cannot unwind")
502+
}
503+
}
504+
TerminatorKind::Terminate => {
505+
let bb = location.block;
506+
if !self.body.basic_blocks[bb].is_cleanup {
507+
self.fail(location, "Cannot `Terminate` from non-cleanup basic block")
474508
}
475509
}
476510
TerminatorKind::Return => {
@@ -665,7 +699,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
665699
return;
666700
};
667701

668-
f_ty.ty
702+
ty::EarlyBinder::bind(f_ty.ty).instantiate(self.tcx, args)
669703
} else {
670704
let Some(&f_ty) = args.as_generator().prefix_tys().get(f.index())
671705
else {

‎compiler/rustc_mir_transform/src/generator.rs

+23
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,10 @@
5050
//! For generators with state 1 (returned) and state 2 (poisoned) it does nothing.
5151
//! Otherwise it drops all the values in scope at the last suspension point.
5252
53+
use crate::abort_unwinding_calls;
5354
use crate::deref_separator::deref_finder;
5455
use crate::errors;
56+
use crate::pass_manager as pm;
5557
use crate::simplify;
5658
use crate::MirPass;
5759
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
@@ -64,6 +66,7 @@ use rustc_index::{Idx, IndexVec};
6466
use rustc_middle::mir::dump_mir;
6567
use rustc_middle::mir::visit::{MutVisitor, PlaceContext, Visitor};
6668
use rustc_middle::mir::*;
69+
use rustc_middle::ty::InstanceDef;
6770
use rustc_middle::ty::{self, AdtDef, Ty, TyCtxt};
6871
use rustc_middle::ty::{GeneratorArgs, GenericArgsRef};
6972
use rustc_mir_dataflow::impls::{
@@ -1147,7 +1150,25 @@ fn create_generator_drop_shim<'tcx>(
11471150
// unrelated code from the resume part of the function
11481151
simplify::remove_dead_blocks(tcx, &mut body);
11491152

1153+
// Update the body's def to become the drop glue.
1154+
// This needs to be updated before the AbortUnwindingCalls pass.
1155+
let gen_instance = body.source.instance;
1156+
let drop_in_place = tcx.require_lang_item(LangItem::DropInPlace, None);
1157+
let drop_instance = InstanceDef::DropGlue(drop_in_place, Some(gen_ty));
1158+
body.source.instance = drop_instance;
1159+
1160+
pm::run_passes_no_validate(
1161+
tcx,
1162+
&mut body,
1163+
&[&abort_unwinding_calls::AbortUnwindingCalls],
1164+
None,
1165+
);
1166+
1167+
// Temporary change MirSource to generator's instance so that dump_mir produces more sensible
1168+
// filename.
1169+
body.source.instance = gen_instance;
11501170
dump_mir(tcx, false, "generator_drop", &0, &body, |_, _| Ok(()));
1171+
body.source.instance = drop_instance;
11511172

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

1341+
pm::run_passes_no_validate(tcx, body, &[&abort_unwinding_calls::AbortUnwindingCalls], None);
1342+
13201343
dump_mir(tcx, false, "generator_resume", &0, body, |_, _| Ok(()));
13211344
}
13221345

‎compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs

+13-3
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use rustc_middle::ty::TyCtxt;
66
use rustc_target::spec::PanicStrategy;
77

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

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

87-
// make sure there's a resume block
87+
// Skip the pass if there are no blocks with a resume terminator.
88+
let has_resume = body
89+
.basic_blocks
90+
.iter_enumerated()
91+
.any(|(_bb, block)| matches!(block.terminator().kind, TerminatorKind::Resume));
92+
if !has_resume {
93+
debug!("remove_noop_landing_pads: no resume block in MIR");
94+
return;
95+
}
96+
97+
// make sure there's a resume block without any statements
8898
let resume_block = {
8999
let mut patch = MirPatch::new(body);
90100
let resume_block = patch.resume_block();

‎compiler/rustc_mir_transform/src/shim.rs

+10-1
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,17 @@ fn make_shim<'tcx>(tcx: TyCtxt<'tcx>, instance: ty::InstanceDef<'tcx>) -> Body<'
7171
// of this function. Is this intentional?
7272
if let Some(ty::Generator(gen_def_id, args, _)) = ty.map(Ty::kind) {
7373
let body = tcx.optimized_mir(*gen_def_id).generator_drop().unwrap();
74-
let body = EarlyBinder::bind(body.clone()).instantiate(tcx, args);
74+
let mut body = EarlyBinder::bind(body.clone()).instantiate(tcx, args);
7575
debug!("make_shim({:?}) = {:?}", instance, body);
76+
77+
// Run empty passes to mark phase change and perform validation.
78+
pm::run_passes(
79+
tcx,
80+
&mut body,
81+
&[],
82+
Some(MirPhase::Runtime(RuntimePhase::Optimized)),
83+
);
84+
7685
return body;
7786
}
7887

‎tests/mir-opt/building/async_await.a-{closure#0}.generator_resume.0.mir

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

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

3636
bb3: {

‎tests/mir-opt/building/async_await.b-{closure#0}.generator_resume.0.mir

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

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

316316
bb29: {

‎tests/run-make/panic-abort-eh_frame/Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66
include ../tools.mk
77

88
all:
9-
$(RUSTC) foo.rs --crate-type=lib --emit=obj=$(TMPDIR)/foo.o -Cpanic=abort
9+
$(RUSTC) foo.rs --crate-type=lib --emit=obj=$(TMPDIR)/foo.o -Cpanic=abort --edition 2021 -Z validate-mir
1010
objdump --dwarf=frames $(TMPDIR)/foo.o | $(CGREP) -v 'DW_CFA'

‎tests/run-make/panic-abort-eh_frame/foo.rs

+24
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
#![no_std]
22

3+
use core::future::Future;
4+
5+
pub struct NeedsDrop;
6+
7+
impl Drop for NeedsDrop {
8+
fn drop(&mut self) {}
9+
}
10+
311
#[panic_handler]
412
fn handler(_: &core::panic::PanicInfo<'_>) -> ! {
513
loop {}
@@ -8,3 +16,19 @@ fn handler(_: &core::panic::PanicInfo<'_>) -> ! {
816
pub unsafe fn oops(x: *const u32) -> u32 {
917
*x
1018
}
19+
20+
pub async fn foo(_: NeedsDrop) {
21+
async fn bar() {}
22+
bar().await;
23+
}
24+
25+
pub fn poll_foo(x: &mut core::task::Context<'_>) {
26+
let _g = NeedsDrop;
27+
let mut p = core::pin::pin!(foo(NeedsDrop));
28+
let _ = p.as_mut().poll(x);
29+
let _ = p.as_mut().poll(x);
30+
}
31+
32+
pub fn drop_foo() {
33+
drop(foo(NeedsDrop));
34+
}

0 commit comments

Comments
 (0)
Failed to load comments.