Skip to content

Commit 3ea5456

Browse files
authored
Rollup merge of #100239 - RalfJung:const-prop-uninit, r=oli-obk
remove an ineffective check in const_prop Based on #100043, only the last two commits are new. ConstProp has a special check when reading from a local that prevents reading uninit locals. However, if that local flows into `force_allocation`, then no check fires and evaluation proceeds. So this check is not really effective at preventing accesses to uninit locals. With #100043, `read_immediate` and friends always fail when reading uninit locals, so I don't see why ConstProp would need a separate check. Thus I propose we remove it. This is needed to be able to do #100085.
2 parents 5555e13 + aff9841 commit 3ea5456

File tree

8 files changed

+64
-58
lines changed

8 files changed

+64
-58
lines changed

compiler/rustc_const_eval/src/interpret/eval_context.rs

-3
Original file line numberDiff line numberDiff line change
@@ -187,9 +187,6 @@ pub enum LocalValue<Prov: Provenance = AllocId> {
187187

188188
impl<'tcx, Prov: Provenance + 'static> LocalState<'tcx, Prov> {
189189
/// Read the local's value or error if the local is not yet live or not live anymore.
190-
///
191-
/// Note: This may only be invoked from the `Machine::access_local` hook and not from
192-
/// anywhere else. You may be invalidating machine invariants if you do!
193190
#[inline]
194191
pub fn access(&self) -> InterpResult<'tcx, &Operand<Prov>> {
195192
match &self.value {

compiler/rustc_const_eval/src/interpret/machine.rs

+3-14
Original file line numberDiff line numberDiff line change
@@ -215,23 +215,12 @@ pub trait Machine<'mir, 'tcx>: Sized {
215215
right: &ImmTy<'tcx, Self::Provenance>,
216216
) -> InterpResult<'tcx, (Scalar<Self::Provenance>, bool, Ty<'tcx>)>;
217217

218-
/// Called to read the specified `local` from the `frame`.
219-
/// Since reading a ZST is not actually accessing memory or locals, this is never invoked
220-
/// for ZST reads.
221-
#[inline]
222-
fn access_local<'a>(
223-
frame: &'a Frame<'mir, 'tcx, Self::Provenance, Self::FrameExtra>,
224-
local: mir::Local,
225-
) -> InterpResult<'tcx, &'a Operand<Self::Provenance>>
226-
where
227-
'tcx: 'mir,
228-
{
229-
frame.locals[local].access()
230-
}
231-
232218
/// Called to write the specified `local` from the `frame`.
233219
/// Since writing a ZST is not actually accessing memory or locals, this is never invoked
234220
/// for ZST reads.
221+
///
222+
/// Due to borrow checker trouble, we indicate the `frame` as an index rather than an `&mut
223+
/// Frame`.
235224
#[inline]
236225
fn access_local_mut<'a>(
237226
ecx: &'a mut InterpCx<'mir, 'tcx, Self>,

compiler/rustc_const_eval/src/interpret/operand.rs

+2-7
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
444444
}
445445
}
446446

447-
/// Read from a local. Will not actually access the local if reading from a ZST.
447+
/// Read from a local.
448448
/// Will not access memory, instead an indirect `Operand` is returned.
449449
///
450450
/// This is public because it is used by [priroda](https://github.com/oli-obk/priroda) to get an
@@ -456,12 +456,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
456456
layout: Option<TyAndLayout<'tcx>>,
457457
) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> {
458458
let layout = self.layout_of_local(frame, local, layout)?;
459-
let op = if layout.is_zst() {
460-
// Bypass `access_local` (helps in ConstProp)
461-
Operand::Immediate(Immediate::Uninit)
462-
} else {
463-
*M::access_local(frame, local)?
464-
};
459+
let op = *frame.locals[local].access()?;
465460
Ok(OpTy { op, layout, align: Some(layout.align.abi) })
466461
}
467462

