about summary refs log tree commit diff
diff options
context:
space:
mode:
authorBryan Garza <1396101+bryangarza@users.noreply.github.com>2022-12-20 00:51:17 +0000
committerBryan Garza <1396101+bryangarza@users.noreply.github.com>2023-01-23 23:56:22 +0000
commit360db516ccf358bd4b35c483ae44634a74c66c0b (patch)
treeb93ee5a07bbfa3db492ba3baaa26f645b4252121
parentc8e6a9e8b6251bbc8276cb78cabe1998deecbed7 (diff)
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.
-rw-r--r--compiler/rustc_borrowck/src/dataflow.rs1
-rw-r--r--compiler/rustc_borrowck/src/invalidation.rs3
-rw-r--r--compiler/rustc_borrowck/src/lib.rs3
-rw-r--r--compiler/rustc_borrowck/src/type_check/mod.rs1
-rw-r--r--compiler/rustc_codegen_ssa/src/mir/statement.rs1
-rw-r--r--compiler/rustc_const_eval/src/const_eval/eval_queries.rs3
-rw-r--r--compiler/rustc_const_eval/src/const_eval/machine.rs1
-rw-r--r--compiler/rustc_const_eval/src/interpret/eval_context.rs5
-rw-r--r--compiler/rustc_const_eval/src/interpret/place.rs11
-rw-r--r--compiler/rustc_const_eval/src/interpret/step.rs5
-rw-r--r--compiler/rustc_const_eval/src/transform/check_consts/check.rs1
-rw-r--r--compiler/rustc_const_eval/src/transform/validate.rs1
-rw-r--r--compiler/rustc_middle/src/mir/mod.rs1
-rw-r--r--compiler/rustc_middle/src/mir/query.rs8
-rw-r--r--compiler/rustc_middle/src/mir/spanview.rs1
-rw-r--r--compiler/rustc_middle/src/mir/syntax.rs7
-rw-r--r--compiler/rustc_middle/src/mir/visit.rs1
-rw-r--r--compiler/rustc_mir_dataflow/src/impls/liveness.rs1
-rw-r--r--compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs1
-rw-r--r--compiler/rustc_mir_dataflow/src/move_paths/builder.rs1
-rw-r--r--compiler/rustc_mir_dataflow/src/value_analysis.rs3
-rw-r--r--compiler/rustc_mir_transform/src/check_unsafety.rs1
-rw-r--r--compiler/rustc_mir_transform/src/coverage/spans.rs2
-rw-r--r--compiler/rustc_mir_transform/src/ctfe_limit.rs92
-rw-r--r--compiler/rustc_mir_transform/src/dead_store_elimination.rs1
-rw-r--r--compiler/rustc_mir_transform/src/dest_prop.rs1
-rw-r--r--compiler/rustc_mir_transform/src/generator.rs1
-rw-r--r--compiler/rustc_mir_transform/src/lib.rs11
-rw-r--r--compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs1
-rw-r--r--compiler/rustc_mir_transform/src/separate_const_switch.rs2
-rw-r--r--compiler/rustc_mir_transform/src/simplify.rs2
-rw-r--r--src/test/ui/consts/const-eval/stable-metric/ctfe-labelled-loop.rs25
-rw-r--r--src/test/ui/consts/const-eval/stable-metric/ctfe-labelled-loop.stderr4
-rw-r--r--src/test/ui/consts/const-eval/stable-metric/ctfe-recursion.rs15
-rw-r--r--src/test/ui/consts/const-eval/stable-metric/ctfe-recursion.stderr4
-rw-r--r--src/test/ui/consts/const-eval/stable-metric/ctfe-simple-loop.rs16
-rw-r--r--src/test/ui/consts/const-eval/stable-metric/ctfe-simple-loop.stderr4
37 files changed, 233 insertions, 9 deletions
diff --git a/compiler/rustc_borrowck/src/dataflow.rs b/compiler/rustc_borrowck/src/dataflow.rs
index 8c4885770ad..2821677c537 100644
--- a/compiler/rustc_borrowck/src/dataflow.rs
+++ b/compiler/rustc_borrowck/src/dataflow.rs
@@ -393,6 +393,7 @@ impl<'tcx> rustc_mir_dataflow::GenKillAnalysis<'tcx> for Borrows<'_, 'tcx> {
             | mir::StatementKind::AscribeUserType(..)
             | mir::StatementKind::Coverage(..)
             | mir::StatementKind::Intrinsic(..)
+            | mir::StatementKind::ConstEvalCounter
             | mir::StatementKind::Nop => {}
         }
     }
