From b3f665be7d9127d8048ec3b556e2a50868e951df Mon Sep 17 00:00:00 2001 From: Erich Gubler Date: Thu, 17 Oct 2024 15:49:24 -0400 Subject: [PATCH] feat(wgsl-in): create skeleton for parsing directives --- CHANGELOG.md | 1 + Cargo.lock | 1 + naga/Cargo.toml | 1 + naga/src/front/wgsl/error.rs | 38 ++++++ naga/src/front/wgsl/mod.rs | 18 +++ naga/src/front/wgsl/parse/directive.rs | 179 +++++++++++++++++++++++++ naga/src/front/wgsl/parse/lexer.rs | 1 - naga/src/front/wgsl/parse/mod.rs | 24 ++++ 8 files changed, 262 insertions(+), 1 deletion(-) create mode 100644 naga/src/front/wgsl/parse/directive.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 894c38085..fdb858e19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -85,6 +85,7 @@ By @bradwerth [#6216](https://github.com/gfx-rs/wgpu/pull/6216). - Support local `const` declarations in WGSL. By @sagudev in [#6156](https://github.com/gfx-rs/wgpu/pull/6156). - Implemented `const_assert` in WGSL. By @sagudev in [#6198](https://github.com/gfx-rs/wgpu/pull/6198). - Support polyfilling `inverse` in WGSL. By @chyyran in [#6385](https://github.com/gfx-rs/wgpu/pull/6385). +- Add an internal skeleton for parsing `requires`, `enable`, and `diagnostic` directives that don't yet do anything besides emit nicer errors. By @ErichDonGubler in [#6352](https://github.com/gfx-rs/wgpu/pull/6352). #### General diff --git a/Cargo.lock b/Cargo.lock index 2c6eebb69..7eac04019 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1897,6 +1897,7 @@ dependencies = [ "rustc-hash", "serde", "spirv 0.3.0+sdk-1.3.268.0", + "strum", "termcolor", "thiserror", "unicode-xid", diff --git a/naga/Cargo.toml b/naga/Cargo.toml index 19912a36a..9ce43465d 100644 --- a/naga/Cargo.toml +++ b/naga/Cargo.toml @@ -101,3 +101,4 @@ ron = "0.8.0" rspirv = { version = "0.11", git = "https://github.com/gfx-rs/rspirv", rev = "b969f175d5663258b4891e44b76c1544da9661ab" } serde = { workspace = true, features = ["derive"] } spirv = { version = "0.3", features = ["deserialize"] } +strum.workspace = true diff --git a/naga/src/front/wgsl/error.rs b/naga/src/front/wgsl/error.rs index 7c65d93de..64fdc3de9 100644 --- a/naga/src/front/wgsl/error.rs +++ b/naga/src/front/wgsl/error.rs @@ -1,3 +1,4 @@ +use crate::front::wgsl::parse::directive::{DirectiveKind, UnimplementedDirectiveKind}; use crate::front::wgsl::parse::lexer::Token; use crate::front::wgsl::Scalar; use crate::proc::{Alignment, ConstantEvaluatorError, ResolveError}; @@ -265,6 +266,13 @@ pub(crate) enum Error<'a> { PipelineConstantIDValue(Span), NotBool(Span), ConstAssertFailed(Span), + DirectiveNotYetImplemented { + kind: UnimplementedDirectiveKind, + span: Span, + }, + DirectiveAfterFirstGlobalDecl { + directive_span: Span, + }, } #[derive(Clone, Debug)] @@ -861,6 +869,36 @@ impl<'a> Error<'a> { labels: vec![(span, "evaluates to false".into())], notes: vec![], }, + Error::DirectiveNotYetImplemented { kind, span } => ParseError { + message: format!( + "`{}` is not yet implemented", + DirectiveKind::Unimplemented(kind).to_ident() + ), + labels: vec![( + span, + "this global directive is standard, but not yet implemented".into(), + )], + notes: vec![format!( + concat!( + "Let Naga maintainers know that you ran into this at ", + ", ", + "so they can prioritize it!" + ), + kind.tracking_issue_num() + )], + }, + Error::DirectiveAfterFirstGlobalDecl { directive_span } => ParseError { + message: "expected global declaration, but found a global directive".into(), + labels: vec![( + directive_span, + "written after first global declaration".into(), + )], + notes: vec![concat!( + "global directives are only allowed before global declarations; ", + "maybe hoist this closer to the top of the shader module?" + ) + .into()], + }, } } } diff --git a/naga/src/front/wgsl/mod.rs b/naga/src/front/wgsl/mod.rs index aec1e657f..a54fd66f0 100644 --- a/naga/src/front/wgsl/mod.rs +++ b/naga/src/front/wgsl/mod.rs @@ -58,3 +58,21 @@ impl Frontend { pub fn parse_str(source: &str) -> Result { Frontend::new().parse(source) } + +#[cfg(test)] +#[track_caller] +pub fn assert_parse_err(input: &str, snapshot: &str) { + let output = parse_str(input) + .expect_err("expected parser error") + .emit_to_string(input); + if output != snapshot { + for diff in diff::lines(snapshot, &output) { + match diff { + diff::Result::Left(l) => println!("-{l}"), + diff::Result::Both(l, _) => println!(" {l}"), + diff::Result::Right(r) => println!("+{r}"), + } + } + panic!("Error snapshot failed"); + } +} diff --git a/naga/src/front/wgsl/parse/directive.rs b/naga/src/front/wgsl/parse/directive.rs new file mode 100644 index 000000000..871d627d2 --- /dev/null +++ b/naga/src/front/wgsl/parse/directive.rs @@ -0,0 +1,179 @@ +//! WGSL directives. The focal point of this API is [`DirectiveKind`]. +//! +//! See also . + +/// A parsed sentinel word indicating the type of directive to be parsed next. +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] +pub enum DirectiveKind { + Unimplemented(UnimplementedDirectiveKind), +} + +impl DirectiveKind { + const DIAGNOSTIC: &'static str = "diagnostic"; + const ENABLE: &'static str = "enable"; + const REQUIRES: &'static str = "requires"; + + /// Convert from a sentinel word in WGSL into its associated [`DirectiveKind`], if possible. + pub fn from_ident(s: &str) -> Option { + Some(match s { + Self::DIAGNOSTIC => Self::Unimplemented(UnimplementedDirectiveKind::Diagnostic), + Self::ENABLE => Self::Unimplemented(UnimplementedDirectiveKind::Enable), + Self::REQUIRES => Self::Unimplemented(UnimplementedDirectiveKind::Requires), + _ => return None, + }) + } + + /// Maps this [`DirectiveKind`] into the sentinel word associated with it in WGSL. + pub const fn to_ident(self) -> &'static str { + match self { + Self::Unimplemented(kind) => match kind { + UnimplementedDirectiveKind::Diagnostic => Self::DIAGNOSTIC, + UnimplementedDirectiveKind::Enable => Self::ENABLE, + UnimplementedDirectiveKind::Requires => Self::REQUIRES, + }, + } + } + + #[cfg(test)] + fn iter() -> impl Iterator { + use strum::IntoEnumIterator; + + UnimplementedDirectiveKind::iter().map(Self::Unimplemented) + } +} + +/// A [`DirectiveKind`] that is not yet implemented. See [`DirectiveKind::Unimplemented`]. +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] +#[cfg_attr(test, derive(strum::EnumIter))] +pub enum UnimplementedDirectiveKind { + Diagnostic, + Enable, + Requires, +} + +impl UnimplementedDirectiveKind { + pub const fn tracking_issue_num(self) -> u16 { + match self { + Self::Diagnostic => 5320, + Self::Requires => 6350, + Self::Enable => 5476, + } + } +} + +#[cfg(test)] +mod test { + use strum::IntoEnumIterator; + + use crate::front::wgsl::assert_parse_err; + + use super::{DirectiveKind, UnimplementedDirectiveKind}; + + #[test] + fn unimplemented_directives() { + for unsupported_shader in UnimplementedDirectiveKind::iter() { + let shader; + let expected_msg; + match unsupported_shader { + UnimplementedDirectiveKind::Diagnostic => { + shader = "diagnostic(off,derivative_uniformity);"; + expected_msg = "\ +error: `diagnostic` is not yet implemented + ┌─ wgsl:1:1 + │ +1 │ diagnostic(off,derivative_uniformity); + │ ^^^^^^^^^^ this global directive is standard, but not yet implemented + │ + = note: Let Naga maintainers know that you ran into this at , so they can prioritize it! + +"; + } + UnimplementedDirectiveKind::Enable => { + shader = "enable f16;"; + expected_msg = "\ +error: `enable` is not yet implemented + ┌─ wgsl:1:1 + │ +1 │ enable f16; + │ ^^^^^^ this global directive is standard, but not yet implemented + │ + = note: Let Naga maintainers know that you ran into this at , so they can prioritize it! + +"; + } + UnimplementedDirectiveKind::Requires => { + shader = "requires readonly_and_readwrite_storage_textures"; + expected_msg = "\ +error: `requires` is not yet implemented + ┌─ wgsl:1:1 + │ +1 │ requires readonly_and_readwrite_storage_textures + │ ^^^^^^^^ this global directive is standard, but not yet implemented + │ + = note: Let Naga maintainers know that you ran into this at , so they can prioritize it! + +"; + } + }; + + assert_parse_err(shader, expected_msg); + } + } + + #[test] + fn directive_after_global_decl() { + for unsupported_shader in DirectiveKind::iter() { + let directive; + let expected_msg; + match unsupported_shader { + DirectiveKind::Unimplemented(UnimplementedDirectiveKind::Diagnostic) => { + directive = "diagnostic(off,derivative_uniformity)"; + expected_msg = "\ +error: expected global declaration, but found a global directive + ┌─ wgsl:2:1 + │ +2 │ diagnostic(off,derivative_uniformity); + │ ^^^^^^^^^^ written after first global declaration + │ + = note: global directives are only allowed before global declarations; maybe hoist this closer to the top of the shader module? + +"; + } + DirectiveKind::Unimplemented(UnimplementedDirectiveKind::Enable) => { + directive = "enable f16"; + expected_msg = "\ +error: expected global declaration, but found a global directive + ┌─ wgsl:2:1 + │ +2 │ enable f16; + │ ^^^^^^ written after first global declaration + │ + = note: global directives are only allowed before global declarations; maybe hoist this closer to the top of the shader module? + +"; + } + DirectiveKind::Unimplemented(UnimplementedDirectiveKind::Requires) => { + directive = "requires readonly_and_readwrite_storage_textures"; + expected_msg = "\ +error: expected global declaration, but found a global directive + ┌─ wgsl:2:1 + │ +2 │ requires readonly_and_readwrite_storage_textures; + │ ^^^^^^^^ written after first global declaration + │ + = note: global directives are only allowed before global declarations; maybe hoist this closer to the top of the shader module? + +"; + } + } + + let shader = format!( + "\ +@group(0) @binding(0) var thing: i32; +{directive}; +" + ); + assert_parse_err(&shader, expected_msg); + } + } +} diff --git a/naga/src/front/wgsl/parse/lexer.rs b/naga/src/front/wgsl/parse/lexer.rs index 3d99eb19c..5dabcced8 100644 --- a/naga/src/front/wgsl/parse/lexer.rs +++ b/naga/src/front/wgsl/parse/lexer.rs @@ -355,7 +355,6 @@ impl<'a> Lexer<'a> { } } - #[allow(dead_code)] pub(in crate::front::wgsl) fn peek_ident_with_span( &mut self, ) -> Result<(&'a str, Span), Error<'a>> { diff --git a/naga/src/front/wgsl/parse/mod.rs b/naga/src/front/wgsl/parse/mod.rs index 3b1d60620..e7097345b 100644 --- a/naga/src/front/wgsl/parse/mod.rs +++ b/naga/src/front/wgsl/parse/mod.rs @@ -1,4 +1,5 @@ use crate::front::wgsl::error::{Error, ExpectedToken}; +use crate::front::wgsl::parse::directive::DirectiveKind; use crate::front::wgsl::parse::lexer::{Lexer, Token}; use crate::front::wgsl::parse::number::Number; use crate::front::wgsl::Scalar; @@ -7,6 +8,7 @@ use crate::{Arena, FastIndexSet, Handle, ShaderStage, Span}; pub mod ast; pub mod conv; +pub mod directive; pub mod lexer; pub mod number; @@ -136,6 +138,7 @@ enum Rule { SingularExpr, UnaryExpr, GeneralExpr, + Directive, } struct ParsedAttribute { @@ -2357,6 +2360,9 @@ impl Parser { let start = lexer.start_byte_offset(); let kind = match lexer.next() { (Token::Separator(';'), _) => None, + (Token::Word(word), directive_span) if DirectiveKind::from_ident(word).is_some() => { + return Err(Error::DirectiveAfterFirstGlobalDecl { directive_span }); + } (Token::Word("struct"), _) => { let name = lexer.next_ident()?; @@ -2474,6 +2480,24 @@ impl Parser { let mut lexer = Lexer::new(source); let mut tu = ast::TranslationUnit::default(); + + // Parse directives. + #[allow(clippy::never_loop, unreachable_code)] + while let Ok((ident, span)) = lexer.peek_ident_with_span() { + if let Some(kind) = DirectiveKind::from_ident(ident) { + self.push_rule_span(Rule::Directive, &mut lexer); + let _ = lexer.next_ident_with_span().unwrap(); + match kind { + DirectiveKind::Unimplemented(kind) => { + return Err(Error::DirectiveNotYetImplemented { kind, span }) + } + } + self.pop_rule_span(&lexer); + } else { + break; + } + } + loop { match self.global_decl(&mut lexer, &mut tu) { Err(error) => return Err(error),