1
Fork 0

Auto merge of #97740 - RalfJung:ctfe-cycle-spans, r=lcnr

use precise spans for recursive const evaluation

This fixes https://github.com/rust-lang/rust/issues/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.)
This commit is contained in:
bors 2022-06-09 01:52:15 +00:00
commit 282445a288
12 changed files with 66 additions and 40 deletions

View file

@ -126,7 +126,9 @@ pub struct Frame<'mir, 'tcx, Tag: Provenance = AllocId, Extra = ()> {
/// this frame (can happen e.g. during frame initialization, and during unwinding on /// this frame (can happen e.g. during frame initialization, and during unwinding on
/// frames without cleanup code). /// frames without cleanup code).
/// We basically abuse `Result` as `Either`. /// We basically abuse `Result` as `Either`.
pub(super) loc: Result<mir::Location, Span>, ///
/// Needs to be public because ConstProp does unspeakable things to it.
pub loc: Result<mir::Location, Span>,
} }
/// What we store about a frame in an interpreter backtrace. /// 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
#[inline] #[inline]
fn layout_tcx_at_span(&self) -> Span { fn layout_tcx_at_span(&self) -> Span {
// Using the cheap root span for performance.
self.tcx.span self.tcx.span
} }
@ -923,7 +926,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
self.param_env self.param_env
}; };
let param_env = param_env.with_const(); let param_env = param_env.with_const();
let val = self.tcx.eval_to_allocation_raw(param_env.and(gid))?; // Use a precise span for better cycle errors.
let val = self.tcx.at(self.cur_span()).eval_to_allocation_raw(param_env.and(gid))?;
self.raw_const_to_mplace(val) self.raw_const_to_mplace(val)
} }

View file

@ -169,7 +169,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
sym::needs_drop => self.tcx.types.bool, sym::needs_drop => self.tcx.types.bool,
sym::type_id => self.tcx.types.u64, sym::type_id => self.tcx.types.u64,
sym::type_name => self.tcx.mk_static_str(), sym::type_name => self.tcx.mk_static_str(),
_ => bug!("already checked for nullary intrinsics"), _ => bug!(),
}; };
let val = let val =
self.tcx.const_eval_global_id(self.param_env, gid, Some(self.tcx.span))?; 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> {
sym::add_with_overflow => BinOp::Add, sym::add_with_overflow => BinOp::Add,
sym::sub_with_overflow => BinOp::Sub, sym::sub_with_overflow => BinOp::Sub,
sym::mul_with_overflow => BinOp::Mul, sym::mul_with_overflow => BinOp::Mul,
_ => bug!("Already checked for int ops"), _ => bug!(),
}; };
self.binop_with_overflow(bin_op, &lhs, &rhs, dest)?; self.binop_with_overflow(bin_op, &lhs, &rhs, dest)?;
} }
@ -251,7 +251,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
sym::unchecked_mul => BinOp::Mul, sym::unchecked_mul => BinOp::Mul,
sym::unchecked_div => BinOp::Div, sym::unchecked_div => BinOp::Div,
sym::unchecked_rem => BinOp::Rem, sym::unchecked_rem => BinOp::Rem,
_ => bug!("Already checked for int ops"), _ => bug!(),
}; };
let (val, overflowed, _ty) = self.overflowing_binary_op(bin_op, &l, &r)?; let (val, overflowed, _ty) = self.overflowing_binary_op(bin_op, &l, &r)?;
if overflowed { if overflowed {

View file

@ -70,7 +70,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
} }
} }
bug!("no non-`#[track_caller]` frame found") span_bug!(self.cur_span(), "no non-`#[track_caller]` frame found")
} }
/// Allocate a `const core::panic::Location` with the provided filename and line/column numbers. /// Allocate a `const core::panic::Location` with the provided filename and line/column numbers.

View file

