Add LLVM range attributes to slice length parameters

This commit is contained in:
Scott McMurray 2025-10-31 16:05:48 -07:00
parent 3be68033b6
commit 0ba7bcfc44
7 changed files with 100 additions and 18 deletions

View file

@ -522,6 +522,28 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> {
let ii = apply(b);
if let BackendRepr::ScalarPair(scalar_a, scalar_b) = arg.layout.backend_repr {
apply_range_attr(llvm::AttributePlace::Argument(i), scalar_a);
let primitive_b = scalar_b.primitive();
let scalar_b = if let rustc_abi::Primitive::Int(int, false) = primitive_b
&& let ty::Ref(_, pointee_ty, _) = *arg.layout.ty.kind()
&& let ty::Slice(element_ty) = *pointee_ty.kind()
&& let elem_size = cx.layout_of(element_ty).size
&& elem_size != rustc_abi::Size::ZERO
{
// Ideally the layout calculations would have set the range,
// but that's complicated due to cycles, so in the mean time
// we calculate and apply it here.
debug_assert!(scalar_b.is_always_valid(cx));
let isize_max = int.signed_max() as u64;
rustc_abi::Scalar::Initialized {
value: primitive_b,
valid_range: rustc_abi::WrappingRange {
start: 0,
end: u128::from(isize_max / elem_size.bytes()),
},
}
} else {
scalar_b
};
apply_range_attr(llvm::AttributePlace::Argument(ii), scalar_b);
}
}

View file

