naga/front/mod.rs
1/*!
2Frontend parsers that consume binary and text shaders and load them into [`Module`](super::Module)s.
3*/
4
5mod interpolator;
6mod type_gen;
7
8#[cfg(feature = "spv-in")]
9pub mod atomic_upgrade;
10#[cfg(feature = "glsl-in")]
11pub mod glsl;
12#[cfg(feature = "spv-in")]
13pub mod spv;
14#[cfg(feature = "wgsl-in")]
15pub mod wgsl;
16
17use crate::{
18 arena::{Arena, Handle, HandleVec, UniqueArena},
19 proc::{ResolveContext, ResolveError, TypeResolution},
20 FastHashMap,
21};
22use std::ops;
23
24/// A table of types for an `Arena<Expression>`.
25///
26/// A front end can use a `Typifier` to get types for an arena's expressions
27/// while it is still contributing expressions to it. At any point, you can call
28/// [`typifier.grow(expr, arena, ctx)`], where `expr` is a `Handle<Expression>`
29/// referring to something in `arena`, and the `Typifier` will resolve the types
30/// of all the expressions up to and including `expr`. Then you can write
31/// `typifier[handle]` to get the type of any handle at or before `expr`.
32///
33/// Note that `Typifier` does *not* build an `Arena<Type>` as a part of its
34/// usual operation. Ideally, a module's type arena should only contain types
35/// actually needed by `Handle<Type>`s elsewhere in the module — functions,
36/// variables, [`Compose`] expressions, other types, and so on — so we don't
37/// want every little thing that occurs as the type of some intermediate
38/// expression to show up there.
39///
40/// Instead, `Typifier` accumulates a [`TypeResolution`] for each expression,
41/// which refers to the `Arena<Type>` in the [`ResolveContext`] passed to `grow`
42/// as needed. [`TypeResolution`] is a lightweight representation for
43/// intermediate types like this; see its documentation for details.
44///
45/// If you do need to register a `Typifier`'s conclusion in an `Arena<Type>`
46/// (say, for a [`LocalVariable`] whose type you've inferred), you can use
47/// [`register_type`] to do so.
48///
49/// [`typifier.grow(expr, arena)`]: Typifier::grow
50/// [`register_type`]: Typifier::register_type
51/// [`Compose`]: crate::Expression::Compose
52/// [`LocalVariable`]: crate::LocalVariable
53#[derive(Debug, Default)]
54pub struct Typifier {
55 resolutions: HandleVec<crate::Expression, TypeResolution>,
56}
57
58impl Typifier {
59 pub const fn new() -> Self {
60 Typifier {
61 resolutions: HandleVec::new(),
62 }
63 }
64
65 pub fn reset(&mut self) {
66 self.resolutions.clear()
67 }
68
69 pub fn get<'a>(
70 &'a self,
71 expr_handle: Handle<crate::Expression>,
72 types: &'a UniqueArena<crate::Type>,
73 ) -> &'a crate::TypeInner {
74 self.resolutions[expr_handle].inner_with(types)
75 }
76
77 /// Add an expression's type to an `Arena<Type>`.
78 ///
79 /// Add the type of `expr_handle` to `types`, and return a `Handle<Type>`
80 /// referring to it.
81 ///
82 /// # Note
83 ///
84 /// If you just need a [`TypeInner`] for `expr_handle`'s type, consider
85 /// using `typifier[expression].inner_with(types)` instead. Calling
86 /// [`TypeResolution::inner_with`] often lets us avoid adding anything to
87 /// the arena, which can significantly reduce the number of types that end
88 /// up in the final module.
89 ///
90 /// [`TypeInner`]: crate::TypeInner
91 pub fn register_type(
92 &self,
93 expr_handle: Handle<crate::Expression>,
94 types: &mut UniqueArena<crate::Type>,
95 ) -> Handle<crate::Type> {
96 match self[expr_handle].clone() {
97 TypeResolution::Handle(handle) => handle,
98 TypeResolution::Value(inner) => {
99 types.insert(crate::Type { name: None, inner }, crate::Span::UNDEFINED)
100 }
101 }
102 }
103
104 /// Grow this typifier until it contains a type for `expr_handle`.
105 pub fn grow(
106 &mut self,
107 expr_handle: Handle<crate::Expression>,
108 expressions: &Arena<crate::Expression>,
109 ctx: &ResolveContext,
110 ) -> Result<(), ResolveError> {
111 if self.resolutions.len() <= expr_handle.index() {
112 for (eh, expr) in expressions.iter().skip(self.resolutions.len()) {
113 //Note: the closure can't `Err` by construction
114 let resolution = ctx.resolve(expr, |h| Ok(&self.resolutions[h]))?;
115 log::debug!("Resolving {:?} = {:?} : {:?}", eh, expr, resolution);
116 self.resolutions.insert(eh, resolution);
117 }
118 }
119 Ok(())
120 }
121
122 /// Recompute the type resolution for `expr_handle`.
123 ///
124 /// If the type of `expr_handle` hasn't yet been calculated, call
125 /// [`grow`](Self::grow) to ensure it is covered.
126 ///
127 /// In either case, when this returns, `self[expr_handle]` should be an
128 /// updated type resolution for `expr_handle`.
129 pub fn invalidate(
130 &mut self,
131 expr_handle: Handle<crate::Expression>,
132 expressions: &Arena<crate::Expression>,
133 ctx: &ResolveContext,
134 ) -> Result<(), ResolveError> {
135 if self.resolutions.len() <= expr_handle.index() {
136 self.grow(expr_handle, expressions, ctx)
137 } else {
138 let expr = &expressions[expr_handle];
139 //Note: the closure can't `Err` by construction
140 let resolution = ctx.resolve(expr, |h| Ok(&self.resolutions[h]))?;
141 self.resolutions[expr_handle] = resolution;
142 Ok(())
143 }
144 }
145}
146
147impl ops::Index<Handle<crate::Expression>> for Typifier {
148 type Output = TypeResolution;
149 fn index(&self, handle: Handle<crate::Expression>) -> &Self::Output {
150 &self.resolutions[handle]
151 }
152}
153
154/// Type representing a lexical scope, associating a name to a single variable
155///
156/// The scope is generic over the variable representation and name representation
157/// in order to allow larger flexibility on the frontends on how they might
158/// represent them.
159type Scope<Name, Var> = FastHashMap<Name, Var>;
160
161/// Structure responsible for managing variable lookups and keeping track of
162/// lexical scopes
163///
164/// The symbol table is generic over the variable representation and its name
165/// to allow larger flexibility on the frontends on how they might represent them.
166///
167/// ```
168/// use naga::front::SymbolTable;
169///
170/// // Create a new symbol table with `u32`s representing the variable
171/// let mut symbol_table: SymbolTable<&str, u32> = SymbolTable::default();
172///
173/// // Add two variables named `var1` and `var2` with 0 and 2 respectively
174/// symbol_table.add("var1", 0);
175/// symbol_table.add("var2", 2);
176///
177/// // Check that `var1` exists and is `0`
178/// assert_eq!(symbol_table.lookup("var1"), Some(&0));
179///
180/// // Push a new scope and add a variable to it named `var1` shadowing the
181/// // variable of our previous scope
182/// symbol_table.push_scope();
183/// symbol_table.add("var1", 1);
184///
185/// // Check that `var1` now points to the new value of `1` and `var2` still
186/// // exists with its value of `2`
187/// assert_eq!(symbol_table.lookup("var1"), Some(&1));
188/// assert_eq!(symbol_table.lookup("var2"), Some(&2));
189///
190/// // Pop the scope
191/// symbol_table.pop_scope();
192///
193/// // Check that `var1` now refers to our initial variable with value `0`
194/// assert_eq!(symbol_table.lookup("var1"), Some(&0));
195/// ```
196///
197/// Scopes are ordered as a LIFO stack so a variable defined in a later scope
198/// with the same name as another variable defined in a earlier scope will take
199/// precedence in the lookup. Scopes can be added with [`push_scope`] and
200/// removed with [`pop_scope`].
201///
202/// A root scope is added when the symbol table is created and must always be
203/// present. Trying to pop it will result in a panic.
204///
205/// Variables can be added with [`add`] and looked up with [`lookup`]. Adding a
206/// variable will do so in the currently active scope and as mentioned
207/// previously a lookup will search from the current scope to the root scope.
208///
209/// [`push_scope`]: Self::push_scope
210/// [`pop_scope`]: Self::push_scope
211/// [`add`]: Self::add
212/// [`lookup`]: Self::lookup
213pub struct SymbolTable<Name, Var> {
214 /// Stack of lexical scopes. Not all scopes are active; see [`cursor`].
215 ///
216 /// [`cursor`]: Self::cursor
217 scopes: Vec<Scope<Name, Var>>,
218 /// Limit of the [`scopes`] stack (exclusive). By using a separate value for
219 /// the stack length instead of `Vec`'s own internal length, the scopes can
220 /// be reused to cache memory allocations.
221 ///
222 /// [`scopes`]: Self::scopes
223 cursor: usize,
224}
225
226impl<Name, Var> SymbolTable<Name, Var> {
227 /// Adds a new lexical scope.
228 ///
229 /// All variables declared after this point will be added to this scope
230 /// until another scope is pushed or [`pop_scope`] is called, causing this
231 /// scope to be removed along with all variables added to it.
232 ///
233 /// [`pop_scope`]: Self::pop_scope
234 pub fn push_scope(&mut self) {
235 // If the cursor is equal to the scope's stack length then we need to
236 // push another empty scope. Otherwise we can reuse the already existing
237 // scope.
238 if self.scopes.len() == self.cursor {
239 self.scopes.push(FastHashMap::default())
240 } else {
241 self.scopes[self.cursor].clear();
242 }
243
244 self.cursor += 1;
245 }
246
247 /// Removes the current lexical scope and all its variables
248 ///
249 /// # PANICS
250 /// - If the current lexical scope is the root scope
251 pub fn pop_scope(&mut self) {
252 // Despite the method title, the variables are only deleted when the
253 // scope is reused. This is because while a clear is inevitable if the
254 // scope needs to be reused, there are cases where the scope might be
255 // popped and not reused, i.e. if another scope with the same nesting
256 // level is never pushed again.
257 assert!(self.cursor != 1, "Tried to pop the root scope");
258
259 self.cursor -= 1;
260 }
261}
262
263impl<Name, Var> SymbolTable<Name, Var>
264where
265 Name: std::hash::Hash + Eq,
266{
267 /// Perform a lookup for a variable named `name`.
268 ///
269 /// As stated in the struct level documentation the lookup will proceed from
270 /// the current scope to the root scope, returning `Some` when a variable is
271 /// found or `None` if there doesn't exist a variable with `name` in any
272 /// scope.
273 pub fn lookup<Q>(&self, name: &Q) -> Option<&Var>
274 where
275 Name: std::borrow::Borrow<Q>,
276 Q: std::hash::Hash + Eq + ?Sized,
277 {
278 // Iterate backwards through the scopes and try to find the variable
279 for scope in self.scopes[..self.cursor].iter().rev() {
280 if let Some(var) = scope.get(name) {
281 return Some(var);
282 }
283 }
284
285 None
286 }
287
288 /// Adds a new variable to the current scope.
289 ///
290 /// Returns the previous variable with the same name in this scope if it
291 /// exists, so that the frontend might handle it in case variable shadowing
292 /// is disallowed.
293 pub fn add(&mut self, name: Name, var: Var) -> Option<Var> {
294 self.scopes[self.cursor - 1].insert(name, var)
295 }
296
297 /// Adds a new variable to the root scope.
298 ///
299 /// This is used in GLSL for builtins which aren't known in advance and only
300 /// when used for the first time, so there must be a way to add those
301 /// declarations to the root unconditionally from the current scope.
302 ///
303 /// Returns the previous variable with the same name in the root scope if it
304 /// exists, so that the frontend might handle it in case variable shadowing
305 /// is disallowed.
306 pub fn add_root(&mut self, name: Name, var: Var) -> Option<Var> {
307 self.scopes[0].insert(name, var)
308 }
309}
310
311impl<Name, Var> Default for SymbolTable<Name, Var> {
312 /// Constructs a new symbol table with a root scope
313 fn default() -> Self {
314 Self {
315 scopes: vec![FastHashMap::default()],
316 cursor: 1,
317 }
318 }
319}
320
321use std::fmt;
322
323impl<Name: fmt::Debug, Var: fmt::Debug> fmt::Debug for SymbolTable<Name, Var> {
324 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
325 f.write_str("SymbolTable ")?;
326 f.debug_list()
327 .entries(self.scopes[..self.cursor].iter())
328 .finish()
329 }
330}