Skip to content

Commit e2a49e2

Browse files
committed
Use trunc nuw+br for 0/1 branches even in optimized builds
Rather than needing to use `switch` for them to include the `unreachable` arm
1 parent a18bd8a commit e2a49e2

File tree

5 files changed

+108
-8
lines changed

5 files changed

+108
-8
lines changed

compiler/rustc_codegen_ssa/src/mir/block.rs

+25
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::cmp;
33
use rustc_abi::{BackendRepr, ExternAbi, HasDataLayout, Reg, WrappingRange};
44
use rustc_ast as ast;
55
use rustc_ast::{InlineAsmOptions, InlineAsmTemplatePiece};
6+
use rustc_data_structures::packed::Pu128;
67
use rustc_hir::lang_items::LangItem;
78
use rustc_middle::mir::{self, AssertKind, InlineAsmMacro, SwitchTargets, UnwindTerminateReason};
89
use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf, ValidityRequirement};
@@ -402,6 +403,30 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
402403
let cmp = bx.icmp(IntPredicate::IntEQ, discr_value, llval);
403404
bx.cond_br_with_expect(cmp, lltarget, llotherwise, expect);
404405
}
406+
} else if target_iter.len() == 2
407+
&& self.mir[targets.otherwise()].is_empty_unreachable()
408+
&& targets.all_values().contains(&Pu128(0))
409+
&& targets.all_values().contains(&Pu128(1))
410+
{
411+
// This is the really common case for `bool`, `Option`, etc.
412+
// By using `trunc nuw` we communicate that other values are
413+
// impossible without needing `switch` or `assume`s.
414+
let true_bb = targets.target_for_value(1);
415+
let false_bb = targets.target_for_value(0);
416+
let true_ll = helper.llbb_with_cleanup(self, true_bb);
417+
let false_ll = helper.llbb_with_cleanup(self, false_bb);
418+
let true_cold = self.cold_blocks[true_bb];
419+
let false_cold = self.cold_blocks[false_bb];
420+
let expect = (true_cold != false_cold).then_some(!true_cold);
421+
422+
let bool_ty = bx.tcx().types.bool;
423+
let cond = if switch_ty == bool_ty {
424+
discr_value
425+
} else {
426+
let bool_llty = bx.immediate_backend_type(bx.layout_of(bool_ty));
427+
bx.unchecked_utrunc(discr_value, bool_llty)
428+
};
429+
bx.cond_br_with_expect(cond, true_ll, false_ll, expect);
405430
} else if self.cx.sess().opts.optimize == OptLevel::No
406431
&& target_iter.len() == 2
407432
&& self.mir[targets.otherwise()].is_empty_unreachable()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//@ compile-flags: -Copt-level=3 -C no-prepopulate-passes
2+
//@ min-llvm-version: 19 (for trunc nuw)
3+
//@ only-x86_64 (because these discriminants are isize)
4+
5+
#![crate_type = "lib"]
6+
7+
// CHECK-LABEL: @option_match
8+
#[no_mangle]
9+
pub fn option_match(x: Option<i32>) -> u16 {
10+
// CHECK: %x = alloca [8 x i8]
11+
// CHECK: store i32 %0, ptr %x
12+
// CHECK: %[[TAG:.+]] = load i32, ptr %x
13+
// CHECK-SAME: !range ![[ZERO_ONE_32:[0-9]+]]
14+
// CHECK: %[[DISCR:.+]] = zext i32 %[[TAG]] to i64
15+
// CHECK: %[[COND:.+]] = trunc nuw i64 %[[DISCR]] to i1
16+
// CHECK: br i1 %[[COND]], label %[[TRUE:[a-z0-9]+]], label %[[FALSE:[a-z0-9]+]]
17+
18+
// CHECK: [[TRUE]]:
19+
// CHECK: store i16 13
20+
21+
// CHECK: [[FALSE]]:
22+
// CHECK: store i16 42
23+
match x {
24+
Some(_) => 13,
25+
None => 42,
26+
}
27+
}
28+
29+
// CHECK-LABEL: @result_match
30+
#[no_mangle]
31+
pub fn result_match(x: Result<u64, i64>) -> u16 {
32+
// CHECK: %x = alloca [16 x i8]
33+
// CHECK: store i64 %0, ptr %x
34+
// CHECK: %[[DISCR:.+]] = load i64, ptr %x
35+
// CHECK-SAME: !range ![[ZERO_ONE_64:[0-9]+]]
36+
// CHECK: %[[COND:.+]] = trunc nuw i64 %[[DISCR]] to i1
37+
// CHECK: br i1 %[[COND]], label %[[TRUE:[a-z0-9]+]], label %[[FALSE:[a-z0-9]+]]
38+
39+
// CHECK: [[TRUE]]:
40+
// CHECK: store i16 13
41+
42+
// CHECK: [[FALSE]]:
43+
// CHECK: store i16 42
44+
match x {
45+
Err(_) => 13,
46+
Ok(_) => 42,
47+
}
48+
}
49+
50+
// CHECK: ![[ZERO_ONE_32]] = !{i32 0, i32 2}
51+
// CHECK: ![[ZERO_ONE_64]] = !{i64 0, i64 2}