compiler/rustc_const_eval/src/interpret/place.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,7 @@ where
642642
// avoid force_allocation.
643643
let src = match self.read_immediate_raw(src)? {
644644
Ok(src_val) => {
645-
assert!(!src.layout.is_unsized(), "cannot have unsized immediates");
645+
assert!(!src.layout.is_unsized(), "cannot copy unsized immediates");
646646
assert!(
647647
!dest.layout.is_unsized(),
648648
"the src is sized, so the dest must also be sized"

compiler/rustc_const_eval/src/interpret/projection.rs

+3
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ where
100100
// This makes several assumptions about what layouts we will encounter; we match what
101101
// codegen does as good as we can (see `extract_field` in `rustc_codegen_ssa/src/mir/operand.rs`).
102102
let field_val: Immediate<_> = match (*base, base.layout.abi) {
103+
// if the entire value is uninit, then so is the field (can happen in ConstProp)
104+
(Immediate::Uninit, _) => Immediate::Uninit,
103105
// the field contains no information, can be left uninit
104106
_ if field_layout.is_zst() => Immediate::Uninit,
105107
// the field covers the entire type
@@ -124,6 +126,7 @@ where
124126
b_val
125127
})
126128
}
129+
// everything else is a bug
127130
_ => span_bug!(
128131
self.cur_span(),
129132
"invalid field access on immediate {}, layout {:#?}",

compiler/rustc_mir_transform/src/const_prop.rs

+29-27
Original file line numberDiff line numberDiff line change
@@ -243,24 +243,6 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for ConstPropMachine<'mir, 'tcx>
243243
throw_machine_stop_str!("pointer arithmetic or comparisons aren't supported in ConstProp")
244244
}
245245

246-
fn access_local<'a>(
247-
frame: &'a Frame<'mir, 'tcx, Self::Provenance, Self::FrameExtra>,
248-
local: Local,
249-
) -> InterpResult<'tcx, &'a interpret::Operand<Self::Provenance>> {
250-
let l = &frame.locals[local];
251-
252-
if matches!(
253-
l.value,
254-
LocalValue::Live(interpret::Operand::Immediate(interpret::Immediate::Uninit))
255-
) {
256-
// For us "uninit" means "we don't know its value, might be initiailized or not".
257-
// So stop here.
258-
throw_machine_stop_str!("tried to access alocal with unknown value ")
259-
}
260-
261-
l.access()
262-
}
263-
264246
fn access_local_mut<'a>(
265247
ecx: &'a mut InterpCx<'mir, 'tcx, Self>,
266248
frame: usize,
@@ -431,7 +413,13 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
431413

