1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
//! Functions for semi-safely casting various things to each other.
//!
//! We mostly use this for slices of geometry and instance data, since you want
//! to be able to write simple Rust `struct`s to manage your shader data, which
//! we then read out as [`f32`] slices or [`Vec`]s.
//!
//! Given a `struct` like
//!
//! ```
//! #[repr(C)]
//! struct MyStruct(u32, u32);
//! ```
//!
//! the following casts are supported and safe:
//!
//! 1. `cast_slice::<MyStruct, u32>(..)`
//! 2. `cast_slice::<u32, MyStruct>(..)` if the slice has the right `len`
//! 3. `cast_slice::<MyStruct, u8>(..)`
//! 4. `cast_vec::<MyStruct, u32>(..)`
//! 5. `cast_vec::<u32, MyStruct>(..)` if the Vec has the right `len` and `capacity`
//!
//! The following casts cause a panic:
//! 1. `cast_slice::<u8, MyStruct>(..)` "from" alignment is not a multiple of "to" alignment
//! 2. `cast_slice::<MyStruct, u64>(..)` "from" alignment is not a multiple of "to" alignment
//! 3. `cast_vec::<MyStruct, u64>(..)` "from" alignment is different from "to" alignment
//! 4. `cast_vec::<MyStruct, u8>(..)` "from" alignment is different from "to" alignment
//!
//! The following casts are unsafe but DON'T cause a panic. Please just don't do them.
//! 1. `cast_slice::<u32, Option<u32>>` or any other data structure that can't be arbitrarily initialized
//! 2. `cast_slice::<u32, (u32, u32)>` doesn't use `#[repr(C)]`, so might behave unexpectedly
//! 3. `cast_vec` with a custom allocator (requires the unstable
//!    [allocator_api feature](https://doc.rust-lang.org/beta/unstable-book/library-features/allocator-api.html))
//!
//!
//! TODO(JP): We should check that you're casting to a struct that can be initialized with
//! arbitrary byte data. Ideally Rust should implement something like an
//! [`Arbitrary` trait](https://github.com/rust-lang/rfcs/issues/2626#issuecomment-633385808).
//! We currently use `'static + Copy` as a proxy for such a trait, since at least this disallows
//! anything that implements `Drop` or has (non-static) references. We should also update
//! places like [`crate::Area::get_slice`] when improving this.
//!
//! Even better could be to add a custom `derive` macro to make sure that a struct only contains
//! a certain type of scalar, e.g. `#[derive(OnlyScalars<f32>)]` for a typical geometry or instance
//! struct. Then we can prevent a bunch of runtime panics at compile time.
//!
//! We can also use something like <https://docs.rs/repr-trait/latest/repr_trait/trait.C.html>
//! to make sure the data is properly represented using `#[repr(C)]`.
//!
//! See:
//! - <https://users.rust-lang.org/t/why-does-vec-from-raw-parts-require-same-size-and-not-same-size-capacity/73036>
//! - <https://github.com/nabijaczleweli/safe-transmute-rs/issues/16#issuecomment-471066699>
//! - <https://rawcdn.githack.com/nabijaczleweli/safe-transmute-rs/doc/safe_transmute/fn.transmute_vec.html>
//! - <https://github.com/rust-lang/rust/blob/190feb65290d39d7ab6d44e994bd99188d339f16/src/libstd/sys/windows/alloc.rs#L46-L57>
//! - <https://github.com/rust-lang/rust/blob/b6f580acc0ce233d5c4d1f9680d354fded88b824/library/std/src/sys/common/alloc.rs#L30>

/// Cast a slice from one type to another.
///
/// See [`crate::cast`] for more information.
pub fn cast_slice<FROM: 'static + Copy, TO: 'static + Copy>(slice: &[FROM]) -> &[TO] {
    assert_eq!(
        std::mem::align_of::<FROM>() % std::mem::align_of::<TO>(),
        0,
        "cast_slice: Alignment of FROM must be equal to -- or a multiple of -- the alignment of TO"
    );
    if std::mem::size_of::<FROM>() >= std::mem::size_of::<TO>() {
        // E.g. going from `(f32,f32,f32)` to `f32`.
        assert_eq!(
            std::mem::size_of::<FROM>() % std::mem::size_of::<TO>(),
            0,
            "cast_slice: Size of FROM must be equal to -- or a multiple of -- the size of TO"
        );
        unsafe {
            std::slice::from_raw_parts(
                slice.as_ptr() as *const TO,
                slice.len() * (std::mem::size_of::<FROM>() / std::mem::size_of::<TO>()),
            )
        }
    } else {
        // E.g. going from `f32` to `(f32,f32,f32)`.
        assert_eq!(
            std::mem::size_of::<TO>() % std::mem::size_of::<FROM>(),
            0,
            "cast_slice: Size of TO must be equal to -- or a multiple of -- the size of FROM"
        );
        let factor = std::mem::size_of::<TO>() / std::mem::size_of::<FROM>();
        assert_eq!(slice.len() % factor, 0, "cast_slice: slice.len() must be able to precisely fit TO without a remainder");
        unsafe { std::slice::from_raw_parts(slice.as_ptr() as *const TO, slice.len() / factor) }
    }
}

