Skip to content

Commit f6d0f73

Browse files
committed
MIR: Stop needing an explicit BB for otherwise:unreachable
So many places to update :D For this PR I tried to keep things doing essentially the same thing as before. No new passes to try to use it more, no change to MIR building for exhaustive matches, etc. That said, `UnreachableProp` still picks it up, and thus there's still a bunch of `otherwise` arms removed and `unreachable` blocks that no longer show.
1 parent fd98df8 commit f6d0f73

File tree

64 files changed

+402
-351
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+402
-351
lines changed

compiler/rustc_codegen_cranelift/src/base.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ pub(crate) fn codegen_fn<'tcx>(
135135
bcx,
136136
block_map,
137137
local_map: IndexVec::with_capacity(mir.local_decls.len()),
138+
shared_unreachable: None,
138139
caller_location: None, // set by `codegen_fn_prelude`
139140

140141
clif_comments,
@@ -441,7 +442,7 @@ fn codegen_fn_body(fx: &mut FunctionCx<'_, '_, '_>, start_block: Block) {
441442
assert_eq!(targets.iter().count(), 1);
442443
let (then_value, then_block) = targets.iter().next().unwrap();
443444
let then_block = fx.get_block(then_block);
444-
let else_block = fx.get_block(targets.otherwise());
445+
let else_block = fx.get_switch_block(targets.otherwise());
445446
let test_zero = match then_value {
446447
0 => true,
447448
1 => false,
@@ -472,7 +473,7 @@ fn codegen_fn_body(fx: &mut FunctionCx<'_, '_, '_>, start_block: Block) {
472473
let block = fx.get_block(block);
473474
switch.set_entry(value, block);
474475
}
475-
let otherwise_block = fx.get_block(targets.otherwise());
476+
let otherwise_block = fx.get_switch_block(targets.otherwise());
476477
switch.emit(&mut fx.bcx, discr, otherwise_block);
477478
}
478479
}
@@ -561,6 +562,11 @@ fn codegen_fn_body(fx: &mut FunctionCx<'_, '_, '_>, start_block: Block) {
561562
}
562563
};
563564
}
565+
566+
if let Some(unreachable) = fx.shared_unreachable {
567+
fx.bcx.switch_to_block(unreachable);
568+
fx.bcx.ins().trap(TrapCode::user(1 /* unreachable */).unwrap());
569+
}
564570
}
565571

566572
fn codegen_stmt<'tcx>(

compiler/rustc_codegen_cranelift/src/common.rs

+12
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ pub(crate) struct FunctionCx<'m, 'clif, 'tcx: 'm> {
298298
pub(crate) bcx: FunctionBuilder<'clif>,
299299
pub(crate) block_map: IndexVec<BasicBlock, Block>,
300300
pub(crate) local_map: IndexVec<Local, CPlace<'tcx>>,
301+
pub(crate) shared_unreachable: Option<Block>,
301302

302303
/// When `#[track_caller]` is used, the implicit caller location is stored in this variable.
303304
pub(crate) caller_location: Option<CValue<'tcx>>,
@@ -375,6 +376,17 @@ impl<'tcx> FunctionCx<'_, '_, 'tcx> {
375376
*self.block_map.get(bb).unwrap()
376377
}
377378

379+
pub(crate) fn get_switch_block(&mut self, sa: SwitchAction) -> Block {
380+
match sa {
381+
SwitchAction::Goto(bb) => self.get_block(bb),
382+
SwitchAction::Unreachable => self.unreachable_block(),
383+
}
384+
}
385+
386+
pub(crate) fn unreachable_block(&mut self) -> Block {
387+
*self.shared_unreachable.get_or_insert_with(|| self.bcx.create_block())
388+
}
389+
378390
pub(crate) fn get_local_place(&mut self, local: Local) -> CPlace<'tcx> {
379391
*self.local_map.get(local).unwrap_or_else(|| {
380392
panic!("Local {:?} doesn't exist", local);

compiler/rustc_codegen_ssa/src/mir/block.rs

+22-8
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ use rustc_ast as ast;
55
use rustc_ast::{InlineAsmOptions, InlineAsmTemplatePiece};
66
use rustc_hir::lang_items::LangItem;
77
use rustc_middle::mir::{
8-
self, AssertKind, BasicBlock, InlineAsmMacro, SwitchTargets, UnwindTerminateReason,
8+
self, AssertKind, BasicBlock, InlineAsmMacro, SwitchAction, SwitchTargets,
9+
UnwindTerminateReason,
910
};
1011
use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf, ValidityRequirement};
1112
use rustc_middle::ty::print::{with_no_trimmed_paths, with_no_visible_paths};
@@ -96,6 +97,17 @@ impl<'a, 'tcx> TerminatorCodegenHelper<'tcx> {
9697
}
9798
}
9899

