[naga] Add DiagnosticDisplay and DiagnosticDebug wrappers.

Define two new types in `naga::common`, `DiagnosticDisplay` and
`DiagnosticDebug`, that allow Naga IR types to be formatted using
`core::fmt::Display` and `core::fmt::Debug`.

In `naga::common::wgsl`, add supporting implementations of
`TypeContext` for `GlobalCtx` and `UniqueArena<Type>`
This commit is contained in:
Jim Blandy 2025-03-22 17:33:31 -07:00 committed by Connor Fitzgerald
parent e284e8055f
commit 8f7dc8c070
4 changed files with 258 additions and 0 deletions

View File

@ -0,0 +1,86 @@
//! Displaying Naga IR terms in debugging output.
#[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))]
use crate::common::wgsl::TypeContext;
use crate::proc::TypeResolution;
use crate::{Handle, Scalar, Type, TypeInner, UniqueArena};
use core::fmt;
/// A wrapper for displaying Naga IR terms in debugging output.
///
/// This is like [`DiagnosticDisplay`], but requires weaker context
/// and produces correspondingly lower-fidelity output. For example,
/// this cannot show the override names for override-sized array
/// lengths.
///
/// [`DiagnosticDisplay`]: super::DiagnosticDisplay
pub struct DiagnosticDebug<T>(pub T);
impl fmt::Debug for DiagnosticDebug<(Handle<Type>, &UniqueArena<Type>)> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (handle, ctx) = self.0;
#[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))]
ctx.write_type(handle, f)?;
#[cfg(not(any(feature = "wgsl-in", feature = "wgsl-out")))]
{
let _ = ctx;
write!(f, "{handle:?}")?;
}
Ok(())
}
}
impl fmt::Debug for DiagnosticDebug<(&TypeInner, &UniqueArena<Type>)> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (inner, ctx) = self.0;
#[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))]
ctx.write_type_inner(inner, f)?;
#[cfg(not(any(feature = "wgsl-in", feature = "wgsl-out")))]
{
let _ = ctx;
write!(f, "{inner:?}")?;
}
Ok(())
}
}
impl fmt::Debug for DiagnosticDebug<(&TypeResolution, &UniqueArena<Type>)> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (resolution, ctx) = self.0;
#[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))]
ctx.write_type_resolution(resolution, f)?;
#[cfg(not(any(feature = "wgsl-in", feature = "wgsl-out")))]
{
let _ = ctx;
write!(f, "{resolution:?}")?;
}
Ok(())
}
}
impl fmt::Debug for DiagnosticDebug<Scalar> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let scalar = self.0;
#[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))]
f.write_str(&crate::common::wgsl::TryToWgsl::to_wgsl_for_diagnostics(
scalar,
))?;
#[cfg(not(any(feature = "wgsl-in", feature = "wgsl-out")))]
write!(f, "{scalar:?}")?;
Ok(())
}
}

View File

