naga/valid/
analyzer.rs

1//! Module analyzer.
2//!
3//! Figures out the following properties:
4//! - control flow uniformity
5//! - texture/sampler pairs
6//! - expression reference counts
7
8use alloc::{boxed::Box, vec};
9use core::ops;
10
11use super::{ExpressionError, FunctionError, ModuleInfo, ShaderStages, ValidationFlags};
12use crate::diagnostic_filter::{DiagnosticFilterNode, StandardFilterableTriggeringRule};
13use crate::span::{AddSpan as _, WithSpan};
14use crate::{
15    arena::{Arena, Handle},
16    proc::{ResolveContext, TypeResolution},
17};
18
19pub type NonUniformResult = Option<Handle<crate::Expression>>;
20
21const DISABLE_UNIFORMITY_REQ_FOR_FRAGMENT_STAGE: bool = true;
22
23bitflags::bitflags! {
24    /// Kinds of expressions that require uniform control flow.
25    #[cfg_attr(feature = "serialize", derive(serde::Serialize))]
26    #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
27    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
28    pub struct UniformityRequirements: u8 {
29        const WORK_GROUP_BARRIER = 0x1;
30        const DERIVATIVE = if DISABLE_UNIFORMITY_REQ_FOR_FRAGMENT_STAGE { 0 } else { 0x2 };
31        const IMPLICIT_LEVEL = if DISABLE_UNIFORMITY_REQ_FOR_FRAGMENT_STAGE { 0 } else { 0x4 };
32        const COOP_OPS = 0x8;
33    }
34}
35
36/// Uniform control flow characteristics.
37#[derive(Clone, Debug)]
38#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
39#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
40#[cfg_attr(test, derive(PartialEq))]
41pub struct Uniformity {
42    /// A child expression with non-uniform result.
43    ///
44    /// This means, when the relevant invocations are scheduled on a compute unit,
45    /// they have to use vector registers to store an individual value
46    /// per invocation.
47    ///
48    /// Whenever the control flow is conditioned on such value,
49    /// the hardware needs to keep track of the mask of invocations,
50    /// and process all branches of the control flow.
51    ///
52    /// Any operations that depend on non-uniform results also produce non-uniform.
53    pub non_uniform_result: NonUniformResult,
54    /// If this expression requires uniform control flow, store the reason here.
55    pub requirements: UniformityRequirements,
56}
57
58impl Uniformity {
59    const fn new() -> Self {
60        Uniformity {
61            non_uniform_result: None,
62            requirements: UniformityRequirements::empty(),
63        }
64    }
65}
66
67bitflags::bitflags! {
68    #[derive(Clone, Copy, Debug, PartialEq)]
69    struct ExitFlags: u8 {
70        /// Control flow may return from the function, which makes all the
71        /// subsequent statements within the current function (only!)
72        /// to be executed in a non-uniform control flow.
73        const MAY_RETURN = 0x1;
74        /// Control flow may be killed. Anything after [`Statement::Kill`] is
75        /// considered inside non-uniform context.
76        ///
77        /// [`Statement::Kill`]: crate::Statement::Kill
78        const MAY_KILL = 0x2;
79    }
80}
81
82/// Uniformity characteristics of a function.
83#[cfg_attr(test, derive(Debug, PartialEq))]
84struct FunctionUniformity {
85    result: Uniformity,
86    exit: ExitFlags,
87}
88
89impl ops::BitOr for FunctionUniformity {
90    type Output = Self;
91    fn bitor(self, other: Self) -> Self {
92        FunctionUniformity {
93            result: Uniformity {
94                non_uniform_result: self
95                    .result
96                    .non_uniform_result
97                    .or(other.result.non_uniform_result),
98                requirements: self.result.requirements | other.result.requirements,
99            },
100            exit: self.exit | other.exit,
101        }
102    }
103}
104
105impl FunctionUniformity {
106    const fn new() -> Self {
107        FunctionUniformity {
108            result: Uniformity::new(),
109            exit: ExitFlags::empty(),
110        }
111    }
112
113    /// Returns a disruptor based on the stored exit flags, if any.
114    const fn exit_disruptor(&self) -> Option<UniformityDisruptor> {
115        if self.exit.contains(ExitFlags::MAY_RETURN) {
116            Some(UniformityDisruptor::Return)
117        } else if self.exit.contains(ExitFlags::MAY_KILL) {
118            Some(UniformityDisruptor::Discard)
119        } else {
120            None
121        }
122    }
123}
124
125bitflags::bitflags! {
126    /// Indicates how a global variable is used.
127    #[cfg_attr(feature = "serialize", derive(serde::Serialize))]
128    #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
129    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
130    pub struct GlobalUse: u8 {
131        /// Data will be read from the variable.
132        const READ = 0x1;
133        /// Data will be written to the variable.
134        const WRITE = 0x2;
135        /// The information about the data is queried.
136        const QUERY = 0x4;
137        /// Atomic operations will be performed on the variable.
138        const ATOMIC = 0x8;
139    }
140}
141
142#[derive(Clone, Debug, Eq, Hash, PartialEq)]
143#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
144#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
145pub struct SamplingKey {
146    pub image: Handle<crate::GlobalVariable>,
147    pub sampler: Handle<crate::GlobalVariable>,
148}
149
150#[derive(Clone, Debug)]
151#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
152#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
153/// Information about an expression in a function body.
154pub struct ExpressionInfo {
155    /// Whether this expression is uniform, and why.
156    ///
157    /// If this expression's value is not uniform, this is the handle
158    /// of the expression from which this one's non-uniformity
159    /// originates. Otherwise, this is `None`.
160    pub uniformity: Uniformity,
161
162    /// The number of direct references to this expression in statements and
163    /// other expressions.
164    ///
165    /// This is a _local_ reference count only, it may be non-zero for
166    /// expressions that are ultimately unused.
167    pub ref_count: usize,
168
169    /// The global variable into which this expression produces a pointer.
170    ///
171    /// This is `None` unless this expression is either a
172    /// [`GlobalVariable`], or an [`Access`] or [`AccessIndex`] that
173    /// ultimately refers to some part of a global.
174    ///
175    /// [`Load`] expressions applied to pointer-typed arguments could
176    /// refer to globals, but we leave this as `None` for them.
177    ///
178    /// [`GlobalVariable`]: crate::Expression::GlobalVariable
179    /// [`Access`]: crate::Expression::Access
180    /// [`AccessIndex`]: crate::Expression::AccessIndex
181    /// [`Load`]: crate::Expression::Load
182    assignable_global: Option<Handle<crate::GlobalVariable>>,
183
184    /// The type of this expression.
185    pub ty: TypeResolution,
186}
187
188impl ExpressionInfo {
189    const fn new() -> Self {
190        ExpressionInfo {
191            uniformity: Uniformity::new(),
192            ref_count: 0,
193            assignable_global: None,
194            // this doesn't matter at this point, will be overwritten
195            ty: TypeResolution::Value(crate::TypeInner::Scalar(crate::Scalar {
196                kind: crate::ScalarKind::Bool,
197                width: 0,
198            })),
199        }
200    }
201}
202
203#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
204#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
205#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
206enum GlobalOrArgument {
207    Global(Handle<crate::GlobalVariable>),
208    Argument(u32),
209}
210
211impl GlobalOrArgument {
212    fn from_expression(
213        expression_arena: &Arena<crate::Expression>,
214        expression: Handle<crate::Expression>,
215    ) -> Result<GlobalOrArgument, ExpressionError> {
216        Ok(match expression_arena[expression] {
217            crate::Expression::GlobalVariable(var) => GlobalOrArgument::Global(var),
218            crate::Expression::FunctionArgument(i) => GlobalOrArgument::Argument(i),
219            crate::Expression::Access { base, .. }
220            | crate::Expression::AccessIndex { base, .. } => match expression_arena[base] {
221                crate::Expression::GlobalVariable(var) => GlobalOrArgument::Global(var),
222                _ => return Err(ExpressionError::ExpectedGlobalOrArgument),
223            },
224            _ => return Err(ExpressionError::ExpectedGlobalOrArgument),
225        })
226    }
227}
228
229#[derive(Debug, Clone, PartialEq, Eq, Hash)]
230#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
231#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
232struct Sampling {
233    image: GlobalOrArgument,
234    sampler: GlobalOrArgument,
235}
236
237#[derive(Debug, Clone)]
238#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
239#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
240pub struct FunctionInfo {
241    /// Validation flags.
242    flags: ValidationFlags,
243    /// Set of shader stages where calling this function is valid.
244    pub available_stages: ShaderStages,
245    /// Uniformity characteristics.
246    pub uniformity: Uniformity,
247    /// Function may kill the invocation.
248    pub may_kill: bool,
249
250    /// All pairs of (texture, sampler) globals that may be used together in
251    /// sampling operations by this function and its callees. This includes
252    /// pairings that arise when this function passes textures and samplers as
253    /// arguments to its callees.
254    ///
255    /// This table does not include uses of textures and samplers passed as
256    /// arguments to this function itself, since we do not know which globals
257    /// those will be. However, this table *is* exhaustive when computed for an
258    /// entry point function: entry points never receive textures or samplers as
259    /// arguments, so all an entry point's sampling can be reported in terms of
260    /// globals.
261    ///
262    /// The GLSL back end uses this table to construct reflection info that
263    /// clients need to construct texture-combined sampler values.
264    pub sampling_set: crate::FastHashSet<SamplingKey>,
265
266    /// How this function and its callees use this module's globals.
267    ///
268    /// This is indexed by `Handle<GlobalVariable>` indices. However,
269    /// `FunctionInfo` implements `core::ops::Index<Handle<GlobalVariable>>`,
270    /// so you can simply index this struct with a global handle to retrieve
271    /// its usage information.
272    pub global_uses: Box<[GlobalUse]>,
273
274    /// Information about each expression in this function's body.
275    ///
276    /// This is indexed by `Handle<Expression>` indices. However, `FunctionInfo`
277    /// implements `core::ops::Index<Handle<Expression>>`, so you can simply
278    /// index this struct with an expression handle to retrieve its
279    /// `ExpressionInfo`.
280    expressions: Box<[ExpressionInfo]>,
281
282    /// All (texture, sampler) pairs that may be used together in sampling
283    /// operations by this function and its callees, whether they are accessed
284    /// as globals or passed as arguments.
285    ///
286    /// Participants are represented by [`GlobalVariable`] handles whenever
287    /// possible, and otherwise by indices of this function's arguments.
288    ///
289    /// When analyzing a function call, we combine this data about the callee
290    /// with the actual arguments being passed to produce the callers' own
291    /// `sampling_set` and `sampling` tables.
292    ///
293    /// [`GlobalVariable`]: crate::GlobalVariable
294    sampling: crate::FastHashSet<Sampling>,
295
296    /// Indicates that the function is using dual source blending.
297    pub dual_source_blending: bool,
298
299    /// The leaf of all module-wide diagnostic filter rules tree parsed from directives in this
300    /// module.
301    ///
302    /// See [`DiagnosticFilterNode`] for details on how the tree is represented and used in
303    /// validation.
304    diagnostic_filter_leaf: Option<Handle<DiagnosticFilterNode>>,
305}
306
307impl FunctionInfo {
308    pub const fn global_variable_count(&self) -> usize {
309        self.global_uses.len()
310    }
311    pub const fn expression_count(&self) -> usize {
312        self.expressions.len()
313    }
314    pub fn dominates_global_use(&self, other: &Self) -> bool {
315        for (self_global_uses, other_global_uses) in
316            self.global_uses.iter().zip(other.global_uses.iter())
317        {
318            if !self_global_uses.contains(*other_global_uses) {
319                return false;
320            }
321        }
322        true
323    }
324}
325
326impl ops::Index<Handle<crate::GlobalVariable>> for FunctionInfo {
327    type Output = GlobalUse;
328    fn index(&self, handle: Handle<crate::GlobalVariable>) -> &GlobalUse {
329        &self.global_uses[handle.index()]
330    }
331}
332
333impl ops::Index<Handle<crate::Expression>> for FunctionInfo {
334    type Output = ExpressionInfo;
335    fn index(&self, handle: Handle<crate::Expression>) -> &ExpressionInfo {
336        &self.expressions[handle.index()]
337    }
338}
339
340/// Disruptor of the uniform control flow.
341#[derive(Clone, Copy, Debug, thiserror::Error)]
342#[cfg_attr(test, derive(PartialEq))]
343pub enum UniformityDisruptor {
344    #[error("Expression {0:?} produced non-uniform result, and control flow depends on it")]
345    Expression(Handle<crate::Expression>),
346    #[error("There is a Return earlier in the control flow of the function")]
347    Return,
348    #[error("There is a Discard earlier in the entry point across all called functions")]
349    Discard,
350}
351
352impl FunctionInfo {
353    /// Record a use of `expr` of the sort given by `global_use`.
354    ///
355    /// Bump `expr`'s reference count, and return its uniformity.
356    ///
357    /// If `expr` is a pointer to a global variable, or some part of
358    /// a global variable, add `global_use` to that global's set of
359    /// uses.
360    #[must_use]
361    fn add_ref_impl(
362        &mut self,
363        expr: Handle<crate::Expression>,
364        global_use: GlobalUse,
365    ) -> NonUniformResult {
366        let info = &mut self.expressions[expr.index()];
367        info.ref_count += 1;
368        // Record usage if this expression may access a global
369        if let Some(global) = info.assignable_global {
370            self.global_uses[global.index()] |= global_use;
371        }
372        info.uniformity.non_uniform_result
373    }
374
375    /// Note an entry point's use of `global` not recorded by [`ModuleInfo::process_function`].
376    ///
377    /// Most global variable usage should be recorded via [`add_ref_impl`] in the process
378    /// of expression behavior analysis by [`ModuleInfo::process_function`]. But that code
379    /// has no access to entrypoint-specific information, so interface analysis uses this
380    /// function to record global uses there (like task shader payloads).
381    ///
382    /// [`add_ref_impl`]: Self::add_ref_impl
383    pub(super) fn insert_global_use(
384        &mut self,
385        global_use: GlobalUse,
386        global: Handle<crate::GlobalVariable>,
387    ) {
388        self.global_uses[global.index()] |= global_use;
389    }
390
391    /// Record a use of `expr` for its value.
392    ///
393    /// This is used for almost all expression references. Anything
394    /// that writes to the value `expr` points to, or otherwise wants
395    /// contribute flags other than `GlobalUse::READ`, should use
396    /// `add_ref_impl` directly.
397    #[must_use]
398    fn add_ref(&mut self, expr: Handle<crate::Expression>) -> NonUniformResult {
399        self.add_ref_impl(expr, GlobalUse::READ)
400    }
401
402    /// Record a use of `expr`, and indicate which global variable it
403    /// refers to, if any.
404    ///
405    /// Bump `expr`'s reference count, and return its uniformity.
406    ///
407    /// If `expr` is a pointer to a global variable, or some part
408    /// thereof, store that global in `*assignable_global`. Leave the
409    /// global's uses unchanged.
410    ///
411    /// This is used to determine the [`assignable_global`] for
412    /// [`Access`] and [`AccessIndex`] expressions that ultimately
413    /// refer to a global variable. Those expressions don't contribute
414    /// any usage to the global themselves; that depends on how other
415    /// expressions use them.
416    ///
417    /// [`assignable_global`]: ExpressionInfo::assignable_global
418    /// [`Access`]: crate::Expression::Access
419    /// [`AccessIndex`]: crate::Expression::AccessIndex
420    #[must_use]
421    fn add_assignable_ref(
422        &mut self,
423        expr: Handle<crate::Expression>,
424        assignable_global: &mut Option<Handle<crate::GlobalVariable>>,
425    ) -> NonUniformResult {
426        let info = &mut self.expressions[expr.index()];
427        info.ref_count += 1;
428        // propagate the assignable global up the chain, till it either hits
429        // a value-type expression, or the assignment statement.
430        if let Some(global) = info.assignable_global {
431            if let Some(_old) = assignable_global.replace(global) {
432                unreachable!()
433            }
434        }
435        info.uniformity.non_uniform_result
436    }
437
438    /// Inherit information from a called function.
439    fn process_call(
440        &mut self,
441        callee: &Self,
442        arguments: &[Handle<crate::Expression>],
443        expression_arena: &Arena<crate::Expression>,
444    ) -> Result<FunctionUniformity, WithSpan<FunctionError>> {
445        self.sampling_set
446            .extend(callee.sampling_set.iter().cloned());
447        for sampling in callee.sampling.iter() {
448            // If the callee was passed the texture or sampler as an argument,
449            // we may now be able to determine which globals those referred to.
450            let image_storage = match sampling.image {
451                GlobalOrArgument::Global(var) => GlobalOrArgument::Global(var),
452                GlobalOrArgument::Argument(i) => {
453                    let Some(handle) = arguments.get(i as usize).cloned() else {
454                        // Argument count mismatch, will be reported later by validate_call
455                        break;
456                    };
457                    GlobalOrArgument::from_expression(expression_arena, handle).map_err(
458                        |source| {
459                            FunctionError::Expression { handle, source }
460                                .with_span_handle(handle, expression_arena)
461                        },
462                    )?
463                }
464            };
465
466            let sampler_storage = match sampling.sampler {
467                GlobalOrArgument::Global(var) => GlobalOrArgument::Global(var),
468                GlobalOrArgument::Argument(i) => {
469                    let Some(handle) = arguments.get(i as usize).cloned() else {
470                        // Argument count mismatch, will be reported later by validate_call
471                        break;
472                    };
473                    GlobalOrArgument::from_expression(expression_arena, handle).map_err(
474                        |source| {
475                            FunctionError::Expression { handle, source }
476                                .with_span_handle(handle, expression_arena)
477                        },
478                    )?
479                }
480            };
481
482            // If we've managed to pin both the image and sampler down to
483            // specific globals, record that in our `sampling_set`. Otherwise,
484            // record as much as we do know in our own `sampling` table, for our
485            // callers to sort out.
486            match (image_storage, sampler_storage) {
487                (GlobalOrArgument::Global(image), GlobalOrArgument::Global(sampler)) => {
488                    self.sampling_set.insert(SamplingKey { image, sampler });
489                }
490                (image, sampler) => {
491                    self.sampling.insert(Sampling { image, sampler });
492                }
493            }
494        }
495
496        // Inherit global use and immediate slot tracking from our callees.
497        for (mine, other) in self.global_uses.iter_mut().zip(callee.global_uses.iter()) {
498            *mine |= *other;
499        }
500
501        Ok(FunctionUniformity {
502            result: callee.uniformity.clone(),
503            exit: if callee.may_kill {
504                ExitFlags::MAY_KILL
505            } else {
506                ExitFlags::empty()
507            },
508        })
509    }
510
511    /// Compute the [`ExpressionInfo`] for `handle`.
512    ///
513    /// Replace the dummy entry in [`self.expressions`] for `handle`
514    /// with a real `ExpressionInfo` value describing that expression.
515    ///
516    /// This function is called as part of a forward sweep through the
517    /// arena, so we can assume that all earlier expressions in the
518    /// arena already have valid info. Since expressions only depend
519    /// on earlier expressions, this includes all our subexpressions.
520    ///
521    /// Adjust the reference counts on all expressions we use.
522    ///
523    /// Also populate the [`sampling_set`], [`sampling`] and
524    /// [`global_uses`] fields of `self`.
525    ///
526    /// [`self.expressions`]: FunctionInfo::expressions
527    /// [`sampling_set`]: FunctionInfo::sampling_set
528    /// [`sampling`]: FunctionInfo::sampling
529    /// [`global_uses`]: FunctionInfo::global_uses
530    #[allow(clippy::or_fun_call)]
531    fn process_expression(
532        &mut self,
533        handle: Handle<crate::Expression>,
534        expression_arena: &Arena<crate::Expression>,
535        other_functions: &[FunctionInfo],
536        resolve_context: &ResolveContext,
537        capabilities: super::Capabilities,
538    ) -> Result<(), ExpressionError> {
539        use crate::{Expression as E, SampleLevel as Sl};
540
541        let expression = &expression_arena[handle];
542        let mut assignable_global = None;
543        let uniformity = match *expression {
544            E::Access { base, index } => {
545                let base_ty = self[base].ty.inner_with(resolve_context.types);
546
547                // build up the caps needed if this is indexed non-uniformly
548                let mut needed_caps = super::Capabilities::empty();
549                let is_binding_array = match *base_ty {
550                    crate::TypeInner::BindingArray {
551                        base: array_element_ty_handle,
552                        ..
553                    } => {
554                        // We're a binding array, so lets use the type of _what_ we are array of to determine if we can non-uniformly index it.
555                        let array_element_ty =
556                            &resolve_context.types[array_element_ty_handle].inner;
557
558                        needed_caps |= match *array_element_ty {
559                            // If we're an image, use the appropriate capability.
560                            crate::TypeInner::Image { class, .. } => match class {
561                                crate::ImageClass::Storage { .. } => {
562                                    super::Capabilities::STORAGE_TEXTURE_BINDING_ARRAY_NON_UNIFORM_INDEXING
563                                }
564                                _ => {
565                                    super::Capabilities::TEXTURE_AND_SAMPLER_BINDING_ARRAY_NON_UNIFORM_INDEXING
566                                }
567                            },
568                            crate::TypeInner::Sampler { .. } => {
569                                super::Capabilities::TEXTURE_AND_SAMPLER_BINDING_ARRAY_NON_UNIFORM_INDEXING
570                            }
571                            // If we're anything but an image or sampler, assume we're a buffer and use the address space.
572                            _ => {
573                                if let E::GlobalVariable(global_handle) = expression_arena[base] {
574                                    let global = &resolve_context.global_vars[global_handle];
575                                    match global.space {
576                                        crate::AddressSpace::Uniform => {
577                                            super::Capabilities::BUFFER_BINDING_ARRAY_NON_UNIFORM_INDEXING
578                                        }
579                                        crate::AddressSpace::Storage { .. } => {
580                                            super::Capabilities::STORAGE_BUFFER_BINDING_ARRAY_NON_UNIFORM_INDEXING
581                                        }
582                                        _ => unreachable!(),
583                                    }
584                                } else {
585                                    unreachable!()
586                                }
587                            }
588                        };
589
590                        true
591                    }
592                    _ => false,
593                };
594
595                if self[index].uniformity.non_uniform_result.is_some()
596                    && !capabilities.contains(needed_caps)
597                    && is_binding_array
598                {
599                    return Err(ExpressionError::MissingCapabilities(needed_caps));
600                }
601
602                Uniformity {
603                    non_uniform_result: self
604                        .add_assignable_ref(base, &mut assignable_global)
605                        .or(self.add_ref(index)),
606                    requirements: UniformityRequirements::empty(),
607                }
608            }
609            E::AccessIndex { base, .. } => Uniformity {
610                non_uniform_result: self.add_assignable_ref(base, &mut assignable_global),
611                requirements: UniformityRequirements::empty(),
612            },
613            // always uniform
614            E::Splat { size: _, value } => Uniformity {
615                non_uniform_result: self.add_ref(value),
616                requirements: UniformityRequirements::empty(),
617            },
618            E::Swizzle { vector, .. } => Uniformity {
619                non_uniform_result: self.add_ref(vector),
620                requirements: UniformityRequirements::empty(),
621            },
622            E::Literal(_) | E::Constant(_) | E::Override(_) | E::ZeroValue(_) => Uniformity::new(),
623            E::Compose { ref components, .. } => {
624                let non_uniform_result = components
625                    .iter()
626                    .fold(None, |nur, &comp| nur.or(self.add_ref(comp)));
627                Uniformity {
628                    non_uniform_result,
629                    requirements: UniformityRequirements::empty(),
630                }
631            }
632            // depends on the builtin
633            E::FunctionArgument(index) => {
634                let arg = &resolve_context.arguments[index as usize];
635                let uniform = match arg.binding {
636                    Some(crate::Binding::BuiltIn(
637                        // per-work-group built-ins are uniform
638                        crate::BuiltIn::WorkGroupId
639                        | crate::BuiltIn::WorkGroupSize
640                        | crate::BuiltIn::NumWorkGroups,
641                    )) => true,
642                    _ => false,
643                };
644                Uniformity {
645                    non_uniform_result: if uniform { None } else { Some(handle) },
646                    requirements: UniformityRequirements::empty(),
647                }
648            }
649            // depends on the address space
650            E::GlobalVariable(gh) => {
651                use crate::AddressSpace as As;
652                assignable_global = Some(gh);
653                let var = &resolve_context.global_vars[gh];
654                let uniform = match var.space {
655                    // local data is non-uniform
656                    As::Function | As::Private | As::RayPayload | As::IncomingRayPayload => false,
657                    // workgroup memory is exclusively accessed by the group
658                    // task payload memory is very similar to workgroup memory
659                    As::WorkGroup | As::TaskPayload => true,
660                    // uniform data
661                    As::Uniform | As::Immediate => true,
662                    // storage data is only uniform when read-only
663                    As::Storage { access } => !access.contains(crate::StorageAccess::STORE),
664                    As::Handle => false,
665                };
666                Uniformity {
667                    non_uniform_result: if uniform { None } else { Some(handle) },
668                    requirements: UniformityRequirements::empty(),
669                }
670            }
671            E::LocalVariable(_) => Uniformity {
672                non_uniform_result: Some(handle),
673                requirements: UniformityRequirements::empty(),
674            },
675            E::Load { pointer } => {
676                let non_uniform_result = self.add_ref(pointer);
677                Uniformity {
678                    non_uniform_result,
679                    requirements: UniformityRequirements::empty(),
680                }
681            }
682            E::ImageSample {
683                image,
684                sampler,
685                gather: _,
686                coordinate,
687                array_index,
688                offset,
689                level,
690                depth_ref,
691                clamp_to_edge: _,
692            } => {
693                let image_storage = GlobalOrArgument::from_expression(expression_arena, image)?;
694                let sampler_storage = GlobalOrArgument::from_expression(expression_arena, sampler)?;
695
696                match (image_storage, sampler_storage) {
697                    (GlobalOrArgument::Global(image), GlobalOrArgument::Global(sampler)) => {
698                        self.sampling_set.insert(SamplingKey { image, sampler });
699                    }
700                    _ => {
701                        self.sampling.insert(Sampling {
702                            image: image_storage,
703                            sampler: sampler_storage,
704                        });
705                    }
706                }
707
708                // "nur" == "Non-Uniform Result"
709                let array_nur = array_index.and_then(|h| self.add_ref(h));
710                let level_nur = match level {
711                    Sl::Auto | Sl::Zero => None,
712                    Sl::Exact(h) | Sl::Bias(h) => self.add_ref(h),
713                    Sl::Gradient { x, y } => self.add_ref(x).or(self.add_ref(y)),
714                };
715                let dref_nur = depth_ref.and_then(|h| self.add_ref(h));
716                let offset_nur = offset.and_then(|h| self.add_ref(h));
717                Uniformity {
718                    non_uniform_result: self
719                        .add_ref(image)
720                        .or(self.add_ref(sampler))
721                        .or(self.add_ref(coordinate))
722                        .or(array_nur)
723                        .or(level_nur)
724                        .or(dref_nur)
725                        .or(offset_nur),
726                    requirements: if level.implicit_derivatives() {
727                        UniformityRequirements::IMPLICIT_LEVEL
728                    } else {
729                        UniformityRequirements::empty()
730                    },
731                }
732            }
733            E::ImageLoad {
734                image,
735                coordinate,
736                array_index,
737                sample,
738                level,
739            } => {
740                let array_nur = array_index.and_then(|h| self.add_ref(h));
741                let sample_nur = sample.and_then(|h| self.add_ref(h));
742                let level_nur = level.and_then(|h| self.add_ref(h));
743                Uniformity {
744                    non_uniform_result: self
745                        .add_ref(image)
746                        .or(self.add_ref(coordinate))
747                        .or(array_nur)
748                        .or(sample_nur)
749                        .or(level_nur),
750                    requirements: UniformityRequirements::empty(),
751                }
752            }
753            E::ImageQuery { image, query } => {
754                let query_nur = match query {
755                    crate::ImageQuery::Size { level: Some(h) } => self.add_ref(h),
756                    _ => None,
757                };
758                Uniformity {
759                    non_uniform_result: self.add_ref_impl(image, GlobalUse::QUERY).or(query_nur),
760                    requirements: UniformityRequirements::empty(),
761                }
762            }
763            E::Unary { expr, .. } => Uniformity {
764                non_uniform_result: self.add_ref(expr),
765                requirements: UniformityRequirements::empty(),
766            },
767            E::Binary { left, right, .. } => Uniformity {
768                non_uniform_result: self.add_ref(left).or(self.add_ref(right)),
769                requirements: UniformityRequirements::empty(),
770            },
771            E::Select {
772                condition,
773                accept,
774                reject,
775            } => Uniformity {
776                non_uniform_result: self
777                    .add_ref(condition)
778                    .or(self.add_ref(accept))
779                    .or(self.add_ref(reject)),
780                requirements: UniformityRequirements::empty(),
781            },
782            // explicit derivatives require uniform
783            E::Derivative { expr, .. } => Uniformity {
784                //Note: taking a derivative of a uniform doesn't make it non-uniform
785                non_uniform_result: self.add_ref(expr),
786                requirements: UniformityRequirements::DERIVATIVE,
787            },
788            E::Relational { argument, .. } => Uniformity {
789                non_uniform_result: self.add_ref(argument),
790                requirements: UniformityRequirements::empty(),
791            },
792            E::Math {
793                fun: _,
794                arg,
795                arg1,
796                arg2,
797                arg3,
798            } => {
799                let arg1_nur = arg1.and_then(|h| self.add_ref(h));
800                let arg2_nur = arg2.and_then(|h| self.add_ref(h));
801                let arg3_nur = arg3.and_then(|h| self.add_ref(h));
802                Uniformity {
803                    non_uniform_result: self.add_ref(arg).or(arg1_nur).or(arg2_nur).or(arg3_nur),
804                    requirements: UniformityRequirements::empty(),
805                }
806            }
807            E::As { expr, .. } => Uniformity {
808                non_uniform_result: self.add_ref(expr),
809                requirements: UniformityRequirements::empty(),
810            },
811            E::CallResult(function) => other_functions[function.index()].uniformity.clone(),
812            E::AtomicResult { .. } | E::RayQueryProceedResult => Uniformity {
813                non_uniform_result: Some(handle),
814                requirements: UniformityRequirements::empty(),
815            },
816            E::WorkGroupUniformLoadResult { .. } => Uniformity {
817                // The result of WorkGroupUniformLoad is always uniform by definition
818                non_uniform_result: None,
819                // The call is what cares about uniformity, not the expression
820                // This expression is never emitted, so this requirement should never be used anyway?
821                requirements: UniformityRequirements::empty(),
822            },
823            E::ArrayLength(expr) => Uniformity {
824                non_uniform_result: self.add_ref_impl(expr, GlobalUse::QUERY),
825                requirements: UniformityRequirements::empty(),
826            },
827            E::RayQueryGetIntersection {
828                query,
829                committed: _,
830            } => Uniformity {
831                non_uniform_result: self.add_ref(query),
832                requirements: UniformityRequirements::empty(),
833            },
834            E::SubgroupBallotResult => Uniformity {
835                non_uniform_result: Some(handle),
836                requirements: UniformityRequirements::empty(),
837            },
838            E::SubgroupOperationResult { .. } => Uniformity {
839                non_uniform_result: Some(handle),
840                requirements: UniformityRequirements::empty(),
841            },
842            E::RayQueryVertexPositions {
843                query,
844                committed: _,
845            } => Uniformity {
846                non_uniform_result: self.add_ref(query),
847                requirements: UniformityRequirements::empty(),
848            },
849            E::CooperativeLoad { ref data, .. } => Uniformity {
850                non_uniform_result: self.add_ref(data.pointer).or(self.add_ref(data.stride)),
851                requirements: UniformityRequirements::COOP_OPS,
852            },
853            E::CooperativeMultiplyAdd { a, b, c } => Uniformity {
854                non_uniform_result: self.add_ref(a).or(self.add_ref(b).or(self.add_ref(c))),
855                requirements: UniformityRequirements::COOP_OPS,
856            },
857        };
858
859        let ty = resolve_context.resolve(expression, |h| Ok(&self[h].ty))?;
860        self.expressions[handle.index()] = ExpressionInfo {
861            uniformity,
862            ref_count: 0,
863            assignable_global,
864            ty,
865        };
866        Ok(())
867    }
868
869    /// Analyzes the uniformity requirements of a block (as a sequence of statements).
870    /// Returns the uniformity characteristics at the *function* level, i.e.
871    /// whether or not the function requires to be called in uniform control flow,
872    /// and whether the produced result is not disrupting the control flow.
873    ///
874    /// The parent control flow is uniform if `disruptor.is_none()`.
875    ///
876    /// Returns a `NonUniformControlFlow` error if any of the expressions in the block
877    /// require uniformity, but the current flow is non-uniform.
878    #[allow(clippy::or_fun_call)]
879    fn process_block(
880        &mut self,
881        statements: &crate::Block,
882        other_functions: &[FunctionInfo],
883        mut disruptor: Option<UniformityDisruptor>,
884        expression_arena: &Arena<crate::Expression>,
885        diagnostic_filter_arena: &Arena<DiagnosticFilterNode>,
886    ) -> Result<FunctionUniformity, WithSpan<FunctionError>> {
887        use crate::Statement as S;
888
889        let mut combined_uniformity = FunctionUniformity::new();
890        for statement in statements {
891            let uniformity = match *statement {
892                S::Emit(ref range) => {
893                    let mut requirements = UniformityRequirements::empty();
894                    for expr in range.clone() {
895                        let req = self.expressions[expr.index()].uniformity.requirements;
896                        if self
897                            .flags
898                            .contains(ValidationFlags::CONTROL_FLOW_UNIFORMITY)
899                            && !req.is_empty()
900                        {
901                            if let Some(cause) = disruptor {
902                                let severity = DiagnosticFilterNode::search(
903                                    self.diagnostic_filter_leaf,
904                                    diagnostic_filter_arena,
905                                    StandardFilterableTriggeringRule::DerivativeUniformity,
906                                );
907                                severity.report_diag(
908                                    FunctionError::NonUniformControlFlow(req, expr, cause)
909                                        .with_span_handle(expr, expression_arena),
910                                    // TODO: Yes, this isn't contextualized with source, because
911                                    // the user is supposed to render what would normally be an
912                                    // error here. Once we actually support warning-level
913                                    // diagnostic items, then we won't need this non-compliant hack:
914                                    // <https://github.com/gfx-rs/wgpu/issues/6458>
915                                    |e, level| log::log!(level, "{e}"),
916                                )?;
917                            }
918                        }
919                        requirements |= req;
920                    }
921                    FunctionUniformity {
922                        result: Uniformity {
923                            non_uniform_result: None,
924                            requirements,
925                        },
926                        exit: ExitFlags::empty(),
927                    }
928                }
929                S::Break | S::Continue => FunctionUniformity::new(),
930                S::Kill => FunctionUniformity {
931                    result: Uniformity::new(),
932                    exit: if disruptor.is_some() {
933                        ExitFlags::MAY_KILL
934                    } else {
935                        ExitFlags::empty()
936                    },
937                },
938                S::ControlBarrier(_) | S::MemoryBarrier(_) => FunctionUniformity {
939                    result: Uniformity {
940                        non_uniform_result: None,
941                        requirements: UniformityRequirements::WORK_GROUP_BARRIER,
942                    },
943                    exit: ExitFlags::empty(),
944                },
945                S::WorkGroupUniformLoad { pointer, .. } => {
946                    let _condition_nur = self.add_ref(pointer);
947
948                    // Don't check that this call occurs in uniform control flow until Naga implements WGSL's standard
949                    // uniformity analysis (https://github.com/gfx-rs/naga/issues/1744).
950                    // The uniformity analysis Naga uses now is less accurate than the one in the WGSL standard,
951                    // causing Naga to reject correct uses of `workgroupUniformLoad` in some interesting programs.
952
953                    /*
954                    if self
955                        .flags
956                        .contains(super::ValidationFlags::CONTROL_FLOW_UNIFORMITY)
957                    {
958                        let condition_nur = self.add_ref(pointer);
959                        let this_disruptor =
960                            disruptor.or(condition_nur.map(UniformityDisruptor::Expression));
961                        if let Some(cause) = this_disruptor {
962                            return Err(FunctionError::NonUniformWorkgroupUniformLoad(cause)
963                                .with_span_static(*span, "WorkGroupUniformLoad"));
964                        }
965                    } */
966                    FunctionUniformity {
967                        result: Uniformity {
968                            non_uniform_result: None,
969                            requirements: UniformityRequirements::WORK_GROUP_BARRIER,
970                        },
971                        exit: ExitFlags::empty(),
972                    }
973                }
974                S::Block(ref b) => self.process_block(
975                    b,
976                    other_functions,
977                    disruptor,
978                    expression_arena,
979                    diagnostic_filter_arena,
980                )?,
981                S::If {
982                    condition,
983                    ref accept,
984                    ref reject,
985                } => {
986                    let condition_nur = self.add_ref(condition);
987                    let branch_disruptor =
988                        disruptor.or(condition_nur.map(UniformityDisruptor::Expression));
989                    let accept_uniformity = self.process_block(
990                        accept,
991                        other_functions,
992                        branch_disruptor,
993                        expression_arena,
994                        diagnostic_filter_arena,
995                    )?;
996                    let reject_uniformity = self.process_block(
997                        reject,
998                        other_functions,
999                        branch_disruptor,
1000                        expression_arena,
1001                        diagnostic_filter_arena,
1002                    )?;
1003                    accept_uniformity | reject_uniformity
1004                }
1005                S::Switch {
1006                    selector,
1007                    ref cases,
1008                } => {
1009                    let selector_nur = self.add_ref(selector);
1010                    let branch_disruptor =
1011                        disruptor.or(selector_nur.map(UniformityDisruptor::Expression));
1012                    let mut uniformity = FunctionUniformity::new();
1013                    let mut case_disruptor = branch_disruptor;
1014                    for case in cases.iter() {
1015                        let case_uniformity = self.process_block(
1016                            &case.body,
1017                            other_functions,
1018                            case_disruptor,
1019                            expression_arena,
1020                            diagnostic_filter_arena,
1021                        )?;
1022                        case_disruptor = if case.fall_through {
1023                            case_disruptor.or(case_uniformity.exit_disruptor())
1024                        } else {
1025                            branch_disruptor
1026                        };
1027                        uniformity = uniformity | case_uniformity;
1028                    }
1029                    uniformity
1030                }
1031                S::Loop {
1032                    ref body,
1033                    ref continuing,
1034                    break_if,
1035                } => {
1036                    let body_uniformity = self.process_block(
1037                        body,
1038                        other_functions,
1039                        disruptor,
1040                        expression_arena,
1041                        diagnostic_filter_arena,
1042                    )?;
1043                    let continuing_disruptor = disruptor.or(body_uniformity.exit_disruptor());
1044                    let continuing_uniformity = self.process_block(
1045                        continuing,
1046                        other_functions,
1047                        continuing_disruptor,
1048                        expression_arena,
1049                        diagnostic_filter_arena,
1050                    )?;
1051                    if let Some(expr) = break_if {
1052                        let _ = self.add_ref(expr);
1053                    }
1054                    body_uniformity | continuing_uniformity
1055                }
1056                S::Return { value } => FunctionUniformity {
1057                    result: Uniformity {
1058                        non_uniform_result: value.and_then(|expr| self.add_ref(expr)),
1059                        requirements: UniformityRequirements::empty(),
1060                    },
1061                    exit: if disruptor.is_some() {
1062                        ExitFlags::MAY_RETURN
1063                    } else {
1064                        ExitFlags::empty()
1065                    },
1066                },
1067                // Here and below, the used expressions are already emitted,
1068                // and their results do not affect the function return value,
1069                // so we can ignore their non-uniformity.
1070                S::Store { pointer, value } => {
1071                    let _ = self.add_ref_impl(pointer, GlobalUse::WRITE);
1072                    let _ = self.add_ref(value);
1073                    FunctionUniformity::new()
1074                }
1075                S::ImageStore {
1076                    image,
1077                    coordinate,
1078                    array_index,
1079                    value,
1080                } => {
1081                    let _ = self.add_ref_impl(image, GlobalUse::WRITE);
1082                    if let Some(expr) = array_index {
1083                        let _ = self.add_ref(expr);
1084                    }
1085                    let _ = self.add_ref(coordinate);
1086                    let _ = self.add_ref(value);
1087                    FunctionUniformity::new()
1088                }
1089                S::Call {
1090                    function,
1091                    ref arguments,
1092                    result: _,
1093                } => {
1094                    for &argument in arguments {
1095                        let _ = self.add_ref(argument);
1096                    }
1097                    let info = &other_functions[function.index()];
1098                    //Note: the result is validated by the Validator, not here
1099                    self.process_call(info, arguments, expression_arena)?
1100                }
1101                S::Atomic {
1102                    pointer,
1103                    ref fun,
1104                    value,
1105                    result: _,
1106                } => {
1107                    let _ = self.add_ref_impl(pointer, GlobalUse::READ | GlobalUse::WRITE);
1108                    let _ = self.add_ref(value);
1109                    if let crate::AtomicFunction::Exchange { compare: Some(cmp) } = *fun {
1110                        let _ = self.add_ref(cmp);
1111                    }
1112                    FunctionUniformity::new()
1113                }
1114                S::ImageAtomic {
1115                    image,
1116                    coordinate,
1117                    array_index,
1118                    fun: _,
1119                    value,
1120                } => {
1121                    let _ = self.add_ref_impl(image, GlobalUse::ATOMIC);
1122                    let _ = self.add_ref(coordinate);
1123                    if let Some(expr) = array_index {
1124                        let _ = self.add_ref(expr);
1125                    }
1126                    let _ = self.add_ref(value);
1127                    FunctionUniformity::new()
1128                }
1129                S::RayQuery { query, ref fun } => {
1130                    let _ = self.add_ref(query);
1131                    match *fun {
1132                        crate::RayQueryFunction::Initialize {
1133                            acceleration_structure,
1134                            descriptor,
1135                        } => {
1136                            let _ = self.add_ref(acceleration_structure);
1137                            let _ = self.add_ref(descriptor);
1138                        }
1139                        crate::RayQueryFunction::Proceed { result: _ } => {}
1140                        crate::RayQueryFunction::GenerateIntersection { hit_t } => {
1141                            let _ = self.add_ref(hit_t);
1142                        }
1143                        crate::RayQueryFunction::ConfirmIntersection => {}
1144                        crate::RayQueryFunction::Terminate => {}
1145                    }
1146                    FunctionUniformity::new()
1147                }
1148                S::SubgroupBallot {
1149                    result: _,
1150                    predicate,
1151                } => {
1152                    if let Some(predicate) = predicate {
1153                        let _ = self.add_ref(predicate);
1154                    }
1155                    FunctionUniformity::new()
1156                }
1157                S::SubgroupCollectiveOperation {
1158                    op: _,
1159                    collective_op: _,
1160                    argument,
1161                    result: _,
1162                } => {
1163                    let _ = self.add_ref(argument);
1164                    FunctionUniformity::new()
1165                }
1166                S::SubgroupGather {
1167                    mode,
1168                    argument,
1169                    result: _,
1170                } => {
1171                    let _ = self.add_ref(argument);
1172                    match mode {
1173                        crate::GatherMode::BroadcastFirst => {}
1174                        crate::GatherMode::Broadcast(index)
1175                        | crate::GatherMode::Shuffle(index)
1176                        | crate::GatherMode::ShuffleDown(index)
1177                        | crate::GatherMode::ShuffleUp(index)
1178                        | crate::GatherMode::ShuffleXor(index)
1179                        | crate::GatherMode::QuadBroadcast(index) => {
1180                            let _ = self.add_ref(index);
1181                        }
1182                        crate::GatherMode::QuadSwap(_) => {}
1183                    }
1184                    FunctionUniformity::new()
1185                }
1186                S::CooperativeStore { target, ref data } => FunctionUniformity {
1187                    result: Uniformity {
1188                        non_uniform_result: self
1189                            .add_ref(target)
1190                            .or(self.add_ref_impl(data.pointer, GlobalUse::WRITE))
1191                            .or(self.add_ref(data.stride)),
1192                        requirements: UniformityRequirements::COOP_OPS,
1193                    },
1194                    exit: ExitFlags::empty(),
1195                },
1196                S::RayPipelineFunction(ref fun) => {
1197                    match *fun {
1198                        crate::RayPipelineFunction::TraceRay {
1199                            acceleration_structure,
1200                            descriptor,
1201                            payload,
1202                        } => {
1203                            let _ = self.add_ref(acceleration_structure);
1204                            let _ = self.add_ref(descriptor);
1205                            let _ = self.add_ref(payload);
1206                        }
1207                    }
1208                    FunctionUniformity::new()
1209                }
1210            };
1211
1212            disruptor = disruptor.or(uniformity.exit_disruptor());
1213            combined_uniformity = combined_uniformity | uniformity;
1214        }
1215        Ok(combined_uniformity)
1216    }
1217}
1218
1219impl ModuleInfo {
1220    /// Populates `self.const_expression_types`
1221    pub(super) fn process_const_expression(
1222        &mut self,
1223        handle: Handle<crate::Expression>,
1224        resolve_context: &ResolveContext,
1225        gctx: crate::proc::GlobalCtx,
1226    ) -> Result<(), super::ConstExpressionError> {
1227        self.const_expression_types[handle.index()] =
1228            resolve_context.resolve(&gctx.global_expressions[handle], |h| Ok(&self[h]))?;
1229        Ok(())
1230    }
1231
1232    /// Builds the `FunctionInfo` based on the function, and validates the
1233    /// uniform control flow if required by the expressions of this function.
1234    pub(super) fn process_function(
1235        &self,
1236        fun: &crate::Function,
1237        module: &crate::Module,
1238        flags: ValidationFlags,
1239        capabilities: super::Capabilities,
1240    ) -> Result<FunctionInfo, WithSpan<FunctionError>> {
1241        let mut info = FunctionInfo {
1242            flags,
1243            available_stages: ShaderStages::all(),
1244            uniformity: Uniformity::new(),
1245            may_kill: false,
1246            sampling_set: crate::FastHashSet::default(),
1247            global_uses: vec![GlobalUse::empty(); module.global_variables.len()].into_boxed_slice(),
1248            expressions: vec![ExpressionInfo::new(); fun.expressions.len()].into_boxed_slice(),
1249            sampling: crate::FastHashSet::default(),
1250            dual_source_blending: false,
1251            diagnostic_filter_leaf: fun.diagnostic_filter_leaf,
1252        };
1253        let resolve_context =
1254            ResolveContext::with_locals(module, &fun.local_variables, &fun.arguments);
1255
1256        for (handle, _) in fun.expressions.iter() {
1257            if let Err(source) = info.process_expression(
1258                handle,
1259                &fun.expressions,
1260                &self.functions,
1261                &resolve_context,
1262                capabilities,
1263            ) {
1264                return Err(FunctionError::Expression { handle, source }
1265                    .with_span_handle(handle, &fun.expressions));
1266            }
1267        }
1268
1269        for (_, expr) in fun.local_variables.iter() {
1270            if let Some(init) = expr.init {
1271                let _ = info.add_ref(init);
1272            }
1273        }
1274
1275        let uniformity = info.process_block(
1276            &fun.body,
1277            &self.functions,
1278            None,
1279            &fun.expressions,
1280            &module.diagnostic_filters,
1281        )?;
1282        info.uniformity = uniformity.result;
1283        info.may_kill = uniformity.exit.contains(ExitFlags::MAY_KILL);
1284
1285        // If there are any globals referenced directly by a named expression,
1286        // ensure they are marked as used even if they are not referenced
1287        // anywhere else. An important case where this matters is phony
1288        // assignments used to include a global in the shader's resource
1289        // interface. https://www.w3.org/TR/WGSL/#phony-assignment-section
1290        for &handle in fun.named_expressions.keys() {
1291            if let Some(global) = info[handle].assignable_global {
1292                if info.global_uses[global.index()].is_empty() {
1293                    info.global_uses[global.index()] = GlobalUse::QUERY;
1294                }
1295            }
1296        }
1297
1298        Ok(info)
1299    }
1300
1301    pub fn get_entry_point(&self, index: usize) -> &FunctionInfo {
1302        &self.entry_points[index]
1303    }
1304}
1305
1306#[test]
1307fn uniform_control_flow() {
1308    use crate::{Expression as E, Statement as S};
1309
1310    let mut type_arena = crate::UniqueArena::new();
1311    let ty = type_arena.insert(
1312        crate::Type {
1313            name: None,
1314            inner: crate::TypeInner::Vector {
1315                size: crate::VectorSize::Bi,
1316                scalar: crate::Scalar::F32,
1317            },
1318        },
1319        Default::default(),
1320    );
1321    let mut global_var_arena = Arena::new();
1322    let non_uniform_global = global_var_arena.append(
1323        crate::GlobalVariable {
1324            name: None,
1325            init: None,
1326            ty,
1327            space: crate::AddressSpace::Handle,
1328            binding: None,
1329            memory_decorations: crate::MemoryDecorations::empty(),
1330        },
1331        Default::default(),
1332    );
1333    let uniform_global = global_var_arena.append(
1334        crate::GlobalVariable {
1335            name: None,
1336            init: None,
1337            ty,
1338            binding: None,
1339            space: crate::AddressSpace::Uniform,
1340            memory_decorations: crate::MemoryDecorations::empty(),
1341        },
1342        Default::default(),
1343    );
1344
1345    let mut expressions = Arena::new();
1346    // checks the uniform control flow
1347    let constant_expr = expressions.append(E::Literal(crate::Literal::U32(0)), Default::default());
1348    // checks the non-uniform control flow
1349    let derivative_expr = expressions.append(
1350        E::Derivative {
1351            axis: crate::DerivativeAxis::X,
1352            ctrl: crate::DerivativeControl::None,
1353            expr: constant_expr,
1354        },
1355        Default::default(),
1356    );
1357    let emit_range_constant_derivative = expressions.range_from(0);
1358    let non_uniform_global_expr =
1359        expressions.append(E::GlobalVariable(non_uniform_global), Default::default());
1360    let uniform_global_expr =
1361        expressions.append(E::GlobalVariable(uniform_global), Default::default());
1362    let emit_range_globals = expressions.range_from(2);
1363
1364    // checks the QUERY flag
1365    let query_expr = expressions.append(E::ArrayLength(uniform_global_expr), Default::default());
1366    // checks the transitive WRITE flag
1367    let access_expr = expressions.append(
1368        E::AccessIndex {
1369            base: non_uniform_global_expr,
1370            index: 1,
1371        },
1372        Default::default(),
1373    );
1374    let emit_range_query_access_globals = expressions.range_from(2);
1375
1376    let mut info = FunctionInfo {
1377        flags: ValidationFlags::all(),
1378        available_stages: ShaderStages::all(),
1379        uniformity: Uniformity::new(),
1380        may_kill: false,
1381        sampling_set: crate::FastHashSet::default(),
1382        global_uses: vec![GlobalUse::empty(); global_var_arena.len()].into_boxed_slice(),
1383        expressions: vec![ExpressionInfo::new(); expressions.len()].into_boxed_slice(),
1384        sampling: crate::FastHashSet::default(),
1385        dual_source_blending: false,
1386        diagnostic_filter_leaf: None,
1387    };
1388    let resolve_context = ResolveContext {
1389        constants: &Arena::new(),
1390        overrides: &Arena::new(),
1391        types: &type_arena,
1392        special_types: &crate::SpecialTypes::default(),
1393        global_vars: &global_var_arena,
1394        local_vars: &Arena::new(),
1395        functions: &Arena::new(),
1396        arguments: &[],
1397    };
1398    for (handle, _) in expressions.iter() {
1399        info.process_expression(
1400            handle,
1401            &expressions,
1402            &[],
1403            &resolve_context,
1404            super::Capabilities::empty(),
1405        )
1406        .unwrap();
1407    }
1408    assert_eq!(info[non_uniform_global_expr].ref_count, 1);
1409    assert_eq!(info[uniform_global_expr].ref_count, 1);
1410    assert_eq!(info[query_expr].ref_count, 0);
1411    assert_eq!(info[access_expr].ref_count, 0);
1412    assert_eq!(info[non_uniform_global], GlobalUse::empty());
1413    assert_eq!(info[uniform_global], GlobalUse::QUERY);
1414
1415    let stmt_emit1 = S::Emit(emit_range_globals.clone());
1416    let stmt_if_uniform = S::If {
1417        condition: uniform_global_expr,
1418        accept: crate::Block::new(),
1419        reject: vec![
1420            S::Emit(emit_range_constant_derivative.clone()),
1421            S::Store {
1422                pointer: constant_expr,
1423                value: derivative_expr,
1424            },
1425        ]
1426        .into(),
1427    };
1428    assert_eq!(
1429        info.process_block(
1430            &vec![stmt_emit1, stmt_if_uniform].into(),
1431            &[],
1432            None,
1433            &expressions,
1434            &Arena::new(),
1435        ),
1436        Ok(FunctionUniformity {
1437            result: Uniformity {
1438                non_uniform_result: None,
1439                requirements: UniformityRequirements::DERIVATIVE,
1440            },
1441            exit: ExitFlags::empty(),
1442        }),
1443    );
1444    assert_eq!(info[constant_expr].ref_count, 2);
1445    assert_eq!(info[uniform_global], GlobalUse::READ | GlobalUse::QUERY);
1446
1447    let stmt_emit2 = S::Emit(emit_range_globals.clone());
1448    let stmt_if_non_uniform = S::If {
1449        condition: non_uniform_global_expr,
1450        accept: vec![
1451            S::Emit(emit_range_constant_derivative),
1452            S::Store {
1453                pointer: constant_expr,
1454                value: derivative_expr,
1455            },
1456        ]
1457        .into(),
1458        reject: crate::Block::new(),
1459    };
1460    {
1461        let block_info = info.process_block(
1462            &vec![stmt_emit2.clone(), stmt_if_non_uniform.clone()].into(),
1463            &[],
1464            None,
1465            &expressions,
1466            &Arena::new(),
1467        );
1468        if DISABLE_UNIFORMITY_REQ_FOR_FRAGMENT_STAGE {
1469            assert_eq!(info[derivative_expr].ref_count, 2);
1470        } else {
1471            assert_eq!(
1472                block_info,
1473                Err(FunctionError::NonUniformControlFlow(
1474                    UniformityRequirements::DERIVATIVE,
1475                    derivative_expr,
1476                    UniformityDisruptor::Expression(non_uniform_global_expr)
1477                )
1478                .with_span()),
1479            );
1480            assert_eq!(info[derivative_expr].ref_count, 1);
1481
1482            // Test that the same thing passes when we disable the `derivative_uniformity`
1483            let mut diagnostic_filters = Arena::new();
1484            let diagnostic_filter_leaf = diagnostic_filters.append(
1485                DiagnosticFilterNode {
1486                    inner: crate::diagnostic_filter::DiagnosticFilter {
1487                        new_severity: crate::diagnostic_filter::Severity::Off,
1488                        triggering_rule:
1489                            crate::diagnostic_filter::FilterableTriggeringRule::Standard(
1490                                StandardFilterableTriggeringRule::DerivativeUniformity,
1491                            ),
1492                    },
1493                    parent: None,
1494                },
1495                crate::Span::default(),
1496            );
1497            let mut info = FunctionInfo {
1498                diagnostic_filter_leaf: Some(diagnostic_filter_leaf),
1499                ..info.clone()
1500            };
1501
1502            let block_info = info.process_block(
1503                &vec![stmt_emit2, stmt_if_non_uniform].into(),
1504                &[],
1505                None,
1506                &expressions,
1507                &diagnostic_filters,
1508            );
1509            assert_eq!(
1510                block_info,
1511                Ok(FunctionUniformity {
1512                    result: Uniformity {
1513                        non_uniform_result: None,
1514                        requirements: UniformityRequirements::DERIVATIVE,
1515                    },
1516                    exit: ExitFlags::empty()
1517                }),
1518            );
1519            assert_eq!(info[derivative_expr].ref_count, 2);
1520        }
1521    }
1522    assert_eq!(info[non_uniform_global], GlobalUse::READ);
1523
1524    let stmt_emit3 = S::Emit(emit_range_globals);
1525    let stmt_return_non_uniform = S::Return {
1526        value: Some(non_uniform_global_expr),
1527    };
1528    assert_eq!(
1529        info.process_block(
1530            &vec![stmt_emit3, stmt_return_non_uniform].into(),
1531            &[],
1532            Some(UniformityDisruptor::Return),
1533            &expressions,
1534            &Arena::new(),
1535        ),
1536        Ok(FunctionUniformity {
1537            result: Uniformity {
1538                non_uniform_result: Some(non_uniform_global_expr),
1539                requirements: UniformityRequirements::empty(),
1540            },
1541            exit: ExitFlags::MAY_RETURN,
1542        }),
1543    );
1544    assert_eq!(info[non_uniform_global_expr].ref_count, 3);
1545
1546    // Check that uniformity requirements reach through a pointer
1547    let stmt_emit4 = S::Emit(emit_range_query_access_globals);
1548    let stmt_assign = S::Store {
1549        pointer: access_expr,
1550        value: query_expr,
1551    };
1552    let stmt_return_pointer = S::Return {
1553        value: Some(access_expr),
1554    };
1555    let stmt_kill = S::Kill;
1556    assert_eq!(
1557        info.process_block(
1558            &vec![stmt_emit4, stmt_assign, stmt_kill, stmt_return_pointer].into(),
1559            &[],
1560            Some(UniformityDisruptor::Discard),
1561            &expressions,
1562            &Arena::new(),
1563        ),
1564        Ok(FunctionUniformity {
1565            result: Uniformity {
1566                non_uniform_result: Some(non_uniform_global_expr),
1567                requirements: UniformityRequirements::empty(),
1568            },
1569            exit: ExitFlags::all(),
1570        }),
1571    );
1572    assert_eq!(info[non_uniform_global], GlobalUse::READ | GlobalUse::WRITE);
1573}