@ -504,7 +504,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
throw_unsup!(ReadExternStatic(def_id)); throw_unsup!(ReadExternStatic(def_id));
} }
(self.tcx.eval_static_initializer(def_id)?, Some(def_id)) // Use a precise span for better cycle errors.
(self.tcx.at(self.cur_span()).eval_static_initializer(def_id)?, Some(def_id))
} }
}; };
M::before_access_global(*self.tcx, &self.machine, id, alloc, def_id, is_write)?; M::before_access_global(*self.tcx, &self.machine, id, alloc, def_id, is_write)?;

View file

@ -154,14 +154,14 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
let result = match bin_op { let result = match bin_op {
Shl => l.checked_shl(r).unwrap(), Shl => l.checked_shl(r).unwrap(),
Shr => l.checked_shr(r).unwrap(), Shr => l.checked_shr(r).unwrap(),
_ => bug!("it has already been checked that this is a shift op"), _ => bug!(),
}; };
result as u128 result as u128
} else { } else {
match bin_op { match bin_op {
Shl => l.checked_shl(r).unwrap(), Shl => l.checked_shl(r).unwrap(),
Shr => l.checked_shr(r).unwrap(), Shr => l.checked_shr(r).unwrap(),
_ => bug!("it has already been checked that this is a shift op"), _ => bug!(),
} }
}; };
let truncated = self.truncate(result, left_layout); let truncated = self.truncate(result, left_layout);

View file

