diff --git a/compiler/rustc_mir_build/src/builder/matches/buckets.rs b/compiler/rustc_mir_build/src/builder/matches/buckets.rs index 0d2e9bf87585d..77f2a938f2b56 100644 --- a/compiler/rustc_mir_build/src/builder/matches/buckets.rs +++ b/compiler/rustc_mir_build/src/builder/matches/buckets.rs @@ -323,6 +323,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { value: case_val, kind: PatConstKind::Float | PatConstKind::Other, }, + ) + | ( + TestKind::AggregateEq { value: test_val, .. }, + TestableCase::Constant { value: case_val, kind: PatConstKind::Aggregate }, ) => { if test_val == case_val { fully_matched = true; @@ -353,6 +357,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { | TestKind::Range { .. } | TestKind::StringEq { .. } | TestKind::ScalarEq { .. } + | TestKind::AggregateEq { .. } | TestKind::Deref { .. }, _, ) => { diff --git a/compiler/rustc_mir_build/src/builder/matches/match_pair.rs b/compiler/rustc_mir_build/src/builder/matches/match_pair.rs index e2d00238e2d59..11fe6bdca3cf9 100644 --- a/compiler/rustc_mir_build/src/builder/matches/match_pair.rs +++ b/compiler/rustc_mir_build/src/builder/matches/match_pair.rs @@ -4,7 +4,7 @@ use rustc_abi::FieldIdx; use rustc_middle::mir::*; use rustc_middle::span_bug; use rustc_middle::thir::*; -use rustc_middle::ty::{self, Ty, TypeVisitableExt}; +use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt}; use crate::builder::Builder; use crate::builder::expr::as_place::{PlaceBase, PlaceBuilder}; @@ -12,6 +12,48 @@ use crate::builder::matches::{ FlatPat, MatchPairTree, PatConstKind, PatternExtraData, SliceLenOp, TestableCase, }; +/// Below this length, an array or slice pattern is compared element by element +/// rather than as a single aggregate, since the per-element comparisons are +/// unlikely to be more expensive than a `PartialEq::eq` call. +const AGGREGATE_EQ_MIN_LEN: usize = 4; + +/// Checks whether every pattern in `elements` is a `PatKind::Constant` and, +/// if so, reconstructs a single aggregate `ty::Value` that represents the whole +/// array or slice. Returns `None` when any element is not a constant or the +/// sequence is too short to benefit from an aggregate comparison. +fn try_reconstruct_aggregate_constant<'tcx>( + tcx: TyCtxt<'tcx>, + aggregate_ty: Ty<'tcx>, + elements: &[Pat<'tcx>], +) -> Option> { + // Short arrays are not worth an aggregate comparison. + if elements.len() < AGGREGATE_EQ_MIN_LEN { + return None; + } + let branches = elements + .iter() + .map(|pat| { + if let PatKind::Constant { value } = pat.kind { + Some(ty::Const::new_value(tcx, value.valtree, value.ty)) + } else { + None + } + }) + .collect::>>()?; + let valtree = ty::ValTree::from_branches(tcx, branches); + Some(ty::Value { ty: aggregate_ty, valtree }) +} + +impl<'a, 'tcx> Builder<'a, 'tcx> { + /// Check if we can use aggregate `PartialEq::eq` comparisons for constant array/slice patterns. + /// This is not possible in const contexts, because `PartialEq` is not const-stable yet. + fn can_use_aggregate_eq(&self) -> bool { + let in_const_context = self.tcx.is_const_fn(self.def_id.to_def_id()) + || !self.tcx.hir_body_owner_kind(self.def_id).is_fn_or_closure(); + !in_const_context + } +} + /// For an array or slice pattern's subpatterns (prefix/slice/suffix), returns a list /// of those subpatterns, each paired with a suitably-projected [`PlaceBuilder`]. fn prefix_slice_suffix<'a, 'tcx>( @@ -220,10 +262,36 @@ impl<'tcx> MatchPairTree<'tcx> { _ => None, }; if let Some(array_len) = array_len { - for (subplace, subpat) in - prefix_slice_suffix(&place_builder, Some(array_len), prefix, slice, suffix) + // When all elements are constants and there is no `..` + // subpattern, compare the whole array at once via + // `PartialEq::eq` rather than element by element. + if slice.is_none() + && suffix.is_empty() + && cx.can_use_aggregate_eq() + && let Some(aggregate_value) = + try_reconstruct_aggregate_constant(cx.tcx, pattern.ty, prefix) { - MatchPairTree::for_pattern(subplace, subpat, cx, &mut subpairs, extra_data); + Some(TestableCase::Constant { + value: aggregate_value, + kind: PatConstKind::Aggregate, + }) + } else { + for (subplace, subpat) in prefix_slice_suffix( + &place_builder, + Some(array_len), + prefix, + slice, + suffix, + ) { + MatchPairTree::for_pattern( + subplace, + subpat, + cx, + &mut subpairs, + extra_data, + ); + } + None } } else { // If the array length couldn't be determined, ignore the @@ -235,33 +303,57 @@ impl<'tcx> MatchPairTree<'tcx> { pattern.ty ), ); + None } - - None } PatKind::Slice { ref prefix, ref slice, ref suffix } => { - for (subplace, subpat) in - prefix_slice_suffix(&place_builder, None, prefix, slice, suffix) + // When there is no `..`, all elements are constants, and + // there are at least two of them, collapse the individual + // element subpairs into a single aggregate comparison that + // is performed after the length check. + if slice.is_none() + && suffix.is_empty() + && cx.can_use_aggregate_eq() + && let Some(aggregate_value) = + try_reconstruct_aggregate_constant(cx.tcx, pattern.ty, prefix) { - MatchPairTree::for_pattern(subplace, subpat, cx, &mut subpairs, extra_data); - } - - if prefix.is_empty() && slice.is_some() && suffix.is_empty() { - // This pattern is shaped like `[..]`. It can match a slice - // of any length, so no length test is needed. - None - } else { - // Any other shape of slice pattern requires a length test. - // Slice patterns with a `..` subpattern require a minimum - // length; those without `..` require an exact length. - Some(TestableCase::Slice { - len: u64::try_from(prefix.len() + suffix.len()).unwrap(), - op: if slice.is_some() { - SliceLenOp::GreaterOrEqual - } else { - SliceLenOp::Equal + subpairs.push(MatchPairTree { + place, + testable_case: TestableCase::Constant { + value: aggregate_value, + kind: PatConstKind::Aggregate, }, + subpairs: Vec::new(), + pattern_span: pattern.span, + }); + Some(TestableCase::Slice { + len: u64::try_from(prefix.len()).unwrap(), + op: SliceLenOp::Equal, }) + } else { + for (subplace, subpat) in + prefix_slice_suffix(&place_builder, None, prefix, slice, suffix) + { + MatchPairTree::for_pattern(subplace, subpat, cx, &mut subpairs, extra_data); + } + + if prefix.is_empty() && slice.is_some() && suffix.is_empty() { + // This pattern is shaped like `[..]`. It can match + // a slice of any length, so no length test is needed. + None + } else { + // Any other shape of slice pattern requires a length test. + // Slice patterns with a `..` subpattern require a minimum + // length; those without `..` require an exact length. + Some(TestableCase::Slice { + len: u64::try_from(prefix.len() + suffix.len()).unwrap(), + op: if slice.is_some() { + SliceLenOp::GreaterOrEqual + } else { + SliceLenOp::Equal + }, + }) + } } } diff --git a/compiler/rustc_mir_build/src/builder/matches/mod.rs b/compiler/rustc_mir_build/src/builder/matches/mod.rs index 5604e86e06722..e92f79709396a 100644 --- a/compiler/rustc_mir_build/src/builder/matches/mod.rs +++ b/compiler/rustc_mir_build/src/builder/matches/mod.rs @@ -1266,6 +1266,10 @@ enum PatConstKind { Float, /// Constant string values, tested via string equality. String, + /// Constant array or slice values where every element is a constant. + /// Tested by calling `PartialEq::eq` on the whole aggregate at once, + /// rather than comparing element by element. + Aggregate, /// Any other constant-pattern is usually tested via some kind of equality /// check. Types that might be encountered here include: /// - raw pointers derived from integer values @@ -1351,6 +1355,10 @@ enum TestKind<'tcx> { /// Tests the place against a constant using scalar equality. ScalarEq { value: ty::Value<'tcx> }, + /// Tests the place against a constant array or slice using `PartialEq::eq`, + /// comparing the whole aggregate at once rather than element by element. + AggregateEq { value: ty::Value<'tcx> }, + /// Test whether the value falls within an inclusive or exclusive range. Range(Arc>), diff --git a/compiler/rustc_mir_build/src/builder/matches/test.rs b/compiler/rustc_mir_build/src/builder/matches/test.rs index 9b7b6f574fe3f..9a7d668f74b14 100644 --- a/compiler/rustc_mir_build/src/builder/matches/test.rs +++ b/compiler/rustc_mir_build/src/builder/matches/test.rs @@ -40,6 +40,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { TestableCase::Constant { value, kind: PatConstKind::String } => { TestKind::StringEq { value } } + TestableCase::Constant { value, kind: PatConstKind::Aggregate } => { + TestKind::AggregateEq { value } + } TestableCase::Constant { value, kind: PatConstKind::Float | PatConstKind::Other } => { TestKind::ScalarEq { value } } @@ -137,27 +140,32 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { self.cfg.terminate(block, self.source_info(match_start_span), terminator); } - TestKind::StringEq { value } => { + TestKind::StringEq { value } | TestKind::AggregateEq { value } => { let tcx = self.tcx; let success_block = target_block(TestBranch::Success); let fail_block = target_block(TestBranch::Failure); - let ref_str_ty = Ty::new_imm_ref(tcx, tcx.lifetimes.re_erased, tcx.types.str_); - assert!(ref_str_ty.is_imm_ref_str(), "{ref_str_ty:?}"); - - // The string constant we're testing against has type `str`, but - // calling `::eq` requires `&str` operands. - // - // Because `str` and `&str` have the same valtree representation, - // we can "cast" to the desired type by just replacing the type. - assert!(value.ty.is_str(), "unexpected value type for StringEq test: {value:?}"); - let expected_value = ty::Value { ty: ref_str_ty, valtree: value.valtree }; + let inner_ty = value.ty; + if matches!(test.kind, TestKind::StringEq { .. }) { + assert!( + inner_ty.is_str(), + "unexpected value type for StringEq test: {value:?}" + ); + } + let ref_ty = Ty::new_imm_ref(tcx, tcx.lifetimes.re_erased, inner_ty); + + // The constant we're testing against has type `str`, `[T; N]`, or `[T]`, + // but calling `::eq` requires a reference operand + // (`&str`, `&[T; N]`, or `&[T]`). Valtree representations are the same + // with or without the reference wrapper, so we can "cast" to the + // desired type by just replacing the type. + let expected_value = ty::Value { ty: ref_ty, valtree: value.valtree }; let expected_value_operand = self.literal_operand(test.span, Const::from_ty_value(tcx, expected_value)); - // Similarly, the scrutinized place has type `str`, but we need `&str`. - // Get a reference by doing `let actual_value_ref_place: &str = &place`. - let actual_value_ref_place = self.temp(ref_str_ty, test.span); + // Similarly, the scrutinised place has the inner type, but we need a + // reference. Get one by doing `let actual_value_ref_place = &place`. + let actual_value_ref_place = self.temp(ref_ty, test.span); self.cfg.push_assign( block, self.source_info(test.span), @@ -165,14 +173,15 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { Rvalue::Ref(tcx.lifetimes.re_erased, BorrowKind::Shared, place), ); - // Compare two strings using `::eq`. - // (Interestingly this means that exhaustiveness analysis relies, for soundness, - // on the `PartialEq` impl for `str` to be correct!) - self.string_compare( + // Compare the two values using `::eq`. + // (Interestingly this means that, for `str`, exhaustiveness analysis + // relies for soundness on the `PartialEq` impl for `str` to be correct!) + self.non_scalar_compare( block, success_block, fail_block, source_info, + inner_ty, expected_value_operand, Operand::Copy(actual_value_ref_place), ); @@ -404,19 +413,22 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ); } - /// Compare two values of type `&str` using `::eq`. - fn string_compare( + /// Compare two reference values using `::eq`. + /// + /// `compared_ty` is the *inner* type (e.g. `str`, `[u8; 64]`); + /// `expect` and `val` must already be references to that type. + fn non_scalar_compare( &mut self, block: BasicBlock, success_block: BasicBlock, fail_block: BasicBlock, source_info: SourceInfo, + compared_ty: Ty<'tcx>, expect: Operand<'tcx>, val: Operand<'tcx>, ) { - let str_ty = self.tcx.types.str_; let eq_def_id = self.tcx.require_lang_item(LangItem::PartialEq, source_info.span); - let method = trait_method(self.tcx, eq_def_id, sym::eq, [str_ty, str_ty]); + let method = trait_method(self.tcx, eq_def_id, sym::eq, [compared_ty, compared_ty]); let bool_ty = self.tcx.types.bool; let eq_result = self.temp(bool_ty, source_info.span); diff --git a/tests/mir-opt/building/match/aggregate_array_eq.array_match.built.after.panic-abort.mir b/tests/mir-opt/building/match/aggregate_array_eq.array_match.built.after.panic-abort.mir new file mode 100644 index 0000000000000..85778f3bee8cb --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.array_match.built.after.panic-abort.mir @@ -0,0 +1,51 @@ +// MIR for `array_match` after built + +fn array_match(_1: [u8; 4]) -> bool { + debug x => _1; + let mut _0: bool; + let mut _2: &[u8; 4]; + let mut _3: bool; + scope 1 { + } + + bb0: { + PlaceMention(_1); + _2 = &_1; + _3 = <[u8; 4] as PartialEq>::eq(copy _2, const &*b"\x01\x02\x03\x04") -> [return: bb4, unwind: bb8]; + } + + bb1: { + _0 = const false; + goto -> bb7; + } + + bb2: { + falseEdge -> [real: bb6, imaginary: bb1]; + } + + bb3: { + goto -> bb1; + } + + bb4: { + switchInt(move _3) -> [0: bb1, otherwise: bb2]; + } + + bb5: { + FakeRead(ForMatchedPlace(None), _1); + unreachable; + } + + bb6: { + _0 = const true; + goto -> bb7; + } + + bb7: { + return; + } + + bb8 (cleanup): { + resume; + } +} diff --git a/tests/mir-opt/building/match/aggregate_array_eq.array_match.built.after.panic-unwind.mir b/tests/mir-opt/building/match/aggregate_array_eq.array_match.built.after.panic-unwind.mir new file mode 100644 index 0000000000000..85778f3bee8cb --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.array_match.built.after.panic-unwind.mir @@ -0,0 +1,51 @@ +// MIR for `array_match` after built + +fn array_match(_1: [u8; 4]) -> bool { + debug x => _1; + let mut _0: bool; + let mut _2: &[u8; 4]; + let mut _3: bool; + scope 1 { + } + + bb0: { + PlaceMention(_1); + _2 = &_1; + _3 = <[u8; 4] as PartialEq>::eq(copy _2, const &*b"\x01\x02\x03\x04") -> [return: bb4, unwind: bb8]; + } + + bb1: { + _0 = const false; + goto -> bb7; + } + + bb2: { + falseEdge -> [real: bb6, imaginary: bb1]; + } + + bb3: { + goto -> bb1; + } + + bb4: { + switchInt(move _3) -> [0: bb1, otherwise: bb2]; + } + + bb5: { + FakeRead(ForMatchedPlace(None), _1); + unreachable; + } + + bb6: { + _0 = const true; + goto -> bb7; + } + + bb7: { + return; + } + + bb8 (cleanup): { + resume; + } +} diff --git a/tests/mir-opt/building/match/aggregate_array_eq.const_array_match.built.after.panic-abort.mir b/tests/mir-opt/building/match/aggregate_array_eq.const_array_match.built.after.panic-abort.mir new file mode 100644 index 0000000000000..c785ea537e9c9 --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.const_array_match.built.after.panic-abort.mir @@ -0,0 +1,64 @@ +// MIR for `const_array_match` after built + +fn const_array_match(_1: [u8; 4]) -> bool { + debug x => _1; + let mut _0: bool; + scope 1 { + } + + bb0: { + PlaceMention(_1); + switchInt(copy _1[0 of 4]) -> [1: bb2, otherwise: bb1]; + } + + bb1: { + _0 = const false; + goto -> bb12; + } + + bb2: { + switchInt(copy _1[1 of 4]) -> [2: bb4, otherwise: bb3]; + } + + bb3: { + goto -> bb1; + } + + bb4: { + switchInt(copy _1[2 of 4]) -> [3: bb6, otherwise: bb5]; + } + + bb5: { + goto -> bb3; + } + + bb6: { + switchInt(copy _1[3 of 4]) -> [4: bb8, otherwise: bb7]; + } + + bb7: { + goto -> bb5; + } + + bb8: { + falseEdge -> [real: bb11, imaginary: bb1]; + } + + bb9: { + goto -> bb7; + } + + bb10: { + FakeRead(ForMatchedPlace(None), _1); + unreachable; + } + + bb11: { + _0 = const true; + goto -> bb12; + } + + bb12: { + return; + } +} diff --git a/tests/mir-opt/building/match/aggregate_array_eq.const_array_match.built.after.panic-unwind.mir b/tests/mir-opt/building/match/aggregate_array_eq.const_array_match.built.after.panic-unwind.mir new file mode 100644 index 0000000000000..c785ea537e9c9 --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.const_array_match.built.after.panic-unwind.mir @@ -0,0 +1,64 @@ +// MIR for `const_array_match` after built + +fn const_array_match(_1: [u8; 4]) -> bool { + debug x => _1; + let mut _0: bool; + scope 1 { + } + + bb0: { + PlaceMention(_1); + switchInt(copy _1[0 of 4]) -> [1: bb2, otherwise: bb1]; + } + + bb1: { + _0 = const false; + goto -> bb12; + } + + bb2: { + switchInt(copy _1[1 of 4]) -> [2: bb4, otherwise: bb3]; + } + + bb3: { + goto -> bb1; + } + + bb4: { + switchInt(copy _1[2 of 4]) -> [3: bb6, otherwise: bb5]; + } + + bb5: { + goto -> bb3; + } + + bb6: { + switchInt(copy _1[3 of 4]) -> [4: bb8, otherwise: bb7]; + } + + bb7: { + goto -> bb5; + } + + bb8: { + falseEdge -> [real: bb11, imaginary: bb1]; + } + + bb9: { + goto -> bb7; + } + + bb10: { + FakeRead(ForMatchedPlace(None), _1); + unreachable; + } + + bb11: { + _0 = const true; + goto -> bb12; + } + + bb12: { + return; + } +} diff --git a/tests/mir-opt/building/match/aggregate_array_eq.const_try_from_matched.built.after.panic-abort.mir b/tests/mir-opt/building/match/aggregate_array_eq.const_try_from_matched.built.after.panic-abort.mir new file mode 100644 index 0000000000000..4b105ec6d5d01 --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.const_try_from_matched.built.after.panic-abort.mir @@ -0,0 +1,197 @@ +// MIR for `const_try_from_matched` after built + +fn const_try_from_matched(_1: [u8; 4]) -> Result { + debug value => _1; + let mut _0: std::result::Result; + let mut _2: &[u8; 4]; + let mut _3: MyEnum; + let mut _4: MyEnum; + let mut _5: MyEnum; + let mut _6: MyEnum; + let mut _7: (); + + bb0: { + StorageLive(_2); + _2 = &_1; + PlaceMention(_2); + switchInt(copy (*_2)[0 of 4]) -> [65: bb2, 69: bb10, 73: bb18, 77: bb26, otherwise: bb1]; + } + + bb1: { + StorageLive(_7); + _7 = (); + _0 = Result::::Err(move _7); + StorageDead(_7); + goto -> bb39; + } + + bb2: { + switchInt(copy (*_2)[1 of 4]) -> [66: bb4, otherwise: bb3]; + } + + bb3: { + goto -> bb1; + } + + bb4: { + switchInt(copy (*_2)[2 of 4]) -> [67: bb6, otherwise: bb5]; + } + + bb5: { + goto -> bb3; + } + + bb6: { + switchInt(copy (*_2)[3 of 4]) -> [68: bb8, otherwise: bb7]; + } + + bb7: { + goto -> bb5; + } + + bb8: { + falseEdge -> [real: bb38, imaginary: bb10]; + } + + bb9: { + goto -> bb7; + } + + bb10: { + switchInt(copy (*_2)[1 of 4]) -> [70: bb12, otherwise: bb11]; + } + + bb11: { + goto -> bb1; + } + + bb12: { + switchInt(copy (*_2)[2 of 4]) -> [71: bb14, otherwise: bb13]; + } + + bb13: { + goto -> bb11; + } + + bb14: { + switchInt(copy (*_2)[3 of 4]) -> [72: bb16, otherwise: bb15]; + } + + bb15: { + goto -> bb13; + } + + bb16: { + falseEdge -> [real: bb37, imaginary: bb18]; + } + + bb17: { + goto -> bb15; + } + + bb18: { + switchInt(copy (*_2)[1 of 4]) -> [74: bb20, otherwise: bb19]; + } + + bb19: { + goto -> bb1; + } + + bb20: { + switchInt(copy (*_2)[2 of 4]) -> [75: bb22, otherwise: bb21]; + } + + bb21: { + goto -> bb19; + } + + bb22: { + switchInt(copy (*_2)[3 of 4]) -> [76: bb24, otherwise: bb23]; + } + + bb23: { + goto -> bb21; + } + + bb24: { + falseEdge -> [real: bb36, imaginary: bb26]; + } + + bb25: { + goto -> bb23; + } + + bb26: { + switchInt(copy (*_2)[1 of 4]) -> [78: bb28, otherwise: bb27]; + } + + bb27: { + goto -> bb1; + } + + bb28: { + switchInt(copy (*_2)[2 of 4]) -> [79: bb30, otherwise: bb29]; + } + + bb29: { + goto -> bb27; + } + + bb30: { + switchInt(copy (*_2)[3 of 4]) -> [80: bb32, otherwise: bb31]; + } + + bb31: { + goto -> bb29; + } + + bb32: { + falseEdge -> [real: bb35, imaginary: bb1]; + } + + bb33: { + goto -> bb31; + } + + bb34: { + FakeRead(ForMatchedPlace(None), _2); + unreachable; + } + + bb35: { + StorageLive(_6); + _6 = MyEnum::D; + _0 = Result::::Ok(move _6); + StorageDead(_6); + goto -> bb39; + } + + bb36: { + StorageLive(_5); + _5 = MyEnum::C; + _0 = Result::::Ok(move _5); + StorageDead(_5); + goto -> bb39; + } + + bb37: { + StorageLive(_4); + _4 = MyEnum::B; + _0 = Result::::Ok(move _4); + StorageDead(_4); + goto -> bb39; + } + + bb38: { + StorageLive(_3); + _3 = MyEnum::A; + _0 = Result::::Ok(move _3); + StorageDead(_3); + goto -> bb39; + } + + bb39: { + StorageDead(_2); + return; + } +} diff --git a/tests/mir-opt/building/match/aggregate_array_eq.const_try_from_matched.built.after.panic-unwind.mir b/tests/mir-opt/building/match/aggregate_array_eq.const_try_from_matched.built.after.panic-unwind.mir new file mode 100644 index 0000000000000..4b105ec6d5d01 --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.const_try_from_matched.built.after.panic-unwind.mir @@ -0,0 +1,197 @@ +// MIR for `const_try_from_matched` after built + +fn const_try_from_matched(_1: [u8; 4]) -> Result { + debug value => _1; + let mut _0: std::result::Result; + let mut _2: &[u8; 4]; + let mut _3: MyEnum; + let mut _4: MyEnum; + let mut _5: MyEnum; + let mut _6: MyEnum; + let mut _7: (); + + bb0: { + StorageLive(_2); + _2 = &_1; + PlaceMention(_2); + switchInt(copy (*_2)[0 of 4]) -> [65: bb2, 69: bb10, 73: bb18, 77: bb26, otherwise: bb1]; + } + + bb1: { + StorageLive(_7); + _7 = (); + _0 = Result::::Err(move _7); + StorageDead(_7); + goto -> bb39; + } + + bb2: { + switchInt(copy (*_2)[1 of 4]) -> [66: bb4, otherwise: bb3]; + } + + bb3: { + goto -> bb1; + } + + bb4: { + switchInt(copy (*_2)[2 of 4]) -> [67: bb6, otherwise: bb5]; + } + + bb5: { + goto -> bb3; + } + + bb6: { + switchInt(copy (*_2)[3 of 4]) -> [68: bb8, otherwise: bb7]; + } + + bb7: { + goto -> bb5; + } + + bb8: { + falseEdge -> [real: bb38, imaginary: bb10]; + } + + bb9: { + goto -> bb7; + } + + bb10: { + switchInt(copy (*_2)[1 of 4]) -> [70: bb12, otherwise: bb11]; + } + + bb11: { + goto -> bb1; + } + + bb12: { + switchInt(copy (*_2)[2 of 4]) -> [71: bb14, otherwise: bb13]; + } + + bb13: { + goto -> bb11; + } + + bb14: { + switchInt(copy (*_2)[3 of 4]) -> [72: bb16, otherwise: bb15]; + } + + bb15: { + goto -> bb13; + } + + bb16: { + falseEdge -> [real: bb37, imaginary: bb18]; + } + + bb17: { + goto -> bb15; + } + + bb18: { + switchInt(copy (*_2)[1 of 4]) -> [74: bb20, otherwise: bb19]; + } + + bb19: { + goto -> bb1; + } + + bb20: { + switchInt(copy (*_2)[2 of 4]) -> [75: bb22, otherwise: bb21]; + } + + bb21: { + goto -> bb19; + } + + bb22: { + switchInt(copy (*_2)[3 of 4]) -> [76: bb24, otherwise: bb23]; + } + + bb23: { + goto -> bb21; + } + + bb24: { + falseEdge -> [real: bb36, imaginary: bb26]; + } + + bb25: { + goto -> bb23; + } + + bb26: { + switchInt(copy (*_2)[1 of 4]) -> [78: bb28, otherwise: bb27]; + } + + bb27: { + goto -> bb1; + } + + bb28: { + switchInt(copy (*_2)[2 of 4]) -> [79: bb30, otherwise: bb29]; + } + + bb29: { + goto -> bb27; + } + + bb30: { + switchInt(copy (*_2)[3 of 4]) -> [80: bb32, otherwise: bb31]; + } + + bb31: { + goto -> bb29; + } + + bb32: { + falseEdge -> [real: bb35, imaginary: bb1]; + } + + bb33: { + goto -> bb31; + } + + bb34: { + FakeRead(ForMatchedPlace(None), _2); + unreachable; + } + + bb35: { + StorageLive(_6); + _6 = MyEnum::D; + _0 = Result::::Ok(move _6); + StorageDead(_6); + goto -> bb39; + } + + bb36: { + StorageLive(_5); + _5 = MyEnum::C; + _0 = Result::::Ok(move _5); + StorageDead(_5); + goto -> bb39; + } + + bb37: { + StorageLive(_4); + _4 = MyEnum::B; + _0 = Result::::Ok(move _4); + StorageDead(_4); + goto -> bb39; + } + + bb38: { + StorageLive(_3); + _3 = MyEnum::A; + _0 = Result::::Ok(move _3); + StorageDead(_3); + goto -> bb39; + } + + bb39: { + StorageDead(_2); + return; + } +} diff --git a/tests/mir-opt/building/match/aggregate_array_eq.rs b/tests/mir-opt/building/match/aggregate_array_eq.rs new file mode 100644 index 0000000000000..94c857458f9ee --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.rs @@ -0,0 +1,63 @@ +// EMIT_MIR_FOR_EACH_PANIC_STRATEGY +//@ compile-flags: -Zmir-opt-level=0 + +// Verify that matching against a constant array pattern produces a single +// `PartialEq::eq` call rather than element-by-element comparisons. +// In const contexts, the aggregate comparison must NOT be used because +// `PartialEq` is not const-stable. + +#![crate_type = "lib"] + +// EMIT_MIR aggregate_array_eq.array_match.built.after.mir +pub fn array_match(x: [u8; 4]) -> bool { + // CHECK-LABEL: fn array_match( + // CHECK: <[u8; 4] as PartialEq>::eq + // CHECK-NOT: switchInt(copy _1[ + matches!(x, [1, 2, 3, 4]) +} + +pub enum MyEnum { + A, + B, + C, + D, +} + +// Regression test for https://github.com/rust-lang/rust/issues/103073. +// EMIT_MIR aggregate_array_eq.try_from_matched.built.after.mir +pub fn try_from_matched(value: [u8; 4]) -> Result { + // CHECK-LABEL: fn try_from_matched( + // CHECK: <[u8; 4] as PartialEq>::eq + // CHECK-NOT: switchInt(copy (*_2)[ + match &value { + b"ABCD" => Ok(MyEnum::A), + b"EFGH" => Ok(MyEnum::B), + b"IJKL" => Ok(MyEnum::C), + b"MNOP" => Ok(MyEnum::D), + _ => Err(()), + } +} + +// In a const fn, the aggregate comparison must not be used because +// `PartialEq::eq` cannot be called during const evaluation. +// EMIT_MIR aggregate_array_eq.const_array_match.built.after.mir +pub const fn const_array_match(x: [u8; 4]) -> bool { + // CHECK-LABEL: fn const_array_match( + // CHECK-NOT: PartialEq + // CHECK: switchInt + matches!(x, [1, 2, 3, 4]) +} + +// EMIT_MIR aggregate_array_eq.const_try_from_matched.built.after.mir +pub const fn const_try_from_matched(value: [u8; 4]) -> Result { + // CHECK-LABEL: fn const_try_from_matched( + // CHECK-NOT: PartialEq + // CHECK: switchInt + match &value { + b"ABCD" => Ok(MyEnum::A), + b"EFGH" => Ok(MyEnum::B), + b"IJKL" => Ok(MyEnum::C), + b"MNOP" => Ok(MyEnum::D), + _ => Err(()), + } +} diff --git a/tests/mir-opt/building/match/aggregate_array_eq.try_from_matched.built.after.panic-abort.mir b/tests/mir-opt/building/match/aggregate_array_eq.try_from_matched.built.after.panic-abort.mir new file mode 100644 index 0000000000000..008691c44deef --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.try_from_matched.built.after.panic-abort.mir @@ -0,0 +1,157 @@ +// MIR for `try_from_matched` after built + +fn try_from_matched(_1: [u8; 4]) -> Result { + debug value => _1; + let mut _0: std::result::Result; + let mut _2: &[u8; 4]; + let mut _3: &[u8; 4]; + let mut _4: bool; + let mut _5: &[u8; 4]; + let mut _6: bool; + let mut _7: &[u8; 4]; + let mut _8: bool; + let mut _9: &[u8; 4]; + let mut _10: bool; + let mut _11: MyEnum; + let mut _12: MyEnum; + let mut _13: MyEnum; + let mut _14: MyEnum; + let mut _15: (); + + bb0: { + StorageLive(_2); + _2 = &_1; + PlaceMention(_2); + _9 = &(*_2); + _10 = <[u8; 4] as PartialEq>::eq(copy _9, const &*b"ABCD") -> [return: bb19, unwind: bb26]; + } + + bb1: { + StorageLive(_15); + _15 = (); + _0 = Result::::Err(move _15); + StorageDead(_15); + goto -> bb25; + } + + bb2: { + falseEdge -> [real: bb24, imaginary: bb4]; + } + + bb3: { + goto -> bb1; + } + + bb4: { + _7 = &(*_2); + _8 = <[u8; 4] as PartialEq>::eq(copy _7, const &*b"EFGH") -> [return: bb18, unwind: bb26]; + } + + bb5: { + goto -> bb1; + } + + bb6: { + falseEdge -> [real: bb23, imaginary: bb8]; + } + + bb7: { + goto -> bb5; + } + + bb8: { + _5 = &(*_2); + _6 = <[u8; 4] as PartialEq>::eq(copy _5, const &*b"IJKL") -> [return: bb17, unwind: bb26]; + } + + bb9: { + goto -> bb5; + } + + bb10: { + falseEdge -> [real: bb22, imaginary: bb12]; + } + + bb11: { + goto -> bb9; + } + + bb12: { + _3 = &(*_2); + _4 = <[u8; 4] as PartialEq>::eq(copy _3, const &*b"MNOP") -> [return: bb16, unwind: bb26]; + } + + bb13: { + goto -> bb9; + } + + bb14: { + falseEdge -> [real: bb21, imaginary: bb1]; + } + + bb15: { + goto -> bb13; + } + + bb16: { + switchInt(move _4) -> [0: bb13, otherwise: bb14]; + } + + bb17: { + switchInt(move _6) -> [0: bb12, otherwise: bb10]; + } + + bb18: { + switchInt(move _8) -> [0: bb8, otherwise: bb6]; + } + + bb19: { + switchInt(move _10) -> [0: bb4, otherwise: bb2]; + } + + bb20: { + FakeRead(ForMatchedPlace(None), _2); + unreachable; + } + + bb21: { + StorageLive(_14); + _14 = MyEnum::D; + _0 = Result::::Ok(move _14); + StorageDead(_14); + goto -> bb25; + } + + bb22: { + StorageLive(_13); + _13 = MyEnum::C; + _0 = Result::::Ok(move _13); + StorageDead(_13); + goto -> bb25; + } + + bb23: { + StorageLive(_12); + _12 = MyEnum::B; + _0 = Result::::Ok(move _12); + StorageDead(_12); + goto -> bb25; + } + + bb24: { + StorageLive(_11); + _11 = MyEnum::A; + _0 = Result::::Ok(move _11); + StorageDead(_11); + goto -> bb25; + } + + bb25: { + StorageDead(_2); + return; + } + + bb26 (cleanup): { + resume; + } +} diff --git a/tests/mir-opt/building/match/aggregate_array_eq.try_from_matched.built.after.panic-unwind.mir b/tests/mir-opt/building/match/aggregate_array_eq.try_from_matched.built.after.panic-unwind.mir new file mode 100644 index 0000000000000..008691c44deef --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.try_from_matched.built.after.panic-unwind.mir @@ -0,0 +1,157 @@ +// MIR for `try_from_matched` after built + +fn try_from_matched(_1: [u8; 4]) -> Result { + debug value => _1; + let mut _0: std::result::Result; + let mut _2: &[u8; 4]; + let mut _3: &[u8; 4]; + let mut _4: bool; + let mut _5: &[u8; 4]; + let mut _6: bool; + let mut _7: &[u8; 4]; + let mut _8: bool; + let mut _9: &[u8; 4]; + let mut _10: bool; + let mut _11: MyEnum; + let mut _12: MyEnum; + let mut _13: MyEnum; + let mut _14: MyEnum; + let mut _15: (); + + bb0: { + StorageLive(_2); + _2 = &_1; + PlaceMention(_2); + _9 = &(*_2); + _10 = <[u8; 4] as PartialEq>::eq(copy _9, const &*b"ABCD") -> [return: bb19, unwind: bb26]; + } + + bb1: { + StorageLive(_15); + _15 = (); + _0 = Result::::Err(move _15); + StorageDead(_15); + goto -> bb25; + } + + bb2: { + falseEdge -> [real: bb24, imaginary: bb4]; + } + + bb3: { + goto -> bb1; + } + + bb4: { + _7 = &(*_2); + _8 = <[u8; 4] as PartialEq>::eq(copy _7, const &*b"EFGH") -> [return: bb18, unwind: bb26]; + } + + bb5: { + goto -> bb1; + } + + bb6: { + falseEdge -> [real: bb23, imaginary: bb8]; + } + + bb7: { + goto -> bb5; + } + + bb8: { + _5 = &(*_2); + _6 = <[u8; 4] as PartialEq>::eq(copy _5, const &*b"IJKL") -> [return: bb17, unwind: bb26]; + } + + bb9: { + goto -> bb5; + } + + bb10: { + falseEdge -> [real: bb22, imaginary: bb12]; + } + + bb11: { + goto -> bb9; + } + + bb12: { + _3 = &(*_2); + _4 = <[u8; 4] as PartialEq>::eq(copy _3, const &*b"MNOP") -> [return: bb16, unwind: bb26]; + } + + bb13: { + goto -> bb9; + } + + bb14: { + falseEdge -> [real: bb21, imaginary: bb1]; + } + + bb15: { + goto -> bb13; + } + + bb16: { + switchInt(move _4) -> [0: bb13, otherwise: bb14]; + } + + bb17: { + switchInt(move _6) -> [0: bb12, otherwise: bb10]; + } + + bb18: { + switchInt(move _8) -> [0: bb8, otherwise: bb6]; + } + + bb19: { + switchInt(move _10) -> [0: bb4, otherwise: bb2]; + } + + bb20: { + FakeRead(ForMatchedPlace(None), _2); + unreachable; + } + + bb21: { + StorageLive(_14); + _14 = MyEnum::D; + _0 = Result::::Ok(move _14); + StorageDead(_14); + goto -> bb25; + } + + bb22: { + StorageLive(_13); + _13 = MyEnum::C; + _0 = Result::::Ok(move _13); + StorageDead(_13); + goto -> bb25; + } + + bb23: { + StorageLive(_12); + _12 = MyEnum::B; + _0 = Result::::Ok(move _12); + StorageDead(_12); + goto -> bb25; + } + + bb24: { + StorageLive(_11); + _11 = MyEnum::A; + _0 = Result::::Ok(move _11); + StorageDead(_11); + goto -> bb25; + } + + bb25: { + StorageDead(_2); + return; + } + + bb26 (cleanup): { + resume; + } +} diff --git a/tests/ui/match/aggregate-array-eq.rs b/tests/ui/match/aggregate-array-eq.rs new file mode 100644 index 0000000000000..f5234e08b4f52 --- /dev/null +++ b/tests/ui/match/aggregate-array-eq.rs @@ -0,0 +1,87 @@ +//! Verify that matching against a constant array pattern produces correct +//! results at runtime, complementing the MIR test in +//! `tests/mir-opt/building/match/aggregate_array_eq.rs` which checks that +//! a single aggregate `PartialEq::eq` call is emitted. +//! +//! Also verify that const-context variants (which fall back to +//! element-by-element comparison) produce the same results. +//@ run-pass + +fn array_match(x: [u8; 4]) -> bool { + matches!(x, [1, 2, 3, 4]) +} + +#[derive(Debug, PartialEq)] +enum MyEnum { + A, + B, + C, + D, +} + +// Regression test for https://github.com/rust-lang/rust/issues/103073. +fn try_from_matched(value: [u8; 4]) -> Result { + match &value { + b"ABCD" => Ok(MyEnum::A), + b"EFGH" => Ok(MyEnum::B), + b"IJKL" => Ok(MyEnum::C), + b"MNOP" => Ok(MyEnum::D), + _ => Err(()), + } +} + +// Const fn variants use element-by-element comparison because +// `PartialEq::eq` is not available in const contexts. +const fn const_array_match(x: [u8; 4]) -> bool { + matches!(x, [1, 2, 3, 4]) +} + +const fn const_try_from_matched(value: [u8; 4]) -> Result { + match &value { + b"ABCD" => Ok(MyEnum::A), + b"EFGH" => Ok(MyEnum::B), + b"IJKL" => Ok(MyEnum::C), + b"MNOP" => Ok(MyEnum::D), + _ => Err(()), + } +} + +fn main() { + assert!(array_match([1, 2, 3, 4])); + assert!(!array_match([1, 2, 3, 5])); + assert!(!array_match([0, 0, 0, 0])); + assert!(!array_match([4, 3, 2, 1])); + + assert_eq!(try_from_matched(*b"ABCD"), Ok(MyEnum::A)); + assert_eq!(try_from_matched(*b"EFGH"), Ok(MyEnum::B)); + assert_eq!(try_from_matched(*b"IJKL"), Ok(MyEnum::C)); + assert_eq!(try_from_matched(*b"MNOP"), Ok(MyEnum::D)); + assert_eq!(try_from_matched(*b"ZZZZ"), Err(())); + assert_eq!(try_from_matched(*b"ABCE"), Err(())); + + // Const fn variants called at runtime. + assert!(const_array_match([1, 2, 3, 4])); + assert!(!const_array_match([1, 2, 3, 5])); + assert!(!const_array_match([0, 0, 0, 0])); + assert!(!const_array_match([4, 3, 2, 1])); + + assert_eq!(const_try_from_matched(*b"ABCD"), Ok(MyEnum::A)); + assert_eq!(const_try_from_matched(*b"EFGH"), Ok(MyEnum::B)); + assert_eq!(const_try_from_matched(*b"IJKL"), Ok(MyEnum::C)); + assert_eq!(const_try_from_matched(*b"MNOP"), Ok(MyEnum::D)); + assert_eq!(const_try_from_matched(*b"ZZZZ"), Err(())); + assert_eq!(const_try_from_matched(*b"ABCE"), Err(())); + + // Const fn variants evaluated at compile time. + const MATCH_TRUE: bool = const_array_match([1, 2, 3, 4]); + const MATCH_FALSE: bool = const_array_match([1, 2, 3, 5]); + assert!(MATCH_TRUE); + assert!(!MATCH_FALSE); + + const FROM_ABCD: Result = const_try_from_matched(*b"ABCD"); + const FROM_MNOP: Result = const_try_from_matched(*b"MNOP"); + const FROM_ZZZZ: Result = const_try_from_matched(*b"ZZZZ"); + assert_eq!(FROM_ABCD, Ok(MyEnum::A)); + assert_eq!(FROM_MNOP, Ok(MyEnum::D)); + assert_eq!(FROM_ZZZZ, Err(())); +}