@@ -604,6 +604,55 @@ impl DstLayout {
604604 DstLayout { _align : align, _size_info : size_info }
605605 }
606606
607+ /// Like `Layout::pad_to_align`, this routine rounds the size of this layout
608+ /// up to the nearest multiple of this type's alignment or `repr_packed`
609+ /// (whichever is less). This method leaves DST layouts unchanged, since the
610+ /// trailing padding of DSTs is computed at runtime.
611+ ///
612+ /// In order to match the layout of a `#[repr(C)]` struct, this method
613+ /// should be invoked after the invocations of [`DstLayout::extend`]. If
614+ /// `self` corresponds to a type marked with `repr(packed(N))`, then
615+ /// `repr_packed` should be set to `Some(N)`, otherwise `None`.
616+ ///
617+ /// This method cannot be used to match the layout of a record with the
618+ /// default representation, as that representation is mostly unspecified.
619+ ///
620+ /// # Safety
621+ ///
622+ /// If a (potentially hypothetical) valid `repr(C)` type begins with fields
623+ /// whose layout are `self` followed only by zero or more bytes of trailing
624+ /// padding (not included in `self`), then unsafe code may rely on
625+ /// `self.pad_to_align(repr_packed)` producing a layout that correctly
626+ /// encapsulates the layout of that type.
627+ ///
628+ /// We make no guarantees to the behavior of this method if `self` cannot
629+ /// appear in a valid Rust type (e.g., because the addition of trailing
630+ /// padding would lead to a size larger than `isize::MAX`).
631+ #[ allow( dead_code) ]
632+ pub ( crate ) const fn pad_to_align ( self ) -> Self {
633+ use util:: core_layout:: _padding_needed_for;
634+
635+ let size_info = match self . _size_info {
636+ // For sized layouts, we add the minimum amount of trailing padding
637+ // needed to satisfy alignment.
638+ SizeInfo :: Sized { _size : unpadded_size } => {
639+ let padding = _padding_needed_for ( unpadded_size, self . _align ) ;
640+ let size = match unpadded_size. checked_add ( padding) {
641+ Some ( size) => size,
642+ None => panic ! ( "Adding padding caused size to overflow `usize`." ) ,
643+ } ;
644+ SizeInfo :: Sized { _size : size }
645+ }
646+ // For DST layouts, trailing padding depends on the length of the
647+ // trailing DST and is computed at runtime. This does not alter the
648+ // offset or element size of the layout, so we leave `size_info`
649+ // unchanged.
650+ size_info @ SizeInfo :: SliceDst ( _) => size_info,
651+ } ;
652+
653+ DstLayout { _align : self . _align , _size_info : size_info }
654+ }
655+
607656 /// Validates that a cast is sound from a layout perspective.
608657 ///
609658 /// Validates that the size and alignment requirements of a type with the
@@ -4191,6 +4240,82 @@ mod tests {
41914240 }
41924241 }
41934242
4243+ /// Tests that calling `pad_to_align` on a sized `DstLayout` adds the
4244+ /// expected amount of trailing padding.
4245+ #[ test]
4246+ fn test_dst_layout_pad_to_align_with_sized ( ) {
4247+ // For all valid alignments `align`, construct a one-byte layout aligned
4248+ // to `align`, call `pad_to_align`, and assert that the size of the
4249+ // resulting layout is equal to `align`.
4250+ for align in ( 0 ..29 ) . map ( |p| NonZeroUsize :: new ( 2usize . pow ( p) ) . unwrap ( ) ) {
4251+ let layout = DstLayout { _align : align, _size_info : SizeInfo :: Sized { _size : 1 } } ;
4252+
4253+ assert_eq ! (
4254+ layout. pad_to_align( ) ,
4255+ DstLayout { _align: align, _size_info: SizeInfo :: Sized { _size: align. get( ) } }
4256+ ) ;
4257+ }
4258+
4259+ // Test explicitly-provided combinations of unpadded and padded
4260+ // counterparts.
4261+
4262+ macro_rules! test {
4263+ ( unpadded { size: $unpadded_size: expr, align: $unpadded_align: expr }
4264+ => padded { size: $padded_size: expr, align: $padded_align: expr } ) => {
4265+ let unpadded = DstLayout {
4266+ _align: NonZeroUsize :: new( $unpadded_align) . unwrap( ) ,
4267+ _size_info: SizeInfo :: Sized { _size: $unpadded_size } ,
4268+ } ;
4269+ let padded = unpadded. pad_to_align( ) ;
4270+
4271+ assert_eq!(
4272+ padded,
4273+ DstLayout {
4274+ _align: NonZeroUsize :: new( $padded_align) . unwrap( ) ,
4275+ _size_info: SizeInfo :: Sized { _size: $padded_size } ,
4276+ }
4277+ ) ;
4278+ } ;
4279+ }
4280+
4281+ test ! ( unpadded { size: 0 , align: 4 } => padded { size: 0 , align: 4 } ) ;
4282+ test ! ( unpadded { size: 1 , align: 4 } => padded { size: 4 , align: 4 } ) ;
4283+ test ! ( unpadded { size: 2 , align: 4 } => padded { size: 4 , align: 4 } ) ;
4284+ test ! ( unpadded { size: 3 , align: 4 } => padded { size: 4 , align: 4 } ) ;
4285+ test ! ( unpadded { size: 4 , align: 4 } => padded { size: 4 , align: 4 } ) ;
4286+ test ! ( unpadded { size: 5 , align: 4 } => padded { size: 8 , align: 4 } ) ;
4287+ test ! ( unpadded { size: 6 , align: 4 } => padded { size: 8 , align: 4 } ) ;
4288+ test ! ( unpadded { size: 7 , align: 4 } => padded { size: 8 , align: 4 } ) ;
4289+ test ! ( unpadded { size: 8 , align: 4 } => padded { size: 8 , align: 4 } ) ;
4290+
4291+ let current_max_align = DstLayout :: CURRENT_MAX_ALIGN . get ( ) ;
4292+
4293+ test ! ( unpadded { size: 1 , align: current_max_align }
4294+ => padded { size: current_max_align, align: current_max_align } ) ;
4295+
4296+ test ! ( unpadded { size: current_max_align + 1 , align: current_max_align }
4297+ => padded { size: current_max_align * 2 , align: current_max_align } ) ;
4298+ }
4299+
4300+ /// Tests that calling `pad_to_align` on a DST `DstLayout` is a no-op.
4301+ #[ test]
4302+ fn test_dst_layout_pad_to_align_with_dst ( ) {
4303+ for align in ( 0 ..29 ) . map ( |p| NonZeroUsize :: new ( 2usize . pow ( p) ) . unwrap ( ) ) {
4304+ for offset in 0 ..10 {
4305+ for elem_size in 0 ..10 {
4306+ let layout = DstLayout {
4307+ _align : align,
4308+ _size_info : SizeInfo :: SliceDst ( TrailingSliceLayout {
4309+ _offset : offset,
4310+ _elem_size : elem_size,
4311+ } ) ,
4312+ } ;
4313+ assert_eq ! ( layout. pad_to_align( ) , layout) ;
4314+ }
4315+ }
4316+ }
4317+ }
4318+
41944319 // This test takes a long time when running under Miri, so we skip it in
41954320 // that case. This is acceptable because this is a logic test that doesn't
41964321 // attempt to expose UB.
@@ -6178,4 +6303,40 @@ mod proofs {
61786303
61796304 let _ = base. extend ( field, packed) ;
61806305 }
6306+
6307+ #[ kani:: proof]
6308+ fn prove_dst_layout_pad_to_align ( ) {
6309+ use crate :: util:: core_layout:: _padding_needed_for;
6310+
6311+ let layout: DstLayout = kani:: any ( ) ;
6312+
6313+ let padded: DstLayout = layout. pad_to_align ( ) ;
6314+
6315+ // Calling `pad_to_align` does not alter the `DstLayout`'s alignment.
6316+ assert_eq ! ( padded. _align, layout. _align) ;
6317+
6318+ if let SizeInfo :: Sized { _size : unpadded_size } = layout. _size_info {
6319+ if let SizeInfo :: Sized { _size : padded_size } = padded. _size_info {
6320+ // If the layout is sized, it will remain sized after padding is
6321+ // added. Its sum will be its unpadded size and the size of the
6322+ // trailing padding needed to satisfy its alignment
6323+ // requirements.
6324+ let padding = _padding_needed_for ( unpadded_size, layout. _align ) ;
6325+ assert_eq ! ( padded_size, unpadded_size + padding) ;
6326+
6327+ // Prove that calling `DstLayout::pad_to_align` behaves
6328+ // identically to `Layout::pad_to_align`.
6329+ let layout_analog =
6330+ Layout :: from_size_align ( unpadded_size, layout. _align . get ( ) ) . unwrap ( ) ;
6331+ let padded_analog = layout_analog. pad_to_align ( ) ;
6332+ assert_eq ! ( padded_analog. align( ) , layout. _align. get( ) ) ;
6333+ assert_eq ! ( padded_analog. size( ) , padded_size) ;
6334+ } else {
6335+ panic ! ( "The padding of a sized layout must result in a sized layout." )
6336+ }
6337+ } else {
6338+ // If the layout is a DST, padding cannot be statically added.
6339+ assert_eq ! ( padded. _size_info, layout. _size_info) ;
6340+ }
6341+ }
61816342}
0 commit comments