naga/front/wgsl/
index.rs

1use alloc::{boxed::Box, vec, vec::Vec};
2
3use super::{Error, Result};
4use crate::front::wgsl::parse::ast;
5use crate::{FastHashMap, Handle, Span};
6
7/// A `GlobalDecl` list in which each definition occurs before all its uses.
8pub struct Index<'a> {
9    dependency_order: Vec<Handle<ast::GlobalDecl<'a>>>,
10}
11
12impl<'a> Index<'a> {
13    /// Generate an `Index` for the given translation unit.
14    ///
15    /// Perform a topological sort on `tu`'s global declarations, placing
16    /// referents before the definitions that refer to them.
17    ///
18    /// Return an error if the graph of references between declarations contains
19    /// any cycles.
20    pub fn generate(tu: &ast::TranslationUnit<'a>) -> Result<'a, Self> {
21        // Produce a map from global definitions' names to their `Handle<GlobalDecl>`s.
22        // While doing so, reject conflicting definitions.
23        let mut globals = FastHashMap::with_capacity_and_hasher(tu.decls.len(), Default::default());
24        for (handle, decl) in tu.decls.iter() {
25            if let Some(ident) = decl_ident(decl) {
26                let name = ident.name;
27                if let Some(old) = globals.insert(name, handle) {
28                    return Err(Box::new(Error::Redefinition {
29                        previous: decl_ident(&tu.decls[old])
30                            .expect("decl should have ident for redefinition")
31                            .span,
32                        current: ident.span,
33                    }));
34                }
35            }
36        }
37
38        let len = tu.decls.len();
39        let solver = DependencySolver {
40            globals: &globals,
41            module: tu,
42            visited: vec![false; len],
43            temp_visited: vec![false; len],
44            path: Vec::new(),
45            out: Vec::with_capacity(len),
46        };
47        let dependency_order = solver.solve()?;
48
49        Ok(Self { dependency_order })
50    }
51
52    /// Iterate over `GlobalDecl`s, visiting each definition before all its uses.
53    ///
54    /// Produce handles for all of the `GlobalDecl`s of the `TranslationUnit`
55    /// passed to `Index::generate`, ordered so that a given declaration is
56    /// produced before any other declaration that uses it.
57    pub fn visit_ordered(&self) -> impl Iterator<Item = Handle<ast::GlobalDecl<'a>>> + '_ {
58        self.dependency_order.iter().copied()
59    }
60}
61
62/// An edge from a reference to its referent in the current depth-first
63/// traversal.
64///
65/// This is like `ast::Dependency`, except that we've determined which
66/// `GlobalDecl` it refers to.
67struct ResolvedDependency<'a> {
68    /// The referent of some identifier used in the current declaration.
69    decl: Handle<ast::GlobalDecl<'a>>,
70
71    /// Where that use occurs within the current declaration.
72    usage: Span,
73}
74
75/// Local state for ordering a `TranslationUnit`'s module-scope declarations.
76///
77/// Values of this type are used temporarily by `Index::generate`
78/// to perform a depth-first sort on the declarations.
79/// Technically, what we want is a topological sort, but a depth-first sort
80/// has one key benefit - it's much more efficient in storing
81/// the path of each node for error generation.
82struct DependencySolver<'source, 'temp> {
83    /// A map from module-scope definitions' names to their handles.
84    globals: &'temp FastHashMap<&'source str, Handle<ast::GlobalDecl<'source>>>,
85
86    /// The translation unit whose declarations we're ordering.
87    module: &'temp ast::TranslationUnit<'source>,
88
89    /// For each handle, whether we have pushed it onto `out` yet.
90    visited: Vec<bool>,
91
92    /// For each handle, whether it is an predecessor in the current depth-first
93    /// traversal. This is used to detect cycles in the reference graph.
94    temp_visited: Vec<bool>,
95
96    /// The current path in our depth-first traversal. Used for generating
97    /// error messages for non-trivial reference cycles.
98    path: Vec<ResolvedDependency<'source>>,
99
100    /// The list of declaration handles, with declarations before uses.
101    out: Vec<Handle<ast::GlobalDecl<'source>>>,
102}
103
104impl<'a> DependencySolver<'a, '_> {
105    /// Produce the sorted list of declaration handles, and check for cycles.
106    fn solve(mut self) -> Result<'a, Vec<Handle<ast::GlobalDecl<'a>>>> {
107        for (id, _) in self.module.decls.iter() {
108            if self.visited[id.index()] {
109                continue;
110            }
111
112            self.dfs(id)?;
113        }
114
115        Ok(self.out)
116    }
117
118    /// Ensure that all declarations used by `id` have been added to the
119    /// ordering, and then append `id` itself.
120    fn dfs(&mut self, id: Handle<ast::GlobalDecl<'a>>) -> Result<'a, ()> {
121        let decl = &self.module.decls[id];
122        let id_usize = id.index();
123
124        self.temp_visited[id_usize] = true;
125        for dep in decl.dependencies.iter() {
126            if let Some(&dep_id) = self.globals.get(dep.ident) {
127                self.path.push(ResolvedDependency {
128                    decl: dep_id,
129                    usage: dep.usage,
130                });
131                let dep_id_usize = dep_id.index();
132
133                if self.temp_visited[dep_id_usize] {
134                    // Found a cycle.
135                    return if dep_id == id {
136                        // A declaration refers to itself directly.
137                        Err(Box::new(Error::RecursiveDeclaration {
138                            ident: decl_ident(decl).expect("decl should have ident").span,
139                            usage: dep.usage,
140                        }))
141                    } else {
142                        // A declaration refers to itself indirectly, through
143                        // one or more other definitions. Report the entire path
144                        // of references.
145                        let start_at = self
146                            .path
147                            .iter()
148                            .rev()
149                            .enumerate()
150                            .find_map(|(i, dep)| (dep.decl == dep_id).then_some(i))
151                            .unwrap_or(0);
152
153                        Err(Box::new(Error::CyclicDeclaration {
154                            ident: decl_ident(&self.module.decls[dep_id])
155                                .expect("decl should have ident")
156                                .span,
157                            path: self.path[start_at..]
158                                .iter()
159                                .map(|curr_dep| {
160                                    let curr_id = curr_dep.decl;
161                                    let curr_decl = &self.module.decls[curr_id];
162
163                                    (
164                                        decl_ident(curr_decl).expect("decl should have ident").span,
165                                        curr_dep.usage,
166                                    )
167                                })
168                                .collect(),
169                        }))
170                    };
171                } else if !self.visited[dep_id_usize] {
172                    self.dfs(dep_id)?;
173                }
174
175                // Remove this edge from the current path.
176                self.path.pop();
177            }
178
179            // Ignore unresolved identifiers; they may be predeclared objects.
180        }
181
182        // Remove this node from the current path.
183        self.temp_visited[id_usize] = false;
184
185        // Now everything this declaration uses has been visited, and is already
186        // present in `out`. That means we we can append this one to the
187        // ordering, and mark it as visited.
188        self.out.push(id);
189        self.visited[id_usize] = true;
190
191        Ok(())
192    }
193}
194
195const fn decl_ident<'a>(decl: &ast::GlobalDecl<'a>) -> Option<ast::Ident<'a>> {
196    match decl.kind {
197        ast::GlobalDeclKind::Fn(ref f) => Some(f.name),
198        ast::GlobalDeclKind::Var(ref v) => Some(v.name),
199        ast::GlobalDeclKind::Const(ref c) => Some(c.name),
200        ast::GlobalDeclKind::Override(ref o) => Some(o.name),
201        ast::GlobalDeclKind::Struct(ref s) => Some(s.name),
202        ast::GlobalDeclKind::Type(ref t) => Some(t.name),
203        ast::GlobalDeclKind::ConstAssert(_) => None,
204    }
205}