Skip to main content

vulkan_rust/
bytecode.rs

1//! SPIR-V bytecode alignment helper.
2
3use std::fmt;
4
5/// Error returned when SPIR-V bytecode has invalid alignment or size.
6///
7/// # Examples
8///
9/// ```
10/// use vulkan_rust::BytecodeError;
11///
12/// let err = BytecodeError::InvalidLength(7);
13/// assert_eq!(err.to_string(), "SPIR-V byte length 7 is not a multiple of 4");
14///
15/// let err = BytecodeError::MisalignedPointer;
16/// assert!(err.to_string().contains("not 4-byte aligned"));
17/// ```
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum BytecodeError {
20    /// Length is not a multiple of 4.
21    InvalidLength(usize),
22    /// Pointer is not aligned to 4 bytes.
23    MisalignedPointer,
24}
25
26impl fmt::Display for BytecodeError {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        match self {
29            Self::InvalidLength(len) => {
30                write!(f, "SPIR-V byte length {len} is not a multiple of 4")
31            }
32            Self::MisalignedPointer => {
33                write!(f, "SPIR-V byte slice pointer is not 4-byte aligned")
34            }
35        }
36    }
37}
38
39impl std::error::Error for BytecodeError {}
40
41/// Cast a byte slice to a `u32` slice for `ShaderModuleCreateInfo`.
42///
43/// Returns an error if `bytes.len()` is not a multiple of 4 or the
44/// pointer is not 4-byte aligned. Use `include_bytes!` or aligned
45/// allocators to ensure correctness.
46///
47/// # Examples
48///
49/// ```
50/// use vulkan_rust::cast_to_u32;
51///
52/// #[repr(align(4))]
53/// struct Aligned([u8; 8]);
54/// let spirv = Aligned([0x03, 0x02, 0x23, 0x07, 0, 0, 0, 0]);
55/// let words = cast_to_u32(&spirv.0).expect("alignment error");
56/// assert_eq!(words.len(), 2);
57/// ```
58pub fn cast_to_u32(bytes: &[u8]) -> Result<&[u32], BytecodeError> {
59    if bytes.is_empty() {
60        return Ok(&[]);
61    }
62    if bytes.len() % 4 != 0 {
63        return Err(BytecodeError::InvalidLength(bytes.len()));
64    }
65    if (bytes.as_ptr() as usize) % 4 != 0 {
66        return Err(BytecodeError::MisalignedPointer);
67    }
68    // SAFETY: length and alignment checked above, pointer is valid, aligned to u32, and in-bounds.
69    Ok(unsafe { std::slice::from_raw_parts(bytes.as_ptr() as *const u32, bytes.len() / 4) })
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn aligned_input_succeeds() {
78        #[repr(align(4))]
79        struct Aligned([u8; 8]);
80        let data = Aligned([0x03, 0x02, 0x23, 0x07, 0, 0, 0, 0]);
81        let words = cast_to_u32(&data.0).expect("aligned input should succeed");
82        assert_eq!(words.len(), 2);
83        assert_eq!(words[0], 0x07230203); // SPIR-V magic number (little-endian)
84    }
85
86    #[test]
87    fn invalid_length_returns_error() {
88        #[repr(align(4))]
89        struct Aligned([u8; 5]);
90        let data = Aligned([1, 2, 3, 4, 5]);
91        assert_eq!(cast_to_u32(&data.0), Err(BytecodeError::InvalidLength(5)));
92    }
93
94    #[test]
95    fn empty_input_succeeds() {
96        let empty: &[u8] = &[];
97        let words = cast_to_u32(empty).expect("empty input should succeed");
98        assert!(words.is_empty());
99    }
100
101    #[test]
102    fn misaligned_pointer_display() {
103        let err = BytecodeError::MisalignedPointer;
104        assert_eq!(
105            err.to_string(),
106            "SPIR-V byte slice pointer is not 4-byte aligned"
107        );
108    }
109
110    #[test]
111    fn invalid_length_display() {
112        let err = BytecodeError::InvalidLength(7);
113        assert_eq!(
114            err.to_string(),
115            "SPIR-V byte length 7 is not a multiple of 4"
116        );
117    }
118
119    #[test]
120    fn misaligned_pointer_returns_error() {
121        // Create a 4-byte-aligned buffer, then take a subslice at offset 1
122        // to guarantee misalignment.
123        #[repr(align(4))]
124        struct Aligned([u8; 8]);
125        let data = Aligned([0; 8]);
126        let misaligned = &data.0[1..5];
127        assert_eq!(
128            cast_to_u32(misaligned),
129            Err(BytecodeError::MisalignedPointer)
130        );
131    }
132
133    #[test]
134    fn bytecode_error_is_std_error() {
135        let err: &dyn std::error::Error = &BytecodeError::InvalidLength(3);
136        // source() should return None (no underlying cause).
137        assert!(err.source().is_none());
138    }
139}