Skip to content

Commit ea75a8c

Browse files
feat(wgsl-in): filter unif. analysis errors with derivative_uniformity
- Remove `DISABLE_UNIFORMITY_REQ_FOR_FRAGMENT_STAGE`. - Add `CHANGELOG` entry. - Add coverage to `naga`'s `valid::analyzer::uniform_control_flow` test.
1 parent b81fcb4 commit ea75a8c

File tree

3 files changed

+117
-29
lines changed

3 files changed

+117
-29
lines changed

CHANGELOG.md

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,62 @@ Bottom level categories:
4040

4141
## Unreleased
4242

43+
## Major changes
44+
45+
### The `diagnostic(…);` directive is now supported in WGSL
46+
47+
Naga now parses `diagnostic(…);` directives according to the WGSL spec. This allows users to control certain lints, similar to Rust's `allow`, `warn`, and `deny` attributes. For example, in standard WGSL (but, notably, not Naga yet—see <https://github.com/gfx-rs/wgpu/issues/4369>) this snippet would emit a uniformity error:
48+
49+
```wgsl
50+
@group(0) @binding(0) var s : sampler;
51+
@group(0) @binding(2) var tex : texture_2d<f32>;
52+
@group(1) @binding(0) var<storage, read> ro_buffer : array<f32, 4>;
53+
54+
@fragment
55+
fn main(@builtin(position) p : vec4f) -> @location(0) vec4f {
56+
if ro_buffer[0] == 0 {
57+
// Emits a derivative uniformity error during validation.
58+
return textureSample(tex, s, vec2(0.,0.));
59+
}
60+
61+
return vec4f(0.);
62+
}
63+
```
64+
65+
…but we can now silence it with the `off` severity level, like so:
66+
67+
```wgsl
68+
// Disable the diagnosic with this…
69+
diagnostic(off, derivative_uniformity);
70+
71+
@group(0) @binding(0) var s : sampler;
72+
@group(0) @binding(2) var tex : texture_2d<f32>;
73+
@group(1) @binding(0) var<storage, read> ro_buffer : array<f32, 4>;
74+
75+
@fragment
76+
fn main(@builtin(position) p : vec4f) -> @location(0) vec4f {
77+
if ro_buffer[0] == 0 {
78+
// Look ma, no error!
79+
return textureSample(tex, s, vec2(0.,0.));
80+
}
81+
82+
return vec4f(0.);
83+
}
84+
```
85+
86+
There are some limitations to keep in mind with this new functionality:
87+
88+
- We do not yet support `diagnostic(…)` rules in attribute position (i.e., `@diagnostic(…) fn my_func { … }`). This is being tracked in <https://github.com/gfx-rs/wgpu/issues/5320>. We expect that rules in `fn` attribute position will be relaxed shortly (see <https://github.com/gfx-rs/wgpu/pull/6353>), but the prioritization for statement positions is unclear. If you are blocked by not being able to parse `diagnostic(…)` rules in statement positions, please let us know in that issue, so we can determine how to prioritize it!
89+
- Standard WGSL specifies `error`, `warning`, `info`, and `off` severity levels. These are all technically usable now! A caveat, though: warning- and info-level are only emitted to `stderr` via the `log` façade, rather than being reported through a `Result::Err` in Naga or the `CompilationInfo` interface in `wgpu{,-core}`. This will require breaking changes in Naga to fix, and is being tracked by <https://github.com/gfx-rs/wgpu/issues/6458>.
90+
- Not all lints can be controlled with `diagnostic(…)` rules. In fact, only the `derivative_uniformity` triggering rule exists in the WGSL standard. That said, Naga contributors are excited to see how this level of control unlocks a new ecosystem of configurable diagnostics.
91+
- Finally, `diagnostic(…)` rules are not yet emitted in WGSL output. This means that `wgsl-in``wgsl-out` is currently a lossy process. We felt that it was important to unblock users who needed `diagnostic(…)` rules (i.e., <https://github.com/gfx-rs/wgpu/issues/3135>) before we took significant effort to fix this (tracked in <https://github.com/gfx-rs/wgpu/issues/6496>).
92+
93+
By @ErichDonGubler in [#6456](https://github.com/gfx-rs/wgpu/pull/6456), [#6148](https://github.com/gfx-rs/wgpu/pull/6148).
94+
4395
### New Features
4496

4597
#### Naga
4698

47-
- Parse `diagnostic(…)` directives, but don't implement any triggering rules yet. By @ErichDonGubler in [#6456](https://github.com/gfx-rs/wgpu/pull/6456).
4899
- Fix an issue where `naga` CLI would incorrectly skip the first positional argument when `--stdin-file-path` was specified. By @ErichDonGubler in [#6480](https://github.com/gfx-rs/wgpu/pull/6480).
49100
- Fix textureNumLevels in the GLSL backend. By @magcius in [#6483](https://github.com/gfx-rs/wgpu/pull/6483).
50101
- Implement `quantizeToF16()` for WGSL frontend, and WGSL, SPIR-V, HLSL, MSL, and GLSL backends. By @jamienicol in [#6519](https://github.com/gfx-rs/wgpu/pull/6519).

naga/src/diagnostic_filter.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ impl Severity {
4848
/// Naga does not yet support diagnostic items at lesser severities than
4949
/// [`Severity::Error`]. When this is implemented, this method should be deleted, and the
5050
/// severity should be used directly for reporting diagnostics.
51-
#[cfg(feature = "wgsl-in")]
5251
pub(crate) fn report_diag<E>(
5352
self,
5453
err: E,
@@ -101,7 +100,6 @@ impl FilterableTriggeringRule {
101100
///
102101
/// See <https://www.w3.org/TR/WGSL/#filterable-triggering-rules> for a table of default
103102
/// severities.
104-
#[allow(dead_code)]
105103
pub(crate) const fn default_severity(self) -> Severity {
106104
match self {
107105
FilterableTriggeringRule::DerivativeUniformity => Severity::Error,
@@ -227,7 +225,6 @@ impl DiagnosticFilterNode {
227225
/// is found, return the value of [`FilterableTriggeringRule::default_severity`].
228226
///
229227
/// When `triggering_rule` is not applicable to this node, its parent is consulted recursively.
230-
#[allow(dead_code)]
231228
pub(crate) fn search(
232229
node: Option<Handle<Self>>,
233230
arena: &Arena<Self>,

naga/src/valid/analyzer.rs

Lines changed: 65 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
//! - expression reference counts
77
88
use super::{ExpressionError, FunctionError, ModuleInfo, ShaderStages, ValidationFlags};
9-
use crate::diagnostic_filter::DiagnosticFilterNode;
9+
use crate::diagnostic_filter::{DiagnosticFilterNode, FilterableTriggeringRule};
1010
use crate::span::{AddSpan as _, WithSpan};
1111
use crate::{
1212
arena::{Arena, Handle},
@@ -16,19 +16,15 @@ use std::ops;
1616

1717
pub type NonUniformResult = Option<Handle<crate::Expression>>;
1818

19-
// Remove this once we update our uniformity analysis and
20-
// add support for the `derivative_uniformity` diagnostic
21-
const DISABLE_UNIFORMITY_REQ_FOR_FRAGMENT_STAGE: bool = true;
22-
2319
bitflags::bitflags! {
2420
/// Kinds of expressions that require uniform control flow.
2521
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
2622
#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
2723
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2824
pub struct UniformityRequirements: u8 {
2925
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 };
26+
const DERIVATIVE = 0x2;
27+
const IMPLICIT_LEVEL = 0x4;
3228
}
3329
}
3430

@@ -828,7 +824,6 @@ impl FunctionInfo {
828824
/// Returns a `NonUniformControlFlow` error if any of the expressions in the block
829825
/// require uniformity, but the current flow is non-uniform.
830826
#[allow(clippy::or_fun_call)]
831-
#[allow(clippy::only_used_in_recursion)]
832827
fn process_block(
833828
&mut self,
834829
statements: &crate::Block,
@@ -852,8 +847,21 @@ impl FunctionInfo {
852847
&& !req.is_empty()
853848
{
854849
if let Some(cause) = disruptor {
855-
return Err(FunctionError::NonUniformControlFlow(req, expr, cause)
856-
.with_span_handle(expr, expression_arena));
850+
let severity = DiagnosticFilterNode::search(
851+
self.diagnostic_filter_leaf,
852+
diagnostic_filter_arena,
853+
FilterableTriggeringRule::DerivativeUniformity,
854+
);
855+
severity.report_diag(
856+
FunctionError::NonUniformControlFlow(req, expr, cause)
857+
.with_span_handle(expr, expression_arena),
858+
// TODO: Yes, this isn't contextualized with source, because
859+
// the user is supposed to render what would normally be an
860+
// error here. Once we actually support warning-level
861+
// diagnostic items, then we won't need this non-compliant hack:
862+
// <https://github.com/gfx-rs/wgpu/issues/6458>
863+
|e, level| log::log!(level, "{e}"),
864+
)?;
857865
}
858866
}
859867
requirements |= req;
@@ -1336,26 +1344,58 @@ fn uniform_control_flow() {
13361344
};
13371345
{
13381346
let block_info = info.process_block(
1339-
&vec![stmt_emit2, stmt_if_non_uniform].into(),
1347+
&vec![stmt_emit2.clone(), stmt_if_non_uniform.clone()].into(),
13401348
&[],
13411349
None,
13421350
&expressions,
13431351
&Arena::new(),
13441352
);
1345-
if DISABLE_UNIFORMITY_REQ_FOR_FRAGMENT_STAGE {
1346-
assert_eq!(info[derivative_expr].ref_count, 2);
1347-
} else {
1348-
assert_eq!(
1349-
block_info,
1350-
Err(FunctionError::NonUniformControlFlow(
1351-
UniformityRequirements::DERIVATIVE,
1352-
derivative_expr,
1353-
UniformityDisruptor::Expression(non_uniform_global_expr)
1354-
)
1355-
.with_span()),
1356-
);
1357-
assert_eq!(info[derivative_expr].ref_count, 1);
1358-
}
1353+
assert_eq!(
1354+
block_info,
1355+
Err(FunctionError::NonUniformControlFlow(
1356+
UniformityRequirements::DERIVATIVE,
1357+
derivative_expr,
1358+
UniformityDisruptor::Expression(non_uniform_global_expr)
1359+
)
1360+
.with_span()),
1361+
);
1362+
assert_eq!(info[derivative_expr].ref_count, 1);
1363+
1364+
// Test that the same thing passes when we disable the `derivative_uniformity`
1365+
let mut diagnostic_filters = Arena::new();
1366+
let diagnostic_filter_leaf = diagnostic_filters.append(
1367+
DiagnosticFilterNode {
1368+
inner: crate::diagnostic_filter::DiagnosticFilter {
1369+
new_severity: crate::diagnostic_filter::Severity::Off,
1370+
triggering_rule: FilterableTriggeringRule::DerivativeUniformity,
1371+
},
1372+
parent: None,
1373+
},
1374+
crate::Span::default(),
1375+
);
1376+
let mut info = FunctionInfo {
1377+
diagnostic_filter_leaf: Some(diagnostic_filter_leaf),
1378+
..info.clone()
1379+
};
1380+
1381+
let block_info = info.process_block(
1382+
&vec![stmt_emit2, stmt_if_non_uniform].into(),
1383+
&[],
1384+
None,
1385+
&expressions,
1386+
&diagnostic_filters,
1387+
);
1388+
assert_eq!(
1389+
block_info,
1390+
Ok(FunctionUniformity {
1391+
result: Uniformity {
1392+
non_uniform_result: None,
1393+
requirements: UniformityRequirements::DERIVATIVE,
1394+
},
1395+
exit: ExitFlags::empty()
1396+
}),
1397+
);
1398+
assert_eq!(info[derivative_expr].ref_count, 2);
13591399
}
13601400
assert_eq!(info[non_uniform_global], GlobalUse::READ);
13611401

0 commit comments

Comments
 (0)