naga/back/spv/
helpers.rs

1use alloc::{vec, vec::Vec};
2
3use arrayvec::ArrayVec;
4use spirv::Word;
5
6use crate::{Handle, UniqueArena};
7
8pub(super) fn bytes_to_words(bytes: &[u8]) -> Vec<Word> {
9    bytes
10        .chunks(4)
11        .map(|chars| chars.iter().rev().fold(0u32, |u, c| (u << 8) | *c as u32))
12        .collect()
13}
14
15pub(super) fn string_to_words(input: &str) -> Vec<Word> {
16    let bytes = input.as_bytes();
17
18    debug_str_bytes_to_words(bytes)
19}
20
21/// Convert bytes to a vector of SPIR-V words, replacing NUL bytes with `?`.
22///
23/// (Using the replacement character or NUL symbol would require changing
24/// the length of the string, which would complicate chunking of the
25/// program source.)
26pub(super) fn debug_str_bytes_to_words(bytes: &[u8]) -> Vec<Word> {
27    let sanitized;
28    let bytes = if bytes.contains(&0) {
29        sanitized = bytes
30            .iter()
31            .map(|&b| if b == 0 { b'?' } else { b })
32            .collect::<Vec<_>>();
33        &sanitized[..]
34    } else {
35        bytes
36    };
37
38    let mut words = bytes_to_words(bytes);
39    if bytes.len().is_multiple_of(4) {
40        // nul-termination
41        words.push(0x0u32);
42    }
43
44    words
45}
46
47/// split a string into chunks and keep utf8 valid
48#[allow(unstable_name_collisions)]
49pub(super) fn string_to_byte_chunks(input: &str, limit: usize) -> Vec<&[u8]> {
50    let mut offset: usize = 0;
51    let mut start: usize = 0;
52    let mut words = vec![];
53    while offset < input.len() {
54        offset = input.floor_char_boundary_polyfill(offset + limit);
55        // Clippy wants us to call as_bytes() first to avoid the UTF-8 check,
56        // but we want to assert the output is valid UTF-8.
57        #[allow(clippy::sliced_string_as_bytes)]
58        words.push(input[start..offset].as_bytes());
59        start = offset;
60    }
61
62    words
63}
64
65pub(super) const fn map_storage_class(space: crate::AddressSpace) -> spirv::StorageClass {
66    match space {
67        crate::AddressSpace::Handle => spirv::StorageClass::UniformConstant,
68        crate::AddressSpace::Function => spirv::StorageClass::Function,
69        crate::AddressSpace::Private => spirv::StorageClass::Private,
70        crate::AddressSpace::Storage { .. } => spirv::StorageClass::StorageBuffer,
71        crate::AddressSpace::Uniform => spirv::StorageClass::Uniform,
72        crate::AddressSpace::WorkGroup => spirv::StorageClass::Workgroup,
73        crate::AddressSpace::Immediate => spirv::StorageClass::PushConstant,
74        crate::AddressSpace::TaskPayload => spirv::StorageClass::TaskPayloadWorkgroupEXT,
75        // We can't require capabilities here but we request capabilities on the ray pipeline stages
76        // and when writing global variables - global variables because we may be writing an
77        // uncompacted module and pipeline stages for all other cases because these can only be
78        //accessed in a ray tracing pipeline stage.
79        crate::AddressSpace::RayPayload => spirv::StorageClass::RayPayloadKHR,
80        crate::AddressSpace::IncomingRayPayload => spirv::StorageClass::IncomingRayPayloadKHR,
81    }
82}
83
84pub(super) fn contains_builtin(
85    binding: Option<&crate::Binding>,
86    ty: Handle<crate::Type>,
87    arena: &UniqueArena<crate::Type>,
88    built_in: crate::BuiltIn,
89) -> bool {
90    if let Some(&crate::Binding::BuiltIn(bi)) = binding {
91        bi == built_in
92    } else if let crate::TypeInner::Struct { ref members, .. } = arena[ty].inner {
93        members
94            .iter()
95            .any(|member| contains_builtin(member.binding.as_ref(), member.ty, arena, built_in))
96    } else {
97        false // unreachable
98    }
99}
100
101impl crate::AddressSpace {
102    pub(super) const fn to_spirv_semantics_and_scope(
103        self,
104    ) -> (spirv::MemorySemantics, spirv::Scope) {
105        match self {
106            Self::Storage { .. } => (spirv::MemorySemantics::empty(), spirv::Scope::Device),
107            Self::WorkGroup => (spirv::MemorySemantics::empty(), spirv::Scope::Workgroup),
108            Self::Uniform => (spirv::MemorySemantics::empty(), spirv::Scope::Device),
109            Self::Handle => (spirv::MemorySemantics::empty(), spirv::Scope::Device),
110            _ => (spirv::MemorySemantics::empty(), spirv::Scope::Invocation),
111        }
112    }
113}
114
115/// Return true if the global requires a type decorated with `Block`.
116///
117/// See [`back::spv::GlobalVariable`] for details.
118///
119/// [`back::spv::GlobalVariable`]: super::GlobalVariable
120pub fn global_needs_wrapper(ir_module: &crate::Module, var: &crate::GlobalVariable) -> bool {
121    match var.space {
122        crate::AddressSpace::Uniform
123        | crate::AddressSpace::Storage { .. }
124        | crate::AddressSpace::Immediate => {}
125        _ => return false,
126    };
127    match ir_module.types[var.ty].inner {
128        crate::TypeInner::Struct {
129            ref members,
130            span: _,
131        } => match members.last() {
132            Some(member) => match ir_module.types[member.ty].inner {
133                // Structs with dynamically sized arrays can't be copied and can't be wrapped.
134                crate::TypeInner::Array {
135                    size: crate::ArraySize::Dynamic,
136                    ..
137                } => false,
138                _ => true,
139            },
140            None => false,
141        },
142        crate::TypeInner::BindingArray { .. } => false,
143        // if it's not a structure or a binding array, let's wrap it to be able to put "Block"
144        _ => true,
145    }
146}
147
148/// Returns true if `pointer` refers to two-row matrix which is a member of a
149/// struct in the [`crate::AddressSpace::Uniform`] address space.
150pub fn is_uniform_matcx2_struct_member_access(
151    ir_function: &crate::Function,
152    fun_info: &crate::valid::FunctionInfo,
153    ir_module: &crate::Module,
154    pointer: Handle<crate::Expression>,
155) -> bool {
156    if let crate::TypeInner::Pointer {
157        base: pointer_base_type,
158        space: crate::AddressSpace::Uniform,
159    } = *fun_info[pointer].ty.inner_with(&ir_module.types)
160    {
161        if let crate::TypeInner::Matrix {
162            rows: crate::VectorSize::Bi,
163            ..
164        } = ir_module.types[pointer_base_type].inner
165        {
166            if let crate::Expression::AccessIndex {
167                base: parent_pointer,
168                ..
169            } = ir_function.expressions[pointer]
170            {
171                if let crate::TypeInner::Pointer {
172                    base: parent_type, ..
173                } = *fun_info[parent_pointer].ty.inner_with(&ir_module.types)
174                {
175                    if let crate::TypeInner::Struct { .. } = ir_module.types[parent_type].inner {
176                        return true;
177                    }
178                }
179            }
180        }
181    }
182
183    false
184}
185
186///HACK: this is taken from std unstable, remove it when std's floor_char_boundary is stable
187/// and available in our msrv.
188trait U8Internal {
189    fn is_utf8_char_boundary_polyfill(&self) -> bool;
190}
191
192impl U8Internal for u8 {
193    fn is_utf8_char_boundary_polyfill(&self) -> bool {
194        // This is bit magic equivalent to: b < 128 || b >= 192
195        (*self as i8) >= -0x40
196    }
197}
198
199trait StrUnstable {
200    fn floor_char_boundary_polyfill(&self, index: usize) -> usize;
201}
202
203impl StrUnstable for str {
204    fn floor_char_boundary_polyfill(&self, index: usize) -> usize {
205        if index >= self.len() {
206            self.len()
207        } else {
208            let lower_bound = index.saturating_sub(3);
209            let new_index = self.as_bytes()[lower_bound..=index]
210                .iter()
211                .rposition(|b| b.is_utf8_char_boundary_polyfill());
212
213            // We know that the character boundary will be within four bytes.
214            lower_bound + new_index.unwrap()
215        }
216    }
217}
218
219#[derive(Debug)]
220pub enum BindingDecorations {
221    BuiltIn(spirv::BuiltIn, ArrayVec<spirv::Decoration, 2>),
222    Location {
223        location: u32,
224        others: ArrayVec<spirv::Decoration, 5>,
225        /// If this is `Some`, use Decoration::Index with blend_src as an operand
226        blend_src: Option<Word>,
227    },
228    None,
229}