Skip to content

Commit 7a38061

Browse files
committed
fix(naga): Reject unsupported enable extensions
Previously we required that the enable extension be declared and that the capability be present if the functionality was used, but allowed the extension to be declared if the functionality was not used.
1 parent 1c94928 commit 7a38061

File tree

11 files changed

+322
-27
lines changed

11 files changed

+322
-27
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ Bottom level categories:
8989

9090
### Changes
9191

92+
#### General
93+
94+
- Naga and `wgpu` now reject shaders with an `enable` directive for functionality that is not available, even if that functionality is not used by the shader. By @andyleiserson in [#8913](https://github.com/gfx-rs/wgpu/pull/8913).
95+
9296
#### Naga
9397

9498
- Prevent UB from incorrectly using ray queries on HLSL. By @Vecvec in [#8763](https://github.com/gfx-rs/wgpu/pull/8763).

naga-cli/src/bin/naga.rs

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,14 @@ struct Args {
146146
/// defines to be passed to the parser (only glsl is supported)
147147
#[argh(option, short = 'D')]
148148
defines: Vec<Defines>,
149+
150+
/// capabilities for parsing and validation.
151+
///
152+
/// Can be a comma-separated list of capability names (e.g.,
153+
/// "shader_float16,dual_source_blending"), a numeric bitflags value (e.g.,
154+
/// "67108864"), the string "none", or the string "all".
155+
#[argh(option, default = "CapabilitiesArg(naga::valid::Capabilities::all())")]
156+
capabilities: CapabilitiesArg,
149157
}
150158

151159
/// Newtype so we can implement [`FromStr`] for `BoundsCheckPolicy`.
@@ -336,6 +344,39 @@ impl FromStr for Defines {
336344
}
337345
}
338346

347+
#[derive(Debug, Clone, Copy)]
348+
struct CapabilitiesArg(naga::valid::Capabilities);
349+
350+
impl FromStr for CapabilitiesArg {
351+
type Err = String;
352+
353+
fn from_str(s: &str) -> Result<Self, Self::Err> {
354+
use naga::valid::Capabilities;
355+
356+
let s = s.to_uppercase();
357+
358+
if s == "NONE" {
359+
Ok(Self(Capabilities::empty()))
360+
} else if s == "ALL" {
361+
Ok(Self(Capabilities::all()))
362+
} else if let Ok(bits) = s.parse::<u64>() {
363+
Capabilities::from_bits(bits)
364+
.map(Self)
365+
.ok_or_else(|| format!("Invalid capabilities bitflags value: {bits}"))
366+
} else {
367+
s.split(',')
368+
.fold(Ok(Capabilities::empty()), |acc, s| {
369+
acc.and_then(|acc| {
370+
Capabilities::from_name(s.trim())
371+
.map(|cap| acc | cap)
372+
.ok_or(format!("Unknown capability {}", s.trim()))
373+
})
374+
})
375+
.map(Self)
376+
}
377+
}
378+
}
379+
339380
#[derive(Default)]
340381
struct Parameters<'a> {
341382
validation_flags: naga::valid::ValidationFlags,
@@ -352,6 +393,7 @@ struct Parameters<'a> {
352393
input_kind: Option<InputKind>,
353394
shader_stage: Option<ShaderStage>,
354395
defines: FastHashMap<String, String>,
396+
capabilities: naga::valid::Capabilities,
355397

356398
/// We use this copy of `args.compact` to know whether we should pass the
357399
/// entrypoint to `process_overrides`, which will result in removal from
@@ -505,6 +547,7 @@ fn run() -> anyhow::Result<()> {
505547
);
506548

507549
params.compact = args.compact;
550+
params.capabilities = args.capabilities.0;
508551

509552
if args.bulk_validate {
510553
return bulk_validate(&args, &params);
@@ -682,7 +725,12 @@ fn parse_input(input_path: &Path, input: Vec<u8>, params: &Parameters) -> anyhow
682725
},
683726
InputKind::Wgsl => {
684727
let input = String::from_utf8(input)?;
685-
let result = naga::front::wgsl::parse_str(&input);
728+
let options = naga::front::wgsl::Options {
729+
parse_doc_comments: false,
730+
capabilities: params.capabilities,
731+
};
732+
let mut frontend = naga::front::wgsl::Frontend::new_with_options(options);
733+
let result = frontend.parse(&input);
686734
match result {
687735
Ok(v) => Parsed {
688736
module: v,
@@ -961,7 +1009,7 @@ fn bulk_validate(args: &Args, params: &Parameters) -> anyhow::Result<()> {
9611009
};
9621010

9631011
let mut validator =
964-
naga::valid::Validator::new(params.validation_flags, naga::valid::Capabilities::all());
1012+
naga::valid::Validator::new(params.validation_flags, params.capabilities);
9651013
validator.subgroup_stages(naga::valid::ShaderStages::all());
9661014
validator.subgroup_operations(naga::valid::SubgroupOperationSet::all());
9671015

naga-test/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ impl From<&WgslInParameters> for naga::front::wgsl::Options {
8484
fn from(value: &WgslInParameters) -> Self {
8585
Self {
8686
parse_doc_comments: value.parse_doc_comments,
87+
capabilities: naga::valid::Capabilities::all(),
8788
}
8889
}
8990
}

naga/src/front/wgsl/error.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,10 @@ pub(crate) enum Error<'a> {
381381
kind: EnableExtension,
382382
span: Span,
383383
},
384+
EnableExtensionNotSupported {
385+
kind: EnableExtension,
386+
span: Span,
387+
},
384388
LanguageExtensionNotYetImplemented {
385389
kind: UnimplementedLanguageExtension,
386390
span: Span,
@@ -1240,6 +1244,17 @@ impl<'a> Error<'a> {
12401244
]
12411245
},
12421246
},
1247+
Error::EnableExtensionNotSupported { kind, span } => ParseError {
1248+
message: format!(
1249+
"the `{}` extension is not supported in the current environment",
1250+
kind.to_ident()
1251+
),
1252+
labels: vec![(
1253+
span,
1254+
"unsupported enable-extension".into(),
1255+
)],
1256+
notes: vec![],
1257+
},
12431258
Error::LanguageExtensionNotYetImplemented { kind, span } => ParseError {
12441259
message: format!(
12451260
"the `{}` language extension is not yet supported",

naga/src/front/wgsl/parse/directive/enable_extension.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,22 @@ pub enum ImplementedEnableExtension {
174174
WgpuCooperativeMatrix,
175175
}
176176

177+
impl ImplementedEnableExtension {
178+
/// Returns the capability required for this enable extension.
179+
pub const fn capability(self) -> crate::valid::Capabilities {
180+
use crate::valid::Capabilities as C;
181+
match self {
182+
Self::F16 => C::SHADER_FLOAT16,
183+
Self::DualSourceBlending => C::DUAL_SOURCE_BLENDING,
184+
Self::ClipDistances => C::CLIP_DISTANCE,
185+
Self::WgpuMeshShader => C::MESH_SHADER,
186+
Self::WgpuRayQuery => C::RAY_QUERY,
187+
Self::WgpuRayQueryVertexReturn => C::RAY_HIT_VERTEX_POSITION,
188+
Self::WgpuCooperativeMatrix => C::COOPERATIVE_MATRIX,
189+
}
190+
}
191+
}
192+
177193
/// A variant of [`EnableExtension::Unimplemented`].
178194
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
179195
pub enum UnimplementedEnableExtension {

naga/src/front/wgsl/parse/mod.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,13 +287,16 @@ impl<'a> BindingParser<'a> {
287287
pub struct Options {
288288
/// Controls whether the parser should parse doc comments.
289289
pub parse_doc_comments: bool,
290+
/// Capabilities to enable during parsing.
291+
pub capabilities: crate::valid::Capabilities,
290292
}
291293

292294
impl Options {
293-
/// Creates a new [`Options`] without doc comments parsing.
295+
/// Creates a new default [`Options`].
294296
pub const fn new() -> Self {
295297
Options {
296298
parse_doc_comments: false,
299+
capabilities: crate::valid::Capabilities::all(),
297300
}
298301
}
299302
}
@@ -3280,6 +3283,14 @@ impl Parser {
32803283
}))
32813284
}
32823285
};
3286+
// Check if the required capability is supported
3287+
let required_capability = extension.capability();
3288+
if !options.capabilities.contains(required_capability) {
3289+
return Err(Box::new(Error::EnableExtensionNotSupported {
3290+
kind,
3291+
span,
3292+
}));
3293+
}
32833294
enable_extensions.add(extension);
32843295
Ok(())
32853296
})?;

