Skip to content

Commit 37e4d7f

Browse files
committed
Auto merge of rust-lang#121557 - RalfJung:const-fn-call-promotion, r=<try>
restrict promotion of `const fn` calls We only promote them in `const`/`static` initializers, but even that is still unfortunate -- we still cannot add promoteds to required_consts. But we should add them there to make sure it's always okay to evaluate every const we encounter in a MIR body. That effort of not promoting things that can fail to evaluate is tracked in rust-lang#80619. These `const fn` calls are the last missing piece. So I propose that we do not promote const-fn calls in const when that may fail without the entire const failing, thereby completing rust-lang#80619. Unfortunately we can't just reject promoting these functions outright due to backwards compatibility. So let's see if we can find a hack that makes crater happy... For the record, this is the [crater analysis](rust-lang#80243 (comment)) from when I tried to entirely forbid this kind of promotion. It's a tiny amount of breakage and if we had a nice alternative for code like that, we could conceivably push it through... but sadly, inline const expressions are still blocked on t-lang concerns about post-monomorphization errors and we haven't yet figured out an implementation that can resolve those concerns. So we're forced to make progress via other means, such as terrible hacks like this. Attempt one: only promote calls on the "safe path" at the beginning of a MIR block. This is the path that starts at the start block and continues via gotos and calls, but stops at the first branch. If we had imposed this restriction before stabilizing `if` and `match` in `const`, this would have definitely been sufficient... EDIT: Turns out that works. :) Here's the t-lang [nomination comment](rust-lang#121557 (comment)). r? `@oli-obk`
2 parents 5a6c1aa + db96f90 commit 37e4d7f

19 files changed

+243
-294
lines changed

compiler/rustc_const_eval/src/interpret/eval_context.rs

+17-17
Original file line numberDiff line numberDiff line change
@@ -792,15 +792,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
792792
self.stack_mut().push(frame);
793793

794794
// Make sure all the constants required by this frame evaluate successfully (post-monomorphization check).
795-
if M::POST_MONO_CHECKS {
796-
for &const_ in &body.required_consts {
797-
let c = self
798-
.instantiate_from_current_frame_and_normalize_erasing_regions(const_.const_)?;
799-
c.eval(*self.tcx, self.param_env, Some(const_.span)).map_err(|err| {
800-
err.emit_note(*self.tcx);
801-
err
802-
})?;
803-
}
795+
for &const_ in &body.required_consts {
796+
let c =
797+
self.instantiate_from_current_frame_and_normalize_erasing_regions(const_.const_)?;
798+
c.eval(*self.tcx, self.param_env, Some(const_.span)).map_err(|err| {
799+
err.emit_note(*self.tcx);
800+
err
801+
})?;
804802
}
805803

806804
// done
@@ -1141,18 +1139,20 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
11411139
self.raw_const_to_mplace(val)
11421140
}
11431141

