naga/proc/
namer.rs

1use alloc::{
2    borrow::Cow,
3    format,
4    string::{String, ToString},
5    vec::Vec,
6};
7
8use crate::{
9    arena::Handle,
10    proc::{keyword_set::CaseInsensitiveKeywordSet, KeywordSet},
11    FastHashMap,
12};
13
14pub type EntryPointIndex = u16;
15const SEPARATOR: char = '_';
16
17/// A component of a lowered external texture.
18///
19/// Whereas the WGSL backend implements [`ImageClass::External`]
20/// images directly, most other Naga backends lower them to a
21/// collection of ordinary textures that represent individual planes
22/// (as received from a video decoder, perhaps), together with a
23/// struct of parameters saying how they should be cropped, sampled,
24/// and color-converted.
25///
26/// This lowering means that individual globals and function
27/// parameters in Naga IR must be split out by the backends into
28/// collections of globals and parameters of simpler types.
29///
30/// A value of this enum serves as a name key for one specific
31/// component in the lowered representation of an external texture.
32/// That is, these keys are for variables/parameters that do not exist
33/// in the Naga IR, only in its lowered form.
34///
35/// [`ImageClass::External`]: crate::ir::ImageClass::External
36#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
37pub enum ExternalTextureNameKey {
38    Plane(usize),
39    Params,
40}
41
42impl ExternalTextureNameKey {
43    const ALL: &[(&str, ExternalTextureNameKey)] = &[
44        ("_plane0", ExternalTextureNameKey::Plane(0)),
45        ("_plane1", ExternalTextureNameKey::Plane(1)),
46        ("_plane2", ExternalTextureNameKey::Plane(2)),
47        ("_params", ExternalTextureNameKey::Params),
48    ];
49}
50
51#[derive(Debug, Eq, Hash, PartialEq)]
52pub enum NameKey {
53    Constant(Handle<crate::Constant>),
54    Override(Handle<crate::Override>),
55    GlobalVariable(Handle<crate::GlobalVariable>),
56    Type(Handle<crate::Type>),
57    StructMember(Handle<crate::Type>, u32),
58    Function(Handle<crate::Function>),
59    FunctionArgument(Handle<crate::Function>, u32),
60    FunctionLocal(Handle<crate::Function>, Handle<crate::LocalVariable>),
61
62    /// A local variable used by ReadZeroSkipWrite bounds-check policy
63    /// when it needs to produce a pointer-typed result for an OOB access.
64    /// These are unique per accessed type, so the second element is a
65    /// type handle. See docs for [`crate::back::msl`].
66    FunctionOobLocal(Handle<crate::Function>, Handle<crate::Type>),
67
68    EntryPoint(EntryPointIndex),
69    EntryPointLocal(EntryPointIndex, Handle<crate::LocalVariable>),
70    EntryPointArgument(EntryPointIndex, u32),
71
72    /// Entry point version of `FunctionOobLocal`.
73    EntryPointOobLocal(EntryPointIndex, Handle<crate::Type>),
74
75    /// A global variable holding a component of a lowered external texture.
76    ///
77    /// See [`ExternalTextureNameKey`] for details.
78    ExternalTextureGlobalVariable(Handle<crate::GlobalVariable>, ExternalTextureNameKey),
79
80    /// A function argument holding a component of a lowered external
81    /// texture.
82    ///
83    /// See [`ExternalTextureNameKey`] for details.
84    ExternalTextureFunctionArgument(Handle<crate::Function>, u32, ExternalTextureNameKey),
85}
86
87/// This processor assigns names to all the things in a module
88/// that may need identifiers in a textual backend.
89#[derive(Default)]
90pub struct Namer {
91    /// The last numeric suffix used for each base name. Zero means "no suffix".
92    unique: FastHashMap<String, u32>,
93    keywords: &'static KeywordSet,
94    builtin_identifiers: &'static KeywordSet,
95    keywords_case_insensitive: &'static CaseInsensitiveKeywordSet,
96    reserved_prefixes: Vec<&'static str>,
97}
98
99impl Namer {
100    /// Return a form of `string` suitable for use as the base of an identifier.
101    ///
102    /// - Drop leading digits.
103    /// - Retain only alphanumeric and `_` characters.
104    /// - Avoid prefixes in [`Namer::reserved_prefixes`].
105    /// - Replace consecutive `_` characters with a single `_` character.
106    ///
107    /// The return value is a valid identifier prefix in all of Naga's output languages,
108    /// and it never ends with a `SEPARATOR` character.
109    /// It is used as a key into the unique table.
110    fn sanitize<'s>(&self, string: &'s str) -> Cow<'s, str> {
111        let string = string
112            .trim_start_matches(|c: char| c.is_numeric())
113            .trim_end_matches(SEPARATOR);
114
115        let base = if !string.is_empty()
116            && !string.contains("__")
117            && string
118                .chars()
119                .all(|c: char| c.is_ascii_alphanumeric() || c == '_')
120        {
121            Cow::Borrowed(string)
122        } else {
123            let mut filtered = string.chars().fold(String::new(), |mut s, c| {
124                let c = match c {
125                    // Make several common characters in C++-ish types become snake case
126                    // separators.
127                    ':' | '<' | '>' | ',' => '_',
128                    c => c,
129                };
130                let had_underscore_at_end = s.ends_with('_');
131                if had_underscore_at_end && c == '_' {
132                    return s;
133                }
134                if c.is_ascii_alphanumeric() || c == '_' {
135                    s.push(c);
136                } else {
137                    use core::fmt::Write as _;
138                    if !s.is_empty() && !had_underscore_at_end {
139                        s.push('_');
140                    }
141                    write!(s, "u{:04x}_", c as u32).unwrap();
142                }
143                s
144            });
145            let stripped_len = filtered.trim_end_matches(SEPARATOR).len();
146            filtered.truncate(stripped_len);
147            if filtered.is_empty() {
148                filtered.push_str("unnamed");
149            } else if filtered.starts_with(|c: char| c.is_ascii_digit()) {
150                unreachable!(
151                    "internal error: invalid identifier starting with ASCII digit {:?}",
152                    filtered.chars().nth(0)
153                )
154            }
155            Cow::Owned(filtered)
156        };
157
158        for prefix in &self.reserved_prefixes {
159            if base.starts_with(prefix) {
160                return format!("gen_{base}").into();
161            }
162        }
163
164        base
165    }
166
167    /// Return a new identifier based on `label_raw`.
168    ///
169    /// The result:
170    /// - is a valid identifier even if `label_raw` is not
171    /// - conflicts with no keywords listed in `Namer::keywords`, and
172    /// - is different from any identifier previously constructed by this
173    ///   `Namer`.
174    ///
175    /// Guarantee uniqueness by applying a numeric suffix when necessary. If `label_raw`
176    /// itself ends with digits, separate them from the suffix with an underscore.
177    pub fn call(&mut self, label_raw: &str) -> String {
178        use core::fmt::Write as _; // for write!-ing to Strings
179
180        let base = self.sanitize(label_raw);
181        debug_assert!(!base.is_empty() && !base.ends_with(SEPARATOR));
182
183        // This would seem to be a natural place to use `HashMap::entry`. However, `entry`
184        // requires an owned key, and we'd like to avoid heap-allocating strings we're
185        // just going to throw away. The approach below double-hashes only when we create
186        // a new entry, in which case the heap allocation of the owned key was more
187        // expensive anyway.
188        match self.unique.get_mut(base.as_ref()) {
189            Some(count) => {
190                *count += 1;
191                // Add the suffix. This may fit in base's existing allocation.
192                let mut suffixed = base.into_owned();
193                write!(suffixed, "{}{}", SEPARATOR, *count).unwrap();
194                suffixed
195            }
196            None => {
197                let mut suffixed = base.to_string();
198                if base.ends_with(char::is_numeric)
199                    || self.keywords.contains(base.as_ref())
200                    || self.keywords_case_insensitive.contains(base.as_ref())
201                    || self.builtin_identifiers.contains(base.as_ref())
202                {
203                    suffixed.push(SEPARATOR);
204                }
205                debug_assert!(!self.keywords.contains(&suffixed));
206                // `self.unique` wants to own its keys. This allocates only if we haven't
207                // already done so earlier.
208                self.unique.insert(base.into_owned(), 0);
209                suffixed
210            }
211        }
212    }
213
214    pub fn call_or(&mut self, label: &Option<String>, fallback: &str) -> String {
215        self.call(match *label {
216            Some(ref name) => name,
217            None => fallback,
218        })
219    }
220
221    /// Enter a local namespace for things like structs.
222    ///
223    /// Struct member names only need to be unique amongst themselves, not
224    /// globally. This function temporarily establishes a fresh, empty naming
225    /// context for the duration of the call to `body`.
226    fn namespace(&mut self, capacity: usize, body: impl FnOnce(&mut Self)) {
227        let empty_unique = FastHashMap::with_capacity_and_hasher(capacity, Default::default());
228        let saved_unique = core::mem::replace(&mut self.unique, empty_unique);
229        let saved_builtin_identifiers = core::mem::take(&mut self.builtin_identifiers);
230        body(self);
231        self.unique = saved_unique;
232        self.builtin_identifiers = saved_builtin_identifiers;
233    }
234
235    pub fn reset(
236        &mut self,
237        module: &crate::Module,
238        reserved_keywords: &'static KeywordSet,
239        builtin_identifiers: &'static KeywordSet,
240        reserved_keywords_case_insensitive: &'static CaseInsensitiveKeywordSet,
241        reserved_prefixes: &[&'static str],
242        output: &mut FastHashMap<NameKey, String>,
243    ) {
244        self.reserved_prefixes.clear();
245        self.reserved_prefixes.extend(reserved_prefixes.iter());
246
247        self.unique.clear();
248        self.keywords = reserved_keywords;
249        self.builtin_identifiers = builtin_identifiers;
250        self.keywords_case_insensitive = reserved_keywords_case_insensitive;
251
252        // Choose fallback names for anonymous entry point return types.
253        let mut entrypoint_type_fallbacks = FastHashMap::default();
254        for ep in &module.entry_points {
255            if let Some(ref result) = ep.function.result {
256                if let crate::Type {
257                    name: None,
258                    inner: crate::TypeInner::Struct { .. },
259                } = module.types[result.ty]
260                {
261                    let label = match ep.stage {
262                        crate::ShaderStage::Vertex => "VertexOutput",
263                        crate::ShaderStage::Fragment => "FragmentOutput",
264                        crate::ShaderStage::Compute => "ComputeOutput",
265                        crate::ShaderStage::Task
266                        | crate::ShaderStage::Mesh
267                        | crate::ShaderStage::RayGeneration
268                        | crate::ShaderStage::ClosestHit
269                        | crate::ShaderStage::AnyHit
270                        | crate::ShaderStage::Miss => unreachable!(),
271                    };
272                    entrypoint_type_fallbacks.insert(result.ty, label);
273                }
274            }
275        }
276
277        let mut temp = String::new();
278
279        for (ty_handle, ty) in module.types.iter() {
280            // If the type is anonymous, check `entrypoint_types` for
281            // something better than just `"type"`.
282            let raw_label = match ty.name {
283                Some(ref given_name) => given_name.as_str(),
284                None => entrypoint_type_fallbacks
285                    .get(&ty_handle)
286                    .cloned()
287                    .unwrap_or("type"),
288            };
289            let ty_name = self.call(raw_label);
290            output.insert(NameKey::Type(ty_handle), ty_name);
291
292            if let crate::TypeInner::Struct { ref members, .. } = ty.inner {
293                // struct members have their own namespace, because access is always prefixed
294                self.namespace(members.len(), |namer| {
295                    for (index, member) in members.iter().enumerate() {
296                        let name = namer.call_or(&member.name, "member");
297                        output.insert(NameKey::StructMember(ty_handle, index as u32), name);
298                    }
299                })
300            }
301        }
302
303        for (ep_index, ep) in module.entry_points.iter().enumerate() {
304            let ep_name = self.call(&ep.name);
305            output.insert(NameKey::EntryPoint(ep_index as _), ep_name);
306            for (index, arg) in ep.function.arguments.iter().enumerate() {
307                let name = self.call_or(&arg.name, "param");
308                output.insert(
309                    NameKey::EntryPointArgument(ep_index as _, index as u32),
310                    name,
311                );
312            }
313            for (handle, var) in ep.function.local_variables.iter() {
314                let name = self.call_or(&var.name, "local");
315                output.insert(NameKey::EntryPointLocal(ep_index as _, handle), name);
316            }
317        }
318
319        for (fun_handle, fun) in module.functions.iter() {
320            let fun_name = self.call_or(&fun.name, "function");
321            output.insert(NameKey::Function(fun_handle), fun_name);
322            for (index, arg) in fun.arguments.iter().enumerate() {
323                let name = self.call_or(&arg.name, "param");
324                output.insert(NameKey::FunctionArgument(fun_handle, index as u32), name);
325
326                if matches!(
327                    module.types[arg.ty].inner,
328                    crate::TypeInner::Image {
329                        class: crate::ImageClass::External,
330                        ..
331                    }
332                ) {
333                    let base = arg.name.as_deref().unwrap_or("param");
334                    for &(suffix, ext_key) in ExternalTextureNameKey::ALL {
335                        let name = self.call(&format!("{base}_{suffix}"));
336                        output.insert(
337                            NameKey::ExternalTextureFunctionArgument(
338                                fun_handle,
339                                index as u32,
340                                ext_key,
341                            ),
342                            name,
343                        );
344                    }
345                }
346            }
347            for (handle, var) in fun.local_variables.iter() {
348                let name = self.call_or(&var.name, "local");
349                output.insert(NameKey::FunctionLocal(fun_handle, handle), name);
350            }
351        }
352
353        for (handle, var) in module.global_variables.iter() {
354            let name = self.call_or(&var.name, "global");
355            output.insert(NameKey::GlobalVariable(handle), name);
356
357            if matches!(
358                module.types[var.ty].inner,
359                crate::TypeInner::Image {
360                    class: crate::ImageClass::External,
361                    ..
362                }
363            ) {
364                let base = var.name.as_deref().unwrap_or("global");
365                for &(suffix, ext_key) in ExternalTextureNameKey::ALL {
366                    let name = self.call(&format!("{base}_{suffix}"));
367                    output.insert(
368                        NameKey::ExternalTextureGlobalVariable(handle, ext_key),
369                        name,
370                    );
371                }
372            }
373        }
374
375        for (handle, constant) in module.constants.iter() {
376            let label = match constant.name {
377                Some(ref name) => name,
378                None => {
379                    use core::fmt::Write;
380                    // Try to be more descriptive about the constant values
381                    temp.clear();
382                    write!(temp, "const_{}", output[&NameKey::Type(constant.ty)]).unwrap();
383                    &temp
384                }
385            };
386            let name = self.call(label);
387            output.insert(NameKey::Constant(handle), name);
388        }
389
390        for (handle, override_) in module.overrides.iter() {
391            let label = match override_.name {
392                Some(ref name) => name,
393                None => {
394                    use core::fmt::Write;
395                    // Try to be more descriptive about the override values
396                    temp.clear();
397                    write!(temp, "override_{}", output[&NameKey::Type(override_.ty)]).unwrap();
398                    &temp
399                }
400            };
401            let name = self.call(label);
402            output.insert(NameKey::Override(handle), name);
403        }
404    }
405}
406
407#[test]
408fn test() {
409    let mut namer = Namer::default();
410    assert_eq!(namer.call("x"), "x");
411    assert_eq!(namer.call("x"), "x_1");
412    assert_eq!(namer.call("x1"), "x1_");
413    assert_eq!(namer.call("__x"), "_x");
414    assert_eq!(namer.call("1___x"), "_x_1");
415}