about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_codegen_ssa/src/size_of_val.rs62
-rw-r--r--compiler/rustc_const_eval/src/interpret/eval_context.rs43
-rw-r--r--src/tools/miri/tests/pass/packed-struct-dyn-trait.rs21
-rw-r--r--tests/ui/packed/dyn-trait.rs21
4 files changed, 99 insertions, 48 deletions
diff --git a/compiler/rustc_codegen_ssa/src/size_of_val.rs b/compiler/rustc_codegen_ssa/src/size_of_val.rs
index b8a4949d59f..087836ca37d 100644
--- a/compiler/rustc_codegen_ssa/src/size_of_val.rs
+++ b/compiler/rustc_codegen_ssa/src/size_of_val.rs
@@ -84,10 +84,13 @@ pub fn size_and_align_of_dst<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
             debug!("DST {} layout: {:?}", t, layout);
 
             let i = layout.fields.count() - 1;
-            let sized_size = layout.fields.offset(i).bytes();
+            let unsized_offset_unadjusted = layout.fields.offset(i).bytes();
             let sized_align = layout.align.abi.bytes();
-            debug!("DST {} statically sized prefix size: {} align: {}", t, sized_size, sized_align);
-            let sized_size = bx.const_usize(sized_size);
+            debug!(
+                "DST {} offset of dyn field: {}, statically sized align: {}",
+                t, unsized_offset_unadjusted, sized_align
+            );
+            let unsized_offset_unadjusted = bx.const_usize(unsized_offset_unadjusted);
             let sized_align = bx.const_usize(sized_align);
 
             // Recurse to get the size of the dynamically sized field (must be
@@ -95,26 +98,26 @@ pub fn size_and_align_of_dst<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
             let field_ty = layout.field(bx, i).ty;
             let (unsized_size, mut unsized_align) = size_and_align_of_dst(bx, field_ty, info);
 
-            // FIXME (#26403, #27023): We should be adding padding
-            // to `sized_size` (to accommodate the `unsized_align`
-            // required of the unsized field that follows) before
-            // summing it with `sized_size`. (Note that since #26403
-            // is unfixed, we do not yet add the necessary padding
-            // here. But this is where the add would go.)
-
-            // Return the sum of sizes and max of aligns.
-            let size = bx.add(sized_size, unsized_size);
-
-            // Packed types ignore the alignment of their fields.
-            if let ty::Adt(def, _) = t.kind() {
-                if def.repr().packed() {
-                    unsized_align = sized_align;
+            // # First compute the dynamic alignment
+
+            // 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);
                 }
             }
 
             // Choose max of two known alignments (combined value must
             // be aligned according to more restrictive of the two).
-            let align = match (
+            let full_align = match (
                 bx.const_to_opt_u128(sized_align, false),
                 bx.const_to_opt_u128(unsized_align, false),
             ) {
@@ -129,6 +132,19 @@ pub fn size_and_align_of_dst<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
                 }
             };
 
+            // # Then compute the dynamic size
+
+            // The full formula for the size would be:
+            // let unsized_offset_adjusted = unsized_offset_unadjusted.align_to(unsized_align);
+            // let full_size = (unsized_offset_adjusted + unsized_size).align_to(full_align);
+            // However, `unsized_size` is a multiple of `unsized_align`.
+            // Therefore, we can equivalently do the `align_to(unsized_align)` *after* adding `unsized_size`:
+            // let full_size = (unsized_offset_unadjusted + unsized_size).align_to(unsized_align).align_to(full_align);
+            // 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);
+
             // Issue #27023: must add any necessary padding to `size`
             // (to make it a multiple of `align`) before returning it.
             //
@@ -140,12 +156,12 @@ 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(align, one);
-            let add = bx.add(size, addend);
-            let neg = bx.neg(align);
-            let size = bx.and(add, neg);
+            let addend = bx.sub(full_align, one);
+            let add = bx.add(full_size, addend);
+            let neg = bx.neg(full_align);
+            let full_size = bx.and(add, neg);
 
-            (size, align)
+            (full_size, full_align)
         }
         _ => bug!("size_and_align_of_dst: {t} not supported"),
     }