1144-
pub fn eval_mir_constant(
1142+
/// Evaluate a constant from the current frame.
1143+
/// This function relies on the fact that when the frame got pushed, all `required_consts` have been checked.
1144+
pub(super) fn eval_frame_mir_constant(
11451145
&self,
1146-
val: &mir::Const<'tcx>,
1146+
const_: &mir::Const<'tcx>,
11471147
span: Option<Span>,
11481148
layout: Option<TyAndLayout<'tcx>>,
11491149
) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> {
1150-
M::eval_mir_constant(self, *val, span, layout, |ecx, val, span, layout| {
1151-
let const_val = val.eval(*ecx.tcx, ecx.param_env, span).map_err(|err| {
1152-
// FIXME: somehow this is reachable even when POST_MONO_CHECKS is on.
1153-
// Are we not always populating `required_consts`?
1154-
err.emit_note(*ecx.tcx);
1155-
err
1150+
M::eval_mir_constant(self, *const_, span, layout, |ecx, val, span, layout| {
1151+
let const_val = val.eval(*ecx.tcx, ecx.param_env, span).map_err(|err| match err {
1152+
ErrorHandled::Reported(..) => {
1153+
bug!("interpret const eval failure of {val:?} which is not in required_consts")
1154+
}
1155+
ErrorHandled::TooGeneric(_) => err,
11561156
})?;
11571157
ecx.const_val_to_op(const_val, val.ty(), layout)
11581158
})

compiler/rustc_const_eval/src/interpret/machine.rs

-3
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,6 @@ pub trait Machine<'mir, 'tcx: 'mir>: Sized {
140140
/// Should the machine panic on allocation failures?
141141
const PANIC_ON_ALLOC_FAIL: bool;
142142

143-
/// Should post-monomorphization checks be run when a stack frame is pushed?
144-
const POST_MONO_CHECKS: bool = true;
145-
146143
/// Whether memory accesses should be alignment-checked.
147144
fn enforce_alignment(ecx: &InterpCx<'mir, 'tcx, Self>) -> bool;
148145

compiler/rustc_const_eval/src/interpret/operand.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -740,14 +740,14 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
740740
// * During ConstProp, with `TooGeneric` or since the `required_consts` were not all
741741
// checked yet.
742742
// * During CTFE, since promoteds in `const`/`static` initializer bodies can fail.
743-
self.eval_mir_constant(&c, Some(constant.span), layout)?
743+
self.eval_frame_mir_constant(&c, Some(constant.span), layout)?
744744
}
745745
};
746746
trace!("{:?}: {:?}", mir_op, op);
747747
Ok(op)
748748
}
749749

750-
pub(crate) fn const_val_to_op(
750+
pub fn const_val_to_op(
751751
&self,
752752
val_val: mir::ConstValue<'tcx>,
753753
ty: Ty<'tcx>,

compiler/rustc_middle/src/mir/consts.rs

+16
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,22 @@ impl<'tcx> Const<'tcx> {
230230
))
231231
}
232232

233+
#[inline]
234+
pub fn is_required_const(&self) -> bool {
235+
// Only unevalated consts must be added to `required_consts` as only those can possibly
236+
// still have latent const-eval errors.
237+
match self {
238+
Const::Ty(c) => match c.kind() {
239+
ty::ConstKind::Param(_) | ty::ConstKind::Error(_) | ty::ConstKind::Value(_) => {
240+
false
241+
}
242+
_ => bug!("only ConstKind::Param/Value should be encountered here, got {:#?}", c),
243+
},
244+
Const::Unevaluated(..) => true,
245+
Const::Val(..) => false,
246+
}
247+
}
248+
233249
#[inline(always)]
234250
pub fn ty(&self) -> Ty<'tcx> {
235251
match self {

compiler/rustc_mir_transform/src/dataflow_const_prop.rs

+23-4
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,16 @@ use rustc_data_structures::fx::FxHashMap;
99
use rustc_hir::def::DefKind;
1010
use rustc_middle::mir::interpret::{AllocId, ConstAllocation, InterpResult, Scalar};
1111
use rustc_middle::mir::visit::{MutVisitor, PlaceContext, Visitor};
12-
use rustc_middle::mir::*;
12+
use rustc_middle::mir::{self, *};
1313
use rustc_middle::query::TyCtxtAt;
14-
use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
14+
use rustc_middle::ty::layout::{HasParamEnv, LayoutOf, TyAndLayout};
1515
use rustc_middle::ty::{self, Ty, TyCtxt};
1616
use rustc_mir_dataflow::value_analysis::{
1717
Map, PlaceIndex, State, TrackElem, ValueAnalysis, ValueAnalysisWrapper, ValueOrPlace,
1818
};
1919
use rustc_mir_dataflow::{lattice::FlatSet, Analysis, Results, ResultsVisitor};
2020
use rustc_span::def_id::DefId;
21-
use rustc_span::DUMMY_SP;
21+
use rustc_span::{Span, DUMMY_SP};
2222
use rustc_target::abi::{Abi, FieldIdx, Size, VariantIdx, FIRST_VARIANT};
2323

2424
/// Macro for machine-specific `InterpError` without allocation.
@@ -393,7 +393,9 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> {
393393
}
394394
}
395395
Operand::Constant(box constant) => {
396-
if let Ok(constant) = self.ecx.eval_mir_constant(&constant.const_, None, None) {
396+
if let Ok(constant) =
397+
DummyMachine::eval_mir_constant(&self.ecx, &constant.const_, None)
398+
{
397399
self.assign_constant(state, place, constant, &[]);
398400
}
399401
}
@@ -889,6 +891,23 @@ impl<'tcx> Visitor<'tcx> for OperandCollector<'tcx, '_, '_, '_> {
889891

890892
pub(crate) struct DummyMachine;
891893

894+
impl DummyMachine {
895+
/// Evaluate a MIR constant that doesn't have to be already pre-checked via `required_consts`.
896+
pub fn eval_mir_constant<'tcx>(
897+
ecx: &InterpCx<'_, 'tcx, Self>,
898+
const_: &mir::Const<'tcx>,
899+
span: Option<Span>,
900+
) -> InterpResult<'tcx, OpTy<'tcx>> {
901+
let const_val = const_.eval(*ecx.tcx, ecx.param_env(), span).map_err(|err| {
902+
// This *may* be the only time we're actually evaluating this const, so show a note at
903+
// the place it is used.
904+
err.emit_note(*ecx.tcx);
905+
err
906+
})?;
907+
ecx.const_val_to_op(const_val, const_.ty(), None)
908+
}
909+
}
910+
892911
impl HasStaticRootDefId for DummyMachine {
893912
fn static_def_id(&self) -> Option<rustc_hir::def_id::LocalDefId> {
894913
None

compiler/rustc_mir_transform/src/gvn.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
367367
Repeat(..) => return None,
368368

369369
Constant { ref value, disambiguator: _ } => {
370-
self.ecx.eval_mir_constant(value, None, None).ok()?
370+
DummyMachine::eval_mir_constant(&self.ecx, value, None).ok()?
371371
}
372372
Aggregate(kind, variant, ref fields) => {
373373
let fields = fields

compiler/rustc_mir_transform/src/jump_threading.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,8 @@ impl<'tcx, 'a> TOFinder<'tcx, 'a> {
416416
match rhs {
417417
// If we expect `lhs ?= A`, we have an opportunity if we assume `constant == A`.
418418
Operand::Constant(constant) => {
419-
let constant = self.ecx.eval_mir_constant(&constant.const_, None, None).ok()?;
419+
let constant =
420+
DummyMachine::eval_mir_constant(&self.ecx, &constant.const_, None).ok()?;
420421
self.process_constant(bb, lhs, constant, state);
421422
}
422423
// Transfer the conditions on the copied rhs.

compiler/rustc_mir_transform/src/known_panics_lint.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -259,9 +259,9 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
259259
// that the `RevealAll` pass has happened and that the body's consts
260260
// are normalized, so any call to resolve before that needs to be
261261
// manually normalized.
262-
let val = self.tcx.try_normalize_erasing_regions(self.param_env, c.const_).ok()?;
262+
let const_ = self.tcx.try_normalize_erasing_regions(self.param_env, c.const_).ok()?;
263263

264-
self.use_ecx(|this| this.ecx.eval_mir_constant(&val, Some(c.span), None))?
264+
self.use_ecx(|this| DummyMachine::eval_mir_constant(&this.ecx, &const_, Some(c.span)))?
265265
.as_mplace_or_imm()
266266
.right()
267267
}

compiler/rustc_mir_transform/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,8 @@ fn mir_promoted(
343343
body.tainted_by_errors = Some(error_reported);
344344
}
345345

346+
// Collect `required_consts` *before* promotion, so if there are any consts being promoted
347+
// we still add them to the list in the outer MIR body.
346348
let mut required_consts = Vec::new();
347349
let mut required_consts_visitor = RequiredConstsVisitor::new(&mut required_consts);
348350
for (bb, bb_data) in traversal::reverse_postorder(&body) {

0 commit comments

Comments
 (0)