1 //
2 // Copyright 2002 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6 
7 // CallDAG.h: Implements a call graph DAG of functions to be re-used accross
8 // analyses, allows to efficiently traverse the functions in topological
9 // order.
10 
11 #include "compiler/translator/CallDAG.h"
12 
13 #include "compiler/translator/Diagnostics.h"
14 #include "compiler/translator/SymbolTable.h"
15 #include "compiler/translator/tree_util/IntermTraverse.h"
16 
17 namespace sh
18 {
19 
20 // The CallDAGCreator does all the processing required to create the CallDAG
21 // structure so that the latter contains only the necessary variables.
22 class CallDAG::CallDAGCreator : public TIntermTraverser
23 {
24   public:
CallDAGCreator(TDiagnostics * diagnostics)25     CallDAGCreator(TDiagnostics *diagnostics)
26         : TIntermTraverser(true, false, false),
27           mDiagnostics(diagnostics),
28           mCurrentFunction(nullptr),
29           mCurrentIndex(0)
30     {}
31 
assignIndices()32     InitResult assignIndices()
33     {
34         int skipped = 0;
35         for (auto &it : mFunctions)
36         {
37             // Skip unimplemented functions
38             if (it.second.definitionNode)
39             {
40                 InitResult result = assignIndicesInternal(&it.second);
41                 if (result != INITDAG_SUCCESS)
42                 {
43                     return result;
44                 }
45             }
46             else
47             {
48                 skipped++;
49             }
50         }
51 
52         ASSERT(mFunctions.size() == mCurrentIndex + skipped);
53         return INITDAG_SUCCESS;
54     }
55 
fillDataStructures(std::vector<Record> * records,std::map<int,int> * idToIndex)56     void fillDataStructures(std::vector<Record> *records, std::map<int, int> *idToIndex)
57     {
58         ASSERT(records->empty());
59         ASSERT(idToIndex->empty());
60 
61         records->resize(mCurrentIndex);
62 
63         for (auto &it : mFunctions)
64         {
65             CreatorFunctionData &data = it.second;
66             // Skip unimplemented functions
67             if (!data.definitionNode)
68             {
69                 continue;
70             }
71             ASSERT(data.index < records->size());
72             Record &record = (*records)[data.index];
73 
74             record.node = data.definitionNode;
75 
76             record.callees.reserve(data.callees.size());
77             for (auto &callee : data.callees)
78             {
79                 record.callees.push_back(static_cast<int>(callee->index));
80             }
81 
82             (*idToIndex)[it.first] = static_cast<int>(data.index);
83         }
84     }
85 
86   private:
87     struct CreatorFunctionData
88     {
CreatorFunctionDatash::CallDAG::CallDAGCreator::CreatorFunctionData89         CreatorFunctionData()
90             : definitionNode(nullptr), name(""), index(0), indexAssigned(false), visiting(false)
91         {}
92 
93         std::set<CreatorFunctionData *> callees;
94         TIntermFunctionDefinition *definitionNode;
95         ImmutableString name;
96         size_t index;
97         bool indexAssigned;
98         bool visiting;
99     };
100 
visitFunctionDefinition(Visit visit,TIntermFunctionDefinition * node)101     bool visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node) override
102     {
103         // Create the record if need be and remember the definition node.
104         mCurrentFunction = &mFunctions[node->getFunction()->uniqueId().get()];
105         // Name will be overwritten here. If we've already traversed the prototype of this function,
106         // it should have had the same name.
107         ASSERT(mCurrentFunction->name == "" ||
108                mCurrentFunction->name == node->getFunction()->name());
109         mCurrentFunction->name           = node->getFunction()->name();
110         mCurrentFunction->definitionNode = node;
111 
112         node->getBody()->traverse(this);
113         mCurrentFunction = nullptr;
114         return false;
115     }
116 
visitFunctionPrototype(TIntermFunctionPrototype * node)117     void visitFunctionPrototype(TIntermFunctionPrototype *node) override
118     {
119         ASSERT(mCurrentFunction == nullptr);
120 
121         // Function declaration, create an empty record.
122         auto &record = mFunctions[node->getFunction()->uniqueId().get()];
123         record.name  = node->getFunction()->name();
124     }
125 
126     // Track functions called from another function.
visitAggregate(Visit visit,TIntermAggregate * node)127     bool visitAggregate(Visit visit, TIntermAggregate *node) override
128     {
129         if (node->getOp() == EOpCallFunctionInAST)
130         {
131             // Function call, add the callees
132             auto it = mFunctions.find(node->getFunction()->uniqueId().get());
133             ASSERT(it != mFunctions.end());
134 
135             // We might be traversing the initializer of a global variable. Even though function
136             // calls in global scope are forbidden by the parser, some subsequent AST
137             // transformations can add them to emulate particular features.
138             if (mCurrentFunction)
139             {
140                 mCurrentFunction->callees.insert(&it->second);
141             }
142         }
143         return true;
144     }
145 
146     // Recursively assigns indices to a sub DAG
assignIndicesInternal(CreatorFunctionData * root)147     InitResult assignIndicesInternal(CreatorFunctionData *root)
148     {
149         // Iterative implementation of the index assignment algorithm. A recursive version
150         // would be prettier but since the CallDAG creation runs before the limiting of the
151         // call depth, we might get stack overflows (computation of the call depth uses the
152         // CallDAG).
153 
154         ASSERT(root);
155 
156         if (root->indexAssigned)
157         {
158             return INITDAG_SUCCESS;
159         }
160 
161         // If we didn't have to detect recursion, functionsToProcess could be a simple queue
162         // in which we add the function being processed's callees. However in order to detect
163         // recursion we need to know which functions we are currently visiting. For that reason
164         // functionsToProcess will look like a concatenation of segments of the form
165         // [F visiting = true, subset of F callees with visiting = false] and the following
166         // segment (if any) will be start with a callee of F.
167         // This way we can remember when we started visiting a function, to put visiting back
168         // to false.
169         TVector<CreatorFunctionData *> functionsToProcess;
170         functionsToProcess.push_back(root);
171 
172         InitResult result = INITDAG_SUCCESS;
173 
174         std::stringstream errorStream = sh::InitializeStream<std::stringstream>();
175 
176         while (!functionsToProcess.empty())
177         {
178             CreatorFunctionData *function = functionsToProcess.back();
179 
180             if (function->visiting)
181             {
182                 function->visiting      = false;
183                 function->index         = mCurrentIndex++;
184                 function->indexAssigned = true;
185 
186                 functionsToProcess.pop_back();
187                 continue;
188             }
189 
190             if (!function->definitionNode)
191             {
192                 errorStream << "Undefined function '" << function->name
193                             << "()' used in the following call chain:";
194                 result = INITDAG_UNDEFINED;
195                 break;
196             }
197 
198             if (function->indexAssigned)
199             {
200                 functionsToProcess.pop_back();
201                 continue;
202             }
203 
204             function->visiting = true;
205 
206             for (auto callee : function->callees)
207             {
208                 functionsToProcess.push_back(callee);
209 
210                 // Check if the callee is already being visited after pushing it so that it appears
211                 // in the chain printed in the info log.
212                 if (callee->visiting)
213                 {
214                     errorStream << "Recursive function call in the following call chain:";
215                     result = INITDAG_RECURSION;
216                     break;
217                 }
218             }
219 
220             if (result != INITDAG_SUCCESS)
221             {
222                 break;
223             }
224         }
225 
226         // The call chain is made of the function we were visiting when the error was detected.
227         if (result != INITDAG_SUCCESS)
228         {
229             bool first = true;
230             for (auto function : functionsToProcess)
231             {
232                 if (function->visiting)
233                 {
234                     if (!first)
235                     {
236                         errorStream << " -> ";
237                     }
238                     errorStream << function->name << ")";
239                     first = false;
240                 }
241             }
242             if (mDiagnostics)
243             {
244                 std::string errorStr = errorStream.str();
245                 mDiagnostics->globalError(errorStr.c_str());
246             }
247         }
248 
249         return result;
250     }
251 
252     TDiagnostics *mDiagnostics;
253 
254     std::map<int, CreatorFunctionData> mFunctions;
255     CreatorFunctionData *mCurrentFunction;
256     size_t mCurrentIndex;
257 };
258 
259 // CallDAG
260 
CallDAG()261 CallDAG::CallDAG() {}
262 
~CallDAG()263 CallDAG::~CallDAG() {}
264 
265 const size_t CallDAG::InvalidIndex = std::numeric_limits<size_t>::max();
266 
findIndex(const TSymbolUniqueId & id) const267 size_t CallDAG::findIndex(const TSymbolUniqueId &id) const
268 {
269     auto it = mFunctionIdToIndex.find(id.get());
270 
271     if (it == mFunctionIdToIndex.end())
272     {
273         return InvalidIndex;
274     }
275     else
276     {
277         return it->second;
278     }
279 }
280 
getRecordFromIndex(size_t index) const281 const CallDAG::Record &CallDAG::getRecordFromIndex(size_t index) const
282 {
283     ASSERT(index != InvalidIndex && index < mRecords.size());
284     return mRecords[index];
285 }
286 
size() const287 size_t CallDAG::size() const
288 {
289     return mRecords.size();
290 }
291 
clear()292 void CallDAG::clear()
293 {
294     mRecords.clear();
295     mFunctionIdToIndex.clear();
296 }
297 
init(TIntermNode * root,TDiagnostics * diagnostics)298 CallDAG::InitResult CallDAG::init(TIntermNode *root, TDiagnostics *diagnostics)
299 {
300     CallDAGCreator creator(diagnostics);
301 
302     // Creates the mapping of functions to callees
303     root->traverse(&creator);
304 
305     // Does the topological sort and detects recursions
306     InitResult result = creator.assignIndices();
307     if (result != INITDAG_SUCCESS)
308     {
309         return result;
310     }
311 
312     creator.fillDataStructures(&mRecords, &mFunctionIdToIndex);
313     return INITDAG_SUCCESS;
314 }
315 
316 }  // namespace sh
317