From d68a62f186a1bdf8d0de63d8c3000afc75d05336 Mon Sep 17 00:00:00 2001 From: asquared31415 <34665709+asquared31415@users.noreply.github.com> Date: Sat, 25 Jul 2026 18:27:34 +0000 Subject: [PATCH 1/2] ensure llvm gets bounds for size_of_val on slices with an element size of one --- compiler/rustc_codegen_ssa/src/size_of_val.rs | 39 +++++++++++++++---- .../intrinsics/size_of_val_bounds_hint.rs | 35 +++++++++++++++++ 2 files changed, 66 insertions(+), 8 deletions(-) create mode 100644 tests/codegen-llvm/intrinsics/size_of_val_bounds_hint.rs diff --git a/compiler/rustc_codegen_ssa/src/size_of_val.rs b/compiler/rustc_codegen_ssa/src/size_of_val.rs index 52ffc321cbb6f..b79715b9f3746 100644 --- a/compiler/rustc_codegen_ssa/src/size_of_val.rs +++ b/compiler/rustc_codegen_ssa/src/size_of_val.rs @@ -45,14 +45,37 @@ pub fn size_and_align_of_dst<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( } ty::Slice(_) | ty::Str => { let unit = layout.field(bx, 0); - // The info in this case is the length of the str, so the size is that - // times the unit size. - ( - // All slice sizes must fit into `isize`, so this multiplication cannot - // wrap -- neither signed nor unsigned. - bx.unchecked_sumul(info.unwrap(), bx.const_usize(unit.size.bytes())), - bx.const_usize(unit.align.bytes()), - ) + let unit_size = bx.const_usize(unit.size.bytes()); + // The info is the length metadata, the total size is that times the unit size. + let length_meta = info.unwrap(); + + let size = match bx.const_to_opt_u128(unit_size, false) { + // When the unit size is 1 (like *const [u8]), the `mul nsw nuw` emitted doesn't + // help LLVM prove anything about the size, and the mul gets removed. + // We help it with an `assume` and not emitting the mul, since it's an identity. + Some(1) => { + let size_bound = bx.data_layout().ptr_sized_integer().signed_max() as u128; + bx.assume_integer_range( + length_meta, + bx.type_isize(), + WrappingRange { start: 0, end: size_bound }, + ); + length_meta + } + // might as well optimize for 0 while we're here, any length times 0 unit size is 0 + Some(0) => bx.const_usize(0), + // For a unit size of 2 or greater, the `mul nsw nuw` is sufficient for LLVM to + // understand the size bounds. + Some(2..) | None => { + // All object sizes must fit into `isize`, so this multiplication cannot + // wrap -- neither signed nor unsigned. + bx.unchecked_sumul(length_meta, unit_size) + } + }; + + let align = bx.const_usize(unit.align.bytes()); + + (size, align) } ty::Foreign(_) => { // `extern` type. We cannot compute the size, so panic. diff --git a/tests/codegen-llvm/intrinsics/size_of_val_bounds_hint.rs b/tests/codegen-llvm/intrinsics/size_of_val_bounds_hint.rs new file mode 100644 index 0000000000000..ea0a5cff08d1c --- /dev/null +++ b/tests/codegen-llvm/intrinsics/size_of_val_bounds_hint.rs @@ -0,0 +1,35 @@ +//@ compile-flags: -Copt-level=3 -Zmerge-functions=disabled + +// Tests that `size_of_val_raw` on a slice with element size of one (*const [u8]) can be proven +// to have a size of <= isize::MAX by LLVM. +// The `mul nsw nuw` in size calculations was not sufficient when the element size was 1, so +// an `assume` is used in that case instead. Also an element size of 0 can always return 0. + +#![feature(layout_for_ptr)] +#![crate_type = "lib"] + +use std::mem::size_of_val_raw; + +// CHECK-LABEL: elem_size_one +// CHECK: start +// CHECK-NEXT: ret i1 true +#[unsafe(no_mangle)] +pub fn elem_size_one(x: *const [u8]) -> bool { + unsafe { size_of_val_raw(x) <= isize::MAX.cast_unsigned() } +} + +// CHECK-LABEL: elem_size_two +// CHECK: start +// CHECK-NEXT: ret i1 true +#[unsafe(no_mangle)] +pub fn elem_size_two(x: *const [u16]) -> bool { + unsafe { size_of_val_raw(x) <= isize::MAX.cast_unsigned() } +} + +// CHECK-LABEL: elem_size_zero +// CHECK: start +// CHECK-NEXT: ret i1 true +#[unsafe(no_mangle)] +pub fn elem_size_zero(x: *const [()]) -> bool { + unsafe { size_of_val_raw(x) == 0 } +} From a508a3516a88ca0ff7f095de1752dcaf0d25ac36 Mon Sep 17 00:00:00 2001 From: asquared31415 <34665709+asquared31415@users.noreply.github.com> Date: Sun, 26 Jul 2026 02:59:38 +0000 Subject: [PATCH 2/2] min llvm version --- tests/codegen-llvm/intrinsics/size_of_val_bounds_hint.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/codegen-llvm/intrinsics/size_of_val_bounds_hint.rs b/tests/codegen-llvm/intrinsics/size_of_val_bounds_hint.rs index ea0a5cff08d1c..fa387b1833530 100644 --- a/tests/codegen-llvm/intrinsics/size_of_val_bounds_hint.rs +++ b/tests/codegen-llvm/intrinsics/size_of_val_bounds_hint.rs @@ -1,4 +1,7 @@ //@ compile-flags: -Copt-level=3 -Zmerge-functions=disabled +//@ min-llvm-version: 23 +// in older LLVM, the `assume` call stays around, newer versions are easier to check +// (but the old versions do still optimize to `ret i1 true`, just with an extra assume) // Tests that `size_of_val_raw` on a slice with element size of one (*const [u8]) can be proven // to have a size of <= isize::MAX by LLVM.