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}