Skip to main content

vulkan_rust/
bytecode.rs

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