Skip to content

Commit 52790a9

Browse files
committed
Auto merge of rust-lang#119670 - cjgillot:gvn-arithmetic, r=oli-obk
Fold arithmetic identities in GVN Extracted from rust-lang#111344 This PR implements a few arithmetic folds for unary and binary operations. This should take care of the missed optimizations introduced by rust-lang#116012.
2 parents 25b706c + 0cc2102 commit 52790a9

37 files changed

+2064
-483
lines changed

compiler/rustc_mir_transform/src/gvn.rs

+175-6
Original file line numberDiff line numberDiff line change
@@ -345,11 +345,20 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
345345
Some(self.insert(Value::Constant { value, disambiguator }))
346346
}
347347

348+
fn insert_bool(&mut self, flag: bool) -> VnIndex {
349+
// Booleans are deterministic.
350+
self.insert(Value::Constant { value: Const::from_bool(self.tcx, flag), disambiguator: 0 })
351+
}
352+
348353
fn insert_scalar(&mut self, scalar: Scalar, ty: Ty<'tcx>) -> VnIndex {
349354
self.insert_constant(Const::from_scalar(self.tcx, scalar, ty))
350355
.expect("scalars are deterministic")
351356
}
352357

358+
fn insert_tuple(&mut self, values: Vec<VnIndex>) -> VnIndex {
359+
self.insert(Value::Aggregate(AggregateTy::Tuple, VariantIdx::from_u32(0), values))
360+
}
361+
353362
#[instrument(level = "trace", skip(self), ret)]
354363
fn eval_to_const(&mut self, value: VnIndex) -> Option<OpTy<'tcx>> {
355364
use Value::*;
@@ -767,10 +776,7 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
767776
}
768777

