Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions compiler/rustc_abi/src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1351,18 +1351,23 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
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;
}
_ => {}
Expand Down
24 changes: 15 additions & 9 deletions compiler/rustc_ty_utils/src/layout/invariant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,11 @@ 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.
Comment thread
scottmcm marked this conversation as resolved.
// 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(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(),
Expand All @@ -102,6 +97,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.
Expand Down Expand Up @@ -159,6 +155,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!(
Expand All @@ -167,6 +166,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!(
Expand Down
40 changes: 40 additions & 0 deletions tests/codegen-llvm/packed-scalars.rs
Original file line number Diff line number Diff line change
@@ -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;
}
Comment on lines +24 to +29

@scottmcm scottmcm Jul 9, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compare what we emit today: https://rust.godbolt.org/z/8Pv61EhbK

This one, for example, emits 2 allocas and 2 memcpys without this PR:

define void @write_packed_scalar(ptr noundef %ptr, i64 noundef %0) unnamed_addr {
start:
  %1 = alloca [8 x i8], align 8
  %val = alloca [8 x i8], align 2
  call void @llvm.lifetime.start.p0(ptr %1)
  store i64 %0, ptr %1, align 8
  call void @llvm.memcpy.p0.p0.i64(ptr align 2 %val, ptr align 8 %1, i64 8, i1 false)
  call void @llvm.lifetime.end.p0(ptr %1)
  call void @llvm.memcpy.p0.p0.i64(ptr align 2 %ptr, ptr align 2 %val, i64 8, i1 false)
  ret void
}

(which LLVM of course cleans up even in -O1, but still nice to just not emit that in the first place.)

View changes since the review


// 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;
}
11 changes: 7 additions & 4 deletions tests/codegen-llvm/read_write_unaligned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ unsafe fn read_unaligned_ptr(ptr: *const NonNull<i16>) -> NonNull<i16> {
unsafe fn read_unaligned_i16(ptr: *const NonZero<i16>) -> NonZero<i16> {
// 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()
}
Expand All @@ -33,8 +33,8 @@ unsafe fn read_unaligned_i16(ptr: *const NonZero<i16>) -> NonZero<i16> {
unsafe fn typed_copy_unaligned_i32(src: *const NonZero<i32>, dst: *mut NonZero<i32>) {
// 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())
Expand Down Expand Up @@ -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}
8 changes: 8 additions & 0 deletions tests/ui/layout/struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
68 changes: 67 additions & 1 deletion tests/ui/layout/struct.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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

12 changes: 9 additions & 3 deletions tests/ui/layout/zero-sized-array-enum-niche.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -328,9 +328,15 @@ error: layout_of(Result<[u32; 0], Packed<U16IsZero>>) = 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),
],
Expand Down
Loading