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:
Bryan Garza 2022-12-20 00:51:17 +00:00
parent c8e6a9e8b6
commit 360db516cc
37 changed files with 233 additions and 9 deletions

View File

@ -393,6 +393,7 @@ impl<'tcx> rustc_mir_dataflow::GenKillAnalysis<'tcx> for Borrows<'_, 'tcx> {
| mir::StatementKind::AscribeUserType(..) | mir::StatementKind::AscribeUserType(..)
| mir::StatementKind::Coverage(..) | mir::StatementKind::Coverage(..)
| mir::StatementKind::Intrinsic(..) | mir::StatementKind::Intrinsic(..)
| mir::StatementKind::ConstEvalCounter
| mir::StatementKind::Nop => {} | mir::StatementKind::Nop => {}
} }
} }

View File

@ -91,7 +91,8 @@ impl<'cx, 'tcx> Visitor<'tcx> for InvalidationGenerator<'cx, 'tcx> {
LocalMutationIsAllowed::Yes, LocalMutationIsAllowed::Yes,
); );
} }
StatementKind::Nop StatementKind::ConstEvalCounter
| StatementKind::Nop
| StatementKind::Retag { .. } | StatementKind::Retag { .. }
| StatementKind::Deinit(..) | StatementKind::Deinit(..)
| StatementKind::SetDiscriminant { .. } => { | StatementKind::SetDiscriminant { .. } => {

View File

@ -620,7 +620,8 @@ impl<'cx, 'tcx> rustc_mir_dataflow::ResultsVisitor<'cx, 'tcx> for MirBorrowckCtx
flow_state, flow_state,
); );
} }
StatementKind::Nop StatementKind::ConstEvalCounter
| StatementKind::Nop
| StatementKind::Retag { .. } | StatementKind::Retag { .. }
| StatementKind::Deinit(..) | StatementKind::Deinit(..)
| StatementKind::SetDiscriminant { .. } => { | StatementKind::SetDiscriminant { .. } => {

View File

@ -1258,6 +1258,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
| StatementKind::StorageDead(..) | StatementKind::StorageDead(..)
| StatementKind::Retag { .. } | StatementKind::Retag { .. }
| StatementKind::Coverage(..) | StatementKind::Coverage(..)
| StatementKind::ConstEvalCounter
| StatementKind::Nop => {} | StatementKind::Nop => {}
StatementKind::Deinit(..) | StatementKind::SetDiscriminant { .. } => { StatementKind::Deinit(..) | StatementKind::SetDiscriminant { .. } => {
bug!("Statement not allowed in this MIR phase") bug!("Statement not allowed in this MIR phase")

View File

@ -91,6 +91,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
mir::StatementKind::FakeRead(..) mir::StatementKind::FakeRead(..)
| mir::StatementKind::Retag { .. } | mir::StatementKind::Retag { .. }
| mir::StatementKind::AscribeUserType(..) | mir::StatementKind::AscribeUserType(..)
| mir::StatementKind::ConstEvalCounter
| mir::StatementKind::Nop => {} | mir::StatementKind::Nop => {}
} }
} }

View File

