Skip to content

Commit 511bf30

Browse files
committedFeb 19, 2025
Emit trunc nuw for unchecked shifts and to_immediate_scalar
- For shifts this shrinks the IR by no longer needing an `assume` while still providing the UB information - Having this on the `i8`→`i1` truncations will hopefully help with some places that have to load `i8`s or pass those in LLVM structs without range information
1 parent ed49386 commit 511bf30

File tree

10 files changed

+77
-50
lines changed

10 files changed

+77
-50
lines changed
 

‎compiler/rustc_codegen_gcc/src/builder.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1694,7 +1694,7 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> {
16941694

16951695
fn to_immediate_scalar(&mut self, val: Self::Value, scalar: abi::Scalar) -> Self::Value {
16961696
if scalar.is_bool() {
1697-
return self.trunc(val, self.cx().type_i1());
1697+
return self.unchecked_utrunc(val, self.cx().type_i1());
16981698
}
16991699
val
17001700
}

‎compiler/rustc_codegen_llvm/src/builder.rs

+26-2
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@ use smallvec::SmallVec;
2929
use tracing::{debug, instrument};
3030

3131
use crate::abi::FnAbiLlvmExt;
32-
use crate::attributes;
3332
use crate::common::Funclet;
3433
use crate::context::{CodegenCx, SimpleCx};
3534
use crate::llvm::{self, AtomicOrdering, AtomicRmwBinOp, BasicBlock, False, Metadata, True};
3635
use crate::type_::Type;
3736
use crate::type_of::LayoutLlvmExt;
3837
use crate::value::Value;
38+
use crate::{attributes, llvm_util};
3939

4040
#[must_use]
4141
pub(crate) struct GenericBuilder<'a, 'll, CX: Borrow<SimpleCx<'ll>>> {
@@ -606,7 +606,7 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
606606

607607
fn to_immediate_scalar(&mut self, val: Self::Value, scalar: abi::Scalar) -> Self::Value {
608608
if scalar.is_bool() {
609-
return self.trunc(val, self.cx().type_i1());
609+
return self.unchecked_utrunc(val, self.cx().type_i1());
610610
}
611611
val
612612
}
@@ -942,6 +942,30 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
942942
unsafe { llvm::LLVMBuildTrunc(self.llbuilder, val, dest_ty, UNNAMED) }
943943
}
944944

945+
fn unchecked_utrunc(&mut self, val: &'ll Value, dest_ty: &'ll Type) -> &'ll Value {
946+
let trunc = self.trunc(val, dest_ty);
947+
if llvm_util::get_version() >= (19, 0, 0) {
948+
unsafe {
949+
if llvm::LLVMIsATruncInst(trunc).is_some() {
950+
llvm::LLVMSetNUW(trunc, True);
951+
}
952+
}
953+
}
954+
trunc
955+
}
956+
957+
fn unchecked_strunc(&mut self, val: &'ll Value, dest_ty: &'ll Type) -> &'ll Value {
958+
let trunc = self.trunc(val, dest_ty);
959+
if llvm_util::get_version() >= (19, 0, 0) {
960+
unsafe {
961+
if llvm::LLVMIsATruncInst(trunc).is_some() {
962+
llvm::LLVMSetNSW(trunc, True);
963+
}
964+
}
965+
}
966+
trunc
967+
}
968+
945969
fn sext(&mut self, val: &'ll Value, dest_ty: &'ll Type) -> &'ll Value {
946970
unsafe { llvm::LLVMBuildSExt(self.llbuilder, val, dest_ty, UNNAMED) }
947971
}