@ -208,17 +208,23 @@ pub fn struct_return() -> S {
#[no_mangle]
pub fn helper(_: usize) {}
// CHECK: @slice(ptr noalias noundef nonnull readonly align 1{{( captures\(address, read_provenance\))?}} %_1.0, [[USIZE]] noundef %_1.1)
// CHECK: @slice(
// CHECK-SAME: ptr noalias noundef nonnull readonly align 1{{( captures\(address, read_provenance\))?}} %_1.0,
// CHECK-SAME: [[USIZE]] noundef range({{i32 0, -2147483648|i64 0, -9223372036854775808}}) %_1.1)
// FIXME #25759 This should also have `nocapture`
#[no_mangle]
pub fn slice(_: &[u8]) {}
// CHECK: @mutable_slice(ptr noalias noundef nonnull align 1 %_1.0, [[USIZE]] noundef %_1.1)
// CHECK: @mutable_slice(
// CHECK-SAME: ptr noalias noundef nonnull align 1 %_1.0,
// CHECK-SAME: [[USIZE]] noundef range({{i32 0, -2147483648|i64 0, -9223372036854775808}}) %_1.1)
// FIXME #25759 This should also have `nocapture`
#[no_mangle]
pub fn mutable_slice(_: &mut [u8]) {}
// CHECK: @unsafe_slice(ptr noundef nonnull align 2 %_1.0, [[USIZE]] noundef %_1.1)
// CHECK: @unsafe_slice(
// CHECK-SAME: ptr noundef nonnull align 2 %_1.0,
// CHECK-SAME: [[USIZE]] noundef range({{i32 0, 1073741824|i64 0, 4611686018427387904}}) %_1.1)
// unsafe interior means this isn't actually readonly and there may be aliases ...
#[no_mangle]
pub fn unsafe_slice(_: &[UnsafeInner]) {}
@ -227,7 +233,9 @@ pub fn unsafe_slice(_: &[UnsafeInner]) {}
#[no_mangle]
pub fn raw_slice(_: *const [u8]) {}
// CHECK: @str(ptr noalias noundef nonnull readonly align 1{{( captures\(address, read_provenance\))?}} %_1.0, [[USIZE]] noundef %_1.1)
// CHECK: @str(
// CHECK-SAME: ptr noalias noundef nonnull readonly align 1{{( captures\(address, read_provenance\))?}} %_1.0,
// CHECK-SAME: [[USIZE]] noundef range({{i32 0, -2147483648|i64 0, -9223372036854775808}}) %_1.1)
// FIXME #25759 This should also have `nocapture`
#[no_mangle]
pub fn str(_: &[u8]) {}
@ -259,7 +267,9 @@ pub fn trait_option(x: Option<Box<dyn Drop + Unpin>>) -> Option<Box<dyn Drop + U
x
}
// CHECK: { ptr, [[USIZE]] } @return_slice(ptr noalias noundef nonnull readonly align 2{{( captures\(address, read_provenance\))?}} %x.0, [[USIZE]] noundef %x.1)
// CHECK: { ptr, [[USIZE]] } @return_slice(
// CHECK-SAME: ptr noalias noundef nonnull readonly align 2{{( captures\(address, read_provenance\))?}} %x.0,
// CHECK-SAME: [[USIZE]] noundef range({{i32 0, 1073741824|i64 0, 4611686018427387904}}) %x.1)
#[no_mangle]
pub fn return_slice(x: &[u16]) -> &[u16] {
x

View file

@ -67,8 +67,26 @@ pub fn enum2_value(x: Enum2) -> Enum2 {
x
}
// CHECK: noundef [[USIZE]] @takes_slice(ptr {{.*}} %x.0, [[USIZE]] noundef %x.1)
// CHECK: noundef [[USIZE]] @takes_slice_4(ptr {{.*}} %x.0, [[USIZE]] noundef
// bit32-SAME: range(i32 0, [[#0x20000000]])
// bit64-SAME: range(i64 0, [[#0x2000000000000000]])
// CHECK-SAME: %x.1)
#[no_mangle]
pub fn takes_slice(x: &[i32]) -> usize {
pub fn takes_slice_4(x: &[i32]) -> usize {
x.len()
}
// CHECK: noundef [[USIZE]] @takes_slice_3(ptr {{.*}} %x.0, [[USIZE]] noundef
// bit32-SAME: range(i32 0, [[#0x2AAAAAAB]])
// bit64-SAME: range(i64 0, [[#0x2AAAAAAAAAAAAAAB]])
// CHECK-SAME: %x.1)
#[no_mangle]
pub fn takes_slice_3(x: &[[u8; 3]]) -> usize {
x.len()
}
// CHECK: noundef [[USIZE]] @takes_zst_slice(ptr {{.*}} %x.0, [[USIZE]] noundef %x.1)
#[no_mangle]
pub fn takes_zst_slice(x: &[()]) -> usize {
x.len()
}

View file

@ -19,7 +19,7 @@ pub fn chunks4(x: &[u8]) -> &[[u8; 4]] {
// CHECK-LABEL: @chunks4_with_remainder
#[no_mangle]
pub fn chunks4_with_remainder(x: &[u8]) -> (&[[u8; 4]], &[u8]) {
// CHECK-DAG: and i64 %x.1, -4
// CHECK-DAG: and i64 %x.1, [[#0x7FFFFFFFFFFFFFFC]]
// CHECK-DAG: and i64 %x.1, 3
// CHECK-DAG: lshr
// CHECK-NOT: mul

View file

@ -51,7 +51,7 @@ pub fn slice_iter_next_back<'a>(it: &mut std::slice::Iter<'a, u32>) -> Option<&'
// attribute is there, and confirms adding the assume back doesn't do anything.
// CHECK-LABEL: @slice_iter_new
// CHECK-SAME: (ptr noalias noundef nonnull {{.+}} %slice.0, {{.+}} noundef %slice.1)
// CHECK-SAME: (ptr noalias noundef nonnull {{.+}} %slice.0, {{.+}} noundef range({{.+}}) %slice.1)
#[no_mangle]
pub fn slice_iter_new(slice: &[u32]) -> std::slice::Iter<'_, u32> {
// CHECK-NOT: slice
@ -66,7 +66,7 @@ pub fn slice_iter_new(slice: &[u32]) -> std::slice::Iter<'_, u32> {
}
// CHECK-LABEL: @slice_iter_mut_new
// CHECK-SAME: (ptr noalias noundef nonnull {{.+}} %slice.0, {{.+}} noundef %slice.1)
// CHECK-SAME: (ptr noalias noundef nonnull {{.+}} %slice.0, {{.+}} noundef range({{.+}}) %slice.1)
#[no_mangle]
pub fn slice_iter_mut_new(slice: &mut [u32]) -> std::slice::IterMut<'_, u32> {
// CHECK-NOT: slice

View file

@ -0,0 +1,32 @@
//@ compile-flags: -C opt-level=3
#![crate_type = "lib"]
#[no_mangle]
// CHECK-LABEL: @len_plus_ten_a
pub fn len_plus_ten_a(s: &[u8]) -> usize {
// CHECK: start:
// CHECK-NOT: add
// CHECK: %[[R:.+]] = add nuw i{{.+}} %s.1, 10
// CHECK-NEXT: ret {{.+}} %[[R]]
s.len().wrapping_add(10)
}
#[no_mangle]
// CHECK-LABEL: @len_plus_ten_b
pub fn len_plus_ten_b(s: &[u32]) -> usize {
// CHECK: start:
// CHECK-NOT: add
// CHECK: %[[R:.+]] = add nuw nsw i{{.+}} %s.1, 10
// CHECK-NEXT: ret {{.+}} %[[R]]
s.len().wrapping_add(10)
}
#[no_mangle]
// CHECK-LABEL: @len_plus_len
pub fn len_plus_len(x: &[u8], y: &[u8]) -> usize {
// CHECK: start:
// CHECK-NOT: add
// CHECK: %[[R:.+]] = add nuw i{{.+}} {{%x.1, %y.1|%y.1, %x.1}}
// CHECK-NEXT: ret {{.+}} %[[R]]
usize::wrapping_add(x.len(), y.len())
}

View file

@ -42,8 +42,8 @@ pub fn is_zero_array(data: &[u8; 4]) -> bool {
// equality for non-byte types also just emit a `bcmp`, not a loop.
// CHECK-LABEL: @eq_slice_of_nested_u8(
// CHECK-SAME: [[USIZE:i16|i32|i64]] noundef %x.1
// CHECK-SAME: [[USIZE]] noundef %y.1
// CHECK-SAME: [[USIZE:i16|i32|i64]] noundef range({{.+}}) %x.1
// CHECK-SAME: [[USIZE]] noundef range({{.+}}) %y.1
#[no_mangle]
fn eq_slice_of_nested_u8(x: &[[u8; 3]], y: &[[u8; 3]]) -> bool {
// CHECK: icmp eq [[USIZE]] %x.1, %y.1
@ -54,8 +54,8 @@ fn eq_slice_of_nested_u8(x: &[[u8; 3]], y: &[[u8; 3]]) -> bool {
}
// CHECK-LABEL: @eq_slice_of_i32(
// CHECK-SAME: [[USIZE:i16|i32|i64]] noundef %x.1
// CHECK-SAME: [[USIZE]] noundef %y.1
// CHECK-SAME: [[USIZE:i16|i32|i64]] noundef range({{.+}}) %x.1
// CHECK-SAME: [[USIZE]] noundef range({{.+}}) %y.1
#[no_mangle]
fn eq_slice_of_i32(x: &[i32], y: &[i32]) -> bool {
// CHECK: icmp eq [[USIZE]] %x.1, %y.1
@ -66,8 +66,8 @@ fn eq_slice_of_i32(x: &[i32], y: &[i32]) -> bool {
}
// CHECK-LABEL: @eq_slice_of_nonzero(
// CHECK-SAME: [[USIZE:i16|i32|i64]] noundef %x.1
// CHECK-SAME: [[USIZE]] noundef %y.1
// CHECK-SAME: [[USIZE:i16|i32|i64]] noundef range({{.+}}) %x.1
// CHECK-SAME: [[USIZE]] noundef range({{.+}}) %y.1
#[no_mangle]
fn eq_slice_of_nonzero(x: &[NonZero<i32>], y: &[NonZero<i32>]) -> bool {
// CHECK: icmp eq [[USIZE]] %x.1, %y.1
@ -78,8 +78,8 @@ fn eq_slice_of_nonzero(x: &[NonZero<i32>], y: &[NonZero<i32>]) -> bool {
}
// CHECK-LABEL: @eq_slice_of_option_of_nonzero(
// CHECK-SAME: [[USIZE:i16|i32|i64]] noundef %x.1
// CHECK-SAME: [[USIZE]] noundef %y.1
// CHECK-SAME: [[USIZE:i16|i32|i64]] noundef range({{.+}}) %x.1
// CHECK-SAME: [[USIZE]] noundef range({{.+}}) %y.1
#[no_mangle]
fn eq_slice_of_option_of_nonzero(x: &[Option<NonZero<i16>>], y: &[Option<NonZero<i16>>]) -> bool {
// CHECK: icmp eq [[USIZE]] %x.1, %y.1