Skip to content

Commit f874ed0

Browse files
authored
Add get_compilation_info (#5410)
* Add get_compilation_info API * Rename glsl ParseError to ParseErrors * Document ParseError label order * Update line_position to count UTF-8 bytes
1 parent 8597367 commit f874ed0

File tree

21 files changed

+705
-191
lines changed

21 files changed

+705
-191
lines changed

CHANGELOG.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,27 @@ Due to a specification change `write_timestamp` is no longer supported on WebGPU
7373

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

76+
#### Querying shader compilation errors
77+
78+
Wgpu now supports querying [shader compilation info](https://www.w3.org/TR/webgpu/#dom-gpushadermodule-getcompilationinfo).
79+
80+
This allows you to get more structured information about compilation errors, warnings and info:
81+
```rust
82+
...
83+
let lighting_shader = ctx.device.create_shader_module(include_wgsl!("lighting.wgsl"));
84+
let compilation_info = lighting_shader.get_compilation_info().await;
85+
for message in compilation_info
86+
.messages
87+
.iter()
88+
.filter(|m| m.message_type == wgpu::CompilationMessageType::Error)
89+
{
90+
let line = message.location.map(|l| l.line_number).unwrap_or(1);
91+
println!("Compile error at line {line}");
92+
}
93+
```
94+
95+
By @stefnotch in [#5410](https://github.com/gfx-rs/wgpu/pull/5410)
96+
7697

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

@@ -125,6 +146,7 @@ By @atlv24 and @cwfitzgerald in [#5154](https://github.com/gfx-rs/wgpu/pull/5154
125146
```
126147
- 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).
127148
- `Features::downlevel{_webgl2,}_features` was made const by @MultisampledNight in [#5343](https://github.com/gfx-rs/wgpu/pull/5343)
149+
- 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)
128150
- More as_hal methods and improvements by @JMS55 in [#5452](https://github.com/gfx-rs/wgpu/pull/5452)
129151
- Added `wgpu::CommandEncoder::as_hal_mut`
130152
- Added `wgpu::TextureView::as_hal`
@@ -171,7 +193,7 @@ By @atlv24 and @cwfitzgerald in [#5154](https://github.com/gfx-rs/wgpu/pull/5154
171193

172194
- 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)
173195
- 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)
174-
- Document precise behaviour of `SourceLocation`. By @stefnotch in [#5386](https://github.com/gfx-rs/wgpu/pull/5386)
196+
- 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)
175197
- Give short example of WGSL `push_constant` syntax. By @waywardmonkeys in [#5393](https://github.com/gfx-rs/wgpu/pull/5393)
176198
- Fix incorrect documentation of `Limits::max_compute_workgroup_storage_size` default value. By @atlv24 in [#5601](https://github.com/gfx-rs/wgpu/pull/5601)
177199

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

naga/src/error.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
use std::{error::Error, fmt};
2+
3+
#[derive(Clone, Debug)]
4+
pub struct ShaderError<E> {
5+
/// The source code of the shader.
6+
pub source: String,
7+
pub label: Option<String>,
8+
pub inner: Box<E>,
9+
}
10+
11+
#[cfg(feature = "wgsl-in")]
12+
impl fmt::Display for ShaderError<crate::front::wgsl::ParseError> {
13+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
14+
let label = self.label.as_deref().unwrap_or_default();
15+
let string = self.inner.emit_to_string(&self.source);
16+
write!(f, "\nShader '{label}' parsing {string}")
17+
}
18+
}
19+
#[cfg(feature = "glsl-in")]
20+
impl fmt::Display for ShaderError<crate::front::glsl::ParseErrors> {
21+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22+
let label = self.label.as_deref().unwrap_or_default();
23+
let string = self.inner.emit_to_string(&self.source);
24+
write!(f, "\nShader '{label}' parsing {string}")
25+
}
26+
}
27+
#[cfg(feature = "spv-in")]
28+
impl fmt::Display for ShaderError<crate::front::spv::Error> {
29+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30+
let label = self.label.as_deref().unwrap_or_default();
31+
let string = self.inner.emit_to_string(&self.source);
32+
write!(f, "\nShader '{label}' parsing {string}")
33+
}
34+
}
35+
impl fmt::Display for ShaderError<crate::WithSpan<crate::valid::ValidationError>> {
36+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37+
use codespan_reporting::{
38+
diagnostic::{Diagnostic, Label},
39+
files::SimpleFile,
40+
term,
41+
};
42+
43+
let label = self.label.as_deref().unwrap_or_default();
44+
let files = SimpleFile::new(label, &self.source);
45+
let config = term::Config::default();
46+
let mut writer = term::termcolor::NoColor::new(Vec::new());
47+
48+
let diagnostic = Diagnostic::error().with_labels(
49+
self.inner
50+
.spans()
51+
.map(|&(span, ref desc)| {
52+
Label::primary((), span.to_range().unwrap()).with_message(desc.to_owned())
53+
})
54+
.collect(),
55+
);
56+
57+
term::emit(&mut writer, &config, &files, &diagnostic).expect("cannot write error");
58+
59+
write!(
60+
f,
61+
"\nShader validation {}",
62+
String::from_utf8_lossy(&writer.into_inner())
63+
)
64+
}
65+
}
66+
impl<E> Error for ShaderError<E>
67+
where
68+
ShaderError<E>: fmt::Display,
69+
E: Error + 'static,
70+
{
71+
fn source(&self) -> Option<&(dyn Error + 'static)> {
72+
Some(&self.inner)
73+
}
74+
}

naga/src/front/glsl/error.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use super::token::TokenValue;
2+
use crate::SourceLocation;
23
use crate::{proc::ConstantEvaluatorError, Span};
34
use codespan_reporting::diagnostic::{Diagnostic, Label};
45
use codespan_reporting::files::SimpleFile;
@@ -137,14 +138,21 @@ pub struct Error {
137138
pub meta: Span,
138139
}
139140

141+
impl Error {
142+
/// Returns a [`SourceLocation`] for the error message.
143+
pub fn location(&self, source: &str) -> Option<SourceLocation> {
144+
Some(self.meta.location(source))
145+
}
146+
}
147+
140148
/// A collection of errors returned during shader parsing.
141149
#[derive(Clone, Debug)]
142150
#[cfg_attr(test, derive(PartialEq))]
143-
pub struct ParseError {
151+
pub struct ParseErrors {
144152
pub errors: Vec<Error>,
145153
}
146154

147-
impl ParseError {
155+
impl ParseErrors {
148156
pub fn emit_to_writer(&self, writer: &mut impl WriteColor, source: &str) {
149157
self.emit_to_writer_with_path(writer, source, "glsl");
150158
}
@@ -172,19 +180,19 @@ impl ParseError {
172180
}
173181
}
174182

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

181-
impl std::error::Error for ParseError {
189+
impl std::error::Error for ParseErrors {
182190
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
183191
None
184192
}
185193
}
186194

187-
impl From<Vec<Error>> for ParseError {
195+
impl From<Vec<Error>> for ParseErrors {
188196
fn from(errors: Vec<Error>) -> Self {
189197
Self { errors }
190198
}

naga/src/front/glsl/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ To begin, take a look at the documentation for the [`Frontend`].
1313
*/
1414

1515
pub use ast::{Precision, Profile};
16-
pub use error::{Error, ErrorKind, ExpectedToken, ParseError};
16+
pub use error::{Error, ErrorKind, ExpectedToken, ParseErrors};
1717
pub use token::TokenValue;
1818

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

202202
let lexer = lex::Lexer::new(source, &options.defines);

naga/src/front/glsl/parser_tests.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use super::{
22
ast::Profile,
33
error::ExpectedToken,
4-
error::{Error, ErrorKind, ParseError},
4+
error::{Error, ErrorKind, ParseErrors},
55
token::TokenValue,
66
Frontend, Options, Span,
77
};
@@ -21,7 +21,7 @@ fn version() {
2121
)
2222
.err()
2323
.unwrap(),
24-
ParseError {
24+
ParseErrors {
2525
errors: vec![Error {
2626
kind: ErrorKind::InvalidVersion(99000),
2727
meta: Span::new(9, 14)
@@ -37,7 +37,7 @@ fn version() {
3737
)
3838
.err()
3939
.unwrap(),
40-
ParseError {
40+
ParseErrors {
4141
errors: vec![Error {
4242
kind: ErrorKind::InvalidVersion(449),
4343
meta: Span::new(9, 12)
@@ -53,7 +53,7 @@ fn version() {
5353
)
5454
.err()
5555
.unwrap(),
56-
ParseError {
56+
ParseErrors {
5757
errors: vec![Error {
5858
kind: ErrorKind::InvalidProfile("smart".into()),
5959
meta: Span::new(13, 18),
@@ -69,7 +69,7 @@ fn version() {
6969
)
7070
.err()
7171
.unwrap(),
72-
ParseError {
72+
ParseErrors {
7373
errors: vec![
7474
Error {
7575
kind: ErrorKind::PreprocessorError(PreprocessorError::UnexpectedHash,),
@@ -455,7 +455,7 @@ fn functions() {
455455
)
456456
.err()
457457
.unwrap(),
458-
ParseError {
458+
ParseErrors {
459459
errors: vec![Error {
460460
kind: ErrorKind::SemanticError("Function already defined".into()),
461461
meta: Span::new(134, 152),
@@ -634,7 +634,7 @@ fn implicit_conversions() {
634634
)
635635
.err()
636636
.unwrap(),
637-
ParseError {
637+
ParseErrors {
638638
errors: vec![Error {
639639
kind: ErrorKind::SemanticError("Unknown function \'test\'".into()),
640640
meta: Span::new(156, 165),
@@ -658,7 +658,7 @@ fn implicit_conversions() {
658658
)
659659
.err()
660660
.unwrap(),
661-
ParseError {
661+
ParseErrors {
662662
errors: vec![Error {
663663
kind: ErrorKind::SemanticError("Ambiguous best function for \'test\'".into()),
664664
meta: Span::new(158, 165),

naga/src/front/spv/error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use codespan_reporting::files::SimpleFile;
55
use codespan_reporting::term;
66
use termcolor::{NoColor, WriteColor};
77

8-
#[derive(Debug, thiserror::Error)]
8+
#[derive(Clone, Debug, thiserror::Error)]
99
pub enum Error {
1010
#[error("invalid header")]
1111
InvalidHeader,

naga/src/front/wgsl/error.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use thiserror::Error;
1313
#[derive(Clone, Debug)]
1414
pub struct ParseError {
1515
message: String,
16+
// The first span should be the primary span, and the other ones should be complementary.
1617
labels: Vec<(Span, Cow<'static, str>)>,
1718
notes: Vec<String>,
1819
}

naga/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ pub mod back;
274274
mod block;
275275
#[cfg(feature = "compact")]
276276
pub mod compact;
277+
pub mod error;
277278
pub mod front;
278279
pub mod keywords;
279280
pub mod proc;

naga/src/span.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ impl Span {
7272
pub fn location(&self, source: &str) -> SourceLocation {
7373
let prefix = &source[..self.start as usize];
7474
let line_number = prefix.matches('\n').count() as u32 + 1;
75-
let line_start = prefix.rfind('\n').map(|pos| pos + 1).unwrap_or(0);
76-
let line_position = source[line_start..self.start as usize].chars().count() as u32 + 1;
75+
let line_start = prefix.rfind('\n').map(|pos| pos + 1).unwrap_or(0) as u32;
76+
let line_position = self.start - line_start + 1;
7777

7878
SourceLocation {
7979
line_number,
@@ -107,14 +107,14 @@ impl std::ops::Index<Span> for str {
107107
/// Roughly corresponds to the positional members of [`GPUCompilationMessage`][gcm] from
108108
/// the WebGPU specification, except
109109
/// - `offset` and `length` are in bytes (UTF-8 code units), instead of UTF-16 code units.
110-
/// - `line_position` counts entire Unicode code points, instead of UTF-16 code units.
110+
/// - `line_position` is in bytes (UTF-8 code units), instead of UTF-16 code units.
111111
///
112112
/// [gcm]: https://www.w3.org/TR/webgpu/#gpucompilationmessage
113113
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
114114
pub struct SourceLocation {
115115
/// 1-based line number.
116116
pub line_number: u32,
117-
/// 1-based column of the start of this span, counted in Unicode code points.
117+
/// 1-based column in code units (in bytes) of the start of the span.
118118
pub line_position: u32,
119119
/// 0-based Offset in code units (in bytes) of the start of the span.
120120
pub offset: u32,

0 commit comments

Comments
 (0)