@ -22,6 +22,8 @@ use crate::interpret::{
RefTracking, StackPopCleanup, RefTracking, StackPopCleanup,
}; };
use tracing::info;
const NOTE_ON_UNDEFINED_BEHAVIOR_ERROR: &str = "The rules on what exactly is undefined behavior aren't clear, \ const NOTE_ON_UNDEFINED_BEHAVIOR_ERROR: &str = "The rules on what exactly is undefined behavior aren't clear, \
so this check might be overzealous. Please open an issue on the rustc \ so this check might be overzealous. Please open an issue on the rustc \
repository if you believe it should not be considered undefined behavior."; repository if you believe it should not be considered undefined behavior.";
@ -33,6 +35,7 @@ fn eval_body_using_ecx<'mir, 'tcx>(
body: &'mir mir::Body<'tcx>, body: &'mir mir::Body<'tcx>,
) -> InterpResult<'tcx, MPlaceTy<'tcx>> { ) -> InterpResult<'tcx, MPlaceTy<'tcx>> {
debug!("eval_body_using_ecx: {:?}, {:?}", cid, ecx.param_env); debug!("eval_body_using_ecx: {:?}, {:?}", cid, ecx.param_env);
info!("HERE body is {:#?}", body);
let tcx = *ecx.tcx; let tcx = *ecx.tcx;
assert!( assert!(
cid.promoted.is_some() cid.promoted.is_some()

View File

@ -369,6 +369,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
} }
} }
#[instrument(skip(ecx), ret)]
fn load_mir( fn load_mir(
ecx: &InterpCx<'mir, 'tcx, Self>, ecx: &InterpCx<'mir, 'tcx, Self>,
instance: ty::InstanceDef<'tcx>, instance: ty::InstanceDef<'tcx>,

View File

@ -46,6 +46,9 @@ pub struct InterpCx<'mir, 'tcx, M: Machine<'mir, 'tcx>> {
/// The recursion limit (cached from `tcx.recursion_limit(())`) /// The recursion limit (cached from `tcx.recursion_limit(())`)
pub recursion_limit: Limit, pub recursion_limit: Limit,
pub const_eval_limit: u32,
pub const_eval_counter: u32,
} }
// The Phantomdata exists to prevent this type from being `Send`. If it were sent across a thread // The Phantomdata exists to prevent this type from being `Send`. If it were sent across a thread
@ -408,6 +411,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
param_env, param_env,
memory: Memory::new(), memory: Memory::new(),
recursion_limit: tcx.recursion_limit(), recursion_limit: tcx.recursion_limit(),
const_eval_limit: 20,
const_eval_counter: 0,
} }
} }

View File

@ -293,6 +293,17 @@ where
Prov: Provenance + 'static, Prov: Provenance + 'static,
M: Machine<'mir, 'tcx, Provenance = Prov>, M: Machine<'mir, 'tcx, Provenance = Prov>,
{ {
pub fn increment_const_eval_counter(&mut self) {
self.const_eval_counter = self.const_eval_counter + 1;
if self.const_eval_counter == self.const_eval_limit {
let mut warn = self.tcx.sess.struct_warn(format!(
"Const eval counter limit ({}) has been crossed",
self.const_eval_limit
));
warn.emit();
}
}
/// Take a value, which represents a (thin or wide) reference, and make it a place. /// Take a value, which represents a (thin or wide) reference, and make it a place.
/// Alignment is just based on the type. This is the inverse of `MemPlace::to_ref()`. /// Alignment is just based on the type. This is the inverse of `MemPlace::to_ref()`.
/// ///

View File

@ -129,6 +129,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
// FIXME(#73156): Handle source code coverage in const eval // FIXME(#73156): Handle source code coverage in const eval
Coverage(..) => {} Coverage(..) => {}
// FIXME(bryangarza): Update this to do some logic!!!
ConstEvalCounter => {
self.increment_const_eval_counter();
}
// Defined to do nothing. These are added by optimization passes, to avoid changing the // Defined to do nothing. These are added by optimization passes, to avoid changing the
// size of MIR constantly. // size of MIR constantly.
Nop => {} Nop => {}

View File

@ -693,6 +693,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
| StatementKind::AscribeUserType(..) | StatementKind::AscribeUserType(..)
| StatementKind::Coverage(..) | StatementKind::Coverage(..)
| StatementKind::Intrinsic(..) | StatementKind::Intrinsic(..)
| StatementKind::ConstEvalCounter
| StatementKind::Nop => {} | StatementKind::Nop => {}
} }
} }

View File

@ -766,6 +766,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
StatementKind::StorageLive(..) StatementKind::StorageLive(..)
| StatementKind::StorageDead(..) | StatementKind::StorageDead(..)
| StatementKind::Coverage(_) | StatementKind::Coverage(_)
| StatementKind::ConstEvalCounter
| StatementKind::Nop => {} | StatementKind::Nop => {}
} }

View File

@ -1461,6 +1461,7 @@ impl Debug for Statement<'_> {
} }
Coverage(box ref coverage) => write!(fmt, "Coverage::{:?}", coverage.kind), Coverage(box ref coverage) => write!(fmt, "Coverage::{:?}", coverage.kind),
Intrinsic(box ref intrinsic) => write!(fmt, "{intrinsic}"), Intrinsic(box ref intrinsic) => write!(fmt, "{intrinsic}"),
ConstEvalCounter => write!(fmt, "ConstEvalCounter"),
Nop => write!(fmt, "nop"), Nop => write!(fmt, "nop"),
} }
} }

View File

