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}