naga/tests/naga/wgsl_errors.rs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4724,3 +4724,129 @@ fn mesh_shader_enable_extension() {
47244724
})
47254725
);
47264726
}
4727+
4728+
#[track_caller]
4729+
fn check_with_capabilities(input: &str, snapshot: &str, capabilities: Capabilities) {
4730+
let mut options = naga::front::wgsl::Options::new();
4731+
options.capabilities = capabilities;
4732+
let mut frontend = naga::front::wgsl::Frontend::new_with_options(options);
4733+
let output = match frontend.parse(input) {
4734+
Ok(_) => panic!("expected parser error, but parsing succeeded!"),
4735+
Err(err) => err.emit_to_string(input),
4736+
};
4737+
if output != snapshot {
4738+
for diff in diff::lines(snapshot, &output) {
4739+
match diff {
4740+
diff::Result::Left(l) => println!("-{l}"),
4741+
diff::Result::Both(l, _) => println!(" {l}"),
4742+
diff::Result::Right(r) => println!("+{r}"),
4743+
}
4744+
}
4745+
panic!("Error snapshot failed");
4746+
}
4747+
}
4748+
4749+
#[test]
4750+
fn enable_f16_without_capability() {
4751+
check_with_capabilities(
4752+
"enable f16;",
4753+
r###"error: the `f16` extension is not supported in the current environment
4754+
┌─ wgsl:1:8
4755+
4756+
1 │ enable f16;
4757+
│ ^^^ unsupported enable-extension
4758+
4759+
"###,
4760+
!Capabilities::SHADER_FLOAT16,
4761+
);
4762+
}
4763+
4764+
#[test]
4765+
fn enable_dual_source_blending_without_capability() {
4766+
check_with_capabilities(
4767+
"enable dual_source_blending;",
4768+
r###"error: the `dual_source_blending` extension is not supported in the current environment
4769+
┌─ wgsl:1:8
4770+
4771+
1 │ enable dual_source_blending;
4772+
│ ^^^^^^^^^^^^^^^^^^^^ unsupported enable-extension
4773+
4774+
"###,
4775+
!Capabilities::DUAL_SOURCE_BLENDING,
4776+
);
4777+
}
4778+
4779+
#[test]
4780+
fn enable_clip_distances_without_capability() {
4781+
check_with_capabilities(
4782+
"enable clip_distances;",
4783+
r###"error: the `clip_distances` extension is not supported in the current environment
4784+
┌─ wgsl:1:8
4785+
4786+
1 │ enable clip_distances;
4787+
│ ^^^^^^^^^^^^^^ unsupported enable-extension
4788+
4789+
"###,
4790+
!Capabilities::CLIP_DISTANCE,
4791+
);
4792+
}
4793+
4794+
#[test]
4795+
fn enable_wgpu_mesh_shader_without_capability() {
4796+
check_with_capabilities(
4797+
"enable wgpu_mesh_shader;",
4798+
r###"error: the `wgpu_mesh_shader` extension is not supported in the current environment
4799+
┌─ wgsl:1:8
4800+
4801+
1 │ enable wgpu_mesh_shader;
4802+
│ ^^^^^^^^^^^^^^^^ unsupported enable-extension
4803+
4804+
"###,
4805+
!Capabilities::MESH_SHADER,
4806+
);
4807+
}
4808+
4809+
#[test]
4810+
fn enable_wgpu_ray_query_without_capability() {
4811+
check_with_capabilities(
4812+
"enable wgpu_ray_query;",
4813+
r###"error: the `wgpu_ray_query` extension is not supported in the current environment
4814+
┌─ wgsl:1:8
4815+
4816+
1 │ enable wgpu_ray_query;
4817+
│ ^^^^^^^^^^^^^^ unsupported enable-extension
4818+
4819+
"###,
4820+
!Capabilities::RAY_QUERY,
4821+
);
4822+
}
4823+
4824+
#[test]
4825+
fn enable_wgpu_ray_query_vertex_return_without_capability() {
4826+
check_with_capabilities(
4827+
"enable wgpu_ray_query_vertex_return;",
4828+
r###"error: the `wgpu_ray_query_vertex_return` extension is not supported in the current environment
4829+
┌─ wgsl:1:8
4830+
4831+
1 │ enable wgpu_ray_query_vertex_return;
4832+
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unsupported enable-extension
4833+
4834+
"###,
4835+
!Capabilities::RAY_HIT_VERTEX_POSITION,
4836+
);
4837+
}
4838+
4839+
#[test]
4840+
fn enable_wgpu_cooperative_matrix_without_capability() {
4841+
check_with_capabilities(
4842+
"enable wgpu_cooperative_matrix;",
4843+
r###"error: the `wgpu_cooperative_matrix` extension is not supported in the current environment
4844+
┌─ wgsl:1:8
4845+
4846+
1 │ enable wgpu_cooperative_matrix;
4847+
│ ^^^^^^^^^^^^^^^^^^^^^^^ unsupported enable-extension
4848+
4849+
"###,
4850+
!Capabilities::COOPERATIVE_MATRIX,
4851+
);
4852+
}

