naga/proc/
namer.rs

1use crate::{arena::Handle, FastHashMap, FastHashSet};
2use std::borrow::Cow;
3use std::hash::{Hash, Hasher};
4
5pub type EntryPointIndex = u16;
6const SEPARATOR: char = '_';
7
8#[derive(Debug, Eq, Hash, PartialEq)]
9pub enum NameKey {
10    Constant(Handle<crate::Constant>),
11    GlobalVariable(Handle<crate::GlobalVariable>),
12    Type(Handle<crate::Type>),
13    StructMember(Handle<crate::Type>, u32),
14    Function(Handle<crate::Function>),
15    FunctionArgument(Handle<crate::Function>, u32),
16    FunctionLocal(Handle<crate::Function>, Handle<crate::LocalVariable>),
17    EntryPoint(EntryPointIndex),
18    EntryPointLocal(EntryPointIndex, Handle<crate::LocalVariable>),
19    EntryPointArgument(EntryPointIndex, u32),
20}
21
22/// This processor assigns names to all the things in a module
23/// that may need identifiers in a textual backend.
24#[derive(Default)]
25pub struct Namer {
26    /// The last numeric suffix used for each base name. Zero means "no suffix".
27    unique: FastHashMap<String, u32>,
28    keywords: FastHashSet<&'static str>,
29    keywords_case_insensitive: FastHashSet<AsciiUniCase<&'static str>>,
30    reserved_prefixes: Vec<&'static str>,
31}
32
33impl Namer {
34    /// Return a form of `string` suitable for use as the base of an identifier.
35    ///
36    /// - Drop leading digits.
37    /// - Retain only alphanumeric and `_` characters.
38    /// - Avoid prefixes in [`Namer::reserved_prefixes`].
39    ///
40    /// The return value is a valid identifier prefix in all of Naga's output languages,
41    /// and it never ends with a `SEPARATOR` character.
42    /// It is used as a key into the unique table.
43    fn sanitize<'s>(&self, string: &'s str) -> Cow<'s, str> {
44        let string = string
45            .trim_start_matches(|c: char| c.is_numeric())
46            .trim_end_matches(SEPARATOR);
47
48        let base = if !string.is_empty()
49            && string
50                .chars()
51                .all(|c: char| c.is_ascii_alphanumeric() || c == '_')
52        {
53            Cow::Borrowed(string)
54        } else {
55            let mut filtered = string
56                .chars()
57                .filter(|&c| c.is_ascii_alphanumeric() || c == '_')
58                .collect::<String>();
59            let stripped_len = filtered.trim_end_matches(SEPARATOR).len();
60            filtered.truncate(stripped_len);
61            if filtered.is_empty() {
62                filtered.push_str("unnamed");
63            }
64            Cow::Owned(filtered)
65        };
66
67        for prefix in &self.reserved_prefixes {
68            if base.starts_with(prefix) {
69                return format!("gen_{base}").into();
70            }
71        }
72
73        base
74    }
75
76    /// Return a new identifier based on `label_raw`.
77    ///
78    /// The result:
79    /// - is a valid identifier even if `label_raw` is not
80    /// - conflicts with no keywords listed in `Namer::keywords`, and
81    /// - is different from any identifier previously constructed by this
82    ///   `Namer`.
83    ///
84    /// Guarantee uniqueness by applying a numeric suffix when necessary. If `label_raw`
85    /// itself ends with digits, separate them from the suffix with an underscore.
86    pub fn call(&mut self, label_raw: &str) -> String {
87        use std::fmt::Write as _; // for write!-ing to Strings
88
89        let base = self.sanitize(label_raw);
90        debug_assert!(!base.is_empty() && !base.ends_with(SEPARATOR));
91
92        // This would seem to be a natural place to use `HashMap::entry`. However, `entry`
93        // requires an owned key, and we'd like to avoid heap-allocating strings we're
94        // just going to throw away. The approach below double-hashes only when we create
95        // a new entry, in which case the heap allocation of the owned key was more
96        // expensive anyway.
97        match self.unique.get_mut(base.as_ref()) {
98            Some(count) => {
99                *count += 1;
100                // Add the suffix. This may fit in base's existing allocation.
101                let mut suffixed = base.into_owned();
102                write!(suffixed, "{}{}", SEPARATOR, *count).unwrap();
103                suffixed
104            }
105            None => {
106                let mut suffixed = base.to_string();
107                if base.ends_with(char::is_numeric)
108                    || self.keywords.contains(base.as_ref())
109                    || self
110                        .keywords_case_insensitive
111                        .contains(&AsciiUniCase(base.as_ref()))
112                {
113                    suffixed.push(SEPARATOR);
114                }
115                debug_assert!(!self.keywords.contains::<str>(&suffixed));
116                // `self.unique` wants to own its keys. This allocates only if we haven't
117                // already done so earlier.
118                self.unique.insert(base.into_owned(), 0);
119                suffixed
120            }
121        }
122    }
123
124    pub fn call_or(&mut self, label: &Option<String>, fallback: &str) -> String {
125        self.call(match *label {
126            Some(ref name) => name,
127            None => fallback,
128        })
129    }
130
131    /// Enter a local namespace for things like structs.
132    ///
133    /// Struct member names only need to be unique amongst themselves, not
134    /// globally. This function temporarily establishes a fresh, empty naming
135    /// context for the duration of the call to `body`.
136    fn namespace(&mut self, capacity: usize, body: impl FnOnce(&mut Self)) {
137        let fresh = FastHashMap::with_capacity_and_hasher(capacity, Default::default());
138        let outer = std::mem::replace(&mut self.unique, fresh);
139        body(self);
140        self.unique = outer;
141    }
142
143    pub fn reset(
144        &mut self,
145        module: &crate::Module,
146        reserved_keywords: &[&'static str],
147        extra_reserved_keywords: &[&'static str],
148        reserved_keywords_case_insensitive: &[&'static str],
149        reserved_prefixes: &[&'static str],
150        output: &mut FastHashMap<NameKey, String>,
151    ) {
152        self.reserved_prefixes.clear();
153        self.reserved_prefixes.extend(reserved_prefixes.iter());
154
155        self.unique.clear();
156        self.keywords.clear();
157        self.keywords.extend(reserved_keywords.iter());
158        self.keywords.extend(extra_reserved_keywords.iter());
159
160        debug_assert!(reserved_keywords_case_insensitive
161            .iter()
162            .all(|s| s.is_ascii()));
163        self.keywords_case_insensitive.clear();
164        self.keywords_case_insensitive.extend(
165            reserved_keywords_case_insensitive
166                .iter()
167                .map(|string| (AsciiUniCase(*string))),
168        );
169
170        let mut temp = String::new();
171
172        for (ty_handle, ty) in module.types.iter() {
173            let ty_name = self.call_or(&ty.name, "type");
174            output.insert(NameKey::Type(ty_handle), ty_name);
175
176            if let crate::TypeInner::Struct { ref members, .. } = ty.inner {
177                // struct members have their own namespace, because access is always prefixed
178                self.namespace(members.len(), |namer| {
179                    for (index, member) in members.iter().enumerate() {
180                        let name = namer.call_or(&member.name, "member");
181                        output.insert(NameKey::StructMember(ty_handle, index as u32), name);
182                    }
183                })
184            }
185        }
186
187        for (ep_index, ep) in module.entry_points.iter().enumerate() {
188            let ep_name = self.call(&ep.name);
189            output.insert(NameKey::EntryPoint(ep_index as _), ep_name);
190            for (index, arg) in ep.function.arguments.iter().enumerate() {
191                let name = self.call_or(&arg.name, "param");
192                output.insert(
193                    NameKey::EntryPointArgument(ep_index as _, index as u32),
194                    name,
195                );
196            }
197            for (handle, var) in ep.function.local_variables.iter() {
198                let name = self.call_or(&var.name, "local");
199                output.insert(NameKey::EntryPointLocal(ep_index as _, handle), name);
200            }
201        }
202
203        for (fun_handle, fun) in module.functions.iter() {
204            let fun_name = self.call_or(&fun.name, "function");
205            output.insert(NameKey::Function(fun_handle), fun_name);
206            for (index, arg) in fun.arguments.iter().enumerate() {
207                let name = self.call_or(&arg.name, "param");
208                output.insert(NameKey::FunctionArgument(fun_handle, index as u32), name);
209            }
210            for (handle, var) in fun.local_variables.iter() {
211                let name = self.call_or(&var.name, "local");
212                output.insert(NameKey::FunctionLocal(fun_handle, handle), name);
213            }
214        }
215
216        for (handle, var) in module.global_variables.iter() {
217            let name = self.call_or(&var.name, "global");
218            output.insert(NameKey::GlobalVariable(handle), name);
219        }
220
221        for (handle, constant) in module.constants.iter() {
222            let label = match constant.name {
223                Some(ref name) => name,
224                None => {
225                    use std::fmt::Write;
226                    // Try to be more descriptive about the constant values
227                    temp.clear();
228                    write!(temp, "const_{}", output[&NameKey::Type(constant.ty)]).unwrap();
229                    &temp
230                }
231            };
232            let name = self.call(label);
233            output.insert(NameKey::Constant(handle), name);
234        }
235    }
236}
237
238/// A string wrapper type with an ascii case insensitive Eq and Hash impl
239struct AsciiUniCase<S: AsRef<str> + ?Sized>(S);
240
241impl<S: AsRef<str>> PartialEq<Self> for AsciiUniCase<S> {
242    #[inline]
243    fn eq(&self, other: &Self) -> bool {
244        self.0.as_ref().eq_ignore_ascii_case(other.0.as_ref())
245    }
246}
247
248impl<S: AsRef<str>> Eq for AsciiUniCase<S> {}
249
250impl<S: AsRef<str>> Hash for AsciiUniCase<S> {
251    #[inline]
252    fn hash<H: Hasher>(&self, hasher: &mut H) {
253        for byte in self
254            .0
255            .as_ref()
256            .as_bytes()
257            .iter()
258            .map(|b| b.to_ascii_lowercase())
259        {
260            hasher.write_u8(byte);
261        }
262    }
263}
264
265#[test]
266fn test() {
267    let mut namer = Namer::default();
268    assert_eq!(namer.call("x"), "x");
269    assert_eq!(namer.call("x"), "x_1");
270    assert_eq!(namer.call("x1"), "x1_");
271}