Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add get_compilation_info #5410

Merged
merged 11 commits into from
Apr 29, 2024
24 changes: 23 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,27 @@ Due to a specification change `write_timestamp` is no longer supported on WebGPU

By @wumpf in [#5188](https://github.com/gfx-rs/wgpu/pull/5188)

#### Querying shader compilation errors

Wgpu now supports querying [shader compilation info](https://www.w3.org/TR/webgpu/#dom-gpushadermodule-getcompilationinfo).

This allows you to get more structured information about compilation errors, warnings and info:
```rust
...
let lighting_shader = ctx.device.create_shader_module(include_wgsl!("lighting.wgsl"));
let compilation_info = lighting_shader.get_compilation_info().await;
for message in compilation_info
.messages
.iter()
.filter(|m| m.message_type == wgpu::CompilationMessageType::Error)
{
let line = message.location.map(|l| l.line_number).unwrap_or(1);
println!("Compile error at line {line}");
}
```

By @stefnotch in [#5410](https://github.com/gfx-rs/wgpu/pull/5410)


#### Wgsl const evaluation for many more built-ins

Expand Down Expand Up @@ -123,6 +144,7 @@ By @atlv24 and @cwfitzgerald in [#5154](https://github.com/gfx-rs/wgpu/pull/5154
```
- Breaking change: [`wgpu_core::pipeline::ProgrammableStageDescriptor`](https://docs.rs/wgpu-core/latest/wgpu_core/pipeline/struct.ProgrammableStageDescriptor.html#structfield.entry_point) is now optional. By @ErichDonGubler in [#5305](https://github.com/gfx-rs/wgpu/pull/5305).
- `Features::downlevel{_webgl2,}_features` was made const by @MultisampledNight in [#5343](https://github.com/gfx-rs/wgpu/pull/5343)
- Breaking change: [`wgpu_core::pipeline::ShaderError`](https://docs.rs/wgpu-core/latest/wgpu_core/pipeline/struct.ShaderError.html) has been moved to `naga`. By @stefnotch in [#5410](https://github.com/gfx-rs/wgpu/pull/5410)
- More as_hal methods and improvements by @JMS55 in [#5452](https://github.com/gfx-rs/wgpu/pull/5452)
- Added `wgpu::CommandEncoder::as_hal_mut`
- Added `wgpu::TextureView::as_hal`
Expand Down Expand Up @@ -169,7 +191,7 @@ By @atlv24 and @cwfitzgerald in [#5154](https://github.com/gfx-rs/wgpu/pull/5154

- Improved `wgpu_hal` documentation. By @jimblandy in [#5516](https://github.com/gfx-rs/wgpu/pull/5516), [#5524](https://github.com/gfx-rs/wgpu/pull/5524), [#5562](https://github.com/gfx-rs/wgpu/pull/5562), [#5563](https://github.com/gfx-rs/wgpu/pull/5563), [#5566](https://github.com/gfx-rs/wgpu/pull/5566), [#5617](https://github.com/gfx-rs/wgpu/pull/5617), [#5618](https://github.com/gfx-rs/wgpu/pull/5618)
- Add mention of primitive restart in the description of `PrimitiveState::strip_index_format`. By @cpsdqs in [#5350](https://github.com/gfx-rs/wgpu/pull/5350)
- Document precise behaviour of `SourceLocation`. By @stefnotch in [#5386](https://github.com/gfx-rs/wgpu/pull/5386)
- Document and tweak precise behaviour of `SourceLocation`. By @stefnotch in [#5386](https://github.com/gfx-rs/wgpu/pull/5386) and [#5410](https://github.com/gfx-rs/wgpu/pull/5410)
- Give short example of WGSL `push_constant` syntax. By @waywardmonkeys in [#5393](https://github.com/gfx-rs/wgpu/pull/5393)
- Fix incorrect documentation of `Limits::max_compute_workgroup_storage_size` default value. By @atlv24 in [#5601](https://github.com/gfx-rs/wgpu/pull/5601)

Expand Down
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

74 changes: 74 additions & 0 deletions naga/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use std::{error::Error, fmt};

#[derive(Clone, Debug)]
pub struct ShaderError<E> {
/// The source code of the shader.
pub source: String,
pub label: Option<String>,
pub inner: Box<E>,
}

#[cfg(feature = "wgsl-in")]
impl fmt::Display for ShaderError<crate::front::wgsl::ParseError> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let label = self.label.as_deref().unwrap_or_default();
let string = self.inner.emit_to_string(&self.source);
write!(f, "\nShader '{label}' parsing {string}")
}
}
#[cfg(feature = "glsl-in")]
impl fmt::Display for ShaderError<crate::front::glsl::ParseErrors> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let label = self.label.as_deref().unwrap_or_default();
let string = self.inner.emit_to_string(&self.source);
write!(f, "\nShader '{label}' parsing {string}")
}
}
#[cfg(feature = "spv-in")]
impl fmt::Display for ShaderError<crate::front::spv::Error> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let label = self.label.as_deref().unwrap_or_default();
let string = self.inner.emit_to_string(&self.source);
write!(f, "\nShader '{label}' parsing {string}")
}
}
impl fmt::Display for ShaderError<crate::WithSpan<crate::valid::ValidationError>> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use codespan_reporting::{
diagnostic::{Diagnostic, Label},
files::SimpleFile,
term,
};

let label = self.label.as_deref().unwrap_or_default();
let files = SimpleFile::new(label, &self.source);
let config = term::Config::default();
let mut writer = term::termcolor::NoColor::new(Vec::new());

let diagnostic = Diagnostic::error().with_labels(
self.inner
.spans()
.map(|&(span, ref desc)| {
Label::primary((), span.to_range().unwrap()).with_message(desc.to_owned())
})
.collect(),
);

term::emit(&mut writer, &config, &files, &diagnostic).expect("cannot write error");

write!(
f,
"\nShader validation {}",
String::from_utf8_lossy(&writer.into_inner())
)
}
}
impl<E> Error for ShaderError<E>
where
ShaderError<E>: fmt::Display,
E: Error + 'static,
{
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(&self.inner)
}
}
18 changes: 13 additions & 5 deletions naga/src/front/glsl/error.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::token::TokenValue;
use crate::SourceLocation;
use crate::{proc::ConstantEvaluatorError, Span};
use codespan_reporting::diagnostic::{Diagnostic, Label};
use codespan_reporting::files::SimpleFile;
Expand Down Expand Up @@ -137,14 +138,21 @@ pub struct Error {
pub meta: Span,
}

impl Error {
/// Returns a [`SourceLocation`] for the error message.
pub fn location(&self, source: &str) -> Option<SourceLocation> {
Some(self.meta.location(source))
}
}

/// A collection of errors returned during shader parsing.
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct ParseError {
pub struct ParseErrors {
pub errors: Vec<Error>,
}

impl ParseError {
impl ParseErrors {
pub fn emit_to_writer(&self, writer: &mut impl WriteColor, source: &str) {
self.emit_to_writer_with_path(writer, source, "glsl");
}
Expand Down Expand Up @@ -172,19 +180,19 @@ impl ParseError {
}
}

impl std::fmt::Display for ParseError {
impl std::fmt::Display for ParseErrors {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.errors.iter().try_for_each(|e| write!(f, "{e:?}"))
}
}

impl std::error::Error for ParseError {
impl std::error::Error for ParseErrors {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}

impl From<Vec<Error>> for ParseError {
impl From<Vec<Error>> for ParseErrors {
fn from(errors: Vec<Error>) -> Self {
Self { errors }
}
Expand Down
4 changes: 2 additions & 2 deletions naga/src/front/glsl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ To begin, take a look at the documentation for the [`Frontend`].
*/

pub use ast::{Precision, Profile};
pub use error::{Error, ErrorKind, ExpectedToken, ParseError};
pub use error::{Error, ErrorKind, ExpectedToken, ParseErrors};
pub use token::TokenValue;

use crate::{proc::Layouter, FastHashMap, FastHashSet, Handle, Module, ShaderStage, Span, Type};
Expand Down Expand Up @@ -196,7 +196,7 @@ impl Frontend {
&mut self,
options: &Options,
source: &str,
) -> std::result::Result<Module, ParseError> {
) -> std::result::Result<Module, ParseErrors> {
self.reset(options.stage);

let lexer = lex::Lexer::new(source, &options.defines);
Expand Down
16 changes: 8 additions & 8 deletions naga/src/front/glsl/parser_tests.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::{
ast::Profile,
error::ExpectedToken,
error::{Error, ErrorKind, ParseError},
error::{Error, ErrorKind, ParseErrors},
token::TokenValue,
Frontend, Options, Span,
};
Expand All @@ -21,7 +21,7 @@ fn version() {
)
.err()
.unwrap(),
ParseError {
ParseErrors {
errors: vec![Error {
kind: ErrorKind::InvalidVersion(99000),
meta: Span::new(9, 14)
Expand All @@ -37,7 +37,7 @@ fn version() {
)
.err()
.unwrap(),
ParseError {
ParseErrors {
errors: vec![Error {
kind: ErrorKind::InvalidVersion(449),
meta: Span::new(9, 12)
Expand All @@ -53,7 +53,7 @@ fn version() {
)
.err()
.unwrap(),
ParseError {
ParseErrors {
errors: vec![Error {
kind: ErrorKind::InvalidProfile("smart".into()),
meta: Span::new(13, 18),
Expand All @@ -69,7 +69,7 @@ fn version() {
)
.err()
.unwrap(),
ParseError {
ParseErrors {
errors: vec![
Error {
kind: ErrorKind::PreprocessorError(PreprocessorError::UnexpectedHash,),
Expand Down Expand Up @@ -455,7 +455,7 @@ fn functions() {
)
.err()
.unwrap(),
ParseError {
ParseErrors {
errors: vec![Error {
kind: ErrorKind::SemanticError("Function already defined".into()),
meta: Span::new(134, 152),
Expand Down Expand Up @@ -634,7 +634,7 @@ fn implicit_conversions() {
)
.err()
.unwrap(),
ParseError {
ParseErrors {
errors: vec![Error {
kind: ErrorKind::SemanticError("Unknown function \'test\'".into()),
meta: Span::new(156, 165),
Expand All @@ -658,7 +658,7 @@ fn implicit_conversions() {
)
.err()
.unwrap(),
ParseError {
ParseErrors {
errors: vec![Error {
kind: ErrorKind::SemanticError("Ambiguous best function for \'test\'".into()),
meta: Span::new(158, 165),
Expand Down
2 changes: 1 addition & 1 deletion naga/src/front/spv/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use codespan_reporting::files::SimpleFile;
use codespan_reporting::term;
use termcolor::{NoColor, WriteColor};

#[derive(Debug, thiserror::Error)]
#[derive(Clone, Debug, thiserror::Error)]
pub enum Error {
#[error("invalid header")]
InvalidHeader,
Expand Down
1 change: 1 addition & 0 deletions naga/src/front/wgsl/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use thiserror::Error;
#[derive(Clone, Debug)]
pub struct ParseError {
message: String,
// The first span should be the primary span, and the other ones should be complementary.
labels: Vec<(Span, Cow<'static, str>)>,
notes: Vec<String>,
}
Expand Down
1 change: 1 addition & 0 deletions naga/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ pub mod back;
mod block;
#[cfg(feature = "compact")]
pub mod compact;
pub mod error;
pub mod front;
pub mod keywords;
pub mod proc;
Expand Down
8 changes: 4 additions & 4 deletions naga/src/span.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ impl Span {
pub fn location(&self, source: &str) -> SourceLocation {
let prefix = &source[..self.start as usize];
let line_number = prefix.matches('\n').count() as u32 + 1;
let line_start = prefix.rfind('\n').map(|pos| pos + 1).unwrap_or(0);
let line_position = source[line_start..self.start as usize].chars().count() as u32 + 1;
let line_start = prefix.rfind('\n').map(|pos| pos + 1).unwrap_or(0) as u32;
let line_position = self.start - line_start + 1;

SourceLocation {
line_number,
Expand Down Expand Up @@ -107,14 +107,14 @@ impl std::ops::Index<Span> for str {
/// Roughly corresponds to the positional members of [`GPUCompilationMessage`][gcm] from
/// the WebGPU specification, except
/// - `offset` and `length` are in bytes (UTF-8 code units), instead of UTF-16 code units.
/// - `line_position` counts entire Unicode code points, instead of UTF-16 code units.
/// - `line_position` is in bytes (UTF-8 code units), instead of UTF-16 code units.
///
/// [gcm]: https://www.w3.org/TR/webgpu/#gpucompilationmessage
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct SourceLocation {
/// 1-based line number.
pub line_number: u32,
/// 1-based column of the start of this span, counted in Unicode code points.
/// 1-based column in code units (in bytes) of the start of the span.
pub line_position: u32,
/// 0-based Offset in code units (in bytes) of the start of the span.
pub offset: u32,
Expand Down
2 changes: 2 additions & 0 deletions tests/tests/shader/compilation_messages/error_shader.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/*🐈🐈🐈🐈🐈🐈🐈*/?
// Expected Error: invalid character found
49 changes: 49 additions & 0 deletions tests/tests/shader/compilation_messages/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use wgpu::include_wgsl;

use wgpu_test::{gpu_test, GpuTestConfiguration, TestParameters};

#[gpu_test]
static SHADER_COMPILE_SUCCESS: GpuTestConfiguration = GpuTestConfiguration::new()
.parameters(TestParameters::default())
.run_async(|ctx| async move {
let sm = ctx
.device
.create_shader_module(include_wgsl!("successful_shader.wgsl"));

let compilation_info = sm.get_compilation_info().await;
for message in compilation_info.messages.iter() {
assert!(message.message_type != wgpu::CompilationMessageType::Error);
}
});

#[gpu_test]
static SHADER_COMPILE_ERROR: GpuTestConfiguration = GpuTestConfiguration::new()
.parameters(TestParameters::default())
.run_async(|ctx| async move {
ctx.device.push_error_scope(wgpu::ErrorFilter::Validation);
let sm = ctx
.device
.create_shader_module(include_wgsl!("error_shader.wgsl"));
assert!(pollster::block_on(ctx.device.pop_error_scope()).is_some());

let compilation_info = sm.get_compilation_info().await;
let error_message = compilation_info
.messages
.iter()
.find(|message| message.message_type == wgpu::CompilationMessageType::Error)
.expect("Expected error message not found");
let span = error_message.location.expect("Expected span not found");
assert_eq!(
span.offset, 32,
"Expected the offset to be 32, because we're counting UTF-8 bytes"
);
assert_eq!(span.length, 1, "Expected length to roughly be 1"); // Could be relaxed, depending on the parser requirements.
assert_eq!(
span.line_number, 1,
"Expected the line number to be 1, because we're counting lines from 1"
);
assert_eq!(
span.line_position, 33,
"Expected the column number to be 33, because we're counting lines from 1"
);
});
Loading
Loading