Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit ab48366

Browse files
committedApr 13, 2025·
Optimize no-wrap niche discriminant cases
1 parent d956b30 commit ab48366

File tree

6 files changed

+845
-108
lines changed

6 files changed

+845
-108
lines changed
 

Diff for: ‎compiler/rustc_abi/src/lib.rs

+34-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ use std::fmt;
4343
#[cfg(feature = "nightly")]
4444
use std::iter::Step;
4545
use std::num::{NonZeroUsize, ParseIntError};
46-
use std::ops::{Add, AddAssign, Mul, RangeInclusive, Sub};
46+
use std::ops::{Add, AddAssign, Mul, RangeFull, RangeInclusive, Sub};
4747
use std::str::FromStr;
4848

4949
use bitflags::bitflags;
@@ -1162,12 +1162,45 @@ impl WrappingRange {
11621162
}
11631163

11641164
/// Returns `true` if `size` completely fills the range.
1165+
///
1166+
/// Note that this is *not* the same as `self == WrappingRange::full(size)`.
1167+
/// Niche calculations can produce full ranges which are not the canonical one;
1168+
/// for example `Option<NonZero<u16>>` gets `valid_range: (..=0) | (1..)`.
11651169
#[inline]
11661170
fn is_full_for(&self, size: Size) -> bool {
11671171
let max_value = size.unsigned_int_max();
11681172
debug_assert!(self.start <= max_value && self.end <= max_value);
11691173
self.start == (self.end.wrapping_add(1) & max_value)
11701174
}
1175+
1176+
/// Checks whether this range is considered non-wrapping when the values are
1177+
/// interpreted as *unsigned* numbers of width `size`.
1178+
///
1179+
/// Returns `Ok(true)` if there's no wrap-around, `Ok(false)` if there is,
1180+
/// and `Err(..)` if the range is full so it depends how you think about it.
1181+
#[inline]
1182+
pub fn no_unsigned_wraparound(&self, size: Size) -> Result<bool, RangeFull> {
1183+
if self.is_full_for(size) { Err(..) } else { Ok(self.start <= self.end) }
1184+
}
1185+
1186+
/// Checks whether this range is considered non-wrapping when the values are
1187+
/// interpreted as *signed* numbers of width `size`.
1188+
///
1189+
/// This is heavily dependent on the `size`, as `100..=200` does wrap when
1190+
/// interpreted as `i8`, but doesn't when interpreted as `i16`.
1191+
///
1192+
/// Returns `Ok(true)` if there's no wrap-around, `Ok(false)` if there is,
1193+
/// and `Err(..)` if the range is full so it depends how you think about it.
1194+
#[inline]
1195+
pub fn no_signed_wraparound(&self, size: Size) -> Result<bool, RangeFull> {
1196+
if self.is_full_for(size) {
1197+
Err(..)
1198+
} else {
1199+
let start: i128 = size.sign_extend(self.start);
1200+
let end: i128 = size.sign_extend(self.end);
1201+
Ok(start <= end)
1202+
}
1203+
}
11711204
}
11721205

11731206
impl fmt::Debug for WrappingRange {

Diff for: ‎compiler/rustc_codegen_ssa/src/mir/operand.rs

+139-39
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ use std::fmt;
33
use arrayvec::ArrayVec;
44
use either::Either;
55
use rustc_abi as abi;
6-
use rustc_abi::{Align, BackendRepr, FIRST_VARIANT, Primitive, Size, TagEncoding, Variants};
6+
use rustc_abi::{
7+
Align, BackendRepr, FIRST_VARIANT, Primitive, Size, TagEncoding, VariantIdx, Variants,
8+
};
79
use rustc_middle::mir::interpret::{Pointer, Scalar, alloc_range};
810
use rustc_middle::mir::{self, ConstValue};
911
use rustc_middle::ty::Ty;
@@ -510,6 +512,8 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
510512
);
511513

512514
let relative_max = niche_variants.end().as_u32() - niche_variants.start().as_u32();
515+
let tag_range = tag_scalar.valid_range(&dl);
516+
let tag_size = tag_scalar.size(&dl);
513517

