Skip to content

Commit 282445a

Browse files
committed
Auto merge of #97740 - RalfJung:ctfe-cycle-spans, r=lcnr
use precise spans for recursive const evaluation This fixes #73283 by using a `TyCtxtAt` with a more precise span when the interpreter recursively calls itself. Hopefully such calls are sufficiently rare that this does not cost us too much performance. (In theory, cycles can also arise through layout computation, as layout can depend on consts -- but layout computation happens all the time so we'd have to do something to not make this terrible for performance.)
2 parents 15f5622 + 467e0f4 commit 282445a

File tree

12 files changed

+66
-40
lines changed

12 files changed

+66
-40
lines changed

compiler/rustc_const_eval/src/interpret/eval_context.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,9 @@ pub struct Frame<'mir, 'tcx, Tag: Provenance = AllocId, Extra = ()> {
126126
/// this frame (can happen e.g. during frame initialization, and during unwinding on
127127
/// frames without cleanup code).
128128
/// We basically abuse `Result` as `Either`.
129-
pub(super) loc: Result<mir::Location, Span>,
129+
///
130+
/// Needs to be public because ConstProp does unspeakable things to it.
131+
pub loc: Result<mir::Location, Span>,
130132
}
131133

132134
/// What we store about a frame in an interpreter backtrace.
@@ -320,6 +322,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> LayoutOfHelpers<'tcx> for InterpC
320322

321323
#[inline]
322324
fn layout_tcx_at_span(&self) -> Span {
325+
// Using the cheap root span for performance.
323326
self.tcx.span
324327
}
325328

@@ -923,7 +926,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
923926
self.param_env
924927
};
925928
let param_env = param_env.with_const();
926-
let val = self.tcx.eval_to_allocation_raw(param_env.and(gid))?;
929+
// Use a precise span for better cycle errors.
930+
let val = self.tcx.at(self.cur_span()).eval_to_allocation_raw(param_env.and(gid))?;
927931
self.raw_const_to_mplace(val)
928932
}
929933

compiler/rustc_const_eval/src/interpret/intrinsics.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
169169
sym::needs_drop => self.tcx.types.bool,
170170
sym::type_id => self.tcx.types.u64,
171171
sym::type_name => self.tcx.mk_static_str(),
172-
_ => bug!("already checked for nullary intrinsics"),
172+
_ => bug!(),
173173
};
174174
let val =
175175
self.tcx.const_eval_global_id(self.param_env, gid, Some(self.tcx.span))?;
@@ -215,7 +215,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
215215
sym::add_with_overflow => BinOp::Add,
216216
sym::sub_with_overflow => BinOp::Sub,
217217
sym::mul_with_overflow => BinOp::Mul,
218-
_ => bug!("Already checked for int ops"),
218+
_ => bug!(),
219219
};
220220
self.binop_with_overflow(bin_op, &lhs, &rhs, dest)?;
221221
}
@@ -251,7 +251,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
251251
sym::unchecked_mul => BinOp::Mul,
252252
sym::unchecked_div => BinOp::Div,
253253
sym::unchecked_rem => BinOp::Rem,
254-
_ => bug!("Already checked for int ops"),
254+
_ => bug!(),
255255
};
256256
let (val, overflowed, _ty) = self.overflowing_binary_op(bin_op, &l, &r)?;
257257
if overflowed {

compiler/rustc_const_eval/src/interpret/intrinsics/caller_location.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
7070
}
7171
}
7272

73-
bug!("no non-`#[track_caller]` frame found")
73+
span_bug!(self.cur_span(), "no non-`#[track_caller]` frame found")
7474
}
7575

7676
/// Allocate a `const core::panic::Location` with the provided filename and line/column numbers.

compiler/rustc_const_eval/src/interpret/memory.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
504504
throw_unsup!(ReadExternStatic(def_id));
505505
}
506506

507-
(self.tcx.eval_static_initializer(def_id)?, Some(def_id))
507+
// Use a precise span for better cycle errors.
508+
(self.tcx.at(self.cur_span()).eval_static_initializer(def_id)?, Some(def_id))
508509
}
509510
};
510511
M::before_access_global(*self.tcx, &self.machine, id, alloc, def_id, is_write)?;