@ -441,10 +441,14 @@ impl<'tcx> TyCtxt<'tcx> {
#[inline] #[inline]
pub fn mir_for_ctfe_opt_const_arg(self, def: ty::WithOptConstParam<DefId>) -> &'tcx Body<'tcx> { pub fn mir_for_ctfe_opt_const_arg(self, def: ty::WithOptConstParam<DefId>) -> &'tcx Body<'tcx> {
if let Some((did, param_did)) = def.as_const_arg() { let res = if let Some((did, param_did)) = def.as_const_arg() {
info!("calling mir_for_ctfe_of_const_arg for DedId {did:?}");
self.mir_for_ctfe_of_const_arg((did, param_did)) self.mir_for_ctfe_of_const_arg((did, param_did))
} else { } else {
info!("calling mir_for_ctfe for DefId {:?}", def.did);
self.mir_for_ctfe(def.did) self.mir_for_ctfe(def.did)
} };
//info!("RES OF CALLING MIR_FOR_CTFE_OPT_CONST_ARG: {:#?}", res);
res
} }
} }

View File

@ -250,6 +250,7 @@ pub fn statement_kind_name(statement: &Statement<'_>) -> &'static str {
AscribeUserType(..) => "AscribeUserType", AscribeUserType(..) => "AscribeUserType",
Coverage(..) => "Coverage", Coverage(..) => "Coverage",
Intrinsic(..) => "Intrinsic", Intrinsic(..) => "Intrinsic",
ConstEvalCounter => "ConstEvalCounter",
Nop => "Nop", Nop => "Nop",
} }
} }

View File

@ -286,7 +286,10 @@ pub enum StatementKind<'tcx> {
/// This is permitted for both generators and ADTs. This does not necessarily write to the /// This is permitted for both generators and ADTs. This does not necessarily write to the
/// entire place; instead, it writes to the minimum set of bytes as required by the layout for /// entire place; instead, it writes to the minimum set of bytes as required by the layout for
/// the type. /// the type.
SetDiscriminant { place: Box<Place<'tcx>>, variant_index: VariantIdx }, SetDiscriminant {
place: Box<Place<'tcx>>,
variant_index: VariantIdx,
},
/// Deinitializes the place. /// Deinitializes the place.
/// ///
@ -355,6 +358,8 @@ pub enum StatementKind<'tcx> {
/// This avoids adding a new block and a terminator for simple intrinsics. /// This avoids adding a new block and a terminator for simple intrinsics.
Intrinsic(Box<NonDivergingIntrinsic<'tcx>>), Intrinsic(Box<NonDivergingIntrinsic<'tcx>>),
ConstEvalCounter,
/// No-op. Useful for deleting instructions without affecting statement indices. /// No-op. Useful for deleting instructions without affecting statement indices.
Nop, Nop,
} }

View File

@ -427,6 +427,7 @@ macro_rules! make_mir_visitor {
} }
} }
} }
StatementKind::ConstEvalCounter => {}
StatementKind::Nop => {} StatementKind::Nop => {}
} }
} }

View File

@ -271,6 +271,7 @@ impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> {
| StatementKind::AscribeUserType(..) | StatementKind::AscribeUserType(..)
| StatementKind::Coverage(..) | StatementKind::Coverage(..)
| StatementKind::Intrinsic(..) | StatementKind::Intrinsic(..)
| StatementKind::ConstEvalCounter
| StatementKind::Nop => None, | StatementKind::Nop => None,
}; };
if let Some(destination) = destination { if let Some(destination) = destination {

View File

@ -141,6 +141,7 @@ impl<'mir, 'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'mir, 'tc
StatementKind::AscribeUserType(..) StatementKind::AscribeUserType(..)
| StatementKind::Coverage(..) | StatementKind::Coverage(..)
| StatementKind::FakeRead(..) | StatementKind::FakeRead(..)
| StatementKind::ConstEvalCounter
| StatementKind::Nop | StatementKind::Nop
| StatementKind::Retag(..) | StatementKind::Retag(..)
| StatementKind::Intrinsic(..) | StatementKind::Intrinsic(..)

View File

@ -331,6 +331,7 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> {
| StatementKind::AscribeUserType(..) | StatementKind::AscribeUserType(..)
| StatementKind::Coverage(..) | StatementKind::Coverage(..)
| StatementKind::Intrinsic(..) | StatementKind::Intrinsic(..)
| StatementKind::ConstEvalCounter
| StatementKind::Nop => {} | StatementKind::Nop => {}
} }
} }