432414
fn get_const(&self, place: Place<'tcx>) -> Option<OpTy<'tcx>> {
433415
let op = match self.ecx.eval_place_to_op(place, None) {
434-
Ok(op) => op,
416+
Ok(op) => {
417+
if matches!(*op, interpret::Operand::Immediate(Immediate::Uninit)) {
418+
// Make sure nobody accidentally uses this value.
419+
return None;
420+
}
421+
op
422+
}
435423
Err(e) => {
436424
trace!("get_const failed: {}", e);
437425
return None;
@@ -643,6 +631,14 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
643631
if rvalue.needs_subst() {
644632
return None;
645633
}
634+
if !rvalue
635+
.ty(&self.ecx.frame().body.local_decls, *self.ecx.tcx)
636+
.is_sized(self.ecx.tcx, self.param_env)
637+
{
638+
// the interpreter doesn't support unsized locals (only unsized arguments),
639+
// but rustc does (in a kinda broken way), so we have to skip them here
640+
return None;
641+
}
646642

647643
if self.tcx.sess.mir_opt_level() >= 4 {
648644
self.eval_rvalue_with_identities(rvalue, place)
@@ -660,18 +656,20 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
660656
self.use_ecx(|this| match rvalue {
661657
Rvalue::BinaryOp(op, box (left, right))
662658
| Rvalue::CheckedBinaryOp(op, box (left, right)) => {
663-
let l = this.ecx.eval_operand(left, None);
664-
let r = this.ecx.eval_operand(right, None);
659+
let l = this.ecx.eval_operand(left, None).and_then(|x| this.ecx.read_immediate(&x));
660+
let r =
661+
this.ecx.eval_operand(right, None).and_then(|x| this.ecx.read_immediate(&x));
665662

666663
let const_arg = match (l, r) {
667-
(Ok(ref x), Err(_)) | (Err(_), Ok(ref x)) => this.ecx.read_immediate(x)?,
668-
(Err(e), Err(_)) => return Err(e),
669-
(Ok(_), Ok(_)) => return this.ecx.eval_rvalue_into_place(rvalue, place),
664+
(Ok(x), Err(_)) | (Err(_), Ok(x)) => x, // exactly one side is known
665+
(Err(e), Err(_)) => return Err(e), // neither side is known
666+
(Ok(_), Ok(_)) => return this.ecx.eval_rvalue_into_place(rvalue, place), // both sides are known
670667
};
671668

672669
if !matches!(const_arg.layout.abi, abi::Abi::Scalar(..)) {
673670
// We cannot handle Scalar Pair stuff.
674-
return this.ecx.eval_rvalue_into_place(rvalue, place);
671+
// No point in calling `eval_rvalue_into_place`, since only one side is known
672+
throw_machine_stop_str!("cannot optimize this")
675673
}
676674

677675
let arg_value = const_arg.to_scalar().to_bits(const_arg.layout.size)?;
@@ -696,7 +694,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
696694
this.ecx.write_immediate(*const_arg, &dest)
697695
}
698696
}
699-
_ => this.ecx.eval_rvalue_into_place(rvalue, place),
697+
_ => throw_machine_stop_str!("cannot optimize this"),
700698
}
701699
}
702700
_ => this.ecx.eval_rvalue_into_place(rvalue, place),
@@ -1073,7 +1071,11 @@ impl<'tcx> MutVisitor<'tcx> for ConstPropagator<'_, 'tcx> {
10731071
if let Some(ref value) = self.eval_operand(&cond) {
10741072
trace!("assertion on {:?} should be {:?}", value, expected);
10751073
let expected = Scalar::from_bool(*expected);
1076-
let value_const = self.ecx.read_scalar(&value).unwrap();
1074+
let Ok(value_const) = self.ecx.read_scalar(&value) else {
1075+
// FIXME should be used use_ecx rather than a local match... but we have
1076+
// quite a few of these read_scalar/read_immediate that need fixing.
1077+
return
1078+
};
10771079
if expected != value_const {
10781080
// Poison all places this operand references so that further code
10791081
// doesn't use the invalid value

compiler/rustc_mir_transform/src/const_prop_lint.rs

+24-5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::const_prop::ConstPropMachine;
66
use crate::const_prop::ConstPropMode;
77
use crate::MirLint;
88
use rustc_const_eval::const_eval::ConstEvalErr;
9+
use rustc_const_eval::interpret::Immediate;
910
use rustc_const_eval::interpret::{
1011
self, InterpCx, InterpResult, LocalState, LocalValue, MemoryKind, OpTy, Scalar, StackPopCleanup,
1112
};
@@ -229,7 +230,13 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
229230

230231
fn get_const(&self, place: Place<'tcx>) -> Option<OpTy<'tcx>> {
231232
let op = match self.ecx.eval_place_to_op(place, None) {
232-
Ok(op) => op,
233+
Ok(op) => {
234+
if matches!(*op, interpret::Operand::Immediate(Immediate::Uninit)) {
235+
// Make sure nobody accidentally uses this value.
236+
return None;
237+
}
238+
op
239+
}
233240
Err(e) => {
234241
trace!("get_const failed: {}", e);
235242
return None;
@@ -515,6 +522,14 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
515522
if rvalue.needs_subst() {
516523
return None;
517524
}
525+
if !rvalue
526+
.ty(&self.ecx.frame().body.local_decls, *self.ecx.tcx)
527+
.is_sized(self.ecx.tcx, self.param_env)
528+
{
529+
// the interpreter doesn't support unsized locals (only unsized arguments),
530+
// but rustc does (in a kinda broken way), so we have to skip them here
531+
return None;
532+
}
518533

519534
self.use_ecx(source_info, |this| this.ecx.eval_rvalue_into_place(rvalue, place))
520535
}
@@ -624,7 +639,11 @@ impl<'tcx> Visitor<'tcx> for ConstPropagator<'_, 'tcx> {
624639
if let Some(ref value) = self.eval_operand(&cond, source_info) {
625640
trace!("assertion on {:?} should be {:?}", value, expected);
626641
let expected = Scalar::from_bool(*expected);
627-
let value_const = self.ecx.read_scalar(&value).unwrap();
642+
let Ok(value_const) = self.ecx.read_scalar(&value) else {
643+
// FIXME should be used use_ecx rather than a local match... but we have
644+
// quite a few of these read_scalar/read_immediate that need fixing.
645+
return
646+
};
628647
if expected != value_const {
629648
enum DbgVal<T> {
630649
Val(T),
@@ -641,9 +660,9 @@ impl<'tcx> Visitor<'tcx> for ConstPropagator<'_, 'tcx> {
641660
let mut eval_to_int = |op| {
642661
// This can be `None` if the lhs wasn't const propagated and we just
643662
// triggered the assert on the value of the rhs.
644-
self.eval_operand(op, source_info).map_or(DbgVal::Underscore, |op| {
645-
DbgVal::Val(self.ecx.read_immediate(&op).unwrap().to_const_int())
646-
})
663+
self.eval_operand(op, source_info)
664+
.and_then(|op| self.ecx.read_immediate(&op).ok())
665+
.map_or(DbgVal::Underscore, |op| DbgVal::Val(op.to_const_int()))
647666
};
648667
let msg = match msg {
649668
AssertKind::DivisionByZero(op) => {

src/test/mir-opt/const_prop/mutable_variable_unprop_assign.main.ConstProp.diff

+2-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@
4141
StorageLive(_4); // scope 2 at $DIR/mutable_variable_unprop_assign.rs:+4:9: +4:10
4242
_4 = (_2.1: i32); // scope 2 at $DIR/mutable_variable_unprop_assign.rs:+4:13: +4:16
4343
StorageLive(_5); // scope 3 at $DIR/mutable_variable_unprop_assign.rs:+5:9: +5:10
44-
_5 = (_2.0: i32); // scope 3 at $DIR/mutable_variable_unprop_assign.rs:+5:13: +5:16
44+
- _5 = (_2.0: i32); // scope 3 at $DIR/mutable_variable_unprop_assign.rs:+5:13: +5:16
45+
+ _5 = const 1_i32; // scope 3 at $DIR/mutable_variable_unprop_assign.rs:+5:13: +5:16
4546
nop; // scope 0 at $DIR/mutable_variable_unprop_assign.rs:+0:11: +6:2
4647
StorageDead(_5); // scope 3 at $DIR/mutable_variable_unprop_assign.rs:+6:1: +6:2
4748
StorageDead(_4); // scope 2 at $DIR/mutable_variable_unprop_assign.rs:+6:1: +6:2

0 commit comments

Comments
 (0)