Skip to content

Commit 4039cef

Browse files
committed
Don't evaluate const blocks in constant promotion
1 parent b3cda16 commit 4039cef

File tree

3 files changed

+142
-4
lines changed

3 files changed

+142
-4
lines changed

compiler/rustc_mir_transform/src/promote_consts.rs

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use rustc_const_eval::check_consts::{ConstCx, qualifs};
1818
use rustc_data_structures::assert_matches;
1919
use rustc_data_structures::fx::FxHashSet;
2020
use rustc_hir as hir;
21+
use rustc_hir::def::DefKind;
2122
use rustc_index::{IndexSlice, IndexVec};
2223
use rustc_middle::mir::visit::{MutVisitor, MutatingUseContext, PlaceContext, Visitor};
2324
use rustc_middle::mir::*;
@@ -329,6 +330,7 @@ impl<'tcx> Validator<'_, 'tcx> {
329330
if let TempState::Defined { location: loc, .. } = self.temps[local]
330331
&& let Left(statement) = self.body.stmt_at(loc)
331332
&& let Some((_, Rvalue::Use(Operand::Constant(c)))) = statement.kind.as_assign()
333+
&& self.should_evaluate_for_promotion_checks(c.const_)
332334
&& let Some(idx) = c.const_.try_eval_target_usize(self.tcx, self.typing_env)
333335
// Determine the type of the thing we are indexing.
334336
&& let ty::Array(_, len) = place_base.ty(self.body, self.tcx).ty.kind()
@@ -484,7 +486,9 @@ impl<'tcx> Validator<'_, 'tcx> {
484486
let sz = lhs_ty.primitive_size(self.tcx);
485487
// Integer division: the RHS must be a non-zero const.
486488
let rhs_val = match rhs {
487-
Operand::Constant(c) => {
489+
Operand::Constant(c)
490+
if self.should_evaluate_for_promotion_checks(c.const_) =>
491+
{
488492
c.const_.try_eval_scalar_int(self.tcx, self.typing_env)
489493
}
490494
_ => None,
@@ -502,9 +506,14 @@ impl<'tcx> Validator<'_, 'tcx> {
502506
// The RHS is -1 or unknown, so we have to be careful.
503507
// But is the LHS int::MIN?
504508
let lhs_val = match lhs {
505-
Operand::Constant(c) => c
506-
.const_
507-
.try_eval_scalar_int(self.tcx, self.typing_env),
509+
Operand::Constant(c)
510+
if self.should_evaluate_for_promotion_checks(
511+
c.const_,
512+
) =>
513+
{
514+
c.const_
515+
.try_eval_scalar_int(self.tcx, self.typing_env)
516+
}
508517
_ => None,
509518
};
510519
let lhs_min = sz.signed_int_min();
@@ -683,6 +692,28 @@ impl<'tcx> Validator<'_, 'tcx> {
683692
// This passed all checks, so let's accept.
684693
Ok(())
685694
}
695+
696+
/// Can we try to evaluate a given constant at this point in compilation? Attempting to evaluate
697+
/// a const block before borrow-checking will result in a query cycle (#150464).
698+
fn should_evaluate_for_promotion_checks(&self, constant: Const<'tcx>) -> bool {
699+
match constant {
700+
// `Const::Ty` is always a `ConstKind::Param` right now and that can never be turned
701+
// into a mir value for promotion
702+
// FIXME(mgca): do we want uses of type_const to be normalized during promotion?
703+
Const::Ty(..) => false,
704+
Const::Val(..) => true,
705+
// Evaluating a MIR constant requires borrow-checking it. For inline consts, as of
706+
// #138499, this means borrow-checking its typeck root. Since borrow-checking the
707+
// typeck root requires promoting its constants, trying to evaluate an inline const here
708+
// will result in a query cycle. To avoid the cycle, we can't evaluate const blocks yet.
709+
// Other kinds of unevaluated's can cause query cycles too when they arise from
710+
// self-reference in user code; e.g. evaluating a constant can require evaluating a
711+
// const function that uses that constant, again requiring evaluation of the constant.
712+
// However, this form of cycle renders both the constant and function unusable in
713+
// general, so we don't need to special-case it here.
714+
Const::Unevaluated(uc, _) => self.tcx.def_kind(uc.def) != DefKind::InlineConst,
715+
}
716+
}
686717
}
687718