diff --git a/compiler/rustc_borrowck/src/invalidation.rs b/compiler/rustc_borrowck/src/invalidation.rs
index 6fd9290058c..6217676d5c1 100644
--- a/compiler/rustc_borrowck/src/invalidation.rs
+++ b/compiler/rustc_borrowck/src/invalidation.rs
@@ -91,7 +91,8 @@ impl<'cx, 'tcx> Visitor<'tcx> for InvalidationGenerator<'cx, 'tcx> {
                     LocalMutationIsAllowed::Yes,
                 );
             }
-            StatementKind::Nop
+            StatementKind::ConstEvalCounter
+            | StatementKind::Nop
             | StatementKind::Retag { .. }
             | StatementKind::Deinit(..)
             | StatementKind::SetDiscriminant { .. } => {
diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs
index 73ea7314b75..8f8fae2c630 100644
--- a/compiler/rustc_borrowck/src/lib.rs
+++ b/compiler/rustc_borrowck/src/lib.rs
@@ -620,7 +620,8 @@ impl<'cx, 'tcx> rustc_mir_dataflow::ResultsVisitor<'cx, 'tcx> for MirBorrowckCtx
                     flow_state,
                 );
             }
-            StatementKind::Nop
+            StatementKind::ConstEvalCounter
+            | StatementKind::Nop
             | StatementKind::Retag { .. }
             | StatementKind::Deinit(..)
             | StatementKind::SetDiscriminant { .. } => {
diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs
index 81bd4c2a783..06087b0c579 100644
--- a/compiler/rustc_borrowck/src/type_check/mod.rs
+++ b/compiler/rustc_borrowck/src/type_check/mod.rs
@@ -1258,6 +1258,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
             | StatementKind::StorageDead(..)
             | StatementKind::Retag { .. }
             | StatementKind::Coverage(..)
+            | StatementKind::ConstEvalCounter
             | StatementKind::Nop => {}
             StatementKind::Deinit(..) | StatementKind::SetDiscriminant { .. } => {
                 bug!("Statement not allowed in this MIR phase")
diff --git a/compiler/rustc_codegen_ssa/src/mir/statement.rs b/compiler/rustc_codegen_ssa/src/mir/statement.rs
index 19452c8cdc8..60fbceb344d 100644
--- a/compiler/rustc_codegen_ssa/src/mir/statement.rs
+++ b/compiler/rustc_codegen_ssa/src/mir/statement.rs
@@ -91,6 +91,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
             mir::StatementKind::FakeRead(..)
             | mir::StatementKind::Retag { .. }
             | mir::StatementKind::AscribeUserType(..)
+            | mir::StatementKind::ConstEvalCounter
             | mir::StatementKind::Nop => {}
         }
     }
diff --git a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs
index 18e01567ca3..041e9d41357 100644
--- a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs
+++ b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs
@@ -22,6 +22,8 @@ use crate::interpret::{
     RefTracking, StackPopCleanup,
 };
 
+use tracing::info;
+
 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 \
      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>,
 ) -> InterpResult<'tcx, MPlaceTy<'tcx>> {
     debug!("eval_body_using_ecx: {:?}, {:?}", cid, ecx.param_env);
+    info!("HERE body is {:#?}", body);
     let tcx = *ecx.tcx;
     assert!(
         cid.promoted.is_some()
diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs
index 4709514c82e..befc71ce6a0 100644
--- a/compiler/rustc_const_eval/src/const_eval/machine.rs
+++ b/compiler/rustc_const_eval/src/const_eval/machine.rs
@@ -369,6 +369,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
         }
     }
 
