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