Skip to content

Commit 781037b

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) And here's the [FCP comment](rust-lang#121557 (comment)). r? `@oli-obk`
2 parents df8ac8f + 8917837 commit 781037b

22 files changed

+363
-295
lines changed

Diff for: compiler/rustc_const_eval/src/const_eval/machine.rs

+5
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,11 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
375375

376376
const PANIC_ON_ALLOC_FAIL: bool = false; // will be raised as a proper error
377377

378+
#[inline]
379+
fn all_required_consts_are_checked(_ecx: &InterpCx<'mir, 'tcx, Self>) -> bool {
380+
true
381+
}
382+
378383
#[inline(always)]
379384
fn enforce_alignment(ecx: &InterpCx<'mir, 'tcx, Self>) -> bool {
380385
matches!(ecx.machine.check_alignment, CheckAlignment::Error)

Diff for: compiler/rustc_const_eval/src/interpret/eval_context.rs

+13-11
Original file line numberDiff line numberDiff line change
@@ -822,15 +822,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
822822
self.stack_mut().push(frame);
823823

824824
// Make sure all the constants required by this frame evaluate successfully (post-monomorphization check).
825-
if M::POST_MONO_CHECKS {
826-
for &const_ in &body.required_consts {
827-
let c = self
828-
.instantiate_from_current_frame_and_normalize_erasing_regions(const_.const_)?;
829-
c.eval(*self.tcx, self.param_env, const_.span).map_err(|err| {
830-
err.emit_note(*self.tcx);
831-
err
832-
})?;
833-
}
825+
for &const_ in &body.required_consts {
826+
let c =
827+
self.instantiate_from_current_frame_and_normalize_erasing_regions(const_.const_)?;
828+
c.eval(*self.tcx, self.param_env, const_.span).map_err(|err| {
829+
err.emit_note(*self.tcx);
830+
err
831+
})?;
834832
}
835833

836834
// done
@@ -1179,8 +1177,12 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
11791177
) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> {
11801178
M::eval_mir_constant(self, *val, span, layout, |ecx, val, span, layout| {
11811179
let const_val = val.eval(*ecx.tcx, ecx.param_env, span).map_err(|err| {
1182-
// FIXME: somehow this is reachable even when POST_MONO_CHECKS is on.
1183-
// Are we not always populating `required_consts`?
1180+
if M::all_required_consts_are_checked(self)
1181+
&& !matches!(err, ErrorHandled::TooGeneric(..))
1182+
{
1183+
// Looks like the const is not captued by `required_consts`, that's bad.
1184+
bug!("interpret const eval failure of {val:?} which is not in required_consts");
1185+
}
11841186
err.emit_note(*ecx.tcx);
11851187
err
11861188
})?;

Diff for: compiler/rustc_const_eval/src/interpret/machine.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,9 @@ 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;
143+
/// Determines whether `eval_mir_constant` can never fail because all required consts have
144+
/// already been checked before.
145+
fn all_required_consts_are_checked(ecx: &InterpCx<'mir, 'tcx, Self>) -> bool;
145146

146147
/// Whether memory accesses should be alignment-checked.
147148
fn enforce_alignment(ecx: &InterpCx<'mir, 'tcx, Self>) -> bool;

Diff for: compiler/rustc_middle/src/mir/consts.rs

+18
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,24 @@ impl<'tcx> Const<'tcx> {
238238
}
239239
}
240240

241+
/// Determines whether we need to add this const to `required_consts`. This is the case if and
242+
/// only if evaluating it may error.
243+
#[inline]
244+
pub fn is_required_const(&self) -> bool {
245+
match self {
246+
Const::Ty(c) => match c.kind() {
247+
ty::ConstKind::Value(_) => false, // already a value, cannot error
248+
ty::ConstKind::Param(_) | ty::ConstKind::Error(_) => true, // these are errors or could be replaced by errors
249+
_ => bug!(
250+
"only ConstKind::Param/Value/Error should be encountered here, got {:#?}",
251+
c
252+
),
253+
},
254+
Const::Unevaluated(..) => true,
255+
Const::Val(..) => false, // already a value, cannot error
256+
}
257+
}
258+
241259
#[inline]
242260
pub fn try_to_scalar(self) -> Option<Scalar> {
243261
match self {

Diff for: compiler/rustc_mir_transform/src/dataflow_const_prop.rs

+6
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,12 @@ impl<'mir, 'tcx: 'mir> rustc_const_eval::interpret::Machine<'mir, 'tcx> for Dumm
902902
type MemoryKind = !;
903903
const PANIC_ON_ALLOC_FAIL: bool = true;
904904

905+
#[inline(always)]
906+
fn all_required_consts_are_checked(_ecx: &InterpCx<'mir, 'tcx, Self>) -> bool {
907+
// We want to just eval random consts in the program, so `eval_mir_const` can fail.
908+
false
909+
}
910+
905911
#[inline(always)]
906912
fn enforce_alignment(_ecx: &InterpCx<'mir, 'tcx, Self>) -> bool {
907913
false // no reason to enforce alignment

Diff for: compiler/rustc_mir_transform/src/inline.rs

+9-14
Original file line numberDiff line numberDiff line change
@@ -706,18 +706,12 @@ impl<'tcx> Inliner<'tcx> {
706706
kind: TerminatorKind::Goto { target: integrator.map_block(START_BLOCK) },
707707
});
708708

709-
// Copy only unevaluated constants from the callee_body into the caller_body.
710-
// Although we are only pushing `ConstKind::Unevaluated` consts to
711-
// `required_consts`, here we may not only have `ConstKind::Unevaluated`
712-
// because we are calling `instantiate_and_normalize_erasing_regions`.
713-
caller_body.required_consts.extend(callee_body.required_consts.iter().copied().filter(
714-
|&ct| match ct.const_ {
715-
Const::Ty(_) => {
716-
bug!("should never encounter ty::UnevaluatedConst in `required_consts`")
717-
}
718-
Const::Val(..) | Const::Unevaluated(..) => true,
719-
},
720-
));
709+
// Copy required constants from the callee_body into the caller_body. Although we are only
710+
// pushing unevaluated consts to `required_consts`, here we they may have been evaluated
711+
// because we are calling `instantiate_and_normalize_erasing_regions` -- so we filter again.
712+
caller_body.required_consts.extend(
713+
callee_body.required_consts.into_iter().filter(|ct| ct.const_.is_required_const()),
714+
);
721715
// Now that we incorporated the callee's `required_consts`, we can remove the callee from
722716
// `mentioned_items` -- but we have to take their `mentioned_items` in return. This does
723717
// some extra work here to save the monomorphization collector work later. It helps a lot,
@@ -733,8 +727,9 @@ impl<'tcx> Inliner<'tcx> {
733727
caller_body.mentioned_items.remove(idx);
734728
caller_body.mentioned_items.extend(callee_body.mentioned_items);
735729
} else {
736-
// If we can't find the callee, there's no point in adding its items.
737-
// Probably it already got removed by being inlined elsewhere in the same function.
730+
// If we can't find the callee, there's no point in adding its items. Probably it
731+
// already got removed by being inlined elsewhere in the same function, so we already
732+
// took its items.
738733
}
739734
}
740735

Diff for: compiler/rustc_mir_transform/src/lib.rs

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

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

0 commit comments

Comments
 (0)