Skip to content

Commit 747a5d2

Browse files
committed
Auto merge of #85316 - eddyb:cg-ssa-on-demand-cleanuppad, r=nagisa
rustc_codegen_ssa: generate MSVC cleanup pads on demand, like GNU landing pads. This unblocks #84993 in terms of codegen tests, as it brings the MSVC-style (`cleanup_pad`) EH (LLVM) block order in line with the GNU-style (`landing_pad`) EH (LLVM) block order, by having both of them be on-demand (instead of MSVC-style being eager and GNU-style lazy/on-demand). It also unifies the two implementations a bit, similar to #84699, but in the opposite direction (as that attempt made both kinds of EH pads eagerly built). ~~Opening as draft because I haven't done enough Windows testing just yet, of both this PR, and of #84993 rebased on it.~~ (**EDIT**: seems to be working as expected) r? `@nagisa`
2 parents 3f46b82 + cb23a79 commit 747a5d2

File tree

2 files changed

+109
-112
lines changed

2 files changed

+109
-112
lines changed

compiler/rustc_codegen_ssa/src/mir/block.rs

+102-31
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use rustc_middle::ty::{self, Instance, Ty, TypeFoldable};
2020
use rustc_span::source_map::Span;
2121
use rustc_span::{sym, Symbol};
2222
use rustc_target::abi::call::{ArgAbi, FnAbi, PassMode};
23-
use rustc_target::abi::{self, LayoutOf};
23+
use rustc_target::abi::{self, HasDataLayout, LayoutOf};
2424
use rustc_target::spec::abi::Abi;
2525

2626
/// Used by `FunctionCx::codegen_terminator` for emitting common patterns
@@ -32,13 +32,34 @@ struct TerminatorCodegenHelper<'tcx> {
3232
}
3333

3434
impl<'a, 'tcx> TerminatorCodegenHelper<'tcx> {
35-
/// Returns the associated funclet from `FunctionCx::funclets` for the
36-
/// `funclet_bb` member if it is not `None`.
35+
/// Returns the appropriate `Funclet` for the current funclet, if on MSVC,
36+
/// either already previously cached, or newly created, by `landing_pad_for`.
3737
fn funclet<'b, Bx: BuilderMethods<'a, 'tcx>>(
3838
&self,
39-
fx: &'b FunctionCx<'a, 'tcx, Bx>,
39+
fx: &'b mut FunctionCx<'a, 'tcx, Bx>,
4040
) -> Option<&'b Bx::Funclet> {
41-
self.funclet_bb.and_then(|funcl| fx.funclets[funcl].as_ref())
41+
let funclet_bb = self.funclet_bb?;
42+
if base::wants_msvc_seh(fx.cx.tcx().sess) {
43+
// If `landing_pad_for` hasn't been called yet to create the `Funclet`,
44+
// it has to be now. This may not seem necessary, as RPO should lead
45+
// to all the unwind edges being visited (and so to `landing_pad_for`
46+
// getting called for them), before building any of the blocks inside
47+
// the funclet itself - however, if MIR contains edges that end up not
48+
// being needed in the LLVM IR after monomorphization, the funclet may
49+
// be unreachable, and we don't have yet a way to skip building it in
50+
// such an eventuality (which may be a better solution than this).
51+
if fx.funclets[funclet_bb].is_none() {
52+
fx.landing_pad_for(funclet_bb);
53+
}
54+
55+
Some(
56+
fx.funclets[funclet_bb]
57+
.as_ref()
58+
.expect("landing_pad_for didn't also create funclets entry"),
59+
)
60+
} else {
61+
None
62+
}
4263
}
4364

4465
fn lltarget<Bx: BuilderMethods<'a, 'tcx>>(
@@ -54,10 +75,10 @@ impl<'a, 'tcx> TerminatorCodegenHelper<'tcx> {
5475
(Some(f), Some(t_f)) if f == t_f || !base::wants_msvc_seh(fx.cx.tcx().sess) => {
5576
(lltarget, false)
5677
}
57-
// jump *into* cleanup - need a landing pad if GNU
58-
(None, Some(_)) => (fx.landing_pad_to(target), false),
78+
// jump *into* cleanup - need a landing pad if GNU, cleanup pad if MSVC
79+
(None, Some(_)) => (fx.landing_pad_for(target), false),
5980
(Some(_), None) => span_bug!(span, "{:?} - jump out of cleanup?", self.terminator),
60-
(Some(_), Some(_)) => (fx.landing_pad_to(target), true),
81+
(Some(_), Some(_)) => (fx.landing_pad_for(target), true),
6182
}
6283
}
6384

