naga/proc/
index.rs

1/*!
2Definitions for index bounds checking.
3*/
4
5use crate::{valid, Handle, UniqueArena};
6use bit_set::BitSet;
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    /// [`ImageLoad`]: crate::Expression::ImageLoad
116    #[cfg_attr(feature = "deserialize", serde(default))]
117    pub image_load: BoundsCheckPolicy,
118
119    /// How should the generated code handle image texel stores that are out
120    /// of range?
121    ///
122    /// This controls the behavior of [`ImageStore`] statements when a coordinate,
123    /// texture array index, level of detail, or multisampled sample number is out of range.
124    ///
125    /// This policy should't be needed since all backends should ignore OOB writes.
126    ///
127    /// [`ImageStore`]: crate::Statement::ImageStore
128    #[cfg_attr(feature = "deserialize", serde(default))]
129    pub image_store: BoundsCheckPolicy,
130
131    /// How should the generated code handle binding array indexes that are out of bounds.
132    #[cfg_attr(feature = "deserialize", serde(default))]
133    pub binding_array: BoundsCheckPolicy,
134}
135
136/// The default `BoundsCheckPolicy` is `Unchecked`.
137impl Default for BoundsCheckPolicy {
138    fn default() -> Self {
139        BoundsCheckPolicy::Unchecked
140    }
141}
142
143impl BoundsCheckPolicies {
144    /// Determine which policy applies to `base`.
145    ///
146    /// `base` is the "base" expression (the expression being indexed) of a `Access`
147    /// and `AccessIndex` expression. This is either a pointer, a value, being directly
148    /// indexed, or a binding array.
149    ///
150    /// See the documentation for [`BoundsCheckPolicy`] for details about
151    /// when each policy applies.
152    pub fn choose_policy(
153        &self,
154        base: Handle<crate::Expression>,
155        types: &UniqueArena<crate::Type>,
156        info: &valid::FunctionInfo,
157    ) -> BoundsCheckPolicy {
158        let ty = info[base].ty.inner_with(types);
159
160        if let crate::TypeInner::BindingArray { .. } = *ty {
161            return self.binding_array;
162        }
163
164        match ty.pointer_space() {
165            Some(crate::AddressSpace::Storage { access: _ } | crate::AddressSpace::Uniform) => {
166                self.buffer
167            }
168            // This covers other address spaces, but also accessing vectors and
169            // matrices by value, where no pointer is involved.
170            _ => self.index,
171        }
172    }
173
174    /// Return `true` if any of `self`'s policies are `policy`.
175    pub fn contains(&self, policy: BoundsCheckPolicy) -> bool {
176        self.index == policy
177            || self.buffer == policy
178            || self.image_load == policy
179            || self.image_store == policy
180    }
181}
182
183/// An index that may be statically known, or may need to be computed at runtime.
184///
185/// This enum lets us handle both [`Access`] and [`AccessIndex`] expressions
186/// with the same code.
187///
188/// [`Access`]: crate::Expression::Access
189/// [`AccessIndex`]: crate::Expression::AccessIndex
190#[derive(Clone, Copy, Debug)]
191pub enum GuardedIndex {
192    Known(u32),
193    Expression(Handle<crate::Expression>),
194}
195
196/// Build a set of expressions used as indices, to cache in temporary variables when
197/// emitted.
198///
199/// Given the bounds-check policies `policies`, construct a `BitSet` containing the handle
200/// indices of all the expressions in `function` that are ever used as guarded indices
201/// under the [`ReadZeroSkipWrite`] policy. The `module` argument must be the module to
202/// which `function` belongs, and `info` should be that function's analysis results.
203///
204/// Such index expressions will be used twice in the generated code: first for the
205/// comparison to see if the index is in bounds, and then for the access itself, should
206/// the comparison succeed. To avoid computing the expressions twice, the generated code
207/// should cache them in temporary variables.
208///
209/// Why do we need to build such a set in advance, instead of just processing access
210/// expressions as we encounter them? Whether an expression needs to be cached depends on
211/// whether it appears as something like the [`index`] operand of an [`Access`] expression
212/// or the [`level`] operand of an [`ImageLoad`] expression, and on the index bounds check
213/// policies that apply to those accesses. But [`Emit`] statements just identify a range
214/// of expressions by index; there's no good way to tell what an expression is used
215/// for. The only way to do it is to just iterate over all the expressions looking for
216/// relevant `Access` expressions --- which is what this function does.
217///
218/// Simple expressions like variable loads and constants don't make sense to cache: it's
219/// no better than just re-evaluating them. But constants are not covered by `Emit`
220/// statements, and `Load`s are always cached to ensure they occur at the right time, so
221/// we don't bother filtering them out from this set.
222///
223/// Fortunately, we don't need to deal with [`ImageStore`] statements here. When we emit
224/// code for a statement, the writer isn't in the middle of an expression, so we can just
225/// emit declarations for temporaries, initialized appropriately.
226///
227/// None of these concerns apply for SPIR-V output, since it's easy to just reuse an
228/// instruction ID in two places; that has the same semantics as a temporary variable, and
229/// it's inherent in the design of SPIR-V. This function is more useful for text-based
230/// back ends.
231///
232/// [`ReadZeroSkipWrite`]: BoundsCheckPolicy::ReadZeroSkipWrite
233/// [`index`]: crate::Expression::Access::index
234/// [`Access`]: crate::Expression::Access
235/// [`level`]: crate::Expression::ImageLoad::level
236/// [`ImageLoad`]: crate::Expression::ImageLoad
237/// [`Emit`]: crate::Statement::Emit
238/// [`ImageStore`]: crate::Statement::ImageStore
239pub fn find_checked_indexes(
240    module: &crate::Module,
241    function: &crate::Function,
242    info: &valid::FunctionInfo,
243    policies: BoundsCheckPolicies,
244) -> BitSet {
245    use crate::Expression as Ex;
246
247    let mut guarded_indices = BitSet::new();
248
249    // Don't bother scanning if we never need `ReadZeroSkipWrite`.
250    if policies.contains(BoundsCheckPolicy::ReadZeroSkipWrite) {
251        for (_handle, expr) in function.expressions.iter() {
252            // There's no need to handle `AccessIndex` expressions, as their
253            // indices never need to be cached.
254            match *expr {
255                Ex::Access { base, index } => {
256                    if policies.choose_policy(base, &module.types, info)
257                        == BoundsCheckPolicy::ReadZeroSkipWrite
258                        && access_needs_check(
259                            base,
260                            GuardedIndex::Expression(index),
261                            module,
262                            function,
263                            info,
264                        )
265                        .is_some()
266                    {
267                        guarded_indices.insert(index.index());
268                    }
269                }
270                Ex::ImageLoad {
271                    coordinate,
272                    array_index,
273                    sample,
274                    level,
275                    ..
276                } => {
277                    if policies.image_load == BoundsCheckPolicy::ReadZeroSkipWrite {
278                        guarded_indices.insert(coordinate.index());
279                        if let Some(array_index) = array_index {
280                            guarded_indices.insert(array_index.index());
281                        }
282                        if let Some(sample) = sample {
283                            guarded_indices.insert(sample.index());
284                        }
285                        if let Some(level) = level {
286                            guarded_indices.insert(level.index());
287                        }
288                    }
289                }
290                _ => {}
291            }
292        }
293    }
294
295    guarded_indices
296}
297
298/// Determine whether `index` is statically known to be in bounds for `base`.
299///
300/// If we can't be sure that the index is in bounds, return the limit within
301/// which valid indices must fall.
302///
303/// The return value is one of the following:
304///
305/// - `Some(Known(n))` indicates that `n` is the largest valid index.
306///
307/// - `Some(Computed(global))` indicates that the largest valid index is one
308///   less than the length of the array that is the last member of the
309///   struct held in `global`.
310///
311/// - `None` indicates that the index need not be checked, either because it
312///   is statically known to be in bounds, or because the applicable policy
313///   is `Unchecked`.
314///
315/// This function only handles subscriptable types: arrays, vectors, and
316/// matrices. It does not handle struct member indices; those never require
317/// run-time checks, so it's best to deal with them further up the call
318/// chain.
319pub fn access_needs_check(
320    base: Handle<crate::Expression>,
321    mut index: GuardedIndex,
322    module: &crate::Module,
323    function: &crate::Function,
324    info: &valid::FunctionInfo,
325) -> Option<IndexableLength> {
326    let base_inner = info[base].ty.inner_with(&module.types);
327    // Unwrap safety: `Err` here indicates unindexable base types and invalid
328    // length constants, but `access_needs_check` is only used by back ends, so
329    // validation should have caught those problems.
330    let length = base_inner.indexable_length(module).unwrap();
331    index.try_resolve_to_constant(function, module);
332    if let (&GuardedIndex::Known(index), &IndexableLength::Known(length)) = (&index, &length) {
333        if index < length {
334            // Index is statically known to be in bounds, no check needed.
335            return None;
336        }
337    };
338
339    Some(length)
340}
341
342impl GuardedIndex {
343    /// Make a `GuardedIndex::Known` from a `GuardedIndex::Expression` if possible.
344    ///
345    /// Return values that are already `Known` unchanged.
346    fn try_resolve_to_constant(&mut self, function: &crate::Function, module: &crate::Module) {
347        if let GuardedIndex::Expression(expr) = *self {
348            if let Ok(value) = module
349                .to_ctx()
350                .eval_expr_to_u32_from(expr, &function.expressions)
351            {
352                *self = GuardedIndex::Known(value);
353            }
354        }
355    }
356}
357
358#[derive(Clone, Copy, Debug, thiserror::Error, PartialEq)]
359pub enum IndexableLengthError {
360    #[error("Type is not indexable, and has no length (validation error)")]
361    TypeNotIndexable,
362    #[error("Array length constant {0:?} is invalid")]
363    InvalidArrayLength(Handle<crate::Expression>),
364}
365
366impl crate::TypeInner {
367    /// Return the length of a subscriptable type.
368    ///
369    /// The `self` parameter should be a handle to a vector, matrix, or array
370    /// type, a pointer to one of those, or a value pointer. Arrays may be
371    /// fixed-size, dynamically sized, or sized by a specializable constant.
372    /// This function does not handle struct member references, as with
373    /// `AccessIndex`.
374    ///
375    /// The value returned is appropriate for bounds checks on subscripting.
376    ///
377    /// Return an error if `self` does not describe a subscriptable type at all.
378    pub fn indexable_length(
379        &self,
380        module: &crate::Module,
381    ) -> Result<IndexableLength, IndexableLengthError> {
382        use crate::TypeInner as Ti;
383        let known_length = match *self {
384            Ti::Vector { size, .. } => size as _,
385            Ti::Matrix { columns, .. } => columns as _,
386            Ti::Array { size, .. } | Ti::BindingArray { size, .. } => {
387                return size.to_indexable_length(module);
388            }
389            Ti::ValuePointer {
390                size: Some(size), ..
391            } => size as _,
392            Ti::Pointer { base, .. } => {
393                // When assigning types to expressions, ResolveContext::Resolve
394                // does a separate sub-match here instead of a full recursion,
395                // so we'll do the same.
396                let base_inner = &module.types[base].inner;
397                match *base_inner {
398                    Ti::Vector { size, .. } => size as _,
399                    Ti::Matrix { columns, .. } => columns as _,
400                    Ti::Array { size, .. } | Ti::BindingArray { size, .. } => {
401                        return size.to_indexable_length(module)
402                    }
403                    _ => return Err(IndexableLengthError::TypeNotIndexable),
404                }
405            }
406            _ => return Err(IndexableLengthError::TypeNotIndexable),
407        };
408        Ok(IndexableLength::Known(known_length))
409    }
410}
411
412/// The number of elements in an indexable type.
413///
414/// This summarizes the length of vectors, matrices, and arrays in a way that is
415/// convenient for indexing and bounds-checking code.
416#[derive(Debug)]
417pub enum IndexableLength {
418    /// Values of this type always have the given number of elements.
419    Known(u32),
420
421    /// The number of elements is determined at runtime.
422    Dynamic,
423}
424
425impl crate::ArraySize {
426    pub const fn to_indexable_length(
427        self,
428        _module: &crate::Module,
429    ) -> Result<IndexableLength, IndexableLengthError> {
430        Ok(match self {
431            Self::Constant(length) => IndexableLength::Known(length.get()),
432            Self::Dynamic => IndexableLength::Dynamic,
433        })
434    }
435}