100+
fn llbb_with_cleanup_from_switch_action<Bx: BuilderMethods<'a, 'tcx>>(
101+
&self,
102+
fx: &mut FunctionCx<'a, 'tcx, Bx>,
103+
target: mir::SwitchAction,
104+
) -> Bx::BasicBlock {
105+
match target {
106+
mir::SwitchAction::Unreachable => fx.unreachable_block(),
107+
mir::SwitchAction::Goto(bb) => self.llbb_with_cleanup(fx, bb),
108+
}
109+
}
110+
99111
fn llbb_characteristics<Bx: BuilderMethods<'a, 'tcx>>(
100112
&self,
101113
fx: &mut FunctionCx<'a, 'tcx, Bx>,
@@ -368,7 +380,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
368380
// If our discriminant is a constant we can branch directly
369381
if let Some(const_discr) = bx.const_to_opt_u128(discr_value, false) {
370382
let target = targets.target_for_value(const_discr);
371-
bx.br(helper.llbb_with_cleanup(self, target));
383+
bx.br(helper.llbb_with_cleanup_from_switch_action(self, target));
372384
return;
373385
};
374386

@@ -379,9 +391,12 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
379391
let (test_value, target) = target_iter.next().unwrap();
380392
let otherwise = targets.otherwise();
381393
let lltarget = helper.llbb_with_cleanup(self, target);
382-
let llotherwise = helper.llbb_with_cleanup(self, otherwise);
394+
let llotherwise = helper.llbb_with_cleanup_from_switch_action(self, otherwise);
383395
let target_cold = self.cold_blocks[target];
384-
let otherwise_cold = self.cold_blocks[otherwise];
396+
let otherwise_cold = match otherwise {
397+
SwitchAction::Goto(otherwise) => self.cold_blocks[otherwise],
398+
SwitchAction::Unreachable => true,
399+
};
385400
// If `target_cold == otherwise_cold`, the branches have the same weight
386401
// so there is no expectation. If they differ, the `target` branch is expected
387402
// when the `otherwise` branch is cold.
@@ -406,7 +421,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
406421
}
407422
} else if self.cx.sess().opts.optimize == OptLevel::No
408423
&& target_iter.len() == 2
409-
&& self.mir[targets.otherwise()].is_empty_unreachable()
424+
&& self.mir.basic_blocks.is_empty_unreachable(targets.otherwise())
410425
{
411426
// In unoptimized builds, if there are two normal targets and the `otherwise` target is
412427
// an unreachable BB, emit `br` instead of `switch`. This leaves behind the unreachable
@@ -431,7 +446,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
431446
} else {
432447
bx.switch(
433448
discr_value,
434-
helper.llbb_with_cleanup(self, targets.otherwise()),
449+
helper.llbb_with_cleanup_from_switch_action(self, targets.otherwise()),
435450
target_iter.map(|(value, target)| (value, helper.llbb_with_cleanup(self, target))),
436451
);
437452
}
@@ -1648,11 +1663,10 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
16481663
}
16491664

