From 2c6804e0f3cdd9166e2e8b97f96371e4b5285cdc Mon Sep 17 00:00:00 2001 From: Scott McMurray Date: Thu, 9 Jul 2026 11:22:50 -0700 Subject: [PATCH 1/2] Allow `BackendRepr::Scalar` for `repr(Rust, packed)` [MCP1007] --- compiler/rustc_abi/src/layout.rs | 13 ++-- .../rustc_ty_utils/src/layout/invariant.rs | 21 +++--- tests/codegen-llvm/packed-scalars.rs | 40 +++++++++++ tests/codegen-llvm/read_write_unaligned.rs | 11 +-- tests/ui/layout/struct.rs | 8 +++ tests/ui/layout/struct.stderr | 68 ++++++++++++++++++- .../layout/zero-sized-array-enum-niche.stderr | 12 +++- 7 files changed, 152 insertions(+), 21 deletions(-) create mode 100644 tests/codegen-llvm/packed-scalars.rs diff --git a/compiler/rustc_abi/src/layout.rs b/compiler/rustc_abi/src/layout.rs index 2218780092287..89255196e8a08 100644 --- a/compiler/rustc_abi/src/layout.rs +++ b/compiler/rustc_abi/src/layout.rs @@ -1351,18 +1351,23 @@ impl LayoutCalculator { layout_of_single_non_zst_field = Some(field); // Field fills the struct and it has a scalar or scalar pair ABI. - if offsets[i].bytes() == 0 && align == field.align.abi && size == field.size { + // [MCP1007] For now only scalars allow arbitrary alignment, + // but future work is expected to allow that for others too. + if offsets[i].bytes() == 0 && size == field.size { match field.backend_repr { // For plain scalars, or vectors of them, we can't unpack // newtypes for `#[repr(C)]`, as that affects C ABIs. - BackendRepr::Scalar(_) | BackendRepr::SimdVector { .. } - if optimize_abi => + BackendRepr::Scalar(_) if optimize_abi => { + abi = field.backend_repr; + } + BackendRepr::SimdVector { .. } + if optimize_abi && align == field.align.abi => { abi = field.backend_repr; } // But scalar pairs are Rust-specific and get // treated as aggregates by C ABIs anyway. - BackendRepr::ScalarPair { .. } => { + BackendRepr::ScalarPair { .. } if align == field.align.abi => { abi = field.backend_repr; } _ => {} diff --git a/compiler/rustc_ty_utils/src/layout/invariant.rs b/compiler/rustc_ty_utils/src/layout/invariant.rs index 70e980490f36b..c04a66019a5ce 100644 --- a/compiler/rustc_ty_utils/src/layout/invariant.rs +++ b/compiler/rustc_ty_utils/src/layout/invariant.rs @@ -83,16 +83,8 @@ pub(super) fn layout_sanity_check<'tcx>(cx: &LayoutCx<'tcx>, layout: &TyAndLayou } fn check_layout_abi<'tcx>(cx: &LayoutCx<'tcx>, layout: &TyAndLayout<'tcx>) { - // Verify the ABI-mandated alignment and size for scalars. - let align = layout.backend_repr.scalar_platform_align(cx); + // Verify the size expectation for scalars. let size = layout.backend_repr.scalar_size(cx); - if let Some(align) = align { - assert_eq!( - layout.layout.align().abi, - align, - "alignment mismatch between ABI and layout in {layout:#?}" - ); - } if let Some(size) = size { assert_eq!( layout.layout.size(), @@ -102,6 +94,7 @@ pub(super) fn layout_sanity_check<'tcx>(cx: &LayoutCx<'tcx>, layout: &TyAndLayou } // Verify per-ABI invariants + let align = layout.backend_repr.scalar_platform_align(cx); match layout.layout.backend_repr() { BackendRepr::Scalar(_) => { // These must always be present for `Scalar` types. @@ -159,6 +152,9 @@ pub(super) fn layout_sanity_check<'tcx>(cx: &LayoutCx<'tcx>, layout: &TyAndLayou } } BackendRepr::ScalarPair { a: scalar1, b: scalar2, b_offset } => { + // These must always be present for `ScalarPair` types. + let align = align.unwrap(); + let _size = size.unwrap(); // Check that the underlying pair of fields matches. let inner = skip_newtypes(cx, layout); assert!( @@ -167,6 +163,13 @@ pub(super) fn layout_sanity_check<'tcx>(cx: &LayoutCx<'tcx>, layout: &TyAndLayou layout.ty, inner.ty ); + // We currently require that a ScalarPair's alignment match the expectations + // of the platform ABI for its scalars, though this may change soon [MCP1007]. + assert_eq!( + layout.layout.align().abi, + align, + "alignment mismatch between ABI and layout in {layout:#?}" + ); // `a` is at memory offset zero, so to keep them from overlapping the offset // to `b` must be at least as much as the size of `a`. assert!( diff --git a/tests/codegen-llvm/packed-scalars.rs b/tests/codegen-llvm/packed-scalars.rs new file mode 100644 index 0000000000000..dd899363b9678 --- /dev/null +++ b/tests/codegen-llvm/packed-scalars.rs @@ -0,0 +1,40 @@ +//@ revisions: OPT DBG +//@ compile-flags: -C no-prepopulate-passes +//@ [DBG] compile-flags: -C opt-level=0 +//@ [OPT] compile-flags: -C opt-level=3 + +#![crate_type = "lib"] + +#[derive(Copy, Clone)] +#[repr(Rust, packed(2))] +pub struct PackedScalar(u64); + +// CHECK-LABEL: @read_packed_scalar +#[no_mangle] +pub unsafe fn read_packed_scalar(ptr: *const PackedScalar) -> PackedScalar { + // CHECK: start + // CHECK-NEXT: [[TEMP:%.+]] = load i64, ptr %ptr, align 2 + // OPT-SAME: !noundef + // CHECK-NEXT: ret i64 [[TEMP]] + *ptr +} + +// CHECK-LABEL: @write_packed_scalar +#[no_mangle] +pub unsafe fn write_packed_scalar(ptr: *mut PackedScalar, val: PackedScalar) { + // CHECK: start + // CHECK-NEXT: store i64 %val, ptr %ptr, align 2 + // CHECK-NEXT: ret void + *ptr = val; +} + +// CHECK-LABEL: @copy_packed_scalar +#[no_mangle] +pub unsafe fn copy_packed_scalar(dst: *mut PackedScalar, src: *const PackedScalar) { + // CHECK: start + // CHECK-NEXT: [[TEMP:%.+]] = load i64, ptr %src, align 2 + // OPT-SAME: !noundef + // CHECK-NEXT: store i64 [[TEMP]], ptr %dst, align 2 + // CHECK-NEXT: ret void + *dst = *src; +} diff --git a/tests/codegen-llvm/read_write_unaligned.rs b/tests/codegen-llvm/read_write_unaligned.rs index beba4400dc54a..90bce95d67ee1 100644 --- a/tests/codegen-llvm/read_write_unaligned.rs +++ b/tests/codegen-llvm/read_write_unaligned.rs @@ -22,8 +22,8 @@ unsafe fn read_unaligned_ptr(ptr: *const NonNull) -> NonNull { unsafe fn read_unaligned_i16(ptr: *const NonZero) -> NonZero { // CHECK: start: // CHECK-NEXT: [[TEMP:%.+]] = load i16, ptr %ptr, align 1 - // CHECK-NOT: !noundef - // CHECK-NOT: !range + // CHECK-SAME: !range [[R16:![0-9]+]] + // CHECK-SAME: !noundef // CHECK-NEXT: ret i16 [[TEMP]] ptr.read_unaligned() } @@ -33,8 +33,8 @@ unsafe fn read_unaligned_i16(ptr: *const NonZero) -> NonZero { unsafe fn typed_copy_unaligned_i32(src: *const NonZero, dst: *mut NonZero) { // CHECK: start: // CHECK-NEXT: [[TEMP:%.+]] = load i32, ptr %src, align 1 - // CHECK-NOT: !noundef - // CHECK-NOT: !range + // CHECK-SAME: !range [[R32:![0-9]+]] + // CHECK-SAME: !noundef // CHECK-NEXT: store i32 [[TEMP]], ptr %dst, align 1 // CHECK-NEXT: ret void dst.write_unaligned(src.read_unaligned()) @@ -69,3 +69,6 @@ unsafe fn write_unaligned_huge(ptr: *mut HugeBuffer, val: HugeBuffer) { // CHECK-NEXT: ret void ptr.write_unaligned(val) } + +// CHECK: [[R16]] = !{i16 1, i16 0} +// CHECK: [[R32]] = !{i32 1, i32 0} diff --git a/tests/ui/layout/struct.rs b/tests/ui/layout/struct.rs index fc218d9fecbd0..859ab7a42f3bd 100644 --- a/tests/ui/layout/struct.rs +++ b/tests/ui/layout/struct.rs @@ -10,3 +10,11 @@ struct AlignedZstPreventsScalar(i16, [i32; 0]); //~ERROR: backend_repr: Memory #[rustc_dump_layout(backend_repr)] struct AlignedZstButStillScalar(i32, [i16; 0]); //~ERROR: backend_repr: Scalar + +#[rustc_dump_layout(debug)] +#[repr(Rust, packed(2))] +struct Packed2RustIsScalar(u32); //~ ERROR: layout_of + +#[rustc_dump_layout(debug)] +#[repr(C, packed(2))] +struct Packed2CIsMemory(u32); //~ ERROR: layout_of diff --git a/tests/ui/layout/struct.stderr b/tests/ui/layout/struct.stderr index 3fb18f767ca69..3355276cae411 100644 --- a/tests/ui/layout/struct.stderr +++ b/tests/ui/layout/struct.stderr @@ -10,5 +10,71 @@ error: backend_repr: Scalar(Initialized { value: Int(I32, true), valid_range: 0. LL | struct AlignedZstButStillScalar(i32, [i16; 0]); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 2 previous errors +error: layout_of(Packed2RustIsScalar) = Layout { + size: Size(4 bytes), + align: AbiAlign { + abi: Align(2 bytes), + }, + backend_repr: Scalar( + Initialized { + value: Int( + I32, + false, + ), + valid_range: 0..=4294967295, + }, + ), + fields: Arbitrary { + offsets: [ + Size(0 bytes), + ], + in_memory_order: [ + 0, + ], + }, + largest_niche: None, + uninhabited: false, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: Align(2 bytes), + randomization_seed: 5197957715782000817, + } + --> $DIR/struct.rs:16:1 + | +LL | struct Packed2RustIsScalar(u32); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: layout_of(Packed2CIsMemory) = Layout { + size: Size(4 bytes), + align: AbiAlign { + abi: Align(2 bytes), + }, + backend_repr: Memory { + sized: true, + }, + fields: Arbitrary { + offsets: [ + Size(0 bytes), + ], + in_memory_order: [ + 0, + ], + }, + largest_niche: None, + uninhabited: false, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: Align(2 bytes), + randomization_seed: 12081895529137334233, + } + --> $DIR/struct.rs:20:1 + | +LL | struct Packed2CIsMemory(u32); + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 4 previous errors diff --git a/tests/ui/layout/zero-sized-array-enum-niche.stderr b/tests/ui/layout/zero-sized-array-enum-niche.stderr index 6618f162853e5..035e4e53fd130 100644 --- a/tests/ui/layout/zero-sized-array-enum-niche.stderr +++ b/tests/ui/layout/zero-sized-array-enum-niche.stderr @@ -328,9 +328,15 @@ error: layout_of(Result<[u32; 0], Packed>) = Layout { }, VariantLayout { size: Size(2 bytes), - backend_repr: Memory { - sized: true, - }, + backend_repr: Scalar( + Initialized { + value: Int( + I16, + false, + ), + valid_range: 0..=0, + }, + ), field_offsets: [ Size(0 bytes), ], From 59fd279cac080fd9448fa0233ae296d502a30d3f Mon Sep 17 00:00:00 2001 From: scottmcm Date: Mon, 13 Jul 2026 14:21:20 +0000 Subject: [PATCH 2/2] Update compiler/rustc_ty_utils/src/layout/invariant.rs Co-authored-by: Ralf Jung --- compiler/rustc_ty_utils/src/layout/invariant.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/rustc_ty_utils/src/layout/invariant.rs b/compiler/rustc_ty_utils/src/layout/invariant.rs index c04a66019a5ce..3a027d4062222 100644 --- a/compiler/rustc_ty_utils/src/layout/invariant.rs +++ b/compiler/rustc_ty_utils/src/layout/invariant.rs @@ -84,6 +84,9 @@ pub(super) fn layout_sanity_check<'tcx>(cx: &LayoutCx<'tcx>, layout: &TyAndLayou fn check_layout_abi<'tcx>(cx: &LayoutCx<'tcx>, layout: &TyAndLayout<'tcx>) { // Verify the size expectation for scalars. + // Note that there is no alignment requirement, except for the + // implicit requirement that `align <= scalar_size` because otherwise + // the size check would fail due to the padding. let size = layout.backend_repr.scalar_size(cx); if let Some(size) = size { assert_eq!(