Skip to content

Commit a7b5a89

Browse files
committed
[naga] Support builtin function overloads correctly.
Define a new trait, `proc::builtins::OverloadSet`, for types that represent a Naga IR builtin function's set of overloads. The `OverloadSet` trait includes operations needed to validate calls, choose automatic type conversions, and generate diagnostics. Add a new function, `ir::MathFunction::overloads`, which returns the given `MathFunction`'s set of overloads as an `impl OverloadSet` value. Use this in the WGSL front end, the validator, and the typifier. To support `MathFunction::overloads`, provide several implementations of `OverloadSet`, some that are very flexible but verbose, and others that are concise but more restrictive.
1 parent b3d996c commit a7b5a89

21 files changed

+2659
-892
lines changed

naga/src/common/diagnostic_debug.rs

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
//! Displaying Naga IR terms in debugging output.
2+
3+
#[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))]
4+
use crate::common::wgsl::TypeContext;
5+
6+
use crate::proc::TypeResolution;
7+
use crate::{Handle, Scalar, Type, TypeInner, UniqueArena};
8+
9+
use core::fmt;
10+
11+
/// A wrapper for displaying Naga IR terms in debugging output.
12+
///
13+
/// This is like [`DiagnosticDisplay`], but requires weaker context
14+
/// and produces correspondingly lower-fidelity output. For example,
15+
/// this cannot show the override names for override-sized array
16+
/// lengths.
17+
///
18+
/// [`DiagnosticDisplay`]: super::DiagnosticDisplay
19+
pub struct DiagnosticDebug<T>(pub T);
20+
21+
impl fmt::Debug for DiagnosticDebug<(Handle<Type>, &UniqueArena<Type>)> {
22+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23+
let (handle, ctx) = self.0;
24+
25+
#[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))]
26+
ctx.write_type(handle, f)?;
27+
28+
#[cfg(not(any(feature = "wgsl-in", feature = "wgsl-out")))]
29+
{
30+
let _ = ctx;
31+
write!(f, "{handle:?}")?;
32+
}
33+
34+
Ok(())
35+
}
36+
}
37+
38+
impl fmt::Debug for DiagnosticDebug<(&TypeInner, &UniqueArena<Type>)> {
39+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40+
let (inner, ctx) = self.0;
41+
42+
#[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))]
43+
ctx.write_type_inner(inner, f)?;
44+
45+
#[cfg(not(any(feature = "wgsl-in", feature = "wgsl-out")))]
46+
{
47+
let _ = ctx;
48+
write!(f, "{inner:?}")?;
49+
}
50+
51+
Ok(())
52+
}
53+
}
54+
55+
impl fmt::Debug for DiagnosticDebug<(&TypeResolution, &UniqueArena<Type>)> {
56+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57+
let (resolution, ctx) = self.0;
58+
59+
#[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))]
60+
ctx.write_type_resolution(resolution, f)?;
61+
62+
#[cfg(not(any(feature = "wgsl-in", feature = "wgsl-out")))]
63+
{
64+
let _ = ctx;
65+
write!(f, "{resolution:?}")?;
66+
}
67+
68+
Ok(())
69+
}
70+
}
71+
72+
impl fmt::Debug for DiagnosticDebug<Scalar> {
73+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74+
let scalar = self.0;
75+
76+
#[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))]
77+
f.write_str(&crate::common::wgsl::TryToWgsl::to_wgsl_for_diagnostics(
78+
scalar,
79+
))?;
80+
81+
#[cfg(not(any(feature = "wgsl-in", feature = "wgsl-out")))]
82+
write!(f, "{scalar:?}")?;
83+
84+
Ok(())
85+
}
86+
}

