Skip to content

Commit 3cdd019

Browse files
committed
Auto merge of rust-lang#106227 - bryangarza:ctfe-limit, r=oli-obk
Use stable metric for const eval limit instead of current terminator-based logic This patch adds a `MirPass` that inserts a new MIR instruction `ConstEvalCounter` to any loops and function calls in the CFG. This instruction is used during Const Eval to count against the `const_eval_limit`, and emit the `StepLimitReached` error, replacing the current logic which uses Terminators only. The new method of counting loops and function calls should be more stable across compiler versions (i.e., not cause crates that compiled successfully before, to no longer compile when changes to the MIR generation/optimization are made). Also see: rust-lang#103877
2 parents bcb064a + bdb815a commit 3cdd019

File tree

50 files changed

+400
-20
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+400
-20
lines changed

compiler/rustc_borrowck/src/dataflow.rs

+1
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,7 @@ impl<'tcx> rustc_mir_dataflow::GenKillAnalysis<'tcx> for Borrows<'_, 'tcx> {
393393
| mir::StatementKind::AscribeUserType(..)
394394
| mir::StatementKind::Coverage(..)
395395
| mir::StatementKind::Intrinsic(..)
396+
| mir::StatementKind::ConstEvalCounter
396397
| mir::StatementKind::Nop => {}
397398
}
398399
}

compiler/rustc_borrowck/src/invalidation.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ impl<'cx, 'tcx> Visitor<'tcx> for InvalidationGenerator<'cx, 'tcx> {
9191
LocalMutationIsAllowed::Yes,
9292
);
9393
}
94-
StatementKind::Nop
94+
StatementKind::ConstEvalCounter
95+
| StatementKind::Nop
9596
| StatementKind::Retag { .. }
9697
| StatementKind::Deinit(..)
9798
| StatementKind::SetDiscriminant { .. } => {

compiler/rustc_borrowck/src/lib.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,8 @@ impl<'cx, 'tcx> rustc_mir_dataflow::ResultsVisitor<'cx, 'tcx> for MirBorrowckCtx
609609
StatementKind::AscribeUserType(..)
610610
// Doesn't have any language semantics
611611
| StatementKind::Coverage(..)
612-
// Does not actually affect borrowck
612+
// These do not actually affect borrowck
613+
| StatementKind::ConstEvalCounter
613614
| StatementKind::StorageLive(..) => {}
614615
StatementKind::StorageDead(local) => {
615616
self.access_place(

compiler/rustc_borrowck/src/type_check/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1258,6 +1258,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
12581258
| StatementKind::StorageDead(..)
12591259
| StatementKind::Retag { .. }
12601260
| StatementKind::Coverage(..)
1261+
| StatementKind::ConstEvalCounter
12611262
| StatementKind::Nop => {}
12621263
StatementKind::Deinit(..) | StatementKind::SetDiscriminant { .. } => {
12631264
bug!("Statement not allowed in this MIR phase")

compiler/rustc_codegen_cranelift/src/base.rs

+1
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,7 @@ fn codegen_stmt<'tcx>(
794794
StatementKind::StorageLive(_)
795795
| StatementKind::StorageDead(_)
796796
| StatementKind::Deinit(_)
797+
| StatementKind::ConstEvalCounter
797798
| StatementKind::Nop
798799
| StatementKind::FakeRead(..)
799800
| StatementKind::Retag { .. }

compiler/rustc_codegen_cranelift/src/constant.rs

+1
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,7 @@ pub(crate) fn mir_operand_get_const_val<'tcx>(
530530
| StatementKind::Retag(_, _)
531531
| StatementKind::AscribeUserType(_, _)
532532
| StatementKind::Coverage(_)
533+
| StatementKind::ConstEvalCounter
533534
| StatementKind::Nop => {}
534535
}
535536
}

compiler/rustc_codegen_ssa/src/mir/statement.rs

+1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
9191
mir::StatementKind::FakeRead(..)
9292
| mir::StatementKind::Retag { .. }
9393
| mir::StatementKind::AscribeUserType(..)
94+
| mir::StatementKind::ConstEvalCounter
9495
| mir::StatementKind::Nop => {}
9596
}
9697
}