+    #[instrument(skip(ecx), ret)]
     fn load_mir(
         ecx: &InterpCx<'mir, 'tcx, Self>,
         instance: ty::InstanceDef<'tcx>,
diff --git a/compiler/rustc_const_eval/src/interpret/eval_context.rs b/compiler/rustc_const_eval/src/interpret/eval_context.rs
index d13fed7a9c2..cc97564e8fc 100644
--- a/compiler/rustc_const_eval/src/interpret/eval_context.rs
+++ b/compiler/rustc_const_eval/src/interpret/eval_context.rs
@@ -46,6 +46,9 @@ pub struct InterpCx<'mir, 'tcx, M: Machine<'mir, 'tcx>> {
 
     /// The recursion limit (cached from `tcx.recursion_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
@@ -408,6 +411,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
             param_env,
             memory: Memory::new(),
             recursion_limit: tcx.recursion_limit(),
+            const_eval_limit: 20,
+            const_eval_counter: 0,
         }
     }
 
diff --git a/compiler/rustc_const_eval/src/interpret/place.rs b/compiler/rustc_const_eval/src/interpret/place.rs
index 274af61ee7c..271a3a74fe3 100644
--- a/compiler/rustc_const_eval/src/interpret/place.rs
+++ b/compiler/rustc_const_eval/src/interpret/place.rs
@@ -293,6 +293,17 @@ where
     Prov: Provenance + 'static,
     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.
     /// Alignment is just based on the type. This is the inverse of `MemPlace::to_ref()`.
     ///
diff --git a/compiler/rustc_const_eval/src/interpret/step.rs b/compiler/rustc_const_eval/src/interpret/step.rs
index fad4cb06cd6..0f0eb5aadd7 100644
--- a/compiler/rustc_const_eval/src/interpret/step.rs
+++ b/compiler/rustc_const_eval/src/interpret/step.rs
@@ -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
             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
             // size of MIR constantly.
             Nop => {}
diff --git a/compiler/rustc_const_eval/src/transform/check_consts/check.rs b/compiler/rustc_const_eval/src/transform/check_consts/check.rs
index 79f1737e32b..16b504dd9d4 100644
--- a/compiler/rustc_const_eval/src/transform/check_consts/check.rs
+++ b/compiler/rustc_const_eval/src/transform/check_consts/check.rs
@@ -693,6 +693,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
             | StatementKind::AscribeUserType(..)
             | StatementKind::Coverage(..)
             | StatementKind::Intrinsic(..)
+            | StatementKind::ConstEvalCounter
             | StatementKind::Nop => {}
         }
     }
diff --git a/compiler/rustc_const_eval/src/transform/validate.rs b/compiler/rustc_const_eval/src/transform/validate.rs
index dd168a9ac3c..4ad699c0395 100644
--- a/compiler/rustc_const_eval/src/transform/validate.rs
+++ b/compiler/rustc_const_eval/src/transform/validate.rs
@@ -766,6 +766,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
             StatementKind::StorageLive(..)
             | StatementKind::StorageDead(..)
             | StatementKind::Coverage(_)
+            | StatementKind::ConstEvalCounter
             | StatementKind::Nop => {}
         }
 
diff --git a/compiler/rustc_middle/src/mir/mod.rs b/compiler/rustc_middle/src/mir/mod.rs
index 4da893e4c07..dae7e84e415 100644
--- a/compiler/rustc_middle/src/mir/mod.rs
+++ b/compiler/rustc_middle/src/mir/mod.rs
@@ -1461,6 +1461,7 @@ impl Debug for Statement<'_> {
             }
             Coverage(box ref coverage) => write!(fmt, "Coverage::{:?}", coverage.kind),
             Intrinsic(box ref intrinsic) => write!(fmt, "{intrinsic}"),
+            ConstEvalCounter => write!(fmt, "ConstEvalCounter"),
             Nop => write!(fmt, "nop"),
         }
     }