769778
// Operations.
770-
Rvalue::Len(ref mut place) => {
771-
let place = self.simplify_place_value(place, location)?;
772-
Value::Len(place)
773-
}
779+
Rvalue::Len(ref mut place) => return self.simplify_len(place, location),
774780
Rvalue::Cast(kind, ref mut value, to) => {
775781
let from = value.ty(self.local_decls, self.tcx);
776782
let value = self.simplify_operand(value, location)?;
@@ -785,17 +791,36 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
785791
Value::Cast { kind, value, from, to }
786792
}
787793
Rvalue::BinaryOp(op, box (ref mut lhs, ref mut rhs)) => {
794+
let ty = lhs.ty(self.local_decls, self.tcx);
788795
let lhs = self.simplify_operand(lhs, location);
789796
let rhs = self.simplify_operand(rhs, location);
790-
Value::BinaryOp(op, lhs?, rhs?)
797+
// Only short-circuit options after we called `simplify_operand`
798+
// on both operands for side effect.
799+
let lhs = lhs?;
800+
let rhs = rhs?;
801+
if let Some(value) = self.simplify_binary(op, false, ty, lhs, rhs) {
802+
return Some(value);
803+
}
804+
Value::BinaryOp(op, lhs, rhs)
791805
}
792806
Rvalue::CheckedBinaryOp(op, box (ref mut lhs, ref mut rhs)) => {
807+
let ty = lhs.ty(self.local_decls, self.tcx);
793808
let lhs = self.simplify_operand(lhs, location);
794809
let rhs = self.simplify_operand(rhs, location);
795-
Value::CheckedBinaryOp(op, lhs?, rhs?)
810+
// Only short-circuit options after we called `simplify_operand`
811+
// on both operands for side effect.
812+
let lhs = lhs?;
813+
let rhs = rhs?;
814+
if let Some(value) = self.simplify_binary(op, true, ty, lhs, rhs) {
815+
return Some(value);
816+
}
817+
Value::CheckedBinaryOp(op, lhs, rhs)
796818
}
797819
Rvalue::UnaryOp(op, ref mut arg) => {
798820
let arg = self.simplify_operand(arg, location)?;
821+
if let Some(value) = self.simplify_unary(op, arg) {
822+
return Some(value);
823+
}
799824
Value::UnaryOp(op, arg)
800825
}
801826
Rvalue::Discriminant(ref mut place) => {
@@ -894,6 +919,150 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
894919

895920
Some(self.insert(Value::Aggregate(ty, variant_index, fields)))
896921
}
922+
923+
#[instrument(level = "trace", skip(self), ret)]
924+
fn simplify_unary(&mut self, op: UnOp, value: VnIndex) -> Option<VnIndex> {
925+
let value = match (op, self.get(value)) {
926+
(UnOp::Not, Value::UnaryOp(UnOp::Not, inner)) => return Some(*inner),
927+
(UnOp::Neg, Value::UnaryOp(UnOp::Neg, inner)) => return Some(*inner),
928+
(UnOp::Not, Value::BinaryOp(BinOp::Eq, lhs, rhs)) => {
929+
Value::BinaryOp(BinOp::Ne, *lhs, *rhs)
930+
}
931+
(UnOp::Not, Value::BinaryOp(BinOp::Ne, lhs, rhs)) => {
932+
Value::BinaryOp(BinOp::Eq, *lhs, *rhs)
933+
}
934+
_ => return None,
935+
};
936+
937+
Some(self.insert(value))
938+
}
939+
940+
#[instrument(level = "trace", skip(self), ret)]
941+
fn simplify_binary(
942+
&mut self,
943+
op: BinOp,
944+
checked: bool,
945+
lhs_ty: Ty<'tcx>,
946+
lhs: VnIndex,
947+
rhs: VnIndex,
948+
) -> Option<VnIndex> {
949+
// Floats are weird enough that none of the logic below applies.
950+
let reasonable_ty =
951+
lhs_ty.is_integral() || lhs_ty.is_bool() || lhs_ty.is_char() || lhs_ty.is_any_ptr();
952+
if !reasonable_ty {
953+
return None;
954+
}
955+
956+
let layout = self.ecx.layout_of(lhs_ty).ok()?;
957+
958+
let as_bits = |value| {
959+
let constant = self.evaluated[value].as_ref()?;
960+
if layout.abi.is_scalar() {
961+
let scalar = self.ecx.read_scalar(constant).ok()?;
962+
scalar.to_bits(constant.layout.size).ok()
963+
} else {
964+
// `constant` is a wide pointer. Do not evaluate to bits.
965+
None
966+
}
967+
};
968+
969+
// Represent the values as `Left(bits)` or `Right(VnIndex)`.
970+
use Either::{Left, Right};
971+
let a = as_bits(lhs).map_or(Right(lhs), Left);
972+
let b = as_bits(rhs).map_or(Right(rhs), Left);
973+
let result = match (op, a, b) {
974+
// Neutral elements.
975+
(BinOp::Add | BinOp::BitOr | BinOp::BitXor, Left(0), Right(p))
976+
| (
977+
BinOp::Add
978+
| BinOp::BitOr
979+
| BinOp::BitXor
980+
| BinOp::Sub
981+
| BinOp::Offset
982+
| BinOp::Shl
983+
| BinOp::Shr,
984+
Right(p),
985+
Left(0),
986+
)
987+
| (BinOp::Mul, Left(1), Right(p))
988+
| (BinOp::Mul | BinOp::Div, Right(p), Left(1)) => p,
989+
// Attempt to simplify `x & ALL_ONES` to `x`, with `ALL_ONES` depending on type size.
990+
(BinOp::BitAnd, Right(p), Left(ones)) | (BinOp::BitAnd, Left(ones), Right(p))
991+
if ones == layout.size.truncate(u128::MAX)
992+
|| (layout.ty.is_bool() && ones == 1) =>
993+
{
994+
p
995+
}
996+
// Absorbing elements.
997+
(BinOp::Mul | BinOp::BitAnd, _, Left(0))
998+
| (BinOp::Rem, _, Left(1))
999+
| (
1000+
BinOp::Mul | BinOp::Div | BinOp::Rem | BinOp::BitAnd | BinOp::Shl | BinOp::Shr,
1001+
Left(0),
1002+
_,
1003+
) => self.insert_scalar(Scalar::from_uint(0u128, layout.size), lhs_ty),
1004+
// Attempt to simplify `x | ALL_ONES` to `ALL_ONES`.
1005+
(BinOp::BitOr, _, Left(ones)) | (BinOp::BitOr, Left(ones), _)
1006+
if ones == layout.size.truncate(u128::MAX)
1007+
|| (layout.ty.is_bool() && ones == 1) =>
1008+
{
1009+
self.insert_scalar(Scalar::from_uint(ones, layout.size), lhs_ty)
1010+
}
1011+
// Sub/Xor with itself.
1012+
(BinOp::Sub | BinOp::BitXor, a, b) if a == b => {
1013+
self.insert_scalar(Scalar::from_uint(0u128, layout.size), lhs_ty)
1014+
}
1015+
// Comparison:
1016+
// - if both operands can be computed as bits, just compare the bits;
1017+
// - if we proved that both operands have the same value, we can insert true/false;
1018+
// - otherwise, do nothing, as we do not try to prove inequality.
1019+
(BinOp::Eq, Left(a), Left(b)) => self.insert_bool(a == b),
1020+
(BinOp::Eq, a, b) if a == b => self.insert_bool(true),
1021+
(BinOp::Ne, Left(a), Left(b)) => self.insert_bool(a != b),
1022+
(BinOp::Ne, a, b) if a == b => self.insert_bool(false),
1023+
_ => return None,
1024+
};
1025+
1026+
if checked {
1027+
let false_val = self.insert_bool(false);
1028+
Some(self.insert_tuple(vec![result, false_val]))
1029+
} else {
1030+
Some(result)
1031+
}
1032+
}
1033+
1034+
fn simplify_len(&mut self, place: &mut Place<'tcx>, location: Location) -> Option<VnIndex> {
1035+
// Trivial case: we are fetching a statically known length.
1036+
let place_ty = place.ty(self.local_decls, self.tcx).ty;
1037+
if let ty::Array(_, len) = place_ty.kind() {
1038+
return self.insert_constant(Const::from_ty_const(*len, self.tcx));
1039+
}
1040+
1041+
let mut inner = self.simplify_place_value(place, location)?;
1042+
1043+
// The length information is stored in the fat pointer.
1044+
// Reborrowing copies length information from one pointer to the other.
1045+
while let Value::Address { place: borrowed, .. } = self.get(inner)
1046+
&& let [PlaceElem::Deref] = borrowed.projection[..]
1047+
&& let Some(borrowed) = self.locals[borrowed.local]
1048+
{
1049+
inner = borrowed;
1050+
}
1051+
1052+
// We have an unsizing cast, which assigns the length to fat pointer metadata.
1053+
if let Value::Cast { kind, from, to, .. } = self.get(inner)
1054+
&& let CastKind::PointerCoercion(ty::adjustment::PointerCoercion::Unsize) = kind
1055+
&& let Some(from) = from.builtin_deref(true)
1056+
&& let ty::Array(_, len) = from.ty.kind()
1057+
&& let Some(to) = to.builtin_deref(true)
1058+
&& let ty::Slice(..) = to.ty.kind()
1059+
{
1060+
return self.insert_constant(Const::from_ty_const(*len, self.tcx));
1061+
}
1062+
1063+
// Fallback: a symbolic `Len`.
1064+
Some(self.insert(Value::Len(inner)))
1065+
}
8971066
}
8981067