514518
// We have a subrange `niche_start..=niche_end` inside `range`.
515519
// If the value of the tag is inside this subrange, it's a
@@ -525,53 +529,149 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
525529
// untagged_variant
526530
// }
527531
// However, we will likely be able to emit simpler code.
528-
let (is_niche, tagged_discr, delta) = if relative_max == 0 {
529-
// Best case scenario: only one tagged variant. This will
530-
// likely become just a comparison and a jump.
531-
// The algorithm is:
532-
// is_niche = tag == niche_start
533-
// discr = if is_niche {
534-
// niche_start
535-
// } else {
536-
// untagged_variant
537-
// }
532+
533+
// First, the incredibly-common case of a two-variant enum (like
534+
// `Option` or `Result`) where we only need one check.
535+
if relative_max == 0 {
538536
let niche_start = bx.cx().const_uint_big(tag_llty, niche_start);
539-
let is_niche = bx.icmp(IntPredicate::IntEQ, tag, niche_start);
540-
let tagged_discr =
541-
bx.cx().const_uint(cast_to, niche_variants.start().as_u32() as u64);
542-
(is_niche, tagged_discr, 0)
543-
} else {
544-
// The special cases don't apply, so we'll have to go with
545-
// the general algorithm.
546-
let relative_discr = bx.sub(tag, bx.cx().const_uint_big(tag_llty, niche_start));
547-
let cast_tag = bx.intcast(relative_discr, cast_to, false);
548-
let is_niche = bx.icmp(
549-
IntPredicate::IntULE,
550-
relative_discr,
551-
bx.cx().const_uint(tag_llty, relative_max as u64),
537+
let is_natural = bx.icmp(IntPredicate::IntNE, tag, niche_start);
538+
return if untagged_variant == VariantIdx::from_u32(1)
539+
&& *niche_variants.start() == VariantIdx::from_u32(0)
540+
{
541+
// The polarity of the comparison above is picked so we can
542+
// just extend for `Option<T>`, which has these variants.
543+
bx.zext(is_natural, cast_to)
544+
} else {
545+
let tagged_discr =
546+
bx.cx().const_uint(cast_to, u64::from(niche_variants.start().as_u32()));
547+
let untagged_discr =
548+
bx.cx().const_uint(cast_to, u64::from(untagged_variant.as_u32()));
549+
bx.select(is_natural, untagged_discr, tagged_discr)
550+
};
551+
}
552+
553+
let niche_end =
554+
tag_size.truncate(u128::from(relative_max).wrapping_add(niche_start));
555+
556+
// Next, the layout algorithm prefers to put the niches at one end,
557+
// so look for cases where we don't need to calculate a relative_tag
558+
// at all and can just look at the original tag value directly.
559+
// This also lets us move any possibly-wrapping addition to the end
560+
// where it's easiest to get rid of in the normal uses: it's easy
561+
// to optimize `COMPLICATED + 7 == 2` to `COMPLICATED == (2 - 7)`.
562+
{
563+
// Work in whichever size is wider, so we don't need to worry
564+
// about wrapping when computing `wide_niche_untagged`.
565+
let (wide_size, wide_ibty) = if cast_to_layout.size > tag_size {
566+
(cast_to_layout.size, cast_to)
567+
} else {
568+
(tag_size, tag_llty)
569+
};
570+
571+
let wide_niche_to_variant =
572+
u128::from(niche_variants.start().as_u32()).wrapping_sub(niche_start);
573+
let wide_niche_untagged = wide_size.truncate(
574+
u128::from(untagged_variant.as_u32()).wrapping_sub(wide_niche_to_variant),
552575
);
553576

554-
// Thanks to parameter attributes and load metadata, LLVM already knows
555-
// the general valid range of the tag. It's possible, though, for there
556-
// to be an impossible value *in the middle*, which those ranges don't
557-
// communicate, so it's worth an `assume` to let the optimizer know.
558-
if niche_variants.contains(&untagged_variant)
559-
&& bx.cx().sess().opts.optimize != OptLevel::No
577+
let wide_tag_and_is_niche_and_needs_assume =
578+
if tag_range.no_unsigned_wraparound(tag_size) == Ok(true) {
579+
let wide_tag = bx.zext(tag, wide_ibty);
580+
Some(if tag_range.start == niche_start {
581+
let end = bx.cx().const_uint_big(tag_llty, niche_end);
582+
(
583+
wide_tag,
584+
bx.icmp(IntPredicate::IntULE, tag, end),
585+
wide_niche_untagged <= niche_end,
586+
)
587+
} else if tag_range.end == niche_end {
588+
let start = bx.cx().const_uint_big(tag_llty, niche_start);
589+
(
590+
wide_tag,
591+
bx.icmp(IntPredicate::IntUGE, tag, start),
592+
wide_niche_untagged >= niche_start,
593+
)
594+
} else {
595+
bug!()
596+
})
597+
} else if tag_range.no_signed_wraparound(tag_size) == Ok(true) {
598+
let wide_tag = bx.sext(tag, wide_ibty);
599+
let wide_niche_untagged = wide_size.sign_extend(wide_niche_untagged);
600+
Some(if tag_range.start == niche_start {
601+
let end = bx.cx().const_uint_big(tag_llty, niche_end);
602+
let niche_end = wide_size.sign_extend(niche_end);
603+
(
604+
wide_tag,
605+
bx.icmp(IntPredicate::IntSLE, tag, end),
606+
wide_niche_untagged <= niche_end,
607+
)
608+
} else if tag_range.end == niche_end {
609+
let start = bx.cx().const_uint_big(tag_llty, niche_start);
610+
let niche_start = wide_size.sign_extend(niche_start);
611+
(
612+
wide_tag,
613+
bx.icmp(IntPredicate::IntSGE, tag, start),
614+
wide_niche_untagged >= niche_start,
615+
)
616+
} else {
617+
bug!()
618+
})
619+
} else {
620+
None
621+
};
622+
if let Some((wide_tag, is_niche, needs_assume)) =
623+
wide_tag_and_is_niche_and_needs_assume
560624
{
561-
let impossible =
562-
u64::from(untagged_variant.as_u32() - niche_variants.start().as_u32());
563-
let impossible = bx.cx().const_uint(tag_llty, impossible);
564-
let ne = bx.icmp(IntPredicate::IntNE, relative_discr, impossible);
565-
bx.assume(ne);
625+
let wide_niche_untagged =
626+
bx.cx().const_uint_big(wide_ibty, wide_niche_untagged);
627+
if needs_assume && bx.cx().sess().opts.optimize != OptLevel::No {
628+
let not_untagged =
629+
bx.icmp(IntPredicate::IntNE, wide_tag, wide_niche_untagged);
630+
bx.assume(not_untagged);
631+
}
632+
633+
let wide_niche = bx.select(is_niche, wide_tag, wide_niche_untagged);
634+
let cast_niche = bx.trunc(wide_niche, cast_to);
635+
let discr = if wide_niche_to_variant == 0 {
636+
cast_niche
637+
} else {
638+
let niche_to_variant =
639+
bx.cx().const_uint_big(cast_to, wide_niche_to_variant);
640+
bx.add(cast_niche, niche_to_variant)
641+
};
642+
return discr;
566643
}
644+
}
567645

568-
(is_niche, cast_tag, niche_variants.start().as_u32() as u128)
569-
};
646+
// Otherwise the special cases don't apply,
647+
// so we'll have to go with the general algorithm.
648+
let relative_tag = bx.sub(tag, bx.cx().const_uint_big(tag_llty, niche_start));
649+
let relative_discr = bx.intcast(relative_tag, cast_to, false);
650+
let is_niche = bx.icmp(
651+
IntPredicate::IntULE,
652+
relative_tag,
653+
bx.cx().const_uint(tag_llty, u64::from(relative_max)),
654+
);
655+
656+
// Thanks to parameter attributes and load metadata, LLVM already knows
657+
// the general valid range of the tag. It's possible, though, for there
658+
// to be an impossible value *in the middle*, which those ranges don't
659+
// communicate, so it's worth an `assume` to let the optimizer know.
660+
if niche_variants.contains(&untagged_variant)
661+
&& bx.cx().sess().opts.optimize != OptLevel::No
662+
{
663+
let impossible =
664+
u64::from(untagged_variant.as_u32() - niche_variants.start().as_u32());
665+
let impossible = bx.cx().const_uint(tag_llty, impossible);
666+
let ne = bx.icmp(IntPredicate::IntNE, relative_tag, impossible);
667+
bx.assume(ne);
668+
}
570669

670+
let delta = niche_variants.start().as_u32();
571671
let tagged_discr = if delta == 0 {
572-
tagged_discr
672+
relative_discr
573673
} else {
574-
bx.add(tagged_discr, bx.cx().const_uint_big(cast_to, delta))
674+
bx.add(relative_discr, bx.cx().const_uint(cast_to, u64::from(delta)))
575675
};
576676

577677
let discr = bx.select(

Diff for: ‎tests/codegen/enum/enum-discriminant-assume.rs

+212
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
//@ compile-flags: -Copt-level=1 -C no-prepopulate-passes
2+
//@ only-64bit (because these discriminants are isize)
3+
4+
#![crate_type = "lib"]
5+
#![feature(core_intrinsics)]
6+
7+
// Depending on the relative ordering of the variants, either
8+
//
9+
// 1. We want to `llvm.assume` to make it clear that the side with the niched
10+
// tags can't actually have a value corresponding to the untagged one, or
11+
//
12+
// 2. The untagged variant would actually be on the side with the values,
13+
// where it's critical that we *don't* assume since that could be one of
14+
// the natural values, and thus we'd introduce UB.
15+
//
16+
// so these tests are particularly about *not* having assumes in the latter case.
17+
18+
// See also `enum-discriminant-eq.rs`, which has payload-in-the-middle tests.
19+
// (That's not actually different in how it's detected during codegen compared
20+
// to the cases here, but it's more relevant to how tests get optimized.)
21+
22+
use std::cmp::Ordering;
23+
use std::intrinsics::discriminant_value;
24+
25+
pub enum PayloadFirst<T> {
26+
Payload(T),
27+
After1,
28+
After2,
29+
}
30+
31+
pub enum PayloadLast<T> {
32+
Before1,
33+
Before2,
34+
Payload(T),
35+
}
36+
37+
// For a bool payload, the niches are 2 and 3.
38+
// - with the payload first, the payload variant equivalent is 1, which is a valid value.
39+
// - with the payload last, the payload variant equivalent is 4, which we assume away.
40+
41+
#[unsafe(no_mangle)]
42+
pub fn payload_first_bool(a: PayloadFirst<bool>) -> isize {
43+
// CHECK-LABEL: @payload_first_bool(
44+
// CHECK-NEXT: start:
45+
// CHECK-NEXT: %[[A_EXT:.+]] = zext i8 %a to i64
46+
// CHECK-NEXT: %[[IS_NICHE:.+]] = icmp uge i8 %a, 2
47+
// CHECK-NEXT: %[[ADJ_DISCR:.+]] = select i1 %[[IS_NICHE]], i64 %[[A_EXT]], i64 1
48+
// CHECK-NEXT: %[[DISCR:.+]] = add i64 %[[ADJ_DISCR]], -1
49+
// CHECK-NEXT: ret i64 %[[DISCR]]
50+
51+
discriminant_value(&a) as _
52+
}
53+
54+
#[unsafe(no_mangle)]
55+
pub fn payload_last_bool(a: PayloadLast<bool>) -> isize {
56+
// CHECK-LABEL: @payload_last_bool(
57+
// CHECK-NEXT: start:
58+
// CHECK-NEXT: %[[A_EXT:.+]] = zext i8 %a to i64
59+
// CHECK-NEXT: %[[IS_NICHE:.+]] = icmp uge i8 %a, 2
60+
// CHECK-NEXT: %[[ASSUME:.+]] = icmp ne i64 %0, 4
61+
// CHECK-NEXT: call void @llvm.assume(i1 %[[ASSUME]])
62+
// CHECK-NEXT: %[[ADJ_DISCR:.+]] = select i1 %[[IS_NICHE]], i64 %[[A_EXT]], i64 4
63+
// CHECK-NEXT: %[[DISCR:.+]] = add i64 %[[ADJ_DISCR]], -2
64+
// CHECK-NEXT: ret i64 %[[DISCR]]
65+
66+
discriminant_value(&a) as _
67+
}
68+
69+
// For a 7/8/9 payload, niches are 5 and 6, *before* the payload values.
70+
// - with the payload first, the payload variant equivalent is 4, which we assume away.
71+
// - with the payload last, the payload variant equivalent is 7, which is a valid value.
72+
73+
pub enum SevenEightNine {
74+
Seven = 7,
75+
Eight = 8,
76+
Nine = 9,
77+
}
78+
79+
#[unsafe(no_mangle)]
80+
pub fn payload_first_789(a: PayloadFirst<SevenEightNine>) -> isize {
81+
// CHECK-LABEL: @payload_first_789(
82+
// CHECK-NEXT: start:
83+
// CHECK-NEXT: %[[A_EXT:.+]] = zext i8 %a to i64
84+
// CHECK-NEXT: %[[IS_NICHE:.+]] = icmp ule i8 %a, 6
85+
// CHECK-NEXT: %[[ASSUME:.+]] = icmp ne i64 %0, 4
86+
// CHECK-NEXT: call void @llvm.assume(i1 %[[ASSUME]])
87+
// CHECK-NEXT: %[[ADJ_DISCR:.+]] = select i1 %[[IS_NICHE]], i64 %[[A_EXT]], i64 4
88+
// CHECK-NEXT: %[[DISCR:.+]] = add i64 %[[ADJ_DISCR]], -4
89+
// CHECK-NEXT: ret i64 %[[DISCR]]
90+
91+
discriminant_value(&a) as _
92+
}
93+
94+
#[unsafe(no_mangle)]
95+
pub fn payload_last_789(a: PayloadLast<SevenEightNine>) -> isize {
96+
// CHECK-LABEL: @payload_last_789(
97+
// CHECK-NEXT: start:
98+
// CHECK-NEXT: %[[A_EXT:.+]] = zext i8 %a to i64
99+
// CHECK-NEXT: %[[IS_NICHE:.+]] = icmp ule i8 %a, 6
100+
// CHECK-NEXT: %[[ADJ_DISCR:.+]] = select i1 %[[IS_NICHE]], i64 %[[A_EXT]], i64 7
101+
// CHECK-NEXT: %[[DISCR:.+]] = add i64 %[[ADJ_DISCR]], -5
102+
// CHECK-NEXT: ret i64 %[[DISCR]]
103+
104+
discriminant_value(&a) as _
105+
}
106+
107+
// For a 2/3/4 payload, the zero niche gets prioritized so the niches are 0 and 1.
108+
// - with the payload first, the payload variant equivalent wraps to isize::MAX,
109+
// which is actually on the value side again, so we don't assume it.
110+
// - with the payload last, the payload variant equivalent is 2, which is a valid value.
111+
// (It also happens to have the tag equal to the discriminant, no adjustment needed.)
112+
113+
pub enum TwoThreeFour {
114+
Two = 2,
115+
Three = 3,
116+
Four = 4,
117+
}
118+
119+
#[unsafe(no_mangle)]
120+
pub fn payload_first_234(a: PayloadFirst<TwoThreeFour>) -> isize {
121+
// CHECK-LABEL: @payload_first_234(
122+
// CHECK-NEXT: start:
123+
// CHECK-NEXT: %[[A_EXT:.+]] = zext i8 %a to i64
124+
// CHECK-NEXT: %[[IS_NICHE:.+]] = icmp ule i8 %a, 1
125+
// CHECK-NEXT: %[[ADJ_DISCR:.+]] = select i1 %[[IS_NICHE]], i64 %[[A_EXT]], i64 -1
126+
// CHECK-NEXT: %[[DISCR:.+]] = add i64 %[[ADJ_DISCR]], 1
127+
// CHECK-NEXT: ret i64 %[[DISCR]]
128+
129+
discriminant_value(&a) as _
130+
}
131+
132+
#[unsafe(no_mangle)]
133+
pub fn payload_last_234(a: PayloadLast<TwoThreeFour>) -> isize {
134+
// CHECK-LABEL: @payload_last_234(
135+
// CHECK-NEXT: start:
136+
// CHECK-NEXT: %[[A_EXT:.+]] = zext i8 %a to i64
137+
// CHECK-NEXT: %[[IS_NICHE:.+]] = icmp ule i8 %a, 1
138+
// CHECK-NEXT: %[[DISCR:.+]] = select i1 %[[IS_NICHE]], i64 %[[A_EXT]], i64 2
139+
// CHECK-NEXT: ret i64 %[[DISCR]]
140+
141+
discriminant_value(&a) as _
142+
}
143+
144+
// For an Ordering payload, the niches are 2 and 3 -- like `bool` but signed.
145+
146+
#[unsafe(no_mangle)]
147+
pub fn payload_first_ordering(a: PayloadFirst<Ordering>) -> isize {
148+
// CHECK-LABEL: @payload_first_ordering(
149+
// CHECK-NEXT: start:
150+
// CHECK-NEXT: %[[A_EXT:.+]] = sext i8 %a to i64
151+
// CHECK-NEXT: %[[IS_NICHE:.+]] = icmp sge i8 %a, 2
152+
// CHECK-NEXT: %[[ADJ_DISCR:.+]] = select i1 %[[IS_NICHE]], i64 %[[A_EXT]], i64 1
153+
// CHECK-NEXT: %[[DISCR:.+]] = add i64 %[[ADJ_DISCR]], -1
154+
// CHECK-NEXT: ret i64 %[[DISCR]]
155+
156+
discriminant_value(&a) as _
157+
}
158+
159+
#[unsafe(no_mangle)]
160+
pub fn payload_last_ordering(a: PayloadLast<Ordering>) -> isize {
161+
// CHECK-LABEL: @payload_last_ordering(
162+
// CHECK-NEXT: start:
163+
// CHECK-NEXT: %[[A_EXT:.+]] = sext i8 %a to i64
164+
// CHECK-NEXT: %[[IS_NICHE:.+]] = icmp sge i8 %a, 2
165+
// CHECK-NEXT: %[[ASSUME:.+]] = icmp ne i64 %0, 4
166+
// CHECK-NEXT: call void @llvm.assume(i1 %[[ASSUME]])
167+
// CHECK-NEXT: %[[ADJ_DISCR:.+]] = select i1 %[[IS_NICHE]], i64 %[[A_EXT]], i64 4
168+
// CHECK-NEXT: %[[DISCR:.+]] = add i64 %[[ADJ_DISCR]], -2
169+
// CHECK-NEXT: ret i64 %[[DISCR]]
170+
171+
discriminant_value(&a) as _
172+
}
173+
174+
// For a 1/2/3 payload, it would be nice if the zero niche were prioritized
175+
// (particularly as that would let this test the 4th case), but layout actually
176+
// puts the niches after the payload here too, so the niches are 4 and 5.
177+
// - with the payload first, the payload variant equivalent is 3, which is a valid value.
178+
// - with the payload last, the payload variant equivalent is 6, which we assume away.
179+
180+
pub enum OneTwoThree {
181+
One = 1,
182+
Two = 2,
183+
Three = 3,
184+
}
185+
186+
#[unsafe(no_mangle)]
187+
pub fn payload_first_123(a: PayloadFirst<OneTwoThree>) -> isize {
188+
// CHECK-LABEL: @payload_first_123(
189+
// CHECK-NEXT: start:
190+
// CHECK-NEXT: %[[A_EXT:.+]] = zext i8 %a to i64
191+
// CHECK-NEXT: %[[IS_NICHE:.+]] = icmp uge i8 %a, 4
192+
// CHECK-NEXT: %[[ADJ_DISCR:.+]] = select i1 %[[IS_NICHE]], i64 %[[A_EXT]], i64 3
193+
// CHECK-NEXT: %[[DISCR:.+]] = add i64 %[[ADJ_DISCR]], -3
194+
// CHECK-NEXT: ret i64 %[[DISCR]]
195+
196+
discriminant_value(&a) as _
197+
}
198+
199+
#[unsafe(no_mangle)]
200+
pub fn payload_last_123(a: PayloadLast<OneTwoThree>) -> isize {
201+
// CHECK-LABEL: @payload_last_123(
202+
// CHECK-NEXT: start:
203+
// CHECK-NEXT: %[[A_EXT:.+]] = zext i8 %a to i64
204+
// CHECK-NEXT: %[[IS_NICHE:.+]] = icmp uge i8 %a, 4
205+
// CHECK-NEXT: %[[ASSUME:.+]] = icmp ne i64 %0, 6
206+
// CHECK-NEXT: call void @llvm.assume(i1 %[[ASSUME]])
207+
// CHECK-NEXT: %[[ADJ_DISCR:.+]] = select i1 %[[IS_NICHE]], i64 %[[A_EXT]], i64 6
208+
// CHECK-NEXT: %[[DISCR:.+]] = add i64 %[[ADJ_DISCR]], -4
209+
// CHECK-NEXT: ret i64 %[[DISCR]]
210+
211+
discriminant_value(&a) as _
212+
}

Diff for: ‎tests/codegen/enum/enum-discriminant-eq.rs

+142-38
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,26 @@
44
// so make sure we emit that in some reasonable way.
55

66
#![crate_type = "lib"]
7-
#![feature(core_intrinsics)]
87
#![feature(ascii_char)]
8+
#![feature(core_intrinsics)]
9+
#![feature(repr128)]
910

1011
use std::ascii::Char as AC;
1112
use std::cmp::Ordering;
1213
use std::intrinsics::discriminant_value;
1314
use std::num::NonZero;
1415

16+
// A type that's bigger than `isize`, unlike the usual cases that have small tags.
17+
#[repr(u128)]
18+
pub enum Giant {
19+
Two = 2,
20+
Three = 3,
21+
Four = 4,
22+
}
23+
1524
#[unsafe(no_mangle)]
16-
pub fn opt_bool_eq(a: Option<bool>, b: Option<bool>) -> bool {
17-
// CHECK-LABEL: @opt_bool_eq(
25+
pub fn opt_bool_eq_discr(a: Option<bool>, b: Option<bool>) -> bool {
26+
// CHECK-LABEL: @opt_bool_eq_discr(
1827
// CHECK: %[[A:.+]] = icmp ne i8 %a, 2
1928
// CHECK: %[[B:.+]] = icmp eq i8 %b, 2
2029
// CHECK: %[[R:.+]] = xor i1 %[[A]], %[[B]]
@@ -24,8 +33,8 @@ pub fn opt_bool_eq(a: Option<bool>, b: Option<bool>) -> bool {
2433
}
2534

2635
#[unsafe(no_mangle)]
27-
pub fn opt_ord_eq(a: Option<Ordering>, b: Option<Ordering>) -> bool {
28-
// CHECK-LABEL: @opt_ord_eq(
36+
pub fn opt_ord_eq_discr(a: Option<Ordering>, b: Option<Ordering>) -> bool {
37+
// CHECK-LABEL: @opt_ord_eq_discr(
2938
// CHECK: %[[A:.+]] = icmp ne i8 %a, 2
3039
// CHECK: %[[B:.+]] = icmp eq i8 %b, 2
3140
// CHECK: %[[R:.+]] = xor i1 %[[A]], %[[B]]
@@ -35,8 +44,8 @@ pub fn opt_ord_eq(a: Option<Ordering>, b: Option<Ordering>) -> bool {
3544
}
3645

3746
#[unsafe(no_mangle)]
38-
pub fn opt_nz32_eq(a: Option<NonZero<u32>>, b: Option<NonZero<u32>>) -> bool {
39-
// CHECK-LABEL: @opt_nz32_eq(
47+
pub fn opt_nz32_eq_discr(a: Option<NonZero<u32>>, b: Option<NonZero<u32>>) -> bool {
48+
// CHECK-LABEL: @opt_nz32_eq_discr(
4049
// CHECK: %[[A:.+]] = icmp ne i32 %a, 0
4150
// CHECK: %[[B:.+]] = icmp eq i32 %b, 0
4251
// CHECK: %[[R:.+]] = xor i1 %[[A]], %[[B]]
@@ -46,8 +55,8 @@ pub fn opt_nz32_eq(a: Option<NonZero<u32>>, b: Option<NonZero<u32>>) -> bool {
4655
}
4756

4857
#[unsafe(no_mangle)]
49-
pub fn opt_ac_eq(a: Option<AC>, b: Option<AC>) -> bool {
50-
// CHECK-LABEL: @opt_ac_eq(
58+
pub fn opt_ac_eq_discr(a: Option<AC>, b: Option<AC>) -> bool {
59+
// CHECK-LABEL: @opt_ac_eq_discr(
5160
// CHECK: %[[A:.+]] = icmp ne i8 %a, -128
5261
// CHECK: %[[B:.+]] = icmp eq i8 %b, -128
5362
// CHECK: %[[R:.+]] = xor i1 %[[A]], %[[B]]
@@ -56,58 +65,153 @@ pub fn opt_ac_eq(a: Option<AC>, b: Option<AC>) -> bool {
5665
discriminant_value(&a) == discriminant_value(&b)
5766
}
5867

68+
#[unsafe(no_mangle)]
69+
pub fn opt_giant_eq_discr(a: Option<Giant>, b: Option<Giant>) -> bool {
70+
// CHECK-LABEL: @opt_giant_eq_discr(
71+
// CHECK: %[[A:.+]] = icmp ne i128 %a, 1
72+
// CHECK: %[[B:.+]] = icmp eq i128 %b, 1
73+
// CHECK: %[[R:.+]] = xor i1 %[[A]], %[[B]]
74+
// CHECK: ret i1 %[[R]]
75+
76+
discriminant_value(&a) == discriminant_value(&b)
77+
}
78+
5979
pub enum Mid<T> {
6080
Before,
6181
Thing(T),
6282
After,
6383
}
6484

6585
#[unsafe(no_mangle)]
66-
pub fn mid_bool_eq(a: Mid<bool>, b: Mid<bool>) -> bool {
67-
// CHECK-LABEL: @mid_bool_eq(
68-
// CHECK: %[[AS:.+]] = add nsw i8 %a, -2
69-
// CHECK: %[[AT:.+]] = icmp ult i8 %[[AS]], 3
70-
// CHECK: %[[AD:.+]] = select i1 %[[AT]], i8 %[[AS]], i8 1
71-
// CHECK: %[[BS:.+]] = add nsw i8 %b, -2
72-
// CHECK: %[[BT:.+]] = icmp ult i8 %[[BS]], 3
73-
// CHECK: %[[BD:.+]] = select i1 %[[BT]], i8 %[[BS]], i8 1
74-
// CHECK: %[[R:.+]] = icmp eq i8 %[[AD]], %[[BD]]
86+
pub fn mid_bool_eq_discr(a: Mid<bool>, b: Mid<bool>) -> bool {
87+
// CHECK-LABEL: @mid_bool_eq_discr(
88+
89+
// CHECK: %[[A_IS_NICHE:.+]] = icmp samesign ugt i8 %a, 1
90+
// CHECK: %[[A_NOT_HOLE:.+]] = icmp ne i8 %a, 3
91+
// CHECK: tail call void @llvm.assume(i1 %[[A_NOT_HOLE]])
92+
// CHECK: %[[A_ADJ_DISCR:.+]] = select i1 %[[A_IS_NICHE]], i8 %a, i8 3
93+
94+
// CHECK: %[[B_IS_NICHE:.+]] = icmp samesign ugt i8 %b, 1
95+
// CHECK: %[[B_NOT_HOLE:.+]] = icmp ne i8 %b, 3
96+
// CHECK: tail call void @llvm.assume(i1 %[[B_NOT_HOLE]])
97+
// CHECK: %[[B_ADJ_DISCR:.+]] = select i1 %[[B_IS_NICHE]], i8 %b, i8 3
98+
99+
// CHECK: %[[R:.+]] = icmp eq i8 %[[A_ADJ_DISCR]], %[[B_ADJ_DISCR]]
75100
// CHECK: ret i1 %[[R]]
76101
discriminant_value(&a) == discriminant_value(&b)
77102
}
78103

79104
#[unsafe(no_mangle)]
80-
pub fn mid_ord_eq(a: Mid<Ordering>, b: Mid<Ordering>) -> bool {
81-
// CHECK-LABEL: @mid_ord_eq(
82-
// CHECK: %[[AS:.+]] = add nsw i8 %a, -2
83-
// CHECK: %[[AT:.+]] = icmp ult i8 %[[AS]], 3
84-
// CHECK: %[[AD:.+]] = select i1 %[[AT]], i8 %[[AS]], i8 1
85-
// CHECK: %[[BS:.+]] = add nsw i8 %b, -2
86-
// CHECK: %[[BT:.+]] = icmp ult i8 %[[BS]], 3
87-
// CHECK: %[[BD:.+]] = select i1 %[[BT]], i8 %[[BS]], i8 1
88-
// CHECK: %[[R:.+]] = icmp eq i8 %[[AD]], %[[BD]]
105+
pub fn mid_ord_eq_discr(a: Mid<Ordering>, b: Mid<Ordering>) -> bool {
106+
// CHECK-LABEL: @mid_ord_eq_discr(
107+
108+
// CHECK: %[[A_IS_NICHE:.+]] = icmp sgt i8 %a, 1
109+
// CHECK: %[[A_NOT_HOLE:.+]] = icmp ne i8 %a, 3
110+
// CHECK: tail call void @llvm.assume(i1 %[[A_NOT_HOLE]])
111+
// CHECK: %[[A_ADJ_DISCR:.+]] = select i1 %[[A_IS_NICHE]], i8 %a, i8 3
112+
113+
// CHECK: %[[B_IS_NICHE:.+]] = icmp sgt i8 %b, 1
114+
// CHECK: %[[B_NOT_HOLE:.+]] = icmp ne i8 %b, 3
115+
// CHECK: tail call void @llvm.assume(i1 %[[B_NOT_HOLE]])
116+
// CHECK: %[[B_ADJ_DISCR:.+]] = select i1 %[[B_IS_NICHE]], i8 %b, i8 3
117+
118+
// CHECK: %[[R:.+]] = icmp eq i8 %[[A_ADJ_DISCR]], %[[B_ADJ_DISCR]]
89119
// CHECK: ret i1 %[[R]]
90120
discriminant_value(&a) == discriminant_value(&b)
91121
}
92122

93123
#[unsafe(no_mangle)]
94-
pub fn mid_nz32_eq(a: Mid<NonZero<u32>>, b: Mid<NonZero<u32>>) -> bool {
95-
// CHECK-LABEL: @mid_nz32_eq(
124+
pub fn mid_nz32_eq_discr(a: Mid<NonZero<u32>>, b: Mid<NonZero<u32>>) -> bool {
125+
// CHECK-LABEL: @mid_nz32_eq_discr(
96126
// CHECK: %[[R:.+]] = icmp eq i32 %a.0, %b.0
97127
// CHECK: ret i1 %[[R]]
98128
discriminant_value(&a) == discriminant_value(&b)
99129
}
100130

101131
#[unsafe(no_mangle)]
102-
pub fn mid_ac_eq(a: Mid<AC>, b: Mid<AC>) -> bool {
103-
// CHECK-LABEL: @mid_ac_eq(
104-
// CHECK: %[[AS:.+]] = xor i8 %a, -128
105-
// CHECK: %[[AT:.+]] = icmp ult i8 %[[AS]], 3
106-
// CHECK: %[[AD:.+]] = select i1 %[[AT]], i8 %[[AS]], i8 1
107-
// CHECK: %[[BS:.+]] = xor i8 %b, -128
108-
// CHECK: %[[BT:.+]] = icmp ult i8 %[[BS]], 3
109-
// CHECK: %[[BD:.+]] = select i1 %[[BT]], i8 %[[BS]], i8 1
110-
// CHECK: %[[R:.+]] = icmp eq i8 %[[AD]], %[[BD]]
132+
pub fn mid_ac_eq_discr(a: Mid<AC>, b: Mid<AC>) -> bool {
133+
// CHECK-LABEL: @mid_ac_eq_discr(
134+
135+
// CHECK: %[[A_IS_NICHE:.+]] = icmp slt i8 %a, 0
136+
// CHECK: %[[A_NOT_HOLE:.+]] = icmp ne i8 %a, -127
137+
// CHECK: tail call void @llvm.assume(i1 %[[A_NOT_HOLE]])
138+
// CHECK: %[[A_ADJ_DISCR:.+]] = select i1 %[[A_IS_NICHE]], i8 %a, i8 -127
139+
140+
// CHECK: %[[B_IS_NICHE:.+]] = icmp slt i8 %b, 0
141+
// CHECK: %[[B_NOT_HOLE:.+]] = icmp ne i8 %b, -127
142+
// CHECK: tail call void @llvm.assume(i1 %[[B_NOT_HOLE]])
143+
// CHECK: %[[B_ADJ_DISCR:.+]] = select i1 %[[B_IS_NICHE]], i8 %b, i8 -127
144+
145+
// CHECK: %[[R:.+]] = icmp eq i8 %[[A_ADJ_DISCR]], %[[B_ADJ_DISCR]]
146+
// CHECK: ret i1 %[[R]]
147+
discriminant_value(&a) == discriminant_value(&b)
148+
}
149+
150+
// Note that we emit the add after the select, where LLVM really ought to optimize
151+
// it out because it's on both sides of the `icmp eq`, but InstCombine sinks it
152+
// inside the `select`. See <https://github.com/llvm/llvm-project/issues/134024>
153+
#[unsafe(no_mangle)]
154+
pub fn mid_giant_eq_discr(a: Mid<Giant>, b: Mid<Giant>) -> bool {
155+
// CHECK-LABEL: @mid_giant_eq_discr(
156+
157+
// CHECK: %[[A_IS_NICHE:.+]] = icmp samesign ugt i128 %a, 4
158+
// CHECK: %[[A_NOT_HOLE:.+]] = icmp ne i128 %a, 6
159+
// CHECK: tail call void @llvm.assume(i1 %[[A_NOT_HOLE]])
160+
// CHECK: %[[A_TRUNC:.+]] = trunc nuw nsw i128 %a to [[ISIZE:i32|i64]]
161+
// CHECK: %[[A_NICHE_DISCR:.+]] = add nsw [[ISIZE]] %[[A_TRUNC]], -5
162+
// CHECK: %[[A_DISCR:.+]] = select i1 %[[A_IS_NICHE]], [[ISIZE]] %[[A_NICHE_DISCR]], [[ISIZE]] 1
163+
164+
// CHECK: %[[B_IS_NICHE:.+]] = icmp samesign ugt i128 %b, 4
165+
// CHECK: %[[B_NOT_HOLE:.+]] = icmp ne i128 %b, 6
166+
// CHECK: tail call void @llvm.assume(i1 %[[B_NOT_HOLE]])
167+
// CHECK: %[[B_TRUNC:.+]] = trunc nuw nsw i128 %b to [[ISIZE]]
168+
// CHECK: %[[B_NICHE_DISCR:.+]] = add nsw [[ISIZE]] %[[B_TRUNC]], -5
169+
// CHECK: %[[B_DISCR:.+]] = select i1 %[[B_IS_NICHE]], [[ISIZE]] %[[B_NICHE_DISCR]], [[ISIZE]] 1
170+
171+
// CHECK: %[[R:.+]] = icmp eq [[ISIZE]] %[[A_DISCR]], %[[B_DISCR]]
111172
// CHECK: ret i1 %[[R]]
112173
discriminant_value(&a) == discriminant_value(&b)
113174
}
175+
176+
// In niche-encoded enums, testing for the untagged variant should optimize to a
177+
// straight-forward comparison looking for the natural range of the payload value.
178+
179+
#[unsafe(no_mangle)]
180+
pub fn mid_bool_is_thing(a: Mid<bool>) -> bool {
181+
// CHECK-LABEL: @mid_bool_is_thing(
182+
// CHECK: %[[R:.+]] = icmp samesign ult i8 %a, 2
183+
// CHECK: ret i1 %[[R]]
184+
discriminant_value(&a) == 1
185+
}
186+
187+
#[unsafe(no_mangle)]
188+
pub fn mid_ord_is_thing(a: Mid<Ordering>) -> bool {
189+
// CHECK-LABEL: @mid_ord_is_thing(
190+
// CHECK: %[[R:.+]] = icmp slt i8 %a, 2
191+
// CHECK: ret i1 %[[R]]
192+
discriminant_value(&a) == 1
193+
}
194+
195+
#[unsafe(no_mangle)]
196+
pub fn mid_nz32_is_thing(a: Mid<NonZero<u32>>) -> bool {
197+
// CHECK-LABEL: @mid_nz32_is_thing(
198+
// CHECK: %[[R:.+]] = icmp eq i32 %a.0, 1
199+
// CHECK: ret i1 %[[R]]
200+
discriminant_value(&a) == 1
201+
}
202+
203+
#[unsafe(no_mangle)]
204+
pub fn mid_ac_is_thing(a: Mid<AC>) -> bool {
205+
// CHECK-LABEL: @mid_ac_is_thing(
206+
// CHECK: %[[R:.+]] = icmp sgt i8 %a, -1
207+
// CHECK: ret i1 %[[R]]
208+
discriminant_value(&a) == 1
209+
}
210+
211+
#[unsafe(no_mangle)]
212+
pub fn mid_giant_is_thing(a: Mid<Giant>) -> bool {
213+
// CHECK-LABEL: @mid_giant_is_thing(
214+
// CHECK: %[[R:.+]] = icmp samesign ult i128 %a, 5
215+
// CHECK: ret i1 %[[R]]
216+
discriminant_value(&a) == 1
217+
}

Diff for: ‎tests/codegen/enum/enum-match.rs

+312-24
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,14 @@ pub enum Enum1 {
3939

4040
// CHECK-LABEL: define noundef{{( range\(i8 [0-9]+, [0-9]+\))?}} i8 @match1(i8{{.+}}%0)
4141
// CHECK-NEXT: start:
42-
// CHECK-NEXT: %[[REL_VAR:.+]] = add{{( nsw)?}} i8 %0, -2
43-
// CHECK-NEXT: %[[REL_VAR_WIDE:.+]] = zext i8 %[[REL_VAR]] to i64
44-
// CHECK-NEXT: %[[IS_NICHE:.+]] = icmp ult i8 %[[REL_VAR]], 2
45-
// CHECK-NEXT: %[[NICHE_DISCR:.+]] = add nuw nsw i64 %[[REL_VAR_WIDE]], 1
46-
// CHECK-NEXT: %[[DISCR:.+]] = select i1 %[[IS_NICHE]], i64 %[[NICHE_DISCR]], i64 0
47-
// CHECK-NEXT: switch i64 %[[DISCR]]
42+
// CHECK-NEXT: %[[ADJ_DISCR:.+]] = tail call i8 @llvm.umax.i8(i8 %0, i8 1)
43+
// CHECK-NEXT: switch i8 %[[ADJ_DISCR]], label %[[UNREACHABLE:.+]] [
44+
// CHECK-NEXT: i8 1,
45+
// CHECK-NEXT: i8 2,
46+
// CHECK-NEXT: i8 3,
47+
// CHECK-NEXT: ]
48+
// CHECK: [[UNREACHABLE]]:
49+
// CHECK-NEXT: unreachable
4850
#[no_mangle]
4951
pub fn match1(e: Enum1) -> u8 {
5052
use Enum1::*;
@@ -147,12 +149,19 @@ pub enum MiddleNiche {
147149

148150
// CHECK-LABEL: define noundef{{( range\(i8 -?[0-9]+, -?[0-9]+\))?}} i8 @match4(i8{{.+}}%0)
149151
// CHECK-NEXT: start:
150-
// CHECK-NEXT: %[[REL_VAR:.+]] = add{{( nsw)?}} i8 %0, -2
151-
// CHECK-NEXT: %[[IS_NICHE:.+]] = icmp ult i8 %[[REL_VAR]], 5
152-
// CHECK-NEXT: %[[NOT_IMPOSSIBLE:.+]] = icmp ne i8 %[[REL_VAR]], 2
152+
// CHECK-NEXT: %[[IS_NICHE:.+]] = icmp samesign ugt i8 %0, 1
153+
// CHECK-NEXT: %[[NOT_IMPOSSIBLE:.+]] = icmp ne i8 %0, 4
153154
// CHECK-NEXT: call void @llvm.assume(i1 %[[NOT_IMPOSSIBLE]])
154-
// CHECK-NEXT: %[[DISCR:.+]] = select i1 %[[IS_NICHE]], i8 %[[REL_VAR]], i8 2
155-
// CHECK-NEXT: switch i8 %[[DISCR]]
155+
// CHECK-NEXT: %[[ADJ_DISCR:.+]] = select i1 %[[IS_NICHE]], i8 %0, i8 4
156+
// CHECK-NEXT: switch i8 %[[ADJ_DISCR]], label %[[UNREACHABLE:.+]] [
157+
// CHECK-NEXT: i8 2,
158+
// CHECK-NEXT: i8 3,
159+
// CHECK-NEXT: i8 4,
160+
// CHECK-NEXT: i8 5,
161+
// CHECK-NEXT: i8 6,
162+
// CHECK-NEXT: ]
163+
// CHECK: [[UNREACHABLE]]:
164+
// CHECK-NEXT: unreachable
156165
#[no_mangle]
157166
pub fn match4(e: MiddleNiche) -> u8 {
158167
use MiddleNiche::*;
@@ -167,9 +176,8 @@ pub fn match4(e: MiddleNiche) -> u8 {
167176

168177
// CHECK-LABEL: define{{.+}}i1 @match4_is_c(i8{{.+}}%e)
169178
// CHECK-NEXT: start
170-
// CHECK-NEXT: %[[REL_VAR:.+]] = add{{( nsw)?}} i8 %e, -2
171-
// CHECK-NEXT: %[[NOT_NICHE:.+]] = icmp ugt i8 %[[REL_VAR]], 4
172-
// CHECK-NEXT: %[[NOT_IMPOSSIBLE:.+]] = icmp ne i8 %[[REL_VAR]], 2
179+
// CHECK-NEXT: %[[NOT_NICHE:.+]] = icmp samesign ult i8 %e, 2
180+
// CHECK-NEXT: %[[NOT_IMPOSSIBLE:.+]] = icmp ne i8 %e, 4
173181
// CHECK-NEXT: call void @llvm.assume(i1 %[[NOT_IMPOSSIBLE]])
174182
// CHECK-NEXT: ret i1 %[[NOT_NICHE]]
175183
#[no_mangle]
@@ -451,17 +459,17 @@ pub enum HugeVariantIndex {
451459

452460
// CHECK-LABEL: define noundef{{( range\(i8 [0-9]+, [0-9]+\))?}} i8 @match5(i8{{.+}}%0)
453461
// CHECK-NEXT: start:
454-
// CHECK-NEXT: %[[REL_VAR:.+]] = add{{( nsw)?}} i8 %0, -2
455-
// CHECK-NEXT: %[[REL_VAR_WIDE:.+]] = zext i8 %[[REL_VAR]] to i64
456-
// CHECK-NEXT: %[[IS_NICHE:.+]] = icmp ult i8 %[[REL_VAR]], 3
457-
// CHECK-NEXT: %[[NOT_IMPOSSIBLE:.+]] = icmp ne i8 %[[REL_VAR]], 1
462+
// CHECK-NEXT: %[[IS_NICHE:.+]] = icmp samesign ugt i8 %0, 1
463+
// CHECK-NEXT: %[[NOT_IMPOSSIBLE:.+]] = icmp ne i8 %0, 3
458464
// CHECK-NEXT: call void @llvm.assume(i1 %[[NOT_IMPOSSIBLE]])
459-
// CHECK-NEXT: %[[NICHE_DISCR:.+]] = add nuw nsw i64 %[[REL_VAR_WIDE]], 257
460-
// CHECK-NEXT: %[[DISCR:.+]] = select i1 %[[IS_NICHE]], i64 %[[NICHE_DISCR]], i64 258
461-
// CHECK-NEXT: switch i64 %[[DISCR]],
462-
// CHECK-NEXT: i64 257,
463-
// CHECK-NEXT: i64 258,
464-
// CHECK-NEXT: i64 259,
465+
// CHECK-NEXT: %[[ADJ_DISCR:.+]] = select i1 %[[IS_NICHE]], i8 %0, i8 3
466+
// CHECK-NEXT: switch i8 %[[ADJ_DISCR]], label %[[UNREACHABLE:.+]] [
467+
// CHECK-NEXT: i8 2,
468+
// CHECK-NEXT: i8 3,
469+
// CHECK-NEXT: i8 4,
470+
// CHECK-NEXT: ]
471+
// CHECK: [[UNREACHABLE]]:
472+
// CHECK-NEXT: unreachable
465473
#[no_mangle]
466474
pub fn match5(e: HugeVariantIndex) -> u8 {
467475
use HugeVariantIndex::*;
@@ -471,3 +479,283 @@ pub fn match5(e: HugeVariantIndex) -> u8 {
471479
Possible259 => 100,
472480
}
473481
}
482+
483+
// Make an enum where the niche tags wrap both as signed and as unsigned, to hit
484+
// the most-fallback case where there's just nothing smart to do.
485+
486+
pub enum E10Through65 {
487+
D10 = 10,
488+
D11 = 11,
489+
D12 = 12,
490+
D13 = 13,
491+
D14 = 14,
492+
D15 = 15,
493+
D16 = 16,
494+
D17 = 17,
495+
D18 = 18,
496+
D19 = 19,
497+
D20 = 20,
498+
D21 = 21,
499+
D22 = 22,
500+
D23 = 23,
501+
D24 = 24,
502+
D25 = 25,
503+
D26 = 26,
504+
D27 = 27,
505+
D28 = 28,
506+
D29 = 29,
507+
D30 = 30,
508+
D31 = 31,
509+
D32 = 32,
510+
D33 = 33,
511+
D34 = 34,
512+
D35 = 35,
513+
D36 = 36,
514+
D37 = 37,
515+
D38 = 38,
516+
D39 = 39,
517+
D40 = 40,
518+
D41 = 41,
519+
D42 = 42,
520+
D43 = 43,
521+
D44 = 44,
522+
D45 = 45,
523+
D46 = 46,
524+
D47 = 47,
525+
D48 = 48,
526+
D49 = 49,
527+
D50 = 50,
528+
D51 = 51,
529+
D52 = 52,
530+
D53 = 53,
531+
D54 = 54,
532+
D55 = 55,
533+
D56 = 56,
534+
D57 = 57,
535+
D58 = 58,
536+
D59 = 59,
537+
D60 = 60,
538+
D61 = 61,
539+
D62 = 62,
540+
D63 = 63,
541+
D64 = 64,
542+
D65 = 65,
543+
}
544+
545+
pub enum Tricky {
546+
Untagged(E10Through65),
547+
V001,
548+
V002,
549+
V003,
550+
V004,
551+
V005,
552+
V006,
553+
V007,
554+
V008,
555+
V009,
556+
V010,
557+
V011,
558+
V012,
559+
V013,
560+
V014,
561+
V015,
562+
V016,
563+
V017,
564+
V018,
565+
V019,
566+
V020,
567+
V021,
568+
V022,
569+
V023,
570+
V024,
571+
V025,
572+
V026,
573+
V027,
574+
V028,
575+
V029,
576+
V030,
577+
V031,
578+
V032,
579+
V033,
580+
V034,
581+
V035,
582+
V036,
583+
V037,
584+
V038,
585+
V039,
586+
V040,
587+
V041,
588+
V042,
589+
V043,
590+
V044,
591+
V045,
592+
V046,
593+
V047,
594+
V048,
595+
V049,
596+
V050,
597+
V051,
598+
V052,
599+
V053,
600+
V054,
601+
V055,
602+
V056,
603+
V057,
604+
V058,
605+
V059,
606+
V060,
607+
V061,
608+
V062,
609+
V063,
610+
V064,
611+
V065,
612+
V066,
613+
V067,
614+
V068,
615+
V069,
616+
V070,
617+
V071,
618+
V072,
619+
V073,
620+
V074,
621+
V075,
622+
V076,
623+
V077,
624+
V078,
625+
V079,
626+
V080,
627+
V081,
628+
V082,
629+
V083,
630+
V084,
631+
V085,
632+
V086,
633+
V087,
634+
V088,
635+
V089,
636+
V090,
637+
V091,
638+
V092,
639+
V093,
640+
V094,
641+
V095,
642+
V096,
643+
V097,
644+
V098,
645+
V099,
646+
V100,
647+
V101,
648+
V102,
649+
V103,
650+
V104,
651+
V105,
652+
V106,
653+
V107,
654+
V108,
655+
V109,
656+
V110,
657+
V111,
658+
V112,
659+
V113,
660+
V114,
661+
V115,
662+
V116,
663+
V117,
664+
V118,
665+
V119,
666+
V120,
667+
V121,
668+
V122,
669+
V123,
670+
V124,
671+
V125,
672+
V126,
673+
V127,
674+
V128,
675+
V129,
676+
V130,
677+
V131,
678+
V132,
679+
V133,
680+
V134,
681+
V135,
682+
V136,
683+
V137,
684+
V138,
685+
V139,
686+
V140,
687+
V141,
688+
V142,
689+
V143,
690+
V144,
691+
V145,
692+
V146,
693+
V147,
694+
V148,
695+
V149,
696+
V150,
697+
V151,
698+
V152,
699+
V153,
700+
V154,
701+
V155,
702+
V156,
703+
V157,
704+
V158,
705+
V159,
706+
V160,
707+
V161,
708+
V162,
709+
V163,
710+
V164,
711+
V165,
712+
V166,
713+
V167,
714+
V168,
715+
V169,
716+
V170,
717+
V171,
718+
V172,
719+
V173,
720+
V174,
721+
V175,
722+
V176,
723+
V177,
724+
V178,
725+
V179,
726+
V180,
727+
V181,
728+
V182,
729+
V183,
730+
V184,
731+
V185,
732+
V186,
733+
V187,
734+
V188,
735+
V189,
736+
V190,
737+
V191,
738+
V192,
739+
V193,
740+
V194,
741+
V195,
742+
V196,
743+
V197,
744+
V198,
745+
V199,
746+
V200,
747+
}
748+
749+
const _: () = assert!(std::intrinsics::discriminant_value(&Tricky::V100) == 100);
750+
751+
// CHECK-LABEL: define noundef{{( range\(i8 [0-9]+, [0-9]+\))?}} i8 @discriminant6(i8 noundef %e)
752+
// CHECK-NEXT: start:
753+
// CHECK-NEXT: %[[REL_VAR:.+]] = add i8 %e, -66
754+
// CHECK-NEXT: %[[IS_NICHE:.+]] = icmp ult i8 %0, -56
755+
// CHECK-NEXT: %[[TAGGED_DISCR:.+]] = add i8 %e, -65
756+
// CHECK-NEXT: %[[DISCR:.+]] = select i1 %1, i8 %2, i8 0
757+
// CHECK-NEXT: ret i8 %[[DISCR]]
758+
#[no_mangle]
759+
pub fn discriminant6(e: Tricky) -> u8 {
760+
std::intrinsics::discriminant_value(&e) as _
761+
}

Diff for: ‎tests/codegen/enum/enum-two-variants-match.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ pub fn result_match(x: Result<u64, i64>) -> u16 {
6060
#[no_mangle]
6161
pub fn option_bool_match(x: Option<bool>) -> char {
6262
// CHECK: %[[RAW:.+]] = load i8, ptr %x
63-
// CHECK: %[[IS_NONE:.+]] = icmp eq i8 %[[RAW]], 2
64-
// CHECK: %[[OPT_DISCR:.+]] = select i1 %[[IS_NONE]], i64 0, i64 1
63+
// CHECK: %[[IS_SOME:.+]] = icmp ne i8 %[[RAW]], 2
64+
// CHECK: %[[OPT_DISCR:.+]] = zext i1 %[[IS_SOME]] to i64
6565
// CHECK: %[[OPT_DISCR_T:.+]] = trunc nuw i64 %[[OPT_DISCR]] to i1
6666
// CHECK: br i1 %[[OPT_DISCR_T]], label %[[BB_SOME:.+]], label %[[BB_NONE:.+]]
6767

@@ -81,8 +81,8 @@ use std::cmp::Ordering::{self, *};
8181
#[no_mangle]
8282
pub fn option_ordering_match(x: Option<Ordering>) -> char {
8383
// CHECK: %[[RAW:.+]] = load i8, ptr %x
84-
// CHECK: %[[IS_NONE:.+]] = icmp eq i8 %[[RAW]], 2
85-
// CHECK: %[[OPT_DISCR:.+]] = select i1 %[[IS_NONE]], i64 0, i64 1
84+
// CHECK: %[[IS_SOME:.+]] = icmp ne i8 %[[RAW]], 2
85+
// CHECK: %[[OPT_DISCR:.+]] = zext i1 %[[IS_SOME]] to i64
8686
// CHECK: %[[OPT_DISCR_T:.+]] = trunc nuw i64 %[[OPT_DISCR]] to i1
8787
// CHECK: br i1 %[[OPT_DISCR_T]], label %[[BB_SOME:.+]], label %[[BB_NONE:.+]]
8888

@@ -109,8 +109,8 @@ pub fn option_ordering_match(x: Option<Ordering>) -> char {
109109
pub fn option_nonzero_match(x: Option<std::num::NonZero<u16>>) -> u16 {
110110
// CHECK: %[[OUT:.+]] = alloca [2 x i8]
111111

112-
// CHECK: %[[IS_NONE:.+]] = icmp eq i16 %x, 0
113-
// CHECK: %[[OPT_DISCR:.+]] = select i1 %[[IS_NONE]], i64 0, i64 1
112+
// CHECK: %[[IS_SOME:.+]] = icmp ne i16 %x, 0
113+
// CHECK: %[[OPT_DISCR:.+]] = zext i1 %[[IS_SOME]] to i64
114114
// CHECK: %[[OPT_DISCR_T:.+]] = trunc nuw i64 %[[OPT_DISCR]] to i1
115115
// CHECK: br i1 %[[OPT_DISCR_T]], label %[[BB_SOME:.+]], label %[[BB_NONE:.+]]
116116

0 commit comments

Comments
 (0)
Please sign in to comment.