Skip to content

Commit ae9b9cb

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

3 files changed

Lines changed: 149 additions & 4 deletions

File tree

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

0 commit comments

Comments
 (0)