feat(wgsl-in): create skeleton for parsing directives

This commit is contained in:
Erich Gubler 2024-10-17 15:49:24 -04:00
parent cb31465811
commit b3f665be7d
8 changed files with 262 additions and 1 deletions

View File

@ -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). - 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). - 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). - 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 #### General

1
Cargo.lock generated
View File

@ -1897,6 +1897,7 @@ dependencies = [
"rustc-hash", "rustc-hash",
"serde", "serde",
"spirv 0.3.0+sdk-1.3.268.0", "spirv 0.3.0+sdk-1.3.268.0",
"strum",
"termcolor", "termcolor",
"thiserror", "thiserror",
"unicode-xid", "unicode-xid",

View File

@ -101,3 +101,4 @@ ron = "0.8.0"
rspirv = { version = "0.11", git = "https://github.com/gfx-rs/rspirv", rev = "b969f175d5663258b4891e44b76c1544da9661ab" } rspirv = { version = "0.11", git = "https://github.com/gfx-rs/rspirv", rev = "b969f175d5663258b4891e44b76c1544da9661ab" }
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }
spirv = { version = "0.3", features = ["deserialize"] } spirv = { version = "0.3", features = ["deserialize"] }
strum.workspace = true

View File

@ -1,3 +1,4 @@
use crate::front::wgsl::parse::directive::{DirectiveKind, UnimplementedDirectiveKind};
use crate::front::wgsl::parse::lexer::Token; use crate::front::wgsl::parse::lexer::Token;
use crate::front::wgsl::Scalar; use crate::front::wgsl::Scalar;
use crate::proc::{Alignment, ConstantEvaluatorError, ResolveError}; use crate::proc::{Alignment, ConstantEvaluatorError, ResolveError};
@ -265,6 +266,13 @@ pub(crate) enum Error<'a> {
PipelineConstantIDValue(Span), PipelineConstantIDValue(Span),
NotBool(Span), NotBool(Span),
ConstAssertFailed(Span), ConstAssertFailed(Span),
DirectiveNotYetImplemented {
kind: UnimplementedDirectiveKind,
span: Span,
},
DirectiveAfterFirstGlobalDecl {
directive_span: Span,
},
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -861,6 +869,36 @@ impl<'a> Error<'a> {
labels: vec![(span, "evaluates to false".into())], labels: vec![(span, "evaluates to false".into())],
notes: vec![], 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 ",
"<https://github.com/gfx-rs/wgpu/issues/{}>, ",
"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()],
},
} }
} }
} }

View File

@ -58,3 +58,21 @@ impl Frontend {
pub fn parse_str(source: &str) -> Result<crate::Module, ParseError> { pub fn parse_str(source: &str) -> Result<crate::Module, ParseError> {
Frontend::new().parse(source) 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");
}
}

View File

@ -0,0 +1,179 @@
//! WGSL directives. The focal point of this API is [`DirectiveKind`].
//!
//! See also <https://www.w3.org/TR/WGSL/#directives>.
/// 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<Self> {
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<Item = Self> {
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 <https://github.com/gfx-rs/wgpu/issues/5320>, 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 <https://github.com/gfx-rs/wgpu/issues/5476>, 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 <https://github.com/gfx-rs/wgpu/issues/6350>, 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<storage> thing: i32;
{directive};
"
);
assert_parse_err(&shader, expected_msg);
}
}
}

View File

@ -355,7 +355,6 @@ impl<'a> Lexer<'a> {
} }
} }
#[allow(dead_code)]
pub(in crate::front::wgsl) fn peek_ident_with_span( pub(in crate::front::wgsl) fn peek_ident_with_span(
&mut self, &mut self,
) -> Result<(&'a str, Span), Error<'a>> { ) -> Result<(&'a str, Span), Error<'a>> {

View File

@ -1,4 +1,5 @@
use crate::front::wgsl::error::{Error, ExpectedToken}; 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::lexer::{Lexer, Token};
use crate::front::wgsl::parse::number::Number; use crate::front::wgsl::parse::number::Number;
use crate::front::wgsl::Scalar; use crate::front::wgsl::Scalar;
@ -7,6 +8,7 @@ use crate::{Arena, FastIndexSet, Handle, ShaderStage, Span};
pub mod ast; pub mod ast;
pub mod conv; pub mod conv;
pub mod directive;
pub mod lexer; pub mod lexer;
pub mod number; pub mod number;
@ -136,6 +138,7 @@ enum Rule {
SingularExpr, SingularExpr,
UnaryExpr, UnaryExpr,
GeneralExpr, GeneralExpr,
Directive,
} }
struct ParsedAttribute<T> { struct ParsedAttribute<T> {
@ -2357,6 +2360,9 @@ impl Parser {
let start = lexer.start_byte_offset(); let start = lexer.start_byte_offset();
let kind = match lexer.next() { let kind = match lexer.next() {
(Token::Separator(';'), _) => None, (Token::Separator(';'), _) => None,
(Token::Word(word), directive_span) if DirectiveKind::from_ident(word).is_some() => {
return Err(Error::DirectiveAfterFirstGlobalDecl { directive_span });
}
(Token::Word("struct"), _) => { (Token::Word("struct"), _) => {
let name = lexer.next_ident()?; let name = lexer.next_ident()?;
@ -2474,6 +2480,24 @@ impl Parser {
let mut lexer = Lexer::new(source); let mut lexer = Lexer::new(source);
let mut tu = ast::TranslationUnit::default(); 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 { loop {
match self.global_decl(&mut lexer, &mut tu) { match self.global_decl(&mut lexer, &mut tu) {
Err(error) => return Err(error), Err(error) => return Err(error),