naga/src/common/diagnostic_display.rs

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
//! Displaying Naga IR terms in diagnostic output.
2+
3+
use crate::proc::{GlobalCtx, Rule};
4+
use crate::{Handle, Scalar, Type, TypeInner};
5+
6+
#[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))]
7+
use crate::common::wgsl::TypeContext;
8+
9+
use core::fmt;
10+
11+
/// A wrapper for displaying Naga IR terms in diagnostic output.
12+
///
13+
/// For some Naga IR type `T`, `DiagnosticDisplay<T>` implements
14+
/// [`core::fmt::Display`] in a way that displays values of type `T`
15+
/// appropriately for diagnostic messages presented to human readers.
16+
///
17+
/// For example, the implementation of [`Display`] for
18+
/// `DiagnosticDisplay<Scalar>` formats the type represented by the
19+
/// given [`Scalar`] appropriately for users.
20+
///
21+
/// Some types like `Handle<Type>` require contextual information like
22+
/// a type arena to be displayed. In such cases, we implement [`Display`]
23+
/// for a type like `DiagnosticDisplay<(Handle<Type>, GlobalCtx)>`, where
24+
/// the [`GlobalCtx`] type provides the necessary context.
25+
///
26+
/// If you only need debugging output, [`DiagnosticDebug`] uses
27+
/// easier-to-obtain context types but still does a good enough job
28+
/// for logging or debugging.
29+
///
30+
/// [`Display`]: core::fmt::Display
31+
/// [`Scalar`]: crate::Scalar
32+
/// [`GlobalCtx`]: crate::proc::GlobalCtx
33+
/// [`DiagnosticDebug`]: super::DiagnosticDebug
34+
///
35+
/// ## Language-sensitive diagnostics
36+
///
37+
/// As discussed in #7268, diagnostic output ought to depend on the
38+
/// source language from which the IR was produced: diagnostics
39+
/// resulting from processing GLSL code should use GLSL type syntax,
40+
/// for example. That means that `DiagnosticDisplay` ought to include
41+
/// some indication of which notation to use.
42+
///
43+
/// For the moment, only WGSL output is implemented, so
44+
/// `DiagnosticDisplay` lacks any support for this. However, the plan
45+
/// is that all language-independent code in Naga should use
46+
/// `DiagnosticDisplay` wherever appropriate, such that when its
47+
/// definition is expanded to include some indication of the right
48+
/// source language to use, any use site that does not supply this
49+
/// indication will provoke a compile-time error.
50+
pub struct DiagnosticDisplay<T>(pub T);
51+
52+
impl fmt::Display for DiagnosticDisplay<(Handle<Type>, GlobalCtx<'_>)> {
53+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54+
let (handle, ref ctx) = self.0;
55+
56+
#[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))]
57+
ctx.write_type(handle, f)?;
58+
59+
#[cfg(not(any(feature = "wgsl-in", feature = "wgsl-out")))]
60+
{
61+
let _ = ctx;
62+
write!(f, "{handle:?}")?;
63+
}
64+
65+
Ok(())
66+
}
67+
}
68+
69+
impl fmt::Display for DiagnosticDisplay<(&TypeInner, GlobalCtx<'_>)> {
70+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71+
let (inner, ref ctx) = self.0;
72+
73+
#[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))]
74+
ctx.write_type_inner(inner, f)?;
75+
76+
#[cfg(not(any(feature = "wgsl-in", feature = "wgsl-out")))]
77+
{
78+
let _ = ctx;
79+
write!(f, "{inner:?}")?;
80+
}
81+
82+
Ok(())
83+
}
84+
}
85+
86+
impl fmt::Display for DiagnosticDisplay<(&str, &Rule, GlobalCtx<'_>)> {
87+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88+
let (name, rule, ref ctx) = self.0;
89+
90+
#[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))]
91+
ctx.write_type_rule(name, rule, f)?;
92+
93+
#[cfg(not(any(feature = "wgsl-in", feature = "wgsl-out")))]
94+
{
95+
let _ = ctx;
96+
write!(
97+
f,
98+
"{name}({:?}) -> {:?}",
99+
rule.subexpressions, rule.conclusion
100+
)?;
101+
}
102+
103+
Ok(())
104+
}
105+
}
106+
107+
impl fmt::Display for DiagnosticDisplay<Scalar> {
108+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109+
let scalar = self.0;
110+
111+
#[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))]
112+
f.write_str(&crate::common::wgsl::TryToWgsl::to_wgsl_for_diagnostics(
113+
scalar,
114+
))?;
115+
116+
#[cfg(not(any(feature = "wgsl-in", feature = "wgsl-out")))]
117+
write!(f, "{scalar:?}")?;
118+
119+
Ok(())
120+
}
121+
}

naga/src/common/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
//! Code common to the front and backends for specific languages.
22
3+
pub mod diagnostic_debug;
4+
pub mod diagnostic_display;
35
pub mod predeclared;
46
pub mod wgsl;
57

8+
pub use diagnostic_debug::DiagnosticDebug;
9+
pub use diagnostic_display::DiagnosticDisplay;
10+
611
/// Helper function that returns the string corresponding to the [`VectorSize`](crate::VectorSize)
712
pub const fn vector_size_str(size: crate::VectorSize) -> &'static str {
813
match size {

naga/src/common/wgsl/types.rs

+104
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,37 @@ pub trait TypeContext {
133133
}
134134
}
135135