688719
fn validate_candidates(
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
error[E0716]: temporary value dropped while borrowed
2+
--> $DIR/dont-eval-const-block-during-promotion.rs:48:14
3+
|
4+
LL | x = &([0][const { 0 }] & 0);
5+
| ^^^^^^^^^^^^^^^^^^^^^^- temporary value is freed at the end of this statement
6+
| |
7+
| creates a temporary value which is freed while still in use
8+
...
9+
LL | (x, y, z);
10+
| - borrow later used here
11+
|
12+
= note: consider using a `let` binding to create a longer lived value
13+
14+
error[E0716]: temporary value dropped while borrowed
15+
--> $DIR/dont-eval-const-block-during-promotion.rs:50:14
16+
|
17+
LL | y = &(1 / const { 1 });
18+
| ^^^^^^^^^^^^^^^^^- temporary value is freed at the end of this statement
19+
| |
20+
| creates a temporary value which is freed while still in use
21+
...
22+
LL | (x, y, z);
23+
| - borrow later used here
24+
|
25+
= note: consider using a `let` binding to create a longer lived value
26+
27+
error[E0716]: temporary value dropped while borrowed
28+
--> $DIR/dont-eval-const-block-during-promotion.rs:52:14
29+
|
30+
LL | z = &(const { 1 } / -1);
31+
| ^^^^^^^^^^^^^^^^^^- temporary value is freed at the end of this statement
32+
| |
33+
| creates a temporary value which is freed while still in use
34+
LL |
35+
LL | (x, y, z);
36+
| - borrow later used here
37+
|
38+
= note: consider using a `let` binding to create a longer lived value
39+
40+
error: aborting due to 3 previous errors
41+
42+
For more information about this error, try `rustc --explain E0716`.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//! Test for #150464: as of #138499, trying to evaluate const blocks during constant promotion will
2+
//! result in a query cycle, so we shouldn't do it. Evaluation can happen when trying to promote
3+
//! integer division and array indexing, where it's necessary for the operation to succeed to be
4+
//! able to use it in a promoted constant.
5+
//@ revisions: pass fail
6+
//@[pass] check-pass
7+
8+
use std::mem::offset_of;
9+
10+
struct Thing(i32);
11+
12+
fn main() {
13+
// For a temporary involving array indexing to be promoted, we evaluate the index to make sure
14+
// it's in-bounds. As of #150557 we treat inline constants as maybe-out-of-bounds to avoid the
15+
// query cycle from evaluating them. That allows this to compile:
16+
let x = &([0][const { 0 }] & 0);
17+
// Likewise, integer divisors must be nonzero. Avoiding the query cycle allows this to compile:
18+
let y = &(1 / const { 1 });
19+
// Likewise, signed integer dividends can't be the integer minimum when the divisor is -1.
20+
let z = &(const { 1 } / -1);
21+
// These temporaries are all lifetime-extended, so they don't need to be promoted for references
22+
// to them to be live later in the block. Generally, code with const blocks in these positions
23+
// should compile as long as being promoted isn't necessary for borrow-checking to succeed.
24+
(x, y, z);
25+
26+
// A reduced example from real code (#150464): this can't be promoted since the array is a local
27+
// variable, but it still resulted in a query cycle because the index was evaluated for the
28+
// bounds-check before checking that. By not evaluating the const block, we avoid the cycle.
29+
// Since this doesn't rely on promotion, it should borrow-check successfully.
30+
let temp = [0u8];
31+
let _ = &(temp[const { 0usize }] & 0u8);
32+
// #150464 was reported because `offset_of!` started desugaring to a const block in #148151.
33+
let _ = &(temp[offset_of!(Thing, 0)] & 0u8);
34+
35+
// Similarly, at the time #150464 was reported, the index here was evaluated before checking
36+
// that the indexed expression is an array. As above, this can't be promoted, but still resulted
37+
// in a query cycle. By not evaluating the const block, we avoid the cycle. Since this doesn't
38+
// rely on promotion, it should borrow-check successfully.
39+
let temp: &[u8] = &[0u8];
40+
let _ = &(temp[const { 0usize }] & 0u8);
41+
42+
// By no longer promoting these temporaries, they're dropped at the ends of their respective
43+
// statements, so we can't refer to them thereafter. This code no longer query-cycles, but it
44+
// fails to borrow-check instead.
45+
#[cfg(fail)]
46+
{
47+
let (x, y, z);
48+
x = &([0][const { 0 }] & 0);
49+
//[fail]~^ ERROR: temporary value dropped while borrowed
50+
y = &(1 / const { 1 });
51+
//[fail]~^ ERROR: temporary value dropped while borrowed
52+
z = &(const { 1 } / -1);
53+
//[fail]~^ ERROR: temporary value dropped while borrowed
54+
(x, y, z);
55+
}
56+
57+
// Sanity check: those temporaries do promote if the const blocks are removed.
58+
// If constant promotion is changed so that these are no longer implicitly promoted, the
59+
// comments on this test file should be reworded to reflect that.
60+
let (x, y, z);
61+
x = &([0][0] & 0);
62+
y = &(1 / 1);
63+
z = &(1 / -1);
64+
(x, y, z);
65+
}

0 commit comments

Comments
 (0)