Skip to content

Commit b05bb29

Browse files
committed
Auto merge of #108872 - cjgillot:simp-const-prop, r=oli-obk
Strengthen state tracking in const-prop Some/many of the changes are replicated between both the const-prop lint and the const-prop optimization. Behaviour changes: - const-prop opt does not give a span to propagated values. This was useless as that span's primary purpose is to diagnose evaluation failure in codegen. - we remove the `OnlyPropagateInto` mode. It was only used for function arguments, which are better modeled by a write before entry. - the tracking of assignments and discriminants make clearer that we do nothing in `NoPropagation` mode or on indirect places.
2 parents 7b4f489 + 8179b2e commit b05bb29

14 files changed

+249
-330
lines changed

compiler/rustc_const_eval/src/interpret/eval_context.rs

+13-17
Original file line numberDiff line numberDiff line change
@@ -536,24 +536,20 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
536536
local: mir::Local,
537537
layout: Option<TyAndLayout<'tcx>>,
538538
) -> InterpResult<'tcx, TyAndLayout<'tcx>> {
539-
// `const_prop` runs into this with an invalid (empty) frame, so we
540-
// have to support that case (mostly by skipping all caching).
541-
match frame.locals.get(local).and_then(|state| state.layout.get()) {
542-
None => {
543-
let layout = from_known_layout(self.tcx, self.param_env, layout, || {
544-
let local_ty = frame.body.local_decls[local].ty;
545-
let local_ty =
546-
self.subst_from_frame_and_normalize_erasing_regions(frame, local_ty)?;
547-
self.layout_of(local_ty)
548-
})?;
549-
if let Some(state) = frame.locals.get(local) {
550-
// Layouts of locals are requested a lot, so we cache them.
551-
state.layout.set(Some(layout));
552-
}
553-
Ok(layout)
554-
}
555-
Some(layout) => Ok(layout),
539+
let state = &frame.locals[local];
540+
if let Some(layout) = state.layout.get() {
541+
return Ok(layout);
556542
}
543+
544+
let layout = from_known_layout(self.tcx, self.param_env, layout, || {
545+
let local_ty = frame.body.local_decls[local].ty;
546+
let local_ty = self.subst_from_frame_and_normalize_erasing_regions(frame, local_ty)?;
547+
self.layout_of(local_ty)
548+
})?;
549+
550+
// Layouts of locals are requested a lot, so we cache them.
551+
state.layout.set(Some(layout));
552+
Ok(layout)
557553
}
558554

559555
/// Returns the actual dynamic size and alignment of the place at the given type.

compiler/rustc_mir_transform/src/const_prop.rs

+100-169
Large diffs are not rendered by default.

compiler/rustc_mir_transform/src/const_prop_lint.rs

+95-108
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,17 @@
11
//! Propagates constants for early reporting of statically known
22
//! assertion failures
33
4-
use std::cell::Cell;
5-
64
use either::{Left, Right};
75

86
use rustc_const_eval::interpret::Immediate;
97
use rustc_const_eval::interpret::{
10-
self, InterpCx, InterpResult, LocalState, LocalValue, MemoryKind, OpTy, Scalar, StackPopCleanup,
8+
self, InterpCx, InterpResult, LocalValue, MemoryKind, OpTy, Scalar, StackPopCleanup,
119
};
1210
use rustc_hir::def::DefKind;
1311
use rustc_hir::HirId;
14-
use rustc_index::bit_set::BitSet;
1512
use rustc_index::vec::IndexVec;
1613
use rustc_middle::mir::visit::Visitor;
17-
use rustc_middle::mir::{
18-
AssertKind, BinOp, Body, Constant, Local, LocalDecl, Location, Operand, Place, Rvalue,
19-
SourceInfo, SourceScope, SourceScopeData, Statement, StatementKind, Terminator, TerminatorKind,
20-
UnOp, RETURN_PLACE,
21-
};
14+
use rustc_middle::mir::*;
2215
use rustc_middle::ty::layout::{LayoutError, LayoutOf, LayoutOfHelpers, TyAndLayout};
2316
use rustc_middle::ty::InternalSubsts;
2417
use rustc_middle::ty::{
@@ -185,17 +178,11 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
185178
let param_env = tcx.param_env_reveal_all_normalized(def_id);
186179

187180
let can_const_prop = CanConstProp::check(tcx, param_env, body);
188-
let mut only_propagate_inside_block_locals = BitSet::new_empty(can_const_prop.len());
189-
for (l, mode) in can_const_prop.iter_enumerated() {
190-
if *mode == ConstPropMode::OnlyInsideOwnBlock {
191-
only_propagate_inside_block_locals.insert(l);
192-
}
193-
}
194181
let mut ecx = InterpCx::new(
195182
tcx,
196183
tcx.def_span(def_id),
197184
param_env,
198-
ConstPropMachine::new(only_propagate_inside_block_locals, can_const_prop),
185+
ConstPropMachine::new(can_const_prop),
199186
);
200187

201188
let ret_layout = ecx
@@ -258,10 +245,8 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
258245
/// Remove `local` from the pool of `Locals`. Allows writing to them,
259246
/// but not reading from them anymore.
260247
fn remove_const(ecx: &mut InterpCx<'mir, 'tcx, ConstPropMachine<'mir, 'tcx>>, local: Local) {
261-
ecx.frame_mut().locals[local] = LocalState {
262-
value: LocalValue::Live(interpret::Operand::Immediate(interpret::Immediate::Uninit)),
263-
layout: Cell::new(None),
264-
};
248+
ecx.frame_mut().locals[local].value =
249+
LocalValue::Live(interpret::Operand::Immediate(interpret::Immediate::Uninit));
265250
}
266251

267252
fn lint_root(&self, source_info: SourceInfo) -> Option<HirId> {
@@ -420,12 +405,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
420405
Some(())
421406
}
422407

423-
fn const_prop(
424-
&mut self,
425-
rvalue: &Rvalue<'tcx>,
426-
source_info: SourceInfo,
427-
place: Place<'tcx>,
428-
) -> Option<()> {
408+
fn check_rvalue(&mut self, rvalue: &Rvalue<'tcx>, source_info: SourceInfo) -> Option<()> {
429409
// Perform any special handling for specific Rvalue types.
430410
// Generally, checks here fall into one of two categories:
431411
// 1. Additional checking to provide useful lints to the user
@@ -501,7 +481,20 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
501481
return None;
502482
}
503483

504-
self.use_ecx(source_info, |this| this.ecx.eval_rvalue_into_place(rvalue, place))
484+
Some(())
485+
}
486+
487+
fn ensure_not_propagated(&mut self, local: Local) {
488+
if cfg!(debug_assertions) {
489+
assert!(
490+
self.get_const(local.into()).is_none()
491+
|| self
492+
.layout_of(self.local_decls[local].ty)
493+
.map_or(true, |layout| layout.is_zst()),
494+
"failed to remove values for `{local:?}`, value={:?}",
495+
self.get_const(local.into()),
496+
)
497+
}
505498
}
506499
}
507500

@@ -522,82 +515,78 @@ impl<'tcx> Visitor<'tcx> for ConstPropagator<'_, 'tcx> {
522515
self.eval_constant(constant, self.source_info.unwrap());
523516
}
524517

518+
fn visit_assign(&mut self, place: &Place<'tcx>, rvalue: &Rvalue<'tcx>, location: Location) {
519+
self.super_assign(place, rvalue, location);
520+
521+
let source_info = self.source_info.unwrap();
522+
let Some(()) = self.check_rvalue(rvalue, source_info) else { return };
523+
524+
match self.ecx.machine.can_const_prop[place.local] {
525+
// Do nothing if the place is indirect.
526+
_ if place.is_indirect() => {}
527+
ConstPropMode::NoPropagation => self.ensure_not_propagated(place.local),
528+
ConstPropMode::OnlyInsideOwnBlock | ConstPropMode::FullConstProp => {
529+
if self
530+
.use_ecx(source_info, |this| this.ecx.eval_rvalue_into_place(rvalue, *place))
531+
.is_none()
532+
{
533+
// Const prop failed, so erase the destination, ensuring that whatever happens
534+
// from here on, does not know about the previous value.
535+
// This is important in case we have
536+
// ```rust
537+
// let mut x = 42;
538+
// x = SOME_MUTABLE_STATIC;
539+
// // x must now be uninit
540+
// ```
541+
// FIXME: we overzealously erase the entire local, because that's easier to
542+
// implement.
543+
trace!(
544+
"propagation into {:?} failed.
545+
Nuking the entire site from orbit, it's the only way to be sure",
546+
place,
547+
);
548+
Self::remove_const(&mut self.ecx, place.local);
549+
}
550+
}
551+
}
552+
}
553+
525554
fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
526555
trace!("visit_statement: {:?}", statement);
527556
let source_info = statement.source_info;
528557
self.source_info = Some(source_info);
529-
if let StatementKind::Assign(box (place, ref rval)) = statement.kind {
530-
let can_const_prop = self.ecx.machine.can_const_prop[place.local];
531-
if let Some(()) = self.const_prop(rval, source_info, place) {
532-
match can_const_prop {
533-
ConstPropMode::OnlyInsideOwnBlock => {
534-
trace!(
535-
"found local restricted to its block. \
536-
Will remove it from const-prop after block is finished. Local: {:?}",
537-
place.local
538-
);
539-
}
540-
ConstPropMode::OnlyPropagateInto | ConstPropMode::NoPropagation => {
541-
trace!("can't propagate into {:?}", place);
542-
if place.local != RETURN_PLACE {
558+
559+
// We want to evaluate operands before any change to the assigned-to value,
560+
// so we recurse first.
561+
self.super_statement(statement, location);
562+
563+
match statement.kind {
564+
StatementKind::SetDiscriminant { ref place, .. } => {
565+
match self.ecx.machine.can_const_prop[place.local] {
566+
// Do nothing if the place is indirect.
567+
_ if place.is_indirect() => {}
568+
ConstPropMode::NoPropagation => self.ensure_not_propagated(place.local),
569+
ConstPropMode::FullConstProp | ConstPropMode::OnlyInsideOwnBlock => {
570+
if self.use_ecx(source_info, |this| this.ecx.statement(statement)).is_some()
571+
{
572+
trace!("propped discriminant into {:?}", place);
573+
} else {
543574
Self::remove_const(&mut self.ecx, place.local);
544575
}
545576
}
546-
ConstPropMode::FullConstProp => {}
547577
}
548-
} else {
549-
// Const prop failed, so erase the destination, ensuring that whatever happens
550-
// from here on, does not know about the previous value.
551-
// This is important in case we have
552-
// ```rust
553-
// let mut x = 42;
554-
// x = SOME_MUTABLE_STATIC;
555-
// // x must now be uninit
556-
// ```
557-
// FIXME: we overzealously erase the entire local, because that's easier to
558-
// implement.
559-
trace!(
560-
"propagation into {:?} failed.
561-
Nuking the entire site from orbit, it's the only way to be sure",
562-
place,
563-
);
564-
Self::remove_const(&mut self.ecx, place.local);
565578
}
566-
} else {
567-
match statement.kind {
568-
StatementKind::SetDiscriminant { ref place, .. } => {
569-
match self.ecx.machine.can_const_prop[place.local] {
570-
ConstPropMode::FullConstProp | ConstPropMode::OnlyInsideOwnBlock => {
571-
if self
572-
.use_ecx(source_info, |this| this.ecx.statement(statement))
573-
.is_some()
574-
{
575-
trace!("propped discriminant into {:?}", place);
576-
} else {
577-
Self::remove_const(&mut self.ecx, place.local);
578-
}
579-
}
580-
ConstPropMode::OnlyPropagateInto | ConstPropMode::NoPropagation => {
581-
Self::remove_const(&mut self.ecx, place.local);
582-
}
583-
}
584-
}
585-
StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => {
586-
let frame = self.ecx.frame_mut();
587-
frame.locals[local].value =
588-
if let StatementKind::StorageLive(_) = statement.kind {
589-
LocalValue::Live(interpret::Operand::Immediate(
590-
interpret::Immediate::Uninit,
591-
))
592-
} else {
593-
LocalValue::Dead
594-
};
595-
}
596-
_ => {}
579+
StatementKind::StorageLive(local) => {
580+
let frame = self.ecx.frame_mut();
581+
frame.locals[local].value =
582+
LocalValue::Live(interpret::Operand::Immediate(interpret::Immediate::Uninit));
583+
}
584+
StatementKind::StorageDead(local) => {
585+
let frame = self.ecx.frame_mut();
586+
frame.locals[local].value = LocalValue::Dead;
597587
}
588+
_ => {}
598589
}
599-
600-
self.super_statement(statement, location);
601590
}
602591

603592
fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
@@ -694,27 +683,25 @@ impl<'tcx> Visitor<'tcx> for ConstPropagator<'_, 'tcx> {
694683
| TerminatorKind::Call { .. }
695684
| TerminatorKind::InlineAsm { .. } => {}
696685
}
686+
}
687+
688+
fn visit_basic_block_data(&mut self, block: BasicBlock, data: &BasicBlockData<'tcx>) {
689+
self.super_basic_block_data(block, data);
697690

698691
// We remove all Locals which are restricted in propagation to their containing blocks and
699692
// which were modified in the current block.
700693
// Take it out of the ecx so we can get a mutable reference to the ecx for `remove_const`.
701-
let mut locals = std::mem::take(&mut self.ecx.machine.written_only_inside_own_block_locals);
702-
for &local in locals.iter() {
703-
Self::remove_const(&mut self.ecx, local);
704-
}
705-
locals.clear();
706-
// Put it back so we reuse the heap of the storage
707-
self.ecx.machine.written_only_inside_own_block_locals = locals;
708-
if cfg!(debug_assertions) {
709-
// Ensure we are correctly erasing locals with the non-debug-assert logic.
710-
for local in self.ecx.machine.only_propagate_inside_block_locals.iter() {
711-
assert!(
712-
self.get_const(local.into()).is_none()
713-
|| self
714-
.layout_of(self.local_decls[local].ty)
715-
.map_or(true, |layout| layout.is_zst())
716-
)
694+
let can_const_prop = std::mem::take(&mut self.ecx.machine.can_const_prop);
695+
for (local, &mode) in can_const_prop.iter_enumerated() {
696+
match mode {
697+
ConstPropMode::FullConstProp => {}
698+
ConstPropMode::NoPropagation => self.ensure_not_propagated(local),
699+
ConstPropMode::OnlyInsideOwnBlock => {
700+
Self::remove_const(&mut self.ecx, local);
701+
self.ensure_not_propagated(local);
702+
}
717703
}
718704
}
705+
self.ecx.machine.can_const_prop = can_const_prop;
719706
}
720707
}

src/tools/clippy/tests/ui/erasing_op.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@ impl core::ops::Mul<i32> for Vec1 {
3131

3232
#[allow(clippy::no_effect)]
3333
#[warn(clippy::erasing_op)]
34-
fn main() {
35-
let x: u8 = 0;
36-
34+
fn test(x: u8) {
3735
x * 0;
3836
0 & x;
3937
0 / x;
4038
0 * Meter; // no error: Output type is different from the non-zero argument
4139
0 * Vec1 { x: 5 };
4240
Vec1 { x: 5 } * 0;
4341
}
42+
43+
fn main() {
44+
test(0)
45+
}

src/tools/clippy/tests/ui/erasing_op.stderr

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,31 @@
11
error: this operation will always return zero. This is likely not the intended outcome
2-
--> $DIR/erasing_op.rs:37:5
2+
--> $DIR/erasing_op.rs:35:5
33
|
44
LL | x * 0;
55
| ^^^^^
66
|
77
= note: `-D clippy::erasing-op` implied by `-D warnings`
88

99
error: this operation will always return zero. This is likely not the intended outcome
10-
--> $DIR/erasing_op.rs:38:5
10+
--> $DIR/erasing_op.rs:36:5
1111
|
1212
LL | 0 & x;
1313
| ^^^^^
1414

1515
error: this operation will always return zero. This is likely not the intended outcome
16-
--> $DIR/erasing_op.rs:39:5
16+
--> $DIR/erasing_op.rs:37:5
1717
|
1818
LL | 0 / x;
1919
| ^^^^^
2020

2121
error: this operation will always return zero. This is likely not the intended outcome
22-
--> $DIR/erasing_op.rs:41:5
22+
--> $DIR/erasing_op.rs:39:5
2323
|
2424
LL | 0 * Vec1 { x: 5 };
2525
| ^^^^^^^^^^^^^^^^^
2626

2727
error: this operation will always return zero. This is likely not the intended outcome
28-
--> $DIR/erasing_op.rs:42:5
28+
--> $DIR/erasing_op.rs:40:5
2929
|
3030
LL | Vec1 { x: 5 } * 0;
3131
| ^^^^^^^^^^^^^^^^^

src/tools/clippy/tests/ui/integer_arithmetic.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#[rustfmt::skip]
55
fn main() {
66
let mut i = 1i32;
7-
let mut var1 = 0i32;
7+
let mut var1 = 13i32;
88
let mut var2 = -1i32;
99
1 + i;
1010
i * 2;

0 commit comments

Comments
 (0)