Skip to content
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

Remove Engine #132338

Merged
merged 2 commits into from
Oct 30, 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
31 changes: 16 additions & 15 deletions compiler/rustc_borrowck/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,9 +193,7 @@ fn do_mir_borrowck<'tcx>(
.map(|(idx, body)| (idx, MoveData::gather_moves(body, tcx, |_| true)));

let mut flow_inits = MaybeInitializedPlaces::new(tcx, body, &move_data)
.into_engine(tcx, body)
.pass_name("borrowck")
.iterate_to_fixpoint()
.iterate_to_fixpoint(tcx, body, Some("borrowck"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Further simplification: should we always give a name and get rid of the option?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just took a look. The optional pass name is unwrapped as "-----" here:

create_dump_file(tcx, "dot", false, A::NAME, &pass_name.unwrap_or("-----"), body)?

So everywhere I've used None would be changed to "-----" which would be weird. More generally, the file naming scheme for IR dumping is complicated. I don't want to delve into that for this PR, which is a straightforward refactoring. Maybe in a follow-up.

.into_results_cursor(body);

let locals_are_invalidated_at_exit = tcx.hir().body_owner_kind(def).is_fn_or_closure();
Expand Down Expand Up @@ -243,18 +241,21 @@ fn do_mir_borrowck<'tcx>(
// usage significantly on some benchmarks.
drop(flow_inits);

let flow_borrows = Borrows::new(tcx, body, &regioncx, &borrow_set)
.into_engine(tcx, body)
.pass_name("borrowck")
.iterate_to_fixpoint();
let flow_uninits = MaybeUninitializedPlaces::new(tcx, body, &move_data)
.into_engine(tcx, body)
.pass_name("borrowck")
.iterate_to_fixpoint();
let flow_ever_inits = EverInitializedPlaces::new(body, &move_data)
.into_engine(tcx, body)
.pass_name("borrowck")
.iterate_to_fixpoint();
let flow_borrows = Borrows::new(tcx, body, &regioncx, &borrow_set).iterate_to_fixpoint(
tcx,
body,
Some("borrowck"),
);
let flow_uninits = MaybeUninitializedPlaces::new(tcx, body, &move_data).iterate_to_fixpoint(
tcx,
body,
Some("borrowck"),
);
let flow_ever_inits = EverInitializedPlaces::new(body, &move_data).iterate_to_fixpoint(
tcx,
body,
Some("borrowck"),
);

let movable_coroutine =
// The first argument is the coroutine type passed by value
Expand Down
12 changes: 4 additions & 8 deletions compiler/rustc_const_eval/src/check_consts/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,7 @@ impl<'mir, 'tcx> Qualifs<'mir, 'tcx> {
let ConstCx { tcx, body, .. } = *ccx;

FlowSensitiveAnalysis::new(NeedsDrop, ccx)
.into_engine(tcx, body)
.iterate_to_fixpoint()
.iterate_to_fixpoint(tcx, body, None)
.into_results_cursor(body)
});

Expand Down Expand Up @@ -93,8 +92,7 @@ impl<'mir, 'tcx> Qualifs<'mir, 'tcx> {
let ConstCx { tcx, body, .. } = *ccx;

FlowSensitiveAnalysis::new(NeedsNonConstDrop, ccx)
.into_engine(tcx, body)
.iterate_to_fixpoint()
.iterate_to_fixpoint(tcx, body, None)
.into_results_cursor(body)
});

Expand Down Expand Up @@ -123,8 +121,7 @@ impl<'mir, 'tcx> Qualifs<'mir, 'tcx> {
let ConstCx { tcx, body, .. } = *ccx;

FlowSensitiveAnalysis::new(HasMutInterior, ccx)
.into_engine(tcx, body)
.iterate_to_fixpoint()
.iterate_to_fixpoint(tcx, body, None)
.into_results_cursor(body)
});

