| // |
| // 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. |
| // |
| // PruneNoOps.cpp: The PruneNoOps function prunes no-op statements. |
| |
| #include "compiler/translator/tree_ops/PruneNoOps.h" |
| |
| #include "compiler/translator/Symbol.h" |
| #include "compiler/translator/tree_util/IntermTraverse.h" |
| |
| namespace sh |
| { |
| |
| namespace |
| { |
| uint32_t GetSwitchConstantAsUInt(const TConstantUnion *value) |
| { |
| TConstantUnion asUInt; |
| if (value->getType() == EbtYuvCscStandardEXT) |
| { |
| asUInt.setUConst(value->getYuvCscStandardEXTConst()); |
| } |
| else |
| { |
| bool valid = asUInt.cast(EbtUInt, *value); |
| ASSERT(valid); |
| } |
| return asUInt.getUConst(); |
| } |
| |
| bool IsNoOpSwitch(TIntermSwitch *node) |
| { |
| if (node == nullptr) |
| { |
| return false; |
| } |
| |
| TIntermConstantUnion *expr = node->getInit()->getAsConstantUnion(); |
| if (expr == nullptr) |
| { |
| return false; |
| } |
| |
| const uint32_t exprValue = GetSwitchConstantAsUInt(expr->getConstantValue()); |
| |
| // See if any block matches the constant value |
| const TIntermSequence &statements = *node->getStatementList()->getSequence(); |
| |
| for (TIntermNode *statement : statements) |
| { |
| TIntermCase *caseLabel = statement->getAsCaseNode(); |
| if (caseLabel == nullptr) |
| { |
| continue; |
| } |
| |
| // Default matches everything, consider it not a no-op. |
| if (!caseLabel->hasCondition()) |
| { |
| return false; |
| } |
| |
| TIntermConstantUnion *condition = caseLabel->getCondition()->getAsConstantUnion(); |
| ASSERT(condition != nullptr); |
| |
| // If any case matches the value, it's not a no-op. |
| const uint32_t caseValue = GetSwitchConstantAsUInt(condition->getConstantValue()); |
| if (caseValue == exprValue) |
| { |
| return false; |
| } |
| } |
| |
| // No case matched the constant value the switch was used on, so the entire switch is a no-op. |
| return true; |
| } |
| |
| bool IsNoOp(TIntermNode *node) |
| { |
| bool isEmptyDeclaration = node->getAsDeclarationNode() != nullptr && |
| node->getAsDeclarationNode()->getSequence()->empty(); |
| if (isEmptyDeclaration) |
| { |
| return true; |
| } |
| |
| if (IsNoOpSwitch(node->getAsSwitchNode())) |
| { |
| return true; |
| } |
| |
| if (node->getAsTyped() == nullptr || node->getAsFunctionPrototypeNode() != nullptr) |
| { |
| return false; |
| } |
| |
| return !node->getAsTyped()->hasSideEffects(); |
| } |
| |
| class PruneNoOpsTraverser : private TIntermTraverser |
| { |
| public: |
| [[nodiscard]] static bool apply(TCompiler *compiler, |
| TIntermBlock *root, |
| TSymbolTable *symbolTable); |
| |
| private: |
| PruneNoOpsTraverser(TSymbolTable *symbolTable); |
| bool visitDeclaration(Visit, TIntermDeclaration *node) override; |
| bool visitBlock(Visit visit, TIntermBlock *node) override; |
| bool visitLoop(Visit visit, TIntermLoop *loop) override; |
| bool visitBranch(Visit visit, TIntermBranch *node) override; |
| TIntermTyped *pruneNoOpCommaExpressions(TIntermTyped *statement); |
| |
| bool mIsBranchVisited = false; |
| }; |
| |
| bool PruneNoOpsTraverser::apply(TCompiler *compiler, TIntermBlock *root, TSymbolTable *symbolTable) |
| { |
| PruneNoOpsTraverser prune(symbolTable); |
| root->traverse(&prune); |
| return prune.updateTree(compiler, root); |
| } |
| |
| PruneNoOpsTraverser::PruneNoOpsTraverser(TSymbolTable *symbolTable) |
| : TIntermTraverser(true, true, true, symbolTable) |
| {} |
| |
| bool PruneNoOpsTraverser::visitDeclaration(Visit visit, TIntermDeclaration *node) |
| { |
| if (visit != PreVisit) |
| { |
| return true; |
| } |
| |
| TIntermSequence *sequence = node->getSequence(); |
| if (sequence->size() >= 1) |
| { |
| TIntermSymbol *declaratorSymbol = sequence->front()->getAsSymbolNode(); |
| // Prune declarations without a variable name, unless it's an interface block declaration. |
| if (declaratorSymbol != nullptr && |
| declaratorSymbol->variable().symbolType() == SymbolType::Empty && |
| !declaratorSymbol->isInterfaceBlock()) |
| { |
| if (sequence->size() > 1) |
| { |
| // Generate a replacement that will remove the empty declarator in the beginning of |
| // a declarator list. Example of a declaration that will be changed: |
| // float, a; |
| // will be changed to |
| // float a; |
| // This applies also to struct declarations. |
| TIntermSequence emptyReplacement; |
| mMultiReplacements.emplace_back(node, declaratorSymbol, |
| std::move(emptyReplacement)); |
| } |
| else if (declaratorSymbol->getBasicType() != EbtStruct) |
| { |
| // If there are entirely empty non-struct declarations, they result in |
| // TIntermDeclaration nodes without any children in the parsing stage. These are |
| // handled in visitBlock and visitLoop. |
| UNREACHABLE(); |
| } |
| else if (declaratorSymbol->getQualifier() != EvqGlobal && |
| declaratorSymbol->getQualifier() != EvqTemporary) |
| { |
| // Single struct declarations may just declare the struct type and no variables, so |
| // they should not be pruned. Here we handle an empty struct declaration with a |
| // qualifier, for example like this: |
| // const struct a { int i; }; |
| // NVIDIA GL driver version 367.27 doesn't accept this kind of declarations, so we |
| // convert the declaration to a regular struct declaration. This is okay, since ESSL |
| // 1.00 spec section 4.1.8 says about structs that "The optional qualifiers only |
| // apply to any declarators, and are not part of the type being defined for name." |
| |
| // Create a new variable to use in the declarator so that the variable and node |
| // types are kept consistent. |
| TType *type = new TType(declaratorSymbol->getType()); |
| if (mInGlobalScope) |
| { |
| type->setQualifier(EvqGlobal); |
| } |
| else |
| { |
| type->setQualifier(EvqTemporary); |
| } |
| TVariable *variable = |
| new TVariable(mSymbolTable, kEmptyImmutableString, type, SymbolType::Empty); |
| queueReplacementWithParent(node, declaratorSymbol, new TIntermSymbol(variable), |
| OriginalNode::IS_DROPPED); |
| } |
| } |
| } |
| return false; |
| } |
| |
| bool PruneNoOpsTraverser::visitBlock(Visit visit, TIntermBlock *node) |
| { |
| ASSERT(visit == PreVisit); |
| |
| TIntermSequence &statements = *node->getSequence(); |
| |
| // Visit each statement in the block one by one. Once a branch is visited (break, continue, |
| // return or discard), drop the rest of the statements. |
| for (size_t statementIndex = 0; statementIndex < statements.size(); ++statementIndex) |
| { |
| TIntermNode *statement = statements[statementIndex]; |
| |
| // If the statement is a switch case label, stop pruning and continue visiting the children. |
| if (statement->getAsCaseNode() != nullptr) |
| { |
| mIsBranchVisited = false; |
| } |
| |
| // If a branch is visited, prune the statement. If the statement is a no-op, also prune it. |
| if (mIsBranchVisited || IsNoOp(statement)) |
| { |
| TIntermSequence emptyReplacement; |
| mMultiReplacements.emplace_back(node, statement, std::move(emptyReplacement)); |
| continue; |
| } |
| |
| // If the statement is a series of expressions delimited by comma, the resulting value is |
| // not used (because this is a block-level statement). Prune the no-op statements, and put |
| // the ones with side effect back together with comma. |
| if (statement->getAsBinaryNode() != nullptr) |
| { |
| statement = pruneNoOpCommaExpressions(statement->getAsBinaryNode()); |
| if (statement == nullptr) |
| { |
| TIntermSequence emptyReplacement; |
| mMultiReplacements.emplace_back(node, statement, std::move(emptyReplacement)); |
| continue; |
| } |
| |
| statements[statementIndex] = statement; |
| } |
| |
| // Visit the statement if not pruned. |
| statement->traverse(this); |
| } |
| |
| // If the parent is a block and mIsBranchVisited is set, this is a nested block without any |
| // condition (like if, loop or switch), so the rest of the parent block should also be pruned. |
| // Otherwise the parent block should be unaffected. |
| if (mIsBranchVisited && getParentNode()->getAsBlock() == nullptr) |
| { |
| mIsBranchVisited = false; |
| } |
| |
| return false; |
| } |
| |
| TIntermTyped *PruneNoOpsTraverser::pruneNoOpCommaExpressions(TIntermTyped *statement) |
| { |
| TIntermBinary *commaSeparatedExpressions = statement->getAsBinaryNode(); |
| if (commaSeparatedExpressions == nullptr || commaSeparatedExpressions->getOp() != EOpComma) |
| { |
| return statement; |
| } |
| |
| TIntermTyped *left = commaSeparatedExpressions->getLeft(); |
| TIntermTyped *right = commaSeparatedExpressions->getRight(); |
| |
| TIntermTyped *prunedLeft = IsNoOp(left) ? nullptr : pruneNoOpCommaExpressions(left); |
| TIntermTyped *prunedRight = IsNoOp(right) ? nullptr : pruneNoOpCommaExpressions(right); |
| |
| if (left == prunedLeft && right == prunedRight) |
| { |
| // Nothing got pruned. |
| return statement; |
| } |
| |
| // If either side is pruned, return the other side. Automatically returns nullptr if both sides |
| // are pruned. |
| if (prunedRight == nullptr) |
| { |
| return prunedLeft; |
| } |
| if (prunedLeft == nullptr) |
| { |
| return prunedRight; |
| } |
| |
| return new TIntermBinary(EOpComma, prunedLeft, prunedRight); |
| } |
| |
| bool PruneNoOpsTraverser::visitLoop(Visit visit, TIntermLoop *loop) |
| { |
| if (visit != PreVisit) |
| { |
| return true; |
| } |
| |
| TIntermTyped *expr = loop->getExpression(); |
| if (expr != nullptr && IsNoOp(expr)) |
| { |
| loop->setExpression(nullptr); |
| } |
| TIntermNode *init = loop->getInit(); |
| if (init != nullptr && IsNoOp(init)) |
| { |
| loop->setInit(nullptr); |
| } |
| |
| return true; |
| } |
| |
| bool PruneNoOpsTraverser::visitBranch(Visit visit, TIntermBranch *node) |
| { |
| ASSERT(visit == PreVisit); |
| |
| mIsBranchVisited = true; |
| |
| // Only possible child is the value of a return statement, which has nothing to prune. |
| return false; |
| } |
| } // namespace |
| |
| bool PruneNoOps(TCompiler *compiler, TIntermBlock *root, TSymbolTable *symbolTable) |
| { |
| return PruneNoOpsTraverser::apply(compiler, root, symbolTable); |
| } |
| |
| } // namespace sh |