Skip to content

Commit c0b15cc

Browse files
committed
Auto merge of #77242 - ecstatic-morse:dataflow-switch-int, r=jonas-schievink
Replace `discriminant_switch_effect` with more general version #68528 added a new edge-specific effect for `SwitchInt` terminators, `discriminant_switch_effect`, to the dataflow framework. While this accomplished the short-term goal of making drop elaboration more precise, it wasn't really useful in other contexts: It only supported `SwitchInt`s on the discriminant of an `enum` and did not allow effects to be applied along the "otherwise" branch. In const-propagation, for example, arbitrary edge-specific effects for the targets of a `SwitchInt` can be used to remember the value a `match` scrutinee must have in each arm. This PR replaces `discriminant_switch_effect` with a more general `switch_int_edge_effects` method. The new method has a slightly different interface from the other edge-specific effect methods (e.g. `call_return_effect`). This divergence is explained in the new method's documentation, and reading the changes to the various dataflow impls as well as `direction.rs` should further clarify things. This PR should not change behavior.
2 parents 7f7a1cb + c0cd1b0 commit c0b15cc

File tree

3 files changed

+228
-132
lines changed

3 files changed

+228
-132
lines changed

compiler/rustc_mir/src/dataflow/framework/direction.rs

+68-78
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use rustc_index::bit_set::BitSet;
22
use rustc_middle::mir::{self, BasicBlock, Location};
3-
use rustc_middle::ty::{self, TyCtxt};
3+
use rustc_middle::ty::TyCtxt;
44
use std::ops::RangeInclusive;
55

66
use super::visitor::{ResultsVisitable, ResultsVisitor};
7-
use super::{Analysis, Effect, EffectIndex, GenKillAnalysis, GenKillSet};
7+
use super::{Analysis, Effect, EffectIndex, GenKillAnalysis, GenKillSet, SwitchIntTarget};
88