@ -55,33 +55,32 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
}; };
let basic_block = &self.body().basic_blocks()[loc.block]; let basic_block = &self.body().basic_blocks()[loc.block];
let old_frames = self.frame_idx();
if let Some(stmt) = basic_block.statements.get(loc.statement_index) { if let Some(stmt) = basic_block.statements.get(loc.statement_index) {
assert_eq!(old_frames, self.frame_idx()); let old_frames = self.frame_idx();
self.statement(stmt)?; self.statement(stmt)?;
// Make sure we are not updating `statement_index` of the wrong frame.
assert_eq!(old_frames, self.frame_idx());
// Advance the program counter.
self.frame_mut().loc.as_mut().unwrap().statement_index += 1;
return Ok(true); return Ok(true);
} }
M::before_terminator(self)?; M::before_terminator(self)?;
let terminator = basic_block.terminator(); let terminator = basic_block.terminator();
assert_eq!(old_frames, self.frame_idx());
self.terminator(terminator)?; self.terminator(terminator)?;
Ok(true) Ok(true)
} }
/// Runs the interpretation logic for the given `mir::Statement` at the current frame and /// Runs the interpretation logic for the given `mir::Statement` at the current frame and
/// statement counter. This also moves the statement counter forward. /// statement counter.
///
/// This does NOT move the statement counter forward, the caller has to do that!
pub fn statement(&mut self, stmt: &mir::Statement<'tcx>) -> InterpResult<'tcx> { pub fn statement(&mut self, stmt: &mir::Statement<'tcx>) -> InterpResult<'tcx> {
info!("{:?}", stmt); info!("{:?}", stmt);
use rustc_middle::mir::StatementKind::*; use rustc_middle::mir::StatementKind::*;
// Some statements (e.g., box) push new stack frames.
// We have to record the stack frame number *before* executing the statement.
let frame_idx = self.frame_idx();
match &stmt.kind { match &stmt.kind {
Assign(box (place, rvalue)) => self.eval_rvalue_into_place(rvalue, *place)?, Assign(box (place, rvalue)) => self.eval_rvalue_into_place(rvalue, *place)?,
@ -144,7 +143,6 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
Nop => {} Nop => {}
} }
self.stack_mut()[frame_idx].loc.as_mut().unwrap().statement_index += 1;
Ok(()) Ok(())
} }
@ -300,6 +298,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
Ok(()) Ok(())
} }
/// Evaluate the given terminator. Will also adjust the stack frame and statement position accordingly.
fn terminator(&mut self, terminator: &mir::Terminator<'tcx>) -> InterpResult<'tcx> { fn terminator(&mut self, terminator: &mir::Terminator<'tcx>) -> InterpResult<'tcx> {
info!("{:?}", terminator.kind); info!("{:?}", terminator.kind);

View file

@ -3,9 +3,9 @@ use super::{ErrorHandled, EvalToConstValueResult, GlobalId};
use crate::mir; use crate::mir;
use crate::ty::fold::TypeFoldable; use crate::ty::fold::TypeFoldable;
use crate::ty::subst::InternalSubsts; use crate::ty::subst::InternalSubsts;
use crate::ty::{self, TyCtxt}; use crate::ty::{self, query::TyCtxtAt, TyCtxt};
use rustc_hir::def_id::DefId; use rustc_hir::def_id::DefId;
use rustc_span::Span; use rustc_span::{Span, DUMMY_SP};
impl<'tcx> TyCtxt<'tcx> { impl<'tcx> TyCtxt<'tcx> {
/// Evaluates a constant without providing any substitutions. This is useful to evaluate consts /// Evaluates a constant without providing any substitutions. This is useful to evaluate consts
@ -86,6 +86,17 @@ impl<'tcx> TyCtxt<'tcx> {
} }
} }
/// Evaluate a static's initializer, returning the allocation of the initializer's memory.
#[inline(always)]
pub fn eval_static_initializer(
self,
def_id: DefId,
) -> Result<mir::ConstAllocation<'tcx>, ErrorHandled> {
self.at(DUMMY_SP).eval_static_initializer(def_id)
}
}
impl<'tcx> TyCtxtAt<'tcx> {
/// Evaluate a static's initializer, returning the allocation of the initializer's memory. /// Evaluate a static's initializer, returning the allocation of the initializer's memory.
pub fn eval_static_initializer( pub fn eval_static_initializer(
self, self,
@ -93,7 +104,7 @@ impl<'tcx> TyCtxt<'tcx> {
) -> Result<mir::ConstAllocation<'tcx>, ErrorHandled> { ) -> Result<mir::ConstAllocation<'tcx>, ErrorHandled> {
trace!("eval_static_initializer: Need to compute {:?}", def_id); trace!("eval_static_initializer: Need to compute {:?}", def_id);
assert!(self.is_static(def_id)); assert!(self.is_static(def_id));
let instance = ty::Instance::mono(self, def_id); let instance = ty::Instance::mono(*self, def_id);
let gid = GlobalId { instance, promoted: None }; let gid = GlobalId { instance, promoted: None };
self.eval_to_allocation(gid, ty::ParamEnv::reveal_all()) self.eval_to_allocation(gid, ty::ParamEnv::reveal_all())
} }
@ -109,7 +120,9 @@ impl<'tcx> TyCtxt<'tcx> {
let raw_const = self.eval_to_allocation_raw(param_env.and(gid))?; let raw_const = self.eval_to_allocation_raw(param_env.and(gid))?;
Ok(self.global_alloc(raw_const.alloc_id).unwrap_memory()) Ok(self.global_alloc(raw_const.alloc_id).unwrap_memory())
} }
}
impl<'tcx> TyCtxt<'tcx> {
/// Destructure a type-level constant ADT or array into its variant index and its field values. /// Destructure a type-level constant ADT or array into its variant index and its field values.
/// Panics if the destructuring fails, use `try_destructure_const` for fallible version. /// Panics if the destructuring fails, use `try_destructure_const` for fallible version.
pub fn destructure_const( pub fn destructure_const(

View file

@ -437,10 +437,12 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
source_info.scope.lint_root(self.source_scopes) source_info.scope.lint_root(self.source_scopes)
} }
fn use_ecx<F, T>(&mut self, f: F) -> Option<T> fn use_ecx<F, T>(&mut self, source_info: SourceInfo, f: F) -> Option<T>
where where
F: FnOnce(&mut Self) -> InterpResult<'tcx, T>, F: FnOnce(&mut Self) -> InterpResult<'tcx, T>,
{ {
// Overwrite the PC -- whatever the interpreter does to it does not make any sense anyway.
self.ecx.frame_mut().loc = Err(source_info.span);
match f(self) { match f(self) {
Ok(val) => Some(val), Ok(val) => Some(val),
Err(error) => { Err(error) => {
@ -501,9 +503,9 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
} }
/// Returns the value, if any, of evaluating `place`. /// Returns the value, if any, of evaluating `place`.
fn eval_place(&mut self, place: Place<'tcx>) -> Option<OpTy<'tcx>> { fn eval_place(&mut self, place: Place<'tcx>, source_info: SourceInfo) -> Option<OpTy<'tcx>> {
trace!("eval_place(place={:?})", place); trace!("eval_place(place={:?})", place);
self.use_ecx(|this| this.ecx.eval_place_to_op(place, None)) self.use_ecx(source_info, |this| this.ecx.eval_place_to_op(place, None))
} }
/// Returns the value, if any, of evaluating `op`. Calls upon `eval_constant` /// Returns the value, if any, of evaluating `op`. Calls upon `eval_constant`
@ -511,7 +513,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
fn eval_operand(&mut self, op: &Operand<'tcx>, source_info: SourceInfo) -> Option<OpTy<'tcx>> { fn eval_operand(&mut self, op: &Operand<'tcx>, source_info: SourceInfo) -> Option<OpTy<'tcx>> {
match *op { match *op {
Operand::Constant(ref c) => self.eval_constant(c, source_info), Operand::Constant(ref c) => self.eval_constant(c, source_info),
Operand::Move(place) | Operand::Copy(place) => self.eval_place(place), Operand::Move(place) | Operand::Copy(place) => self.eval_place(place, source_info),
} }
} }
@ -537,7 +539,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
arg: &Operand<'tcx>, arg: &Operand<'tcx>,
source_info: SourceInfo, source_info: SourceInfo,
) -> Option<()> { ) -> Option<()> {
if let (val, true) = self.use_ecx(|this| { if let (val, true) = self.use_ecx(source_info, |this| {
let val = this.ecx.read_immediate(&this.ecx.eval_operand(arg, None)?)?; let val = this.ecx.read_immediate(&this.ecx.eval_operand(arg, None)?)?;
let (_res, overflow, _ty) = this.ecx.overflowing_unary_op(op, &val)?; let (_res, overflow, _ty) = this.ecx.overflowing_unary_op(op, &val)?;
Ok((val, overflow)) Ok((val, overflow))
@ -564,8 +566,12 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
right: &Operand<'tcx>, right: &Operand<'tcx>,
source_info: SourceInfo, source_info: SourceInfo,
) -> Option<()> { ) -> Option<()> {
let r = self.use_ecx(|this| this.ecx.read_immediate(&this.ecx.eval_operand(right, None)?)); let r = self.use_ecx(source_info, |this| {
let l = self.use_ecx(|this| this.ecx.read_immediate(&this.ecx.eval_operand(left, None)?)); this.ecx.read_immediate(&this.ecx.eval_operand(right, None)?)
});
let l = self.use_ecx(source_info, |this| {
this.ecx.read_immediate(&this.ecx.eval_operand(left, None)?)
});
// Check for exceeding shifts *even if* we cannot evaluate the LHS. // Check for exceeding shifts *even if* we cannot evaluate the LHS.
if op == BinOp::Shr || op == BinOp::Shl { if op == BinOp::Shr || op == BinOp::Shl {
let r = r?; let r = r?;
@ -602,7 +608,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
if let (Some(l), Some(r)) = (&l, &r) { if let (Some(l), Some(r)) = (&l, &r) {
// The remaining operators are handled through `overflowing_binary_op`. // The remaining operators are handled through `overflowing_binary_op`.
if self.use_ecx(|this| { if self.use_ecx(source_info, |this| {
let (_res, overflow, _ty) = this.ecx.overflowing_binary_op(op, l, r)?; let (_res, overflow, _ty) = this.ecx.overflowing_binary_op(op, l, r)?;
Ok(overflow) Ok(overflow)
})? { })? {
@ -690,7 +696,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
return None; return None;
} }
self.use_ecx(|this| this.ecx.eval_rvalue_into_place(rvalue, place)) self.use_ecx(source_info, |this| this.ecx.eval_rvalue_into_place(rvalue, place))
} }
} }
@ -890,7 +896,10 @@ impl<'tcx> Visitor<'tcx> for ConstPropagator<'_, 'tcx> {
StatementKind::SetDiscriminant { ref place, .. } => { StatementKind::SetDiscriminant { ref place, .. } => {
match self.ecx.machine.can_const_prop[place.local] { match self.ecx.machine.can_const_prop[place.local] {
ConstPropMode::FullConstProp | ConstPropMode::OnlyInsideOwnBlock => { ConstPropMode::FullConstProp | ConstPropMode::OnlyInsideOwnBlock => {
if self.use_ecx(|this| this.ecx.statement(statement)).is_some() { if self
.use_ecx(source_info, |this| this.ecx.statement(statement))
.is_some()
{
trace!("propped discriminant into {:?}", place); trace!("propped discriminant into {:?}", place);
} else { } else {
Self::remove_const(&mut self.ecx, place.local); Self::remove_const(&mut self.ecx, place.local);

View file

@ -5,10 +5,10 @@ LL | static FOO: () = FOO;
| ^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^
| |
note: ...which requires const-evaluating + checking `FOO`... note: ...which requires const-evaluating + checking `FOO`...
--> $DIR/recursive-zst-static.rs:10:1 --> $DIR/recursive-zst-static.rs:10:18
| |
LL | static FOO: () = FOO; LL | static FOO: () = FOO;
| ^^^^^^^^^^^^^^^^^^^^^ | ^^^
= note: ...which again requires const-evaluating + checking `FOO`, completing the cycle = note: ...which again requires const-evaluating + checking `FOO`, completing the cycle
= note: cycle used when running analysis passes on this crate = note: cycle used when running analysis passes on this crate

View file

@ -5,10 +5,10 @@ LL | static FOO: () = FOO;
| ^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^
| |
note: ...which requires const-evaluating + checking `FOO`... note: ...which requires const-evaluating + checking `FOO`...
--> $DIR/recursive-zst-static.rs:10:1 --> $DIR/recursive-zst-static.rs:10:18
| |
LL | static FOO: () = FOO; LL | static FOO: () = FOO;
| ^^^^^^^^^^^^^^^^^^^^^ | ^^^
= note: ...which again requires const-evaluating + checking `FOO`, completing the cycle = note: ...which again requires const-evaluating + checking `FOO`, completing the cycle
= note: cycle used when running analysis passes on this crate = note: cycle used when running analysis passes on this crate

View file

@ -11,10 +11,10 @@ LL | pub static mut C: u32 = unsafe { C = 1; 0 };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
note: ...which requires const-evaluating + checking `C`... note: ...which requires const-evaluating + checking `C`...
--> $DIR/write-to-static-mut-in-static.rs:5:1 --> $DIR/write-to-static-mut-in-static.rs:5:34
| |
LL | pub static mut C: u32 = unsafe { C = 1; 0 }; LL | pub static mut C: u32 = unsafe { C = 1; 0 };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^
= note: ...which again requires const-evaluating + checking `C`, completing the cycle = note: ...which again requires const-evaluating + checking `C`, completing the cycle
= note: cycle used when running analysis passes on this crate = note: cycle used when running analysis passes on this crate

View file

@ -5,10 +5,10 @@ LL | pub static FOO: u32 = FOO;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
note: ...which requires const-evaluating + checking `FOO`... note: ...which requires const-evaluating + checking `FOO`...
--> $DIR/recursive-static-definition.rs:1:1 --> $DIR/recursive-static-definition.rs:1:23
| |
LL | pub static FOO: u32 = FOO; LL | pub static FOO: u32 = FOO;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^
= note: ...which again requires const-evaluating + checking `FOO`, completing the cycle = note: ...which again requires const-evaluating + checking `FOO`, completing the cycle
= note: cycle used when running analysis passes on this crate = note: cycle used when running analysis passes on this crate