diff --git a/compiler/rustc_const_eval/src/interpret/eval_context.rs b/compiler/rustc_const_eval/src/interpret/eval_context.rs
index bbebf329d26..847d6503f20 100644
--- a/compiler/rustc_const_eval/src/interpret/eval_context.rs
+++ b/compiler/rustc_const_eval/src/interpret/eval_context.rs
@@ -686,14 +686,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 assert!(layout.fields.count() > 0);
                 trace!("DST layout: {:?}", layout);
 
-                let sized_size = layout.fields.offset(layout.fields.count() - 1);
+                let unsized_offset_unadjusted = layout.fields.offset(layout.fields.count() - 1);
                 let sized_align = layout.align.abi;
-                trace!(
-                    "DST {} statically sized prefix size: {:?} align: {:?}",
-                    layout.ty,
-                    sized_size,
-                    sized_align
-                );
 
                 // Recurse to get the size of the dynamically sized field (must be
                 // the last field). Can't have foreign types here, how would we
@@ -707,36 +701,35 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                     return Ok(None);
                 };
 
-                // FIXME (#26403, #27023): We should be adding padding
-                // to `sized_size` (to accommodate the `unsized_align`
-                // required of the unsized field that follows) before
-                // summing it with `sized_size`. (Note that since #26403
-                // is unfixed, we do not yet add the necessary padding
-                // here. But this is where the add would go.)
-
-                // Return the sum of sizes and max of aligns.
-                let size = sized_size + unsized_size; // `Size` addition
+                // # First compute the dynamic alignment
 
-                // Packed types ignore the alignment of their fields.
+                // Packed type alignment needs to be capped.
                 if let ty::Adt(def, _) = layout.ty.kind() {
-                    if def.repr().packed() {
-                        unsized_align = sized_align;
+                    if let Some(packed) = def.repr().pack {
+                        unsized_align = unsized_align.min(packed);
                     }
                 }
 
                 // Choose max of two known alignments (combined value must
                 // be aligned according to more restrictive of the two).
-                let align = sized_align.max(unsized_align);
+                let full_align = sized_align.max(unsized_align);
+
+                // # Then compute the dynamic size
 
-                // Issue #27023: must add any necessary padding to `size`
-                // (to make it a multiple of `align`) before returning it.
-                let size = size.align_to(align);
+                let unsized_offset_adjusted = unsized_offset_unadjusted.align_to(unsized_align);
+                let full_size = (unsized_offset_adjusted + unsized_size).align_to(full_align);
+
+                // Just for our sanitiy's sake, assert that this is equal to what codegen would compute.
+                assert_eq!(
+                    full_size,
+                    (unsized_offset_unadjusted + unsized_size).align_to(full_align)
+                );
 
                 // Check if this brought us over the size limit.
-                if size > self.max_size_of_val() {
+                if full_size > self.max_size_of_val() {
                     throw_ub!(InvalidMeta(InvalidMetaKind::TooBig));
                 }
-                Ok(Some((size, align)))
+                Ok(Some((full_size, full_align)))
             }
             ty::Dynamic(_, _, ty::Dyn) => {
                 let vtable = metadata.unwrap_meta().to_pointer(self)?;
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 00000000000..bb73c26c18a
--- /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<T: ?Sized>(u8, core::mem::ManuallyDrop<T>);
+
+    let p = Packed(0, core::mem::ManuallyDrop::new(1));
+    let p: &Packed<usize> = &p;
+    let sized = (core::mem::size_of_val(p), core::mem::align_of_val(p));
+    let sized_offset = unsafe { addr_of!(p.1).cast::<u8>().offset_from(addr_of!(p.0)) };
+    let p: &Packed<dyn Send> = 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::<u8>().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 00000000000..bb73c26c18a
--- /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<T: ?Sized>(u8, core::mem::ManuallyDrop<T>);
+
+    let p = Packed(0, core::mem::ManuallyDrop::new(1));
+    let p: &Packed<usize> = &p;
+    let sized = (core::mem::size_of_val(p), core::mem::align_of_val(p));
+    let sized_offset = unsafe { addr_of!(p.1).cast::<u8>().offset_from(addr_of!(p.0)) };
+    let p: &Packed<dyn Send> = 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::<u8>().offset_from(addr_of!(p.0)) };
+    assert_eq!(sized, un_sized);
+    assert_eq!(sized_offset, un_sized_offset);
+}