diff --git a/naga/src/back/wgsl/writer.rs b/naga/src/back/wgsl/writer.rs index 19386bfd6c..30fb802ea8 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::{ @@ -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,227 +425,37 @@ impl Writer { writeln!(self.out)?; } - write!(self.out, "}}")?; - - writeln!(self.out)?; + writeln!(self.out, "}}")?; Ok(()) } - /// Helper method used to write non image/sampler types - /// - /// # Notes - /// Adds no trailing or leading whitespace 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_value_type(module, other)?, - } + // 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(()) } - /// Helper method used to write value types - /// - /// # Notes - /// Adds no trailing or leading whitespace - fn write_value_type(&mut self, module: &Module, inner: &TypeInner) -> BackendResult { - match *inner { - TypeInner::Vector { size, scalar } => write!( - self.out, - "vec{}<{}>", - common::vector_size_str(size), - scalar.to_wgsl_if_implemented()?, - )?, - TypeInner::Sampler { comparison: false } => { - write!(self.out, "sampler")?; - } - TypeInner::Sampler { comparison: true } => { - write!(self.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 { "" }, - crate::Scalar { kind, width: 4 }.to_wgsl_if_implemented()?, - "", - ), - 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!( - self.out, - "texture_{class_str}{multisampled_str}{dim_str}{arrayed_str}" - )?; - - if !format_str.is_empty() { - write!(self.out, "<{format_str}{storage_str}>")?; - } - } - TypeInner::Scalar(scalar) => { - write!(self.out, "{}", scalar.to_wgsl_if_implemented()?)?; - } - TypeInner::Atomic(scalar) => { - write!(self.out, "atomic<{}>", scalar.to_wgsl_if_implemented()?)?; - } - TypeInner::Array { - base, - size, - stride: _, - } => { - // More info https://gpuweb.github.io/gpuweb/wgsl/#array-types - // array -- Constant array - // array -- Dynamic array - write!(self.out, "array<")?; - match size { - crate::ArraySize::Constant(len) => { - self.write_type(module, base)?; - write!(self.out, ", {len}")?; - } - crate::ArraySize::Pending(_) => { - unreachable!(); - } - crate::ArraySize::Dynamic => { - self.write_type(module, base)?; - } - } - write!(self.out, ">")?; - } - TypeInner::BindingArray { base, size } => { - // More info https://github.com/gpuweb/gpuweb/issues/2105 - write!(self.out, "binding_array<")?; - match size { - crate::ArraySize::Constant(len) => { - self.write_type(module, base)?; - write!(self.out, ", {len}")?; - } - crate::ArraySize::Pending(_) => { - unreachable!(); - } - crate::ArraySize::Dynamic => { - self.write_type(module, base)?; - } - } - write!(self.out, ">")?; - } - TypeInner::Matrix { - columns, - rows, - scalar, - } => { - write!( - self.out, - "mat{}x{}<{}>", - common::vector_size_str(columns), - common::vector_size_str(rows), - scalar.to_wgsl_if_implemented()? - )?; - } - 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!(self.out, "ptr<{space}, ")?; - } - self.write_type(module, base)?; - if address.is_some() { - if let Some(access) = maybe_access { - write!(self.out, ", {access}")?; - } - write!(self.out, ">")?; - } - } - TypeInner::ValuePointer { - size: None, - scalar, - space, - } => { - let (address, maybe_access) = address_space_str(space); - if let Some(space) = address { - write!( - self.out, - "ptr<{}, {}", - space, - scalar.to_wgsl_if_implemented()? - )?; - if let Some(access) = maybe_access { - write!(self.out, ", {access}")?; - } - write!(self.out, ">")?; - } else { - return Err(Error::Unimplemented(format!( - "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!( - self.out, - "ptr<{}, vec{}<{}>", - space, - common::vector_size_str(size), - scalar.to_wgsl_if_implemented()? - )?; - if let Some(access) = maybe_access { - write!(self.out, ", {access}")?; - } - write!(self.out, ">")?; - } else { - return Err(Error::Unimplemented(format!( - "ValuePointer to AddressSpace::Handle {inner:?}" - ))); - } - write!(self.out, ">")?; - } - TypeInner::AccelerationStructure { vertex_return } => { - let caps = if vertex_return { "" } else { "" }; - write!(self.out, "acceleration_structure{}", caps)? - } - _ => { - return Err(Error::Unimplemented(format!("write_value_type {inner:?}"))); - } - } + 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 @@ -1190,7 +1006,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)?; } } } @@ -1904,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) => { diff --git a/naga/src/common/wgsl.rs b/naga/src/common/wgsl/mod.rs similarity index 99% rename from naga/src/common/wgsl.rs rename to naga/src/common/wgsl/mod.rs index 8a9c6b5b7a..d3418d923c 100644 --- a/naga/src/common/wgsl.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(()) + } +}