16501665
fn unreachable_block(&mut self) -> Bx::BasicBlock {
1651-
self.unreachable_block.unwrap_or_else(|| {
1666+
*self.unreachable_block.get_or_insert_with(|| {
16521667
let llbb = Bx::append_block(self.cx, self.llfn, "unreachable");
16531668
let mut bx = Bx::build(self.cx, llbb);
16541669
bx.unreachable();
1655-
self.unreachable_block = Some(llbb);
16561670
llbb
16571671
})
16581672
}

compiler/rustc_const_eval/src/interpret/step.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -478,12 +478,15 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
478478
&ImmTy::from_uint(const_int, discr.layout),
479479
)?;
480480
if res.to_scalar().to_bool()? {
481-
target_block = target;
481+
target_block = mir::SwitchAction::Goto(target);
482482
break;
483483
}
484484
}
485485

486-
self.go_to_block(target_block);
486+
match target_block {
487+
mir::SwitchAction::Goto(target) => self.go_to_block(target),
488+
mir::SwitchAction::Unreachable => throw_ub!(Unreachable),
489+
}
487490
}
488491

489492
Call {

compiler/rustc_middle/src/mir/basic_blocks.rs

+13-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ use rustc_serialize::{Decodable, Decoder, Encodable, Encoder};
99
use smallvec::SmallVec;
1010

1111
use crate::mir::traversal::Postorder;
12-
use crate::mir::{BasicBlock, BasicBlockData, START_BLOCK, Terminator, TerminatorKind};
12+
use crate::mir::{
13+
BasicBlock, BasicBlockData, START_BLOCK, SwitchAction, Terminator, TerminatorKind,
14+
};
1315

1416
#[derive(Clone, TyEncodable, TyDecodable, Debug, HashStable, TypeFoldable, TypeVisitable)]
1517
pub struct BasicBlocks<'tcx> {
@@ -84,7 +86,9 @@ impl<'tcx> BasicBlocks<'tcx> {
8486
for (value, target) in targets.iter() {
8587
switch_sources.entry((target, bb)).or_default().push(Some(value));
8688
}
87-
switch_sources.entry((targets.otherwise(), bb)).or_default().push(None);
89+
if let SwitchAction::Goto(otherwise) = targets.otherwise() {
90+
switch_sources.entry((otherwise, bb)).or_default().push(None);
91+
}
8892
}
8993
}
9094
switch_sources
@@ -122,6 +126,13 @@ impl<'tcx> BasicBlocks<'tcx> {
122126
pub fn invalidate_cfg_cache(&mut self) {
123127
self.cache = Cache::default();
124128
}
129+
130+
pub fn is_empty_unreachable(&self, action: SwitchAction) -> bool {
131+
match action {
132+
SwitchAction::Goto(bb) => self[bb].is_empty_unreachable(),
133+
SwitchAction::Unreachable => true,
134+
}
135+
}
125136
}
126137

