Create stable metric to measure long computation in Const Eval
This patch adds a `MirPass` that tracks the number of back-edges and function calls in the CFG, adds a new MIR instruction to increment a counter every time they are encountered during Const Eval, and emit a warning if a configured limit is breached.
This commit is contained in:
parent
c8e6a9e8b6
commit
360db516cc
37 changed files with 233 additions and 9 deletions
|
@ -104,6 +104,7 @@ impl<'tcx> Visitor<'tcx> for UnsafetyChecker<'_, 'tcx> {
|
|||
| StatementKind::AscribeUserType(..)
|
||||
| StatementKind::Coverage(..)
|
||||
| StatementKind::Intrinsic(..)
|
||||
| StatementKind::ConstEvalCounter
|
||||
| StatementKind::Nop => {
|
||||
// safe (at least as emitted during MIR construction)
|
||||
}
|
||||
|
|
|
@ -802,6 +802,8 @@ pub(super) fn filtered_statement_span(statement: &Statement<'_>) -> Option<Span>
|
|||
| StatementKind::StorageDead(_)
|
||||
// Coverage should not be encountered, but don't inject coverage coverage
|
||||
| StatementKind::Coverage(_)
|
||||
// Ignore `ConstEvalCounter`s
|
||||
| StatementKind::ConstEvalCounter
|
||||
// Ignore `Nop`s
|
||||
| StatementKind::Nop => None,
|
||||
|
||||
|
|
92
compiler/rustc_mir_transform/src/ctfe_limit.rs
Normal file
92
compiler/rustc_mir_transform/src/ctfe_limit.rs
Normal file
|
@ -0,0 +1,92 @@
|
|||
use crate::MirPass;
|
||||
|
||||
use rustc_middle::mir::{BasicBlock, Body, Statement, StatementKind, TerminatorKind};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
|
||||
use tracing::{info, instrument};
|
||||
|
||||
pub struct CtfeLimit;
|
||||
|
||||
impl<'tcx> MirPass<'tcx> for CtfeLimit {
|
||||
#[instrument(skip(self, _tcx, body))]
|
||||
fn run_pass(&self, _tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
|
||||
let doms = body.basic_blocks.dominators();
|
||||
//info!("Got body with {} basic blocks: {:#?}", body.basic_blocks.len(), body.basic_blocks);
|
||||
//info!("With doms: {doms:?}");
|
||||
|
||||
/*
|
||||
for (index, basic_block) in body.basic_blocks.iter().enumerate() {
|
||||
info!("bb{index}: {basic_block:#?}")
|
||||
}*/
|
||||
for (index, basic_block) in body.basic_blocks.iter().enumerate() {
|
||||
info!(
|
||||
"bb{index} -> successors = {:?}",
|
||||
basic_block.terminator().successors().collect::<Vec<BasicBlock>>()
|
||||
);
|
||||
}
|
||||
for (index, basic_block) in body.basic_blocks.iter().enumerate() {
|
||||
info!("bb{index} -> unwind = {:?}", basic_block.terminator().unwind())
|
||||
}
|
||||
|
||||
let mut dominators = Vec::new();
|
||||
for idom in 0..body.basic_blocks.len() {
|
||||
let mut nodes = Vec::new();
|
||||
for inode in 0..body.basic_blocks.len() {
|
||||
let dom = BasicBlock::from_usize(idom);
|
||||
let node = BasicBlock::from_usize(inode);
|
||||
if doms.is_reachable(dom)
|
||||
&& doms.is_reachable(node)
|
||||
&& doms.is_dominated_by(node, dom)
|
||||
{
|
||||
//info!("{idom} dominates {inode}");
|
||||
nodes.push(true);
|
||||
} else {
|
||||
nodes.push(false);
|
||||
}
|
||||
}
|
||||
dominators.push(nodes);
|
||||
}
|
||||
/*
|
||||
for idom in 0..body.basic_blocks.len() {
|
||||
print!("{idom} | dom | ");
|
||||
for inode in 0..body.basic_blocks.len() {
|
||||
if dominators[idom][inode] {
|
||||
print!("{inode} | ");
|
||||
} else {
|
||||
print!(" | ");
|
||||
}
|
||||
}
|
||||
print!("\n");
|
||||
}
|
||||
*/
|
||||
|
||||
for (index, basic_block) in body.basic_blocks_mut().iter_mut().enumerate() {
|
||||
// info!("bb{index}: {basic_block:#?}");
|
||||
//info!("bb{index} -> successors = {:?}", basic_block.terminator().successors().collect::<Vec<BasicBlock>>());
|
||||
let is_back_edge_or_fn_call = 'label: {
|
||||
match basic_block.terminator().kind {
|
||||
TerminatorKind::Call { .. } => {
|
||||
break 'label true;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
for successor in basic_block.terminator().successors() {
|
||||
let s_index = successor.as_usize();
|
||||
if dominators[s_index][index] {
|
||||
info!("{s_index} to {index} is a loop");
|
||||
break 'label true;
|
||||
}
|
||||
}
|
||||
false
|
||||
};
|
||||
if is_back_edge_or_fn_call {
|
||||
basic_block.statements.push(Statement {
|
||||
source_info: basic_block.terminator().source_info,
|
||||
kind: StatementKind::ConstEvalCounter,
|
||||
});
|
||||
info!("New basic block statements vector: {:?}", basic_block.statements);
|
||||
}
|
||||
}
|
||||
info!("With doms: {doms:?}");
|
||||
}
|
||||
}
|
|
@ -53,6 +53,7 @@ pub fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, borrowed: &BitS
|
|||
| StatementKind::StorageDead(_)
|
||||
| StatementKind::Coverage(_)
|
||||
| StatementKind::Intrinsic(_)
|
||||
| StatementKind::ConstEvalCounter
|
||||
| StatementKind::Nop => (),
|
||||
|
||||
StatementKind::FakeRead(_) | StatementKind::AscribeUserType(_, _) => {
|
||||
|
|
|
@ -577,6 +577,7 @@ impl WriteInfo {
|
|||
self.add_place(**place);
|
||||
}
|
||||
StatementKind::Intrinsic(_)
|
||||
| StatementKind::ConstEvalCounter
|
||||
| StatementKind::Nop
|
||||
| StatementKind::Coverage(_)
|
||||
| StatementKind::StorageLive(_)
|
||||
|
|
|
@ -1583,6 +1583,7 @@ impl<'tcx> Visitor<'tcx> for EnsureGeneratorFieldAssignmentsNeverAlias<'_> {
|
|||
| StatementKind::AscribeUserType(..)
|
||||
| StatementKind::Coverage(..)
|
||||
| StatementKind::Intrinsic(..)
|
||||
| StatementKind::ConstEvalCounter
|
||||
| StatementKind::Nop => {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ mod const_goto;
|
|||
mod const_prop;
|
||||
mod const_prop_lint;
|
||||
mod coverage;
|
||||
mod ctfe_limit;
|
||||
mod dataflow_const_prop;
|
||||
mod dead_store_elimination;
|
||||
mod deaggregator;
|
||||
|
@ -349,11 +350,14 @@ fn mir_promoted(
|
|||
/// Compute the MIR that is used during CTFE (and thus has no optimizations run on it)
|
||||
fn mir_for_ctfe(tcx: TyCtxt<'_>, def_id: DefId) -> &Body<'_> {
|
||||
let did = def_id.expect_local();
|
||||
if let Some(def) = ty::WithOptConstParam::try_lookup(did, tcx) {
|
||||
let body = if let Some(def) = ty::WithOptConstParam::try_lookup(did, tcx) {
|
||||
tcx.mir_for_ctfe_of_const_arg(def)
|
||||
} else {
|
||||
tcx.arena.alloc(inner_mir_for_ctfe(tcx, ty::WithOptConstParam::unknown(did)))
|
||||
}
|
||||
};
|
||||
//info!("MIR_FOR_CTFE (DefId = {def_id:?}) body res: {:#?}", body);
|
||||
info!("MIR_FOR_CTFE (DefId = {def_id:?})");
|
||||
body
|
||||
}
|
||||
|
||||
/// Same as `mir_for_ctfe`, but used to get the MIR of a const generic parameter.
|
||||
|
@ -447,6 +451,7 @@ fn mir_drops_elaborated_and_const_checked(
|
|||
|
||||
run_analysis_to_runtime_passes(tcx, &mut body);
|
||||
|
||||
//info!("MIR after runtime passes: {:#?}", body);
|
||||
tcx.alloc_steal_mir(body)
|
||||
}
|
||||
|
||||
|
@ -517,6 +522,7 @@ fn run_runtime_lowering_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
|
|||
// CTFE support for aggregates.
|
||||
&deaggregator::Deaggregator,
|
||||
&Lint(const_prop_lint::ConstProp),
|
||||
&ctfe_limit::CtfeLimit,
|
||||
];
|
||||
pm::run_passes_no_validate(tcx, body, passes, Some(MirPhase::Runtime(RuntimePhase::Initial)));
|
||||
}
|
||||
|
@ -617,6 +623,7 @@ fn inner_optimized_mir(tcx: TyCtxt<'_>, did: LocalDefId) -> Body<'_> {
|
|||
let mut body = remap_mir_for_const_eval_select(tcx, body, hir::Constness::NotConst);
|
||||
debug!("body: {:#?}", body);
|
||||
run_optimization_passes(tcx, &mut body);
|
||||
//info!("body after OPTIMIZATION: {:#?}", body);
|
||||
|
||||
debug_assert!(!body.has_free_regions(), "Free regions in optimized MIR");
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ impl RemoveNoopLandingPads {
|
|||
| StatementKind::StorageDead(_)
|
||||
| StatementKind::AscribeUserType(..)
|
||||
| StatementKind::Coverage(..)
|
||||
| StatementKind::ConstEvalCounter
|
||||
| StatementKind::Nop => {
|
||||
// These are all noops in a landing pad
|
||||
}
|
||||
|
|
|
@ -250,6 +250,7 @@ fn is_likely_const<'tcx>(mut tracked_place: Place<'tcx>, block: &BasicBlockData<
|
|||
| StatementKind::Coverage(_)
|
||||
| StatementKind::StorageDead(_)
|
||||
| StatementKind::Intrinsic(_)
|
||||
| StatementKind::ConstEvalCounter
|
||||
| StatementKind::Nop => {}
|
||||
}
|
||||
}
|
||||
|
@ -318,6 +319,7 @@ fn find_determining_place<'tcx>(
|
|||
| StatementKind::AscribeUserType(_, _)
|
||||
| StatementKind::Coverage(_)
|
||||
| StatementKind::Intrinsic(_)
|
||||
| StatementKind::ConstEvalCounter
|
||||
| StatementKind::Nop => {}
|
||||
|
||||
// If the discriminant is set, it is always set
|
||||
|
|
|
@ -517,7 +517,7 @@ impl<'tcx> Visitor<'tcx> for UsedLocals {
|
|||
self.super_statement(statement, location);
|
||||
}
|
||||
|
||||
StatementKind::Nop => {}
|
||||
StatementKind::ConstEvalCounter | StatementKind::Nop => {}
|
||||
|
||||
StatementKind::StorageLive(_local) | StatementKind::StorageDead(_local) => {}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue