• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 The Tint Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "src/resolver/dependency_graph.h"
16 
17 #include <string>
18 #include <unordered_set>
19 #include <utility>
20 #include <vector>
21 
22 #include "src/ast/continue_statement.h"
23 #include "src/ast/discard_statement.h"
24 #include "src/ast/fallthrough_statement.h"
25 #include "src/ast/traverse_expressions.h"
26 #include "src/scope_stack.h"
27 #include "src/sem/intrinsic.h"
28 #include "src/utils/defer.h"
29 #include "src/utils/map.h"
30 #include "src/utils/scoped_assignment.h"
31 #include "src/utils/unique_vector.h"
32 
33 #define TINT_DUMP_DEPENDENCY_GRAPH 0
34 
35 namespace tint {
36 namespace resolver {
37 namespace {
38 
39 // Forward declaration
40 struct Global;
41 
42 /// Dependency describes how one global depends on another global
43 struct DependencyInfo {
44   /// The source of the symbol that forms the dependency
45   Source source;
46   /// A string describing how the dependency is referenced. e.g. 'calls'
47   const char* action = nullptr;
48 };
49 
50 /// DependencyEdge describes the two Globals used to define a dependency
51 /// relationship.
52 struct DependencyEdge {
53   /// The Global that depends on #to
54   const Global* from;
55   /// The Global that is depended on by #from
56   const Global* to;
57 };
58 
59 /// DependencyEdgeCmp implements the contracts of std::equal_to<DependencyEdge>
60 /// and std::hash<DependencyEdge>.
61 struct DependencyEdgeCmp {
62   /// Equality operator
operator ()tint::resolver::__anon6cc0d6e10111::DependencyEdgeCmp63   bool operator()(const DependencyEdge& lhs, const DependencyEdge& rhs) const {
64     return lhs.from == rhs.from && lhs.to == rhs.to;
65   }
66   /// Hashing operator
operator ()tint::resolver::__anon6cc0d6e10111::DependencyEdgeCmp67   inline std::size_t operator()(const DependencyEdge& d) const {
68     return utils::Hash(d.from, d.to);
69   }
70 };
71 
72 /// A map of DependencyEdge to DependencyInfo
73 using DependencyEdges = std::unordered_map<DependencyEdge,
74                                            DependencyInfo,
75                                            DependencyEdgeCmp,
76                                            DependencyEdgeCmp>;
77 
78 /// Global describes a module-scope variable, type or function.
79 struct Global {
Globaltint::resolver::__anon6cc0d6e10111::Global80   explicit Global(const ast::Node* n) : node(n) {}
81 
82   /// The declaration ast::Node
83   const ast::Node* node;
84   /// A list of dependencies that this global depends on
85   std::vector<Global*> deps;
86 };
87 
88 /// A map of global name to Global
89 using GlobalMap = std::unordered_map<Symbol, Global*>;
90 
91 /// Raises an ICE that a global ast::Node type was not handled by this system.
UnhandledNode(diag::List & diagnostics,const ast::Node * node)92 void UnhandledNode(diag::List& diagnostics, const ast::Node* node) {
93   TINT_ICE(Resolver, diagnostics)
94       << "unhandled node type: " << node->TypeInfo().name;
95 }
96 
97 /// Raises an error diagnostic with the given message and source.
AddError(diag::List & diagnostics,const std::string & msg,const Source & source)98 void AddError(diag::List& diagnostics,
99               const std::string& msg,
100               const Source& source) {
101   diagnostics.add_error(diag::System::Resolver, msg, source);
102 }
103 
104 /// Raises a note diagnostic with the given message and source.
AddNote(diag::List & diagnostics,const std::string & msg,const Source & source)105 void AddNote(diag::List& diagnostics,
106              const std::string& msg,
107              const Source& source) {
108   diagnostics.add_note(diag::System::Resolver, msg, source);
109 }
110 
111 /// DependencyScanner is used to traverse a module to build the list of
112 /// global-to-global dependencies.
113 class DependencyScanner {
114  public:
115   /// Constructor
116   /// @param syms the program symbol table
117   /// @param globals_by_name map of global symbol to Global pointer
118   /// @param diagnostics diagnostic messages, appended with any errors found
119   /// @param graph the dependency graph to populate with resolved symbols
120   /// @param edges the map of globals-to-global dependency edges, which will
121   /// be populated by calls to Scan()
DependencyScanner(const SymbolTable & syms,const GlobalMap & globals_by_name,diag::List & diagnostics,DependencyGraph & graph,DependencyEdges & edges)122   DependencyScanner(const SymbolTable& syms,
123                     const GlobalMap& globals_by_name,
124                     diag::List& diagnostics,
125                     DependencyGraph& graph,
126                     DependencyEdges& edges)
127       : symbols_(syms),
128         globals_(globals_by_name),
129         diagnostics_(diagnostics),
130         graph_(graph),
131         dependency_edges_(edges) {
132     // Register all the globals at global-scope
133     for (auto it : globals_by_name) {
134       scope_stack_.Set(it.first, it.second->node);
135     }
136   }
137 
138   /// Walks the global declarations, resolving symbols, and determining the
139   /// dependencies of each global.
Scan(Global * global)140   void Scan(Global* global) {
141     TINT_SCOPED_ASSIGNMENT(current_global_, global);
142 
143     if (auto* str = global->node->As<ast::Struct>()) {
144       Declare(str->name, str);
145       for (auto* member : str->members) {
146         TraverseType(member->type);
147       }
148       return;
149     }
150     if (auto* alias = global->node->As<ast::Alias>()) {
151       Declare(alias->name, alias);
152       TraverseType(alias->type);
153       return;
154     }
155     if (auto* func = global->node->As<ast::Function>()) {
156       Declare(func->symbol, func);
157       TraverseDecorations(func->decorations);
158       TraverseFunction(func);
159       return;
160     }
161     if (auto* var = global->node->As<ast::Variable>()) {
162       Declare(var->symbol, var);
163       TraverseType(var->type);
164       if (var->constructor) {
165         TraverseExpression(var->constructor);
166       }
167       return;
168     }
169     UnhandledNode(diagnostics_, global->node);
170   }
171 
172  private:
173   /// Traverses the function, performing symbol resolution and determining
174   /// global dependencies.
TraverseFunction(const ast::Function * func)175   void TraverseFunction(const ast::Function* func) {
176     scope_stack_.Push();
177     TINT_DEFER(scope_stack_.Pop());
178 
179     for (auto* param : func->params) {
180       if (auto* shadows = scope_stack_.Get(param->symbol)) {
181         graph_.shadows.emplace(param, shadows);
182       }
183       Declare(param->symbol, param);
184       TraverseType(param->type);
185     }
186     if (func->body) {
187       TraverseStatements(func->body->statements);
188     }
189     TraverseType(func->return_type);
190   }
191 
192   /// Traverses the statements, performing symbol resolution and determining
193   /// global dependencies.
TraverseStatements(const ast::StatementList & stmts)194   void TraverseStatements(const ast::StatementList& stmts) {
195     for (auto* s : stmts) {
196       TraverseStatement(s);
197     }
198   }
199 
200   /// Traverses the statement, performing symbol resolution and determining
201   /// global dependencies.
TraverseStatement(const ast::Statement * stmt)202   void TraverseStatement(const ast::Statement* stmt) {
203     if (stmt == nullptr) {
204       return;
205     }
206     if (auto* b = stmt->As<ast::AssignmentStatement>()) {
207       TraverseExpression(b->lhs);
208       TraverseExpression(b->rhs);
209       return;
210     }
211     if (auto* b = stmt->As<ast::BlockStatement>()) {
212       scope_stack_.Push();
213       TINT_DEFER(scope_stack_.Pop());
214       TraverseStatements(b->statements);
215       return;
216     }
217     if (auto* r = stmt->As<ast::CallStatement>()) {
218       TraverseExpression(r->expr);
219       return;
220     }
221     if (auto* l = stmt->As<ast::ForLoopStatement>()) {
222       scope_stack_.Push();
223       TINT_DEFER(scope_stack_.Pop());
224       TraverseStatement(l->initializer);
225       TraverseExpression(l->condition);
226       TraverseStatement(l->continuing);
227       TraverseStatement(l->body);
228       return;
229     }
230     if (auto* l = stmt->As<ast::LoopStatement>()) {
231       scope_stack_.Push();
232       TINT_DEFER(scope_stack_.Pop());
233       TraverseStatements(l->body->statements);
234       TraverseStatement(l->continuing);
235       return;
236     }
237     if (auto* i = stmt->As<ast::IfStatement>()) {
238       TraverseExpression(i->condition);
239       TraverseStatement(i->body);
240       for (auto* e : i->else_statements) {
241         TraverseExpression(e->condition);
242         TraverseStatement(e->body);
243       }
244       return;
245     }
246     if (auto* r = stmt->As<ast::ReturnStatement>()) {
247       TraverseExpression(r->value);
248       return;
249     }
250     if (auto* s = stmt->As<ast::SwitchStatement>()) {
251       TraverseExpression(s->condition);
252       for (auto* c : s->body) {
253         for (auto* sel : c->selectors) {
254           TraverseExpression(sel);
255         }
256         TraverseStatement(c->body);
257       }
258       return;
259     }
260     if (auto* v = stmt->As<ast::VariableDeclStatement>()) {
261       if (auto* shadows = scope_stack_.Get(v->variable->symbol)) {
262         graph_.shadows.emplace(v->variable, shadows);
263       }
264       TraverseType(v->variable->type);
265       TraverseExpression(v->variable->constructor);
266       Declare(v->variable->symbol, v->variable);
267       return;
268     }
269     if (stmt->IsAnyOf<ast::BreakStatement, ast::ContinueStatement,
270                       ast::DiscardStatement, ast::FallthroughStatement>()) {
271       return;
272     }
273 
274     UnhandledNode(diagnostics_, stmt);
275   }
276 
277   /// Adds the symbol definition to the current scope, raising an error if two
278   /// symbols collide within the same scope.
Declare(Symbol symbol,const ast::Node * node)279   void Declare(Symbol symbol, const ast::Node* node) {
280     auto* old = scope_stack_.Set(symbol, node);
281     if (old != nullptr && node != old) {
282       auto name = symbols_.NameFor(symbol);
283       AddError(diagnostics_, "redeclaration of '" + name + "'", node->source);
284       AddNote(diagnostics_, "'" + name + "' previously declared here",
285               old->source);
286     }
287   }
288 
289   /// Traverses the expression, performing symbol resolution and determining
290   /// global dependencies.
TraverseExpression(const ast::Expression * root)291   void TraverseExpression(const ast::Expression* root) {
292     if (!root) {
293       return;
294     }
295     ast::TraverseExpressions(
296         root, diagnostics_, [&](const ast::Expression* expr) {
297           if (auto* ident = expr->As<ast::IdentifierExpression>()) {
298             auto* node = scope_stack_.Get(ident->symbol);
299             if (node == nullptr) {
300               if (!IsIntrinsic(ident->symbol)) {
301                 UnknownSymbol(ident->symbol, ident->source, "identifier");
302               }
303               return ast::TraverseAction::Descend;
304             }
305             auto global_it = globals_.find(ident->symbol);
306             if (global_it != globals_.end() &&
307                 node == global_it->second->node) {
308               AddGlobalDependency(ident, ident->symbol, "identifier",
309                                   "references");
310             } else {
311               graph_.resolved_symbols.emplace(ident, node);
312             }
313           }
314           if (auto* call = expr->As<ast::CallExpression>()) {
315             if (call->target.name) {
316               if (!IsIntrinsic(call->target.name->symbol)) {
317                 AddGlobalDependency(call->target.name,
318                                     call->target.name->symbol, "function",
319                                     "calls");
320                 graph_.resolved_symbols.emplace(
321                     call,
322                     utils::Lookup(graph_.resolved_symbols, call->target.name));
323               }
324             }
325             if (call->target.type) {
326               TraverseType(call->target.type);
327               graph_.resolved_symbols.emplace(
328                   call,
329                   utils::Lookup(graph_.resolved_symbols, call->target.type));
330             }
331           }
332           if (auto* cast = expr->As<ast::BitcastExpression>()) {
333             TraverseType(cast->type);
334           }
335           return ast::TraverseAction::Descend;
336         });
337   }
338 
339   /// Traverses the type node, performing symbol resolution and determining
340   /// global dependencies.
TraverseType(const ast::Type * ty)341   void TraverseType(const ast::Type* ty) {
342     if (ty == nullptr) {
343       return;
344     }
345     if (auto* arr = ty->As<ast::Array>()) {
346       TraverseType(arr->type);
347       TraverseExpression(arr->count);
348       return;
349     }
350     if (auto* atomic = ty->As<ast::Atomic>()) {
351       TraverseType(atomic->type);
352       return;
353     }
354     if (auto* mat = ty->As<ast::Matrix>()) {
355       TraverseType(mat->type);
356       return;
357     }
358     if (auto* ptr = ty->As<ast::Pointer>()) {
359       TraverseType(ptr->type);
360       return;
361     }
362     if (auto* tn = ty->As<ast::TypeName>()) {
363       AddGlobalDependency(tn, tn->name, "type", "references");
364       return;
365     }
366     if (auto* vec = ty->As<ast::Vector>()) {
367       TraverseType(vec->type);
368       return;
369     }
370     if (auto* tex = ty->As<ast::SampledTexture>()) {
371       TraverseType(tex->type);
372       return;
373     }
374     if (auto* tex = ty->As<ast::MultisampledTexture>()) {
375       TraverseType(tex->type);
376       return;
377     }
378     if (ty->IsAnyOf<ast::Void, ast::Bool, ast::I32, ast::U32, ast::F32,
379                     ast::DepthTexture, ast::DepthMultisampledTexture,
380                     ast::StorageTexture, ast::ExternalTexture,
381                     ast::Sampler>()) {
382       return;
383     }
384 
385     UnhandledNode(diagnostics_, ty);
386   }
387 
388   /// Traverses the decoration list, performing symbol resolution and
389   /// determining global dependencies.
TraverseDecorations(const ast::DecorationList & decos)390   void TraverseDecorations(const ast::DecorationList& decos) {
391     for (auto* deco : decos) {
392       TraverseDecoration(deco);
393     }
394   }
395 
396   /// Traverses the decoration, performing symbol resolution and determining
397   /// global dependencies.
TraverseDecoration(const ast::Decoration * deco)398   void TraverseDecoration(const ast::Decoration* deco) {
399     if (auto* wg = deco->As<ast::WorkgroupDecoration>()) {
400       TraverseExpression(wg->x);
401       TraverseExpression(wg->y);
402       TraverseExpression(wg->z);
403       return;
404     }
405     if (deco->IsAnyOf<ast::BindingDecoration, ast::BuiltinDecoration,
406                       ast::GroupDecoration, ast::InternalDecoration,
407                       ast::InterpolateDecoration, ast::InvariantDecoration,
408                       ast::LocationDecoration, ast::OverrideDecoration,
409                       ast::StageDecoration, ast::StrideDecoration,
410                       ast::StructBlockDecoration,
411                       ast::StructMemberAlignDecoration,
412                       ast::StructMemberOffsetDecoration,
413                       ast::StructMemberSizeDecoration>()) {
414       return;
415     }
416 
417     UnhandledNode(diagnostics_, deco);
418   }
419 
420   /// Adds the dependency to the currently processed global
AddGlobalDependency(const ast::Node * from,Symbol to,const char * use,const char * action)421   void AddGlobalDependency(const ast::Node* from,
422                            Symbol to,
423                            const char* use,
424                            const char* action) {
425     auto global_it = globals_.find(to);
426     if (global_it != globals_.end()) {
427       auto* global = global_it->second;
428       if (dependency_edges_
429               .emplace(DependencyEdge{current_global_, global},
430                        DependencyInfo{from->source, action})
431               .second) {
432         current_global_->deps.emplace_back(global);
433       }
434       graph_.resolved_symbols.emplace(from, global->node);
435     } else {
436       UnknownSymbol(to, from->source, use);
437     }
438   }
439 
440   /// @returns true if `name` is the name of an intrinsic function
IsIntrinsic(Symbol name) const441   bool IsIntrinsic(Symbol name) const {
442     return sem::ParseIntrinsicType(symbols_.NameFor(name)) !=
443            sem::IntrinsicType::kNone;
444   }
445 
446   /// Appends an error to the diagnostics that the given symbol cannot be
447   /// resolved.
UnknownSymbol(Symbol name,Source source,const char * use)448   void UnknownSymbol(Symbol name, Source source, const char* use) {
449     AddError(
450         diagnostics_,
451         "unknown " + std::string(use) + ": '" + symbols_.NameFor(name) + "'",
452         source);
453   }
454 
455   using VariableMap = std::unordered_map<Symbol, const ast::Variable*>;
456   const SymbolTable& symbols_;
457   const GlobalMap& globals_;
458   diag::List& diagnostics_;
459   DependencyGraph& graph_;
460   DependencyEdges& dependency_edges_;
461 
462   ScopeStack<const ast::Node*> scope_stack_;
463   Global* current_global_ = nullptr;
464 };
465 
466 /// The global dependency analysis system
467 struct DependencyAnalysis {
468  public:
469   /// Constructor
DependencyAnalysistint::resolver::__anon6cc0d6e10111::DependencyAnalysis470   DependencyAnalysis(const SymbolTable& symbols,
471                      diag::List& diagnostics,
472                      DependencyGraph& graph)
473       : symbols_(symbols), diagnostics_(diagnostics), graph_(graph) {}
474 
475   /// Performs global dependency analysis on the module, emitting any errors to
476   /// #diagnostics.
477   /// @returns true if analysis found no errors, otherwise false.
Runtint::resolver::__anon6cc0d6e10111::DependencyAnalysis478   bool Run(const ast::Module& module, bool allow_out_of_order_decls) {
479     // Collect all the named globals from the AST module
480     GatherGlobals(module);
481 
482     // Traverse the named globals to build the dependency graph
483     DetermineDependencies();
484 
485     // Sort the globals into dependency order
486     SortGlobals();
487 
488     // Dump the dependency graph if TINT_DUMP_DEPENDENCY_GRAPH is non-zero
489     DumpDependencyGraph();
490 
491     if (!allow_out_of_order_decls) {
492       // Prevent out-of-order declarations.
493       ErrorOnOutOfOrderDeclarations();
494     }
495 
496     graph_.ordered_globals = std::move(sorted_);
497 
498     return !diagnostics_.contains_errors();
499   }
500 
501  private:
502   /// @param node the ast::Node of the global declaration
503   /// @returns the symbol of the global declaration node
504   /// @note will raise an ICE if the node is not a type, function or variable
505   /// declaration
SymbolOftint::resolver::__anon6cc0d6e10111::DependencyAnalysis506   Symbol SymbolOf(const ast::Node* node) const {
507     if (auto* td = node->As<ast::TypeDecl>()) {
508       return td->name;
509     }
510     if (auto* func = node->As<ast::Function>()) {
511       return func->symbol;
512     }
513     if (auto* var = node->As<ast::Variable>()) {
514       return var->symbol;
515     }
516     UnhandledNode(diagnostics_, node);
517     return {};
518   }
519 
520   /// @param node the ast::Node of the global declaration
521   /// @returns the name of the global declaration node
522   /// @note will raise an ICE if the node is not a type, function or variable
523   /// declaration
NameOftint::resolver::__anon6cc0d6e10111::DependencyAnalysis524   std::string NameOf(const ast::Node* node) const {
525     return symbols_.NameFor(SymbolOf(node));
526   }
527 
528   /// @param node the ast::Node of the global declaration
529   /// @returns a string representation of the global declaration kind
530   /// @note will raise an ICE if the node is not a type, function or variable
531   /// declaration
KindOftint::resolver::__anon6cc0d6e10111::DependencyAnalysis532   std::string KindOf(const ast::Node* node) {
533     if (node->Is<ast::Struct>()) {
534       return "struct";
535     }
536     if (node->Is<ast::Alias>()) {
537       return "alias";
538     }
539     if (node->Is<ast::Function>()) {
540       return "function";
541     }
542     if (auto* var = node->As<ast::Variable>()) {
543       return var->is_const ? "let" : "var";
544     }
545     UnhandledNode(diagnostics_, node);
546     return {};
547   }
548 
549   /// Traverses `module`, collecting all the global declarations and populating
550   /// the #globals and #declaration_order fields.
GatherGlobalstint::resolver::__anon6cc0d6e10111::DependencyAnalysis551   void GatherGlobals(const ast::Module& module) {
552     for (auto* node : module.GlobalDeclarations()) {
553       auto* global = allocator_.Create(node);
554       globals_.emplace(SymbolOf(node), global);
555       declaration_order_.emplace_back(global);
556     }
557   }
558 
559   /// Walks the global declarations, determining the dependencies of each global
560   /// and adding these to each global's Global::deps field.
DetermineDependenciestint::resolver::__anon6cc0d6e10111::DependencyAnalysis561   void DetermineDependencies() {
562     DependencyScanner scanner(symbols_, globals_, diagnostics_, graph_,
563                               dependency_edges_);
564     for (auto* global : declaration_order_) {
565       scanner.Scan(global);
566     }
567   }
568 
569   /// Performs a depth-first traversal of `root`'s dependencies, calling `enter`
570   /// as the function decends into each dependency and `exit` when bubbling back
571   /// up towards the root.
572   /// @param enter is a function with the signature: `bool(Global*)`. The
573   /// `enter` function returns true if TraverseDependencies() should traverse
574   /// the dependency, otherwise it will be skipped.
575   /// @param exit is a function with the signature: `void(Global*)`. The `exit`
576   /// function is only called if the corresponding `enter` call returned true.
577   template <typename ENTER, typename EXIT>
TraverseDependenciestint::resolver::__anon6cc0d6e10111::DependencyAnalysis578   void TraverseDependencies(const Global* root, ENTER&& enter, EXIT&& exit) {
579     // Entry is a single entry in the traversal stack. Entry points to a
580     // dep_idx'th dependency of Entry::global.
581     struct Entry {
582       const Global* global;  // The parent global
583       size_t dep_idx;        // The dependency index in `global->deps`
584     };
585 
586     if (!enter(root)) {
587       return;
588     }
589 
590     std::vector<Entry> stack{Entry{root, 0}};
591     while (true) {
592       auto& entry = stack.back();
593       // Have we exhausted the dependencies of entry.global?
594       if (entry.dep_idx < entry.global->deps.size()) {
595         // No, there's more dependencies to traverse.
596         auto& dep = entry.global->deps[entry.dep_idx];
597         // Does the caller want to enter this dependency?
598         if (enter(dep)) {                  // Yes.
599           stack.push_back(Entry{dep, 0});  // Enter the dependency.
600         } else {
601           entry.dep_idx++;  // No. Skip this node.
602         }
603       } else {
604         // Yes. Time to back up.
605         // Exit this global, pop the stack, and if there's another parent node,
606         // increment its dependency index, and loop again.
607         exit(entry.global);
608         stack.pop_back();
609         if (stack.empty()) {
610           return;  // All done.
611         }
612         stack.back().dep_idx++;
613       }
614     }
615   }
616 
617   /// SortGlobals sorts the globals into dependency order, erroring if cyclic
618   /// dependencies are found. The sorted dependencies are assigned to #sorted.
SortGlobalstint::resolver::__anon6cc0d6e10111::DependencyAnalysis619   void SortGlobals() {
620     if (diagnostics_.contains_errors()) {
621       return;  // This code assumes there are no undeclared identifiers.
622     }
623 
624     std::unordered_set<const Global*> visited;
625     for (auto* global : declaration_order_) {
626       utils::UniqueVector<const Global*> stack;
627       TraverseDependencies(
628           global,
629           [&](const Global* g) {  // Enter
630             if (!stack.add(g)) {
631               CyclicDependencyFound(g, stack);
632               return false;
633             }
634             if (sorted_.contains(g->node)) {
635               // Visited this global already.
636               // stack was pushed, but exit() will not be called when we return
637               // false, so pop here.
638               stack.pop_back();
639               return false;
640             }
641             return true;
642           },
643           [&](const Global* g) {  // Exit. Only called if Enter returned true.
644             sorted_.add(g->node);
645             stack.pop_back();
646           });
647 
648       sorted_.add(global->node);
649 
650       if (!stack.empty()) {
651         // Each stack.push() must have a corresponding stack.pop_back().
652         TINT_ICE(Resolver, diagnostics_)
653             << "stack not empty after returning from TraverseDependencies()";
654       }
655     }
656   }
657 
658   /// DepInfoFor() looks up the global dependency information for the dependency
659   /// of global `from` depending on `to`.
660   /// @note will raise an ICE if the edge is not found.
DepInfoFortint::resolver::__anon6cc0d6e10111::DependencyAnalysis661   DependencyInfo DepInfoFor(const Global* from, const Global* to) const {
662     auto it = dependency_edges_.find(DependencyEdge{from, to});
663     if (it != dependency_edges_.end()) {
664       return it->second;
665     }
666     TINT_ICE(Resolver, diagnostics_)
667         << "failed to find dependency info for edge: '" << NameOf(from->node)
668         << "' -> '" << NameOf(to->node) << "'";
669     return {};
670   }
671 
672   // TODO(crbug.com/tint/1266): Errors if there are any uses of globals before
673   // their declaration. Out-of-order declarations was added to the WGSL
674   // specification with https://github.com/gpuweb/gpuweb/pull/2244, but Mozilla
675   // have objections to this change so this feature is currently disabled via
676   // this function.
ErrorOnOutOfOrderDeclarationstint::resolver::__anon6cc0d6e10111::DependencyAnalysis677   void ErrorOnOutOfOrderDeclarations() {
678     if (diagnostics_.contains_errors()) {
679       // Might have already errored about cyclic dependencies. No need to report
680       // out-of-order errors as well.
681       return;
682     }
683     std::unordered_set<const Global*> seen;
684     for (auto* global : declaration_order_) {
685       for (auto* dep : global->deps) {
686         if (!seen.count(dep)) {
687           auto info = DepInfoFor(global, dep);
688           auto name = NameOf(dep->node);
689           AddError(diagnostics_,
690                    KindOf(dep->node) + " '" + name +
691                        "' used before it has been declared",
692                    info.source);
693           AddNote(diagnostics_,
694                   KindOf(dep->node) + " '" + name + "' declared here",
695                   dep->node->source);
696         }
697       }
698       seen.emplace(global);
699     }
700   }
701 
702   /// CyclicDependencyFound() emits an error diagnostic for a cyclic dependency.
703   /// @param root is the global that starts the cyclic dependency, which must be
704   /// found in `stack`.
705   /// @param stack is the global dependency stack that contains a loop.
CyclicDependencyFoundtint::resolver::__anon6cc0d6e10111::DependencyAnalysis706   void CyclicDependencyFound(const Global* root,
707                              const std::vector<const Global*>& stack) {
708     std::stringstream msg;
709     msg << "cyclic dependency found: ";
710     constexpr size_t kLoopNotStarted = ~0u;
711     size_t loop_start = kLoopNotStarted;
712     for (size_t i = 0; i < stack.size(); i++) {
713       auto* e = stack[i];
714       if (loop_start == kLoopNotStarted && e == root) {
715         loop_start = i;
716       }
717       if (loop_start != kLoopNotStarted) {
718         msg << "'" << NameOf(e->node) << "' -> ";
719       }
720     }
721     msg << "'" << NameOf(root->node) << "'";
722     AddError(diagnostics_, msg.str(), root->node->source);
723     for (size_t i = loop_start; i < stack.size(); i++) {
724       auto* from = stack[i];
725       auto* to = (i + 1 < stack.size()) ? stack[i + 1] : stack[loop_start];
726       auto info = DepInfoFor(from, to);
727       AddNote(diagnostics_,
728               KindOf(from->node) + " '" + NameOf(from->node) + "' " +
729                   info.action + " " + KindOf(to->node) + " '" +
730                   NameOf(to->node) + "' here",
731               info.source);
732     }
733   }
734 
DumpDependencyGraphtint::resolver::__anon6cc0d6e10111::DependencyAnalysis735   void DumpDependencyGraph() {
736 #if TINT_DUMP_DEPENDENCY_GRAPH == 0
737     if ((true)) {
738       return;
739     }
740 #endif  // TINT_DUMP_DEPENDENCY_GRAPH
741     printf("=========================\n");
742     printf("------ declaration ------ \n");
743     for (auto* global : declaration_order_) {
744       printf("%s\n", NameOf(global->node).c_str());
745     }
746     printf("------ dependencies ------ \n");
747     for (auto* node : sorted_) {
748       auto symbol = SymbolOf(node);
749       auto* global = globals_.at(symbol);
750       printf("%s depends on:\n", symbols_.NameFor(symbol).c_str());
751       for (auto* dep : global->deps) {
752         printf("  %s\n", NameOf(dep->node).c_str());
753       }
754     }
755     printf("=========================\n");
756   }
757 
758   /// Program symbols
759   const SymbolTable& symbols_;
760 
761   /// Program diagnostics
762   diag::List& diagnostics_;
763 
764   /// The resulting dependency graph
765   DependencyGraph& graph_;
766 
767   /// Allocator of Globals
768   BlockAllocator<Global> allocator_;
769 
770   /// Global map, keyed by name. Populated by GatherGlobals().
771   GlobalMap globals_;
772 
773   /// Map of DependencyEdge to DependencyInfo. Populated by
774   /// DetermineDependencies().
775   DependencyEdges dependency_edges_;
776 
777   /// Globals in declaration order. Populated by GatherGlobals().
778   std::vector<Global*> declaration_order_;
779 
780   /// Globals in sorted dependency order. Populated by SortGlobals().
781   utils::UniqueVector<const ast::Node*> sorted_;
782 };
783 
784 }  // namespace
785 
786 DependencyGraph::DependencyGraph() = default;
787 DependencyGraph::DependencyGraph(DependencyGraph&&) = default;
788 DependencyGraph::~DependencyGraph() = default;
789 
Build(const ast::Module & module,const SymbolTable & symbols,diag::List & diagnostics,DependencyGraph & output,bool allow_out_of_order_decls)790 bool DependencyGraph::Build(const ast::Module& module,
791                             const SymbolTable& symbols,
792                             diag::List& diagnostics,
793                             DependencyGraph& output,
794                             bool allow_out_of_order_decls) {
795   DependencyAnalysis da{symbols, diagnostics, output};
796   return da.Run(module, allow_out_of_order_decls);
797 }
798 
799 }  // namespace resolver
800 }  // namespace tint
801