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