263 lines
7.1 KiB
Rust
263 lines
7.1 KiB
Rust
use core::fmt::Debug;
|
|
use core::mem::size_of;
|
|
use std::boxed::ThinBox;
|
|
|
|
#[test]
|
|
fn want_niche_optimization() {
|
|
fn uses_niche<T: ?Sized>() -> bool {
|
|
size_of::<*const ()>() == size_of::<Option<ThinBox<T>>>()
|
|
}
|
|
|
|
trait Tr {}
|
|
assert!(uses_niche::<dyn Tr>());
|
|
assert!(uses_niche::<[i32]>());
|
|
assert!(uses_niche::<i32>());
|
|
}
|
|
|
|
#[test]
|
|
fn want_thin() {
|
|
fn is_thin<T: ?Sized>() -> bool {
|
|
size_of::<*const ()>() == size_of::<ThinBox<T>>()
|
|
}
|
|
|
|
trait Tr {}
|
|
assert!(is_thin::<dyn Tr>());
|
|
assert!(is_thin::<[i32]>());
|
|
assert!(is_thin::<i32>());
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
fn assert_covariance() {
|
|
fn thin_box<'new>(b: ThinBox<[&'static str]>) -> ThinBox<[&'new str]> {
|
|
b
|
|
}
|
|
}
|
|
|
|
#[track_caller]
|
|
fn verify_aligned<T>(ptr: *const T) {
|
|
// Use `black_box` to attempt to obscure the fact that we're calling this
|
|
// function on pointers that come from box/references, which the compiler
|
|
// would otherwise realize is impossible (because it would mean we've
|
|
// already executed UB).
|
|
//
|
|
// That is, we'd *like* it to be possible for the asserts in this function
|
|
// to detect brokenness in the ThinBox impl.
|
|
//
|
|
// It would probably be better if we instead had these as debug_asserts
|
|
// inside `ThinBox`, prior to the point where we do the UB. Anyway, in
|
|
// practice these checks are mostly just smoke-detectors for an extremely
|
|
// broken `ThinBox` impl, since it's an extremely subtle piece of code.
|
|
let ptr = core::hint::black_box(ptr);
|
|
assert!(
|
|
ptr.is_aligned() && !ptr.is_null(),
|
|
"misaligned ThinBox data; valid pointers to `{ty}` should be aligned to {align}: {ptr:p}",
|
|
ty = core::any::type_name::<T>(),
|
|
align = core::mem::align_of::<T>(),
|
|
);
|
|
}
|
|
|
|
#[track_caller]
|
|
fn check_thin_sized<T: Debug + PartialEq + Clone>(make: impl FnOnce() -> T) {
|
|
let value = make();
|
|
let boxed = ThinBox::new(value.clone());
|
|
let val = &*boxed;
|
|
verify_aligned(val as *const T);
|
|
assert_eq!(val, &value);
|
|
}
|
|
|
|
#[track_caller]
|
|
fn check_thin_dyn<T: Debug + PartialEq + Clone>(make: impl FnOnce() -> T) {
|
|
let value = make();
|
|
let wanted_debug = format!("{value:?}");
|
|
let boxed: ThinBox<dyn Debug> = ThinBox::new_unsize(value.clone());
|
|
let val = &*boxed;
|
|
// wide reference -> wide pointer -> thin pointer
|
|
verify_aligned(val as *const dyn Debug as *const T);
|
|
let got_debug = format!("{val:?}");
|
|
assert_eq!(wanted_debug, got_debug);
|
|
}
|
|
|
|
macro_rules! define_test {
|
|
(
|
|
@test_name: $testname:ident;
|
|
|
|
$(#[$m:meta])*
|
|
struct $Type:ident($inner:ty);
|
|
|
|
$($test_stmts:tt)*
|
|
) => {
|
|
#[test]
|
|
fn $testname() {
|
|
use core::sync::atomic::{AtomicIsize, Ordering};
|
|
// Define the type, and implement new/clone/drop in such a way that
|
|
// the number of live instances will be counted.
|
|
$(#[$m])*
|
|
#[derive(Debug, PartialEq)]
|
|
struct $Type {
|
|
_priv: $inner,
|
|
}
|
|
|
|
impl Clone for $Type {
|
|
fn clone(&self) -> Self {
|
|
verify_aligned(self);
|
|
Self::new(self._priv.clone())
|
|
}
|
|
}
|
|
|
|
impl Drop for $Type {
|
|
fn drop(&mut self) {
|
|
verify_aligned(self);
|
|
Self::modify_live(-1);
|
|
}
|
|
}
|
|
|
|
impl $Type {
|
|
fn new(i: $inner) -> Self {
|
|
Self::modify_live(1);
|
|
Self { _priv: i }
|
|
}
|
|
|
|
fn modify_live(n: isize) -> isize {
|
|
static COUNTER: AtomicIsize = AtomicIsize::new(0);
|
|
COUNTER.fetch_add(n, Ordering::Relaxed) + n
|
|
}
|
|
|
|
fn live_objects() -> isize {
|
|
Self::modify_live(0)
|
|
}
|
|
}
|
|
// Run the test statements
|
|
let _: () = { $($test_stmts)* };
|
|
// Check that we didn't leak anything, or call drop too many times.
|
|
assert_eq!(
|
|
$Type::live_objects(), 0,
|
|
"Wrong number of drops of {}, `initializations - drops` should be 0.",
|
|
stringify!($Type),
|
|
);
|
|
}
|
|
};
|
|
}
|
|
|
|
define_test! {
|
|
@test_name: align1zst;
|
|
struct Align1Zst(());
|
|
|
|
check_thin_sized(|| Align1Zst::new(()));
|
|
check_thin_dyn(|| Align1Zst::new(()));
|
|
}
|
|
|
|
define_test! {
|
|
@test_name: align1small;
|
|
struct Align1Small(u8);
|
|
|
|
check_thin_sized(|| Align1Small::new(50));
|
|
check_thin_dyn(|| Align1Small::new(50));
|
|
}
|
|
|
|
define_test! {
|
|
@test_name: align1_size_not_pow2;
|
|
struct Align64NotPow2Size([u8; 79]);
|
|
|
|
check_thin_sized(|| Align64NotPow2Size::new([100; 79]));
|
|
check_thin_dyn(|| Align64NotPow2Size::new([100; 79]));
|
|
}
|
|
|
|
define_test! {
|
|
@test_name: align1big;
|
|
struct Align1Big([u8; 256]);
|
|
|
|
check_thin_sized(|| Align1Big::new([5u8; 256]));
|
|
check_thin_dyn(|| Align1Big::new([5u8; 256]));
|
|
}
|
|
|
|
// Note: `#[repr(align(2))]` is worth testing because
|
|
// - can have pointers which are misaligned, unlike align(1)
|
|
// - is still expected to have an alignment less than the alignment of a vtable.
|
|
define_test! {
|
|
@test_name: align2zst;
|
|
#[repr(align(2))]
|
|
struct Align2Zst(());
|
|
|
|
check_thin_sized(|| Align2Zst::new(()));
|
|
check_thin_dyn(|| Align2Zst::new(()));
|
|
}
|
|
|
|
define_test! {
|
|
@test_name: align2small;
|
|
#[repr(align(2))]
|
|
struct Align2Small(u8);
|
|
|
|
check_thin_sized(|| Align2Small::new(60));
|
|
check_thin_dyn(|| Align2Small::new(60));
|
|
}
|
|
|
|
define_test! {
|
|
@test_name: align2full;
|
|
#[repr(align(2))]
|
|
struct Align2Full([u8; 2]);
|
|
check_thin_sized(|| Align2Full::new([3u8; 2]));
|
|
check_thin_dyn(|| Align2Full::new([3u8; 2]));
|
|
}
|
|
|
|
define_test! {
|
|
@test_name: align2_size_not_pow2;
|
|
#[repr(align(2))]
|
|
struct Align2NotPower2Size([u8; 6]);
|
|
|
|
check_thin_sized(|| Align2NotPower2Size::new([3; 6]));
|
|
check_thin_dyn(|| Align2NotPower2Size::new([3; 6]));
|
|
}
|
|
|
|
define_test! {
|
|
@test_name: align2big;
|
|
#[repr(align(2))]
|
|
struct Align2Big([u8; 256]);
|
|
|
|
check_thin_sized(|| Align2Big::new([5u8; 256]));
|
|
check_thin_dyn(|| Align2Big::new([5u8; 256]));
|
|
}
|
|
|
|
define_test! {
|
|
@test_name: align64zst;
|
|
#[repr(align(64))]
|
|
struct Align64Zst(());
|
|
|
|
check_thin_sized(|| Align64Zst::new(()));
|
|
check_thin_dyn(|| Align64Zst::new(()));
|
|
}
|
|
|
|
define_test! {
|
|
@test_name: align64small;
|
|
#[repr(align(64))]
|
|
struct Align64Small(u8);
|
|
|
|
check_thin_sized(|| Align64Small::new(50));
|
|
check_thin_dyn(|| Align64Small::new(50));
|
|
}
|
|
|
|
define_test! {
|
|
@test_name: align64med;
|
|
#[repr(align(64))]
|
|
struct Align64Med([u8; 64]);
|
|
check_thin_sized(|| Align64Med::new([10; 64]));
|
|
check_thin_dyn(|| Align64Med::new([10; 64]));
|
|
}
|
|
|
|
define_test! {
|
|
@test_name: align64_size_not_pow2;
|
|
#[repr(align(64))]
|
|
struct Align64NotPow2Size([u8; 192]);
|
|
|
|
check_thin_sized(|| Align64NotPow2Size::new([10; 192]));
|
|
check_thin_dyn(|| Align64NotPow2Size::new([10; 192]));
|
|
}
|
|
|
|
define_test! {
|
|
@test_name: align64big;
|
|
#[repr(align(64))]
|
|
struct Align64Big([u8; 256]);
|
|
|
|
check_thin_sized(|| Align64Big::new([10; 256]));
|
|
check_thin_dyn(|| Align64Big::new([10; 256]));
|
|
}
|