View File

@ -84,7 +84,8 @@ pub trait ValueAnalysis<'tcx> {
StatementKind::Retag(..) => { StatementKind::Retag(..) => {
// We don't track references. // We don't track references.
} }
StatementKind::Nop StatementKind::ConstEvalCounter
| StatementKind::Nop
| StatementKind::FakeRead(..) | StatementKind::FakeRead(..)
| StatementKind::Coverage(..) | StatementKind::Coverage(..)
| StatementKind::AscribeUserType(..) => (), | StatementKind::AscribeUserType(..) => (),

View File

@ -104,6 +104,7 @@ impl<'tcx> Visitor<'tcx> for UnsafetyChecker<'_, 'tcx> {
| StatementKind::AscribeUserType(..) | StatementKind::AscribeUserType(..)
| StatementKind::Coverage(..) | StatementKind::Coverage(..)
| StatementKind::Intrinsic(..) | StatementKind::Intrinsic(..)
| StatementKind::ConstEvalCounter
| StatementKind::Nop => { | StatementKind::Nop => {
// safe (at least as emitted during MIR construction) // safe (at least as emitted during MIR construction)
} }

View File

@ -802,6 +802,8 @@ pub(super) fn filtered_statement_span(statement: &Statement<'_>) -> Option<Span>
| StatementKind::StorageDead(_) | StatementKind::StorageDead(_)
// Coverage should not be encountered, but don't inject coverage coverage // Coverage should not be encountered, but don't inject coverage coverage
| StatementKind::Coverage(_) | StatementKind::Coverage(_)
// Ignore `ConstEvalCounter`s
| StatementKind::ConstEvalCounter
// Ignore `Nop`s // Ignore `Nop`s
| StatementKind::Nop => None, | StatementKind::Nop => None,

View 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:?}");
}
}

View File

@ -53,6 +53,7 @@ pub fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, borrowed: &BitS
| StatementKind::StorageDead(_) | StatementKind::StorageDead(_)
| StatementKind::Coverage(_) | StatementKind::Coverage(_)
| StatementKind::Intrinsic(_) | StatementKind::Intrinsic(_)
| StatementKind::ConstEvalCounter
| StatementKind::Nop => (), | StatementKind::Nop => (),
StatementKind::FakeRead(_) | StatementKind::AscribeUserType(_, _) => { StatementKind::FakeRead(_) | StatementKind::AscribeUserType(_, _) => {

View File

@ -577,6 +577,7 @@ impl WriteInfo {
self.add_place(**place); self.add_place(**place);
} }
StatementKind::Intrinsic(_) StatementKind::Intrinsic(_)
| StatementKind::ConstEvalCounter
| StatementKind::Nop | StatementKind::Nop
| StatementKind::Coverage(_) | StatementKind::Coverage(_)
| StatementKind::StorageLive(_) | StatementKind::StorageLive(_)

View File

@ -1583,6 +1583,7 @@ impl<'tcx> Visitor<'tcx> for EnsureGeneratorFieldAssignmentsNeverAlias<'_> {
| StatementKind::AscribeUserType(..) | StatementKind::AscribeUserType(..)
| StatementKind::Coverage(..) | StatementKind::Coverage(..)
| StatementKind::Intrinsic(..) | StatementKind::Intrinsic(..)
| StatementKind::ConstEvalCounter
| StatementKind::Nop => {} | StatementKind::Nop => {}
} }
} }

View File

