Skip to content

Commit e2a7ba2

Browse files
committed
Auto merge of rust-lang#113858 - cjgillot:const-prop-pairs, r=oli-obk
Always const-prop scalars and scalar pairs This removes some complexity from the pass. The limitation to propagate ScalarPairs only for tuple comes from rust-lang#67015, when ScalarPair constant were modeled using `Rvalue::Aggregate`. Nowadays, we use `ConstValue::ByRef`, which does not care about the underlying type. The justification for not propagating in all cases was perf. This seems not to be a clear cut any more: rust-lang#113858 (comment)
2 parents 399b068 + 4a17740 commit e2a7ba2

File tree

58 files changed

+180
-318
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+180
-318
lines changed

compiler/rustc_mir_transform/src/const_prop.rs

+43-172
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ use rustc_middle::mir::visit::{
1414
};
1515
use rustc_middle::mir::*;
1616
use rustc_middle::ty::layout::{LayoutError, LayoutOf, LayoutOfHelpers, TyAndLayout};
17-
use rustc_middle::ty::GenericArgs;
18-
use rustc_middle::ty::{self, ConstKind, Instance, ParamEnv, Ty, TyCtxt, TypeVisitableExt};
17+
use rustc_middle::ty::{self, GenericArgs, Instance, ParamEnv, Ty, TyCtxt, TypeVisitableExt};
1918
use rustc_span::{def_id::DefId, Span, DUMMY_SP};
2019
use rustc_target::abi::{self, Align, HasDataLayout, Size, TargetDataLayout};
2120
use rustc_target::spec::abi::Abi as CallAbi;
@@ -407,51 +406,9 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
407406
ecx.machine.written_only_inside_own_block_locals.remove(&local);
408407
}
409408

410-
/// Returns the value, if any, of evaluating `c`.
411-
fn eval_constant(&mut self, c: &Constant<'tcx>) -> Option<OpTy<'tcx>> {
412-
// FIXME we need to revisit this for #67176
413-
if c.has_param() {
414-
return None;
415-
}
416-
417-
// No span, we don't want errors to be shown.
418-
self.ecx.eval_mir_constant(&c.literal, None, None).ok()
419-
}
420-
421-
/// Returns the value, if any, of evaluating `place`.
422-
fn eval_place(&mut self, place: Place<'tcx>) -> Option<OpTy<'tcx>> {
423-
trace!("eval_place(place={:?})", place);
424-
self.ecx.eval_place_to_op(place, None).ok()
425-
}
426-
427-
/// Returns the value, if any, of evaluating `op`. Calls upon `eval_constant`
428-
/// or `eval_place`, depending on the variant of `Operand` used.
429-
fn eval_operand(&mut self, op: &Operand<'tcx>) -> Option<OpTy<'tcx>> {
430-
match *op {
431-
Operand::Constant(ref c) => self.eval_constant(c),
432-
Operand::Move(place) | Operand::Copy(place) => self.eval_place(place),
433-
}
434-
}
435-
436409
fn propagate_operand(&mut self, operand: &mut Operand<'tcx>) {
437-
match *operand {
438-
Operand::Copy(l) | Operand::Move(l) => {
439-
if let Some(value) = self.get_const(l) && self.should_const_prop(&value) {
440-
// FIXME(felix91gr): this code only handles `Scalar` cases.
441-
// For now, we're not handling `ScalarPair` cases because
442-
// doing so here would require a lot of code duplication.
443-
// We should hopefully generalize `Operand` handling into a fn,
444-
// and use it to do const-prop here and everywhere else
445-
// where it makes sense.
446-
if let interpret::Operand::Immediate(interpret::Immediate::Scalar(
447-
scalar,
448-
)) = *value
449-
{
450-
*operand = self.operand_from_scalar(scalar, value.layout.ty);
451-
}
452-
}
453-
}
454-
Operand::Constant(_) => (),
410+
if let Some(place) = operand.place() && let Some(op) = self.replace_with_const(place) {
411+
*operand = op;
455412
}
456413
}
457414

@@ -579,93 +536,45 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
579536
}))
580537
}
581538

