naga/proc/
index.rs

1/*!
2Definitions for index bounds checking.
3*/
4
5use crate::arena::{Handle, HandleSet, UniqueArena};
6use crate::valid;
7
8/// How should code generated by Naga do bounds checks?
9///
10/// When a vector, matrix, or array index is out of bounds—either negative, or
11/// greater than or equal to the number of elements in the type—WGSL requires
12/// that some other index of the implementation's choice that is in bounds is
13/// used instead. (There are no types with zero elements.)
14///
15/// Similarly, when out-of-bounds coordinates, array indices, or sample indices
16/// are presented to the WGSL `textureLoad` and `textureStore` operations, the
17/// operation is redirected to do something safe.
18///
19/// Different users of Naga will prefer different defaults:
20///
21/// -   When used as part of a WebGPU implementation, the WGSL specification
22///     requires the `Restrict` behavior for array, vector, and matrix accesses,
23///     and either the `Restrict` or `ReadZeroSkipWrite` behaviors for texture
24///     accesses.
25///
26/// -   When used by the `wgpu` crate for native development, `wgpu` selects
27///     `ReadZeroSkipWrite` as its default.
28///
29/// -   Naga's own default is `Unchecked`, so that shader translations
30///     are as faithful to the original as possible.
31///
32/// Sometimes the underlying hardware and drivers can perform bounds checks
33/// themselves, in a way that performs better than the checks Naga would inject.
34/// If you're using native checks like this, then having Naga inject its own
35/// checks as well would be redundant, and the `Unchecked` policy is
36/// appropriate.
37#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
38#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
39#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
40pub enum BoundsCheckPolicy {
41    /// Replace out-of-bounds indexes with some arbitrary in-bounds index.
42    ///
43    /// (This does not necessarily mean clamping. For example, interpreting the
44    /// index as unsigned and taking the minimum with the largest valid index
45    /// would also be a valid implementation. That would map negative indices to
46    /// the last element, not the first.)
47    Restrict,
48
49    /// Out-of-bounds reads return zero, and writes have no effect.
50    ///
51    /// When applied to a chain of accesses, like `a[i][j].b[k]`, all index
52    /// expressions are evaluated, regardless of whether prior or later index
53    /// expressions were in bounds. But all the accesses per se are skipped
54    /// if any index is out of bounds.
55    ReadZeroSkipWrite,
56
57    /// Naga adds no checks to indexing operations. Generate the fastest code
58    /// possible. This is the default for Naga, as a translator, but consumers
59    /// should consider defaulting to a safer behavior.
60    Unchecked,
61}
62
63/// Policies for injecting bounds checks during code generation.
64#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
65#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
66#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
67pub struct BoundsCheckPolicies {
68    /// How should the generated code handle array, vector, or matrix indices
69    /// that are out of range?
70    #[cfg_attr(feature = "deserialize", serde(default))]
71    pub index: BoundsCheckPolicy,
72
73    /// How should the generated code handle array, vector, or matrix indices
74    /// that are out of range, when those values live in a [`GlobalVariable`] in
75    /// the [`Storage`] or [`Uniform`] address spaces?
76    ///
77    /// Some graphics hardware provides "robust buffer access", a feature that
78    /// ensures that using a pointer cannot access memory outside the 'buffer'
79    /// that it was derived from. In Naga terms, this means that the hardware
80    /// ensures that pointers computed by applying [`Access`] and
81    /// [`AccessIndex`] expressions to a [`GlobalVariable`] whose [`space`] is
82    /// [`Storage`] or [`Uniform`] will never read or write memory outside that
83    /// global variable.
84    ///
85    /// When hardware offers such a feature, it is probably undesirable to have
86    /// Naga inject bounds checking code for such accesses, since the hardware
87    /// can probably provide the same protection more efficiently. However,
88    /// bounds checks are still needed on accesses to indexable values that do
89    /// not live in buffers, like local variables.
90    ///
91    /// So, this option provides a separate policy that applies only to accesses
92    /// to storage and uniform globals. When depending on hardware bounds
93    /// checking, this policy can be `Unchecked` to avoid unnecessary overhead.
94    ///
95    /// When special hardware support is not available, this should probably be
96    /// the same as `index_bounds_check_policy`.
97    ///
98    /// [`GlobalVariable`]: crate::GlobalVariable
99    /// [`space`]: crate::GlobalVariable::space
100    /// [`Restrict`]: crate::back::BoundsCheckPolicy::Restrict
101    /// [`ReadZeroSkipWrite`]: crate::back::BoundsCheckPolicy::ReadZeroSkipWrite
102    /// [`Access`]: crate::Expression::Access
103    /// [`AccessIndex`]: crate::Expression::AccessIndex
104    /// [`Storage`]: crate::AddressSpace::Storage
105    /// [`Uniform`]: crate::AddressSpace::Uniform
106    #[cfg_attr(feature = "deserialize", serde(default))]
107    pub buffer: BoundsCheckPolicy,
108
109    /// How should the generated code handle image texel loads that are out
110    /// of range?
111    ///
112    /// This controls the behavior of [`ImageLoad`] expressions when a coordinate,
113    /// texture array index, level of detail, or multisampled sample number is out of range.
114    ///
115    /// There is no corresponding policy for [`ImageStore`] statements. All the
116    /// platforms we support already discard out-of-bounds image stores,
117    /// effectively implementing the "skip write" part of [`ReadZeroSkipWrite`].
118    ///
119    /// [`ImageLoad`]: crate::Expression::ImageLoad
120    /// [`ImageStore`]: crate::Statement::ImageStore
121    /// [`ReadZeroSkipWrite`]: BoundsCheckPolicy::ReadZeroSkipWrite
122    #[cfg_attr(feature = "deserialize", serde(default))]
123    pub image_load: BoundsCheckPolicy,
124
125    /// How should the generated code handle binding array indexes that are out of bounds.
126    #[cfg_attr(feature = "deserialize", serde(default))]
127    pub binding_array: BoundsCheckPolicy,
128}
129
130/// The default `BoundsCheckPolicy` is `Unchecked`.
131impl Default for BoundsCheckPolicy {
132    fn default() -> Self {
133        BoundsCheckPolicy::Unchecked
134    }
135}
136
137impl BoundsCheckPolicies {
138    /// Determine which policy applies to `base`.
139    ///
140    /// `base` is the "base" expression (the expression being indexed) of a `Access`
141    /// and `AccessIndex` expression. This is either a pointer, a value, being directly
142    /// indexed, or a binding array.
143    ///
144    /// See the documentation for [`BoundsCheckPolicy`] for details about
145    /// when each policy applies.
146    pub fn choose_policy(
147        &self,
148        base: Handle<crate::Expression>,
149        types: &UniqueArena<crate::Type>,
150        info: &valid::FunctionInfo,
151    ) -> BoundsCheckPolicy {
152        let ty = info[base].ty.inner_with(types);
153
154        if let crate::TypeInner::BindingArray { .. } = *ty {
155            return self.binding_array;
156        }
157
158        match ty.pointer_space() {
159            Some(crate::AddressSpace::Storage { access: _ } | crate::AddressSpace::Uniform) => {
160                self.buffer
161            }
162            // This covers other address spaces, but also accessing vectors and
163            // matrices by value, where no pointer is involved.
164            _ => self.index,
165        }
166    }
167
168    /// Return `true` if any of `self`'s policies are `policy`.
169    pub fn contains(&self, policy: BoundsCheckPolicy) -> bool {
170        self.index == policy || self.buffer == policy || self.image_load == policy
171    }
172}
173
174/// An index that may be statically known, or may need to be computed at runtime.
175///
176/// This enum lets us handle both [`Access`] and [`AccessIndex`] expressions
177/// with the same code.
178///
179/// [`Access`]: crate::Expression::Access
180/// [`AccessIndex`]: crate::Expression::AccessIndex
181#[derive(Clone, Copy, Debug)]
182pub enum GuardedIndex {
183    Known(u32),
184    Expression(Handle<crate::Expression>),
185}
186
187/// Build a set of expressions used as indices, to cache in temporary variables when
188/// emitted.
189///
190/// Given the bounds-check policies `policies`, construct a `HandleSet` containing the handle
191/// indices of all the expressions in `function` that are ever used as guarded indices
192/// under the [`ReadZeroSkipWrite`] policy. The `module` argument must be the module to
193/// which `function` belongs, and `info` should be that function's analysis results.
194///
195/// Such index expressions will be used twice in the generated code: first for the
196/// comparison to see if the index is in bounds, and then for the access itself, should
197/// the comparison succeed. To avoid computing the expressions twice, the generated code
198/// should cache them in temporary variables.
199///
200/// Why do we need to build such a set in advance, instead of just processing access
201/// expressions as we encounter them? Whether an expression needs to be cached depends on
202/// whether it appears as something like the [`index`] operand of an [`Access`] expression
203/// or the [`level`] operand of an [`ImageLoad`] expression, and on the index bounds check
204/// policies that apply to those accesses. But [`Emit`] statements just identify a range
205/// of expressions by index; there's no good way to tell what an expression is used
206/// for. The only way to do it is to just iterate over all the expressions looking for
207/// relevant `Access` expressions --- which is what this function does.
208///
209/// Simple expressions like variable loads and constants don't make sense to cache: it's
210/// no better than just re-evaluating them. But constants are not covered by `Emit`
211/// statements, and `Load`s are always cached to ensure they occur at the right time, so
212/// we don't bother filtering them out from this set.
213///
214/// Fortunately, we don't need to deal with [`ImageStore`] statements here. When we emit
215/// code for a statement, the writer isn't in the middle of an expression, so we can just
216/// emit declarations for temporaries, initialized appropriately.
217///
218/// None of these concerns apply for SPIR-V output, since it's easy to just reuse an
219/// instruction ID in two places; that has the same semantics as a temporary variable, and
220/// it's inherent in the design of SPIR-V. This function is more useful for text-based
221/// back ends.
222///
223/// [`ReadZeroSkipWrite`]: BoundsCheckPolicy::ReadZeroSkipWrite
224/// [`index`]: crate::Expression::Access::index
225/// [`Access`]: crate::Expression::Access
226/// [`level`]: crate::Expression::ImageLoad::level
227/// [`ImageLoad`]: crate::Expression::ImageLoad
228/// [`Emit`]: crate::Statement::Emit
229/// [`ImageStore`]: crate::Statement::ImageStore
230pub fn find_checked_indexes(
231    module: &crate::Module,
232    function: &crate::Function,
233    info: &valid::FunctionInfo,
234    policies: BoundsCheckPolicies,
235) -> HandleSet<crate::Expression> {
236    use crate::Expression as Ex;
237
238    let mut guarded_indices = HandleSet::for_arena(&function.expressions);
239
240    // Don't bother scanning if we never need `ReadZeroSkipWrite`.
241    if policies.contains(BoundsCheckPolicy::ReadZeroSkipWrite) {
242        for (_handle, expr) in function.expressions.iter() {
243            // There's no need to handle `AccessIndex` expressions, as their
244            // indices never need to be cached.
245            match *expr {
246                Ex::Access { base, index } => {
247                    if policies.choose_policy(base, &module.types, info)
248                        == BoundsCheckPolicy::ReadZeroSkipWrite
249                        && access_needs_check(
250                            base,
251                            GuardedIndex::Expression(index),
252                            module,
253                            &function.expressions,
254                            info,
255                        )
256                        .is_some()
257                    {
258                        guarded_indices.insert(index);
259                    }
260                }
261                Ex::ImageLoad {
262                    coordinate,
263                    array_index,
264                    sample,
265                    level,
266                    ..
267                } => {
268                    if policies.image_load == BoundsCheckPolicy::ReadZeroSkipWrite {
269                        guarded_indices.insert(coordinate);
270                        if let Some(array_index) = array_index {
271                            guarded_indices.insert(array_index);
272                        }
273                        if let Some(sample) = sample {
274                            guarded_indices.insert(sample);
275                        }
276                        if let Some(level) = level {
277                            guarded_indices.insert(level);
278                        }
279                    }
280                }
281                _ => {}
282            }
283        }
284    }
285
286    guarded_indices
287}
288
289/// Determine whether `index` is statically known to be in bounds for `base`.
290///
291/// If we can't be sure that the index is in bounds, return the limit within
292/// which valid indices must fall.
293///
294/// The return value is one of the following:
295///
296/// - `Some(Known(n))` indicates that `n` is the largest valid index.
297///
298/// - `Some(Computed(global))` indicates that the largest valid index is one
299///   less than the length of the array that is the last member of the
300///   struct held in `global`.
301///
302/// - `None` indicates that the index need not be checked, either because it
303///   is statically known to be in bounds, or because the applicable policy
304///   is `Unchecked`.
305///
306/// This function only handles subscriptable types: arrays, vectors, and
307/// matrices. It does not handle struct member indices; those never require
308/// run-time checks, so it's best to deal with them further up the call
309/// chain.
310pub fn access_needs_check(
311    base: Handle<crate::Expression>,
312    mut index: GuardedIndex,
313    module: &crate::Module,
314    expressions: &crate::Arena<crate::Expression>,
315    info: &valid::FunctionInfo,
316) -> Option<IndexableLength> {
317    let base_inner = info[base].ty.inner_with(&module.types);
318    // Unwrap safety: `Err` here indicates unindexable base types and invalid
319    // length constants, but `access_needs_check` is only used by back ends, so
320    // validation should have caught those problems.
321    let length = base_inner.indexable_length(module).unwrap();
322    index.try_resolve_to_constant(expressions, module);
323    if let (&GuardedIndex::Known(index), &IndexableLength::Known(length)) = (&index, &length) {
324        if index < length {
325            // Index is statically known to be in bounds, no check needed.
326            return None;
327        }
328    };
329
330    Some(length)
331}
332
333impl GuardedIndex {
334    /// Make a `GuardedIndex::Known` from a `GuardedIndex::Expression` if possible.
335    ///
336    /// Return values that are already `Known` unchanged.
337    pub(crate) fn try_resolve_to_constant(
338        &mut self,
339        expressions: &crate::Arena<crate::Expression>,
340        module: &crate::Module,
341    ) {
342        if let GuardedIndex::Expression(expr) = *self {
343            *self = GuardedIndex::from_expression(expr, expressions, module);
344        }
345    }
346
347    pub(crate) fn from_expression(
348        expr: Handle<crate::Expression>,
349        expressions: &crate::Arena<crate::Expression>,
350        module: &crate::Module,
351    ) -> Self {
352        match module.to_ctx().eval_expr_to_u32_from(expr, expressions) {
353            Ok(value) => Self::Known(value),
354            Err(_) => Self::Expression(expr),
355        }
356    }
357}
358
359#[derive(Clone, Copy, Debug, thiserror::Error, PartialEq)]
360pub enum IndexableLengthError {
361    #[error("Type is not indexable, and has no length (validation error)")]
362    TypeNotIndexable,
363    #[error("Array length constant {0:?} is invalid")]
364    InvalidArrayLength(Handle<crate::Expression>),
365}
366
367impl crate::TypeInner {
368    /// Return the length of a subscriptable type.
369    ///
370    /// The `self` parameter should be a handle to a vector, matrix, or array
371    /// type, a pointer to one of those, or a value pointer. Arrays may be
372    /// fixed-size, dynamically sized, or sized by a specializable constant.
373    /// This function does not handle struct member references, as with
374    /// `AccessIndex`.
375    ///
376    /// The value returned is appropriate for bounds checks on subscripting.
377    ///
378    /// Return an error if `self` does not describe a subscriptable type at all.
379    pub fn indexable_length(
380        &self,
381        module: &crate::Module,
382    ) -> Result<IndexableLength, IndexableLengthError> {
383        use crate::TypeInner as Ti;
384        let known_length = match *self {
385            Ti::Vector { size, .. } => size as _,
386            Ti::Matrix { columns, .. } => columns as _,
387            Ti::Array { size, .. } | Ti::BindingArray { size, .. } => {
388                return size.to_indexable_length(module);
389            }
390            Ti::ValuePointer {
391                size: Some(size), ..
392            } => size as _,
393            Ti::Pointer { base, .. } => {
394                // When assigning types to expressions, ResolveContext::Resolve
395                // does a separate sub-match here instead of a full recursion,
396                // so we'll do the same.
397                let base_inner = &module.types[base].inner;
398                match *base_inner {
399                    Ti::Vector { size, .. } => size as _,
400                    Ti::Matrix { columns, .. } => columns as _,
401                    Ti::Array { size, .. } | Ti::BindingArray { size, .. } => {
402                        return size.to_indexable_length(module)
403                    }
404                    _ => return Err(IndexableLengthError::TypeNotIndexable),
405                }
406            }
407            _ => return Err(IndexableLengthError::TypeNotIndexable),
408        };
409        Ok(IndexableLength::Known(known_length))
410    }
411}
412
413/// The number of elements in an indexable type.
414///
415/// This summarizes the length of vectors, matrices, and arrays in a way that is
416/// convenient for indexing and bounds-checking code.
417#[derive(Debug)]
418pub enum IndexableLength {
419    /// Values of this type always have the given number of elements.
420    Known(u32),
421
422    /// The number of elements is determined at runtime.
423    Dynamic,
424}
425
426impl crate::ArraySize {
427    pub const fn to_indexable_length(
428        self,
429        _module: &crate::Module,
430    ) -> Result<IndexableLength, IndexableLengthError> {
431        Ok(match self {
432            Self::Constant(length) => IndexableLength::Known(length.get()),
433            Self::Dynamic => IndexableLength::Dynamic,
434        })
435    }
436}