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            }
254        }
255        TypeInner::Scalar(scalar) => {
256            ctx.write_scalar(scalar, out)?;
257        }
258        TypeInner::Atomic(scalar) => {
259            out.write_str("atomic<")?;
260            ctx.write_scalar(scalar, out)?;
261            out.write_str(">")?;
262        }
263        TypeInner::Array {
264            base,
265            size,
266            stride: _,
267        } => {
268            // More info https://gpuweb.github.io/gpuweb/wgsl/#array-types
269            // array<A, 3> -- Constant array
270            // array<A> -- Dynamic array
271            write!(out, "array<")?;
272            match size {
273                crate::ArraySize::Constant(len) => {
274                    ctx.write_type(base, out)?;
275                    write!(out, ", {len}")?;
276                }
277                crate::ArraySize::Pending(r#override) => {
278                    ctx.write_override(r#override, out)?;
279                }
280                crate::ArraySize::Dynamic => {
281                    ctx.write_type(base, out)?;
282                }
283            }
284            write!(out, ">")?;
285        }
286        TypeInner::BindingArray { base, size } => {
287            // More info https://github.com/gpuweb/gpuweb/issues/2105
288            write!(out, "binding_array<")?;
289            match size {
290                crate::ArraySize::Constant(len) => {
291                    ctx.write_type(base, out)?;
292                    write!(out, ", {len}")?;
293                }
294                crate::ArraySize::Pending(r#override) => {
295                    ctx.write_override(r#override, out)?;
296                }
297                crate::ArraySize::Dynamic => {
298                    ctx.write_type(base, out)?;
299                }
300            }
301            write!(out, ">")?;
302        }
303        TypeInner::Matrix {
304            columns,
305            rows,
306            scalar,
307        } => {
308            write!(
309                out,
310                "mat{}x{}<",
311                common::vector_size_str(columns),
312                common::vector_size_str(rows),
313            )?;
314            ctx.write_scalar(scalar, out)?;
315            out.write_str(">")?;
316        }
317        TypeInner::Pointer { base, space } => {
318            let (address, maybe_access) = address_space_str(space);
319            // Everything but `AddressSpace::Handle` gives us a `address` name, but
320            // Naga IR never produces pointers to handles, so it doesn't matter much
321            // how we write such a type. Just write it as the base type alone.
322            if let Some(space) = address {
323                write!(out, "ptr<{space}, ")?;
324            }
325            ctx.write_type(base, out)?;
326            if address.is_some() {
327                if let Some(access) = maybe_access {
328                    write!(out, ", {access}")?;
329                }
330                write!(out, ">")?;
331            }
332        }
333        TypeInner::ValuePointer {
334            size: None,
335            scalar,
336            space,
337        } => {
338            let (address, maybe_access) = address_space_str(space);
339            if let Some(space) = address {
340                write!(out, "ptr<{space}, ")?;
341                ctx.write_scalar(scalar, out)?;
342                if let Some(access) = maybe_access {
343                    write!(out, ", {access}")?;
344                }
345                write!(out, ">")?;
346            } else {
347                return Err(WriteTypeError::NonWgsl);
348            }
349        }
350        TypeInner::ValuePointer {
351            size: Some(size),
352            scalar,
353            space,
354        } => {
355            let (address, maybe_access) = address_space_str(space);
356            if let Some(space) = address {
357                write!(out, "ptr<{}, vec{}<", space, common::vector_size_str(size),)?;
358                ctx.write_scalar(scalar, out)?;
359                out.write_str(">")?;
360                if let Some(access) = maybe_access {
361                    write!(out, ", {access}")?;
362                }
363                write!(out, ">")?;
364            } else {
365                return Err(WriteTypeError::NonWgsl);
366            }
367            write!(out, ">")?;
368        }
369        TypeInner::AccelerationStructure { vertex_return } => {
370            let caps = if vertex_return { "<vertex_return>" } else { "" };
371            write!(out, "acceleration_structure{caps}")?
372        }
373        TypeInner::Struct { .. } => {
374            ctx.write_unnamed_struct(inner, out)?;
375        }
376        TypeInner::RayQuery { vertex_return } => {
377            let caps = if vertex_return { "<vertex_return>" } else { "" };
378            write!(out, "ray_query{caps}")?
379        }
380    }
381
382    Ok(())
383}
384
385/// Error type returned by `try_write_type_inner`.
386///
387/// This type is private to the module.
388enum WriteTypeError {
389    Format(core::fmt::Error),
390    NonWgsl,
391}
392
393impl From<core::fmt::Error> for WriteTypeError {
394    fn from(err: core::fmt::Error) -> Self {
395        Self::Format(err)
396    }
397}
398
399/// Format types as WGSL based on a [`GlobalCtx`].
400///
401/// This is probably good enough for diagnostic output, but it has some
402/// limitations:
403///
404/// - It does not apply [`Namer`] renamings, to avoid collisions.
405///
406/// - It generates invalid WGSL for anonymous struct types.
407///
408/// - It doesn't write the lengths of override-expression-sized arrays
409///   correctly, unless the expression is just the override identifier.
410///
411/// [`GlobalCtx`]: crate::proc::GlobalCtx
412/// [`Namer`]: crate::proc::Namer
413impl TypeContext for crate::proc::GlobalCtx<'_> {
414    fn lookup_type(&self, handle: Handle<crate::Type>) -> &crate::Type {
415        &self.types[handle]
416    }
417
418    fn type_name(&self, handle: Handle<crate::Type>) -> &str {
419        self.types[handle]
420            .name
421            .as_deref()
422            .unwrap_or("{anonymous type}")
423    }
424
425    fn write_unnamed_struct<W: Write>(&self, _: &TypeInner, out: &mut W) -> core::fmt::Result {
426        write!(out, "{{unnamed struct}}")
427    }
428
429    fn write_override<W: Write>(
430        &self,
431        handle: Handle<crate::Override>,
432        out: &mut W,
433    ) -> core::fmt::Result {
434        match self.overrides[handle].name {
435            Some(ref name) => out.write_str(name),
436            None => write!(out, "{{anonymous override {handle:?}}}"),
437        }
438    }
439}
440
441/// Format types as WGSL based on a `UniqueArena<Type>`.
442///
443/// This is probably only good enough for logging:
444///
445/// - It does not apply any kind of [`Namer`] renamings.
446///
447/// - It generates invalid WGSL for anonymous struct types.
448///
449/// - It doesn't write override-sized arrays properly.
450///
451/// [`Namer`]: crate::proc::Namer
452impl TypeContext for crate::UniqueArena<crate::Type> {
453    fn lookup_type(&self, handle: Handle<crate::Type>) -> &crate::Type {
454        &self[handle]
455    }
456
457    fn type_name(&self, handle: Handle<crate::Type>) -> &str {
458        self[handle].name.as_deref().unwrap_or("{anonymous type}")
459    }
460
461    fn write_unnamed_struct<W: Write>(&self, inner: &TypeInner, out: &mut W) -> core::fmt::Result {
462        write!(out, "{{unnamed struct {inner:?}}}")
463    }
464
465    fn write_override<W: Write>(
466        &self,
467        handle: Handle<crate::Override>,
468        out: &mut W,
469    ) -> core::fmt::Result {
470        write!(out, "{{override {handle:?}}}")
471    }
472}