compiler/rustc_const_eval/src/const_eval/machine.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -561,8 +561,8 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
561561
throw_unsup_format!("pointer arithmetic or comparison is not supported at compile-time");
562562
}
563563

564-
fn before_terminator(ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> {
565-
// The step limit has already been hit in a previous call to `before_terminator`.
564+
fn increment_const_eval_counter(ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> {
565+
// The step limit has already been hit in a previous call to `increment_const_eval_counter`.
566566
if ecx.machine.steps_remaining == 0 {
567567
return Ok(());
568568
}

compiler/rustc_const_eval/src/interpret/machine.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -244,12 +244,18 @@ pub trait Machine<'mir, 'tcx>: Sized {
244244
}
245245

246246
/// Called before a basic block terminator is executed.
247-
/// You can use this to detect endlessly running programs.
248247
#[inline]
249248
fn before_terminator(_ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> {
250249
Ok(())
251250
}
252251

252+
/// Called when the interpreter encounters a `StatementKind::ConstEvalCounter` instruction.
253+
/// You can use this to detect long or endlessly running programs.
254+
#[inline]
255+
fn increment_const_eval_counter(_ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> {
256+
Ok(())
257+
}
258+
253259
/// Called before a global allocation is accessed.
254260
/// `def_id` is `Some` if this is the "lazy" allocation of a static.
255261
#[inline]

compiler/rustc_const_eval/src/interpret/step.rs

+4
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
129129
// FIXME(#73156): Handle source code coverage in const eval
130130
Coverage(..) => {}
131131

132+
ConstEvalCounter => {
133+
M::increment_const_eval_counter(self)?;
134+
}
135+
132136
// Defined to do nothing. These are added by optimization passes, to avoid changing the
133137
// size of MIR constantly.
134138
Nop => {}

compiler/rustc_const_eval/src/transform/check_consts/check.rs

+1
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
693693
| StatementKind::AscribeUserType(..)
694694
| StatementKind::Coverage(..)
695695
| StatementKind::Intrinsic(..)
696+
| StatementKind::ConstEvalCounter
696697
| StatementKind::Nop => {}
697698
}
698699
}

compiler/rustc_const_eval/src/transform/validate.rs

+1
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
761761
StatementKind::StorageLive(..)
762762
| StatementKind::StorageDead(..)
763763
| StatementKind::Coverage(_)
764+
| StatementKind::ConstEvalCounter
764765
| StatementKind::Nop => {}
765766
}
766767

compiler/rustc_data_structures/src/graph/dominators/mod.rs

+48-1
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,47 @@ pub fn dominators<G: ControlFlowGraph>(graph: G) -> Dominators<G::Node> {
135135
// This loop computes the semi[w] for w.
136136
semi[w] = w;
137137
for v in graph.predecessors(pre_order_to_real[w]) {
138-
// Reachable vertices may have unreachable predecessors, so ignore any of them
138+
// TL;DR: Reachable vertices may have unreachable predecessors, so ignore any of them.
139+
//
140+
// Ignore blocks which are not connected to the entry block.
141+
//
142+
// The algorithm that was used to traverse the graph and build the
143+
// `pre_order_to_real` and `real_to_pre_order` vectors does so by
144+
// starting from the entry block and following the successors.
145+
// Therefore, any blocks not reachable from the entry block will be
146+
// set to `None` in the `pre_order_to_real` vector.
147+
//
148+
// For example, in this graph, A and B should be skipped:
149+
//
150+
// ┌─────┐
151+
// │ │
152+
// └──┬──┘
153+
// │
154+
// ┌──▼──┐ ┌─────┐
155+
// │ │ │ A │
156+
// └──┬──┘ └──┬──┘
157+
// │ │
158+
// ┌───────┴───────┐ │
159+
// │ │ │
160+
// ┌──▼──┐ ┌──▼──┐ ┌──▼──┐
161+
// │ │ │ │ │ B │
162+
// └──┬──┘ └──┬──┘ └──┬──┘
163+
// │ └──────┬─────┘
164+
// ┌──▼──┐ │
165+
// │ │ │
166+
// └──┬──┘ ┌──▼──┐
167+
// │ │ │
168+
// │ └─────┘
169+
// ┌──▼──┐
170+
// │ │
171+
// └──┬──┘
172+
// │
173+
// ┌──▼──┐
174+
// │ │
175+
// └─────┘
176+
//
177+
// ...this may be the case if a MirPass modifies the CFG to remove
178+
// or rearrange certain blocks/edges.
139179
let Some(v) = real_to_pre_order[v] else {
140180
continue
141181
};
@@ -264,13 +304,18 @@ fn compress(
264304
}
265305
}
266306

307+
/// Tracks the list of dominators for each node.
267308
#[derive(Clone, Debug)]
268309
pub struct Dominators<N: Idx> {
269310
post_order_rank: IndexVec<N, usize>,
311+
// Even though we track only the immediate dominator of each node, it's
312+
// possible to get its full list of dominators by looking up the dominator
313+
// of each dominator. (See the `impl Iterator for Iter` definition).
270314
immediate_dominators: IndexVec<N, Option<N>>,
271315
}
272316

273317
impl<Node: Idx> Dominators<Node> {
318+
/// Whether the given Node has an immediate dominator.
274319
pub fn is_reachable(&self, node: Node) -> bool {
275320
self.immediate_dominators[node].is_some()
276321
}
@@ -280,6 +325,8 @@ impl<Node: Idx> Dominators<Node> {
280325
self.immediate_dominators[node].unwrap()
281326
}
282327

328+
/// Provides an iterator over each dominator up the CFG, for the given Node.
329+
/// See the `impl Iterator for Iter` definition to understand how this works.
283330
pub fn dominators(&self, node: Node) -> Iter<'_, Node> {
284331
assert!(self.is_reachable(node), "node {node:?} is not reachable");
285332
Iter { dominators: self, node: Some(node) }

compiler/rustc_interface/src/tests.rs

+1
Original file line numberDiff line numberDiff line change
@@ -802,6 +802,7 @@ fn test_unstable_options_tracking_hash() {
802802
tracked!(teach, true);
803803
tracked!(thinlto, Some(true));
804804
tracked!(thir_unsafeck, true);
805+
tracked!(tiny_const_eval_limit, true);
805806
tracked!(tls_model, Some(TlsModel::GeneralDynamic));
806807
tracked!(trait_solver, TraitSolver::Chalk);
807808
tracked!(translate_remapped_path_to_local_path, false);

compiler/rustc_middle/src/mir/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1463,6 +1463,7 @@ impl Debug for Statement<'_> {
14631463
}
14641464
Coverage(box ref coverage) => write!(fmt, "Coverage::{:?}", coverage.kind),
14651465
Intrinsic(box ref intrinsic) => write!(fmt, "{intrinsic}"),
1466+
ConstEvalCounter => write!(fmt, "ConstEvalCounter"),
14661467
Nop => write!(fmt, "nop"),
14671468
}
14681469
}

compiler/rustc_middle/src/mir/spanview.rs

+1
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ pub fn statement_kind_name(statement: &Statement<'_>) -> &'static str {
250250
AscribeUserType(..) => "AscribeUserType",
251251
Coverage(..) => "Coverage",
252252
Intrinsic(..) => "Intrinsic",
253+
ConstEvalCounter => "ConstEvalCounter",
253254
Nop => "Nop",
254255
}
255256
}

compiler/rustc_middle/src/mir/syntax.rs

+6
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,12 @@ pub enum StatementKind<'tcx> {
355355
/// This avoids adding a new block and a terminator for simple intrinsics.
356356
Intrinsic(Box<NonDivergingIntrinsic<'tcx>>),
357357

358+
/// Instructs the const eval interpreter to increment a counter; this counter is used to track
359+
/// how many steps the interpreter has taken. It is used to prevent the user from writing const
360+
/// code that runs for too long or infinitely. Other than in the const eval interpreter, this
361+
/// is a no-op.
362+
ConstEvalCounter,
363+
358364
/// No-op. Useful for deleting instructions without affecting statement indices.
359365
Nop,
360366
}

compiler/rustc_middle/src/mir/visit.rs

+1
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,7 @@ macro_rules! make_mir_visitor {
427427
}
428428
}
429429
}
430+
StatementKind::ConstEvalCounter => {}
430431
StatementKind::Nop => {}
431432
}
432433
}