@ -0,0 +1,100 @@
//! Displaying Naga IR terms in diagnostic output.
use crate::proc::GlobalCtx;
use crate::{Handle, Scalar, Type, TypeInner};
#[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))]
use crate::common::wgsl::TypeContext;
use core::fmt;
/// A wrapper for displaying Naga IR terms in diagnostic output.
///
/// For some Naga IR type `T`, `DiagnosticDisplay<T>` implements
/// [`core::fmt::Display`] in a way that displays values of type `T`
/// appropriately for diagnostic messages presented to human readers.
///
/// For example, the implementation of [`Display`] for
/// `DiagnosticDisplay<Scalar>` formats the type represented by the
/// given [`Scalar`] appropriately for users.
///
/// Some types like `Handle<Type>` require contextual information like
/// a type arena to be displayed. In such cases, we implement [`Display`]
/// for a type like `DiagnosticDisplay<(Handle<Type>, GlobalCtx)>`, where
/// the [`GlobalCtx`] type provides the necessary context.
///
/// If you only need debugging output, [`DiagnosticDebug`] uses
/// easier-to-obtain context types but still does a good enough job
/// for logging or debugging.
///
/// [`Display`]: core::fmt::Display
/// [`Scalar`]: crate::Scalar
/// [`GlobalCtx`]: crate::proc::GlobalCtx
/// [`DiagnosticDebug`]: super::DiagnosticDebug
///
/// ## Language-sensitive diagnostics
///
/// Diagnostic output ought to depend on the source language from
/// which the IR was produced: diagnostics resulting from processing
/// GLSL code should use GLSL type syntax, for example. That means
/// that `DiagnosticDisplay` ought to include some indication of which
/// notation to use.
///
/// For the moment, only WGSL output is implemented, so
/// `DiagnosticDisplay` lacks any support for this (#7268). However,
/// the plan is that all language-independent code in Naga should use
/// `DiagnosticDisplay` wherever appropriate, such that when its
/// definition is expanded to include some indication of the right
/// source language to use, any use site that does not supply this
/// indication will provoke a compile-time error.
pub struct DiagnosticDisplay<T>(pub T);
impl fmt::Display for DiagnosticDisplay<(Handle<Type>, GlobalCtx<'_>)> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (handle, ref ctx) = self.0;
#[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))]
ctx.write_type(handle, f)?;
#[cfg(not(any(feature = "wgsl-in", feature = "wgsl-out")))]
{
let _ = ctx;
write!(f, "{handle:?}")?;
}
Ok(())
}
}
impl fmt::Display for DiagnosticDisplay<(&TypeInner, GlobalCtx<'_>)> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (inner, ref ctx) = self.0;
#[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))]
ctx.write_type_inner(inner, f)?;
#[cfg(not(any(feature = "wgsl-in", feature = "wgsl-out")))]
{
let _ = ctx;
write!(f, "{inner:?}")?;
}
Ok(())
}
}
impl fmt::Display for DiagnosticDisplay<Scalar> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let scalar = self.0;
#[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))]
f.write_str(&crate::common::wgsl::TryToWgsl::to_wgsl_for_diagnostics(
scalar,
))?;
#[cfg(not(any(feature = "wgsl-in", feature = "wgsl-out")))]
write!(f, "{scalar:?}")?;
Ok(())
}
}

View File

@ -1,8 +1,13 @@
//! Code common to the front and backends for specific languages.
mod diagnostic_debug;
mod diagnostic_display;
pub mod predeclared;
pub mod wgsl;
pub use diagnostic_debug::DiagnosticDebug;
pub use diagnostic_display::DiagnosticDisplay;
/// Helper function that returns the string corresponding to the [`VectorSize`](crate::VectorSize)
pub const fn vector_size_str(size: crate::VectorSize) -> &'static str {
match size {

View File

@ -356,3 +356,70 @@ impl From<core::fmt::Error> for WriteTypeError {
Self::Format(err)
}
}
/// Format types as WGSL based on a [`GlobalCtx`].
///
/// This is probably good enough for diagnostic output, but it has some
/// limitations:
///
/// - It does not apply [`Namer`] renamings, to avoid collisions.
///
/// - It generates invalid WGSL for anonymous struct types.
///
/// - It doesn't write the lengths of override-expression-sized arrays
/// correctly, unless the expression is just the override identifier.
///
/// [`GlobalCtx`]: crate::proc::GlobalCtx
/// [`Namer`]: crate::proc::Namer
impl TypeContext for crate::proc::GlobalCtx<'_> {
fn lookup_type(&self, handle: Handle<crate::Type>) -> &crate::Type {
&self.types[handle]
}
fn type_name(&self, handle: Handle<crate::Type>) -> &str {
self.types[handle]
.name
.as_deref()
.unwrap_or("{anonymous type}")
}
fn write_override<W: Write>(
&self,
handle: Handle<crate::Override>,
out: &mut W,
) -> core::fmt::Result {
match self.overrides[handle].name {
Some(ref name) => out.write_str(name),
None => write!(out, "{{anonymous override {handle:?}}}"),
}
}
}
/// Format types as WGSL based on a `UniqueArena<Type>`.
///
/// This is probably only good enough for logging:
///
/// - It does not apply any kind of [`Namer`] renamings.
///
/// - It generates invalid WGSL for anonymous struct types.
///
/// - It doesn't write override-sized arrays properly.
///
/// [`Namer`]: crate::proc::Namer
impl TypeContext for crate::UniqueArena<crate::Type> {
fn lookup_type(&self, handle: Handle<crate::Type>) -> &crate::Type {
&self[handle]
}
fn type_name(&self, handle: Handle<crate::Type>) -> &str {
self[handle].name.as_deref().unwrap_or("{anonymous type}")
}
fn write_override<W: Write>(
&self,
handle: Handle<crate::Override>,
out: &mut W,
) -> core::fmt::Result {
write!(out, "{{override {handle:?}}}")
}
}