naga/common/wgsl/
types.rs

1//! Code for formatting Naga IR types as WGSL source code.
2
3use super::{address_space_str, ToWgsl, TryToWgsl};
4use crate::common;
5use crate::proc::TypeResolution;
6use crate::{Handle, Scalar, TypeInner};
7
8use alloc::string::String;
9use core::fmt::Write;
10
11/// A context for printing Naga IR types as WGSL.
12///
13/// This trait's default methods [`write_type`] and
14/// [`write_type_inner`] do the work of formatting types as WGSL.
15/// Implementors must provide the remaining methods, to customize
16/// behavior for the context at hand.
17///
18/// For example, the WGSL backend would provide an implementation of
19/// [`type_name`] that handles hygienic renaming, whereas the WGSL
20/// front end would simply show the name that was given in the source.
21///
22/// [`write_type`]: TypeContext::write_type
23/// [`write_type_inner`]: TypeContext::write_type_inner
24/// [`type_name`]: TypeContext::type_name
25pub trait TypeContext {
26    /// Return the [`Type`] referred to by `handle`.
27    ///
28    /// [`Type`]: crate::Type
29    fn lookup_type(&self, handle: Handle<crate::Type>) -> &crate::Type;
30
31    /// Return the name to be used for the type referred to by
32    /// `handle`.
33    fn type_name(&self, handle: Handle<crate::Type>) -> &str;
34
35    /// Write the WGSL form of `override` to `out`.
36    fn write_override<W: Write>(
37        &self,
38        r#override: Handle<crate::Override>,
39        out: &mut W,
40    ) -> core::fmt::Result;
41
42    /// Write a [`TypeInner::Struct`] for which we are unable to find a name.
43    ///
44    /// The names of struct types are only available if we have `Handle<Type>`,
45    /// not from [`TypeInner`]. For logging and debugging, it's fine to just
46    /// write something helpful to the developer, but for generating WGSL,
47    /// this should be unreachable.
48    fn write_unnamed_struct<W: Write>(&self, inner: &TypeInner, out: &mut W) -> core::fmt::Result;
49
50    /// Write a [`TypeInner`] that has no representation as WGSL source,
51    /// even including Naga extensions.
52    ///
53    /// A backend might implement this with a call to the [`unreachable!`]
54    /// macro, since backends are allowed to assume that the module has passed
55    /// validation.
56    ///
57    /// The default implementation is appropriate for generating type names to
58    /// appear in error messages. It punts to `TypeInner`'s [`core::fmt::Debug`]
59    /// implementation, since it's probably best to show the user something they
60    /// can act on.
61    fn write_non_wgsl_inner<W: Write>(&self, inner: &TypeInner, out: &mut W) -> core::fmt::Result {
62        write!(out, "{{non-WGSL Naga type {inner:?}}}")
63    }
64
65    /// Write a [`Scalar`] that has no representation as WGSL source,
66    /// even including Naga extensions.
67    ///
68    /// A backend might implement this with a call to the [`unreachable!`]
69    /// macro, since backends are allowed to assume that the module has passed
70    /// validation.
71    ///
72    /// The default implementation is appropriate for generating type names to
73    /// appear in error messages. It punts to `Scalar`'s [`core::fmt::Debug`]
74    /// implementation, since it's probably best to show the user something they
75    /// can act on.
76    fn write_non_wgsl_scalar<W: Write>(&self, scalar: Scalar, out: &mut W) -> core::fmt::Result {
77        match scalar.kind {
78            crate::ScalarKind::Sint
79            | crate::ScalarKind::Uint
80            | crate::ScalarKind::Float
81            | crate::ScalarKind::Bool => write!(out, "{{non-WGSL Naga scalar {scalar:?}}}"),
82
83            // The abstract types are kind of an odd quasi-WGSL category:
84            // they are definitely part of the spec, but they are not expressible
85            // in WGSL itself. So we want to call them out by name in error messages,
86            // but the WGSL backend should never generate these.
87            crate::ScalarKind::AbstractInt => out.write_str("{AbstractInt}"),
88            crate::ScalarKind::AbstractFloat => out.write_str("{AbstractFloat}"),
89        }
90    }
91
92    /// Write the type `ty` as it would appear in a value's declaration.
93    ///
94    /// Write the type referred to by `ty` in `module` as it would appear in
95    /// a `var`, `let`, etc. declaration, or in a function's argument list.
96    fn write_type<W: Write>(&self, handle: Handle<crate::Type>, out: &mut W) -> core::fmt::Result {
97        let ty = self.lookup_type(handle);
98        match ty.inner {
99            TypeInner::Struct { .. } => out.write_str(self.type_name(handle))?,
100            ref other => self.write_type_inner(other, out)?,
101        }
102
103        Ok(())
104    }
105
106    /// Write the [`TypeInner`] `inner` as it would appear in a value's declaration.
107    ///
108    /// Write `inner` as it would appear in a `var`, `let`, etc.
109    /// declaration, or in a function's argument list.
110    ///
111    /// Note that this cannot handle writing [`Struct`] types: those
112    /// must be referred to by name, but the name isn't available in
113    /// [`TypeInner`].
114    ///
115    /// [`Struct`]: TypeInner::Struct
116    fn write_type_inner<W: Write>(&self, inner: &TypeInner, out: &mut W) -> core::fmt::Result {
117        match try_write_type_inner(self, inner, out) {
118            Ok(()) => Ok(()),
119            Err(WriteTypeError::Format(err)) => Err(err),
120            Err(WriteTypeError::NonWgsl) => self.write_non_wgsl_inner(inner, out),
121        }
122    }
123
124    /// Write the [`Scalar`] `scalar` as a WGSL type.
125    fn write_scalar<W: Write>(&self, scalar: Scalar, out: &mut W) -> core::fmt::Result {
126        match scalar.try_to_wgsl() {
127            Some(string) => out.write_str(string),
128            None => self.write_non_wgsl_scalar(scalar, out),
129        }
130    }
131
132    /// Write the [`TypeResolution`] `resolution` as a WGSL type.
133    fn write_type_resolution<W: Write>(
134        &self,
135        resolution: &TypeResolution,
136        out: &mut W,
137    ) -> core::fmt::Result {
138        match *resolution {
139            TypeResolution::Handle(handle) => self.write_type(handle, out),
140            TypeResolution::Value(ref inner) => self.write_type_inner(inner, out),
141        }
142    }
143
144    fn write_type_conclusion<W: Write>(
145        &self,
146        conclusion: &crate::proc::Conclusion,
147        out: &mut W,
148    ) -> core::fmt::Result {
149        use crate::proc::Conclusion as Co;
150
151        match *conclusion {
152            Co::Value(ref inner) => self.write_type_inner(inner, out),
153            Co::Predeclared(ref predeclared) => out.write_str(&predeclared.struct_name()),
154        }
155    }
156
157    fn write_type_rule<W: Write>(
158        &self,
159        name: &str,
160        rule: &crate::proc::Rule,
161        out: &mut W,
162    ) -> core::fmt::Result {
163        write!(out, "fn {name}(")?;
164        for (i, arg) in rule.arguments.iter().enumerate() {
165            if i > 0 {
166                out.write_str(", ")?;
167            }
168            self.write_type_resolution(arg, out)?
169        }
170        out.write_str(") -> ")?;
171        self.write_type_conclusion(&rule.conclusion, out)?;
172        Ok(())
173    }
174
175    fn type_to_string(&self, handle: Handle<crate::Type>) -> String {
176        let mut buf = String::new();
177        self.write_type(handle, &mut buf).unwrap();
178        buf
179    }
180
181    fn type_resolution_to_string(&self, resolution: &TypeResolution) -> String {
182        let mut buf = String::new();
183        self.write_type_resolution(resolution, &mut buf).unwrap();
184        buf
185    }
186
187    fn type_rule_to_string(&self, name: &str, rule: &crate::proc::Rule) -> String {
188        let mut buf = String::new();
189        self.write_type_rule(name, rule, &mut buf).unwrap();
190        buf
191    }
192}
193
194fn try_write_type_inner<C, W>(ctx: &C, inner: &TypeInner, out: &mut W) -> Result<(), WriteTypeError>
195where
196    C: TypeContext + ?Sized,
197    W: Write,
198{
199    match *inner {
200        TypeInner::Vector { size, scalar } => {
201            write!(out, "vec{}<", common::vector_size_str(size))?;
202            ctx.write_scalar(scalar, out)?;
203            out.write_str(">")?;
204        }
205        TypeInner::Sampler { comparison: false } => {
206            write!(out, "sampler")?;
207        }
208        TypeInner::Sampler { comparison: true } => {
209            write!(out, "sampler_comparison")?;
210        }
211        TypeInner::Image {
212            dim,
213            arrayed,
214            class,
215        } => {
216            // More about texture types: https://gpuweb.github.io/gpuweb/wgsl/#sampled-texture-type
217            use crate::ImageClass as Ic;
218
219            let dim_str = dim.to_wgsl();
220            let arrayed_str = if arrayed { "_array" } else { "" };
221            match class {
222                Ic::Sampled { kind, multi } => {
223                    let multisampled_str = if multi { "multisampled_" } else { "" };
224                    write!(out, "texture_{multisampled_str}{dim_str}{arrayed_str}<")?;
225                    ctx.write_scalar(Scalar { kind, width: 4 }, out)?;
226                    out.write_str(">")?;
227                }
228                Ic::Depth { multi } => {
229                    let multisampled_str = if multi { "multisampled_" } else { "" };
230                    write!(
231                        out,
232                        "texture_depth_{multisampled_str}{dim_str}{arrayed_str}"
233                    )?;
234                }
235                Ic::Storage { format, access } => {
236                    let format_str = format.to_wgsl();
237                    let access_str = if access.contains(crate::StorageAccess::ATOMIC) {
238                        ",atomic"
239                    } else if access
240                        .contains(crate::StorageAccess::LOAD | crate::StorageAccess::STORE)
241                    {
242                        ",read_write"
243                    } else if access.contains(crate::StorageAccess::LOAD) {
244                        ",read"
245                    } else {
246                        ",write"
247                    };
248                    write!(
249                        out,
250                        "texture_storage_{dim_str}{arrayed_str}<{format_str}{access_str}>"
251                    )?;
252                }
253                Ic::External => {
254                    write!(out, "texture_external")?;
255                }
256            }
257        }
258        TypeInner::Scalar(scalar) => {
259            ctx.write_scalar(scalar, out)?;
260        }
261        TypeInner::Atomic(scalar) => {
262            out.write_str("atomic<")?;
263            ctx.write_scalar(scalar, out)?;
264            out.write_str(">")?;
265        }
266        TypeInner::Array {
267            base,
268            size,
269            stride: _,
270        } => {
271            // More info https://gpuweb.github.io/gpuweb/wgsl/#array-types
272            // array<A, 3> -- Constant array
273            // array<A> -- Dynamic array
274            write!(out, "array<")?;
275            match size {
276                crate::ArraySize::Constant(len) => {
277                    ctx.write_type(base, out)?;
278                    write!(out, ", {len}")?;
279                }
280                crate::ArraySize::Pending(r#override) => {
281                    ctx.write_override(r#override, out)?;
282                }
283                crate::ArraySize::Dynamic => {
284                    ctx.write_type(base, out)?;
285                }
286            }
287            write!(out, ">")?;
288        }
289        TypeInner::BindingArray { base, size } => {
290            // More info https://github.com/gpuweb/gpuweb/issues/2105
291            write!(out, "binding_array<")?;
292            match size {
293                crate::ArraySize::Constant(len) => {
294                    ctx.write_type(base, out)?;
295                    write!(out, ", {len}")?;
296                }
297                crate::ArraySize::Pending(r#override) => {
298                    ctx.write_override(r#override, out)?;
299                }
300                crate::ArraySize::Dynamic => {
301                    ctx.write_type(base, out)?;
302                }
303            }
304            write!(out, ">")?;
305        }
306        TypeInner::Matrix {
307            columns,
308            rows,
309            scalar,
310        } => {
311            write!(
312                out,
313                "mat{}x{}<",
314                common::vector_size_str(columns),
315                common::vector_size_str(rows),
316            )?;
317            ctx.write_scalar(scalar, out)?;
318            out.write_str(">")?;
319        }
320        TypeInner::CooperativeMatrix {
321            columns,
322            rows,
323            scalar,
324            role,
325        } => {
326            write!(
327                out,
328                "coop_mat{}x{}<{},{}>",
329                columns as u32,
330                rows as u32,
331                scalar.try_to_wgsl().unwrap_or_default(),
332                role.to_wgsl(),
333            )?;
334        }
335        TypeInner::Pointer { base, space } => {
336            let (address, maybe_access) = address_space_str(space);
337            // Everything but `AddressSpace::Handle` gives us a `address` name, but
338            // Naga IR never produces pointers to handles, so it doesn't matter much
339            // how we write such a type. Just write it as the base type alone.
340            if let Some(space) = address {
341                write!(out, "ptr<{space}, ")?;
342            }
343            ctx.write_type(base, out)?;
344            if address.is_some() {
345                if let Some(access) = maybe_access {
346                    write!(out, ", {access}")?;
347                }
348                write!(out, ">")?;
349            }
350        }
351        TypeInner::ValuePointer {
352            size: None,
353            scalar,
354            space,
355        } => {
356            let (address, maybe_access) = address_space_str(space);
357            if let Some(space) = address {
358                write!(out, "ptr<{space}, ")?;
359                ctx.write_scalar(scalar, out)?;
360                if let Some(access) = maybe_access {
361                    write!(out, ", {access}")?;
362                }
363                write!(out, ">")?;
364            } else {
365                return Err(WriteTypeError::NonWgsl);
366            }
367        }
368        TypeInner::ValuePointer {
369            size: Some(size),
370            scalar,
371            space,
372        } => {
373            let (address, maybe_access) = address_space_str(space);
374            if let Some(space) = address {
375                write!(out, "ptr<{}, vec{}<", space, common::vector_size_str(size),)?;
376                ctx.write_scalar(scalar, out)?;
377                out.write_str(">")?;
378                if let Some(access) = maybe_access {
379                    write!(out, ", {access}")?;
380                }
381                write!(out, ">")?;
382            } else {
383                return Err(WriteTypeError::NonWgsl);
384            }
385            write!(out, ">")?;
386        }
387        TypeInner::AccelerationStructure { vertex_return } => {
388            let caps = if vertex_return { "<vertex_return>" } else { "" };
389            write!(out, "acceleration_structure{caps}")?
390        }
391        TypeInner::Struct { .. } => {
392            ctx.write_unnamed_struct(inner, out)?;
393        }
394        TypeInner::RayQuery { vertex_return } => {
395            let caps = if vertex_return { "<vertex_return>" } else { "" };
396            write!(out, "ray_query{caps}")?
397        }
398    }
399
400    Ok(())
401}
402
403/// Error type returned by `try_write_type_inner`.
404///
405/// This type is private to the module.
406enum WriteTypeError {
407    Format(core::fmt::Error),
408    NonWgsl,
409}
410
411impl From<core::fmt::Error> for WriteTypeError {
412    fn from(err: core::fmt::Error) -> Self {
413        Self::Format(err)
414    }
415}
416
417/// Format types as WGSL based on a [`GlobalCtx`].
418///
419/// This is probably good enough for diagnostic output, but it has some
420/// limitations:
421///
422/// - It does not apply [`Namer`] renamings, to avoid collisions.
423///
424/// - It generates invalid WGSL for anonymous struct types.
425///
426/// - It doesn't write the lengths of override-expression-sized arrays
427///   correctly, unless the expression is just the override identifier.
428///
429/// [`GlobalCtx`]: crate::proc::GlobalCtx
430/// [`Namer`]: crate::proc::Namer
431impl TypeContext for crate::proc::GlobalCtx<'_> {
432    fn lookup_type(&self, handle: Handle<crate::Type>) -> &crate::Type {
433        &self.types[handle]
434    }
435
436    fn type_name(&self, handle: Handle<crate::Type>) -> &str {
437        self.types[handle]
438            .name
439            .as_deref()
440            .unwrap_or("{anonymous type}")
441    }
442
443    fn write_unnamed_struct<W: Write>(&self, _: &TypeInner, out: &mut W) -> core::fmt::Result {
444        write!(out, "{{unnamed struct}}")
445    }
446
447    fn write_override<W: Write>(
448        &self,
449        handle: Handle<crate::Override>,
450        out: &mut W,
451    ) -> core::fmt::Result {
452        match self.overrides[handle].name {
453            Some(ref name) => out.write_str(name),
454            None => write!(out, "{{anonymous override {handle:?}}}"),
455        }
456    }
457}
458
459/// Format types as WGSL based on a `UniqueArena<Type>`.
460///
461/// This is probably only good enough for logging:
462///
463/// - It does not apply any kind of [`Namer`] renamings.
464///
465/// - It generates invalid WGSL for anonymous struct types.
466///
467/// - It doesn't write override-sized arrays properly.
468///
469/// [`Namer`]: crate::proc::Namer
470impl TypeContext for crate::UniqueArena<crate::Type> {
471    fn lookup_type(&self, handle: Handle<crate::Type>) -> &crate::Type {
472        &self[handle]
473    }
474
475    fn type_name(&self, handle: Handle<crate::Type>) -> &str {
476        self[handle].name.as_deref().unwrap_or("{anonymous type}")
477    }
478
479    fn write_unnamed_struct<W: Write>(&self, inner: &TypeInner, out: &mut W) -> core::fmt::Result {
480        write!(out, "{{unnamed struct {inner:?}}}")
481    }
482
483    fn write_override<W: Write>(
484        &self,
485        handle: Handle<crate::Override>,
486        out: &mut W,
487    ) -> core::fmt::Result {
488        write!(out, "{{override {handle:?}}}")
489    }
490}