tests/tests/wgpu-gpu/dual_source_blending.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ fn vs_main() -> @builtin(position) vec4f {
1818
"#;
1919

2020
const FRAGMENT_SHADER_WITHOUT_DUAL_SOURCE_BLENDING: &str = r#"
21-
@fragment
21+
@fragment
2222
fn fs_main() -> @location(0) vec4f {
2323
return vec4f(1.0);
2424
}
@@ -31,7 +31,7 @@ struct FragmentOutput {
3131
@location(0) @blend_src(1) output1_: vec4<f32>,
3232
}
3333
34-
@fragment
34+
@fragment
3535
fn fs_main() -> FragmentOutput {
3636
return FragmentOutput(vec4<f32>(0.4f, 0.3f, 0.2f, 0.1f), vec4<f32>(0.9f, 0.8f, 0.7f, 0.6f));
3737
}
@@ -112,7 +112,7 @@ async fn dual_source_blending_disabled(ctx: TestingContext) {
112112
source: ShaderSource::Wgsl(FRAGMENT_SHADER_WITH_DUAL_SOURCE_BLENDING.into()),
113113
});
114114
},
115-
Some("Capability Capabilities(DUAL_SOURCE_BLENDING) is not supported"),
115+
Some("the `dual_source_blending` extension is not supported in the current environment"),
116116
);
117117
}
118118

0 commit comments

Comments
 (0)