Expand Down Expand Up @@ -239,8 +236,7 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> {
let always_live_locals = &always_storage_live_locals(&ccx.body);
let mut maybe_storage_live =
MaybeStorageLive::new(Cow::Borrowed(always_live_locals))
.into_engine(ccx.tcx, &ccx.body)
.iterate_to_fixpoint()
.iterate_to_fixpoint(ccx.tcx, &ccx.body, None)
.into_results_cursor(&ccx.body);

// And then check all `Return` in the MIR, and if a local is "maybe live" at a
Expand Down
109 changes: 90 additions & 19 deletions compiler/rustc_mir_dataflow/src/framework/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,17 @@
//!
//! The `impls` module contains several examples of dataflow analyses.
//!
//! Create an `Engine` for your analysis using the `into_engine` method on the `Analysis` trait,
//! then call `iterate_to_fixpoint`. From there, you can use a `ResultsCursor` to inspect the
//! fixpoint solution to your dataflow problem, or implement the `ResultsVisitor` interface and use
//! `visit_results`. The following example uses the `ResultsCursor` approach.
//! Then call `iterate_to_fixpoint` on your type that impls `Analysis` to get a `Results`. From
//! there, you can use a `ResultsCursor` to inspect the fixpoint solution to your dataflow problem,
//! or implement the `ResultsVisitor` interface and use `visit_results`. The following example uses
//! the `ResultsCursor` approach.
//!
//! ```ignore (cross-crate-imports)
//! use rustc_const_eval::dataflow::Analysis; // Makes `into_engine` available.
//! use rustc_const_eval::dataflow::Analysis; // Makes `iterate_to_fixpoint` available.
//!
//! fn do_my_analysis(tcx: TyCtxt<'tcx>, body: &mir::Body<'tcx>) {
//! let analysis = MyAnalysis::new()
//! .into_engine(tcx, body)
//! .iterate_to_fixpoint()
//! .iterate_to_fixpoint(tcx, body, None)
//! .into_results_cursor(body);
//!
//! // Print the dataflow state *after* each statement in the start block.
Expand All @@ -34,23 +33,29 @@

use std::cmp::Ordering;

use rustc_index::Idx;
use rustc_data_structures::work_queue::WorkQueue;
use rustc_index::bit_set::{BitSet, ChunkedBitSet, HybridBitSet};
use rustc_middle::mir::{self, BasicBlock, CallReturnPlaces, Location, TerminatorEdges};
use rustc_index::{Idx, IndexVec};
use rustc_middle::bug;
use rustc_middle::mir::{self, BasicBlock, CallReturnPlaces, Location, TerminatorEdges, traversal};
use rustc_middle::ty::TyCtxt;
use tracing::error;

use self::results::write_graphviz_results;
use super::fmt::DebugWithContext;

mod cursor;
mod direction;
mod engine;
pub mod fmt;
pub mod graphviz;
pub mod lattice;
mod results;
mod visitor;

pub use self::cursor::ResultsCursor;
pub use self::direction::{Backward, Direction, Forward};
pub use self::engine::{Engine, Results};
pub use self::lattice::{JoinSemiLattice, MaybeReachable};
pub use self::results::Results;
pub use self::visitor::{ResultsVisitable, ResultsVisitor, visit_results};

/// Analysis domains are all bitsets of various kinds. This trait holds
Expand Down Expand Up @@ -223,26 +228,92 @@ pub trait Analysis<'tcx> {

/* Extension methods */

/// Creates an `Engine` to find the fixpoint for this dataflow problem.
/// Finds the fixpoint for this dataflow problem.
///
/// You shouldn't need to override this. Its purpose is to enable method chaining like so:
///
/// ```ignore (cross-crate-imports)
/// let results = MyAnalysis::new(tcx, body)
/// .into_engine(tcx, body, def_id)
/// .iterate_to_fixpoint()
/// .iterate_to_fixpoint(tcx, body, None)
/// .into_results_cursor(body);
/// ```
#[inline]
fn into_engine<'mir>(
self,
/// You can optionally add a `pass_name` to the graphviz output for this particular run of a
/// dataflow analysis. Some analyses are run multiple times in the compilation pipeline.
/// Without a `pass_name` to differentiates them, only the results for the latest run will be
/// saved.
fn iterate_to_fixpoint<'mir>(
mut self,
tcx: TyCtxt<'tcx>,
body: &'mir mir::Body<'tcx>,
) -> Engine<'mir, 'tcx, Self>
pass_name: Option<&'static str>,
) -> Results<'tcx, Self>
where
Self: Sized,
Self::Domain: DebugWithContext<Self>,
{
Engine::new(tcx, body, self)
let mut entry_sets =
IndexVec::from_fn_n(|_| self.bottom_value(body), body.basic_blocks.len());
self.initialize_start_block(body, &mut entry_sets[mir::START_BLOCK]);

if Self::Direction::IS_BACKWARD && entry_sets[mir::START_BLOCK] != self.bottom_value(body) {
bug!("`initialize_start_block` is not yet supported for backward dataflow analyses");
}

let mut dirty_queue: WorkQueue<BasicBlock> = WorkQueue::with_none(body.basic_blocks.len());

if Self::Direction::IS_FORWARD {
for (bb, _) in traversal::reverse_postorder(body) {
dirty_queue.insert(bb);
}
} else {
// Reverse post-order on the reverse CFG may generate a better iteration order for
// backward dataflow analyses, but probably not enough to matter.
for (bb, _) in traversal::postorder(body) {
dirty_queue.insert(bb);
}
}

// `state` is not actually used between iterations;
// this is just an optimization to avoid reallocating
// every iteration.
let mut state = self.bottom_value(body);
while let Some(bb) = dirty_queue.pop() {
let bb_data = &body[bb];

// Set the state to the entry state of the block.
// This is equivalent to `state = entry_sets[bb].clone()`,
// but it saves an allocation, thus improving compile times.
state.clone_from(&entry_sets[bb]);

// Apply the block transfer function, using the cached one if it exists.
let edges = Self::Direction::apply_effects_in_block(&mut self, &mut state, bb, bb_data);

Self::Direction::join_state_into_successors_of(
&mut self,
body,
&mut state,
bb,
edges,
|target: BasicBlock, state: &Self::Domain| {
let set_changed = entry_sets[target].join(state);
if set_changed {
dirty_queue.insert(target);
}
},
);
}

let results = Results { analysis: self, entry_sets };

if tcx.sess.opts.unstable_opts.dump_mir_dataflow {
let (res, results) = write_graphviz_results(tcx, body, results, pass_name);
if let Err(e) = res {
error!("Failed to write graphviz dataflow results: {}", e);
}
results
} else {
results
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
//! A solver for dataflow problems.
//! Dataflow analysis results.

use std::ffi::OsString;
use std::path::PathBuf;

use rustc_data_structures::work_queue::WorkQueue;
use rustc_hir::def_id::DefId;
use rustc_index::IndexVec;
use rustc_middle::bug;
use rustc_middle::mir::{self, BasicBlock, create_dump_file, dump_enabled, traversal};
use rustc_middle::ty::TyCtxt;
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_span::symbol::{Symbol, sym};
use tracing::{debug, error};
use tracing::debug;
use {rustc_ast as ast, rustc_graphviz as dot};

use super::fmt::DebugWithContext;
use super::{
Analysis, Direction, JoinSemiLattice, ResultsCursor, ResultsVisitor, graphviz, visit_results,
};
use super::{Analysis, ResultsCursor, ResultsVisitor, graphviz, visit_results};
use crate::errors::{
DuplicateValuesFor, PathMustEndInFilename, RequiresAnArgument, UnknownFormatter,
};
Expand Down Expand Up @@ -65,124 +61,17 @@ where
body: &'mir mir::Body<'tcx>,
vis: &mut impl ResultsVisitor<'mir, 'tcx, Self, Domain = A::Domain>,
) {
let blocks = mir::traversal::reachable(body);
let blocks = traversal::reachable(body);
visit_results(body, blocks.map(|(bb, _)| bb), self, vis)
}
}