compiler/rustc_const_eval/src/interpret/operator.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -154,14 +154,14 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
154154
let result = match bin_op {
155155
Shl => l.checked_shl(r).unwrap(),
156156
Shr => l.checked_shr(r).unwrap(),
157-
_ => bug!("it has already been checked that this is a shift op"),
157+
_ => bug!(),
158158
};
159159
result as u128
160160
} else {
161161
match bin_op {
162162
Shl => l.checked_shl(r).unwrap(),
163163
Shr => l.checked_shr(r).unwrap(),
164-
_ => bug!("it has already been checked that this is a shift op"),
164+
_ => bug!(),
165165
}
166166
};
167167
let truncated = self.truncate(result, left_layout);

compiler/rustc_const_eval/src/interpret/step.rs

+9-10
Original file line numberDiff line numberDiff line change
@@ -55,33 +55,32 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
5555
};
5656
let basic_block = &self.body().basic_blocks()[loc.block];
5757

58-
let old_frames = self.frame_idx();
59-
6058
if let Some(stmt) = basic_block.statements.get(loc.statement_index) {
61-
assert_eq!(old_frames, self.frame_idx());
59+
let old_frames = self.frame_idx();
6260
self.statement(stmt)?;
61+
// Make sure we are not updating `statement_index` of the wrong frame.
62+
assert_eq!(old_frames, self.frame_idx());
63+
// Advance the program counter.
64+
self.frame_mut().loc.as_mut().unwrap().statement_index += 1;
6365
return Ok(true);
6466
}
6567

6668
M::before_terminator(self)?;
6769

6870
let terminator = basic_block.terminator();
69-
assert_eq!(old_frames, self.frame_idx());
7071
self.terminator(terminator)?;
7172
Ok(true)
7273
}
7374

7475
/// Runs the interpretation logic for the given `mir::Statement` at the current frame and
75-
/// statement counter. This also moves the statement counter forward.
76+
/// statement counter.
77+
///
78+
/// This does NOT move the statement counter forward, the caller has to do that!
7679
pub fn statement(&mut self, stmt: &mir::Statement<'tcx>) -> InterpResult<'tcx> {
7780
info!("{:?}", stmt);
7881

7982
use rustc_middle::mir::StatementKind::*;
8083

81-
// Some statements (e.g., box) push new stack frames.
82-
// We have to record the stack frame number *before* executing the statement.
83-
let frame_idx = self.frame_idx();
84-
8584
match &stmt.kind {
8685
Assign(box (place, rvalue)) => self.eval_rvalue_into_place(rvalue, *place)?,
8786

@@ -144,7 +143,6 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
144143
Nop => {}
145144
}
146145

147-
self.stack_mut()[frame_idx].loc.as_mut().unwrap().statement_index += 1;
148146
Ok(())
149147
}
150148

@@ -300,6 +298,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
300298
Ok(())
301299
}
302300

301+
/// Evaluate the given terminator. Will also adjust the stack frame and statement position accordingly.
303302
fn terminator(&mut self, terminator: &mir::Terminator<'tcx>) -> InterpResult<'tcx> {
304303
info!("{:?}", terminator.kind);
305304

compiler/rustc_middle/src/mir/interpret/queries.rs

+16-3
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ use super::{ErrorHandled, EvalToConstValueResult, GlobalId};
33
use crate::mir;
44
use crate::ty::fold::TypeFoldable;
55
use crate::ty::subst::InternalSubsts;
6-
use crate::ty::{self, TyCtxt};
6+
use crate::ty::{self, query::TyCtxtAt, TyCtxt};
77
use rustc_hir::def_id::DefId;
8-
use rustc_span::Span;
8+
use rustc_span::{Span, DUMMY_SP};
99

1010
impl<'tcx> TyCtxt<'tcx> {
1111
/// Evaluates a constant without providing any substitutions. This is useful to evaluate consts
@@ -86,14 +86,25 @@ impl<'tcx> TyCtxt<'tcx> {
8686
}
8787
}
8888

89+
/// Evaluate a static's initializer, returning the allocation of the initializer's memory.
90+
#[inline(always)]
91+
pub fn eval_static_initializer(
92+
self,
93+
def_id: DefId,
94+
) -> Result<mir::ConstAllocation<'tcx>, ErrorHandled> {
95+
self.at(DUMMY_SP).eval_static_initializer(def_id)
96+
}
97+
}
98+
99+
impl<'tcx> TyCtxtAt<'tcx> {
89100
/// Evaluate a static's initializer, returning the allocation of the initializer's memory.
90101
pub fn eval_static_initializer(
91102
self,
92103
def_id: DefId,
93104
) -> Result<mir::ConstAllocation<'tcx>, ErrorHandled> {
94105
trace!("eval_static_initializer: Need to compute {:?}", def_id);
95106
assert!(self.is_static(def_id));
96-
let instance = ty::Instance::mono(self, def_id);
107+
let instance = ty::Instance::mono(*self, def_id);
97108
let gid = GlobalId { instance, promoted: None };
98109
self.eval_to_allocation(gid, ty::ParamEnv::reveal_all())
99110
}
@@ -109,7 +120,9 @@ impl<'tcx> TyCtxt<'tcx> {
109120
let raw_const = self.eval_to_allocation_raw(param_env.and(gid))?;
110121
Ok(self.global_alloc(raw_const.alloc_id).unwrap_memory())
111122
}
123+
}
112124