compiler/rustc_middle/src/ty/context.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ use std::iter;
7777
use std::mem;
7878
use std::ops::{Bound, Deref};
7979

80+
const TINY_CONST_EVAL_LIMIT: Limit = Limit(20);
81+
8082
pub trait OnDiskCache<'tcx>: rustc_data_structures::sync::Sync {
8183
/// Creates a new `OnDiskCache` instance from the serialized data in `data`.
8284
fn new(sess: &'tcx Session, data: Mmap, start_pos: usize) -> Self
@@ -1104,7 +1106,11 @@ impl<'tcx> TyCtxt<'tcx> {
11041106
}
11051107

11061108
pub fn const_eval_limit(self) -> Limit {
1107-
self.limits(()).const_eval_limit
1109+
if self.sess.opts.unstable_opts.tiny_const_eval_limit {
1110+
TINY_CONST_EVAL_LIMIT
1111+
} else {
1112+
self.limits(()).const_eval_limit
1113+
}
11081114
}
11091115

11101116
pub fn all_traits(self) -> impl Iterator<Item = DefId> + 'tcx {

compiler/rustc_mir_dataflow/src/impls/liveness.rs

+1
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> {
271271
| StatementKind::AscribeUserType(..)
272272
| StatementKind::Coverage(..)
273273
| StatementKind::Intrinsic(..)
274+
| StatementKind::ConstEvalCounter
274275
| StatementKind::Nop => None,
275276
};
276277
if let Some(destination) = destination {

compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs

+1
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ impl<'mir, 'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'mir, 'tc
141141
StatementKind::AscribeUserType(..)
142142
| StatementKind::Coverage(..)
143143
| StatementKind::FakeRead(..)
144+
| StatementKind::ConstEvalCounter
144145
| StatementKind::Nop
145146
| StatementKind::Retag(..)
146147
| StatementKind::Intrinsic(..)

compiler/rustc_mir_dataflow/src/move_paths/builder.rs

+1
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,7 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> {
331331
| StatementKind::AscribeUserType(..)
332332
| StatementKind::Coverage(..)
333333
| StatementKind::Intrinsic(..)
334+
| StatementKind::ConstEvalCounter
334335
| StatementKind::Nop => {}
335336
}
336337
}

compiler/rustc_mir_dataflow/src/value_analysis.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ pub trait ValueAnalysis<'tcx> {
8484
StatementKind::Retag(..) => {
8585
// We don't track references.
8686
}
87-
StatementKind::Nop
87+
StatementKind::ConstEvalCounter
88+
| StatementKind::Nop
8889
| StatementKind::FakeRead(..)
8990
| StatementKind::Coverage(..)
9091
| StatementKind::AscribeUserType(..) => (),

compiler/rustc_mir_transform/src/check_unsafety.rs

+1
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ impl<'tcx> Visitor<'tcx> for UnsafetyChecker<'_, 'tcx> {
104104
| StatementKind::AscribeUserType(..)
105105
| StatementKind::Coverage(..)
106106
| StatementKind::Intrinsic(..)
107+
| StatementKind::ConstEvalCounter
107108
| StatementKind::Nop => {
108109
// safe (at least as emitted during MIR construction)
109110
}

compiler/rustc_mir_transform/src/coverage/spans.rs

+2
Original file line numberDiff line numberDiff line change
@@ -802,6 +802,8 @@ pub(super) fn filtered_statement_span(statement: &Statement<'_>) -> Option<Span>
802802
| StatementKind::StorageDead(_)
803803
// Coverage should not be encountered, but don't inject coverage coverage
804804
| StatementKind::Coverage(_)
805+
// Ignore `ConstEvalCounter`s
806+
| StatementKind::ConstEvalCounter
805807
// Ignore `Nop`s
806808
| StatementKind::Nop => None,
807809

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//! A pass that inserts the `ConstEvalCounter` instruction into any blocks that have a back edge
2+
//! (thus indicating there is a loop in the CFG), or whose terminator is a function call.
3+
use crate::MirPass;
4+
5+
use rustc_data_structures::graph::dominators::Dominators;
6+
use rustc_middle::mir::{
7+
BasicBlock, BasicBlockData, Body, Statement, StatementKind, TerminatorKind,
8+
};
9+
use rustc_middle::ty::TyCtxt;
10+
11+
pub struct CtfeLimit;
12+
13+
impl<'tcx> MirPass<'tcx> for CtfeLimit {
14+
#[instrument(skip(self, _tcx, body))]
15+
fn run_pass(&self, _tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
16+
let doms = body.basic_blocks.dominators();
17+
let indices: Vec<BasicBlock> = body
18+
.basic_blocks
19+
.iter_enumerated()
20+
.filter_map(|(node, node_data)| {
21+
if matches!(node_data.terminator().kind, TerminatorKind::Call { .. })
22+
// Back edges in a CFG indicate loops
23+
|| has_back_edge(&doms, node, &node_data)
24+
{
25+
Some(node)
26+
} else {
27+
None
28+
}
29+
})
30+
.collect();
31+
for index in indices {
32+
insert_counter(
33+
body.basic_blocks_mut()
34+
.get_mut(index)
35+
.expect("basic_blocks index {index} should exist"),
36+
);
37+
}
38+
}
39+
}
40+
41+
fn has_back_edge(
42+
doms: &Dominators<BasicBlock>,
43+
node: BasicBlock,
44+
node_data: &BasicBlockData<'_>,
45+
) -> bool {
46+
if !doms.is_reachable(node) {
47+
return false;
48+
}
49+
// Check if any of the dominators of the node are also the node's successor.
50+
doms.dominators(node)
51+
.any(|dom| node_data.terminator().successors().into_iter().any(|succ| succ == dom))
52+
}
53+
54+
fn insert_counter(basic_block_data: &mut BasicBlockData<'_>) {
55+
basic_block_data.statements.push(Statement {
56+
source_info: basic_block_data.terminator().source_info,
57+
kind: StatementKind::ConstEvalCounter,
58+
});
59+
}

compiler/rustc_mir_transform/src/dead_store_elimination.rs

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ pub fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, borrowed: &BitS
5353
| StatementKind::StorageDead(_)
5454
| StatementKind::Coverage(_)
5555
| StatementKind::Intrinsic(_)
56+
| StatementKind::ConstEvalCounter
5657
| StatementKind::Nop => (),
5758

5859
StatementKind::FakeRead(_) | StatementKind::AscribeUserType(_, _) => {

compiler/rustc_mir_transform/src/dest_prop.rs

+1
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,7 @@ impl WriteInfo {
577577
self.add_place(**place);
578578
}
579579
StatementKind::Intrinsic(_)
580+
| StatementKind::ConstEvalCounter
580581
| StatementKind::Nop
581582
| StatementKind::Coverage(_)
582583
| StatementKind::StorageLive(_)

compiler/rustc_mir_transform/src/generator.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1657,6 +1657,7 @@ impl<'tcx> Visitor<'tcx> for EnsureGeneratorFieldAssignmentsNeverAlias<'_> {
16571657
| StatementKind::AscribeUserType(..)
16581658
| StatementKind::Coverage(..)
16591659
| StatementKind::Intrinsic(..)
1660+
| StatementKind::ConstEvalCounter
16601661
| StatementKind::Nop => {}
16611662
}
16621663
}

0 commit comments

Comments
 (0)