Skip to content

Commit fe7a54b

Browse files
authored
Add DstLayout::pad_to_align (#638)
This method is comparable to `Layout::pad_to_align`, but also handles dynamically sized types. Makes progress towards #29.
1 parent 2fa7cfc commit fe7a54b

File tree

1 file changed

+161
-0
lines changed

1 file changed

+161
-0
lines changed

src/lib.rs

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)