/// A solver for dataflow problems.
pub struct Engine<'mir, 'tcx, A>
where
A: Analysis<'tcx>,
{
tcx: TyCtxt<'tcx>,
body: &'mir mir::Body<'tcx>,
entry_sets: IndexVec<BasicBlock, A::Domain>,
pass_name: Option<&'static str>,
analysis: A,
}

impl<'mir, 'tcx, A, D> Engine<'mir, 'tcx, A>
where
A: Analysis<'tcx, Domain = D>,
D: Clone + JoinSemiLattice,
{
/// Creates a new `Engine` to solve a dataflow problem with an arbitrary transfer
/// function.
pub(crate) fn new(tcx: TyCtxt<'tcx>, body: &'mir mir::Body<'tcx>, analysis: A) -> Self {
let mut entry_sets =
IndexVec::from_fn_n(|_| analysis.bottom_value(body), body.basic_blocks.len());
analysis.initialize_start_block(body, &mut entry_sets[mir::START_BLOCK]);

if A::Direction::IS_BACKWARD && entry_sets[mir::START_BLOCK] != analysis.bottom_value(body)
{
bug!("`initialize_start_block` is not yet supported for backward dataflow analyses");
}

Engine { analysis, tcx, body, pass_name: None, entry_sets }
}

/// Adds an identifier to the graphviz output for this particular run of a dataflow analysis.
///
/// Some analyses are run multiple times in the compilation pipeline. Give them a `pass_name`
/// to differentiate them. Otherwise, only the results for the latest run will be saved.
pub fn pass_name(mut self, name: &'static str) -> Self {
self.pass_name = Some(name);
self
}

/// Computes the fixpoint for this dataflow problem and returns it.
pub fn iterate_to_fixpoint(self) -> Results<'tcx, A>
where
A::Domain: DebugWithContext<A>,
{
let Engine { mut analysis, body, mut entry_sets, tcx, pass_name } = self;

let mut dirty_queue: WorkQueue<BasicBlock> = WorkQueue::with_none(body.basic_blocks.len());

if A::Direction::IS_FORWARD {
for (bb, _) in traversal::reverse_postorder(body) {
dirty_queue.insert(bb);
}
} else {
// Reverse post-order on the reverse CFG may generate a better iteration order for
// backward dataflow analyses, but probably not enough to matter.
for (bb, _) in traversal::postorder(body) {
dirty_queue.insert(bb);
}
}

// `state` is not actually used between iterations;
// this is just an optimization to avoid reallocating
// every iteration.
let mut state = analysis.bottom_value(body);
while let Some(bb) = dirty_queue.pop() {
let bb_data = &body[bb];

// Set the state to the entry state of the block.
// This is equivalent to `state = entry_sets[bb].clone()`,
// but it saves an allocation, thus improving compile times.
state.clone_from(&entry_sets[bb]);

// Apply the block transfer function, using the cached one if it exists.
let edges =
A::Direction::apply_effects_in_block(&mut analysis, &mut state, bb, bb_data);

A::Direction::join_state_into_successors_of(
&mut analysis,
body,
&mut state,
bb,
edges,
|target: BasicBlock, state: &A::Domain| {
let set_changed = entry_sets[target].join(state);
if set_changed {
dirty_queue.insert(target);
}
},
);
}

let results = Results { analysis, entry_sets };

if tcx.sess.opts.unstable_opts.dump_mir_dataflow {
let (res, results) = write_graphviz_results(tcx, body, results, pass_name);
if let Err(e) = res {
error!("Failed to write graphviz dataflow results: {}", e);
}
results
} else {
results
}
}
}

// Graphviz

/// Writes a DOT file containing the results of a dataflow analysis if the user requested it via
/// `rustc_mir` attributes and `-Z dump-mir-dataflow`. The `Result` in and the `Results` out are
/// the same.
fn write_graphviz_results<'tcx, A>(
pub(super) fn write_graphviz_results<'tcx, A>(
tcx: TyCtxt<'tcx>,
body: &mir::Body<'tcx>,
results: Results<'tcx, A>,
Expand Down
Loading
Loading