tests/codegen/intrinsics/cold_path2.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ pub fn test(x: Option<bool>) {
2626
}
2727

2828
// CHECK-LABEL: @test(
29-
// CHECK: br i1 %1, label %bb2, label %bb1, !prof ![[NUM:[0-9]+]]
29+
// CHECK: %[[IS_NONE:.+]] = icmp eq i8 %0, 2
30+
// CHECK: br i1 %[[IS_NONE]], label %bb2, label %bb1, !prof ![[NUM:[0-9]+]]
3031
// CHECK: bb1:
3132
// CHECK: path_a
3233
// CHECK: bb2:

tests/codegen/issues/issue-122600-ptr-discriminant-update.rs

+26-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33

44
#![crate_type = "lib"]
55

6+
// The bug here was that it was loading and storing the whole value.
7+
// It's ok for it to load the discriminant,
8+
// to preserve the UB from `unreachable_unchecked`,
9+
// but it better only store the constant discriminant of `B`.
10+
611
pub enum State {
712
A([u8; 753]),
813
B([u8; 753]),
@@ -11,9 +16,27 @@ pub enum State {
1116
// CHECK-LABEL: @update
1217
#[no_mangle]
1318
pub unsafe fn update(s: *mut State) {
14-
// CHECK-NEXT: start:
15-
// CHECK-NEXT: store i8
16-
// CHECK-NEXT: ret
19+
// CHECK-NOT: alloca
20+
21+
// CHECK-NOT: load
22+
// CHECK-NOT: store
23+
// CHECK-NOT: memcpy
24+
// CHECK-NOT: 75{{3|4}}
25+
26+
// CHECK: %[[TAG:.+]] = load i8, ptr %s, align 1
27+
// CHECK-NEXT: trunc nuw i8 %[[TAG]] to i1
28+
29+
// CHECK-NOT: load
30+
// CHECK-NOT: store
31+
// CHECK-NOT: memcpy
32+
// CHECK-NOT: 75{{3|4}}
33+
34+
// CHECK: store i8 1, ptr %s, align 1
35+
36+
// CHECK-NOT: load
37+
// CHECK-NOT: store
38+
// CHECK-NOT: memcpy
39+
// CHECK-NOT: 75{{3|4}}
1740
let State::A(v) = s.read() else { std::hint::unreachable_unchecked() };
1841
s.write(State::B(v));
1942
}

tests/codegen/try_question_mark_nop.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ use std::ptr::NonNull;
1616
#[no_mangle]
1717
pub fn option_nop_match_32(x: Option<u32>) -> Option<u32> {
1818
// CHECK: start:
19-
// TWENTY-NEXT: %trunc = trunc nuw i32 %0 to i1
20-
// TWENTY-NEXT: %.2 = select i1 %trunc, i32 %1, i32 undef
19+
// TWENTY-NEXT: %[[IS_SOME:.+]] = trunc nuw i32 %0 to i1
20+
// TWENTY-NEXT: %.2 = select i1 %[[IS_SOME]], i32 %1, i32 undef
2121
// CHECK-NEXT: [[REG1:%.*]] = insertvalue { i32, i32 } poison, i32 %0, 0
2222
// NINETEEN-NEXT: [[REG2:%.*]] = insertvalue { i32, i32 } [[REG1]], i32 %1, 1
2323
// TWENTY-NEXT: [[REG2:%.*]] = insertvalue { i32, i32 } [[REG1]], i32 %.2, 1
@@ -32,8 +32,8 @@ pub fn option_nop_match_32(x: Option<u32>) -> Option<u32> {
3232
#[no_mangle]
3333
pub fn option_nop_traits_32(x: Option<u32>) -> Option<u32> {
3434
// CHECK: start:
35-
// TWENTY-NEXT: %trunc = trunc nuw i32 %0 to i1
36-
// TWENTY-NEXT: %.1 = select i1 %trunc, i32 %1, i32 undef
35+
// TWENTY-NEXT: %[[IS_SOME:.+]] = trunc nuw i32 %0 to i1
36+
// TWENTY-NEXT: %.1 = select i1 %[[IS_SOME]], i32 %1, i32 undef
3737
// CHECK-NEXT: insertvalue { i32, i32 }
3838
// CHECK-NEXT: insertvalue { i32, i32 }
3939
// CHECK-NEXT: ret { i32, i32 }

0 commit comments

Comments
 (0)