| // | |
| // Copyright 2002 The ANGLE Project Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| // | |
| // CallDAG.h: Implements a call graph DAG of functions to be re-used accross | |
| // analyses, allows to efficiently traverse the functions in topological | |
| // order. | |
| #include "compiler/translator/CallDAG.h" | |
| #include "compiler/translator/Diagnostics.h" | |
| #include "compiler/translator/SymbolTable.h" | |
| #include "compiler/translator/tree_util/IntermTraverse.h" | |
| namespace sh | |
| { | |
| // The CallDAGCreator does all the processing required to create the CallDAG | |
| // structure so that the latter contains only the necessary variables. | |
| class CallDAG::CallDAGCreator : public TIntermTraverser | |
| { | |
| public: | |
| CallDAGCreator(TDiagnostics *diagnostics) | |
| : TIntermTraverser(true, false, false), | |
| mDiagnostics(diagnostics), | |
| mCurrentFunction(nullptr), | |
| mCurrentIndex(0) | |
| {} | |
| InitResult assignIndices() | |
| { | |
| int skipped = 0; | |
| for (auto &it : mFunctions) | |
| { | |
| // Skip unimplemented functions | |
| if (it.second.definitionNode) | |
| { | |
| InitResult result = assignIndicesInternal(&it.second); | |
| if (result != INITDAG_SUCCESS) | |
| { | |
| return result; | |
| } | |
| } | |
| else | |
| { | |
| skipped++; | |
| } | |
| } | |
| ASSERT(mFunctions.size() == mCurrentIndex + skipped); | |
| return INITDAG_SUCCESS; | |
| } | |
| void fillDataStructures(std::vector<Record> *records, std::map<int, int> *idToIndex) | |
| { | |
| ASSERT(records->empty()); | |
| ASSERT(idToIndex->empty()); | |
| records->resize(mCurrentIndex); | |
| for (auto &it : mFunctions) | |
| { | |
| CreatorFunctionData &data = it.second; | |
| // Skip unimplemented functions | |
| if (!data.definitionNode) | |
| { | |
| continue; | |
| } | |
| ASSERT(data.index < records->size()); | |
| Record &record = (*records)[data.index]; | |
| record.node = data.definitionNode; | |
| record.callees.reserve(data.callees.size()); | |
| for (auto &callee : data.callees) | |
| { | |
| record.callees.push_back(static_cast<int>(callee->index)); | |
| } | |
| (*idToIndex)[it.first] = static_cast<int>(data.index); | |
| } | |
| } | |
| private: | |
| struct CreatorFunctionData | |
| { | |
| CreatorFunctionData() | |
| : definitionNode(nullptr), name(""), index(0), indexAssigned(false), visiting(false) | |
| {} | |
| std::set<CreatorFunctionData *> callees; | |
| TIntermFunctionDefinition *definitionNode; | |
| ImmutableString name; | |
| size_t index; | |
| bool indexAssigned; | |
| bool visiting; | |
| }; | |
| bool visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node) override | |
| { | |
| // Create the record if need be and remember the definition node. | |
| mCurrentFunction = &mFunctions[node->getFunction()->uniqueId().get()]; | |
| // Name will be overwritten here. If we've already traversed the prototype of this function, | |
| // it should have had the same name. | |
| ASSERT(mCurrentFunction->name == "" || | |
| mCurrentFunction->name == node->getFunction()->name()); | |
| mCurrentFunction->name = node->getFunction()->name(); | |
| mCurrentFunction->definitionNode = node; | |
| node->getBody()->traverse(this); | |
| mCurrentFunction = nullptr; | |
| return false; | |
| } | |
| void visitFunctionPrototype(TIntermFunctionPrototype *node) override | |
| { | |
| ASSERT(mCurrentFunction == nullptr); | |
| // Function declaration, create an empty record. | |
| auto &record = mFunctions[node->getFunction()->uniqueId().get()]; | |
| record.name = node->getFunction()->name(); | |
| } | |
| // Track functions called from another function. | |
| bool visitAggregate(Visit visit, TIntermAggregate *node) override | |
| { | |
| if (node->getOp() == EOpCallFunctionInAST) | |
| { | |
| // Function call, add the callees | |
| auto it = mFunctions.find(node->getFunction()->uniqueId().get()); | |
| ASSERT(it != mFunctions.end()); | |
| // We might be traversing the initializer of a global variable. Even though function | |
| // calls in global scope are forbidden by the parser, some subsequent AST | |
| // transformations can add them to emulate particular features. | |
| if (mCurrentFunction) | |
| { | |
| mCurrentFunction->callees.insert(&it->second); | |
| } | |
| } | |
| return true; | |
| } | |
| // Recursively assigns indices to a sub DAG | |
| InitResult assignIndicesInternal(CreatorFunctionData *root) | |
| { | |
| // Iterative implementation of the index assignment algorithm. A recursive version | |
| // would be prettier but since the CallDAG creation runs before the limiting of the | |
| // call depth, we might get stack overflows (computation of the call depth uses the | |
| // CallDAG). | |
| ASSERT(root); | |
| if (root->indexAssigned) | |
| { | |
| return INITDAG_SUCCESS; | |
| } | |
| // If we didn't have to detect recursion, functionsToProcess could be a simple queue | |
| // in which we add the function being processed's callees. However in order to detect | |
| // recursion we need to know which functions we are currently visiting. For that reason | |
| // functionsToProcess will look like a concatenation of segments of the form | |
| // [F visiting = true, subset of F callees with visiting = false] and the following | |
| // segment (if any) will be start with a callee of F. | |
| // This way we can remember when we started visiting a function, to put visiting back | |
| // to false. | |
| TVector<CreatorFunctionData *> functionsToProcess; | |
| functionsToProcess.push_back(root); | |
| InitResult result = INITDAG_SUCCESS; | |
| std::stringstream errorStream = sh::InitializeStream<std::stringstream>(); | |
| while (!functionsToProcess.empty()) | |
| { | |
| CreatorFunctionData *function = functionsToProcess.back(); | |
| if (function->visiting) | |
| { | |
| function->visiting = false; | |
| function->index = mCurrentIndex++; | |
| function->indexAssigned = true; | |
| functionsToProcess.pop_back(); | |
| continue; | |
| } | |
| if (!function->definitionNode) | |
| { | |
| errorStream << "Undefined function '" << function->name | |
| << ")' used in the following call chain:"; | |
| result = INITDAG_UNDEFINED; | |
| break; | |
| } | |
| if (function->indexAssigned) | |
| { | |
| functionsToProcess.pop_back(); | |
| continue; | |
| } | |
| function->visiting = true; | |
| for (auto callee : function->callees) | |
| { | |
| functionsToProcess.push_back(callee); | |
| // Check if the callee is already being visited after pushing it so that it appears | |
| // in the chain printed in the info log. | |
| if (callee->visiting) | |
| { | |
| errorStream << "Recursive function call in the following call chain:"; | |
| result = INITDAG_RECURSION; | |
| break; | |
| } | |
| } | |
| if (result != INITDAG_SUCCESS) | |
| { | |
| break; | |
| } | |
| } | |
| // The call chain is made of the function we were visiting when the error was detected. | |
| if (result != INITDAG_SUCCESS) | |
| { | |
| bool first = true; | |
| for (auto function : functionsToProcess) | |
| { | |
| if (function->visiting) | |
| { | |
| if (!first) | |
| { | |
| errorStream << " -> "; | |
| } | |
| errorStream << function->name << ")"; | |
| first = false; | |
| } | |
| } | |
| if (mDiagnostics) | |
| { | |
| std::string errorStr = errorStream.str(); | |
| mDiagnostics->globalError(errorStr.c_str()); | |
| } | |
| } | |
| return result; | |
| } | |
| TDiagnostics *mDiagnostics; | |
| std::map<int, CreatorFunctionData> mFunctions; | |
| CreatorFunctionData *mCurrentFunction; | |
| size_t mCurrentIndex; | |
| }; | |
| // CallDAG | |
| CallDAG::CallDAG() {} | |
| CallDAG::~CallDAG() {} | |
| const size_t CallDAG::InvalidIndex = std::numeric_limits<size_t>::max(); | |
| size_t CallDAG::findIndex(const TSymbolUniqueId &id) const | |
| { | |
| auto it = mFunctionIdToIndex.find(id.get()); | |
| if (it == mFunctionIdToIndex.end()) | |
| { | |
| return InvalidIndex; | |
| } | |
| else | |
| { | |
| return it->second; | |
| } | |
| } | |
| const CallDAG::Record &CallDAG::getRecordFromIndex(size_t index) const | |
| { | |
| ASSERT(index != InvalidIndex && index < mRecords.size()); | |
| return mRecords[index]; | |
| } | |
| size_t CallDAG::size() const | |
| { | |
| return mRecords.size(); | |
| } | |
| void CallDAG::clear() | |
| { | |
| mRecords.clear(); | |
| mFunctionIdToIndex.clear(); | |
| } | |
| CallDAG::InitResult CallDAG::init(TIntermNode *root, TDiagnostics *diagnostics) | |
| { | |
| CallDAGCreator creator(diagnostics); | |
| // Creates the mapping of functions to callees | |
| root->traverse(&creator); | |
| // Does the topological sort and detects recursions | |
| InitResult result = creator.assignIndices(); | |
| if (result != INITDAG_SUCCESS) | |
| { | |
| return result; | |
| } | |
| creator.fillDataStructures(&mRecords, &mFunctionIdToIndex); | |
| return INITDAG_SUCCESS; | |
| } | |
| } // namespace sh |