Skip to content

Commit 158c309

Browse files
committed
Auto merge of #110197 - cjgillot:codegen-discr, r=pnkfelix
Do not attempt to commute comparison and cast to codegen discriminants The general algorithm to compute a discriminant is: ``` relative_tag = tag - niche_start is_niche = relative_tag <= (ule) relative_max discr = if is_niche { cast(relative_tag) + niche_variants.start() } else { untagged_variant } ``` We have an optimization branch which attempts to merge the addition and the subtraction by commuting them with the cast. We currently get this optimization wrong. This PR takes the easiest and safest way: remove the optimization, and let LLVM handle it. (Perf may not agree with that course of action 😅) There may be a less invasive solution, but I don't have the necessary knowledge of LLVM semantics to find it. Cranelift has the same optimization, which should be handled similarly. cc `@nikic` and `@bjorn3` if you have a better solution. Fixes #110128
2 parents 84dd17b + a8857d2 commit 158c309

File tree

6 files changed

+22
-199
lines changed

6 files changed

+22
-199
lines changed

compiler/rustc_codegen_cranelift/src/discriminant.rs

-89
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@ pub(crate) fn codegen_get_discriminant<'tcx>(
103103
}
104104
};
105105

106-
let cast_to_size = dest_layout.layout.size();
107106
let cast_to = fx.clif_type(dest_layout.ty).unwrap();
108107

