Skip to content

Commit f1a6ce1

Browse files
Rollup merge of rust-lang#151771 - hoodmane:wasm-double-panic, r=bjorn3
Fix: On wasm targets, call `panic_in_cleanup` if panic occurs in cleanup Previously this was not correctly implemented. Each funclet may need its own terminate block, so this changes the `terminate_block` into a `terminate_blocks` `IndexVec` which can have a terminate_block for each funclet. We key on the first basic block of the funclet -- in particular, this is the start block for the old case of the top level terminate function. I also fixed the `terminate` handler to not be invoked when a foreign exception is raised, mimicking the behavior from msvc. On wasm, in order to avoid generating a `catch_all` we need to call `llvm.wasm.get.exception` and `llvm.wasm.get.ehselector`.
2 parents 458795c + acbfd79 commit f1a6ce1

7 files changed

Lines changed: 138 additions & 24 deletions

File tree

compiler/rustc_codegen_gcc/src/builder.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1655,6 +1655,10 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> {
16551655
unimplemented!();
16561656
}
16571657

1658+
fn get_funclet_cleanuppad(&self, _funclet: &Funclet) -> RValue<'gcc> {
1659+
unimplemented!();
1660+
}
1661+
16581662
// Atomic Operations
16591663
fn atomic_cmpxchg(
16601664
&mut self,

compiler/rustc_codegen_llvm/src/builder.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1296,6 +1296,10 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
12961296
ret
12971297
}
12981298