@ -55,6 +55,7 @@ mod const_goto;
mod const_prop; mod const_prop;
mod const_prop_lint; mod const_prop_lint;
mod coverage; mod coverage;
mod ctfe_limit;
mod dataflow_const_prop; mod dataflow_const_prop;
mod dead_store_elimination; mod dead_store_elimination;
mod deaggregator; 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) /// 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<'_> { fn mir_for_ctfe(tcx: TyCtxt<'_>, def_id: DefId) -> &Body<'_> {
let did = def_id.expect_local(); 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) tcx.mir_for_ctfe_of_const_arg(def)
} else { } else {
tcx.arena.alloc(inner_mir_for_ctfe(tcx, ty::WithOptConstParam::unknown(did))) 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. /// 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); run_analysis_to_runtime_passes(tcx, &mut body);
//info!("MIR after runtime passes: {:#?}", body);
tcx.alloc_steal_mir(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. // CTFE support for aggregates.
&deaggregator::Deaggregator, &deaggregator::Deaggregator,
&Lint(const_prop_lint::ConstProp), &Lint(const_prop_lint::ConstProp),
&ctfe_limit::CtfeLimit,
]; ];
pm::run_passes_no_validate(tcx, body, passes, Some(MirPhase::Runtime(RuntimePhase::Initial))); 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); let mut body = remap_mir_for_const_eval_select(tcx, body, hir::Constness::NotConst);
debug!("body: {:#?}", body); debug!("body: {:#?}", body);
run_optimization_passes(tcx, &mut body); run_optimization_passes(tcx, &mut body);
//info!("body after OPTIMIZATION: {:#?}", body);
debug_assert!(!body.has_free_regions(), "Free regions in optimized MIR"); debug_assert!(!body.has_free_regions(), "Free regions in optimized MIR");

View File

@ -35,6 +35,7 @@ impl RemoveNoopLandingPads {
| StatementKind::StorageDead(_) | StatementKind::StorageDead(_)
| StatementKind::AscribeUserType(..) | StatementKind::AscribeUserType(..)
| StatementKind::Coverage(..) | StatementKind::Coverage(..)
| StatementKind::ConstEvalCounter
| StatementKind::Nop => { | StatementKind::Nop => {
// These are all noops in a landing pad // These are all noops in a landing pad
} }

View File

@ -250,6 +250,7 @@ fn is_likely_const<'tcx>(mut tracked_place: Place<'tcx>, block: &BasicBlockData<
| StatementKind::Coverage(_) | StatementKind::Coverage(_)
| StatementKind::StorageDead(_) | StatementKind::StorageDead(_)
| StatementKind::Intrinsic(_) | StatementKind::Intrinsic(_)
| StatementKind::ConstEvalCounter
| StatementKind::Nop => {} | StatementKind::Nop => {}
} }
} }
@ -318,6 +319,7 @@ fn find_determining_place<'tcx>(
| StatementKind::AscribeUserType(_, _) | StatementKind::AscribeUserType(_, _)
| StatementKind::Coverage(_) | StatementKind::Coverage(_)
| StatementKind::Intrinsic(_) | StatementKind::Intrinsic(_)
| StatementKind::ConstEvalCounter
| StatementKind::Nop => {} | StatementKind::Nop => {}
// If the discriminant is set, it is always set // If the discriminant is set, it is always set

View File

@ -517,7 +517,7 @@ impl<'tcx> Visitor<'tcx> for UsedLocals {
self.super_statement(statement, location); self.super_statement(statement, location);
} }
StatementKind::Nop => {} StatementKind::ConstEvalCounter | StatementKind::Nop => {}
StatementKind::StorageLive(_local) | StatementKind::StorageDead(_local) => {} StatementKind::StorageLive(_local) | StatementKind::StorageDead(_local) => {}

View File

@ -0,0 +1,25 @@
// check-pass
#![feature(const_for)]
const fn labelled_loop() -> u32 {
let mut n = 0;
'outer: loop {
'inner: loop {
n = n + 1;
if n > 5 && n <= 10 {
n = n + 1;
continue 'inner
}
if n > 30 {
break 'outer
}
}
}
n
}
const X: u32 = labelled_loop();
fn main() {
println!("{X}");
}

View File

@ -0,0 +1,4 @@
warning: Const eval counter limit (20) has been crossed
warning: 1 warning emitted

View File

@ -0,0 +1,15 @@
// check-pass
const fn recurse(n: u32) -> u32 {
if n == 0 {
n
} else {
recurse(n - 1)
}
}
const X: u32 = recurse(30);
fn main() {
println!("{X}");
}

View File

@ -0,0 +1,4 @@
warning: Const eval counter limit (20) has been crossed
warning: 1 warning emitted

View File

@ -0,0 +1,16 @@
// check-pass
const fn simple_loop(n: u32) -> u32 {
let mut index = 0;
let mut res = 0;
while index < n {
res = res + index;
index = index + 1;
}
res
}
const X: u32 = simple_loop(30);
fn main() {
println!("{X}");
}

View File

@ -0,0 +1,4 @@
warning: Const eval counter limit (20) has been crossed
warning: 1 warning emitted