// // Copyright (c) 2017 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. // // RemoveUnreferencedVariables.cpp: // Drop variables that are declared but never referenced in the AST. This avoids adding unnecessary // initialization code for them. Also removes unreferenced struct types. // #include "compiler/translator/RemoveUnreferencedVariables.h" #include "compiler/translator/IntermTraverse.h" #include "compiler/translator/SymbolTable.h" namespace sh { namespace { class CollectVariableRefCountsTraverser : public TIntermTraverser { public: CollectVariableRefCountsTraverser(); using RefCountMap = std::unordered_map; RefCountMap &getSymbolIdRefCounts() { return mSymbolIdRefCounts; } RefCountMap &getStructIdRefCounts() { return mStructIdRefCounts; } void visitSymbol(TIntermSymbol *node) override; bool visitAggregate(Visit visit, TIntermAggregate *node) override; bool visitFunctionPrototype(Visit visit, TIntermFunctionPrototype *node) override; private: void incrementStructTypeRefCount(const TType &type); RefCountMap mSymbolIdRefCounts; // Structure reference counts are counted from symbols, constructors, function calls, function // return values and from interface block and structure fields. We need to track both function // calls and function return values since there's a compiler option not to prune unused // functions. The type of a constant union may also be a struct, but statements that are just a // constant union are always pruned, and if the constant union is used somehow it will get // counted by something else. RefCountMap mStructIdRefCounts; }; CollectVariableRefCountsTraverser::CollectVariableRefCountsTraverser() : TIntermTraverser(true, false, false) { } void CollectVariableRefCountsTraverser::incrementStructTypeRefCount(const TType &type) { if (type.isInterfaceBlock()) { const auto *block = type.getInterfaceBlock(); ASSERT(block); // We can end up incrementing ref counts of struct types referenced from an interface block // multiple times for the same block. This doesn't matter, because interface blocks can't be // pruned so we'll never do the reverse operation. for (const auto &field : block->fields()) { ASSERT(!field->type()->isInterfaceBlock()); incrementStructTypeRefCount(*field->type()); } return; } const auto *structure = type.getStruct(); if (structure != nullptr) { auto structIter = mStructIdRefCounts.find(structure->uniqueId()); if (structIter == mStructIdRefCounts.end()) { mStructIdRefCounts[structure->uniqueId()] = 1u; for (const auto &field : structure->fields()) { incrementStructTypeRefCount(*field->type()); } return; } ++(structIter->second); } } void CollectVariableRefCountsTraverser::visitSymbol(TIntermSymbol *node) { incrementStructTypeRefCount(node->getType()); auto iter = mSymbolIdRefCounts.find(node->getId()); if (iter == mSymbolIdRefCounts.end()) { mSymbolIdRefCounts[node->getId()] = 1u; return; } ++(iter->second); } bool CollectVariableRefCountsTraverser::visitAggregate(Visit visit, TIntermAggregate *node) { // This tracks struct references in both function calls and constructors. incrementStructTypeRefCount(node->getType()); return true; } bool CollectVariableRefCountsTraverser::visitFunctionPrototype(Visit visit, TIntermFunctionPrototype *node) { incrementStructTypeRefCount(node->getType()); return true; } // Traverser that removes all unreferenced variables on one traversal. class RemoveUnreferencedVariablesTraverser : public TIntermTraverser { public: RemoveUnreferencedVariablesTraverser( CollectVariableRefCountsTraverser::RefCountMap *symbolIdRefCounts, CollectVariableRefCountsTraverser::RefCountMap *structIdRefCounts, TSymbolTable *symbolTable); bool visitDeclaration(Visit visit, TIntermDeclaration *node) override; void visitSymbol(TIntermSymbol *node) override; bool visitAggregate(Visit visit, TIntermAggregate *node) override; // Traverse loop and block nodes in reverse order. Note that this traverser does not track // parent block positions, so insertStatementInParentBlock is unusable! void traverseBlock(TIntermBlock *block) override; void traverseLoop(TIntermLoop *loop) override; private: void removeVariableDeclaration(TIntermDeclaration *node, TIntermTyped *declarator); void decrementStructTypeRefCount(const TType &type); CollectVariableRefCountsTraverser::RefCountMap *mSymbolIdRefCounts; CollectVariableRefCountsTraverser::RefCountMap *mStructIdRefCounts; bool mRemoveReferences; }; RemoveUnreferencedVariablesTraverser::RemoveUnreferencedVariablesTraverser( CollectVariableRefCountsTraverser::RefCountMap *symbolIdRefCounts, CollectVariableRefCountsTraverser::RefCountMap *structIdRefCounts, TSymbolTable *symbolTable) : TIntermTraverser(true, false, true, symbolTable), mSymbolIdRefCounts(symbolIdRefCounts), mStructIdRefCounts(structIdRefCounts), mRemoveReferences(false) { } void RemoveUnreferencedVariablesTraverser::decrementStructTypeRefCount(const TType &type) { auto *structure = type.getStruct(); if (structure != nullptr) { ASSERT(mStructIdRefCounts->find(structure->uniqueId()) != mStructIdRefCounts->end()); unsigned int structRefCount = --(*mStructIdRefCounts)[structure->uniqueId()]; if (structRefCount == 0) { for (const auto &field : structure->fields()) { decrementStructTypeRefCount(*field->type()); } } } } void RemoveUnreferencedVariablesTraverser::removeVariableDeclaration(TIntermDeclaration *node, TIntermTyped *declarator) { if (declarator->getType().isStructSpecifier() && !declarator->getType().isNamelessStruct()) { unsigned int structId = declarator->getType().getStruct()->uniqueId(); if ((*mStructIdRefCounts)[structId] > 1u) { // If this declaration declares a named struct type that is used elsewhere, we need to // keep it. We can still change the declarator though so that it doesn't declare an // unreferenced variable. // Note that since we're not removing the entire declaration, the struct's reference // count will end up being one less than the correct refcount. But since the struct // declaration is kept, the incorrect refcount can't cause any other problems. if (declarator->getAsSymbolNode() && declarator->getAsSymbolNode()->getSymbol().empty()) { // Already an empty declaration - nothing to do. return; } queueReplacementWithParent(node, declarator, new TIntermSymbol(mSymbolTable->getEmptySymbolId(), TString(""), declarator->getType()), OriginalNode::IS_DROPPED); return; } } if (getParentNode()->getAsBlock()) { TIntermSequence emptyReplacement; mMultiReplacements.push_back( NodeReplaceWithMultipleEntry(getParentNode()->getAsBlock(), node, emptyReplacement)); } else { ASSERT(getParentNode()->getAsLoopNode()); queueReplacement(nullptr, OriginalNode::IS_DROPPED); } } bool RemoveUnreferencedVariablesTraverser::visitDeclaration(Visit visit, TIntermDeclaration *node) { if (visit == PreVisit) { // SeparateDeclarations should have already been run. ASSERT(node->getSequence()->size() == 1u); TIntermTyped *declarator = node->getSequence()->back()->getAsTyped(); ASSERT(declarator); // We can only remove variables that are not a part of the shader interface. TQualifier qualifier = declarator->getQualifier(); if (qualifier != EvqTemporary && qualifier != EvqGlobal) { return true; } bool canRemoveVariable = false; TIntermSymbol *symbolNode = declarator->getAsSymbolNode(); if (symbolNode != nullptr) { canRemoveVariable = (*mSymbolIdRefCounts)[symbolNode->getId()] == 1u || symbolNode->getSymbol().empty(); } TIntermBinary *initNode = declarator->getAsBinaryNode(); if (initNode != nullptr) { ASSERT(initNode->getLeft()->getAsSymbolNode()); int symbolId = initNode->getLeft()->getAsSymbolNode()->getId(); canRemoveVariable = (*mSymbolIdRefCounts)[symbolId] == 1u && !initNode->getRight()->hasSideEffects(); } if (canRemoveVariable) { removeVariableDeclaration(node, declarator); mRemoveReferences = true; } return true; } ASSERT(visit == PostVisit); mRemoveReferences = false; return true; } void RemoveUnreferencedVariablesTraverser::visitSymbol(TIntermSymbol *node) { if (mRemoveReferences) { ASSERT(mSymbolIdRefCounts->find(node->getId()) != mSymbolIdRefCounts->end()); --(*mSymbolIdRefCounts)[node->getId()]; decrementStructTypeRefCount(node->getType()); } } bool RemoveUnreferencedVariablesTraverser::visitAggregate(Visit visit, TIntermAggregate *node) { if (mRemoveReferences) { decrementStructTypeRefCount(node->getType()); } return true; } void RemoveUnreferencedVariablesTraverser::traverseBlock(TIntermBlock *node) { // We traverse blocks in reverse order. This way reference counts can be decremented when // removing initializers, and variables that become unused when initializers are removed can be // removed on the same traversal. ScopedNodeInTraversalPath addToPath(this, node); bool visit = true; TIntermSequence *sequence = node->getSequence(); if (preVisit) visit = visitBlock(PreVisit, node); if (visit) { for (auto iter = sequence->rbegin(); iter != sequence->rend(); ++iter) { (*iter)->traverse(this); if (visit && inVisit) { if ((iter + 1) != sequence->rend()) visit = visitBlock(InVisit, node); } } } if (visit && postVisit) visitBlock(PostVisit, node); } void RemoveUnreferencedVariablesTraverser::traverseLoop(TIntermLoop *node) { // We traverse loops in reverse order as well. The loop body gets traversed before the init // node. ScopedNodeInTraversalPath addToPath(this, node); bool visit = true; if (preVisit) visit = visitLoop(PreVisit, node); if (visit) { // We don't need to traverse loop expressions or conditions since they can't be declarations // in the AST (loops which have a declaration in their condition get transformed in the // parsing stage). ASSERT(node->getExpression() == nullptr || node->getExpression()->getAsDeclarationNode() == nullptr); ASSERT(node->getCondition() == nullptr || node->getCondition()->getAsDeclarationNode() == nullptr); if (node->getBody()) node->getBody()->traverse(this); if (node->getInit()) node->getInit()->traverse(this); } if (visit && postVisit) visitLoop(PostVisit, node); } } // namespace void RemoveUnreferencedVariables(TIntermBlock *root, TSymbolTable *symbolTable) { CollectVariableRefCountsTraverser collector; root->traverse(&collector); RemoveUnreferencedVariablesTraverser traverser(&collector.getSymbolIdRefCounts(), &collector.getStructIdRefCounts(), symbolTable); root->traverse(&traverser); traverser.updateTree(); } } // namespace sh