99
pub trait Direction {
1010
fn is_forward() -> bool;
@@ -425,8 +425,8 @@ impl Direction for Forward {
425425

426426
fn join_state_into_successors_of<A>(
427427
analysis: &A,
428-
tcx: TyCtxt<'tcx>,
429-
body: &mir::Body<'tcx>,
428+
_tcx: TyCtxt<'tcx>,
429+
_body: &mir::Body<'tcx>,
430430
dead_unwinds: Option<&BitSet<BasicBlock>>,
431431
exit_state: &mut A::Domain,
432432
(bb, bb_data): (BasicBlock, &'_ mir::BasicBlockData<'tcx>),
@@ -489,88 +489,78 @@ impl Direction for Forward {
489489
}
490490

491491
SwitchInt { ref targets, ref values, ref discr, switch_ty: _ } => {
492-
let enum_ = discr
493-
.place()
494-
.and_then(|discr| switch_on_enum_discriminant(tcx, &body, bb_data, discr));
495-
match enum_ {
496-
// If this is a switch on an enum discriminant, a custom effect may be applied
497-
// along each outgoing edge.
498-
Some((enum_place, enum_def)) => {
499-
// MIR building adds discriminants to the `values` array in the same order as they
500-
// are yielded by `AdtDef::discriminants`. We rely on this to match each
501-
// discriminant in `values` to its corresponding variant in linear time.
502-
let mut tmp = analysis.bottom_value(body);
503-
let mut discriminants = enum_def.discriminants(tcx);
504-
for (value, target) in values.iter().zip(targets.iter().copied()) {
505-
let (variant_idx, _) =
506-
discriminants.find(|&(_, discr)| discr.val == *value).expect(
507-
"Order of `AdtDef::discriminants` differed \
508-
from that of `SwitchInt::values`",
509-
);
510-
511-
tmp.clone_from(exit_state);
512-
analysis.apply_discriminant_switch_effect(
513-
&mut tmp,
514-
bb,
515-
enum_place,
516-
enum_def,
517-
variant_idx,
518-
);
519-
propagate(target, &tmp);
520-
}
521-
522-
// Move out of `tmp` so we don't accidentally use it below.
523-
std::mem::drop(tmp);
524-
525-
// Propagate dataflow state along the "otherwise" edge.
526-
let otherwise = targets.last().copied().unwrap();
527-
propagate(otherwise, exit_state)
528-
}
529-
530-
// Otherwise, it's just a normal `SwitchInt`, and every successor sees the same
531-
// exit state.
532-
None => {
533-
for target in targets.iter().copied() {
534-
propagate(target, exit_state);
535-
}
492+
let mut applier = SwitchIntEdgeEffectApplier {
493+
exit_state,
494+
targets: targets.as_ref(),
495+
values: values.as_ref(),
496+
propagate,
497+
effects_applied: false,
498+
};
499+
500+
analysis.apply_switch_int_edge_effects(bb, discr, &mut applier);
501+
502+
let SwitchIntEdgeEffectApplier {
503+
exit_state, mut propagate, effects_applied, ..
504+
} = applier;
505+
506+
if !effects_applied {
507+
for &target in targets.iter() {
508+
propagate(target, exit_state);
536509
}
537510
}
538511
}
539512
}
540513
}
541514
}
542515

543-
/// Inspect a `SwitchInt`-terminated basic block to see if the condition of that `SwitchInt` is
544-
/// an enum discriminant.
545-
///
546-
/// We expect such blocks to have a call to `discriminant` as their last statement like so:
547-
/// _42 = discriminant(_1)
548-
/// SwitchInt(_42, ..)
549-
///
550-
/// If the basic block matches this pattern, this function returns the place corresponding to the
551-
/// enum (`_1` in the example above) as well as the `AdtDef` of that enum.
552-
fn switch_on_enum_discriminant(
553-
tcx: TyCtxt<'tcx>,
554-
body: &'mir mir::Body<'tcx>,
555-
block: &'mir mir::BasicBlockData<'tcx>,
556-
switch_on: mir::Place<'tcx>,
557-
) -> Option<(mir::Place<'tcx>, &'tcx ty::AdtDef)> {
558-
match block.statements.last().map(|stmt| &stmt.kind) {
559-
Some(mir::StatementKind::Assign(box (lhs, mir::Rvalue::Discriminant(discriminated))))
560-
if *lhs == switch_on =>
561-
{
562-
match &discriminated.ty(body, tcx).ty.kind() {
563-
ty::Adt(def, _) => Some((*discriminated, def)),
564-
565-
// `Rvalue::Discriminant` is also used to get the active yield point for a
566-
// generator, but we do not need edge-specific effects in that case. This may
567-
// change in the future.
568-
ty::Generator(..) => None,
569-
570-
t => bug!("`discriminant` called on unexpected type {:?}", t),
571-
}
516+
struct SwitchIntEdgeEffectApplier<'a, D, F> {
517+
exit_state: &'a mut D,
518+
values: &'a [u128],
519+
targets: &'a [BasicBlock],
520+
propagate: F,
521+
522+
effects_applied: bool,
523+
}
524+
525+
impl<D, F> super::SwitchIntEdgeEffects<D> for SwitchIntEdgeEffectApplier<'_, D, F>
526+
where
527+
D: Clone,
528+
F: FnMut(BasicBlock, &D),
529+
{
530+
fn apply(&mut self, mut apply_edge_effect: impl FnMut(&mut D, SwitchIntTarget)) {
531+
assert!(!self.effects_applied);
532+
533+
let mut tmp = None;
534+
for (&value, &target) in self.values.iter().zip(self.targets.iter()) {
535+
let tmp = opt_clone_from_or_clone(&mut tmp, self.exit_state);
536+
apply_edge_effect(tmp, SwitchIntTarget { value: Some(value), target });
537+
(self.propagate)(target, tmp);
572538
}
573539

574-
_ => None,
540+
// Once we get to the final, "otherwise" branch, there is no need to preserve `exit_state`,
541+
// so pass it directly to `apply_edge_effect` to save a clone of the dataflow state.
542+
let otherwise = self.targets.last().copied().unwrap();
543+
apply_edge_effect(self.exit_state, SwitchIntTarget { value: None, target: otherwise });
544+
(self.propagate)(otherwise, self.exit_state);
545+
546+
self.effects_applied = true;
547+
}
548+
}
549+
550+
/// An analogue of `Option::get_or_insert_with` that stores a clone of `val` into `opt`, but uses
551+
/// the more efficient `clone_from` if `opt` was `Some`.
552+
///
553+
/// Returns a mutable reference to the new clone that resides in `opt`.
554+
//
555+
// FIXME: Figure out how to express this using `Option::clone_from`, or maybe lift it into the
556+
// standard library?
557+
fn opt_clone_from_or_clone<T: Clone>(opt: &'a mut Option<T>, val: &T) -> &'a mut T {
558+
if opt.is_some() {
559+
let ret = opt.as_mut().unwrap();
560+
ret.clone_from(val);
561+
ret
562+
} else {
563+
*opt = Some(val.clone());
564+
opt.as_mut().unwrap()
575565
}
576566
}

compiler/rustc_mir/src/dataflow/framework/mod.rs

+44-21
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,7 @@ use rustc_hir::def_id::DefId;
3737
use rustc_index::bit_set::{BitSet, HybridBitSet};
3838
use rustc_index::vec::Idx;
3939
use rustc_middle::mir::{self, BasicBlock, Location};
40-
use rustc_middle::ty::{self, TyCtxt};
41-
use rustc_target::abi::VariantIdx;
40+
use rustc_middle::ty::TyCtxt;
4241

4342
mod cursor;
4443
mod direction;
@@ -152,6 +151,8 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> {
152151
) {
153152
}
154153

154+
/* Edge-specific effects */
155+
155156
/// Updates the current dataflow state with the effect of a successful return from a `Call`
156157
/// terminator.
157158
///
@@ -183,20 +184,28 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> {
183184
/// Updates the current dataflow state with the effect of taking a particular branch in a
184185
/// `SwitchInt` terminator.
185186
///
186-
/// Much like `apply_call_return_effect`, this effect is only propagated along a single
187-
/// outgoing edge from this basic block.
187+
/// Unlike the other edge-specific effects, which are allowed to mutate `Self::Domain`
188+
/// directly, overriders of this method must pass a callback to
189+
/// `SwitchIntEdgeEffects::apply`. The callback will be run once for each outgoing edge and
190+
/// will have access to the dataflow state that will be propagated along that edge.
191+
///
192+
/// This interface is somewhat more complex than the other visitor-like "effect" methods.
193+
/// However, it is both more ergonomic—callers don't need to recompute or cache information
194+
/// about a given `SwitchInt` terminator for each one of its edges—and more efficient—the
195+
/// engine doesn't need to clone the exit state for a block unless
196+
/// `SwitchIntEdgeEffects::apply` is actually called.
188197
///
189198
/// FIXME: This class of effects is not supported for backward dataflow analyses.
190-
fn apply_discriminant_switch_effect(
199+
fn apply_switch_int_edge_effects(
191200
&self,
192-
_state: &mut Self::Domain,
193201
_block: BasicBlock,
194-
_enum_place: mir::Place<'tcx>,
195-
_adt: &ty::AdtDef,
196-
_variant: VariantIdx,
202+
_discr: &mir::Operand<'tcx>,
203+
_apply_edge_effects: &mut impl SwitchIntEdgeEffects<Self::Domain>,
197204
) {
198205
}
199206

207+
/* Extension methods */
208+
200209
/// Creates an `Engine` to find the fixpoint for this dataflow problem.
201210
///
202211
/// You shouldn't need to override this outside this module, since the combination of the
@@ -267,6 +276,8 @@ pub trait GenKillAnalysis<'tcx>: Analysis<'tcx> {
267276
) {
268277
}
269278

279+
/* Edge-specific effects */
280+
270281
/// See `Analysis::apply_call_return_effect`.
271282
fn call_return_effect(
272283
&self,
@@ -286,14 +297,12 @@ pub trait GenKillAnalysis<'tcx>: Analysis<'tcx> {
286297
) {
287298
}
288299

289-
/// See `Analysis::apply_discriminant_switch_effect`.
290-
fn discriminant_switch_effect(
300+
/// See `Analysis::apply_switch_int_edge_effects`.
301+
fn switch_int_edge_effects<G: GenKill<Self::Idx>>(
291302
&self,
292-
_state: &mut impl GenKill<Self::Idx>,
293303
_block: BasicBlock,
294-
_enum_place: mir::Place<'tcx>,
295-
_adt: &ty::AdtDef,
296-
_variant: VariantIdx,
304+
_discr: &mir::Operand<'tcx>,
305+
_edge_effects: &mut impl SwitchIntEdgeEffects<G>,
297306
) {
298307
}
299308
}
@@ -339,6 +348,8 @@ where
339348
self.before_terminator_effect(state, terminator, location);
340349
}
341350

351+
/* Edge-specific effects */
352+
342353
fn apply_call_return_effect(
343354
&self,
344355
state: &mut A::Domain,
@@ -359,17 +370,17 @@ where
359370
self.yield_resume_effect(state, resume_block, resume_place);
360371
}
361372

362-
fn apply_discriminant_switch_effect(
373+
fn apply_switch_int_edge_effects(
363374
&self,
364-
state: &mut A::Domain,
365375
block: BasicBlock,
366-
enum_place: mir::Place<'tcx>,
367-
adt: &ty::AdtDef,
368-
variant: VariantIdx,
376+
discr: &mir::Operand<'tcx>,
377+
edge_effects: &mut impl SwitchIntEdgeEffects<A::Domain>,
369378
) {
370-
self.discriminant_switch_effect(state, block, enum_place, adt, variant);
379+
self.switch_int_edge_effects(block, discr, edge_effects);
371380
}
372381

382+
/* Extension methods */
383+
373384
fn into_engine(
374385
self,
375386
tcx: TyCtxt<'tcx>,
@@ -531,5 +542,17 @@ impl EffectIndex {
531542
}
532543
}
533544

545+
pub struct SwitchIntTarget {
546+
pub value: Option<u128>,
547+
pub target: BasicBlock,
548+
}
549+
550+
/// A type that records the edge-specific effects for a `SwitchInt` terminator.
551+
pub trait SwitchIntEdgeEffects<D> {
552+
/// Calls `apply_edge_effect` for each outgoing edge from a `SwitchInt` terminator and
553+
/// records the results.
554+
fn apply(&mut self, apply_edge_effect: impl FnMut(&mut D, SwitchIntTarget));
555+
}
556+
534557
#[cfg(test)]
535558
mod tests;

0 commit comments

Comments
 (0)