diff --git a/compiler/rustc_middle/src/mir/query.rs b/compiler/rustc_middle/src/mir/query.rs
index a8a4532223c..c281fe00591 100644
--- a/compiler/rustc_middle/src/mir/query.rs
+++ b/compiler/rustc_middle/src/mir/query.rs
@@ -441,10 +441,14 @@ impl<'tcx> TyCtxt<'tcx> {
 
     #[inline]
     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))
         } else {
+            info!("calling mir_for_ctfe for DefId {:?}", def.did);
             self.mir_for_ctfe(def.did)
-        }
+        };
+        //info!("RES OF CALLING MIR_FOR_CTFE_OPT_CONST_ARG: {:#?}", res);
+        res
     }
 }
diff --git a/compiler/rustc_middle/src/mir/spanview.rs b/compiler/rustc_middle/src/mir/spanview.rs
index 887ee571575..7efe1fde093 100644
--- a/compiler/rustc_middle/src/mir/spanview.rs
+++ b/compiler/rustc_middle/src/mir/spanview.rs
@@ -250,6 +250,7 @@ pub fn statement_kind_name(statement: &Statement<'_>) -> &'static str {
         AscribeUserType(..) => "AscribeUserType",
         Coverage(..) => "Coverage",
         Intrinsic(..) => "Intrinsic",
+        ConstEvalCounter => "ConstEvalCounter",
         Nop => "Nop",
     }
 }
diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs
index 52c2b10cbbe..faf903a5949 100644
--- a/compiler/rustc_middle/src/mir/syntax.rs
+++ b/compiler/rustc_middle/src/mir/syntax.rs
@@ -286,7 +286,10 @@ pub enum StatementKind<'tcx> {
     /// 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
     /// the type.
-    SetDiscriminant { place: Box<Place<'tcx>>, variant_index: VariantIdx },
+    SetDiscriminant {
+        place: Box<Place<'tcx>>,
+        variant_index: VariantIdx,
+    },
 
     /// Deinitializes the place.
     ///
@@ -355,6 +358,8 @@ pub enum StatementKind<'tcx> {
     /// This avoids adding a new block and a terminator for simple intrinsics.
     Intrinsic(Box<NonDivergingIntrinsic<'tcx>>),
 
+    ConstEvalCounter,
+
     /// No-op. Useful for deleting instructions without affecting statement indices.
     Nop,
 }
diff --git a/compiler/rustc_middle/src/mir/visit.rs b/compiler/rustc_middle/src/mir/visit.rs
index 1a264d2d5af..3ddac5e11fb 100644
--- a/compiler/rustc_middle/src/mir/visit.rs
+++ b/compiler/rustc_middle/src/mir/visit.rs
@@ -427,6 +427,7 @@ macro_rules! make_mir_visitor {
                             }
                         }
                     }
+                    StatementKind::ConstEvalCounter => {}
                     StatementKind::Nop => {}
                 }
             }
diff --git a/compiler/rustc_mir_dataflow/src/impls/liveness.rs b/compiler/rustc_mir_dataflow/src/impls/liveness.rs
index 923dc16c11b..2890fa32cc9 100644
--- a/compiler/rustc_mir_dataflow/src/impls/liveness.rs
+++ b/compiler/rustc_mir_dataflow/src/impls/liveness.rs
@@ -271,6 +271,7 @@ impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> {
             | StatementKind::AscribeUserType(..)
             | StatementKind::Coverage(..)
             | StatementKind::Intrinsic(..)
+            | StatementKind::ConstEvalCounter
             | StatementKind::Nop => None,
         };
         if let Some(destination) = destination {
diff --git a/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs b/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs
index 8d379b90a86..fcf0ce9d821 100644
--- a/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs
+++ b/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs
@@ -141,6 +141,7 @@ impl<'mir, 'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'mir, 'tc
             StatementKind::AscribeUserType(..)
             | StatementKind::Coverage(..)
             | StatementKind::FakeRead(..)
+            | StatementKind::ConstEvalCounter
             | StatementKind::Nop
             | StatementKind::Retag(..)
             | StatementKind::Intrinsic(..)
diff --git a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs
index f46fd118bde..0195693a7cb 100644
--- a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs
+++ b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs
@@ -331,6 +331,7 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> {
             | StatementKind::AscribeUserType(..)
             | StatementKind::Coverage(..)
             | StatementKind::Intrinsic(..)
+            | StatementKind::ConstEvalCounter
             | StatementKind::Nop => {}
         }
     }
