Skip to content

Simplify SwitchInt handling #133328

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 1 addition & 11 deletions compiler/rustc_borrowck/src/dataflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use rustc_mir_dataflow::impls::{
EverInitializedPlaces, EverInitializedPlacesDomain, MaybeUninitializedPlaces,
MaybeUninitializedPlacesDomain,
};
use rustc_mir_dataflow::{Analysis, GenKill, JoinSemiLattice, SwitchIntEdgeEffects};
use rustc_mir_dataflow::{Analysis, GenKill, JoinSemiLattice};
use tracing::debug;

use crate::{BorrowSet, PlaceConflictBias, PlaceExt, RegionInferenceContext, places_conflict};
Expand Down Expand Up @@ -101,16 +101,6 @@ impl<'a, 'tcx> Analysis<'tcx> for Borrowck<'a, 'tcx> {
// This is only reachable from `iterate_to_fixpoint`, which this analysis doesn't use.
unreachable!();
}

fn apply_switch_int_edge_effects(
&mut self,
_block: BasicBlock,
_discr: &mir::Operand<'tcx>,
_apply_edge_effects: &mut impl SwitchIntEdgeEffects<Self::Domain>,
) {
// This is only reachable from `iterate_to_fixpoint`, which this analysis doesn't use.
unreachable!();
}
}