136+
fn write_type_conclusion<W: Write>(
137+
&self,
138+
conclusion: &crate::proc::Conclusion,
139+
out: &mut W,
140+
) -> core::fmt::Result {
141+
use crate::proc::Conclusion as Co;
142+
143+
match *conclusion {
144+
Co::Value(ref inner) => self.write_type_inner(inner, out),
145+
Co::Predeclared(ref predeclared) => out.write_str(&predeclared.struct_name()),
146+
}
147+
}
148+
149+
fn write_type_rule<W: Write>(
150+
&self,
151+
name: &str,
152+
rule: &crate::proc::Rule,
153+
out: &mut W,
154+
) -> core::fmt::Result {
155+
write!(out, "fn {name}(")?;
156+
for (i, arg) in rule.subexpressions.iter().enumerate() {
157+
if i > 0 {
158+
out.write_str(", ")?;
159+
}
160+
self.write_type_resolution(arg, out)?
161+
}
162+
out.write_str(") -> ")?;
163+
self.write_type_conclusion(&rule.conclusion, out)?;
164+
Ok(())
165+
}
166+
136167
fn type_to_string(&self, handle: Handle<crate::Type>) -> String {
137168
let mut buf = String::new();
138169
self.write_type(handle, &mut buf).unwrap();
@@ -150,6 +181,12 @@ pub trait TypeContext {
150181
self.write_type_resolution(resolution, &mut buf).unwrap();
151182
buf
152183
}
184+
185+
fn type_rule_to_string(&self, name: &str, rule: &crate::proc::Rule) -> String {
186+
let mut buf = String::new();
187+
self.write_type_rule(name, rule, &mut buf).unwrap();
188+
buf
189+
}
153190
}
154191

155192
fn try_write_type_inner<C, W>(ctx: &C, inner: &TypeInner, out: &mut W) -> Result<(), WriteTypeError>
@@ -356,3 +393,70 @@ impl From<core::fmt::Error> for WriteTypeError {
356393
Self::Format(err)
357394
}
358395
}
396+
397+
/// Format types as WGSL based on a [`GlobalCtx`].
398+
///
399+
/// This is probably good enough for diagnostic output, but it has some
400+
/// limitations:
401+
///
402+
/// - It does not apply [`Namer`] renamings, to avoid collisions.
403+
///
404+
/// - It generates invalid WGSL for anonymous struct types.
405+
///
406+
/// - It doesn't write the lengths of override-expression-sized arrays
407+
/// correctly, unless the expression is just the override identifier.
408+
///
409+
/// [`GlobalCtx`]: crate::proc::GlobalCtx
410+
/// [`Namer`]: crate::proc::Namer
411+
impl TypeContext for crate::proc::GlobalCtx<'_> {
412+
fn lookup_type(&self, handle: Handle<crate::Type>) -> &crate::Type {
413+
&self.types[handle]
414+
}
415+
416+
fn type_name(&self, handle: Handle<crate::Type>) -> &str {
417+
self.types[handle]
418+
.name
419+
.as_deref()
420+
.unwrap_or("{anonymous type}")
421+
}
422+
423+
fn write_override<W: Write>(
424+
&self,
425+
handle: Handle<crate::Override>,
426+
out: &mut W,
427+
) -> core::fmt::Result {
428+
match self.overrides[handle].name {
429+
Some(ref name) => out.write_str(name),
430+
None => write!(out, "{{anonymous override {handle:?}}}"),
431+
}
432+
}
433+
}
434+
435+
/// Format types as WGSL based on a `UniqueArena<Type>`.
436+
///
437+
/// This is probably only good enough for logging:
438+
///
439+
/// - It does not apply any kind of [`Namer`] renamings.
440+
///
441+
/// - It generates invalid WGSL for anonymous struct types.
442+
///
443+
/// - It doesn't write override-sized arrays properly.
444+
///
445+
/// [`Namer`]: crate::proc::Namer
446+
impl TypeContext for crate::UniqueArena<crate::Type> {
447+
fn lookup_type(&self, handle: Handle<crate::Type>) -> &crate::Type {
448+
&self[handle]
449+
}
450+
451+
fn type_name(&self, handle: Handle<crate::Type>) -> &str {
452+
self[handle].name.as_deref().unwrap_or("{anonymous type}")
453+
}
454+
455+
fn write_override<W: Write>(
456+
&self,
457+
handle: Handle<crate::Override>,
458+
out: &mut W,
459+
) -> core::fmt::Result {
460+
write!(out, "{{override {handle:?}}}")
461+
}
462+
}

0 commit comments

Comments
 (0)