diff --git a/compiler/rustc_mir_dataflow/src/value_analysis.rs b/compiler/rustc_mir_dataflow/src/value_analysis.rs
index 0522c657939..6bdbda909d7 100644
--- a/compiler/rustc_mir_dataflow/src/value_analysis.rs
+++ b/compiler/rustc_mir_dataflow/src/value_analysis.rs
@@ -84,7 +84,8 @@ pub trait ValueAnalysis<'tcx> {
             StatementKind::Retag(..) => {
                 // We don't track references.
             }
-            StatementKind::Nop
+            StatementKind::ConstEvalCounter
+            | StatementKind::Nop
             | StatementKind::FakeRead(..)
             | StatementKind::Coverage(..)
             | StatementKind::AscribeUserType(..) => (),
diff --git a/compiler/rustc_mir_transform/src/check_unsafety.rs b/compiler/rustc_mir_transform/src/check_unsafety.rs
index adf6ae4c727..837233953e7 100644
--- a/compiler/rustc_mir_transform/src/check_unsafety.rs
+++ b/compiler/rustc_mir_transform/src/check_unsafety.rs
@@ -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)
             }
diff --git a/compiler/rustc_mir_transform/src/coverage/spans.rs b/compiler/rustc_mir_transform/src/coverage/spans.rs
index 31d5541a31b..f973c1ed28f 100644
--- a/compiler/rustc_mir_transform/src/coverage/spans.rs
+++ b/compiler/rustc_mir_transform/src/coverage/spans.rs
@@ -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,
 
diff --git a/compiler/rustc_mir_transform/src/ctfe_limit.rs b/compiler/rustc_mir_transform/src/ctfe_limit.rs
new file mode 100644
index 00000000000..dc54b983c0e
--- /dev/null
+++ b/compiler/rustc_mir_transform/src/ctfe_limit.rs
@@ -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:?}");
+    }
+}
diff --git a/compiler/rustc_mir_transform/src/dead_store_elimination.rs b/compiler/rustc_mir_transform/src/dead_store_elimination.rs
index 09546330cec..9dbfb089dc6 100644
--- a/compiler/rustc_mir_transform/src/dead_store_elimination.rs
+++ b/compiler/rustc_mir_transform/src/dead_store_elimination.rs
@@ -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(_, _) => {
diff --git a/compiler/rustc_mir_transform/src/dest_prop.rs b/compiler/rustc_mir_transform/src/dest_prop.rs
index 08e296a8371..20ffb0ab334 100644
--- a/compiler/rustc_mir_transform/src/dest_prop.rs
+++ b/compiler/rustc_mir_transform/src/dest_prop.rs
@@ -577,6 +577,7 @@ impl WriteInfo {
                 self.add_place(**place);
             }
             StatementKind::Intrinsic(_)
+            | StatementKind::ConstEvalCounter
             | StatementKind::Nop
             | StatementKind::Coverage(_)
             | StatementKind::StorageLive(_)
diff --git a/compiler/rustc_mir_transform/src/generator.rs b/compiler/rustc_mir_transform/src/generator.rs
index 39c61a34afc..0df732aa22b 100644
--- a/compiler/rustc_mir_transform/src/generator.rs
+++ b/compiler/rustc_mir_transform/src/generator.rs
@@ -1583,6 +1583,7 @@ impl<'tcx> Visitor<'tcx> for EnsureGeneratorFieldAssignmentsNeverAlias<'_> {
             | StatementKind::AscribeUserType(..)
             | StatementKind::Coverage(..)
             | StatementKind::Intrinsic(..)
+            | StatementKind::ConstEvalCounter
             | StatementKind::Nop => {}
         }
     }
diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs
index 20b7fdcfe6d..e5c8127bea1 100644
--- a/compiler/rustc_mir_transform/src/lib.rs
+++ b/compiler/rustc_mir_transform/src/lib.rs
@@ -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");
 
