From 46d86aa669860badb2b928cf0372906baaed362e Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 13 Dec 2023 19:53:44 +0100 Subject: [PATCH] fix computing the dynamic alignment of packed structs with dyn trait tails --- compiler/rustc_codegen_ssa/src/size_of_val.rs | 27 +++++++++---------- .../tests/pass/packed-struct-dyn-trait.rs | 21 +++++++++++++++ tests/ui/packed/dyn-trait.rs | 21 +++++++++++++++ 3 files changed, 54 insertions(+), 15 deletions(-) create mode 100644 src/tools/miri/tests/pass/packed-struct-dyn-trait.rs create mode 100644 tests/ui/packed/dyn-trait.rs diff --git a/compiler/rustc_codegen_ssa/src/size_of_val.rs b/compiler/rustc_codegen_ssa/src/size_of_val.rs index cb1975e402ac8..c160d6eb600e9 100644 --- a/compiler/rustc_codegen_ssa/src/size_of_val.rs +++ b/compiler/rustc_codegen_ssa/src/size_of_val.rs @@ -100,19 +100,18 @@ pub fn size_and_align_of_dst<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( // # First compute the dynamic alignment - // Packed type alignment would have to be capped, but their tails always have known alignment. - // Therefore, their alignment has already been taken into account when computing `sized_align` - // and `unsized_offset_unadjusted`, so no further adjustment is needed. - if let ty::Adt(def, _) = t.kind() { - if def.repr().packed() { - let unsized_tail = - bx.tcx().struct_tail_with_normalize(field_ty, |ty| ty, || {}); - assert!(matches!(unsized_tail.kind(), ty::Slice(..) | ty::Str)); - // Therefore we do not need to adjust anything. - // It's not actually correct to say that the unsized field has this alignment, but the - // code below works correctly if we set this and it avoids having to compute - // the actual alignment (which is `unsized_align.min(packed)`). - unsized_align = sized_align; + // For packed types, we need to cap the alignment. + if let ty::Adt(def, _) = t.kind() + && let Some(packed) = def.repr().pack + { + if packed.bytes() == 1 { + // We know this will be capped to 1. + unsized_align = bx.const_usize(1); + } else { + // We have to dynamically compute `min(unsized_align, packed)`. + let packed = bx.const_usize(packed.bytes()); + let cmp = bx.icmp(IntPredicate::IntULT, unsized_align, packed); + unsized_align = bx.select(cmp, unsized_align, packed); } } @@ -143,8 +142,6 @@ pub fn size_and_align_of_dst<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( // let full_size = (offset + unsized_size).align_to(unsized_align).align_to(full_align); // Furthermore, `align >= unsized_align`, and therefore we only need to do: // let full_size = (offset + unsized_size).align_to(full_align); - // This formula also has the advantage of working correctly when the type is packed - // and we set `unsized_align = sized_align`. let full_size = bx.add(unsized_offset_unadjusted, unsized_size); diff --git a/src/tools/miri/tests/pass/packed-struct-dyn-trait.rs b/src/tools/miri/tests/pass/packed-struct-dyn-trait.rs new file mode 100644 index 0000000000000..bb73c26c18a0d --- /dev/null +++ b/src/tools/miri/tests/pass/packed-struct-dyn-trait.rs @@ -0,0 +1,21 @@ +// run-pass +use std::ptr::addr_of; + +// When the unsized tail is a `dyn Trait`, its alignments is only dynamically known. This means the +// packed(2) needs to be applied at runtime: the actual alignment of the field is `min(2, +// usual_alignment)`. Here we check that we do this right by comparing size, alignment, and field +// offset before and after unsizing. +fn main() { + #[repr(C, packed(2))] + struct Packed(u8, core::mem::ManuallyDrop); + + let p = Packed(0, core::mem::ManuallyDrop::new(1)); + let p: &Packed = &p; + let sized = (core::mem::size_of_val(p), core::mem::align_of_val(p)); + let sized_offset = unsafe { addr_of!(p.1).cast::().offset_from(addr_of!(p.0)) }; + let p: &Packed = p; + let un_sized = (core::mem::size_of_val(p), core::mem::align_of_val(p)); + let un_sized_offset = unsafe { addr_of!(p.1).cast::().offset_from(addr_of!(p.0)) }; + assert_eq!(sized, un_sized); + assert_eq!(sized_offset, un_sized_offset); +} diff --git a/tests/ui/packed/dyn-trait.rs b/tests/ui/packed/dyn-trait.rs new file mode 100644 index 0000000000000..bb73c26c18a0d --- /dev/null +++ b/tests/ui/packed/dyn-trait.rs @@ -0,0 +1,21 @@ +// run-pass +use std::ptr::addr_of; + +// When the unsized tail is a `dyn Trait`, its alignments is only dynamically known. This means the +// packed(2) needs to be applied at runtime: the actual alignment of the field is `min(2, +// usual_alignment)`. Here we check that we do this right by comparing size, alignment, and field +// offset before and after unsizing. +fn main() { + #[repr(C, packed(2))] + struct Packed(u8, core::mem::ManuallyDrop); + + let p = Packed(0, core::mem::ManuallyDrop::new(1)); + let p: &Packed = &p; + let sized = (core::mem::size_of_val(p), core::mem::align_of_val(p)); + let sized_offset = unsafe { addr_of!(p.1).cast::().offset_from(addr_of!(p.0)) }; + let p: &Packed = p; + let un_sized = (core::mem::size_of_val(p), core::mem::align_of_val(p)); + let un_sized_offset = unsafe { addr_of!(p.1).cast::().offset_from(addr_of!(p.0)) }; + assert_eq!(sized, un_sized); + assert_eq!(sized_offset, un_sized_offset); +}