127138
impl<'tcx> std::ops::Deref for BasicBlocks<'tcx> {

compiler/rustc_middle/src/mir/pretty.rs

+8-6
Original file line numberDiff line numberDiff line change
@@ -1015,12 +1015,14 @@ impl<'tcx> TerminatorKind<'tcx> {
10151015
| Unreachable
10161016
| CoroutineDrop => vec![],
10171017
Goto { .. } => vec!["".into()],
1018-
SwitchInt { ref targets, .. } => targets
1019-
.values
1020-
.iter()
1021-
.map(|&u| Cow::Owned(u.to_string()))
1022-
.chain(iter::once("otherwise".into()))
1023-
.collect(),
1018+
SwitchInt { ref targets, .. } => {
1019+
let mut labels: Vec<_> =
1020+
targets.values.iter().map(|&u| Cow::Owned(u.to_string())).collect();
1021+
if let SwitchAction::Goto(_) = targets.otherwise() {
1022+
labels.push("otherwise".into());
1023+
}
1024+
labels
1025+
}
10241026
Call { target: Some(_), unwind: UnwindAction::Cleanup(_), .. } => {
10251027
vec!["return".into(), "unwind".into()]
10261028
}

compiler/rustc_middle/src/mir/syntax.rs

+34-4
Original file line numberDiff line numberDiff line change
@@ -959,21 +959,51 @@ pub struct SwitchTargets {
959959
/// are found in the corresponding indices from the `targets` vector.
960960
pub(super) values: SmallVec<[Pu128; 1]>,
961961

962-
/// Possible branch sites. The last element of this vector is used
963-
/// for the otherwise branch, so targets.len() == values.len() + 1
964-
/// should hold.
962+
/// Possible branch sites.
963+
///
964+
/// If `targets.len() == values.len() + 1`, the last element of this vector
965+
/// is used for the otherwise branch in [`SwitchAction::Goto`].
966+
///
967+
/// If `targets.len() == values.len()`, the otherwise branch is
968+
/// [`SwitchAction::Unreachable`].
969+
///
970+
/// You must ensure that we're always in at one of those cases.
965971
//
966972
// This invariant is quite non-obvious and also could be improved.
967973
// One way to make this invariant is to have something like this instead:
968974
//
969975
// branches: Vec<(ConstInt, BasicBlock)>,
970-
// otherwise: Option<BasicBlock> // exhaustive if None
976+
// otherwise: SwitchAction,
971977
//
972978
// However we’ve decided to keep this as-is until we figure a case
973979
// where some other approach seems to be strictly better than other.
974980
pub(super) targets: SmallVec<[BasicBlock; 2]>,
975981
}
976982

983+
/// The action to be taken for a particular input to the [`TerminatorKind::SwitchInt`]
984+
#[derive(Copy, Clone, Debug, PartialEq, Eq, TyEncodable, TyDecodable, Hash, HashStable)]
985+
#[derive(TypeFoldable, TypeVisitable)]
986+
pub enum SwitchAction {
987+
/// Jump to the specified block
988+
Goto(BasicBlock),
989+
/// Triggers undefined behavior
990+
///
991+
/// This is equivalent to jumping to a block with [`TerminatorKind::Unreachable`],
992+
/// but lets us not have to store such a block in the body.
993+
/// It also makes it easier in analysis to know this action is unreachable
994+
/// without needing to have all the basic blocks around to look at.
995+
Unreachable,
996+
}
997+
998+
impl SwitchAction {
999+
pub fn into_terminator<'tcx>(self) -> TerminatorKind<'tcx> {
1000+
match self {
1001+
SwitchAction::Goto(bb) => TerminatorKind::Goto { target: bb },
1002+
SwitchAction::Unreachable => TerminatorKind::Unreachable,
1003+
}
1004+
}
1005+
}
1006+
9771007
/// Action to be taken when a stack unwind happens.
9781008
#[derive(Copy, Clone, Debug, PartialEq, Eq, TyEncodable, TyDecodable, Hash, HashStable)]
9791009
#[derive(TypeFoldable, TypeVisitable)]

compiler/rustc_middle/src/mir/terminator.rs

+27-9
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ impl SwitchTargets {
1414
///
1515
/// The iterator may be empty, in which case the `SwitchInt` instruction is equivalent to
1616
/// `goto otherwise;`.
17-
pub fn new(targets: impl Iterator<Item = (u128, BasicBlock)>, otherwise: BasicBlock) -> Self {
17+
pub fn new(targets: impl Iterator<Item = (u128, BasicBlock)>, otherwise: SwitchAction) -> Self {
1818
let (values, mut targets): (SmallVec<_>, SmallVec<_>) =
1919
targets.map(|(v, t)| (Pu128(v), t)).unzip();
20-
targets.push(otherwise);
20+
if let SwitchAction::Goto(otherwise) = otherwise {
21+
targets.push(otherwise);
22+
}
2123
Self { values, targets }
2224
}
2325

@@ -39,10 +41,17 @@ impl SwitchTargets {
3941
}
4042
}
4143

42-
/// Returns the fallback target that is jumped to when none of the values match the operand.
44+
/// Returns the fallback action when none of the values match the operand.
4345
#[inline]
44-
pub fn otherwise(&self) -> BasicBlock {
45-
*self.targets.last().unwrap()
46+
pub fn otherwise(&self) -> SwitchAction {
47+
debug_assert!(
48+
self.targets.len() == self.values.len() || self.targets.len() == self.values.len() + 1
49+
);
50+
if self.targets.len() > self.values.len() {
51+
SwitchAction::Goto(*self.targets.last().unwrap())
52+
} else {
53+
SwitchAction::Unreachable
54+
}
4655
}
4756

4857
/// Returns an iterator over the switch targets.
@@ -82,8 +91,9 @@ impl SwitchTargets {
8291
/// specific value. This cannot fail, as it'll return the `otherwise`
8392
/// branch if there's not a specific match for the value.
8493
#[inline]
85-
pub fn target_for_value(&self, value: u128) -> BasicBlock {
86-
self.iter().find_map(|(v, t)| (v == value).then_some(t)).unwrap_or_else(|| self.otherwise())
94+
pub fn target_for_value(&self, value: u128) -> SwitchAction {
95+
let specific = self.iter().find_map(|(v, t)| (v == value).then_some(t));
96+
if let Some(specific) = specific { SwitchAction::Goto(specific) } else { self.otherwise() }
8797
}
8898

8999
/// Adds a new target to the switch. But You cannot add an already present value.
@@ -93,8 +103,12 @@ impl SwitchTargets {
93103
if self.values.contains(&value) {
94104
bug!("target value {:?} already present", value);
95105
}
106+
107+
// There may or may not be a fallback in `targets`, so make sure to
108+
// insert the new target at the same index as its value.
109+
let insert_point = self.values.len();
96110
self.values.push(value);
97-
self.targets.insert(self.targets.len() - 1, bb);
111+
self.targets.insert(insert_point, bb);
98112
}
99113

100114
/// Returns true if all targets (including the fallback target) are distinct.
@@ -431,7 +445,11 @@ mod helper {
431445
#[inline]
432446
pub fn successors_for_value(&self, value: u128) -> Successors<'_> {
433447
let target = self.target_for_value(value);
434-
(&[]).into_iter().copied().chain(Some(target))
448+
(&[]).into_iter().copied().chain(if let SwitchAction::Goto(otherwise) = target {
449+
Some(otherwise)
450+
} else {
451+
None
452+
})
435453
}
436454
}
437455

compiler/rustc_mir_build/src/builder/custom/parse/instruction.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ impl<'a, 'tcx> ParseCtxt<'a, 'tcx> {
163163
targets.push(self.parse_block(arm.body)?);
164164
}
165165

166-
Ok(SwitchTargets::new(values.into_iter().zip(targets), otherwise))
166+
Ok(SwitchTargets::new(values.into_iter().zip(targets), SwitchAction::Goto(otherwise)))
167167
}
168168

169169
fn parse_call(&self, args: &[ExprId]) -> PResult<TerminatorKind<'tcx>> {

compiler/rustc_mir_build/src/builder/matches/test.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
9292
None
9393
}
9494
}),
95-
otherwise_block,
95+
SwitchAction::Goto(otherwise_block),
9696
);
9797
debug!("num_enum_variants: {}", adt_def.variants().len());
9898
let discr_ty = adt_def.repr().discr_type().to_ty(self.tcx);
@@ -124,7 +124,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
124124
None
125125
}
126126
}),
127-
otherwise_block,
127+
SwitchAction::Goto(otherwise_block),
128128
);
129129
let terminator = TerminatorKind::SwitchInt {
130130
discr: Operand::Copy(place),

compiler/rustc_mir_dataflow/src/elaborate_drops.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,10 @@ where
591591
succ: BasicBlock,
592592
unwind: Unwind,
593593
) -> BasicBlock {
594+
// FIXME: the arguments here are still using the manual-unreachable;
595+
// consider changing them to allow `SwitchAction::Unreachable` somehow.
596+
debug_assert_eq!(blocks.len(), values.len() + 1);
597+
594598
// If there are multiple variants, then if something
595599
// is present within the enum the discriminant, tracked
596600
// by the rest path, must be initialized.
@@ -609,7 +613,7 @@ where
609613
discr: Operand::Move(discr),
610614
targets: SwitchTargets::new(
611615
values.iter().copied().zip(blocks.iter().copied()),
612-
*blocks.last().unwrap(),
616+
SwitchAction::Goto(*blocks.last().unwrap()),
613617
),
614618
},
615619
}),

0 commit comments

Comments
 (0)