diff --git a/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs b/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs
index f1bbf2ea7e8..e3a03aa08af 100644
--- a/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs
+++ b/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs
@@ -35,6 +35,7 @@ impl RemoveNoopLandingPads {
                 | StatementKind::StorageDead(_)
                 | StatementKind::AscribeUserType(..)
                 | StatementKind::Coverage(..)
+                | StatementKind::ConstEvalCounter
                 | StatementKind::Nop => {
                     // These are all noops in a landing pad
                 }
diff --git a/compiler/rustc_mir_transform/src/separate_const_switch.rs b/compiler/rustc_mir_transform/src/separate_const_switch.rs
index 2f116aaa958..a24d2d34d79 100644
--- a/compiler/rustc_mir_transform/src/separate_const_switch.rs
+++ b/compiler/rustc_mir_transform/src/separate_const_switch.rs
@@ -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
diff --git a/compiler/rustc_mir_transform/src/simplify.rs b/compiler/rustc_mir_transform/src/simplify.rs
index 8f6abe7a912..7b6fa2baf2f 100644
--- a/compiler/rustc_mir_transform/src/simplify.rs
+++ b/compiler/rustc_mir_transform/src/simplify.rs
@@ -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) => {}
 
diff --git a/src/test/ui/consts/const-eval/stable-metric/ctfe-labelled-loop.rs b/src/test/ui/consts/const-eval/stable-metric/ctfe-labelled-loop.rs
new file mode 100644
index 00000000000..71f29ce8731
--- /dev/null
+++ b/src/test/ui/consts/const-eval/stable-metric/ctfe-labelled-loop.rs
@@ -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}");
+}
diff --git a/src/test/ui/consts/const-eval/stable-metric/ctfe-labelled-loop.stderr b/src/test/ui/consts/const-eval/stable-metric/ctfe-labelled-loop.stderr
new file mode 100644
index 00000000000..183bed3b75b
--- /dev/null
+++ b/src/test/ui/consts/const-eval/stable-metric/ctfe-labelled-loop.stderr
@@ -0,0 +1,4 @@
+warning: Const eval counter limit (20) has been crossed
+
+warning: 1 warning emitted
+
diff --git a/src/test/ui/consts/const-eval/stable-metric/ctfe-recursion.rs b/src/test/ui/consts/const-eval/stable-metric/ctfe-recursion.rs
new file mode 100644
index 00000000000..00b4fc25859
--- /dev/null
+++ b/src/test/ui/consts/const-eval/stable-metric/ctfe-recursion.rs
@@ -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}");
+}
diff --git a/src/test/ui/consts/const-eval/stable-metric/ctfe-recursion.stderr b/src/test/ui/consts/const-eval/stable-metric/ctfe-recursion.stderr
new file mode 100644
index 00000000000..183bed3b75b
--- /dev/null
+++ b/src/test/ui/consts/const-eval/stable-metric/ctfe-recursion.stderr
@@ -0,0 +1,4 @@
+warning: Const eval counter limit (20) has been crossed
+
+warning: 1 warning emitted
+
diff --git a/src/test/ui/consts/const-eval/stable-metric/ctfe-simple-loop.rs b/src/test/ui/consts/const-eval/stable-metric/ctfe-simple-loop.rs
new file mode 100644
index 00000000000..74dc74734b4
--- /dev/null
+++ b/src/test/ui/consts/const-eval/stable-metric/ctfe-simple-loop.rs
@@ -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}");
+}
diff --git a/src/test/ui/consts/const-eval/stable-metric/ctfe-simple-loop.stderr b/src/test/ui/consts/const-eval/stable-metric/ctfe-simple-loop.stderr
new file mode 100644
index 00000000000..183bed3b75b
--- /dev/null
+++ b/src/test/ui/consts/const-eval/stable-metric/ctfe-simple-loop.stderr
@@ -0,0 +1,4 @@
+warning: Const eval counter limit (20) has been crossed
+
+warning: 1 warning emitted
+