‎compiler/rustc_codegen_llvm/src/llvm/ffi.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1165,6 +1165,7 @@ unsafe extern "C" {
11651165

11661166
// Operations on instructions
11671167
pub(crate) fn LLVMIsAInstruction(Val: &Value) -> Option<&Value>;
1168+
pub(crate) fn LLVMIsATruncInst(Val: &Value) -> Option<&Value>;
11681169
pub(crate) fn LLVMGetFirstBasicBlock(Fn: &Value) -> &BasicBlock;
11691170
pub(crate) fn LLVMGetOperand(Val: &Value, Index: c_uint) -> Option<&Value>;
11701171

‎compiler/rustc_codegen_ssa/src/base.rs

+2-8
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use rustc_middle::query::Providers;
2424
use rustc_middle::ty::layout::{HasTyCtxt, HasTypingEnv, LayoutOf, TyAndLayout};
2525
use rustc_middle::ty::{self, Instance, Ty, TyCtxt};
2626
use rustc_session::Session;
27-
use rustc_session::config::{self, CrateType, EntryFnType, OptLevel, OutputType};
27+
use rustc_session::config::{self, CrateType, EntryFnType, OutputType};
2828
use rustc_span::{DUMMY_SP, Symbol, sym};
2929
use rustc_trait_selection::infer::{BoundRegionConversionTime, TyCtxtInferExt};
3030
use rustc_trait_selection::traits::{ObligationCause, ObligationCtxt};
@@ -364,13 +364,7 @@ pub(crate) fn build_shift_expr_rhs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
364364
let rhs_sz = bx.cx().int_width(rhs_llty);
365365
let lhs_sz = bx.cx().int_width(lhs_llty);
366366
if lhs_sz < rhs_sz {
367-
if is_unchecked && bx.sess().opts.optimize != OptLevel::No {
368-
// FIXME: Use `trunc nuw` once that's available
369-
let inrange = bx.icmp(IntPredicate::IntULE, rhs, mask);
370-
bx.assume(inrange);
371-
}
372-
373-
bx.trunc(rhs, lhs_llty)
367+
if is_unchecked { bx.unchecked_utrunc(rhs, lhs_llty) } else { bx.trunc(rhs, lhs_llty) }
374368
} else if lhs_sz > rhs_sz {
375369
// We zero-extend even if the RHS is signed. So e.g. `(x: i32) << -1i8` will zero-extend the
376370
// RHS to `255i32`. But then we mask the shift amount to be within the size of the LHS

‎compiler/rustc_codegen_ssa/src/traits/builder.rs

+11
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,17 @@ pub trait BuilderMethods<'a, 'tcx>:
340340
}
341341

342342
fn trunc(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
343+
/// Produces the same value as [`Self::trunc`] (and defaults to that),
344+
/// but is UB unless the *zero*-extending the result can reproduce `val`.
345+
fn unchecked_utrunc(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value {
346+
self.trunc(val, dest_ty)
347+
}
348+
/// Produces the same value as [`Self::trunc`] (and defaults to that),
349+
/// but is UB unless the *sign*-extending the result can reproduce `val`.
350+
fn unchecked_strunc(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value {
351+
self.trunc(val, dest_ty)
352+
}
353+
343354
fn sext(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
344355
fn fptoui_sat(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
345356
fn fptosi_sat(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;

‎tests/codegen/intrinsics/transmute-niched.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ pub unsafe fn check_bool_from_ordering(x: std::cmp::Ordering) -> bool {
170170
// OPT: call void @llvm.assume(i1 %2)
171171
// CHECK-NOT: icmp
172172
// CHECK-NOT: assume
173-
// CHECK: %[[R:.+]] = trunc i8 %x to i1
173+
// CHECK: %[[R:.+]] = trunc{{( nuw)?}} i8 %x to i1
174174
// CHECK: ret i1 %[[R]]
175175

176176
transmute(x)

‎tests/codegen/intrinsics/transmute.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ use std::intrinsics::mir::*;
1111
use std::intrinsics::{transmute, transmute_unchecked};
1212
use std::mem::MaybeUninit;
1313

14+
// FIXME(LLVM18REMOVED): `trunc nuw` doesn't exist in LLVM 18, so once we no
15+
// longer support it the optional flag checks can be changed to required.
16+
1417
pub enum ZstNever {}
1518

1619
#[repr(align(2))]
@@ -153,7 +156,7 @@ pub unsafe fn check_from_newtype(x: Scalar64) -> u64 {
153156
pub unsafe fn check_aggregate_to_bool(x: Aggregate8) -> bool {
154157
// CHECK: %x = alloca [1 x i8], align 1
155158
// CHECK: %[[BYTE:.+]] = load i8, ptr %x, align 1
156-
// CHECK: %[[BOOL:.+]] = trunc i8 %[[BYTE]] to i1
159+
// CHECK: %[[BOOL:.+]] = trunc{{( nuw)?}} i8 %[[BYTE]] to i1
157160
// CHECK: ret i1 %[[BOOL]]
158161
transmute(x)
159162
}
@@ -171,7 +174,7 @@ pub unsafe fn check_aggregate_from_bool(x: bool) -> Aggregate8 {
171174
#[no_mangle]
172175
pub unsafe fn check_byte_to_bool(x: u8) -> bool {
173176
// CHECK-NOT: alloca
174-
// CHECK: %[[R:.+]] = trunc i8 %x to i1
177+
// CHECK: %[[R:.+]] = trunc{{( nuw)?}} i8 %x to i1
175178
// CHECK: ret i1 %[[R]]
176179
transmute(x)
177180
}
@@ -284,7 +287,7 @@ pub unsafe fn check_long_array_more_aligned(x: [u8; 100]) -> [u32; 25] {
284287
#[no_mangle]
285288
pub unsafe fn check_pair_with_bool(x: (u8, bool)) -> (bool, i8) {
286289
// CHECK-NOT: alloca
287-
// CHECK: trunc i8 %x.0 to i1
290+
// CHECK: trunc{{( nuw)?}} i8 %x.0 to i1
288291
// CHECK: zext i1 %x.1 to i8
289292
transmute(x)
290293
}
@@ -338,7 +341,7 @@ pub unsafe fn check_heterogeneous_integer_pair(x: (i32, bool)) -> (bool, u32) {
338341
// CHECK: store i8 %[[WIDER]]
339342

340343
// CHECK: %[[BYTE:.+]] = load i8
341-
// CHECK: trunc i8 %[[BYTE:.+]] to i1
344+
// CHECK: trunc{{( nuw)?}} i8 %[[BYTE:.+]] to i1
342345
// CHECK: load i32
343346
transmute(x)
344347
}

‎tests/codegen/transmute-scalar.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pub fn bool_to_byte(b: bool) -> u8 {
2626
}
2727

2828
// CHECK-LABEL: define{{.*}}zeroext i1 @byte_to_bool(i8{{.*}} %byte)
29-
// CHECK: %_0 = trunc i8 %byte to i1
29+
// CHECK: %_0 = trunc{{( nuw)?}} i8 %byte to i1
3030
// CHECK-NEXT: ret i1 %_0
3131
#[no_mangle]
3232
pub unsafe fn byte_to_bool(byte: u8) -> bool {

‎tests/codegen/unchecked_shifts.rs

+26-32
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
//@ compile-flags: -Copt-level=3
1+
//@ revisions: LLVM18 LLVM19PLUS
2+
//@ compile-flags: -Copt-level=3 -C no-prepopulate-passes
3+
//@[LLVM18] exact-llvm-major-version: 18
4+
//@[LLVM19PLUS] min-llvm-version: 19
5+
6+
// This runs mir-opts to inline the standard library call, but doesn't run LLVM
7+
// optimizations so it doesn't need to worry about them adding more flags.
28

39
#![crate_type = "lib"]
410
#![feature(unchecked_shifts)]
@@ -17,12 +23,9 @@ pub unsafe fn unchecked_shl_unsigned_same(a: u32, b: u32) -> u32 {
1723
// CHECK-LABEL: @unchecked_shl_unsigned_smaller
1824
#[no_mangle]
1925
pub unsafe fn unchecked_shl_unsigned_smaller(a: u16, b: u32) -> u16 {
20-
// This uses -DAG to avoid failing on irrelevant reorderings,
21-
// like emitting the truncation earlier.
22-
23-
// CHECK-DAG: %[[INRANGE:.+]] = icmp ult i32 %b, 16
24-
// CHECK-DAG: tail call void @llvm.assume(i1 %[[INRANGE]])
25-
// CHECK-DAG: %[[TRUNC:.+]] = trunc{{( nuw)?( nsw)?}} i32 %b to i16
26+
// CHECK-NOT: assume
27+
// LLVM18-DAG: %[[TRUNC:.+]] = trunc i32 %b to i16
28+
// LLVM19PLUS-DAG: %[[TRUNC:.+]] = trunc nuw i32 %b to i16
2629
// CHECK-DAG: shl i16 %a, %[[TRUNC]]
2730
a.unchecked_shl(b)
2831
}
@@ -31,7 +34,7 @@ pub unsafe fn unchecked_shl_unsigned_smaller(a: u16, b: u32) -> u16 {
3134
#[no_mangle]
3235
pub unsafe fn unchecked_shl_unsigned_bigger(a: u64, b: u32) -> u64 {
3336
// CHECK-NOT: assume
34-
// CHECK: %[[EXT:.+]] = zext{{( nneg)?}} i32 %b to i64
37+
// CHECK: %[[EXT:.+]] = zext i32 %b to i64
3538
// CHECK: shl i64 %a, %[[EXT]]
3639
a.unchecked_shl(b)
3740
}
@@ -49,21 +52,18 @@ pub unsafe fn unchecked_shr_signed_same(a: i32, b: u32) -> i32 {
4952
// CHECK-LABEL: @unchecked_shr_signed_smaller
5053
#[no_mangle]
5154
pub unsafe fn unchecked_shr_signed_smaller(a: i16, b: u32) -> i16 {
52-
// This uses -DAG to avoid failing on irrelevant reorderings,
53-
// like emitting the truncation earlier.
54-
55-
// CHECK-DAG: %[[INRANGE:.+]] = icmp ult i32 %b, 16
56-
// CHECK-DAG: tail call void @llvm.assume(i1 %[[INRANGE]])
57-
// CHECK-DAG: %[[TRUNC:.+]] = trunc{{( nuw)?( nsw)?}} i32 %b to i16
58-
// CHECK-DAG: ashr i16 %a, %[[TRUNC]]
55+
// CHECK-NOT: assume
56+
// LLVM18: %[[TRUNC:.+]] = trunc i32 %b to i16
57+
// LLVM19PLUS: %[[TRUNC:.+]] = trunc nuw i32 %b to i16
58+
// CHECK: ashr i16 %a, %[[TRUNC]]
5959
a.unchecked_shr(b)
6060
}
6161

6262
// CHECK-LABEL: @unchecked_shr_signed_bigger
6363
#[no_mangle]
6464
pub unsafe fn unchecked_shr_signed_bigger(a: i64, b: u32) -> i64 {
6565
// CHECK-NOT: assume
66-
// CHECK: %[[EXT:.+]] = zext{{( nneg)?}} i32 %b to i64
66+
// CHECK: %[[EXT:.+]] = zext i32 %b to i64
6767
// CHECK: ashr i64 %a, %[[EXT]]
6868
a.unchecked_shr(b)
6969
}
@@ -72,7 +72,7 @@ pub unsafe fn unchecked_shr_signed_bigger(a: i64, b: u32) -> i64 {
7272
#[no_mangle]
7373
pub unsafe fn unchecked_shr_u128_i8(a: u128, b: i8) -> u128 {
7474
// CHECK-NOT: assume
75-
// CHECK: %[[EXT:.+]] = zext{{( nneg)?}} i8 %b to i128
75+
// CHECK: %[[EXT:.+]] = zext i8 %b to i128
7676
// CHECK: lshr i128 %a, %[[EXT]]
7777
std::intrinsics::unchecked_shr(a, b)
7878
}
@@ -81,33 +81,27 @@ pub unsafe fn unchecked_shr_u128_i8(a: u128, b: i8) -> u128 {
8181
#[no_mangle]
8282
pub unsafe fn unchecked_shl_i128_u8(a: i128, b: u8) -> i128 {
8383
// CHECK-NOT: assume
84-
// CHECK: %[[EXT:.+]] = zext{{( nneg)?}} i8 %b to i128
84+
// CHECK: %[[EXT:.+]] = zext i8 %b to i128
8585
// CHECK: shl i128 %a, %[[EXT]]
8686
std::intrinsics::unchecked_shl(a, b)
8787
}
8888

8989
// CHECK-LABEL: @unchecked_shl_u8_i128
9090
#[no_mangle]
9191
pub unsafe fn unchecked_shl_u8_i128(a: u8, b: i128) -> u8 {
92-
// This uses -DAG to avoid failing on irrelevant reorderings,
93-
// like emitting the truncation earlier.
94-
95-
// CHECK-DAG: %[[INRANGE:.+]] = icmp ult i128 %b, 8
96-
// CHECK-DAG: tail call void @llvm.assume(i1 %[[INRANGE]])
97-
// CHECK-DAG: %[[TRUNC:.+]] = trunc{{( nuw)?( nsw)?}} i128 %b to i8
98-
// CHECK-DAG: shl i8 %a, %[[TRUNC]]
92+
// CHECK-NOT: assume
93+
// LLVM18: %[[TRUNC:.+]] = trunc i128 %b to i8
94+
// LLVM19PLUS: %[[TRUNC:.+]] = trunc nuw i128 %b to i8
95+
// CHECK: shl i8 %a, %[[TRUNC]]
9996
std::intrinsics::unchecked_shl(a, b)
10097
}
10198

10299
// CHECK-LABEL: @unchecked_shr_i8_u128
103100
#[no_mangle]
104101
pub unsafe fn unchecked_shr_i8_u128(a: i8, b: u128) -> i8 {
105-
// This uses -DAG to avoid failing on irrelevant reorderings,
106-
// like emitting the truncation earlier.
107-
108-
// CHECK-DAG: %[[INRANGE:.+]] = icmp ult i128 %b, 8
109-
// CHECK-DAG: tail call void @llvm.assume(i1 %[[INRANGE]])
110-
// CHECK-DAG: %[[TRUNC:.+]] = trunc{{( nuw)?( nsw)?}} i128 %b to i8
111-
// CHECK-DAG: ashr i8 %a, %[[TRUNC]]
102+
// CHECK-NOT: assume
103+
// LLVM18: %[[TRUNC:.+]] = trunc i128 %b to i8
104+
// LLVM19PLUS: %[[TRUNC:.+]] = trunc nuw i128 %b to i8
105+
// CHECK: ashr i8 %a, %[[TRUNC]]
112106
std::intrinsics::unchecked_shr(a, b)
113107
}

‎tests/codegen/union-abi.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,4 @@ pub union UnionBool {
142142
pub fn test_UnionBool(b: UnionBool) -> bool {
143143
unsafe { b.b }
144144
}
145-
// CHECK: %_0 = trunc i8 %b to i1
145+
// CHECK: %_0 = trunc{{( nuw)?}} i8 %b to i1

0 commit comments

Comments
 (0)
Please sign in to comment.