@@ -1170,38 +1191,88 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
11701191
}
11711192
}
11721193

1173-
/// Returns the landing-pad wrapper around the given basic block.
1174-
///
1175-
/// No-op in MSVC SEH scheme.
1176-
fn landing_pad_to(&mut self, target_bb: mir::BasicBlock) -> Bx::BasicBlock {
1177-
if let Some(block) = self.landing_pads[target_bb] {
1178-
return block;
1194+
/// Returns the landing/cleanup pad wrapper around the given basic block.
1195+
// FIXME(eddyb) rename this to `eh_pad_for`.
1196+
fn landing_pad_for(&mut self, bb: mir::BasicBlock) -> Bx::BasicBlock {
1197+
if let Some(landing_pad) = self.landing_pads[bb] {
1198+
return landing_pad;
11791199
}
11801200

1181-
let block = self.blocks[target_bb];
1182-
let landing_pad = self.landing_pad_uncached(block);
1183-
self.landing_pads[target_bb] = Some(landing_pad);
1201+
let landing_pad = self.landing_pad_for_uncached(bb);
1202+
self.landing_pads[bb] = Some(landing_pad);
11841203
landing_pad
11851204
}
11861205

1187-
fn landing_pad_uncached(&mut self, target_bb: Bx::BasicBlock) -> Bx::BasicBlock {
1206+
// FIXME(eddyb) rename this to `eh_pad_for_uncached`.
1207+
fn landing_pad_for_uncached(&mut self, bb: mir::BasicBlock) -> Bx::BasicBlock {
1208+
let llbb = self.blocks[bb];
11881209
if base::wants_msvc_seh(self.cx.sess()) {
1189-
span_bug!(self.mir.span, "landing pad was not inserted?")
1190-
}
1191-
1192-
let mut bx = self.new_block("cleanup");
1210+
let funclet;
1211+
let ret_llbb;
1212+
match self.mir[bb].terminator.as_ref().map(|t| &t.kind) {
1213+
// This is a basic block that we're aborting the program for,
1214+
// notably in an `extern` function. These basic blocks are inserted
1215+
// so that we assert that `extern` functions do indeed not panic,
1216+
// and if they do we abort the process.
1217+
//
1218+
// On MSVC these are tricky though (where we're doing funclets). If
1219+
// we were to do a cleanuppad (like below) the normal functions like
1220+
// `longjmp` would trigger the abort logic, terminating the
1221+
// program. Instead we insert the equivalent of `catch(...)` for C++
1222+
// which magically doesn't trigger when `longjmp` files over this
1223+
// frame.
1224+
//
1225+
// Lots more discussion can be found on #48251 but this codegen is
1226+
// modeled after clang's for:
1227+
//
1228+
// try {
1229+
// foo();
1230+
// } catch (...) {
1231+
// bar();
1232+
// }
1233+
Some(&mir::TerminatorKind::Abort) => {
1234+
let mut cs_bx = self.new_block(&format!("cs_funclet{:?}", bb));
1235+
let mut cp_bx = self.new_block(&format!("cp_funclet{:?}", bb));
1236+
ret_llbb = cs_bx.llbb();
1237+
1238+
let cs = cs_bx.catch_switch(None, None, 1);
1239+
cs_bx.add_handler(cs, cp_bx.llbb());
1240+
1241+
// The "null" here is actually a RTTI type descriptor for the
1242+
// C++ personality function, but `catch (...)` has no type so
1243+
// it's null. The 64 here is actually a bitfield which
1244+
// represents that this is a catch-all block.
1245+
let null = cp_bx.const_null(
1246+
cp_bx.type_i8p_ext(cp_bx.cx().data_layout().instruction_address_space),
1247+
);
1248+
let sixty_four = cp_bx.const_i32(64);
1249+
funclet = cp_bx.catch_pad(cs, &[null, sixty_four, null]);
1250+
cp_bx.br(llbb);
1251+
}
1252+
_ => {
1253+
let mut cleanup_bx = self.new_block(&format!("funclet_{:?}", bb));
1254+
ret_llbb = cleanup_bx.llbb();
1255+
funclet = cleanup_bx.cleanup_pad(None, &[]);
1256+
cleanup_bx.br(llbb);
1257+
}
1258+
}
1259+
self.funclets[bb] = Some(funclet);
1260+
ret_llbb
1261+
} else {
1262+
let mut bx = self.new_block("cleanup");
11931263

1194-
let llpersonality = self.cx.eh_personality();
1195-
let llretty = self.landing_pad_type();
1196-
let lp = bx.landing_pad(llretty, llpersonality, 1);
1197-
bx.set_cleanup(lp);
1264+
let llpersonality = self.cx.eh_personality();
1265+
let llretty = self.landing_pad_type();
1266+
let lp = bx.landing_pad(llretty, llpersonality, 1);
1267+
bx.set_cleanup(lp);
11981268

1199-
let slot = self.get_personality_slot(&mut bx);
1200-
slot.storage_live(&mut bx);
1201-
Pair(bx.extract_value(lp, 0), bx.extract_value(lp, 1)).store(&mut bx, slot);
1269+
let slot = self.get_personality_slot(&mut bx);
1270+
slot.storage_live(&mut bx);
1271+
Pair(bx.extract_value(lp, 0), bx.extract_value(lp, 1)).store(&mut bx, slot);
12021272

1203-
bx.br(target_bb);
1204-
bx.llbb()
1273+
bx.br(llbb);
1274+
bx.llbb()
1275+
}
12051276
}
12061277

12071278
fn landing_pad_type(&self) -> Bx::Type {

compiler/rustc_codegen_ssa/src/mir/mod.rs

+7-81
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
1-
use crate::base;
21
use crate::traits::*;
32
use rustc_errors::ErrorReported;
43
use rustc_middle::mir;
54
use rustc_middle::mir::interpret::ErrorHandled;
65
use rustc_middle::ty::layout::{FnAbiExt, HasTyCtxt, TyAndLayout};
76
use rustc_middle::ty::{self, Instance, Ty, TypeFoldable};
87
use rustc_target::abi::call::{FnAbi, PassMode};
9-
use rustc_target::abi::HasDataLayout;
108

119
use std::iter;
1210

1311
use rustc_index::bit_set::BitSet;
1412
use rustc_index::vec::IndexVec;
1513

16-
use self::analyze::CleanupKind;
1714
use self::debuginfo::{FunctionDebugContext, PerLocalVarDebugInfo};
1815
use self::place::PlaceRef;
1916
use rustc_middle::mir::traversal;
@@ -49,12 +46,13 @@ pub struct FunctionCx<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> {
4946
/// The funclet status of each basic block
5047
cleanup_kinds: IndexVec<mir::BasicBlock, analyze::CleanupKind>,
5148

52-
/// When targeting MSVC, this stores the cleanup info for each funclet
53-
/// BB. This is initialized as we compute the funclets' head block in RPO.
49+
/// When targeting MSVC, this stores the cleanup info for each funclet BB.
50+
/// This is initialized at the same time as the `landing_pads` entry for the
51+
/// funclets' head block, i.e. when needed by an unwind / `cleanup_ret` edge.
5452
funclets: IndexVec<mir::BasicBlock, Option<Bx::Funclet>>,
5553

56-
/// This stores the landing-pad block for a given BB, computed lazily on GNU
57-
/// and eagerly on MSVC.
54+
/// This stores the cached landing/cleanup pad block for a given BB.
55+
// FIXME(eddyb) rename this to `eh_pads`.
5856
landing_pads: IndexVec<mir::BasicBlock, Option<Bx::BasicBlock>>,
5957

6058
/// Cached unreachable block
@@ -165,7 +163,6 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
165163
})
166164
.collect();
167165

168-
let (landing_pads, funclets) = create_funclets(&mir, &mut bx, &cleanup_kinds, &block_bxs);
169166
let mut fx = FunctionCx {
170167
instance,
171168
mir,
@@ -176,8 +173,8 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
176173
blocks: block_bxs,
177174
unreachable_block: None,
178175
cleanup_kinds,
179-
landing_pads,
180-
funclets,
176+
landing_pads: IndexVec::from_elem(None, mir.basic_blocks()),
177+
funclets: IndexVec::from_fn_n(|_| None, mir.basic_blocks().len()),
181178
locals: IndexVec::new(),
182179
debug_context,
183180
per_local_var_debug_info: None,
@@ -273,77 +270,6 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
273270
}
274271
}
275272

276-
fn create_funclets<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
277-
mir: &'tcx mir::Body<'tcx>,
278-
bx: &mut Bx,
279-
cleanup_kinds: &IndexVec<mir::BasicBlock, CleanupKind>,
280-
block_bxs: &IndexVec<mir::BasicBlock, Bx::BasicBlock>,
281-
) -> (
282-
IndexVec<mir::BasicBlock, Option<Bx::BasicBlock>>,
283-
IndexVec<mir::BasicBlock, Option<Bx::Funclet>>,
284-
) {
285-
iter::zip(block_bxs.iter_enumerated(), cleanup_kinds)
286-
.map(|((bb, &llbb), cleanup_kind)| {
287-
match *cleanup_kind {
288-
CleanupKind::Funclet if base::wants_msvc_seh(bx.sess()) => {}
289-
_ => return (None, None),
290-
}
291-
292-
let funclet;
293-
let ret_llbb;
294-
match mir[bb].terminator.as_ref().map(|t| &t.kind) {
295-
// This is a basic block that we're aborting the program for,
296-
// notably in an `extern` function. These basic blocks are inserted
297-
// so that we assert that `extern` functions do indeed not panic,
298-
// and if they do we abort the process.
299-
//
300-
// On MSVC these are tricky though (where we're doing funclets). If
301-
// we were to do a cleanuppad (like below) the normal functions like
302-
// `longjmp` would trigger the abort logic, terminating the
303-
// program. Instead we insert the equivalent of `catch(...)` for C++
304-
// which magically doesn't trigger when `longjmp` files over this
305-
// frame.
306-
//
307-
// Lots more discussion can be found on #48251 but this codegen is
308-
// modeled after clang's for:
309-
//
310-
// try {
311-
// foo();
312-
// } catch (...) {
313-
// bar();
314-
// }
315-
Some(&mir::TerminatorKind::Abort) => {
316-
let mut cs_bx = bx.build_sibling_block(&format!("cs_funclet{:?}", bb));
317-
let mut cp_bx = bx.build_sibling_block(&format!("cp_funclet{:?}", bb));
318-
ret_llbb = cs_bx.llbb();
319-
320-
let cs = cs_bx.catch_switch(None, None, 1);
321-
cs_bx.add_handler(cs, cp_bx.llbb());
322-
323-
// The "null" here is actually a RTTI type descriptor for the
324-
// C++ personality function, but `catch (...)` has no type so
325-
// it's null. The 64 here is actually a bitfield which
326-
// represents that this is a catch-all block.
327-
let null = bx.const_null(
328-
bx.type_i8p_ext(bx.cx().data_layout().instruction_address_space),
329-
);
330-
let sixty_four = bx.const_i32(64);
331-
funclet = cp_bx.catch_pad(cs, &[null, sixty_four, null]);
332-
cp_bx.br(llbb);
333-
}
334-
_ => {
335-
let mut cleanup_bx = bx.build_sibling_block(&format!("funclet_{:?}", bb));
336-
ret_llbb = cleanup_bx.llbb();
337-
funclet = cleanup_bx.cleanup_pad(None, &[]);
338-
cleanup_bx.br(llbb);
339-
}
340-
};
341-
342-
(Some(ret_llbb), Some(funclet))
343-
})
344-
.unzip()
345-
}
346-
347273
/// Produces, for each argument, a `Value` pointing at the
348274
/// argument's value. As arguments are places, these are always
349275
/// indirect.

0 commit comments

Comments
 (0)