impl JoinSemiLattice for BorrowckDomain {
Expand Down
153 changes: 36 additions & 117 deletions compiler/rustc_mir_dataflow/src/framework/direction.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
use std::ops::RangeInclusive;

use rustc_middle::mir::{
self, BasicBlock, CallReturnPlaces, Location, SwitchTargets, TerminatorEdges,
};
use rustc_middle::mir::{self, BasicBlock, CallReturnPlaces, Location, TerminatorEdges};

use super::visitor::ResultsVisitor;
use super::{Analysis, Effect, EffectIndex, Results, SwitchIntTarget};
Expand Down Expand Up @@ -78,8 +76,6 @@ impl Direction for Backward {
for pred in body.basic_blocks.predecessors()[block].iter().copied() {
match body[pred].terminator().kind {
// Apply terminator-specific edge effects.
//
// FIXME(ecstaticmorse): Avoid cloning the exit state unconditionally.
mir::TerminatorKind::Call { destination, target: Some(dest), .. }
if dest == block =>
{
Expand Down Expand Up @@ -115,18 +111,18 @@ impl Direction for Backward {
}

mir::TerminatorKind::SwitchInt { targets: _, ref discr } => {
let mut applier = BackwardSwitchIntEdgeEffectsApplier {
body,
pred,
exit_state,
block,
propagate: &mut propagate,
effects_applied: false,
};

analysis.apply_switch_int_edge_effects(pred, discr, &mut applier);

if !applier.effects_applied {
if let Some(mut data) = analysis.get_switch_int_data(block, discr) {
let values = &body.basic_blocks.switch_sources()[&(block, pred)];
let targets =
values.iter().map(|&value| SwitchIntTarget { value, target: block });

let mut tmp = analysis.bottom_value(body);
for target in targets {
tmp.clone_from(&exit_state);
analysis.apply_switch_int_edge_effect(&mut data, &mut tmp, target);
propagate(pred, &tmp);
}
} else {
propagate(pred, exit_state)
}
}
Expand Down Expand Up @@ -245,37 +241,6 @@ impl Direction for Backward {
}
}

struct BackwardSwitchIntEdgeEffectsApplier<'mir, 'tcx, D, F> {
body: &'mir mir::Body<'tcx>,
pred: BasicBlock,
exit_state: &'mir mut D,
block: BasicBlock,
propagate: &'mir mut F,
effects_applied: bool,
}

impl<D, F> super::SwitchIntEdgeEffects<D> for BackwardSwitchIntEdgeEffectsApplier<'_, '_, D, F>
where
D: Clone,
F: FnMut(BasicBlock, &D),
{
fn apply(&mut self, mut apply_edge_effect: impl FnMut(&mut D, SwitchIntTarget)) {
assert!(!self.effects_applied);

let values = &self.body.basic_blocks.switch_sources()[&(self.block, self.pred)];
let targets = values.iter().map(|&value| SwitchIntTarget { value, target: self.block });

let mut tmp = None;
for target in targets {
let tmp = opt_clone_from_or_clone(&mut tmp, self.exit_state);
apply_edge_effect(tmp, target);
(self.propagate)(self.pred, tmp);
}

self.effects_applied = true;
}
}

/// Dataflow that runs from the entry of a block (the first statement), to its exit (terminator).
pub struct Forward;

Expand All @@ -284,7 +249,7 @@ impl Direction for Forward {

fn apply_effects_in_block<'mir, 'tcx, A>(
analysis: &mut A,
_body: &mir::Body<'tcx>,
body: &mir::Body<'tcx>,
state: &mut A::Domain,
block: BasicBlock,
block_data: &'mir mir::BasicBlockData<'tcx>,
Expand Down Expand Up @@ -324,23 +289,28 @@ impl Direction for Forward {
}
}
TerminatorEdges::SwitchInt { targets, discr } => {
let mut applier = ForwardSwitchIntEdgeEffectsApplier {
exit_state,
targets,
propagate,
effects_applied: false,
};

analysis.apply_switch_int_edge_effects(block, discr, &mut applier);

let ForwardSwitchIntEdgeEffectsApplier {
exit_state,
mut propagate,
effects_applied,
..
} = applier;

if !effects_applied {
if let Some(mut data) = analysis.get_switch_int_data(block, discr) {
let mut tmp = analysis.bottom_value(body);
for (value, target) in targets.iter() {
tmp.clone_from(&exit_state);
analysis.apply_switch_int_edge_effect(
&mut data,
&mut tmp,
SwitchIntTarget { value: Some(value), target },
);
propagate(target, &tmp);
}

// Once we get to the final, "otherwise" branch, there is no need to preserve
// `exit_state`, so pass it directly to `apply_switch_int_edge_effect` to save
// a clone of the dataflow state.
let otherwise = targets.otherwise();
analysis.apply_switch_int_edge_effect(&mut data, exit_state, SwitchIntTarget {
value: None,
target: otherwise,
});
propagate(otherwise, exit_state);
} else {
for target in targets.all_targets() {
propagate(*target, exit_state);
}
Expand Down Expand Up @@ -454,54 +424,3 @@ impl Direction for Forward {
vis.visit_block_end(state);
}
}

struct ForwardSwitchIntEdgeEffectsApplier<'mir, D, F> {
exit_state: &'mir mut D,
targets: &'mir SwitchTargets,
propagate: F,

effects_applied: bool,
}

impl<D, F> super::SwitchIntEdgeEffects<D> for ForwardSwitchIntEdgeEffectsApplier<'_, D, F>
where
D: Clone,
F: FnMut(BasicBlock, &D),
{
fn apply(&mut self, mut apply_edge_effect: impl FnMut(&mut D, SwitchIntTarget)) {
assert!(!self.effects_applied);

let mut tmp = None;
for (value, target) in self.targets.iter() {
let tmp = opt_clone_from_or_clone(&mut tmp, self.exit_state);
apply_edge_effect(tmp, SwitchIntTarget { value: Some(value), target });
(self.propagate)(target, tmp);
}

// Once we get to the final, "otherwise" branch, there is no need to preserve `exit_state`,
// so pass it directly to `apply_edge_effect` to save a clone of the dataflow state.
let otherwise = self.targets.otherwise();
apply_edge_effect(self.exit_state, SwitchIntTarget { value: None, target: otherwise });
(self.propagate)(otherwise, self.exit_state);

self.effects_applied = true;
}
}

/// An analogue of `Option::get_or_insert_with` that stores a clone of `val` into `opt`, but uses
/// the more efficient `clone_from` if `opt` was `Some`.
///
/// Returns a mutable reference to the new clone that resides in `opt`.
//
// FIXME: Figure out how to express this using `Option::clone_from`, or maybe lift it into the
// standard library?
fn opt_clone_from_or_clone<'a, T: Clone>(opt: &'a mut Option<T>, val: &T) -> &'a mut T {
if opt.is_some() {
let ret = opt.as_mut().unwrap();
ret.clone_from(val);
ret
} else {
*opt = Some(val.clone());
opt.as_mut().unwrap()
}
}
39 changes: 23 additions & 16 deletions compiler/rustc_mir_dataflow/src/framework/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ pub trait Analysis<'tcx> {
/// The direction of this analysis. Either `Forward` or `Backward`.
type Direction: Direction = Forward;

/// Auxiliary data used for analyzing `SwitchInt` terminators, if necessary.
type SwitchIntData = !;

/// A descriptive name for this analysis. Used only for debugging.
///
/// This name should be brief and contain no spaces, periods or other characters that are not
Expand Down Expand Up @@ -190,25 +193,36 @@ pub trait Analysis<'tcx> {
) {
}

/// Updates the current dataflow state with the effect of taking a particular branch in a
/// `SwitchInt` terminator.
/// Used to update the current dataflow state with the effect of taking a particular branch in
/// a `SwitchInt` terminator.
///
/// Unlike the other edge-specific effects, which are allowed to mutate `Self::Domain`
/// directly, overriders of this method must pass a callback to
/// `SwitchIntEdgeEffects::apply`. The callback will be run once for each outgoing edge and
/// will have access to the dataflow state that will be propagated along that edge.
/// directly, overriders of this method must return a `Self::SwitchIntData` value (wrapped in
/// `Some`). The `apply_switch_int_edge_effect` method will then be called once for each
/// outgoing edge and will have access to the dataflow state that will be propagated along that
/// edge, and also the `Self::SwitchIntData` value.
///
/// This interface is somewhat more complex than the other visitor-like "effect" methods.
/// However, it is both more ergonomic—callers don't need to recompute or cache information
/// about a given `SwitchInt` terminator for each one of its edges—and more efficient—the
/// engine doesn't need to clone the exit state for a block unless
/// `SwitchIntEdgeEffects::apply` is actually called.
fn apply_switch_int_edge_effects(
/// `get_switch_int_data` is actually called.
fn get_switch_int_data(
&mut self,
_block: BasicBlock,
_block: mir::BasicBlock,
_discr: &mir::Operand<'tcx>,
_apply_edge_effects: &mut impl SwitchIntEdgeEffects<Self::Domain>,
) -> Option<Self::SwitchIntData> {
None
}

/// See comments on `get_switch_int_data`.
fn apply_switch_int_edge_effect(
&mut self,
_data: &mut Self::SwitchIntData,
_state: &mut Self::Domain,
_edge: SwitchIntTarget,
) {
unreachable!();
}

/* Extension methods */
Expand Down Expand Up @@ -421,12 +435,5 @@ pub struct SwitchIntTarget {
pub target: BasicBlock,
}

/// A type that records the edge-specific effects for a `SwitchInt` terminator.
pub trait SwitchIntEdgeEffects<D> {
/// Calls `apply_edge_effect` for each outgoing edge from a `SwitchInt` terminator and
/// records the results.
fn apply(&mut self, apply_edge_effect: impl FnMut(&mut D, SwitchIntTarget));
}

#[cfg(test)]
mod tests;
Loading
Loading