diff --git a/naga/src/back/wgsl/writer.rs b/naga/src/back/wgsl/writer.rs index 6af268a89c..cab4d1e5a8 100644 --- a/naga/src/back/wgsl/writer.rs +++ b/naga/src/back/wgsl/writer.rs @@ -1726,7 +1726,7 @@ struct WriterTypeContext<'m> { names: &'m crate::FastHashMap, } -impl TypeContext for WriterTypeContext<'_> { +impl TypeContext for WriterTypeContext<'_> { fn lookup_type(&self, handle: Handle) -> &crate::Type { &self.module.types[handle] } @@ -1735,9 +1735,17 @@ impl TypeContext for WriterTypeContext<'_> { self.names[&NameKey::Type(handle)].as_str() } - fn write_override(&self, _: Handle, _: &mut W) -> core::fmt::Result { + fn write_override(&self, _: Handle, _: &mut W) -> core::fmt::Result { unreachable!("overrides should be validated out"); } + + fn write_non_wgsl_inner(&self, _: &TypeInner, _: &mut W) -> core::fmt::Result { + unreachable!("backends should only be passed validated modules"); + } + + fn write_non_wgsl_scalar(&self, _: crate::Scalar, _: &mut W) -> core::fmt::Result { + unreachable!("backends should only be passed validated modules"); + } } fn map_binding_to_attribute(binding: &crate::Binding) -> Vec { diff --git a/naga/src/common/wgsl/diagnostics.rs b/naga/src/common/wgsl/diagnostics.rs new file mode 100644 index 0000000000..1c64eb51e0 --- /dev/null +++ b/naga/src/common/wgsl/diagnostics.rs @@ -0,0 +1,69 @@ +//! WGSL diagnostic filters and severities. + +use core::fmt::{self, Display, Formatter}; + +use crate::diagnostic_filter::{ + FilterableTriggeringRule, Severity, StandardFilterableTriggeringRule, +}; + +impl Severity { + const ERROR: &'static str = "error"; + const WARNING: &'static str = "warning"; + const INFO: &'static str = "info"; + const OFF: &'static str = "off"; + + /// Convert from a sentinel word in WGSL into its associated [`Severity`], if possible. + pub fn from_wgsl_ident(s: &str) -> Option { + Some(match s { + Self::ERROR => Self::Error, + Self::WARNING => Self::Warning, + Self::INFO => Self::Info, + Self::OFF => Self::Off, + _ => return None, + }) + } +} + +pub struct DisplayFilterableTriggeringRule<'a>(&'a FilterableTriggeringRule); + +impl Display for DisplayFilterableTriggeringRule<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let &Self(inner) = self; + match *inner { + FilterableTriggeringRule::Standard(rule) => write!(f, "{}", rule.to_wgsl_ident()), + FilterableTriggeringRule::Unknown(ref rule) => write!(f, "{rule}"), + FilterableTriggeringRule::User(ref rules) => { + let &[ref seg1, ref seg2] = rules.as_ref(); + write!(f, "{seg1}.{seg2}") + } + } + } +} + +impl FilterableTriggeringRule { + /// [`Display`] this rule's identifiers in WGSL. + pub const fn display_wgsl_ident(&self) -> impl Display + '_ { + DisplayFilterableTriggeringRule(self) + } +} + +impl StandardFilterableTriggeringRule { + const DERIVATIVE_UNIFORMITY: &'static str = "derivative_uniformity"; + + /// Convert from a sentinel word in WGSL into its associated + /// [`StandardFilterableTriggeringRule`], if possible. + pub fn from_wgsl_ident(s: &str) -> Option { + Some(match s { + Self::DERIVATIVE_UNIFORMITY => Self::DerivativeUniformity, + _ => return None, + }) + } + + /// Maps this [`StandardFilterableTriggeringRule`] into the sentinel word associated with it in + /// WGSL. + pub const fn to_wgsl_ident(self) -> &'static str { + match self { + Self::DerivativeUniformity => Self::DERIVATIVE_UNIFORMITY, + } + } +} diff --git a/naga/src/common/wgsl/mod.rs b/naga/src/common/wgsl/mod.rs index d3418d923c..fe4cfac9d4 100644 --- a/naga/src/common/wgsl/mod.rs +++ b/naga/src/common/wgsl/mod.rs @@ -1,388 +1,9 @@ //! Code shared between the WGSL front and back ends. +mod diagnostics; +mod to_wgsl; mod types; -use core::fmt::{self, Display, Formatter}; - -use crate::diagnostic_filter::{ - FilterableTriggeringRule, Severity, StandardFilterableTriggeringRule, -}; - +pub use diagnostics::DisplayFilterableTriggeringRule; +pub use to_wgsl::{address_space_str, ToWgsl, TryToWgsl}; pub use types::TypeContext; - -impl Severity { - const ERROR: &'static str = "error"; - const WARNING: &'static str = "warning"; - const INFO: &'static str = "info"; - const OFF: &'static str = "off"; - - /// Convert from a sentinel word in WGSL into its associated [`Severity`], if possible. - pub fn from_wgsl_ident(s: &str) -> Option { - Some(match s { - Self::ERROR => Self::Error, - Self::WARNING => Self::Warning, - Self::INFO => Self::Info, - Self::OFF => Self::Off, - _ => return None, - }) - } -} - -struct DisplayFilterableTriggeringRule<'a>(&'a FilterableTriggeringRule); - -impl Display for DisplayFilterableTriggeringRule<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let &Self(inner) = self; - match *inner { - FilterableTriggeringRule::Standard(rule) => write!(f, "{}", rule.to_wgsl_ident()), - FilterableTriggeringRule::Unknown(ref rule) => write!(f, "{rule}"), - FilterableTriggeringRule::User(ref rules) => { - let &[ref seg1, ref seg2] = rules.as_ref(); - write!(f, "{seg1}.{seg2}") - } - } - } -} - -impl FilterableTriggeringRule { - /// [`Display`] this rule's identifiers in WGSL. - pub const fn display_wgsl_ident(&self) -> impl Display + '_ { - DisplayFilterableTriggeringRule(self) - } -} - -impl StandardFilterableTriggeringRule { - const DERIVATIVE_UNIFORMITY: &'static str = "derivative_uniformity"; - - /// Convert from a sentinel word in WGSL into its associated - /// [`StandardFilterableTriggeringRule`], if possible. - pub fn from_wgsl_ident(s: &str) -> Option { - Some(match s { - Self::DERIVATIVE_UNIFORMITY => Self::DerivativeUniformity, - _ => return None, - }) - } - - /// Maps this [`StandardFilterableTriggeringRule`] into the sentinel word associated with it in - /// WGSL. - pub const fn to_wgsl_ident(self) -> &'static str { - match self { - Self::DerivativeUniformity => Self::DERIVATIVE_UNIFORMITY, - } - } -} - -/// Types that can return the WGSL source representation of their -/// values as a `'static` string. -/// -/// This trait is specifically for types whose WGSL forms are simple -/// enough that they can always be returned as a static string. -/// -/// - If only some values have a WGSL representation, consider -/// implementing [`TryToWgsl`] instead. -/// -/// - If a type's WGSL form requires dynamic formatting, so that -/// returning a `&'static str` isn't feasible, consider implementing -/// [`std::fmt::Display`] on some wrapper type instead. -pub trait ToWgsl: Sized { - /// Return WGSL source code representation of `self`. - fn to_wgsl(self) -> &'static str; -} - -/// Types that may be able to return the WGSL source representation -/// for their values as a `'static' string. -/// -/// This trait is specifically for types whose values are either -/// simple enough that their WGSL form can be represented a static -/// string, or aren't representable in WGSL at all. -/// -/// - If all values in the type have `&'static str` representations in -/// WGSL, consider implementing [`ToWgsl`] instead. -/// -/// - If a type's WGSL form requires dynamic formatting, so that -/// returning a `&'static str` isn't feasible, consider implementing -/// [`std::fmt::Display`] on some wrapper type instead. -pub trait TryToWgsl: Sized { - /// Return the WGSL form of `self` as a `'static` string. - /// - /// If `self` doesn't have a representation in WGSL (standard or - /// as extended by Naga), then return `None`. - fn try_to_wgsl(self) -> Option<&'static str>; - - /// What kind of WGSL thing `Self` represents. - const DESCRIPTION: &'static str; -} - -impl TryToWgsl for crate::MathFunction { - const DESCRIPTION: &'static str = "math function"; - - fn try_to_wgsl(self) -> Option<&'static str> { - use crate::MathFunction as Mf; - - Some(match self { - Mf::Abs => "abs", - Mf::Min => "min", - Mf::Max => "max", - Mf::Clamp => "clamp", - Mf::Saturate => "saturate", - Mf::Cos => "cos", - Mf::Cosh => "cosh", - Mf::Sin => "sin", - Mf::Sinh => "sinh", - Mf::Tan => "tan", - Mf::Tanh => "tanh", - Mf::Acos => "acos", - Mf::Asin => "asin", - Mf::Atan => "atan", - Mf::Atan2 => "atan2", - Mf::Asinh => "asinh", - Mf::Acosh => "acosh", - Mf::Atanh => "atanh", - Mf::Radians => "radians", - Mf::Degrees => "degrees", - Mf::Ceil => "ceil", - Mf::Floor => "floor", - Mf::Round => "round", - Mf::Fract => "fract", - Mf::Trunc => "trunc", - Mf::Modf => "modf", - Mf::Frexp => "frexp", - Mf::Ldexp => "ldexp", - Mf::Exp => "exp", - Mf::Exp2 => "exp2", - Mf::Log => "log", - Mf::Log2 => "log2", - Mf::Pow => "pow", - Mf::Dot => "dot", - Mf::Cross => "cross", - Mf::Distance => "distance", - Mf::Length => "length", - Mf::Normalize => "normalize", - Mf::FaceForward => "faceForward", - Mf::Reflect => "reflect", - Mf::Refract => "refract", - Mf::Sign => "sign", - Mf::Fma => "fma", - Mf::Mix => "mix", - Mf::Step => "step", - Mf::SmoothStep => "smoothstep", - Mf::Sqrt => "sqrt", - Mf::InverseSqrt => "inverseSqrt", - Mf::Transpose => "transpose", - Mf::Determinant => "determinant", - Mf::QuantizeToF16 => "quantizeToF16", - Mf::CountTrailingZeros => "countTrailingZeros", - Mf::CountLeadingZeros => "countLeadingZeros", - Mf::CountOneBits => "countOneBits", - Mf::ReverseBits => "reverseBits", - Mf::ExtractBits => "extractBits", - Mf::InsertBits => "insertBits", - Mf::FirstTrailingBit => "firstTrailingBit", - Mf::FirstLeadingBit => "firstLeadingBit", - Mf::Pack4x8snorm => "pack4x8snorm", - Mf::Pack4x8unorm => "pack4x8unorm", - Mf::Pack2x16snorm => "pack2x16snorm", - Mf::Pack2x16unorm => "pack2x16unorm", - Mf::Pack2x16float => "pack2x16float", - Mf::Pack4xI8 => "pack4xI8", - Mf::Pack4xU8 => "pack4xU8", - Mf::Unpack4x8snorm => "unpack4x8snorm", - Mf::Unpack4x8unorm => "unpack4x8unorm", - Mf::Unpack2x16snorm => "unpack2x16snorm", - Mf::Unpack2x16unorm => "unpack2x16unorm", - Mf::Unpack2x16float => "unpack2x16float", - Mf::Unpack4xI8 => "unpack4xI8", - Mf::Unpack4xU8 => "unpack4xU8", - - // Non-standard math functions. - Mf::Inverse | Mf::Outer => return None, - }) - } -} - -impl TryToWgsl for crate::BuiltIn { - const DESCRIPTION: &'static str = "builtin value"; - - fn try_to_wgsl(self) -> Option<&'static str> { - use crate::BuiltIn as Bi; - Some(match self { - Bi::Position { .. } => "position", - Bi::ViewIndex => "view_index", - Bi::InstanceIndex => "instance_index", - Bi::VertexIndex => "vertex_index", - Bi::FragDepth => "frag_depth", - Bi::FrontFacing => "front_facing", - Bi::PrimitiveIndex => "primitive_index", - Bi::SampleIndex => "sample_index", - Bi::SampleMask => "sample_mask", - Bi::GlobalInvocationId => "global_invocation_id", - Bi::LocalInvocationId => "local_invocation_id", - Bi::LocalInvocationIndex => "local_invocation_index", - Bi::WorkGroupId => "workgroup_id", - Bi::NumWorkGroups => "num_workgroups", - Bi::NumSubgroups => "num_subgroups", - Bi::SubgroupId => "subgroup_id", - Bi::SubgroupSize => "subgroup_size", - Bi::SubgroupInvocationId => "subgroup_invocation_id", - - // Non-standard built-ins. - Bi::BaseInstance - | Bi::BaseVertex - | Bi::ClipDistance - | Bi::CullDistance - | Bi::PointSize - | Bi::DrawID - | Bi::PointCoord - | Bi::WorkGroupSize => return None, - }) - } -} - -impl ToWgsl for crate::Interpolation { - fn to_wgsl(self) -> &'static str { - match self { - crate::Interpolation::Perspective => "perspective", - crate::Interpolation::Linear => "linear", - crate::Interpolation::Flat => "flat", - } - } -} - -impl ToWgsl for crate::Sampling { - fn to_wgsl(self) -> &'static str { - match self { - crate::Sampling::Center => "center", - crate::Sampling::Centroid => "centroid", - crate::Sampling::Sample => "sample", - crate::Sampling::First => "first", - crate::Sampling::Either => "either", - } - } -} - -impl ToWgsl for crate::StorageFormat { - fn to_wgsl(self) -> &'static str { - use crate::StorageFormat as Sf; - - match self { - Sf::R8Unorm => "r8unorm", - Sf::R8Snorm => "r8snorm", - Sf::R8Uint => "r8uint", - Sf::R8Sint => "r8sint", - Sf::R16Uint => "r16uint", - Sf::R16Sint => "r16sint", - Sf::R16Float => "r16float", - Sf::Rg8Unorm => "rg8unorm", - Sf::Rg8Snorm => "rg8snorm", - Sf::Rg8Uint => "rg8uint", - Sf::Rg8Sint => "rg8sint", - Sf::R32Uint => "r32uint", - Sf::R32Sint => "r32sint", - Sf::R32Float => "r32float", - Sf::Rg16Uint => "rg16uint", - Sf::Rg16Sint => "rg16sint", - Sf::Rg16Float => "rg16float", - Sf::Rgba8Unorm => "rgba8unorm", - Sf::Rgba8Snorm => "rgba8snorm", - Sf::Rgba8Uint => "rgba8uint", - Sf::Rgba8Sint => "rgba8sint", - Sf::Bgra8Unorm => "bgra8unorm", - Sf::Rgb10a2Uint => "rgb10a2uint", - Sf::Rgb10a2Unorm => "rgb10a2unorm", - Sf::Rg11b10Ufloat => "rg11b10float", - Sf::R64Uint => "r64uint", - Sf::Rg32Uint => "rg32uint", - Sf::Rg32Sint => "rg32sint", - Sf::Rg32Float => "rg32float", - Sf::Rgba16Uint => "rgba16uint", - Sf::Rgba16Sint => "rgba16sint", - Sf::Rgba16Float => "rgba16float", - Sf::Rgba32Uint => "rgba32uint", - Sf::Rgba32Sint => "rgba32sint", - Sf::Rgba32Float => "rgba32float", - Sf::R16Unorm => "r16unorm", - Sf::R16Snorm => "r16snorm", - Sf::Rg16Unorm => "rg16unorm", - Sf::Rg16Snorm => "rg16snorm", - Sf::Rgba16Unorm => "rgba16unorm", - Sf::Rgba16Snorm => "rgba16snorm", - } - } -} - -impl TryToWgsl for crate::Scalar { - const DESCRIPTION: &'static str = "scalar type"; - - fn try_to_wgsl(self) -> Option<&'static str> { - use crate::Scalar; - - Some(match self { - Scalar::F64 => "f64", - Scalar::F32 => "f32", - Scalar::I32 => "i32", - Scalar::U32 => "u32", - Scalar::I64 => "i64", - Scalar::U64 => "u64", - Scalar::BOOL => "bool", - _ => return None, - }) - } -} - -impl ToWgsl for crate::ImageDimension { - fn to_wgsl(self) -> &'static str { - use crate::ImageDimension as IDim; - - match self { - IDim::D1 => "1d", - IDim::D2 => "2d", - IDim::D3 => "3d", - IDim::Cube => "cube", - } - } -} - -/// Return the WGSL address space and access mode strings for `space`. -/// -/// Why don't we implement [`ToWgsl`] for [`AddressSpace`]? -/// -/// In WGSL, the full form of a pointer type is `ptr`, where: -/// - `AS` is the address space, -/// - `T` is the store type, and -/// - `AM` is the access mode. -/// -/// Since the type `T` intervenes between the address space and the -/// access mode, there isn't really any individual WGSL grammar -/// production that corresponds to an [`AddressSpace`], so [`ToWgsl`] -/// is too simple-minded for this case. -/// -/// Furthermore, we want to write `var` for most address -/// spaces, but we want to just write `var foo: T` for handle types. -/// -/// [`AddressSpace`]: crate::AddressSpace -pub const fn address_space_str( - space: crate::AddressSpace, -) -> (Option<&'static str>, Option<&'static str>) { - use crate::AddressSpace as As; - - ( - Some(match space { - As::Private => "private", - As::Uniform => "uniform", - As::Storage { access } => { - if access.contains(crate::StorageAccess::ATOMIC) { - return (Some("storage"), Some("atomic")); - } else if access.contains(crate::StorageAccess::STORE) { - return (Some("storage"), Some("read_write")); - } else { - "storage" - } - } - As::PushConstant => "push_constant", - As::WorkGroup => "workgroup", - As::Handle => return (None, None), - As::Function => "function", - }), - None, - ) -} diff --git a/naga/src/common/wgsl/to_wgsl.rs b/naga/src/common/wgsl/to_wgsl.rs new file mode 100644 index 0000000000..08783ed067 --- /dev/null +++ b/naga/src/common/wgsl/to_wgsl.rs @@ -0,0 +1,353 @@ +//! Generating WGSL source code for Naga IR types. + +use alloc::format; +use alloc::string::{String, ToString}; + +/// Types that can return the WGSL source representation of their +/// values as a `'static` string. +/// +/// This trait is specifically for types whose WGSL forms are simple +/// enough that they can always be returned as a static string. +/// +/// - If only some values have a WGSL representation, consider +/// implementing [`TryToWgsl`] instead. +/// +/// - If a type's WGSL form requires dynamic formatting, so that +/// returning a `&'static str` isn't feasible, consider implementing +/// [`std::fmt::Display`] on some wrapper type instead. +pub trait ToWgsl: Sized { + /// Return WGSL source code representation of `self`. + fn to_wgsl(self) -> &'static str; +} + +/// Types that may be able to return the WGSL source representation +/// for their values as a `'static' string. +/// +/// This trait is specifically for types whose values are either +/// simple enough that their WGSL form can be represented a static +/// string, or aren't representable in WGSL at all. +/// +/// - If all values in the type have `&'static str` representations in +/// WGSL, consider implementing [`ToWgsl`] instead. +/// +/// - If a type's WGSL form requires dynamic formatting, so that +/// returning a `&'static str` isn't feasible, consider implementing +/// [`std::fmt::Display`] on some wrapper type instead. +pub trait TryToWgsl: Sized { + /// Return the WGSL form of `self` as a `'static` string. + /// + /// If `self` doesn't have a representation in WGSL (standard or + /// as extended by Naga), then return `None`. + fn try_to_wgsl(self) -> Option<&'static str>; + + /// What kind of WGSL thing `Self` represents. + const DESCRIPTION: &'static str; + + /// Return the WGSL form of `self` as appropriate for diagnostics. + /// + /// If `self` can be expressed in WGSL, return that form as a + /// [`String`]. Otherwise, return some representation of `self` + /// that is appropriate for use in diagnostic messages. + /// + /// The default implementation of this function falls back to + /// `self`'s [`Debug`] form. + /// + /// [`Debug`]: core::fmt::Debug + fn to_wgsl_for_diagnostics(self) -> String + where + Self: core::fmt::Debug + Copy, + { + match self.try_to_wgsl() { + Some(static_string) => static_string.to_string(), + None => format!("{{non-WGSL {} {self:?}}}", Self::DESCRIPTION), + } + } +} + +impl TryToWgsl for crate::MathFunction { + const DESCRIPTION: &'static str = "math function"; + + fn try_to_wgsl(self) -> Option<&'static str> { + use crate::MathFunction as Mf; + + Some(match self { + Mf::Abs => "abs", + Mf::Min => "min", + Mf::Max => "max", + Mf::Clamp => "clamp", + Mf::Saturate => "saturate", + Mf::Cos => "cos", + Mf::Cosh => "cosh", + Mf::Sin => "sin", + Mf::Sinh => "sinh", + Mf::Tan => "tan", + Mf::Tanh => "tanh", + Mf::Acos => "acos", + Mf::Asin => "asin", + Mf::Atan => "atan", + Mf::Atan2 => "atan2", + Mf::Asinh => "asinh", + Mf::Acosh => "acosh", + Mf::Atanh => "atanh", + Mf::Radians => "radians", + Mf::Degrees => "degrees", + Mf::Ceil => "ceil", + Mf::Floor => "floor", + Mf::Round => "round", + Mf::Fract => "fract", + Mf::Trunc => "trunc", + Mf::Modf => "modf", + Mf::Frexp => "frexp", + Mf::Ldexp => "ldexp", + Mf::Exp => "exp", + Mf::Exp2 => "exp2", + Mf::Log => "log", + Mf::Log2 => "log2", + Mf::Pow => "pow", + Mf::Dot => "dot", + Mf::Cross => "cross", + Mf::Distance => "distance", + Mf::Length => "length", + Mf::Normalize => "normalize", + Mf::FaceForward => "faceForward", + Mf::Reflect => "reflect", + Mf::Refract => "refract", + Mf::Sign => "sign", + Mf::Fma => "fma", + Mf::Mix => "mix", + Mf::Step => "step", + Mf::SmoothStep => "smoothstep", + Mf::Sqrt => "sqrt", + Mf::InverseSqrt => "inverseSqrt", + Mf::Transpose => "transpose", + Mf::Determinant => "determinant", + Mf::QuantizeToF16 => "quantizeToF16", + Mf::CountTrailingZeros => "countTrailingZeros", + Mf::CountLeadingZeros => "countLeadingZeros", + Mf::CountOneBits => "countOneBits", + Mf::ReverseBits => "reverseBits", + Mf::ExtractBits => "extractBits", + Mf::InsertBits => "insertBits", + Mf::FirstTrailingBit => "firstTrailingBit", + Mf::FirstLeadingBit => "firstLeadingBit", + Mf::Pack4x8snorm => "pack4x8snorm", + Mf::Pack4x8unorm => "pack4x8unorm", + Mf::Pack2x16snorm => "pack2x16snorm", + Mf::Pack2x16unorm => "pack2x16unorm", + Mf::Pack2x16float => "pack2x16float", + Mf::Pack4xI8 => "pack4xI8", + Mf::Pack4xU8 => "pack4xU8", + Mf::Unpack4x8snorm => "unpack4x8snorm", + Mf::Unpack4x8unorm => "unpack4x8unorm", + Mf::Unpack2x16snorm => "unpack2x16snorm", + Mf::Unpack2x16unorm => "unpack2x16unorm", + Mf::Unpack2x16float => "unpack2x16float", + Mf::Unpack4xI8 => "unpack4xI8", + Mf::Unpack4xU8 => "unpack4xU8", + + // Non-standard math functions. + Mf::Inverse | Mf::Outer => return None, + }) + } +} + +impl TryToWgsl for crate::BuiltIn { + const DESCRIPTION: &'static str = "builtin value"; + + fn try_to_wgsl(self) -> Option<&'static str> { + use crate::BuiltIn as Bi; + Some(match self { + Bi::Position { .. } => "position", + Bi::ViewIndex => "view_index", + Bi::InstanceIndex => "instance_index", + Bi::VertexIndex => "vertex_index", + Bi::FragDepth => "frag_depth", + Bi::FrontFacing => "front_facing", + Bi::PrimitiveIndex => "primitive_index", + Bi::SampleIndex => "sample_index", + Bi::SampleMask => "sample_mask", + Bi::GlobalInvocationId => "global_invocation_id", + Bi::LocalInvocationId => "local_invocation_id", + Bi::LocalInvocationIndex => "local_invocation_index", + Bi::WorkGroupId => "workgroup_id", + Bi::NumWorkGroups => "num_workgroups", + Bi::NumSubgroups => "num_subgroups", + Bi::SubgroupId => "subgroup_id", + Bi::SubgroupSize => "subgroup_size", + Bi::SubgroupInvocationId => "subgroup_invocation_id", + + // Non-standard built-ins. + Bi::BaseInstance + | Bi::BaseVertex + | Bi::ClipDistance + | Bi::CullDistance + | Bi::PointSize + | Bi::DrawID + | Bi::PointCoord + | Bi::WorkGroupSize => return None, + }) + } +} + +impl ToWgsl for crate::Interpolation { + fn to_wgsl(self) -> &'static str { + match self { + crate::Interpolation::Perspective => "perspective", + crate::Interpolation::Linear => "linear", + crate::Interpolation::Flat => "flat", + } + } +} + +impl ToWgsl for crate::Sampling { + fn to_wgsl(self) -> &'static str { + match self { + crate::Sampling::Center => "center", + crate::Sampling::Centroid => "centroid", + crate::Sampling::Sample => "sample", + crate::Sampling::First => "first", + crate::Sampling::Either => "either", + } + } +} + +impl ToWgsl for crate::StorageFormat { + fn to_wgsl(self) -> &'static str { + use crate::StorageFormat as Sf; + + match self { + Sf::R8Unorm => "r8unorm", + Sf::R8Snorm => "r8snorm", + Sf::R8Uint => "r8uint", + Sf::R8Sint => "r8sint", + Sf::R16Uint => "r16uint", + Sf::R16Sint => "r16sint", + Sf::R16Float => "r16float", + Sf::Rg8Unorm => "rg8unorm", + Sf::Rg8Snorm => "rg8snorm", + Sf::Rg8Uint => "rg8uint", + Sf::Rg8Sint => "rg8sint", + Sf::R32Uint => "r32uint", + Sf::R32Sint => "r32sint", + Sf::R32Float => "r32float", + Sf::Rg16Uint => "rg16uint", + Sf::Rg16Sint => "rg16sint", + Sf::Rg16Float => "rg16float", + Sf::Rgba8Unorm => "rgba8unorm", + Sf::Rgba8Snorm => "rgba8snorm", + Sf::Rgba8Uint => "rgba8uint", + Sf::Rgba8Sint => "rgba8sint", + Sf::Bgra8Unorm => "bgra8unorm", + Sf::Rgb10a2Uint => "rgb10a2uint", + Sf::Rgb10a2Unorm => "rgb10a2unorm", + Sf::Rg11b10Ufloat => "rg11b10float", + Sf::R64Uint => "r64uint", + Sf::Rg32Uint => "rg32uint", + Sf::Rg32Sint => "rg32sint", + Sf::Rg32Float => "rg32float", + Sf::Rgba16Uint => "rgba16uint", + Sf::Rgba16Sint => "rgba16sint", + Sf::Rgba16Float => "rgba16float", + Sf::Rgba32Uint => "rgba32uint", + Sf::Rgba32Sint => "rgba32sint", + Sf::Rgba32Float => "rgba32float", + Sf::R16Unorm => "r16unorm", + Sf::R16Snorm => "r16snorm", + Sf::Rg16Unorm => "rg16unorm", + Sf::Rg16Snorm => "rg16snorm", + Sf::Rgba16Unorm => "rgba16unorm", + Sf::Rgba16Snorm => "rgba16snorm", + } + } +} + +impl TryToWgsl for crate::Scalar { + const DESCRIPTION: &'static str = "scalar type"; + + fn try_to_wgsl(self) -> Option<&'static str> { + use crate::Scalar; + + Some(match self { + Scalar::F64 => "f64", + Scalar::F32 => "f32", + Scalar::I32 => "i32", + Scalar::U32 => "u32", + Scalar::I64 => "i64", + Scalar::U64 => "u64", + Scalar::BOOL => "bool", + _ => return None, + }) + } + + fn to_wgsl_for_diagnostics(self) -> String { + match self.try_to_wgsl() { + Some(static_string) => static_string.to_string(), + None => match self.kind { + crate::ScalarKind::Sint + | crate::ScalarKind::Uint + | crate::ScalarKind::Float + | crate::ScalarKind::Bool => format!("{{non-WGSL scalar {self:?}}}"), + crate::ScalarKind::AbstractInt => "{AbstractInt}".to_string(), + crate::ScalarKind::AbstractFloat => "{AbstractFloat}".to_string(), + }, + } + } +} + +impl ToWgsl for crate::ImageDimension { + fn to_wgsl(self) -> &'static str { + use crate::ImageDimension as IDim; + + match self { + IDim::D1 => "1d", + IDim::D2 => "2d", + IDim::D3 => "3d", + IDim::Cube => "cube", + } + } +} + +/// Return the WGSL address space and access mode strings for `space`. +/// +/// Why don't we implement [`ToWgsl`] for [`AddressSpace`]? +/// +/// In WGSL, the full form of a pointer type is `ptr`, where: +/// - `AS` is the address space, +/// - `T` is the store type, and +/// - `AM` is the access mode. +/// +/// Since the type `T` intervenes between the address space and the +/// access mode, there isn't really any individual WGSL grammar +/// production that corresponds to an [`AddressSpace`], so [`ToWgsl`] +/// is too simple-minded for this case. +/// +/// Furthermore, we want to write `var` for most address +/// spaces, but we want to just write `var foo: T` for handle types. +/// +/// [`AddressSpace`]: crate::AddressSpace +pub const fn address_space_str( + space: crate::AddressSpace, +) -> (Option<&'static str>, Option<&'static str>) { + use crate::AddressSpace as As; + + ( + Some(match space { + As::Private => "private", + As::Uniform => "uniform", + As::Storage { access } => { + if access.contains(crate::StorageAccess::ATOMIC) { + return (Some("storage"), Some("atomic")); + } else if access.contains(crate::StorageAccess::STORE) { + return (Some("storage"), Some("read_write")); + } else { + "storage" + } + } + As::PushConstant => "push_constant", + As::WorkGroup => "workgroup", + As::Handle => return (None, None), + As::Function => "function", + }), + None, + ) +} diff --git a/naga/src/common/wgsl/types.rs b/naga/src/common/wgsl/types.rs index 579e785b1d..8c3e831593 100644 --- a/naga/src/common/wgsl/types.rs +++ b/naga/src/common/wgsl/types.rs @@ -2,8 +2,10 @@ use super::{address_space_str, ToWgsl, TryToWgsl}; use crate::common; -use crate::{Handle, TypeInner}; +use crate::proc::TypeResolution; +use crate::{Handle, Scalar, TypeInner}; +use alloc::string::String; use core::fmt::Write; /// A context for printing Naga IR types as WGSL. @@ -20,7 +22,7 @@ use core::fmt::Write; /// [`write_type`]: TypeContext::write_type /// [`write_type_inner`]: TypeContext::write_type_inner /// [`type_name`]: TypeContext::type_name -pub trait TypeContext { +pub trait TypeContext { /// Return the [`Type`] referred to by `handle`. /// /// [`Type`]: crate::Type @@ -31,14 +33,59 @@ pub trait TypeContext { 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; + fn write_override( + &self, + r#override: Handle, + out: &mut W, + ) -> core::fmt::Result; + + /// Write a [`TypeInner`] that has no representation as WGSL source, + /// even including Naga extensions. + /// + /// A backend might implement this with a call to the [`unreachable!`] + /// macro, since backends are allowed to assume that the module has passed + /// validation. + /// + /// The default implementation is appropriate for generating type names to + /// appear in error messages. It punts to `TypeInner`'s [`core::fmt::Debug`] + /// implementation, since it's probably best to show the user something they + /// can act on. + fn write_non_wgsl_inner(&self, inner: &TypeInner, out: &mut W) -> core::fmt::Result { + write!(out, "{{non-WGSL Naga type {inner:?}}}") + } + + /// Write a [`Scalar`] that has no representation as WGSL source, + /// even including Naga extensions. + /// + /// A backend might implement this with a call to the [`unreachable!`] + /// macro, since backends are allowed to assume that the module has passed + /// validation. + /// + /// The default implementation is appropriate for generating type names to + /// appear in error messages. It punts to `Scalar`'s [`core::fmt::Debug`] + /// implementation, since it's probably best to show the user something they + /// can act on. + fn write_non_wgsl_scalar(&self, scalar: Scalar, out: &mut W) -> core::fmt::Result { + match scalar.kind { + crate::ScalarKind::Sint + | crate::ScalarKind::Uint + | crate::ScalarKind::Float + | crate::ScalarKind::Bool => write!(out, "{{non-WGSL Naga scalar {scalar:?}}}"), + + // The abstract types are kind of an odd quasi-WGSL category: + // they are definitely part of the spec, but they are not expressible + // in WGSL itself. So we want to call them out by name in error messages, + // but the WGSL backend should never generate these. + crate::ScalarKind::AbstractInt => out.write_str("{AbstractInt}"), + crate::ScalarKind::AbstractFloat => out.write_str("{AbstractFloat}"), + } + } /// 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 { + 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))?, @@ -58,202 +105,254 @@ pub trait TypeContext { /// [`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 - ); - }) + fn write_type_inner(&self, inner: &TypeInner, out: &mut W) -> core::fmt::Result { + match try_write_type_inner(self, inner, out) { + Ok(()) => Ok(()), + Err(WriteTypeError::Format(err)) => Err(err), + Err(WriteTypeError::NonWgsl) => self.write_non_wgsl_inner(inner, out), } + } - 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; + /// Write the [`Scalar`] `scalar` as a WGSL type. + fn write_scalar(&self, scalar: Scalar, out: &mut W) -> core::fmt::Result { + match scalar.try_to_wgsl() { + Some(string) => out.write_str(string), + None => self.write_non_wgsl_scalar(scalar, out), + } + } + + /// Write the [`TypeResolution`] `resolution` as a WGSL type. + fn write_type_resolution( + &self, + resolution: &TypeResolution, + out: &mut W, + ) -> core::fmt::Result { + match *resolution { + TypeResolution::Handle(handle) => self.write_type(handle, out), + TypeResolution::Value(ref inner) => self.write_type_inner(inner, out), + } + } - let dim_str = dim.to_wgsl(); - let arrayed_str = if arrayed { "_array" } else { "" }; - match class { - Ic::Sampled { kind, multi } => { - let multisampled_str = if multi { "multisampled_" } else { "" }; - let type_str = unwrap_to_wgsl(crate::Scalar { kind, width: 4 }); - write!( - out, - "texture_{multisampled_str}{dim_str}{arrayed_str}<{type_str}>" - )?; - } - Ic::Depth { multi } => { - let multisampled_str = if multi { "multisampled_" } else { "" }; - write!( - out, - "texture_depth_{multisampled_str}{dim_str}{arrayed_str}" - )?; - } - Ic::Storage { format, access } => { - let format_str = format.to_wgsl(); - let access_str = 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_storage_{dim_str}{arrayed_str}<{format_str}{access_str}>" - )?; - } + fn type_to_string(&self, handle: Handle) -> String { + let mut buf = String::new(); + self.write_type(handle, &mut buf).unwrap(); + buf + } + + fn type_inner_to_string(&self, inner: &TypeInner) -> String { + let mut buf = String::new(); + self.write_type_inner(inner, &mut buf).unwrap(); + buf + } + + fn type_resolution_to_string(&self, resolution: &TypeResolution) -> String { + let mut buf = String::new(); + self.write_type_resolution(resolution, &mut buf).unwrap(); + buf + } +} + +fn try_write_type_inner(ctx: &C, inner: &TypeInner, out: &mut W) -> Result<(), WriteTypeError> +where + C: TypeContext + ?Sized, + W: Write, +{ + match *inner { + TypeInner::Vector { size, scalar } => { + write!(out, "vec{}<", common::vector_size_str(size))?; + ctx.write_scalar(scalar, out)?; + out.write_str(">")?; + } + 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 { "" }; + match class { + Ic::Sampled { kind, multi } => { + let multisampled_str = if multi { "multisampled_" } else { "" }; + write!(out, "texture_{multisampled_str}{dim_str}{arrayed_str}<")?; + ctx.write_scalar(Scalar { kind, width: 4 }, out)?; + out.write_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)?; - } + Ic::Depth { multi } => { + let multisampled_str = if multi { "multisampled_" } else { "" }; + write!( + out, + "texture_depth_{multisampled_str}{dim_str}{arrayed_str}" + )?; } - 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)?; - } + Ic::Storage { format, access } => { + let format_str = format.to_wgsl(); + let access_str = 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_storage_{dim_str}{arrayed_str}<{format_str}{access_str}>" + )?; } - 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}, ")?; + } + TypeInner::Scalar(scalar) => { + ctx.write_scalar(scalar, out)?; + } + TypeInner::Atomic(scalar) => { + out.write_str("atomic<")?; + ctx.write_scalar(scalar, out)?; + out.write_str(">")?; + } + 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) => { + ctx.write_type(base, out)?; + write!(out, ", {len}")?; + } + crate::ArraySize::Pending(r#override) => { + ctx.write_override(r#override, out)?; } - self.write_type(base, out)?; - if address.is_some() { - if let Some(access) = maybe_access { - write!(out, ", {access}")?; - } - write!(out, ">")?; + crate::ArraySize::Dynamic => { + ctx.write_type(base, 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:?}"); + 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) => { + ctx.write_type(base, out)?; + write!(out, ", {len}")?; + } + crate::ArraySize::Pending(r#override) => { + ctx.write_override(r#override, out)?; + } + crate::ArraySize::Dynamic => { + ctx.write_type(base, out)?; } } - 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::Matrix { + columns, + rows, + scalar, + } => { + write!( + out, + "mat{}x{}<", + common::vector_size_str(columns), + common::vector_size_str(rows), + )?; + ctx.write_scalar(scalar, out)?; + out.write_str(">")?; + } + 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}, ")?; + } + ctx.write_type(base, out)?; + if address.is_some() { + if let Some(access) = maybe_access { + write!(out, ", {access}")?; } write!(out, ">")?; } - TypeInner::AccelerationStructure { vertex_return } => { - let caps = if vertex_return { "" } else { "" }; - write!(out, "acceleration_structure{}", caps)? - } - TypeInner::Struct { .. } => { - unreachable!("structs can only be referenced by name in WGSL"); + } + TypeInner::ValuePointer { + size: None, + scalar, + space, + } => { + let (address, maybe_access) = address_space_str(space); + if let Some(space) = address { + write!(out, "ptr<{}, ", space)?; + ctx.write_scalar(scalar, out)?; + if let Some(access) = maybe_access { + write!(out, ", {access}")?; + } + write!(out, ">")?; + } else { + return Err(WriteTypeError::NonWgsl); } - TypeInner::RayQuery { vertex_return } => { - let caps = if vertex_return { "" } else { "" }; - write!(out, "ray_query{}", caps)? + } + 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),)?; + ctx.write_scalar(scalar, out)?; + out.write_str(">")?; + if let Some(access) = maybe_access { + write!(out, ", {access}")?; + } + write!(out, ">")?; + } else { + return Err(WriteTypeError::NonWgsl); } + write!(out, ">")?; + } + TypeInner::AccelerationStructure { vertex_return } => { + let caps = if vertex_return { "" } else { "" }; + write!(out, "acceleration_structure{}", caps)? } + TypeInner::Struct { .. } => { + unreachable!("structs can only be referenced by name in WGSL"); + } + TypeInner::RayQuery { vertex_return } => { + let caps = if vertex_return { "" } else { "" }; + write!(out, "ray_query{}", caps)? + } + } - Ok(()) + Ok(()) +} + +/// Error type returned by `try_write_type_inner`. +/// +/// This type is private to the module. +enum WriteTypeError { + Format(core::fmt::Error), + NonWgsl, +} + +impl From for WriteTypeError { + fn from(err: core::fmt::Error) -> Self { + Self::Format(err) } } diff --git a/naga/src/front/wgsl/error.rs b/naga/src/front/wgsl/error.rs index ac6bc617ea..eed18b9a13 100644 --- a/naga/src/front/wgsl/error.rs +++ b/naga/src/front/wgsl/error.rs @@ -1,12 +1,15 @@ -use alloc::{ - borrow::Cow, - boxed::Box, - format, - string::{String, ToString}, - vec, - vec::Vec, +//! Formatting WGSL front end error messages. + +use crate::common::wgsl::TryToWgsl; +use crate::diagnostic_filter::ConflictingDiagnosticRuleError; +use crate::proc::{Alignment, ConstantEvaluatorError, ResolveError}; +use crate::{Scalar, SourceLocation, Span}; + +use super::parse::directive::enable_extension::{EnableExtension, UnimplementedEnableExtension}; +use super::parse::directive::language_extension::{ + LanguageExtension, UnimplementedLanguageExtension, }; -use core::ops::Range; +use super::parse::lexer::Token; use codespan_reporting::diagnostic::{Diagnostic, Label}; use codespan_reporting::files::SimpleFile; @@ -14,17 +17,15 @@ use codespan_reporting::term; use termcolor::{ColorChoice, NoColor, StandardStream}; use thiserror::Error; -use crate::diagnostic_filter::ConflictingDiagnosticRuleError; -use crate::front::wgsl::parse::directive::enable_extension::{ - EnableExtension, UnimplementedEnableExtension, -}; -use crate::front::wgsl::parse::directive::language_extension::{ - LanguageExtension, UnimplementedLanguageExtension, +use alloc::{ + borrow::Cow, + boxed::Box, + format, + string::{String, ToString}, + vec, + vec::Vec, }; -use crate::front::wgsl::parse::lexer::Token; -use crate::front::wgsl::Scalar; -use crate::proc::{Alignment, ConstantEvaluatorError, ResolveError}; -use crate::{SourceLocation, Span}; +use core::ops::Range; #[derive(Clone, Debug)] pub struct ParseError { @@ -446,7 +447,7 @@ impl<'a> Error<'a> { Error::BadMatrixScalarKind(span, scalar) => ParseError { message: format!( "matrix scalar type must be floating-point, but found `{}`", - scalar.to_wgsl() + scalar.to_wgsl_for_diagnostics() ), labels: vec![(span, "must be floating-point (e.g. `f32`)".into())], notes: vec![], @@ -469,7 +470,7 @@ impl<'a> Error<'a> { Error::BadTextureSampleType { span, scalar } => ParseError { message: format!( "texture sample type must be one of f32, i32 or u32, but found {}", - scalar.to_wgsl() + scalar.to_wgsl_for_diagnostics() ), labels: vec![(span, "must be one of f32, i32 or u32".into())], notes: vec![], diff --git a/naga/src/front/wgsl/lower/construction.rs b/naga/src/front/wgsl/lower/construction.rs index 0acb90f321..fe5f4655ed 100644 --- a/naga/src/front/wgsl/lower/construction.rs +++ b/naga/src/front/wgsl/lower/construction.rs @@ -7,6 +7,7 @@ use alloc::{ }; use core::num::NonZeroU32; +use crate::common::wgsl::TypeContext; use crate::front::wgsl::lower::{ExpressionContext, Lowerer}; use crate::front::wgsl::parse::ast; use crate::front::wgsl::{Error, Result}; @@ -71,7 +72,7 @@ impl Constructor<(Handle, &crate::TypeInner)> { format!("mat{}x{}", columns as u32, rows as u32,) } Self::PartialArray => "array".to_string(), - Self::Type((handle, _inner)) => handle.to_wgsl(&ctx.module.to_ctx()), + Self::Type((handle, _inner)) => ctx.type_to_string(handle), } } } @@ -536,7 +537,7 @@ impl<'source> Lowerer<'source, '_> { // Bad conversion (type cast) (Components::One { span, ty_inner, .. }, constructor) => { - let from_type = ty_inner.to_wgsl(&ctx.module.to_ctx()); + let from_type = ctx.type_inner_to_string(ty_inner); return Err(Box::new(Error::BadTypeCast { span, from_type, diff --git a/naga/src/front/wgsl/lower/conversion.rs b/naga/src/front/wgsl/lower/conversion.rs index a72141522d..d750f20b11 100644 --- a/naga/src/front/wgsl/lower/conversion.rs +++ b/naga/src/front/wgsl/lower/conversion.rs @@ -2,6 +2,7 @@ use alloc::{boxed::Box, string::String, vec::Vec}; +use crate::common::wgsl::{TryToWgsl, TypeContext}; use crate::front::wgsl::error::{ AutoConversionError, AutoConversionLeafScalarError, ConcretizationFailedError, }; @@ -53,9 +54,8 @@ impl<'source> super::ExpressionContext<'source, '_, '_> { match expr_inner.automatically_converts_to(goal_inner, types) { Some(scalars) => scalars, None => { - let gctx = &self.module.to_ctx(); - let source_type = expr_resolution.to_wgsl(gctx); - let dest_type = goal_ty.to_wgsl(gctx); + let source_type = self.type_resolution_to_string(expr_resolution); + let dest_type = self.type_resolution_to_string(goal_ty); return Err(Box::new(super::Error::AutoConversion(Box::new( AutoConversionError { @@ -95,11 +95,10 @@ impl<'source> super::ExpressionContext<'source, '_, '_> { let expr_inner = expr_resolution.inner_with(types); let make_error = || { - let gctx = &self.module.to_ctx(); - let source_type = expr_resolution.to_wgsl(gctx); + let source_type = self.type_resolution_to_string(expr_resolution); super::Error::AutoConversionLeafScalar(Box::new(AutoConversionLeafScalarError { dest_span: goal_span, - dest_scalar: goal_scalar.to_wgsl(), + dest_scalar: goal_scalar.to_wgsl_for_diagnostics(), source_span: expr_span, source_type, })) @@ -275,8 +274,8 @@ impl<'source> super::ExpressionContext<'source, '_, '_> { let expr_type = &self.typifier()[expr]; super::Error::ConcretizationFailed(Box::new(ConcretizationFailedError { expr_span, - expr_type: expr_type.to_wgsl(&self.module.to_ctx()), - scalar: concretized.to_wgsl(), + expr_type: self.type_resolution_to_string(expr_type), + scalar: concretized.to_wgsl_for_diagnostics(), inner: err, })) })?; @@ -316,11 +315,12 @@ impl<'source> super::ExpressionContext<'source, '_, '_> { .into_iter() .map(|&c| self.typifier()[c].inner_with(types)); log::debug!( - "wgsl automatic_conversion_consensus: {:?}", + "wgsl automatic_conversion_consensus: {}", inners .clone() - .map(|inner| inner.to_wgsl(&self.module.to_ctx())) + .map(|inner| self.type_inner_to_string(inner)) .collect::>() + .join(", ") ); let mut best = inners.next().unwrap().scalar().ok_or(0_usize)?; for (inner, i) in inners.zip(1..) { @@ -333,7 +333,7 @@ impl<'source> super::ExpressionContext<'source, '_, '_> { } } - log::debug!(" consensus: {:?}", best.to_wgsl()); + log::debug!(" consensus: {}", best.to_wgsl_for_diagnostics()); Ok(best) } } diff --git a/naga/src/front/wgsl/lower/mod.rs b/naga/src/front/wgsl/lower/mod.rs index 00246e3156..0a13f438f0 100644 --- a/naga/src/front/wgsl/lower/mod.rs +++ b/naga/src/front/wgsl/lower/mod.rs @@ -6,6 +6,7 @@ use alloc::{ }; use core::num::NonZeroU32; +use crate::common::wgsl::TypeContext; use crate::front::wgsl::error::{Error, ExpectedToken, InvalidAssignmentType}; use crate::front::wgsl::index::Index; use crate::front::wgsl::parse::number::Number; @@ -388,6 +389,30 @@ pub struct ExpressionContext<'source, 'temp, 'out> { expr_type: ExpressionContextType<'temp, 'out>, } +impl TypeContext for ExpressionContext<'_, '_, '_> { + fn lookup_type(&self, handle: Handle) -> &crate::Type { + &self.module.types[handle] + } + + fn type_name(&self, handle: Handle) -> &str { + self.module.types[handle] + .name + .as_deref() + .unwrap_or("{anonymous type}") + } + + fn write_override( + &self, + handle: Handle, + out: &mut W, + ) -> core::fmt::Result { + match self.module.overrides[handle].name { + Some(ref name) => out.write_str(name), + None => write!(out, "{{anonymous override {handle:?}}}"), + } + } +} + impl<'source, 'temp, 'out> ExpressionContext<'source, 'temp, 'out> { #[allow(dead_code)] fn as_const(&mut self) -> ExpressionContext<'source, '_, '_> { @@ -1256,8 +1281,8 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { if !explicit_inner.equivalent(init_inner, &ectx.module.types) { return Err(Box::new(Error::InitializationTypeMismatch { name: name.span, - expected: explicit_inner.to_wgsl(&ectx.module.to_ctx()), - got: init_inner.to_wgsl(&ectx.module.to_ctx()), + expected: ectx.type_inner_to_string(explicit_inner), + got: ectx.type_inner_to_string(init_inner), })); } ty = explicit_ty; @@ -1487,11 +1512,10 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { .inner .equivalent(&ctx.module.types[init_ty].inner, &ctx.module.types) { - let gctx = &ctx.module.to_ctx(); return Err(Box::new(Error::InitializationTypeMismatch { name: l.name.span, - expected: ty.to_wgsl(gctx), - got: init_ty.to_wgsl(gctx), + expected: ctx.type_to_string(ty), + got: ctx.type_to_string(init_ty), })); } } @@ -2192,11 +2216,10 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { crate::TypeInner::Vector { scalar, .. } => scalar, _ => { let ty = resolve!(ctx, expr); - let gctx = &ctx.module.to_ctx(); return Err(Box::new(Error::BadTypeCast { - from_type: ty.to_wgsl(gctx), + from_type: ctx.type_resolution_to_string(ty), span: ty_span, - to_type: to_resolved.to_wgsl(gctx), + to_type: ctx.type_to_string(to_resolved), })); } }; diff --git a/naga/src/front/wgsl/mod.rs b/naga/src/front/wgsl/mod.rs index 7ea66265cb..977829a217 100644 --- a/naga/src/front/wgsl/mod.rs +++ b/naga/src/front/wgsl/mod.rs @@ -10,7 +10,6 @@ mod lower; mod parse; #[cfg(test)] mod tests; -mod to_wgsl; pub use crate::front::wgsl::error::ParseError; pub use crate::front::wgsl::parse::directive::language_extension::{ diff --git a/naga/src/front/wgsl/to_wgsl.rs b/naga/src/front/wgsl/to_wgsl.rs deleted file mode 100644 index a1677be016..0000000000 --- a/naga/src/front/wgsl/to_wgsl.rs +++ /dev/null @@ -1,254 +0,0 @@ -//! Producing the WGSL forms of types, for use in error messages. - -use crate::common::wgsl::ToWgsl; - -use alloc::{ - format, - string::{String, ToString}, -}; - -use crate::proc::GlobalCtx; -use crate::Handle; - -impl crate::proc::TypeResolution { - pub fn to_wgsl(&self, gctx: &GlobalCtx) -> String { - match *self { - crate::proc::TypeResolution::Handle(handle) => handle.to_wgsl(gctx), - crate::proc::TypeResolution::Value(ref inner) => inner.to_wgsl(gctx), - } - } -} - -impl Handle { - /// Formats the type as it is written in wgsl. - /// - /// For example `vec3`. - pub fn to_wgsl(self, gctx: &GlobalCtx) -> String { - let ty = &gctx.types[self]; - match ty.name { - Some(ref name) => name.clone(), - None => ty.inner.to_wgsl(gctx), - } - } -} - -impl crate::TypeInner { - /// Formats the type as it is written in wgsl. - /// - /// For example `vec3`. - /// - /// Note: `TypeInner::Struct` doesn't include the name of the - /// struct type. Therefore this method will simply return "struct" - /// for them. - pub fn to_wgsl(&self, gctx: &GlobalCtx) -> String { - use crate::TypeInner as Ti; - - match *self { - Ti::Scalar(scalar) => scalar.to_wgsl(), - Ti::Vector { size, scalar } => { - format!("vec{}<{}>", size as u32, scalar.to_wgsl()) - } - Ti::Matrix { - columns, - rows, - scalar, - } => { - format!( - "mat{}x{}<{}>", - columns as u32, - rows as u32, - scalar.to_wgsl(), - ) - } - Ti::Atomic(scalar) => { - format!("atomic<{}>", scalar.to_wgsl()) - } - Ti::Pointer { base, .. } => { - let name = base.to_wgsl(gctx); - format!("ptr<{name}>") - } - Ti::ValuePointer { scalar, .. } => { - format!("ptr<{}>", scalar.to_wgsl()) - } - Ti::Array { base, size, .. } => { - let base = base.to_wgsl(gctx); - match size { - crate::ArraySize::Constant(size) => format!("array<{base}, {size}>"), - crate::ArraySize::Pending(_) => unreachable!(), - crate::ArraySize::Dynamic => format!("array<{base}>"), - } - } - Ti::Struct { .. } => { - // TODO: Actually output the struct? - "struct".to_string() - } - Ti::Image { - dim, - arrayed, - class, - } => { - let dim_suffix = match dim { - crate::ImageDimension::D1 => "_1d", - crate::ImageDimension::D2 => "_2d", - crate::ImageDimension::D3 => "_3d", - crate::ImageDimension::Cube => "_cube", - }; - let array_suffix = if arrayed { "_array" } else { "" }; - - let class_suffix = match class { - crate::ImageClass::Sampled { multi: true, .. } => "_multisampled", - crate::ImageClass::Depth { multi: false } => "_depth", - crate::ImageClass::Depth { multi: true } => "_depth_multisampled", - crate::ImageClass::Sampled { multi: false, .. } - | crate::ImageClass::Storage { .. } => "", - }; - - let type_in_brackets = match class { - crate::ImageClass::Sampled { kind, .. } => { - // Note: The only valid widths are 4 bytes wide. - // The lexer has already verified this, so we can safely assume it here. - // https://gpuweb.github.io/gpuweb/wgsl/#sampled-texture-type - let element_type = crate::Scalar { kind, width: 4 }.to_wgsl(); - format!("<{element_type}>") - } - crate::ImageClass::Depth { multi: _ } => String::new(), - crate::ImageClass::Storage { format, access } => { - if access.contains(crate::StorageAccess::STORE) { - format!("<{},write>", format.to_wgsl()) - } else { - format!("<{}>", format.to_wgsl()) - } - } - }; - - format!("texture{class_suffix}{dim_suffix}{array_suffix}{type_in_brackets}") - } - Ti::Sampler { .. } => "sampler".to_string(), - Ti::AccelerationStructure { vertex_return } => { - let caps = if vertex_return { "" } else { "" }; - format!("acceleration_structure{}", caps) - } - Ti::RayQuery { vertex_return } => { - let caps = if vertex_return { "" } else { "" }; - format!("ray_query{}", caps) - } - Ti::BindingArray { base, size, .. } => { - let member_type = &gctx.types[base]; - let base = member_type.name.as_deref().unwrap_or("unknown"); - match size { - crate::ArraySize::Constant(size) => format!("binding_array<{base}, {size}>"), - crate::ArraySize::Pending(_) => unreachable!(), - crate::ArraySize::Dynamic => format!("binding_array<{base}>"), - } - } - } - } -} - -impl crate::Scalar { - /// Format a scalar kind+width as a type is written in wgsl. - /// - /// Examples: `f32`, `u64`, `bool`. - pub fn to_wgsl(self) -> String { - let prefix = match self.kind { - crate::ScalarKind::Sint => "i", - crate::ScalarKind::Uint => "u", - crate::ScalarKind::Float => "f", - crate::ScalarKind::Bool => return "bool".to_string(), - crate::ScalarKind::AbstractInt => return "{AbstractInt}".to_string(), - crate::ScalarKind::AbstractFloat => return "{AbstractFloat}".to_string(), - }; - format!("{}{}", prefix, self.width * 8) - } -} - -#[cfg(test)] -mod tests { - use alloc::{string::ToString, vec}; - - #[test] - fn to_wgsl() { - use core::num::NonZeroU32; - - let mut types = crate::UniqueArena::new(); - - let mytype1 = types.insert( - crate::Type { - name: Some("MyType1".to_string()), - inner: crate::TypeInner::Struct { - members: vec![], - span: 0, - }, - }, - Default::default(), - ); - let mytype2 = types.insert( - crate::Type { - name: Some("MyType2".to_string()), - inner: crate::TypeInner::Struct { - members: vec![], - span: 0, - }, - }, - Default::default(), - ); - - let gctx = crate::proc::GlobalCtx { - types: &types, - constants: &crate::Arena::new(), - overrides: &crate::Arena::new(), - global_expressions: &crate::Arena::new(), - }; - let array = crate::TypeInner::Array { - base: mytype1, - stride: 4, - size: crate::ArraySize::Constant(unsafe { NonZeroU32::new_unchecked(32) }), - }; - assert_eq!(array.to_wgsl(&gctx), "array"); - - let mat = crate::TypeInner::Matrix { - rows: crate::VectorSize::Quad, - columns: crate::VectorSize::Bi, - scalar: crate::Scalar::F64, - }; - assert_eq!(mat.to_wgsl(&gctx), "mat2x4"); - - let ptr = crate::TypeInner::Pointer { - base: mytype2, - space: crate::AddressSpace::Storage { - access: crate::StorageAccess::default(), - }, - }; - assert_eq!(ptr.to_wgsl(&gctx), "ptr"); - - let img1 = crate::TypeInner::Image { - dim: crate::ImageDimension::D2, - arrayed: false, - class: crate::ImageClass::Sampled { - kind: crate::ScalarKind::Float, - multi: true, - }, - }; - assert_eq!(img1.to_wgsl(&gctx), "texture_multisampled_2d"); - - let img2 = crate::TypeInner::Image { - dim: crate::ImageDimension::Cube, - arrayed: true, - class: crate::ImageClass::Depth { multi: false }, - }; - assert_eq!(img2.to_wgsl(&gctx), "texture_depth_cube_array"); - - let img3 = crate::TypeInner::Image { - dim: crate::ImageDimension::D2, - arrayed: false, - class: crate::ImageClass::Depth { multi: true }, - }; - assert_eq!(img3.to_wgsl(&gctx), "texture_depth_multisampled_2d"); - - let array = crate::TypeInner::BindingArray { - base: mytype1, - size: crate::ArraySize::Constant(unsafe { NonZeroU32::new_unchecked(32) }), - }; - assert_eq!(array.to_wgsl(&gctx), "binding_array"); - } -} diff --git a/naga/src/proc/constant_evaluator.rs b/naga/src/proc/constant_evaluator.rs index bb6893bc1f..1fef42620e 100644 --- a/naga/src/proc/constant_evaluator.rs +++ b/naga/src/proc/constant_evaluator.rs @@ -14,6 +14,9 @@ use crate::{ ScalarKind, Span, Type, TypeInner, UnaryOperator, }; +#[cfg(feature = "wgsl-in")] +use crate::common::wgsl::TryToWgsl; + /// A macro that allows dollar signs (`$`) to be emitted by other macros. Useful for generating /// `macro_rules!` items that, in turn, emit their own `macro_rules!` items. /// @@ -1554,7 +1557,7 @@ impl<'a> ConstantEvaluator<'a> { let from = format!("{:?} {:?}", expr, self.expressions[expr]); #[cfg(feature = "wgsl-in")] - let to = target.to_wgsl(); + let to = target.to_wgsl_for_diagnostics(); #[cfg(not(feature = "wgsl-in"))] let to = format!("{target:?}");