From 12a18ee52d179e69dc611a3d965b8fcd7b6133d7 Mon Sep 17 00:00:00 2001 From: Tony Kan Date: Sat, 21 Feb 2026 21:31:13 -0800 Subject: [PATCH 1/5] perf(codegen): Use `nuw nsw` and `assume` to eliminate `size_of_val == 0` for non-ZST DSTs --- compiler/rustc_codegen_ssa/src/size_of_val.rs | 15 +++++++++++++-- tests/codegen-llvm/dst-vtable-align-nonzero.rs | 11 +++++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/size_of_val.rs b/compiler/rustc_codegen_ssa/src/size_of_val.rs index 52ffc321cbb6f..00e6c2512231f 100644 --- a/compiler/rustc_codegen_ssa/src/size_of_val.rs +++ b/compiler/rustc_codegen_ssa/src/size_of_val.rs @@ -159,7 +159,10 @@ pub fn size_and_align_of_dst<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( // Furthermore, `align >= unsized_align`, and therefore we only need to do: // let full_size = (unsized_offset_unadjusted + unsized_size).align_to(full_align); - let full_size = bx.add(unsized_offset_unadjusted, unsized_size); + // This is the unrounded size before alignment padding. It cannot exceed the + // rounded size, which itself cannot exceed `isize::MAX`. Thus the addition + // cannot overflow `isize::MAX`, let alone `usize::MAX`. + let unrounded_size = bx.unchecked_suadd(unsized_offset_unadjusted, unsized_size); // Issue #27023: must add any necessary padding to `size` // (to make it a multiple of `align`) before returning it. @@ -173,10 +176,18 @@ pub fn size_and_align_of_dst<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( // `(size + (align-1)) & -align` let one = bx.const_usize(1); let addend = bx.sub(full_align, one); - let add = bx.add(full_size, addend); + let add = bx.add(unrounded_size, addend); let neg = bx.neg(full_align); let full_size = bx.and(add, neg); + // Alignment rounding can only increase the size, never decrease it: + // `round_up(x, a) >= x` for power-of-two `a`. With the `nuw` on the + // addition above, LLVM can therefore deduce + // `full_size >= unrounded_size >= offset`, which proves `full_size > 0` + // for types with a non-zero-sized prefix (#152788). + let size_ge = bx.icmp(IntPredicate::IntUGE, full_size, unrounded_size); + bx.assume(size_ge); + (full_size, full_align) } _ => bug!("size_and_align_of_dst: {t} not supported"), diff --git a/tests/codegen-llvm/dst-vtable-align-nonzero.rs b/tests/codegen-llvm/dst-vtable-align-nonzero.rs index 2eee91876683c..1d677fb0124a4 100644 --- a/tests/codegen-llvm/dst-vtable-align-nonzero.rs +++ b/tests/codegen-llvm/dst-vtable-align-nonzero.rs @@ -30,9 +30,8 @@ pub struct Struct { pub fn eliminates_runtime_check_when_align_1( x: &Struct>, ) -> &WrapperWithAlign1 { - // CHECK: load [[USIZE:i[0-9]+]], {{.+}} !range [[RANGE_META:![0-9]+]] + // CHECK: load [[USIZE:i[0-9]+]] // CHECK-NOT: llvm.umax - // CHECK-NOT: icmp // CHECK-NOT: select // CHECK: ret &x.dst @@ -43,7 +42,7 @@ pub fn eliminates_runtime_check_when_align_1( pub fn does_not_eliminate_runtime_check_when_align_2( x: &Struct>, ) -> &WrapperWithAlign2 { - // CHECK: [[X0:%[0-9]+]] = load [[USIZE]], {{.+}} !range [[RANGE_META]] + // CHECK: load [[USIZE]] // CHECK: {{icmp|llvm.umax}} // CHECK: ret &x.dst @@ -52,7 +51,7 @@ pub fn does_not_eliminate_runtime_check_when_align_2( // CHECK-LABEL: @align_load_from_align_of_val #[no_mangle] pub fn align_load_from_align_of_val(x: &dyn Trait) -> usize { - // CHECK: {{%[0-9]+}} = load [[USIZE]], {{.+}} !range [[RANGE_META]] + // CHECK: {{%[0-9]+}} = load [[USIZE]], {{.+}} !range [[ALIGN_RANGE:![0-9]+]] core::mem::align_of_val(x) } @@ -60,8 +59,8 @@ pub fn align_load_from_align_of_val(x: &dyn Trait) -> usize { #[no_mangle] pub unsafe fn align_load_from_vtable_align_intrinsic(x: &dyn Trait) -> usize { let (data, vtable): (*const (), *const ()) = core::mem::transmute(x); - // CHECK: {{%[0-9]+}} = load [[USIZE]], {{.+}} !range [[RANGE_META]] + // CHECK: {{%[0-9]+}} = load [[USIZE]], {{.+}} !range [[ALIGN_RANGE]] core::intrinsics::vtable_align(vtable) } -// CHECK: [[RANGE_META]] = !{[[USIZE]] 1, [[USIZE]] [[#0x20000001]] +// CHECK: [[ALIGN_RANGE]] = !{[[USIZE]] 1, [[USIZE]] [[#0x20000001]] From 8339cfe8ea6608055b8cae626a781b09aaff794b Mon Sep 17 00:00:00 2001 From: Tony Kan Date: Sat, 21 Feb 2026 21:32:00 -0800 Subject: [PATCH 2/5] test(codegen): Add regression test for `size_of_val == 0` elimination on non-ZST DSTs --- tests/codegen-llvm/dst-size-of-val-not-zst.rs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 tests/codegen-llvm/dst-size-of-val-not-zst.rs diff --git a/tests/codegen-llvm/dst-size-of-val-not-zst.rs b/tests/codegen-llvm/dst-size-of-val-not-zst.rs new file mode 100644 index 0000000000000..9db959cb64d8f --- /dev/null +++ b/tests/codegen-llvm/dst-size-of-val-not-zst.rs @@ -0,0 +1,36 @@ +//@ compile-flags: -Copt-level=3 -Z merge-functions=disabled +//@ needs-deterministic-layouts + +#![crate_type = "lib"] + +// Regression test for #152788: `size_of_val(p) == 0` should optimize to `false` +// for types whose statically-known prefix makes them clearly not ZSTs. +// +// This works because: +// 1. The `offset + unsized_size` addition has NUW+NSW, so LLVM knows +// `unrounded_size >= offset` +// 2. An `llvm.assume` tells LLVM `aligned_size >= unrounded_size` +// 3. Together: `aligned_size >= unrounded_size >= offset > 0` + +pub struct Foo(pub [u32; 3], pub T); + +// CHECK-LABEL: @size_of_val_dyn_not_zero +#[no_mangle] +pub fn size_of_val_dyn_not_zero(p: &Foo) -> bool { + // CHECK: ret i1 false + std::mem::size_of_val(p) == 0 +} + +// CHECK-LABEL: @size_of_val_slice_u8_not_zero +#[no_mangle] +pub fn size_of_val_slice_u8_not_zero(p: &Foo<[u8]>) -> bool { + // CHECK: ret i1 false + std::mem::size_of_val(p) == 0 +} + +// CHECK-LABEL: @size_of_val_slice_i32_not_zero +#[no_mangle] +pub fn size_of_val_slice_i32_not_zero(p: &Foo<[i32]>) -> bool { + // CHECK: ret i1 false + std::mem::size_of_val(p) == 0 +} From 689cd64c048b6bd64801aa7736c59490ce362f9f Mon Sep 17 00:00:00 2001 From: Tony Kan Date: Sun, 22 Feb 2026 13:39:16 -0800 Subject: [PATCH 3/5] test(codegen): Restore original CHECK patterns in dst-vtable-align-nonzero Co-authored-by: Scott McMurray --- tests/codegen-llvm/dst-vtable-align-nonzero.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/codegen-llvm/dst-vtable-align-nonzero.rs b/tests/codegen-llvm/dst-vtable-align-nonzero.rs index 1d677fb0124a4..03f26693208a8 100644 --- a/tests/codegen-llvm/dst-vtable-align-nonzero.rs +++ b/tests/codegen-llvm/dst-vtable-align-nonzero.rs @@ -30,8 +30,9 @@ pub struct Struct { pub fn eliminates_runtime_check_when_align_1( x: &Struct>, ) -> &WrapperWithAlign1 { - // CHECK: load [[USIZE:i[0-9]+]] + // CHECK: load [[USIZE:i[0-9]+]], {{.+}} !range [[ALIGN_RANGE:![0-9]+]] // CHECK-NOT: llvm.umax + // CHECK-NOT: icmp // CHECK-NOT: select // CHECK: ret &x.dst @@ -42,7 +43,7 @@ pub fn eliminates_runtime_check_when_align_1( pub fn does_not_eliminate_runtime_check_when_align_2( x: &Struct>, ) -> &WrapperWithAlign2 { - // CHECK: load [[USIZE]] + // CHECK: [[X0:%[0-9]+]] = load [[USIZE]], {{.+}} !range [[ALIGN_RANGE]] // CHECK: {{icmp|llvm.umax}} // CHECK: ret &x.dst @@ -51,7 +52,7 @@ pub fn does_not_eliminate_runtime_check_when_align_2( // CHECK-LABEL: @align_load_from_align_of_val #[no_mangle] pub fn align_load_from_align_of_val(x: &dyn Trait) -> usize { - // CHECK: {{%[0-9]+}} = load [[USIZE]], {{.+}} !range [[ALIGN_RANGE:![0-9]+]] + // CHECK: {{%[0-9]+}} = load [[USIZE]], {{.+}} !range [[ALIGN_RANGE]] core::mem::align_of_val(x) } From 3e6f372ef648da6c2dbff3f83032c95eea6af9a3 Mon Sep 17 00:00:00 2001 From: Tony Kan Date: Sun, 22 Feb 2026 13:41:50 -0800 Subject: [PATCH 4/5] test(codegen): Revert unnecessary ALIGN_RANGE rename in dst-vtable-align-nonzero Co-authored-by: Scott McMurray --- tests/codegen-llvm/dst-vtable-align-nonzero.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/codegen-llvm/dst-vtable-align-nonzero.rs b/tests/codegen-llvm/dst-vtable-align-nonzero.rs index 03f26693208a8..2eee91876683c 100644 --- a/tests/codegen-llvm/dst-vtable-align-nonzero.rs +++ b/tests/codegen-llvm/dst-vtable-align-nonzero.rs @@ -30,7 +30,7 @@ pub struct Struct { pub fn eliminates_runtime_check_when_align_1( x: &Struct>, ) -> &WrapperWithAlign1 { - // CHECK: load [[USIZE:i[0-9]+]], {{.+}} !range [[ALIGN_RANGE:![0-9]+]] + // CHECK: load [[USIZE:i[0-9]+]], {{.+}} !range [[RANGE_META:![0-9]+]] // CHECK-NOT: llvm.umax // CHECK-NOT: icmp // CHECK-NOT: select @@ -43,7 +43,7 @@ pub fn eliminates_runtime_check_when_align_1( pub fn does_not_eliminate_runtime_check_when_align_2( x: &Struct>, ) -> &WrapperWithAlign2 { - // CHECK: [[X0:%[0-9]+]] = load [[USIZE]], {{.+}} !range [[ALIGN_RANGE]] + // CHECK: [[X0:%[0-9]+]] = load [[USIZE]], {{.+}} !range [[RANGE_META]] // CHECK: {{icmp|llvm.umax}} // CHECK: ret &x.dst @@ -52,7 +52,7 @@ pub fn does_not_eliminate_runtime_check_when_align_2( // CHECK-LABEL: @align_load_from_align_of_val #[no_mangle] pub fn align_load_from_align_of_val(x: &dyn Trait) -> usize { - // CHECK: {{%[0-9]+}} = load [[USIZE]], {{.+}} !range [[ALIGN_RANGE]] + // CHECK: {{%[0-9]+}} = load [[USIZE]], {{.+}} !range [[RANGE_META]] core::mem::align_of_val(x) } @@ -60,8 +60,8 @@ pub fn align_load_from_align_of_val(x: &dyn Trait) -> usize { #[no_mangle] pub unsafe fn align_load_from_vtable_align_intrinsic(x: &dyn Trait) -> usize { let (data, vtable): (*const (), *const ()) = core::mem::transmute(x); - // CHECK: {{%[0-9]+}} = load [[USIZE]], {{.+}} !range [[ALIGN_RANGE]] + // CHECK: {{%[0-9]+}} = load [[USIZE]], {{.+}} !range [[RANGE_META]] core::intrinsics::vtable_align(vtable) } -// CHECK: [[ALIGN_RANGE]] = !{[[USIZE]] 1, [[USIZE]] [[#0x20000001]] +// CHECK: [[RANGE_META]] = !{[[USIZE]] 1, [[USIZE]] [[#0x20000001]] From 45b1d74c8fa7236a64aa584e1237ecddb525047e Mon Sep 17 00:00:00 2001 From: Tony Kan Date: Sun, 22 Feb 2026 14:39:06 -0800 Subject: [PATCH 5/5] test(codegen): Fix dst-vtable-align-nonzero patterns for aarch64 Co-authored-by: Scott McMurray --- tests/codegen-llvm/dst-vtable-align-nonzero.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/codegen-llvm/dst-vtable-align-nonzero.rs b/tests/codegen-llvm/dst-vtable-align-nonzero.rs index 2eee91876683c..e9bb1b5616138 100644 --- a/tests/codegen-llvm/dst-vtable-align-nonzero.rs +++ b/tests/codegen-llvm/dst-vtable-align-nonzero.rs @@ -30,9 +30,8 @@ pub struct Struct { pub fn eliminates_runtime_check_when_align_1( x: &Struct>, ) -> &WrapperWithAlign1 { - // CHECK: load [[USIZE:i[0-9]+]], {{.+}} !range [[RANGE_META:![0-9]+]] + // CHECK: load [[USIZE:i[0-9]+]] // CHECK-NOT: llvm.umax - // CHECK-NOT: icmp // CHECK-NOT: select // CHECK: ret &x.dst @@ -43,7 +42,7 @@ pub fn eliminates_runtime_check_when_align_1( pub fn does_not_eliminate_runtime_check_when_align_2( x: &Struct>, ) -> &WrapperWithAlign2 { - // CHECK: [[X0:%[0-9]+]] = load [[USIZE]], {{.+}} !range [[RANGE_META]] + // CHECK: load [[USIZE]] // CHECK: {{icmp|llvm.umax}} // CHECK: ret &x.dst @@ -52,7 +51,7 @@ pub fn does_not_eliminate_runtime_check_when_align_2( // CHECK-LABEL: @align_load_from_align_of_val #[no_mangle] pub fn align_load_from_align_of_val(x: &dyn Trait) -> usize { - // CHECK: {{%[0-9]+}} = load [[USIZE]], {{.+}} !range [[RANGE_META]] + // CHECK: {{%[0-9]+}} = load [[USIZE]], {{.+}} !range [[RANGE_META:![0-9]+]] core::mem::align_of_val(x) }