8991068
fn op_to_prop_const<'tcx>(

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

+4-3
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@
1818
_2 = [const 0_u32, const 1_u32, const 2_u32, const 3_u32];
1919
StorageLive(_3);
2020
_3 = const 2_usize;
21-
_4 = Len(_2);
21+
- _4 = Len(_2);
2222
- _5 = Lt(_3, _4);
2323
- assert(move _5, "index out of bounds: the length is {} but the index is {}", move _4, _3) -> [success: bb1, unwind unreachable];
24-
+ _5 = Lt(const 2_usize, _4);
25-
+ assert(move _5, "index out of bounds: the length is {} but the index is {}", move _4, const 2_usize) -> [success: bb1, unwind unreachable];
24+
+ _4 = const 4_usize;
25+
+ _5 = const true;
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];
2627
}
2728

2829
bb1: {

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

+4-3
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@
1818
_2 = [const 0_u32, const 1_u32, const 2_u32, const 3_u32];
1919
StorageLive(_3);
2020
_3 = const 2_usize;
21-
_4 = Len(_2);
21+
- _4 = Len(_2);
2222
- _5 = Lt(_3, _4);
2323
- assert(move _5, "index out of bounds: the length is {} but the index is {}", move _4, _3) -> [success: bb1, unwind continue];
24-
+ _5 = Lt(const 2_usize, _4);
25-
+ assert(move _5, "index out of bounds: the length is {} but the index is {}", move _4, const 2_usize) -> [success: bb1, unwind continue];
24+
+ _4 = const 4_usize;
25+
+ _5 = const true;
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];
2627
}
2728

2829
bb1: {

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

+4-3
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@
1818
_2 = [const 0_u32, const 1_u32, const 2_u32, const 3_u32];
1919
StorageLive(_3);
2020
_3 = const 2_usize;
21-
_4 = Len(_2);
21+
- _4 = Len(_2);
2222
- _5 = Lt(_3, _4);
2323
- assert(move _5, "index out of bounds: the length is {} but the index is {}", move _4, _3) -> [success: bb1, unwind unreachable];
24-
+ _5 = Lt(const 2_usize, _4);
25-
+ assert(move _5, "index out of bounds: the length is {} but the index is {}", move _4, const 2_usize) -> [success: bb1, unwind unreachable];
24+
+ _4 = const 4_usize;
25+
+ _5 = const true;
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];
2627
}
2728