1299+
fn get_funclet_cleanuppad(&self, funclet: &Funclet<'ll>) -> &'ll Value {
1300+
funclet.cleanuppad()
1301+
}
1302+
12991303
// Atomic Operations
13001304
fn atomic_cmpxchg(
13011305
&mut self,

compiler/rustc_codegen_ssa/src/mir/block.rs

Lines changed: 77 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -215,19 +215,18 @@ impl<'a, 'tcx> TerminatorCodegenHelper<'tcx> {
215215
mir::UnwindAction::Continue => None,
216216
mir::UnwindAction::Unreachable => None,
217217
mir::UnwindAction::Terminate(reason) => {
218-
if fx.mir[self.bb].is_cleanup && base::wants_new_eh_instructions(fx.cx.tcx().sess) {
218+
if fx.mir[self.bb].is_cleanup && base::wants_wasm_eh(fx.cx.tcx().sess) {
219+
// For wasm, we need to generate a nested `cleanuppad within %outer_pad`
220+
// to catch exceptions during cleanup and call `panic_in_cleanup`.
221+
Some(fx.terminate_block(reason, Some(self.bb)))
222+
} else if fx.mir[self.bb].is_cleanup
223+
&& base::wants_new_eh_instructions(fx.cx.tcx().sess)
224+
{
219225
// MSVC SEH will abort automatically if an exception tries to
220226
// propagate out from cleanup.
221-
222-
// FIXME(@mirkootter): For wasm, we currently do not support terminate during
223-
// cleanup, because this requires a few more changes: The current code
224-
// caches the `terminate_block` for each function; funclet based code - however -
225-
// requires a different terminate_block for each funclet
226-
// Until this is implemented, we just do not unwind inside cleanup blocks
227-
228227
None
229228
} else {
230-
Some(fx.terminate_block(reason))
229+
Some(fx.terminate_block(reason, None))
231230
}
232231
}
233232
};
@@ -239,7 +238,7 @@ impl<'a, 'tcx> TerminatorCodegenHelper<'tcx> {
239238

240239
if let Some(unwind_block) = unwind_block {
241240
let ret_llbb = if let Some((_, target)) = destination {
242-
fx.llbb(target)
241+
self.llbb_with_cleanup(fx, target)
243242
} else {
244243
fx.unreachable_block()
245244
};
@@ -310,15 +309,15 @@ impl<'a, 'tcx> TerminatorCodegenHelper<'tcx> {
310309
) -> MergingSucc {
311310
let unwind_target = match unwind {
312311
mir::UnwindAction::Cleanup(cleanup) => Some(self.llbb_with_cleanup(fx, cleanup)),
313-
mir::UnwindAction::Terminate(reason) => Some(fx.terminate_block(reason)),
312+
mir::UnwindAction::Terminate(reason) => Some(fx.terminate_block(reason, None)),
314313
mir::UnwindAction::Continue => None,
315314
mir::UnwindAction::Unreachable => None,
316315
};
317316

318317
if operands.iter().any(|x| matches!(x, InlineAsmOperandRef::Label { .. })) {
319318
assert!(unwind_target.is_none());
320319
let ret_llbb = if let Some(target) = destination {
321-
fx.llbb(target)
320+
self.llbb_with_cleanup(fx, target)
322321
} else {
323322
fx.unreachable_block()
324323
};
@@ -335,7 +334,7 @@ impl<'a, 'tcx> TerminatorCodegenHelper<'tcx> {
335334
MergingSucc::False
336335
} else if let Some(cleanup) = unwind_target {
337336
let ret_llbb = if let Some(target) = destination {
338-
fx.llbb(target)
337+
self.llbb_with_cleanup(fx, target)
339338
} else {
340339
fx.unreachable_block()
341340
};
@@ -1830,8 +1829,39 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
18301829
})
18311830
}
18321831

1833-
fn terminate_block(&mut self, reason: UnwindTerminateReason) -> Bx::BasicBlock {
1834-
if let Some((cached_bb, cached_reason)) = self.terminate_block
1832+
fn terminate_block(
1833+
&mut self,
1834+
reason: UnwindTerminateReason,
1835+
outer_catchpad_bb: Option<mir::BasicBlock>,
1836+
) -> Bx::BasicBlock {
1837+
// mb_funclet_bb should be present if and only if the target is wasm and
1838+
// we're terminating because of an unwind in a cleanup block. In that
1839+
// case we have nested funclets and the inner catch_switch needs to know
1840+
// what outer catch_pad it is contained in.
1841+
debug_assert!(
1842+
outer_catchpad_bb.is_some()
1843+
== (base::wants_wasm_eh(self.cx.tcx().sess)
1844+
&& reason == UnwindTerminateReason::InCleanup)
1845+
);
1846+
1847+
// When we aren't in a wasm InCleanup block, there's only one terminate
1848+
// block needed so we cache at START_BLOCK index.
1849+
let mut cache_bb = mir::START_BLOCK;
1850+
// In wasm eh InCleanup, use the outer funclet's cleanup BB as the cache
1851+
// key.
1852+
if let Some(outer_bb) = outer_catchpad_bb {
1853+
let cleanup_kinds =
1854+
self.cleanup_kinds.as_ref().expect("cleanup_kinds required for funclets");
1855+
cache_bb = cleanup_kinds[outer_bb]
1856+
.funclet_bb(outer_bb)
1857+
.expect("funclet_bb should be in a funclet");
1858+
1859+
// Ensure the outer funclet is created first
1860+
if self.funclets[cache_bb].is_none() {
1861+
self.landing_pad_for(cache_bb);
1862+
}
1863+
}
1864+
if let Some((cached_bb, cached_reason)) = self.terminate_blocks[cache_bb]
18351865
&& reason == cached_reason
18361866
{
18371867
return cached_bb;
@@ -1869,12 +1899,35 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
18691899
// cp_terminate:
18701900
// %cp = catchpad within %cs [null, i32 64, null]
18711901
// ...
1902+
//
1903+
// By contrast, on WebAssembly targets, we specifically _do_ want to
1904+
// catch foreign exceptions. The situation with MSVC is a
1905+
// regrettable hack which we don't want to extend to other targets
1906+
// unless necessary. For WebAssembly, to generate catch(...) and
1907+
// catch only C++ exception instead of generating a catch_all, we
1908+
// need to call the intrinsics @llvm.wasm.get.exception and
1909+
// @llvm.wasm.get.ehselector in the catch pad. Since we don't do
1910+
// this, we generate a catch_all. We originally got this behavior
1911+
// by accident but it luckily matches our intention.
18721912

18731913
llbb = Bx::append_block(self.cx, self.llfn, "cs_terminate");
1874-
let cp_llbb = Bx::append_block(self.cx, self.llfn, "cp_terminate");
18751914

18761915
let mut cs_bx = Bx::build(self.cx, llbb);
1877-
let cs = cs_bx.catch_switch(None, None, &[cp_llbb]);
1916+
1917+
// For wasm InCleanup blocks, our catch_switch is nested within the
1918+
// outer catchpad, so we need to provide it as the parent value to
1919+
// catch_switch.
1920+
let mut outer_cleanuppad = None;
1921+
if outer_catchpad_bb.is_some() {
1922+
// Get the outer funclet's catchpad
1923+
let outer_funclet = self.funclets[cache_bb]
1924+
.as_ref()
1925+
.expect("landing_pad_for didn't create funclet");
1926+
outer_cleanuppad = Some(cs_bx.get_funclet_cleanuppad(outer_funclet));
1927+
}
1928+
let cp_llbb = Bx::append_block(self.cx, self.llfn, "cp_terminate");
1929+
let cs = cs_bx.catch_switch(outer_cleanuppad, None, &[cp_llbb]);
1930+
drop(cs_bx);
18781931

18791932
bx = Bx::build(self.cx, cp_llbb);
18801933
let null =
@@ -1895,13 +1948,18 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
18951948
} else {
18961949
// Specifying more arguments than necessary usually doesn't
18971950
// hurt, but the `WasmEHPrepare` LLVM pass does not recognize
1898-
// anything other than a single `null` as a `catch (...)` block,
1951+
// anything other than a single `null` as a `catch_all` block,
18991952
// leading to problems down the line during instruction
19001953
// selection.
19011954
&[null] as &[_]
19021955
};
19031956

19041957
funclet = Some(bx.catch_pad(cs, args));
1958+
// On wasm, if we wanted to generate a catch(...) and only catch C++
1959+
// exceptions, we'd call @llvm.wasm.get.exception and
1960+
// @llvm.wasm.get.ehselector selectors here. We want a catch_all so
1961+
// we leave them out. This is intentionally diverging from the MSVC
1962+
// behavior.
19051963
} else {
19061964
llbb = Bx::append_block(self.cx, self.llfn, "terminate");
19071965
bx = Bx::build(self.cx, llbb);
@@ -1927,7 +1985,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
19271985

19281986
bx.unreachable();
19291987

1930-
self.terminate_block = Some((llbb, reason));
1988+
self.terminate_blocks[cache_bb] = Some((llbb, reason));
19311989
llbb
19321990
}
19331991

compiler/rustc_codegen_ssa/src/mir/mod.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,11 @@ pub struct FunctionCx<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> {
9090
/// Cached unreachable block
9191
unreachable_block: Option<Bx::BasicBlock>,
9292

93-
/// Cached terminate upon unwinding block and its reason
94-
terminate_block: Option<(Bx::BasicBlock, UnwindTerminateReason)>,
93+
/// Cached terminate upon unwinding block and its reason. For non-wasm
94+
/// targets, there is at most one such block per function, stored at index
95+
/// `START_BLOCK`. For wasm targets, each funclet needs its own terminate
96+
/// block, indexed by the cleanup block that is the funclet's head.
97+
terminate_blocks: IndexVec<mir::BasicBlock, Option<(Bx::BasicBlock, UnwindTerminateReason)>>,
9598

9699
/// A bool flag for each basic block indicating whether it is a cold block.
97100
/// A cold block is a block that is unlikely to be executed at runtime.
@@ -227,7 +230,7 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
227230
personality_slot: None,
228231
cached_llbbs,
229232
unreachable_block: None,
230-
terminate_block: None,
233+
terminate_blocks: IndexVec::from_elem(None, &mir.basic_blocks),
231234
cleanup_kinds,
232235
landing_pads: IndexVec::from_elem(None, &mir.basic_blocks),
233236
funclets: IndexVec::from_fn_n(|_| None, mir.basic_blocks.len()),

compiler/rustc_codegen_ssa/src/traits/builder.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -552,12 +552,12 @@ pub trait BuilderMethods<'a, 'tcx>:
552552

553553
fn set_personality_fn(&mut self, personality: Self::Function);
554554

555-
// These are used by everyone except msvc
555+
// These are used by everyone except msvc and wasm EH
556556
fn cleanup_landing_pad(&mut self, pers_fn: Self::Function) -> (Self::Value, Self::Value);
557557
fn filter_landing_pad(&mut self, pers_fn: Self::Function);
558558
fn resume(&mut self, exn0: Self::Value, exn1: Self::Value);
559559

560-
// These are used only by msvc
560+
// These are used by msvc and wasm EH
561561
fn cleanup_pad(&mut self, parent: Option<Self::Value>, args: &[Self::Value]) -> Self::Funclet;
562562
fn cleanup_ret(&mut self, funclet: &Self::Funclet, unwind: Option<Self::BasicBlock>);
563563
fn catch_pad(&mut self, parent: Self::Value, args: &[Self::Value]) -> Self::Funclet;
@@ -567,6 +567,7 @@ pub trait BuilderMethods<'a, 'tcx>:
567567
unwind: Option<Self::BasicBlock>,
568568
handlers: &[Self::BasicBlock],
569569
) -> Self::Value;
570+
fn get_funclet_cleanuppad(&self, funclet: &Self::Funclet) -> Self::Value;
570571

571572
fn atomic_cmpxchg(
572573
&mut self,
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//@ compile-flags: -C panic=unwind -Copt-level=0
2+
//@ needs-unwind
3+
//@ only-wasm32
4+
5+
#![crate_type = "lib"]
6+
7+
// Test that `panic_in_cleanup` is called on webassembly targets when a panic
8+
// occurs in a destructor during unwinding.
9+
10+
extern "Rust" {
11+
fn may_panic();
12+
}
13+
14+
struct PanicOnDrop;
15+
16+
impl Drop for PanicOnDrop {
17+
fn drop(&mut self) {
18+
unsafe { may_panic() }
19+
}
20+
}
21+
22+
// CHECK-LABEL: @double_panic
23+
// CHECK: invoke void @may_panic()
24+
// CHECK: invoke void @{{.+}}drop_in_place{{.+}}
25+
// CHECK: unwind label %[[TERMINATE:.*]]
26+
//
27+
// CHECK: [[TERMINATE]]:
28+
// CHECK: call void @{{.*panic_in_cleanup}}
29+
// CHECK: unreachable
30+
#[no_mangle]
31+
pub fn double_panic() {
32+
let _guard = PanicOnDrop;
33+
unsafe { may_panic() }
34+
}

tests/codegen-llvm/terminating-catchpad.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
// Ensure a catch-all generates:
1010
// - `catchpad ... [ptr null]` on Wasm (otherwise LLVM gets confused)
1111
// - `catchpad ... [ptr null, i32 64, ptr null]` on Windows (otherwise we catch SEH exceptions)
12+
//
13+
// Unlike on windows, on Wasm, we specifically do want to catch foreign
14+
// exceptions. To catch only C++ exceptions we'd need to call
15+
// @llvm.wasm.get.exception and @llvm.wasm.get.ehselector in the catchpad.
1216

1317
#![feature(no_core, lang_items, rustc_attrs)]
1418
#![crate_type = "lib"]
@@ -36,8 +40,14 @@ fn panic_cannot_unwind() -> ! {
3640
#[no_mangle]
3741
#[rustc_nounwind]
3842
pub fn doesnt_unwind() {
43+
// CHECK: catchswitch within none [label %{{.*}}] unwind to caller
3944
// emscripten: %catchpad = catchpad within %catchswitch [ptr null]
4045
// wasi: %catchpad = catchpad within %catchswitch [ptr null]
4146
// seh: %catchpad = catchpad within %catchswitch [ptr null, i32 64, ptr null]
47+
//
48+
// We don't call these intrinsics on wasm targets so we generate a catch_all
49+
// instruction which also picks up foreign exceptions
50+
// NOT: @llvm.wasm.get.exception
51+
// NOT: @llvm.wasm.get.ehselector
4252
unwinds();
4353
}

0 commit comments

Comments
 (0)