From 2d34c0b63778b9b4828828be6c037b09f619bef8 Mon Sep 17 00:00:00 2001 From: Jim Blandy Date: Tue, 4 Mar 2025 07:41:09 -0800 Subject: [PATCH 1/7] [naga wgsl-out] Doc fixes and trivial cleanups. Document various functions in the WGSL backend. Clean up some `write!` and `writeln!` macro usage. No change in behavior. --- naga/src/back/wgsl/writer.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/naga/src/back/wgsl/writer.rs b/naga/src/back/wgsl/writer.rs index 19386bfd6c..cdbb01e72c 100644 --- a/naga/src/back/wgsl/writer.rs +++ b/naga/src/back/wgsl/writer.rs @@ -393,9 +393,15 @@ impl Writer { } /// Helper method used to write structs + /// Write the full declaration of a struct type. /// - /// # Notes - /// Ends in a newline + /// Write out a definition of the struct type referred to by + /// `handle` in `module`. The output will be an instance of the + /// `struct_decl` production in the WGSL grammar. + /// + /// Use `members` as the list of `handle`'s members. (This + /// function is usually called after matching a `TypeInner`, so + /// the callers already have the members at hand.) fn write_struct( &mut self, module: &Module, @@ -419,17 +425,15 @@ impl Writer { writeln!(self.out)?; } - write!(self.out, "}}")?; - - writeln!(self.out)?; + writeln!(self.out, "}}")?; Ok(()) } - /// Helper method used to write non image/sampler types + /// Write the type `ty` as it would appear in a value's declaration. /// - /// # Notes - /// Adds no trailing or leading whitespace + /// Write the type referred to by `ty` in `module` as it would appear in + /// a `var`, `let`, etc. declaration, or in a function's argument list. fn write_type(&mut self, module: &Module, ty: Handle) -> BackendResult { let inner = &module.types[ty].inner; match *inner { From ce059b9da2d7f4e4d7a41803cd79cdcf68f999b2 Mon Sep 17 00:00:00 2001 From: Jim Blandy Date: Tue, 4 Mar 2025 07:48:33 -0800 Subject: [PATCH 2/7] [naga wgsl-out] Rename `write_value_type` to `write_type_inner`. Rename `naga::back::wgsl::Writer::write_value_type` to `write_type_inner`, since its job is generating WGSL code for a `TypeInner`. Add documentation. --- naga/src/back/wgsl/writer.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/naga/src/back/wgsl/writer.rs b/naga/src/back/wgsl/writer.rs index cdbb01e72c..a0b42452db 100644 --- a/naga/src/back/wgsl/writer.rs +++ b/naga/src/back/wgsl/writer.rs @@ -440,17 +440,23 @@ impl Writer { TypeInner::Struct { .. } => { write!(self.out, "{}", self.names[&NameKey::Type(ty)])?; } - ref other => self.write_value_type(module, other)?, + ref other => self.write_type_inner(module, other)?, } Ok(()) } - /// Helper method used to write value types + /// Write the [`TypeInner`] `inner` as it would appear in a value's declaration. /// - /// # Notes - /// Adds no trailing or leading whitespace - fn write_value_type(&mut self, module: &Module, inner: &TypeInner) -> BackendResult { + /// Write `inner` as it would appear in a `var`, `let`, etc. + /// declaration, or in a function's argument list. + /// + /// Note that this cannot handle writing [`Struct`] types: those + /// must be referred to by name, but the name isn't available in + /// [`TypeInner`]. + /// + /// [`Struct`]: TypeInner::Struct + fn write_type_inner(&mut self, module: &Module, inner: &TypeInner) -> BackendResult { match *inner { TypeInner::Vector { size, scalar } => write!( self.out, @@ -638,7 +644,7 @@ impl Writer { write!(self.out, "acceleration_structure{}", caps)? } _ => { - return Err(Error::Unimplemented(format!("write_value_type {inner:?}"))); + return Err(Error::Unimplemented(format!("write_type_inner {inner:?}"))); } } @@ -1194,7 +1200,7 @@ impl Writer { self.write_type(module, handle)?; } proc::TypeResolution::Value(ref inner) => { - self.write_value_type(module, inner)?; + self.write_type_inner(module, inner)?; } } } From a1c440f557e22424bca99e2aa4d2a381eadc157a Mon Sep 17 00:00:00 2001 From: Jim Blandy Date: Tue, 4 Mar 2025 07:50:46 -0800 Subject: [PATCH 3/7] [naga wgsl-out] Split WGSL type output into its own impl block. This change is purely syntactic, and should have no effect on the meaning of the program. --- naga/src/back/wgsl/writer.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/naga/src/back/wgsl/writer.rs b/naga/src/back/wgsl/writer.rs index a0b42452db..a255e1a336 100644 --- a/naga/src/back/wgsl/writer.rs +++ b/naga/src/back/wgsl/writer.rs @@ -429,7 +429,9 @@ impl Writer { Ok(()) } +} +impl Writer { /// Write the type `ty` as it would appear in a value's declaration. /// /// Write the type referred to by `ty` in `module` as it would appear in @@ -650,6 +652,9 @@ impl Writer { Ok(()) } +} + +impl Writer { /// Helper method used to write statements /// /// # Notes From 2e82dd6ea0670f23385385a0947b4a1b03f0ae15 Mon Sep 17 00:00:00 2001 From: Jim Blandy Date: Tue, 4 Mar 2025 14:40:48 -0800 Subject: [PATCH 4/7] [naga wgsl-out] New `TypeContext`, for writing Naga types as WGSL. In `naga::back::wgsl`, introduce a new trait, `TypeContext`, which provides all the context necessary to generate WGSL code for a type. `Writer::write_type` and `write_type_inner` become default methods on `TypeContext`. `Writer` replaces them with functions that simply create a `TypeContext` from the `Writer` and forward the call. For simplicity's sake, rather than trying to return errors for Naga IR that we can't generate WGSL for, just panic. Validation should have rejected any such values, and it is not practical to expect Naga backends when given invalid modules: backends need to be free to assume that handles are valid, that arenas are free of cycles, and so on. --- naga/src/back/wgsl/writer.rs | 190 ++++++++++++++++++++++++----------- 1 file changed, 129 insertions(+), 61 deletions(-) diff --git a/naga/src/back/wgsl/writer.rs b/naga/src/back/wgsl/writer.rs index a255e1a336..df0b38f961 100644 --- a/naga/src/back/wgsl/writer.rs +++ b/naga/src/back/wgsl/writer.rs @@ -431,18 +431,43 @@ impl Writer { } } -impl Writer { +/// A context for printing Naga IR types as WGSL. +/// +/// This trait's default methods [`write_type`] and +/// [`write_type_inner`] do the work of formatting types as WGSL. +/// Implementors must provide the remaining methods, to customize +/// behavior for the context at hand. +/// +/// For example, the WGSL backend would provide an implementation of +/// [`type_name`] that handles hygienic renaming, whereas the WGSL +/// front end would simply show the name that was given in the source. +/// +/// [`write_type`]: TypeContext::write_type +/// [`write_type_inner`]: TypeContext::write_type_inner +/// [`type_name`]: TypeContext::type_name +trait TypeContext { + /// Return the [`Type`] referred to by `handle`. + /// + /// [`Type`]: crate::Type + fn lookup_type(&self, handle: Handle) -> &crate::Type; + + /// Return the name to be used for the type referred to by + /// `handle`. + fn type_name(&self, handle: Handle) -> &str; + + /// Write the WGSL form of `override` to `out`. + fn write_override(&self, r#override: Handle, out: &mut W) + -> core::fmt::Result; + /// Write the type `ty` as it would appear in a value's declaration. /// /// Write the type referred to by `ty` in `module` as it would appear in /// a `var`, `let`, etc. declaration, or in a function's argument list. - fn write_type(&mut self, module: &Module, ty: Handle) -> BackendResult { - let inner = &module.types[ty].inner; - match *inner { - TypeInner::Struct { .. } => { - write!(self.out, "{}", self.names[&NameKey::Type(ty)])?; - } - ref other => self.write_type_inner(module, other)?, + fn write_type(&self, handle: Handle, out: &mut W) -> core::fmt::Result { + let ty = self.lookup_type(handle); + match ty.inner { + TypeInner::Struct { .. } => out.write_str(self.type_name(handle))?, + ref other => self.write_type_inner(other, out)?, } Ok(()) @@ -458,19 +483,28 @@ impl Writer { /// [`TypeInner`]. /// /// [`Struct`]: TypeInner::Struct - fn write_type_inner(&mut self, module: &Module, inner: &TypeInner) -> BackendResult { + fn write_type_inner(&self, inner: &TypeInner, out: &mut W) -> core::fmt::Result { + fn unwrap_to_wgsl(value: T) -> &'static str { + value.try_to_wgsl().unwrap_or_else(|| { + unreachable!( + "validation should have forbidden {}: {value:?}", + T::DESCRIPTION + ); + }) + } + match *inner { TypeInner::Vector { size, scalar } => write!( - self.out, + out, "vec{}<{}>", common::vector_size_str(size), - scalar.to_wgsl_if_implemented()?, + unwrap_to_wgsl(scalar), )?, TypeInner::Sampler { comparison: false } => { - write!(self.out, "sampler")?; + write!(out, "sampler")?; } TypeInner::Sampler { comparison: true } => { - write!(self.out, "sampler_comparison")?; + write!(out, "sampler_comparison")?; } TypeInner::Image { dim, @@ -486,7 +520,7 @@ impl Writer { Ic::Sampled { kind, multi } => ( "", if multi { "multisampled_" } else { "" }, - crate::Scalar { kind, width: 4 }.to_wgsl_if_implemented()?, + unwrap_to_wgsl(crate::Scalar { kind, width: 4 }), "", ), Ic::Depth { multi } => { @@ -510,19 +544,19 @@ impl Writer { ), }; write!( - self.out, + out, "texture_{class_str}{multisampled_str}{dim_str}{arrayed_str}" )?; if !format_str.is_empty() { - write!(self.out, "<{format_str}{storage_str}>")?; + write!(out, "<{format_str}{storage_str}>")?; } } TypeInner::Scalar(scalar) => { - write!(self.out, "{}", scalar.to_wgsl_if_implemented()?)?; + write!(out, "{}", unwrap_to_wgsl(scalar))?; } TypeInner::Atomic(scalar) => { - write!(self.out, "atomic<{}>", scalar.to_wgsl_if_implemented()?)?; + write!(out, "atomic<{}>", unwrap_to_wgsl(scalar))?; } TypeInner::Array { base, @@ -532,37 +566,37 @@ impl Writer { // More info https://gpuweb.github.io/gpuweb/wgsl/#array-types // array -- Constant array // array -- Dynamic array - write!(self.out, "array<")?; + write!(out, "array<")?; match size { crate::ArraySize::Constant(len) => { - self.write_type(module, base)?; - write!(self.out, ", {len}")?; + self.write_type(base, out)?; + write!(out, ", {len}")?; } - crate::ArraySize::Pending(_) => { - unreachable!(); + crate::ArraySize::Pending(r#override) => { + self.write_override(r#override, out)?; } crate::ArraySize::Dynamic => { - self.write_type(module, base)?; + self.write_type(base, out)?; } } - write!(self.out, ">")?; + write!(out, ">")?; } TypeInner::BindingArray { base, size } => { // More info https://github.com/gpuweb/gpuweb/issues/2105 - write!(self.out, "binding_array<")?; + write!(out, "binding_array<")?; match size { crate::ArraySize::Constant(len) => { - self.write_type(module, base)?; - write!(self.out, ", {len}")?; + self.write_type(base, out)?; + write!(out, ", {len}")?; } - crate::ArraySize::Pending(_) => { - unreachable!(); + crate::ArraySize::Pending(r#override) => { + self.write_override(r#override, out)?; } crate::ArraySize::Dynamic => { - self.write_type(module, base)?; + self.write_type(base, out)?; } } - write!(self.out, ">")?; + write!(out, ">")?; } TypeInner::Matrix { columns, @@ -570,11 +604,11 @@ impl Writer { scalar, } => { write!( - self.out, + out, "mat{}x{}<{}>", common::vector_size_str(columns), common::vector_size_str(rows), - scalar.to_wgsl_if_implemented()? + unwrap_to_wgsl(scalar) )?; } TypeInner::Pointer { base, space } => { @@ -583,14 +617,14 @@ impl Writer { // Naga IR never produces pointers to handles, so it doesn't matter much // how we write such a type. Just write it as the base type alone. if let Some(space) = address { - write!(self.out, "ptr<{space}, ")?; + write!(out, "ptr<{space}, ")?; } - self.write_type(module, base)?; + self.write_type(base, out)?; if address.is_some() { if let Some(access) = maybe_access { - write!(self.out, ", {access}")?; + write!(out, ", {access}")?; } - write!(self.out, ">")?; + write!(out, ">")?; } } TypeInner::ValuePointer { @@ -600,20 +634,13 @@ impl Writer { } => { let (address, maybe_access) = address_space_str(space); if let Some(space) = address { - write!( - self.out, - "ptr<{}, {}", - space, - scalar.to_wgsl_if_implemented()? - )?; + write!(out, "ptr<{}, {}", space, unwrap_to_wgsl(scalar))?; if let Some(access) = maybe_access { - write!(self.out, ", {access}")?; + write!(out, ", {access}")?; } - write!(self.out, ">")?; + write!(out, ">")?; } else { - return Err(Error::Unimplemented(format!( - "ValuePointer to AddressSpace::Handle {inner:?}" - ))); + unreachable!("ValuePointer to AddressSpace::Handle {inner:?}"); } } TypeInner::ValuePointer { @@ -624,37 +651,78 @@ impl Writer { let (address, maybe_access) = address_space_str(space); if let Some(space) = address { write!( - self.out, + out, "ptr<{}, vec{}<{}>", space, common::vector_size_str(size), - scalar.to_wgsl_if_implemented()? + unwrap_to_wgsl(scalar) )?; if let Some(access) = maybe_access { - write!(self.out, ", {access}")?; + write!(out, ", {access}")?; } - write!(self.out, ">")?; + write!(out, ">")?; } else { - return Err(Error::Unimplemented(format!( - "ValuePointer to AddressSpace::Handle {inner:?}" - ))); + unreachable!("ValuePointer to AddressSpace::Handle {inner:?}"); } - write!(self.out, ">")?; + write!(out, ">")?; } TypeInner::AccelerationStructure { vertex_return } => { let caps = if vertex_return { "" } else { "" }; - write!(self.out, "acceleration_structure{}", caps)? - } - _ => { - return Err(Error::Unimplemented(format!("write_type_inner {inner:?}"))); + write!(out, "acceleration_structure{}", caps)? } + _ => unreachable!("invalid TypeInner"), } Ok(()) } } +struct WriterTypeContext<'m> { + module: &'m Module, + names: &'m crate::FastHashMap, +} + +impl TypeContext for WriterTypeContext<'_> { + fn lookup_type(&self, handle: Handle) -> &crate::Type { + &self.module.types[handle] + } + + fn type_name(&self, handle: Handle) -> &str { + self.names[&NameKey::Type(handle)].as_str() + } + + fn write_override(&self, _: Handle, _: &mut W) -> core::fmt::Result { + unreachable!("overrides should be validated out"); + } +} + impl Writer { + fn write_type(&mut self, module: &Module, ty: Handle) -> BackendResult { + // This actually can't be factored out into a nice constructor method, + // because the borrow checker needs to be able to see that the borrows + // of `self.names` and `self.out` are disjoint. + let type_context = WriterTypeContext { + module, + names: &self.names, + }; + type_context.write_type(ty, &mut self.out)?; + + Ok(()) + } + + fn write_type_inner(&mut self, module: &Module, inner: &TypeInner) -> BackendResult { + // This actually can't be factored out into a nice constructor method, + // because the borrow checker needs to be able to see that the borrows + // of `self.names` and `self.out` are disjoint. + let type_context = WriterTypeContext { + module, + names: &self.names, + }; + type_context.write_type_inner(inner, &mut self.out)?; + + Ok(()) + } + /// Helper method used to write statements /// /// # Notes From fcea83c142638a44a630317502aa5ad451c7685b Mon Sep 17 00:00:00 2001 From: Jim Blandy Date: Tue, 4 Mar 2025 17:28:04 -0800 Subject: [PATCH 5/7] [naga] Create a subdirectory for WGSL common code. Promote `src/common/wgsl.rs` to `src/common/wgsl/mod.rs`. No functional changes, just code motion. --- naga/src/common/{wgsl.rs => wgsl/mod.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename naga/src/common/{wgsl.rs => wgsl/mod.rs} (100%) diff --git a/naga/src/common/wgsl.rs b/naga/src/common/wgsl/mod.rs similarity index 100% rename from naga/src/common/wgsl.rs rename to naga/src/common/wgsl/mod.rs From ae4af657b09f9dea804965e188ecabd4ef973921 Mon Sep 17 00:00:00 2001 From: Jim Blandy Date: Wed, 5 Mar 2025 12:59:13 -0800 Subject: [PATCH 6/7] [naga wgsl] Move code to generate WGSL for types into `common`. Move the `TypeContext` trait from `naga::back::wgsl` into `naga::common::wgsl`. Adjust imports and publicity markers as needed. There should be no changes to behavior in this commit, only code motion. --- naga/src/back/wgsl/writer.rs | 248 +-------------------------------- naga/src/common/wgsl/mod.rs | 4 + naga/src/common/wgsl/types.rs | 253 ++++++++++++++++++++++++++++++++++ 3 files changed, 258 insertions(+), 247 deletions(-) create mode 100644 naga/src/common/wgsl/types.rs diff --git a/naga/src/back/wgsl/writer.rs b/naga/src/back/wgsl/writer.rs index df0b38f961..ea96fcb6fa 100644 --- a/naga/src/back/wgsl/writer.rs +++ b/naga/src/back/wgsl/writer.rs @@ -8,7 +8,7 @@ use core::fmt::Write; use super::Error; use super::ToWgslIfImplemented as _; -use crate::back::wgsl::polyfill::InversePolyfill; +use crate::{back::wgsl::polyfill::InversePolyfill, common::wgsl::TypeContext}; use crate::{ back::{self, Baked}, common::{ @@ -431,252 +431,6 @@ impl Writer { } } -/// A context for printing Naga IR types as WGSL. -/// -/// This trait's default methods [`write_type`] and -/// [`write_type_inner`] do the work of formatting types as WGSL. -/// Implementors must provide the remaining methods, to customize -/// behavior for the context at hand. -/// -/// For example, the WGSL backend would provide an implementation of -/// [`type_name`] that handles hygienic renaming, whereas the WGSL -/// front end would simply show the name that was given in the source. -/// -/// [`write_type`]: TypeContext::write_type -/// [`write_type_inner`]: TypeContext::write_type_inner -/// [`type_name`]: TypeContext::type_name -trait TypeContext { - /// Return the [`Type`] referred to by `handle`. - /// - /// [`Type`]: crate::Type - fn lookup_type(&self, handle: Handle) -> &crate::Type; - - /// Return the name to be used for the type referred to by - /// `handle`. - fn type_name(&self, handle: Handle) -> &str; - - /// Write the WGSL form of `override` to `out`. - fn write_override(&self, r#override: Handle, out: &mut W) - -> core::fmt::Result; - - /// Write the type `ty` as it would appear in a value's declaration. - /// - /// Write the type referred to by `ty` in `module` as it would appear in - /// a `var`, `let`, etc. declaration, or in a function's argument list. - fn write_type(&self, handle: Handle, out: &mut W) -> core::fmt::Result { - let ty = self.lookup_type(handle); - match ty.inner { - TypeInner::Struct { .. } => out.write_str(self.type_name(handle))?, - ref other => self.write_type_inner(other, out)?, - } - - Ok(()) - } - - /// Write the [`TypeInner`] `inner` as it would appear in a value's declaration. - /// - /// Write `inner` as it would appear in a `var`, `let`, etc. - /// declaration, or in a function's argument list. - /// - /// Note that this cannot handle writing [`Struct`] types: those - /// must be referred to by name, but the name isn't available in - /// [`TypeInner`]. - /// - /// [`Struct`]: TypeInner::Struct - fn write_type_inner(&self, inner: &TypeInner, out: &mut W) -> core::fmt::Result { - fn unwrap_to_wgsl(value: T) -> &'static str { - value.try_to_wgsl().unwrap_or_else(|| { - unreachable!( - "validation should have forbidden {}: {value:?}", - T::DESCRIPTION - ); - }) - } - - match *inner { - TypeInner::Vector { size, scalar } => write!( - out, - "vec{}<{}>", - common::vector_size_str(size), - unwrap_to_wgsl(scalar), - )?, - TypeInner::Sampler { comparison: false } => { - write!(out, "sampler")?; - } - TypeInner::Sampler { comparison: true } => { - write!(out, "sampler_comparison")?; - } - TypeInner::Image { - dim, - arrayed, - class, - } => { - // More about texture types: https://gpuweb.github.io/gpuweb/wgsl/#sampled-texture-type - use crate::ImageClass as Ic; - - let dim_str = dim.to_wgsl(); - let arrayed_str = if arrayed { "_array" } else { "" }; - let (class_str, multisampled_str, format_str, storage_str) = match class { - Ic::Sampled { kind, multi } => ( - "", - if multi { "multisampled_" } else { "" }, - unwrap_to_wgsl(crate::Scalar { kind, width: 4 }), - "", - ), - Ic::Depth { multi } => { - ("depth_", if multi { "multisampled_" } else { "" }, "", "") - } - Ic::Storage { format, access } => ( - "storage_", - "", - format.to_wgsl(), - if access.contains(crate::StorageAccess::ATOMIC) { - ",atomic" - } else if access - .contains(crate::StorageAccess::LOAD | crate::StorageAccess::STORE) - { - ",read_write" - } else if access.contains(crate::StorageAccess::LOAD) { - ",read" - } else { - ",write" - }, - ), - }; - write!( - out, - "texture_{class_str}{multisampled_str}{dim_str}{arrayed_str}" - )?; - - if !format_str.is_empty() { - write!(out, "<{format_str}{storage_str}>")?; - } - } - TypeInner::Scalar(scalar) => { - write!(out, "{}", unwrap_to_wgsl(scalar))?; - } - TypeInner::Atomic(scalar) => { - write!(out, "atomic<{}>", unwrap_to_wgsl(scalar))?; - } - TypeInner::Array { - base, - size, - stride: _, - } => { - // More info https://gpuweb.github.io/gpuweb/wgsl/#array-types - // array -- Constant array - // array -- Dynamic array - write!(out, "array<")?; - match size { - crate::ArraySize::Constant(len) => { - self.write_type(base, out)?; - write!(out, ", {len}")?; - } - crate::ArraySize::Pending(r#override) => { - self.write_override(r#override, out)?; - } - crate::ArraySize::Dynamic => { - self.write_type(base, out)?; - } - } - write!(out, ">")?; - } - TypeInner::BindingArray { base, size } => { - // More info https://github.com/gpuweb/gpuweb/issues/2105 - write!(out, "binding_array<")?; - match size { - crate::ArraySize::Constant(len) => { - self.write_type(base, out)?; - write!(out, ", {len}")?; - } - crate::ArraySize::Pending(r#override) => { - self.write_override(r#override, out)?; - } - crate::ArraySize::Dynamic => { - self.write_type(base, out)?; - } - } - write!(out, ">")?; - } - TypeInner::Matrix { - columns, - rows, - scalar, - } => { - write!( - out, - "mat{}x{}<{}>", - common::vector_size_str(columns), - common::vector_size_str(rows), - unwrap_to_wgsl(scalar) - )?; - } - TypeInner::Pointer { base, space } => { - let (address, maybe_access) = address_space_str(space); - // Everything but `AddressSpace::Handle` gives us a `address` name, but - // Naga IR never produces pointers to handles, so it doesn't matter much - // how we write such a type. Just write it as the base type alone. - if let Some(space) = address { - write!(out, "ptr<{space}, ")?; - } - self.write_type(base, out)?; - if address.is_some() { - if let Some(access) = maybe_access { - write!(out, ", {access}")?; - } - write!(out, ">")?; - } - } - TypeInner::ValuePointer { - size: None, - scalar, - space, - } => { - let (address, maybe_access) = address_space_str(space); - if let Some(space) = address { - write!(out, "ptr<{}, {}", space, unwrap_to_wgsl(scalar))?; - if let Some(access) = maybe_access { - write!(out, ", {access}")?; - } - write!(out, ">")?; - } else { - unreachable!("ValuePointer to AddressSpace::Handle {inner:?}"); - } - } - TypeInner::ValuePointer { - size: Some(size), - scalar, - space, - } => { - let (address, maybe_access) = address_space_str(space); - if let Some(space) = address { - write!( - out, - "ptr<{}, vec{}<{}>", - space, - common::vector_size_str(size), - unwrap_to_wgsl(scalar) - )?; - if let Some(access) = maybe_access { - write!(out, ", {access}")?; - } - write!(out, ">")?; - } else { - unreachable!("ValuePointer to AddressSpace::Handle {inner:?}"); - } - write!(out, ">")?; - } - TypeInner::AccelerationStructure { vertex_return } => { - let caps = if vertex_return { "" } else { "" }; - write!(out, "acceleration_structure{}", caps)? - } - _ => unreachable!("invalid TypeInner"), - } - - Ok(()) - } -} - struct WriterTypeContext<'m> { module: &'m Module, names: &'m crate::FastHashMap, diff --git a/naga/src/common/wgsl/mod.rs b/naga/src/common/wgsl/mod.rs index 8a9c6b5b7a..d3418d923c 100644 --- a/naga/src/common/wgsl/mod.rs +++ b/naga/src/common/wgsl/mod.rs @@ -1,11 +1,15 @@ //! Code shared between the WGSL front and back ends. +mod types; + use core::fmt::{self, Display, Formatter}; use crate::diagnostic_filter::{ FilterableTriggeringRule, Severity, StandardFilterableTriggeringRule, }; +pub use types::TypeContext; + impl Severity { const ERROR: &'static str = "error"; const WARNING: &'static str = "warning"; diff --git a/naga/src/common/wgsl/types.rs b/naga/src/common/wgsl/types.rs new file mode 100644 index 0000000000..2a25302ee5 --- /dev/null +++ b/naga/src/common/wgsl/types.rs @@ -0,0 +1,253 @@ +//! Code for formatting Naga IR types as WGSL source code. + +use super::{address_space_str, ToWgsl, TryToWgsl}; +use crate::common; +use crate::{Handle, TypeInner}; + +use core::fmt::Write; + +/// A context for printing Naga IR types as WGSL. +/// +/// This trait's default methods [`write_type`] and +/// [`write_type_inner`] do the work of formatting types as WGSL. +/// Implementors must provide the remaining methods, to customize +/// behavior for the context at hand. +/// +/// For example, the WGSL backend would provide an implementation of +/// [`type_name`] that handles hygienic renaming, whereas the WGSL +/// front end would simply show the name that was given in the source. +/// +/// [`write_type`]: TypeContext::write_type +/// [`write_type_inner`]: TypeContext::write_type_inner +/// [`type_name`]: TypeContext::type_name +pub trait TypeContext { + /// Return the [`Type`] referred to by `handle`. + /// + /// [`Type`]: crate::Type + fn lookup_type(&self, handle: Handle) -> &crate::Type; + + /// Return the name to be used for the type referred to by + /// `handle`. + fn type_name(&self, handle: Handle) -> &str; + + /// Write the WGSL form of `override` to `out`. + fn write_override(&self, r#override: Handle, out: &mut W) + -> core::fmt::Result; + + /// Write the type `ty` as it would appear in a value's declaration. + /// + /// Write the type referred to by `ty` in `module` as it would appear in + /// a `var`, `let`, etc. declaration, or in a function's argument list. + fn write_type(&self, handle: Handle, out: &mut W) -> core::fmt::Result { + let ty = self.lookup_type(handle); + match ty.inner { + TypeInner::Struct { .. } => out.write_str(self.type_name(handle))?, + ref other => self.write_type_inner(other, out)?, + } + + Ok(()) + } + + /// Write the [`TypeInner`] `inner` as it would appear in a value's declaration. + /// + /// Write `inner` as it would appear in a `var`, `let`, etc. + /// declaration, or in a function's argument list. + /// + /// Note that this cannot handle writing [`Struct`] types: those + /// must be referred to by name, but the name isn't available in + /// [`TypeInner`]. + /// + /// [`Struct`]: TypeInner::Struct + fn write_type_inner(&self, inner: &TypeInner, out: &mut W) -> core::fmt::Result { + fn unwrap_to_wgsl(value: T) -> &'static str { + value.try_to_wgsl().unwrap_or_else(|| { + unreachable!( + "validation should have forbidden {}: {value:?}", + T::DESCRIPTION + ); + }) + } + + match *inner { + TypeInner::Vector { size, scalar } => write!( + out, + "vec{}<{}>", + common::vector_size_str(size), + unwrap_to_wgsl(scalar), + )?, + TypeInner::Sampler { comparison: false } => { + write!(out, "sampler")?; + } + TypeInner::Sampler { comparison: true } => { + write!(out, "sampler_comparison")?; + } + TypeInner::Image { + dim, + arrayed, + class, + } => { + // More about texture types: https://gpuweb.github.io/gpuweb/wgsl/#sampled-texture-type + use crate::ImageClass as Ic; + + let dim_str = dim.to_wgsl(); + let arrayed_str = if arrayed { "_array" } else { "" }; + let (class_str, multisampled_str, format_str, storage_str) = match class { + Ic::Sampled { kind, multi } => ( + "", + if multi { "multisampled_" } else { "" }, + unwrap_to_wgsl(crate::Scalar { kind, width: 4 }), + "", + ), + Ic::Depth { multi } => { + ("depth_", if multi { "multisampled_" } else { "" }, "", "") + } + Ic::Storage { format, access } => ( + "storage_", + "", + format.to_wgsl(), + if access.contains(crate::StorageAccess::ATOMIC) { + ",atomic" + } else if access + .contains(crate::StorageAccess::LOAD | crate::StorageAccess::STORE) + { + ",read_write" + } else if access.contains(crate::StorageAccess::LOAD) { + ",read" + } else { + ",write" + }, + ), + }; + write!( + out, + "texture_{class_str}{multisampled_str}{dim_str}{arrayed_str}" + )?; + + if !format_str.is_empty() { + write!(out, "<{format_str}{storage_str}>")?; + } + } + TypeInner::Scalar(scalar) => { + write!(out, "{}", unwrap_to_wgsl(scalar))?; + } + TypeInner::Atomic(scalar) => { + write!(out, "atomic<{}>", unwrap_to_wgsl(scalar))?; + } + TypeInner::Array { + base, + size, + stride: _, + } => { + // More info https://gpuweb.github.io/gpuweb/wgsl/#array-types + // array -- Constant array + // array -- Dynamic array + write!(out, "array<")?; + match size { + crate::ArraySize::Constant(len) => { + self.write_type(base, out)?; + write!(out, ", {len}")?; + } + crate::ArraySize::Pending(r#override) => { + self.write_override(r#override, out)?; + } + crate::ArraySize::Dynamic => { + self.write_type(base, out)?; + } + } + write!(out, ">")?; + } + TypeInner::BindingArray { base, size } => { + // More info https://github.com/gpuweb/gpuweb/issues/2105 + write!(out, "binding_array<")?; + match size { + crate::ArraySize::Constant(len) => { + self.write_type(base, out)?; + write!(out, ", {len}")?; + } + crate::ArraySize::Pending(r#override) => { + self.write_override(r#override, out)?; + } + crate::ArraySize::Dynamic => { + self.write_type(base, out)?; + } + } + write!(out, ">")?; + } + TypeInner::Matrix { + columns, + rows, + scalar, + } => { + write!( + out, + "mat{}x{}<{}>", + common::vector_size_str(columns), + common::vector_size_str(rows), + unwrap_to_wgsl(scalar) + )?; + } + TypeInner::Pointer { base, space } => { + let (address, maybe_access) = address_space_str(space); + // Everything but `AddressSpace::Handle` gives us a `address` name, but + // Naga IR never produces pointers to handles, so it doesn't matter much + // how we write such a type. Just write it as the base type alone. + if let Some(space) = address { + write!(out, "ptr<{space}, ")?; + } + self.write_type(base, out)?; + if address.is_some() { + if let Some(access) = maybe_access { + write!(out, ", {access}")?; + } + write!(out, ">")?; + } + } + TypeInner::ValuePointer { + size: None, + scalar, + space, + } => { + let (address, maybe_access) = address_space_str(space); + if let Some(space) = address { + write!(out, "ptr<{}, {}", space, unwrap_to_wgsl(scalar))?; + if let Some(access) = maybe_access { + write!(out, ", {access}")?; + } + write!(out, ">")?; + } else { + unreachable!("ValuePointer to AddressSpace::Handle {inner:?}"); + } + } + TypeInner::ValuePointer { + size: Some(size), + scalar, + space, + } => { + let (address, maybe_access) = address_space_str(space); + if let Some(space) = address { + write!( + out, + "ptr<{}, vec{}<{}>", + space, + common::vector_size_str(size), + unwrap_to_wgsl(scalar) + )?; + if let Some(access) = maybe_access { + write!(out, ", {access}")?; + } + write!(out, ">")?; + } else { + unreachable!("ValuePointer to AddressSpace::Handle {inner:?}"); + } + write!(out, ">")?; + } + TypeInner::AccelerationStructure { vertex_return } => { + let caps = if vertex_return { "" } else { "" }; + write!(out, "acceleration_structure{}", caps)? + } + _ => unreachable!("invalid TypeInner"), + } + + Ok(()) + } +} From e18779b84a6fa94857da8739c166234aac8107e4 Mon Sep 17 00:00:00 2001 From: Jim Blandy Date: Wed, 5 Mar 2025 20:59:13 -0800 Subject: [PATCH 7/7] [naga wgsl-out] Rearrange `Writer` and `WriterTypeContext`. In `naga::back::wgsl`, move `WriterTypeContext` out of the way, and coalesce `Writer`'s methods back into a single `impl` block. This is just code motion; there should be no change in behavior. --- naga/src/back/wgsl/writer.rs | 40 +++++++++++++++++------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/naga/src/back/wgsl/writer.rs b/naga/src/back/wgsl/writer.rs index ea96fcb6fa..30fb802ea8 100644 --- a/naga/src/back/wgsl/writer.rs +++ b/naga/src/back/wgsl/writer.rs @@ -429,28 +429,7 @@ impl Writer { Ok(()) } -} - -struct WriterTypeContext<'m> { - module: &'m Module, - names: &'m crate::FastHashMap, -} - -impl TypeContext for WriterTypeContext<'_> { - fn lookup_type(&self, handle: Handle) -> &crate::Type { - &self.module.types[handle] - } - - fn type_name(&self, handle: Handle) -> &str { - self.names[&NameKey::Type(handle)].as_str() - } - - fn write_override(&self, _: Handle, _: &mut W) -> core::fmt::Result { - unreachable!("overrides should be validated out"); - } -} -impl Writer { fn write_type(&mut self, module: &Module, ty: Handle) -> BackendResult { // This actually can't be factored out into a nice constructor method, // because the borrow checker needs to be able to see that the borrows @@ -1741,6 +1720,25 @@ impl Writer { } } +struct WriterTypeContext<'m> { + module: &'m Module, + names: &'m crate::FastHashMap, +} + +impl TypeContext for WriterTypeContext<'_> { + fn lookup_type(&self, handle: Handle) -> &crate::Type { + &self.module.types[handle] + } + + fn type_name(&self, handle: Handle) -> &str { + self.names[&NameKey::Type(handle)].as_str() + } + + fn write_override(&self, _: Handle, _: &mut W) -> core::fmt::Result { + unreachable!("overrides should be validated out"); + } +} + fn map_binding_to_attribute(binding: &crate::Binding) -> Vec { match *binding { crate::Binding::BuiltIn(built_in) => {