125+
impl<'tcx> TyCtxt<'tcx> {
113126
/// Destructure a type-level constant ADT or array into its variant index and its field values.
114127
/// Panics if the destructuring fails, use `try_destructure_const` for fallible version.
115128
pub fn destructure_const(

compiler/rustc_mir_transform/src/const_prop_lint.rs

+19-10
Original file line numberDiff line numberDiff line change
@@ -437,10 +437,12 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
437437
source_info.scope.lint_root(self.source_scopes)
438438
}
439439

440-
fn use_ecx<F, T>(&mut self, f: F) -> Option<T>
440+
fn use_ecx<F, T>(&mut self, source_info: SourceInfo, f: F) -> Option<T>
441441
where
442442
F: FnOnce(&mut Self) -> InterpResult<'tcx, T>,
443443
{
444+
// Overwrite the PC -- whatever the interpreter does to it does not make any sense anyway.
445+
self.ecx.frame_mut().loc = Err(source_info.span);
444446
match f(self) {
445447
Ok(val) => Some(val),
446448
Err(error) => {
@@ -501,17 +503,17 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
501503
}
502504

503505
/// Returns the value, if any, of evaluating `place`.
504-
fn eval_place(&mut self, place: Place<'tcx>) -> Option<OpTy<'tcx>> {
506+
fn eval_place(&mut self, place: Place<'tcx>, source_info: SourceInfo) -> Option<OpTy<'tcx>> {
505507
trace!("eval_place(place={:?})", place);
506-
self.use_ecx(|this| this.ecx.eval_place_to_op(place, None))
508+
self.use_ecx(source_info, |this| this.ecx.eval_place_to_op(place, None))
507509
}
508510

509511
/// Returns the value, if any, of evaluating `op`. Calls upon `eval_constant`
510512
/// or `eval_place`, depending on the variant of `Operand` used.
511513
fn eval_operand(&mut self, op: &Operand<'tcx>, source_info: SourceInfo) -> Option<OpTy<'tcx>> {
512514
match *op {
513515
Operand::Constant(ref c) => self.eval_constant(c, source_info),
514-
Operand::Move(place) | Operand::Copy(place) => self.eval_place(place),
516+
Operand::Move(place) | Operand::Copy(place) => self.eval_place(place, source_info),
515517
}
516518
}
517519

@@ -537,7 +539,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
537539
arg: &Operand<'tcx>,
538540
source_info: SourceInfo,
539541
) -> Option<()> {
540-
if let (val, true) = self.use_ecx(|this| {
542+
if let (val, true) = self.use_ecx(source_info, |this| {
541543
let val = this.ecx.read_immediate(&this.ecx.eval_operand(arg, None)?)?;
542544
let (_res, overflow, _ty) = this.ecx.overflowing_unary_op(op, &val)?;
543545
Ok((val, overflow))
@@ -564,8 +566,12 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
564566
right: &Operand<'tcx>,
565567
source_info: SourceInfo,
566568
) -> Option<()> {
567-
let r = self.use_ecx(|this| this.ecx.read_immediate(&this.ecx.eval_operand(right, None)?));
568-
let l = self.use_ecx(|this| this.ecx.read_immediate(&this.ecx.eval_operand(left, None)?));
569+
let r = self.use_ecx(source_info, |this| {
570+
this.ecx.read_immediate(&this.ecx.eval_operand(right, None)?)
571+
});
572+
let l = self.use_ecx(source_info, |this| {
573+
this.ecx.read_immediate(&this.ecx.eval_operand(left, None)?)
574+
});
569575
// Check for exceeding shifts *even if* we cannot evaluate the LHS.
570576
if op == BinOp::Shr || op == BinOp::Shl {
571577
let r = r?;
@@ -602,7 +608,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
602608

603609
if let (Some(l), Some(r)) = (&l, &r) {
604610
// The remaining operators are handled through `overflowing_binary_op`.
605-
if self.use_ecx(|this| {
611+
if self.use_ecx(source_info, |this| {
606612
let (_res, overflow, _ty) = this.ecx.overflowing_binary_op(op, l, r)?;
607613
Ok(overflow)
608614
})? {
@@ -690,7 +696,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
690696
return None;
691697
}
692698

693-
self.use_ecx(|this| this.ecx.eval_rvalue_into_place(rvalue, place))
699+
self.use_ecx(source_info, |this| this.ecx.eval_rvalue_into_place(rvalue, place))
694700
}
695701
}
696702

@@ -890,7 +896,10 @@ impl<'tcx> Visitor<'tcx> for ConstPropagator<'_, 'tcx> {
890896
StatementKind::SetDiscriminant { ref place, .. } => {
891897
match self.ecx.machine.can_const_prop[place.local] {
892898
ConstPropMode::FullConstProp | ConstPropMode::OnlyInsideOwnBlock => {
893-
if self.use_ecx(|this| this.ecx.statement(statement)).is_some() {
899+
if self
900+
.use_ecx(source_info, |this| this.ecx.statement(statement))
901+
.is_some()
902+
{
894903
trace!("propped discriminant into {:?}", place);
895904
} else {
896905
Self::remove_const(&mut self.ecx, place.local);

src/test/ui/consts/recursive-zst-static.default.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ LL | static FOO: () = FOO;
55
| ^^^^^^^^^^^^^^^^^^^^^
66
|
77
note: ...which requires const-evaluating + checking `FOO`...
8-
--> $DIR/recursive-zst-static.rs:10:1
8+
--> $DIR/recursive-zst-static.rs:10:18
99
|
1010
LL | static FOO: () = FOO;
11-
| ^^^^^^^^^^^^^^^^^^^^^
11+
| ^^^
1212
= note: ...which again requires const-evaluating + checking `FOO`, completing the cycle
1313
= note: cycle used when running analysis passes on this crate
1414

src/test/ui/consts/recursive-zst-static.unleash.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ LL | static FOO: () = FOO;
55
| ^^^^^^^^^^^^^^^^^^^^^
66
|
77
note: ...which requires const-evaluating + checking `FOO`...
8-
--> $DIR/recursive-zst-static.rs:10:1
8+
--> $DIR/recursive-zst-static.rs:10:18
99
|
1010
LL | static FOO: () = FOO;
11-
| ^^^^^^^^^^^^^^^^^^^^^
11+
| ^^^
1212
= note: ...which again requires const-evaluating + checking `FOO`, completing the cycle
1313
= note: cycle used when running analysis passes on this crate
1414

src/test/ui/consts/write-to-static-mut-in-static.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ LL | pub static mut C: u32 = unsafe { C = 1; 0 };
1111
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1212
|
1313
note: ...which requires const-evaluating + checking `C`...
14-
--> $DIR/write-to-static-mut-in-static.rs:5:1
14+
--> $DIR/write-to-static-mut-in-static.rs:5:34
1515
|
1616
LL | pub static mut C: u32 = unsafe { C = 1; 0 };
17-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
17+
| ^^^^^
1818
= note: ...which again requires const-evaluating + checking `C`, completing the cycle
1919
= note: cycle used when running analysis passes on this crate
2020

src/test/ui/recursion/recursive-static-definition.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ LL | pub static FOO: u32 = FOO;
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
66
|
77
note: ...which requires const-evaluating + checking `FOO`...
8-
--> $DIR/recursive-static-definition.rs:1:1
8+
--> $DIR/recursive-static-definition.rs:1:23
99
|
1010
LL | pub static FOO: u32 = FOO;
11-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
11+
| ^^^
1212
= note: ...which again requires const-evaluating + checking `FOO`, completing the cycle
1313
= note: cycle used when running analysis passes on this crate
1414

0 commit comments

Comments
 (0)