1use 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 #[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#[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 pub non_uniform_result: NonUniformResult,
54 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 const MAY_RETURN = 0x1;
74 const MAY_KILL = 0x2;
79 }
80}
81
82#[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 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 #[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 const READ = 0x1;
133 const WRITE = 0x2;
135 const QUERY = 0x4;
137 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))]
153pub struct ExpressionInfo {
155 pub uniformity: Uniformity,
161
162 pub ref_count: usize,
168
169 assignable_global: Option<Handle<crate::GlobalVariable>>,
183
184 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 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 flags: ValidationFlags,
243 pub available_stages: ShaderStages,
245 pub uniformity: Uniformity,
247 pub may_kill: bool,
249
250 pub sampling_set: crate::FastHashSet<SamplingKey>,
265
266 pub global_uses: Box<[GlobalUse]>,
273
274 expressions: Box<[ExpressionInfo]>,
281
282 sampling: crate::FastHashSet<Sampling>,
295
296 pub dual_source_blending: bool,
298
299 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#[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 #[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 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 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 #[must_use]
398 fn add_ref(&mut self, expr: Handle<crate::Expression>) -> NonUniformResult {
399 self.add_ref_impl(expr, GlobalUse::READ)
400 }
401
402 #[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 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 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 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 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 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 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 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 #[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 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 let array_element_ty =
556 &resolve_context.types[array_element_ty_handle].inner;
557
558 needed_caps |= match *array_element_ty {
559 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 _ => {
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 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 E::FunctionArgument(index) => {
634 let arg = &resolve_context.arguments[index as usize];
635 let uniform = match arg.binding {
636 Some(crate::Binding::BuiltIn(
637 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 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 As::Function | As::Private | As::RayPayload | As::IncomingRayPayload => false,
657 As::WorkGroup | As::TaskPayload => true,
660 As::Uniform | As::Immediate => true,
662 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 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 E::Derivative { expr, .. } => Uniformity {
784 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 non_uniform_result: None,
819 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 #[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 |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 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 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 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 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 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 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 let constant_expr = expressions.append(E::Literal(crate::Literal::U32(0)), Default::default());
1348 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 let query_expr = expressions.append(E::ArrayLength(uniform_global_expr), Default::default());
1366 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 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 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}