109108
// Read the tag/niche-encoded discriminant from memory.
@@ -122,21 +121,7 @@ pub(crate) fn codegen_get_discriminant<'tcx>(
122121
dest.write_cvalue(fx, res);
123122
}
124123
TagEncoding::Niche { untagged_variant, ref niche_variants, niche_start } => {
125-
let tag_size = tag_scalar.size(fx);
126-
let max_unsigned = tag_size.unsigned_int_max();
127-
let max_signed = tag_size.signed_int_max() as u128;
128-
let min_signed = max_signed + 1;
129124
let relative_max = niche_variants.end().as_u32() - niche_variants.start().as_u32();
130-
let niche_end = niche_start.wrapping_add(relative_max as u128) & max_unsigned;
131-
let range = tag_scalar.valid_range(fx);
132-
133-
let sle = |lhs: u128, rhs: u128| -> bool {
134-
// Signed and unsigned comparisons give the same results,
135-
// except that in signed comparisons an integer with the
136-
// sign bit set is less than one with the sign bit clear.
137-
// Toggle the sign bit to do a signed comparison.
138-
(lhs ^ min_signed) <= (rhs ^ min_signed)
139-
};
140125

141126
// We have a subrange `niche_start..=niche_end` inside `range`.
142127
// If the value of the tag is inside this subrange, it's a
@@ -153,45 +138,6 @@ pub(crate) fn codegen_get_discriminant<'tcx>(
153138
// }
154139
// However, we will likely be able to emit simpler code.
155140

156-
// Find the least and greatest values in `range`, considered
157-
// both as signed and unsigned.
158-
let (low_unsigned, high_unsigned) =
159-
if range.start <= range.end { (range.start, range.end) } else { (0, max_unsigned) };
160-
let (low_signed, high_signed) = if sle(range.start, range.end) {
161-
(range.start, range.end)
162-
} else {
163-
(min_signed, max_signed)
164-
};
165-
166-
let niches_ule = niche_start <= niche_end;
167-
let niches_sle = sle(niche_start, niche_end);
168-
let cast_smaller = cast_to_size <= tag_size;
169-
170-
// In the algorithm above, we can change
171-
// cast(relative_tag) + niche_variants.start()
172-
// into
173-
// cast(tag + (niche_variants.start() - niche_start))
174-
// if either the casted type is no larger than the original
175-
// type, or if the niche values are contiguous (in either the
176-
// signed or unsigned sense).
177-
let can_incr = cast_smaller || niches_ule || niches_sle;
178-
179-
let data_for_boundary_niche = || -> Option<(IntCC, u128)> {
180-
if !can_incr {
181-
None
182-
} else if niche_start == low_unsigned {
183-
Some((IntCC::UnsignedLessThanOrEqual, niche_end))
184-
} else if niche_end == high_unsigned {
185-
Some((IntCC::UnsignedGreaterThanOrEqual, niche_start))
186-
} else if niche_start == low_signed {
187-
Some((IntCC::SignedLessThanOrEqual, niche_end))
188-
} else if niche_end == high_signed {
189-
Some((IntCC::SignedGreaterThanOrEqual, niche_start))
190-
} else {
191-
None
192-
}
193-
};
194-
195141
let (is_niche, tagged_discr, delta) = if relative_max == 0 {
196142
// Best case scenario: only one tagged variant. This will
197143
// likely become just a comparison and a jump.
@@ -206,41 +152,6 @@ pub(crate) fn codegen_get_discriminant<'tcx>(
206152
let tagged_discr =
207153
fx.bcx.ins().iconst(cast_to, niche_variants.start().as_u32() as i64);
208154
(is_niche, tagged_discr, 0)
209-
} else if let Some((predicate, constant)) = data_for_boundary_niche() {
210-
// The niche values are either the lowest or the highest in
211-
// `range`. We can avoid the first subtraction in the
212-
// algorithm.
213-
// The algorithm is now this:
214-
// is_niche = tag <= niche_end
215-
// discr = if is_niche {
216-
// cast(tag + (niche_variants.start() - niche_start))
217-
// } else {
218-
// untagged_variant
219-
// }
220-
// (the first line may instead be tag >= niche_start,
221-
// and may be a signed or unsigned comparison)
222-
// The arithmetic must be done before the cast, so we can
223-
// have the correct wrapping behavior. See issue #104519 for
224-
// the consequences of getting this wrong.
225-
let is_niche = codegen_icmp_imm(fx, predicate, tag, constant as i128);
226-
let delta = (niche_variants.start().as_u32() as u128).wrapping_sub(niche_start);
227-
let incr_tag = if delta == 0 {
228-
tag
229-
} else {
230-
let delta = match fx.bcx.func.dfg.value_type(tag) {
231-
types::I128 => {
232-
let lsb = fx.bcx.ins().iconst(types::I64, delta as u64 as i64);
233-
let msb = fx.bcx.ins().iconst(types::I64, (delta >> 64) as u64 as i64);
234-
fx.bcx.ins().iconcat(lsb, msb)
235-
}
236-
ty => fx.bcx.ins().iconst(ty, delta as i64),
237-
};
238-
fx.bcx.ins().iadd(tag, delta)
239-
};
240-
241-
let cast_tag = clif_intcast(fx, incr_tag, cast_to, !niches_ule);
242-
243-
(is_niche, cast_tag, 0)
244155
} else {
245156
// The special cases don't apply, so we'll have to go with
246157
// the general algorithm.

compiler/rustc_codegen_ssa/src/mir/place.rs

-92
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,6 @@ impl<'a, 'tcx, V: CodegenObject> PlaceRef<'tcx, V> {
211211
) -> V {
212212
let dl = &bx.tcx().data_layout;
213213
let cast_to_layout = bx.cx().layout_of(cast_to);
214-
let cast_to_size = cast_to_layout.layout.size();
215214
let cast_to = bx.cx().immediate_backend_type(cast_to_layout);
216215
if self.layout.abi.is_uninhabited() {
217216
return bx.cx().const_poison(cast_to);
@@ -261,21 +260,7 @@ impl<'a, 'tcx, V: CodegenObject> PlaceRef<'tcx, V> {
261260
_ => (tag_imm, bx.cx().immediate_backend_type(tag_op.layout)),
262261
};
263262

264-
let tag_size = tag_scalar.size(bx.cx());
265-
let max_unsigned = tag_size.unsigned_int_max();
266-
let max_signed = tag_size.signed_int_max() as u128;
267-
let min_signed = max_signed + 1;
268263
let relative_max = niche_variants.end().as_u32() - niche_variants.start().as_u32();
269-
let niche_end = niche_start.wrapping_add(relative_max as u128) & max_unsigned;
270-
let range = tag_scalar.valid_range(bx.cx());
271-
272-
let sle = |lhs: u128, rhs: u128| -> bool {
273-
// Signed and unsigned comparisons give the same results,
274-
// except that in signed comparisons an integer with the
275-
// sign bit set is less than one with the sign bit clear.
276-
// Toggle the sign bit to do a signed comparison.
277-
(lhs ^ min_signed) <= (rhs ^ min_signed)
278-
};
279264

280265
// We have a subrange `niche_start..=niche_end` inside `range`.
281266
// If the value of the tag is inside this subrange, it's a
@@ -291,49 +276,6 @@ impl<'a, 'tcx, V: CodegenObject> PlaceRef<'tcx, V> {
291276
// untagged_variant
292277
// }
293278
// However, we will likely be able to emit simpler code.
294-
295-
// Find the least and greatest values in `range`, considered
296-
// both as signed and unsigned.
297-
let (low_unsigned, high_unsigned) = if range.start <= range.end {
298-
(range.start, range.end)
299-
} else {
300-
(0, max_unsigned)
301-
};
302-
let (low_signed, high_signed) = if sle(range.start, range.end) {
303-
(range.start, range.end)
304-
} else {
305-
(min_signed, max_signed)
306-
};
307-
308-
let niches_ule = niche_start <= niche_end;
309-
let niches_sle = sle(niche_start, niche_end);
310-
let cast_smaller = cast_to_size <= tag_size;
311-
312-
// In the algorithm above, we can change
313-
// cast(relative_tag) + niche_variants.start()
314-
// into
315-
// cast(tag + (niche_variants.start() - niche_start))
316-
// if either the casted type is no larger than the original
317-
// type, or if the niche values are contiguous (in either the
318-
// signed or unsigned sense).
319-
let can_incr = cast_smaller || niches_ule || niches_sle;
320-
321-
let data_for_boundary_niche = || -> Option<(IntPredicate, u128)> {
322-
if !can_incr {
323-
None
324-
} else if niche_start == low_unsigned {
325-
Some((IntPredicate::IntULE, niche_end))
326-
} else if niche_end == high_unsigned {
327-
Some((IntPredicate::IntUGE, niche_start))
328-
} else if niche_start == low_signed {
329-
Some((IntPredicate::IntSLE, niche_end))
330-
} else if niche_end == high_signed {
331-
Some((IntPredicate::IntSGE, niche_start))
332-
} else {
333-
None
334-
}
335-
};
336-
337279
let (is_niche, tagged_discr, delta) = if relative_max == 0 {
338280
// Best case scenario: only one tagged variant. This will
339281
// likely become just a comparison and a jump.
@@ -349,40 +291,6 @@ impl<'a, 'tcx, V: CodegenObject> PlaceRef<'tcx, V> {
349291
let tagged_discr =
350292
bx.cx().const_uint(cast_to, niche_variants.start().as_u32() as u64);
351293
(is_niche, tagged_discr, 0)
352-
} else if let Some((predicate, constant)) = data_for_boundary_niche() {
353-
// The niche values are either the lowest or the highest in
354-
// `range`. We can avoid the first subtraction in the
355-
// algorithm.
356-
// The algorithm is now this:
357-
// is_niche = tag <= niche_end
358-
// discr = if is_niche {
359-
// cast(tag + (niche_variants.start() - niche_start))
360-
// } else {
361-
// untagged_variant
362-
// }
363-
// (the first line may instead be tag >= niche_start,
364-
// and may be a signed or unsigned comparison)
365-
// The arithmetic must be done before the cast, so we can
366-
// have the correct wrapping behavior. See issue #104519 for
367-
// the consequences of getting this wrong.
368-
let is_niche =
369-
bx.icmp(predicate, tag, bx.cx().const_uint_big(tag_llty, constant));
370-
let delta = (niche_variants.start().as_u32() as u128).wrapping_sub(niche_start);
371-
let incr_tag = if delta == 0 {
372-
tag
373-
} else {
374-
bx.add(tag, bx.cx().const_uint_big(tag_llty, delta))
375-
};
376-
377-
let cast_tag = if cast_smaller {
378-
bx.intcast(incr_tag, cast_to, false)
379-
} else if niches_ule {
380-
bx.zext(incr_tag, cast_to)
381-
} else {
382-
bx.sext(incr_tag, cast_to)
383-
};
384-
385-
(is_niche, cast_tag, 0)
386294
} else {
387295
// The special cases don't apply, so we'll have to go with
388296
// the general algorithm.

src/tools/tidy/src/ui_tests.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::path::{Path, PathBuf};
1010
// FIXME: The following limits should be reduced eventually.
1111
const ENTRY_LIMIT: usize = 885;
1212
const ROOT_ENTRY_LIMIT: usize = 891;
13-
const ISSUES_ENTRY_LIMIT: usize = 1978;
13+
const ISSUES_ENTRY_LIMIT: usize = 1977;
1414

1515
fn check_entries(tests_path: &Path, bad: &mut bool) {
1616
let mut directories: HashMap<PathBuf, usize> = HashMap::new();

tests/codegen/enum-match.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,11 @@ pub enum Enum1 {
3434

3535
// CHECK: define noundef i8 @match1{{.*}}
3636
// CHECK-NEXT: start:
37-
// CHECK-NEXT: [[DISCR:%.*]] = {{.*}}call i8 @llvm.usub.sat.i8(i8 %0, i8 1)
38-
// CHECK-NEXT: switch i8 [[DISCR]], label {{.*}} [
37+
// CHECK-NEXT: %1 = add i8 %0, -2
38+
// CHECK-NEXT: %2 = zext i8 %1 to i64
39+
// CHECK-NEXT: %3 = icmp ult i8 %1, 2
40+
// CHECK-NEXT: %4 = add nuw nsw i64 %2, 1
41+
// CHECK-NEXT: %_2 = select i1 %3, i64 %4, i64 0
3942
#[no_mangle]
4043
pub fn match1(e: Enum1) -> u8 {
4144
use Enum1::*;

tests/ui/enum-discriminant/issue-104519.rs

-10
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,4 @@ fn some_match(result: OpenResult) -> u8 {
2323
fn main() {
2424
let result = OpenResult::Ok(());
2525
assert_eq!(some_match(result), 0);
26-
27-
let result = OpenResult::Ok(());
28-
match result {
29-
OpenResult::Ok(()) => (),
30-
_ => unreachable!("message a"),
31-
}
32-
match result {
33-
OpenResult::Ok(()) => (),
34-
_ => unreachable!("message b"),
35-
}
3626
}

tests/ui/issues/issue-61696.rs renamed to tests/ui/enum-discriminant/issue-61696.rs

+16-5
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,23 @@ pub enum E2<X> {
5555
V4,
5656
}
5757

58-
fn main() {
59-
if let E1::V2 { .. } = (E1::V1 { f: true }) {
60-
unreachable!()
58+
#[inline(never)]
59+
fn match_e1(y: E1) -> u8 {
60+
match y {
61+
E1::V2 { .. } => 1,
62+
_ => 0,
6163
}
64+
}
6265

63-
if let E2::V1 { .. } = E2::V3::<Infallible> {
64-
unreachable!()
66+
#[inline(never)]
67+
fn match_e2(y: E2<Infallible>) -> u8 {
68+
match y {
69+
E2::V1 { .. } => 1,
70+
_ => 0,
6571
}
6672
}
73+
74+
fn main() {
75+
assert_eq!(match_e1(E1::V1 { f: true }), 0);
76+
assert_eq!(match_e2(E2::V3), 0);
77+
}

0 commit comments

Comments
 (0)