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}