/// Cast a [`Vec`] from one type to another.
///
/// See [`crate::cast`] for more information
pub fn cast_vec<FROM: 'static + Copy, TO: 'static + Copy>(vec: Vec<FROM>) -> Vec<TO> {
    // We have to be careful when casting [`Vec`]s. The
    // reason for this is that when the new, casted [`Vec`] gets dropped, then
    // [`std::alloc::GlobalAlloc::dealloc`] function will get called with a different
    // [`std::alloc::Layout`], which can cause memory corruption. This means that we
    // need the exact same `align`, and we need to make sure that `size*capacity` also
    // remains the same.
    //
    // We might want to consider writing our own allocator that doesn't care about
    // different layouts. This is maybe a bit crazy, but might enable simpler code
    // in some places. However, for now these casts will suffice.
    // See https://github.com/Zaplib/zaplib/issues/103
    assert_eq!(std::mem::align_of::<FROM>(), std::mem::align_of::<TO>(), "cast_vec: Alignments of both types must be the same");

    let mut vec = std::mem::ManuallyDrop::new(vec);

    let new_vec = if std::mem::size_of::<FROM>() >= std::mem::size_of::<TO>() {
        // E.g. going from `(f32,f32,f32)` to `f32`.
        assert_eq!(
            std::mem::size_of::<FROM>() % std::mem::size_of::<TO>(),
            0,
            "cast_vec: Size of FROM must be equal to -- or a multiple of -- the size of TO"
        );
        let factor = std::mem::size_of::<FROM>() / std::mem::size_of::<TO>();
        unsafe { Vec::from_raw_parts(vec.as_mut_ptr() as *mut TO, vec.len() * factor, vec.capacity() * factor) }
    } else {
        // E.g. going from `f32` to `(f32,f32,f32)`.
        assert_eq!(
            std::mem::size_of::<TO>() % std::mem::size_of::<FROM>(),
            0,
            "cast_vec: Size of TO must be equal to -- or a multiple of -- the size of FROM"
        );
        let factor = std::mem::size_of::<TO>() / std::mem::size_of::<FROM>();
        assert_eq!(vec.len() % factor, 0, "cast_vec: vec.len() must be able to precisely fit TO without a remainder");
        assert_eq!(vec.capacity() % factor, 0, "cast_vec: vec.capacity() must be able to precisely fit TO without a remainder");
        unsafe { Vec::from_raw_parts(vec.as_mut_ptr() as *mut TO, vec.len() / factor, vec.capacity() / factor) }
    };

    new_vec
}

#[cfg(test)]
mod tests {
    use crate::cast::*;

    #[test]
    fn test_casts() {
        #[derive(Clone, Copy, PartialEq, Debug)]
        #[repr(C)]
        struct MyStruct(u32, u32);
        let vec = vec![MyStruct(1, 2), MyStruct(3, 4)];
        assert_eq!(cast_slice::<MyStruct, u32>(&vec), &[1, 2, 3, 4]);
        assert_eq!(cast_slice::<u32, MyStruct>(cast_slice::<MyStruct, u32>(&vec)), &vec);
        assert_eq!(
            cast_slice::<MyStruct, u8>(&vec),
            cast_slice::<[u8; 4], u8>(&[1u32.to_ne_bytes(), 2u32.to_ne_bytes(), 3u32.to_ne_bytes(), 4u32.to_ne_bytes()])
        );

        assert_eq!(cast_vec::<MyStruct, u32>(vec.clone()), vec![1, 2, 3, 4]);
        assert_eq!(cast_vec::<u32, MyStruct>(cast_vec::<MyStruct, u32>(vec.clone())), vec);
    }
}