2829
bb1: {

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

+4-3
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@
1818
_2 = [const 0_u32, const 1_u32, const 2_u32, const 3_u32];
1919
StorageLive(_3);
2020
_3 = const 2_usize;
21-
_4 = Len(_2);
21+
- _4 = Len(_2);
2222
- _5 = Lt(_3, _4);
2323
- assert(move _5, "index out of bounds: the length is {} but the index is {}", move _4, _3) -> [success: bb1, unwind continue];
24-
+ _5 = Lt(const 2_usize, _4);
25-
+ assert(move _5, "index out of bounds: the length is {} but the index is {}", move _4, const 2_usize) -> [success: bb1, unwind continue];
24+
+ _4 = const 4_usize;
25+
+ _5 = const true;
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];
2627
}
2728

2829
bb1: {

tests/mir-opt/const_prop/boolean_identities.rs

+3-4
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@ pub fn test(x: bool, y: bool) -> bool {
55
// CHECK-LABEL: fn test(
66
// CHECK: debug a => [[a:_.*]];
77
// CHECK: debug b => [[b:_.*]];
8-
// FIXME(cjgillot) simplify algebraic identity
9-
// CHECK-NOT: [[a]] = const true;
10-
// CHECK-NOT: [[b]] = const false;
11-
// CHECK-NOT: _0 = const false;
8+
// CHECK: [[a]] = const true;
9+
// CHECK: [[b]] = const false;
10+
// CHECK: _0 = const false;
1211
let a = (y | true);
1312
let b = (x & false);
1413
a & b

tests/mir-opt/const_prop/boolean_identities.test.GVN.diff

+7-5
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,23 @@
2424
StorageLive(_4);
2525
_4 = _2;
2626
- _3 = BitOr(move _4, const true);
27-
+ _3 = BitOr(_2, const true);
27+
+ _3 = const true;
2828
StorageDead(_4);
2929
- StorageLive(_5);
3030
+ nop;
3131
StorageLive(_6);
3232
_6 = _1;
3333
- _5 = BitAnd(move _6, const false);
34-
+ _5 = BitAnd(_1, const false);
34+
+ _5 = const false;
3535
StorageDead(_6);
3636
StorageLive(_7);
37-
_7 = _3;
37+
- _7 = _3;
38+
+ _7 = const true;
3839
StorageLive(_8);
39-
_8 = _5;
40+
- _8 = _5;
4041
- _0 = BitAnd(move _7, move _8);
41-
+ _0 = BitAnd(_3, _5);
42+
+ _8 = const false;
43+
+ _0 = const false;
4244
StorageDead(_8);
4345
StorageDead(_7);
4446
- StorageDead(_5);

tests/mir-opt/const_prop/boxes.main.GVN.panic-abort.diff

+6-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020

2121
bb0: {
2222
StorageLive(_1);
23-
StorageLive(_2);
23+
- StorageLive(_2);
24+
+ nop;
2425
StorageLive(_3);
2526
- _4 = SizeOf(i32);
2627
- _5 = AlignOf(i32);
@@ -39,8 +40,10 @@
3940
StorageDead(_7);
4041
_9 = (((_3.0: std::ptr::Unique<i32>).0: std::ptr::NonNull<i32>).0: *const i32);
4142
_2 = (*_9);
42-
_1 = Add(move _2, const 0_i32);
43-
StorageDead(_2);
43+
- _1 = Add(move _2, const 0_i32);
44+
- StorageDead(_2);
45+
+ _1 = _2;
46+
+ nop;
4447
drop(_3) -> [return: bb2, unwind unreachable];
4548
}
4649

tests/mir-opt/const_prop/boxes.main.GVN.panic-unwind.diff

+6-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020

2121
bb0: {
2222
StorageLive(_1);
23-
StorageLive(_2);
23+
- StorageLive(_2);
24+
+ nop;
2425
StorageLive(_3);
2526
- _4 = SizeOf(i32);
2627
- _5 = AlignOf(i32);
@@ -39,8 +40,10 @@
3940
StorageDead(_7);
4041
_9 = (((_3.0: std::ptr::Unique<i32>).0: std::ptr::NonNull<i32>).0: *const i32);
4142
_2 = (*_9);
42-
_1 = Add(move _2, const 0_i32);
43-
StorageDead(_2);
43+
- _1 = Add(move _2, const 0_i32);
44+
- StorageDead(_2);
45+
+ _1 = _2;
46+
+ nop;
4447
drop(_3) -> [return: bb2, unwind: bb3];
4548
}
4649

tests/mir-opt/const_prop/boxes.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ fn main() {
1212
// CHECK: debug x => [[x:_.*]];
1313
// CHECK: (*{{_.*}}) = const 42_i32;
1414
// CHECK: [[tmp:_.*]] = (*{{_.*}});
15-
// CHECK: [[x]] = Add(move [[tmp]], const 0_i32);
15+
// CHECK: [[x]] = [[tmp]];
1616
let x = *(#[rustc_box]
1717
Box::new(42))
1818
+ 0;

0 commit comments

Comments
 (0)