582-
fn replace_with_const(&mut self, place: Place<'tcx>, rval: &mut Rvalue<'tcx>) {
539+
fn replace_with_const(&mut self, place: Place<'tcx>) -> Option<Operand<'tcx>> {
583540
// This will return None if the above `const_prop` invocation only "wrote" a
584541
// type whose creation requires no write. E.g. a generator whose initial state
585542
// consists solely of uninitialized memory (so it doesn't capture any locals).
586-
let Some(ref value) = self.get_const(place) else { return };
587-
if !self.should_const_prop(value) {
588-
return;
589-
}
590-
trace!("replacing {:?}={:?} with {:?}", place, rval, value);
591-
592-
if let Rvalue::Use(Operand::Constant(c)) = rval {
593-
match c.literal {
594-
ConstantKind::Ty(c) if matches!(c.kind(), ConstKind::Unevaluated(..)) => {}
595-
_ => {
596-
trace!("skipping replace of Rvalue::Use({:?} because it is already a const", c);
597-
return;
598-
}
599-
}
543+
let value = self.get_const(place)?;
544+
if !self.tcx.consider_optimizing(|| format!("ConstantPropagation - {value:?}")) {
545+
return None;
600546
}
547+
trace!("replacing {:?} with {:?}", place, value);
601548

602-
trace!("attempting to replace {:?} with {:?}", rval, value);
603549
// FIXME> figure out what to do when read_immediate_raw fails
604-
let imm = self.ecx.read_immediate_raw(value).ok();
550+
let imm = self.ecx.read_immediate_raw(&value).ok()?;
605551

606-
if let Some(Right(imm)) = imm {
607-
match *imm {
608-
interpret::Immediate::Scalar(scalar) => {
609-
*rval = Rvalue::Use(self.operand_from_scalar(scalar, value.layout.ty));
610-
}
611-
Immediate::ScalarPair(..) => {
612-
// Found a value represented as a pair. For now only do const-prop if the type
613-
// of `rvalue` is also a tuple with two scalars.
614-
// FIXME: enable the general case stated above ^.
615-
let ty = value.layout.ty;
616-
// Only do it for tuples
617-
if let ty::Tuple(types) = ty.kind() {
618-
// Only do it if tuple is also a pair with two scalars
619-
if let [ty1, ty2] = types[..] {
620-
let ty_is_scalar = |ty| {
621-
self.ecx.layout_of(ty).ok().map(|layout| layout.abi.is_scalar())
622-
== Some(true)
623-
};
624-
let alloc = if ty_is_scalar(ty1) && ty_is_scalar(ty2) {
625-
let alloc = self
626-
.ecx
627-
.intern_with_temp_alloc(value.layout, |ecx, dest| {
628-
ecx.write_immediate(*imm, dest)
629-
})
630-
.unwrap();
631-
Some(alloc)
632-
} else {
633-
None
634-
};
635-
636-
if let Some(alloc) = alloc {
637-
// Assign entire constant in a single statement.
638-
// We can't use aggregates, as we run after the aggregate-lowering `MirPhase`.
639-
let const_val = ConstValue::ByRef { alloc, offset: Size::ZERO };
640-
let literal = ConstantKind::Val(const_val, ty);
641-
*rval = Rvalue::Use(Operand::Constant(Box::new(Constant {
642-
span: DUMMY_SP,
643-
user_ty: None,
644-
literal,
645-
})));
646-
}
647-
}
648-
}
649-
}
650-
// Scalars or scalar pairs that contain undef values are assumed to not have
651-
// successfully evaluated and are thus not propagated.
652-
_ => {}
552+
let Right(imm) = imm else { return None };
553+
match *imm {
554+
Immediate::Scalar(scalar) if scalar.try_to_int().is_ok() => {
555+
Some(self.operand_from_scalar(scalar, value.layout.ty))
653556
}
654-
}
655-
}
656-
657-
/// Returns `true` if and only if this `op` should be const-propagated into.
658-
fn should_const_prop(&mut self, op: &OpTy<'tcx>) -> bool {
659-
if !self.tcx.consider_optimizing(|| format!("ConstantPropagation - OpTy: {:?}", op)) {
660-
return false;
661-
}
662-
663-
match **op {
664-
interpret::Operand::Immediate(Immediate::Scalar(s)) => s.try_to_int().is_ok(),
665-
interpret::Operand::Immediate(Immediate::ScalarPair(l, r)) => {
666-
l.try_to_int().is_ok() && r.try_to_int().is_ok()
557+
Immediate::ScalarPair(l, r) if l.try_to_int().is_ok() && r.try_to_int().is_ok() => {
558+
let alloc = self
559+
.ecx
560+
.intern_with_temp_alloc(value.layout, |ecx, dest| {
561+
ecx.write_immediate(*imm, dest)
562+
})
563+
.ok()?;
564+
565+
let literal = ConstantKind::Val(
566+
ConstValue::ByRef { alloc, offset: Size::ZERO },
567+
value.layout.ty,
568+
);
569+
Some(Operand::Constant(Box::new(Constant {
570+
span: DUMMY_SP,
571+
user_ty: None,
572+
literal,
573+
})))
667574
}
668-
_ => false,
575+
// Scalars or scalar pairs that contain undef values are assumed to not have
576+
// successfully evaluated and are thus not propagated.
577+
_ => None,
669578
}
670579
}
671580

@@ -810,12 +719,7 @@ impl<'tcx> MutVisitor<'tcx> for ConstPropagator<'_, 'tcx> {
810719

811720
fn visit_operand(&mut self, operand: &mut Operand<'tcx>, location: Location) {
812721
self.super_operand(operand, location);
813-
814-
// Only const prop copies and moves on `mir_opt_level=3` as doing so
815-
// currently slightly increases compile time in some cases.
816-
if self.tcx.sess.mir_opt_level() >= 3 {
817-
self.propagate_operand(operand)
818-
}
722+
self.propagate_operand(operand)
819723
}
820724

821725
fn process_projection_elem(
@@ -825,8 +729,7 @@ impl<'tcx> MutVisitor<'tcx> for ConstPropagator<'_, 'tcx> {
825729
) -> Option<PlaceElem<'tcx>> {
826730
if let PlaceElem::Index(local) = elem
827731
&& let Some(value) = self.get_const(local.into())
828-
&& self.should_const_prop(&value)
829-
&& let interpret::Operand::Immediate(interpret::Immediate::Scalar(scalar)) = *value
732+
&& let interpret::Operand::Immediate(Immediate::Scalar(scalar)) = *value
830733
&& let Ok(offset) = scalar.to_target_usize(&self.tcx)
831734
&& let Some(min_length) = offset.checked_add(1)
832735
{
@@ -852,7 +755,14 @@ impl<'tcx> MutVisitor<'tcx> for ConstPropagator<'_, 'tcx> {
852755
ConstPropMode::NoPropagation => self.ensure_not_propagated(place.local),
853756
ConstPropMode::OnlyInsideOwnBlock | ConstPropMode::FullConstProp => {
854757
if let Some(()) = self.eval_rvalue_with_identities(rvalue, *place) {
855-
self.replace_with_const(*place, rvalue);
758+
// If this was already an evaluated constant, keep it.
759+
if let Rvalue::Use(Operand::Constant(c)) = rvalue
760+
&& let ConstantKind::Val(..) = c.literal
761+
{
762+
trace!("skipping replace of Rvalue::Use({:?} because it is already a const", c);
763+
} else if let Some(operand) = self.replace_with_const(*place) {
764+
*rvalue = Rvalue::Use(operand);
765+
}
856766
} else {
857767
// Const prop failed, so erase the destination, ensuring that whatever happens
858768
// from here on, does not know about the previous value.
@@ -919,45 +829,6 @@ impl<'tcx> MutVisitor<'tcx> for ConstPropagator<'_, 'tcx> {
919829
}
920830
}
921831

922-
fn visit_terminator(&mut self, terminator: &mut Terminator<'tcx>, location: Location) {
923-
self.super_terminator(terminator, location);
924-
925-
match &mut terminator.kind {
926-
TerminatorKind::Assert { expected, ref mut cond, .. } => {
927-
if let Some(ref value) = self.eval_operand(&cond)
928-
&& let Ok(value_const) = self.ecx.read_scalar(&value)
929-
&& self.should_const_prop(value)
930-
{
931-
trace!("assertion on {:?} should be {:?}", value, expected);
932-
*cond = self.operand_from_scalar(value_const, self.tcx.types.bool);
933-
}
934-
}
935-
TerminatorKind::SwitchInt { ref mut discr, .. } => {
936-
// FIXME: This is currently redundant with `visit_operand`, but sadly
937-
// always visiting operands currently causes a perf regression in LLVM codegen, so
938-
// `visit_operand` currently only runs for propagates places for `mir_opt_level=4`.
939-
self.propagate_operand(discr)
940-
}
941-
// None of these have Operands to const-propagate.
942-
TerminatorKind::Goto { .. }
943-
| TerminatorKind::Resume
944-
| TerminatorKind::Terminate
945-
| TerminatorKind::Return
946-
| TerminatorKind::Unreachable
947-
| TerminatorKind::Drop { .. }
948-
| TerminatorKind::Yield { .. }
949-
| TerminatorKind::GeneratorDrop
950-
| TerminatorKind::FalseEdge { .. }
951-
| TerminatorKind::FalseUnwind { .. }
952-
| TerminatorKind::InlineAsm { .. } => {}
953-
// Every argument in our function calls have already been propagated in `visit_operand`.
954-
//
955-
// NOTE: because LLVM codegen gives slight performance regressions with it, so this is
956-
// gated on `mir_opt_level=3`.
957-
TerminatorKind::Call { .. } => {}
958-
}
959-
}
960-
961832
fn visit_basic_block_data(&mut self, block: BasicBlock, data: &mut BasicBlockData<'tcx>) {
962833
self.super_basic_block_data(block, data);
963834

tests/mir-opt/const_prop/aggregate.main.ConstProp.panic-abort.diff

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@
2626
StorageLive(_4);
2727
StorageLive(_5);
2828
- _5 = _1;
29+
- _4 = foo(move _5) -> [return: bb1, unwind unreachable];
2930
+ _5 = const 1_u8;
30-
_4 = foo(move _5) -> [return: bb1, unwind unreachable];
31+
+ _4 = foo(const 1_u8) -> [return: bb1, unwind unreachable];
3132
}
3233

3334
bb1: {

tests/mir-opt/const_prop/aggregate.main.ConstProp.panic-unwind.diff

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@
2626
StorageLive(_4);
2727
StorageLive(_5);
2828
- _5 = _1;
29+
- _4 = foo(move _5) -> [return: bb1, unwind continue];
2930
+ _5 = const 1_u8;
30-
_4 = foo(move _5) -> [return: bb1, unwind continue];
31+
+ _4 = foo(const 1_u8) -> [return: bb1, unwind continue];
3132
}
3233

3334
bb1: {

tests/mir-opt/const_prop/aggregate.main.PreCodegen.after.panic-abort.mir

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ fn main() -> () {
2323
StorageLive(_4);
2424
StorageLive(_5);
2525
_5 = const 1_u8;
26-
_4 = foo(move _5) -> [return: bb1, unwind unreachable];
26+
_4 = foo(const 1_u8) -> [return: bb1, unwind unreachable];
2727
}
2828

2929
bb1: {

tests/mir-opt/const_prop/aggregate.main.PreCodegen.after.panic-unwind.mir

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ fn main() -> () {
2323
StorageLive(_4);
2424
StorageLive(_5);
2525
_5 = const 1_u8;
26-
_4 = foo(move _5) -> [return: bb1, unwind continue];
26+
_4 = foo(const 1_u8) -> [return: bb1, unwind continue];
2727
}
2828

2929
bb1: {

tests/mir-opt/const_prop/array_index.main.ConstProp.32bit.panic-abort.diff

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
- assert(move _5, "index out of bounds: the length is {} but the index is {}", move _4, _3) -> [success: bb1, unwind unreachable];
2424
+ _4 = const 4_usize;
2525
+ _5 = const true;
26-
+ assert(const true, "index out of bounds: the length is {} but the index is {}", move _4, _3) -> [success: bb1, unwind unreachable];
26+
+ assert(const true, "index out of bounds: the length is {} but the index is {}", const 4_usize, const 2_usize) -> [success: bb1, unwind unreachable];
2727
}
2828

2929
bb1: {

tests/mir-opt/const_prop/array_index.main.ConstProp.32bit.panic-unwind.diff

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
- assert(move _5, "index out of bounds: the length is {} but the index is {}", move _4, _3) -> [success: bb1, unwind continue];
2424
+ _4 = const 4_usize;
2525
+ _5 = const true;
26-
+ assert(const true, "index out of bounds: the length is {} but the index is {}", move _4, _3) -> [success: bb1, unwind continue];
26+
+ assert(const true, "index out of bounds: the length is {} but the index is {}", const 4_usize, const 2_usize) -> [success: bb1, unwind continue];
2727
}
2828

2929
bb1: {

tests/mir-opt/const_prop/array_index.main.ConstProp.64bit.panic-abort.diff

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
- assert(move _5, "index out of bounds: the length is {} but the index is {}", move _4, _3) -> [success: bb1, unwind unreachable];
2424
+ _4 = const 4_usize;
2525
+ _5 = const true;
26-
+ assert(const true, "index out of bounds: the length is {} but the index is {}", move _4, _3) -> [success: bb1, unwind unreachable];
26+
+ assert(const true, "index out of bounds: the length is {} but the index is {}", const 4_usize, const 2_usize) -> [success: bb1, unwind unreachable];
2727
}
2828

2929
bb1: {

tests/mir-opt/const_prop/array_index.main.ConstProp.64bit.panic-unwind.diff

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
- assert(move _5, "index out of bounds: the length is {} but the index is {}", move _4, _3) -> [success: bb1, unwind continue];
2424
+ _4 = const 4_usize;
2525
+ _5 = const true;
26-
+ assert(const true, "index out of bounds: the length is {} but the index is {}", move _4, _3) -> [success: bb1, unwind continue];
26+
+ assert(const true, "index out of bounds: the length is {} but the index is {}", const 4_usize, const 2_usize) -> [success: bb1, unwind continue];
2727
}
2828

2929
bb1: {

tests/mir-opt/const_prop/bad_op_div_by_zero.main.ConstProp.panic-abort.diff

+3-2
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,12 @@
3838
+ _5 = const false;
3939
+ _6 = const false;
4040
+ _7 = const false;
41-
+ assert(!const false, "attempt to compute `{} / {}`, which would overflow", const 1_i32, _3) -> [success: bb2, unwind unreachable];
41+
+ assert(!const false, "attempt to compute `{} / {}`, which would overflow", const 1_i32, const 0_i32) -> [success: bb2, unwind unreachable];
4242
}
4343

4444
bb2: {
45-
_2 = Div(const 1_i32, move _3);
45+
- _2 = Div(const 1_i32, move _3);
46+
+ _2 = Div(const 1_i32, const 0_i32);
4647
StorageDead(_3);
4748
_0 = const ();
4849
StorageDead(_2);

tests/mir-opt/const_prop/bad_op_div_by_zero.main.ConstProp.panic-unwind.diff

+3-2
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,12 @@
3838
+ _5 = const false;
3939
+ _6 = const false;
4040
+ _7 = const false;
41-
+ assert(!const false, "attempt to compute `{} / {}`, which would overflow", const 1_i32, _3) -> [success: bb2, unwind continue];
41+
+ assert(!const false, "attempt to compute `{} / {}`, which would overflow", const 1_i32, const 0_i32) -> [success: bb2, unwind continue];
4242
}
4343

4444
bb2: {
45-
_2 = Div(const 1_i32, move _3);
45+
- _2 = Div(const 1_i32, move _3);
46+
+ _2 = Div(const 1_i32, const 0_i32);
4647
StorageDead(_3);
4748
_0 = const ();
4849
StorageDead(_2);

tests/mir-opt/const_prop/bad_op_mod_by_zero.main.ConstProp.panic-abort.diff

+3-2
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,12 @@
3838
+ _5 = const false;
3939
+ _6 = const false;
4040
+ _7 = const false;
41-
+ assert(!const false, "attempt to compute the remainder of `{} % {}`, which would overflow", const 1_i32, _3) -> [success: bb2, unwind unreachable];
41+
+ assert(!const false, "attempt to compute the remainder of `{} % {}`, which would overflow", const 1_i32, const 0_i32) -> [success: bb2, unwind unreachable];
4242
}
4343

4444
bb2: {
45-
_2 = Rem(const 1_i32, move _3);
45+
- _2 = Rem(const 1_i32, move _3);
46+
+ _2 = Rem(const 1_i32, const 0_i32);
4647
StorageDead(_3);
4748
_0 = const ();
4849
StorageDead(_2);

0 commit comments

Comments
 (0)