| // | |
| // 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. | |
| // | |
| // RemoveSwitchFallThrough.cpp: Remove fall-through from switch statements. | |
| // Note that it is unsafe to do further AST transformations on the AST generated | |
| // by this function. It leaves duplicate nodes in the AST making replacements | |
| // unreliable. | |
| #include "compiler/translator/tree_ops/RemoveSwitchFallThrough.h" | |
| #include "compiler/translator/Diagnostics.h" | |
| #include "compiler/translator/tree_util/IntermTraverse.h" | |
| namespace sh | |
| { | |
| namespace | |
| { | |
| class RemoveSwitchFallThroughTraverser : public TIntermTraverser | |
| { | |
| public: | |
| static TIntermBlock *removeFallThrough(TIntermBlock *statementList, | |
| PerformanceDiagnostics *perfDiagnostics); | |
| private: | |
| RemoveSwitchFallThroughTraverser(TIntermBlock *statementList, | |
| PerformanceDiagnostics *perfDiagnostics); | |
| void visitSymbol(TIntermSymbol *node) override; | |
| void visitConstantUnion(TIntermConstantUnion *node) override; | |
| bool visitDeclaration(Visit, TIntermDeclaration *node) override; | |
| bool visitBinary(Visit, TIntermBinary *node) override; | |
| bool visitUnary(Visit, TIntermUnary *node) override; | |
| bool visitTernary(Visit visit, TIntermTernary *node) override; | |
| bool visitSwizzle(Visit, TIntermSwizzle *node) override; | |
| bool visitIfElse(Visit visit, TIntermIfElse *node) override; | |
| bool visitSwitch(Visit, TIntermSwitch *node) override; | |
| bool visitCase(Visit, TIntermCase *node) override; | |
| bool visitAggregate(Visit, TIntermAggregate *node) override; | |
| bool visitBlock(Visit, TIntermBlock *node) override; | |
| bool visitLoop(Visit, TIntermLoop *node) override; | |
| bool visitBranch(Visit, TIntermBranch *node) override; | |
| void outputSequence(TIntermSequence *sequence, size_t startIndex); | |
| void handlePreviousCase(); | |
| TIntermBlock *mStatementList; | |
| TIntermBlock *mStatementListOut; | |
| bool mLastStatementWasBreak; | |
| TIntermBlock *mPreviousCase; | |
| std::vector<TIntermBlock *> mCasesSharingBreak; | |
| PerformanceDiagnostics *mPerfDiagnostics; | |
| }; | |
| TIntermBlock *RemoveSwitchFallThroughTraverser::removeFallThrough( | |
| TIntermBlock *statementList, | |
| PerformanceDiagnostics *perfDiagnostics) | |
| { | |
| RemoveSwitchFallThroughTraverser rm(statementList, perfDiagnostics); | |
| ASSERT(statementList); | |
| statementList->traverse(&rm); | |
| ASSERT(rm.mPreviousCase || statementList->getSequence()->empty()); | |
| if (!rm.mLastStatementWasBreak && rm.mPreviousCase) | |
| { | |
| // Make sure that there's a branch at the end of the final case inside the switch statement. | |
| // This also ensures that any cases that fall through to the final case will get the break. | |
| TIntermBranch *finalBreak = new TIntermBranch(EOpBreak, nullptr); | |
| rm.mPreviousCase->getSequence()->push_back(finalBreak); | |
| rm.mLastStatementWasBreak = true; | |
| } | |
| rm.handlePreviousCase(); | |
| return rm.mStatementListOut; | |
| } | |
| RemoveSwitchFallThroughTraverser::RemoveSwitchFallThroughTraverser( | |
| TIntermBlock *statementList, | |
| PerformanceDiagnostics *perfDiagnostics) | |
| : TIntermTraverser(true, false, false), | |
| mStatementList(statementList), | |
| mLastStatementWasBreak(false), | |
| mPreviousCase(nullptr), | |
| mPerfDiagnostics(perfDiagnostics) | |
| { | |
| mStatementListOut = new TIntermBlock(); | |
| } | |
| void RemoveSwitchFallThroughTraverser::visitSymbol(TIntermSymbol *node) | |
| { | |
| // Note that this assumes that switch statements which don't begin by a case statement | |
| // have already been weeded out in validation. | |
| mPreviousCase->getSequence()->push_back(node); | |
| mLastStatementWasBreak = false; | |
| } | |
| void RemoveSwitchFallThroughTraverser::visitConstantUnion(TIntermConstantUnion *node) | |
| { | |
| // Conditions of case labels are not traversed, so this is a constant statement like "0;". | |
| // These are no-ops so there's no need to add them back to the statement list. Should have | |
| // already been pruned out of the AST, in fact. | |
| UNREACHABLE(); | |
| } | |
| bool RemoveSwitchFallThroughTraverser::visitDeclaration(Visit, TIntermDeclaration *node) | |
| { | |
| mPreviousCase->getSequence()->push_back(node); | |
| mLastStatementWasBreak = false; | |
| return false; | |
| } | |
| bool RemoveSwitchFallThroughTraverser::visitBinary(Visit, TIntermBinary *node) | |
| { | |
| mPreviousCase->getSequence()->push_back(node); | |
| mLastStatementWasBreak = false; | |
| return false; | |
| } | |
| bool RemoveSwitchFallThroughTraverser::visitUnary(Visit, TIntermUnary *node) | |
| { | |
| mPreviousCase->getSequence()->push_back(node); | |
| mLastStatementWasBreak = false; | |
| return false; | |
| } | |
| bool RemoveSwitchFallThroughTraverser::visitTernary(Visit, TIntermTernary *node) | |
| { | |
| mPreviousCase->getSequence()->push_back(node); | |
| mLastStatementWasBreak = false; | |
| return false; | |
| } | |
| bool RemoveSwitchFallThroughTraverser::visitSwizzle(Visit, TIntermSwizzle *node) | |
| { | |
| mPreviousCase->getSequence()->push_back(node); | |
| mLastStatementWasBreak = false; | |
| return false; | |
| } | |
| bool RemoveSwitchFallThroughTraverser::visitIfElse(Visit, TIntermIfElse *node) | |
| { | |
| mPreviousCase->getSequence()->push_back(node); | |
| mLastStatementWasBreak = false; | |
| return false; | |
| } | |
| bool RemoveSwitchFallThroughTraverser::visitSwitch(Visit, TIntermSwitch *node) | |
| { | |
| mPreviousCase->getSequence()->push_back(node); | |
| mLastStatementWasBreak = false; | |
| // Don't go into nested switch statements | |
| return false; | |
| } | |
| void RemoveSwitchFallThroughTraverser::outputSequence(TIntermSequence *sequence, size_t startIndex) | |
| { | |
| for (size_t i = startIndex; i < sequence->size(); ++i) | |
| { | |
| mStatementListOut->getSequence()->push_back(sequence->at(i)); | |
| } | |
| } | |
| void RemoveSwitchFallThroughTraverser::handlePreviousCase() | |
| { | |
| if (mPreviousCase) | |
| mCasesSharingBreak.push_back(mPreviousCase); | |
| if (mLastStatementWasBreak) | |
| { | |
| for (size_t i = 0; i < mCasesSharingBreak.size(); ++i) | |
| { | |
| ASSERT(!mCasesSharingBreak.at(i)->getSequence()->empty()); | |
| if (mCasesSharingBreak.at(i)->getSequence()->size() == 1) | |
| { | |
| // Fall-through is allowed in case the label has no statements. | |
| outputSequence(mCasesSharingBreak.at(i)->getSequence(), 0); | |
| } | |
| else | |
| { | |
| // Include all the statements that this case can fall through under the same label. | |
| if (mCasesSharingBreak.size() > i + 1u) | |
| { | |
| mPerfDiagnostics->warning(mCasesSharingBreak.at(i)->getLine(), | |
| "Performance: non-empty fall-through cases in " | |
| "switch statements generate extra code.", | |
| "switch"); | |
| } | |
| for (size_t j = i; j < mCasesSharingBreak.size(); ++j) | |
| { | |
| size_t startIndex = | |
| j > i ? 1 : 0; // Add the label only from the first sequence. | |
| outputSequence(mCasesSharingBreak.at(j)->getSequence(), startIndex); | |
| } | |
| } | |
| } | |
| mCasesSharingBreak.clear(); | |
| } | |
| mLastStatementWasBreak = false; | |
| mPreviousCase = nullptr; | |
| } | |
| bool RemoveSwitchFallThroughTraverser::visitCase(Visit, TIntermCase *node) | |
| { | |
| handlePreviousCase(); | |
| mPreviousCase = new TIntermBlock(); | |
| mPreviousCase->getSequence()->push_back(node); | |
| mPreviousCase->setLine(node->getLine()); | |
| // Don't traverse the condition of the case statement | |
| return false; | |
| } | |
| bool RemoveSwitchFallThroughTraverser::visitAggregate(Visit, TIntermAggregate *node) | |
| { | |
| mPreviousCase->getSequence()->push_back(node); | |
| mLastStatementWasBreak = false; | |
| return false; | |
| } | |
| bool DoesBlockAlwaysBreak(TIntermBlock *node) | |
| { | |
| if (node->getSequence()->empty()) | |
| { | |
| return false; | |
| } | |
| TIntermBlock *lastStatementAsBlock = node->getSequence()->back()->getAsBlock(); | |
| if (lastStatementAsBlock) | |
| { | |
| return DoesBlockAlwaysBreak(lastStatementAsBlock); | |
| } | |
| TIntermBranch *lastStatementAsBranch = node->getSequence()->back()->getAsBranchNode(); | |
| return lastStatementAsBranch != nullptr; | |
| } | |
| bool RemoveSwitchFallThroughTraverser::visitBlock(Visit, TIntermBlock *node) | |
| { | |
| if (node != mStatementList) | |
| { | |
| mPreviousCase->getSequence()->push_back(node); | |
| mLastStatementWasBreak = DoesBlockAlwaysBreak(node); | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool RemoveSwitchFallThroughTraverser::visitLoop(Visit, TIntermLoop *node) | |
| { | |
| mPreviousCase->getSequence()->push_back(node); | |
| mLastStatementWasBreak = false; | |
| return false; | |
| } | |
| bool RemoveSwitchFallThroughTraverser::visitBranch(Visit, TIntermBranch *node) | |
| { | |
| mPreviousCase->getSequence()->push_back(node); | |
| // TODO: Verify that accepting return or continue statements here doesn't cause problems. | |
| mLastStatementWasBreak = true; | |
| return false; | |
| } | |
| } // anonymous namespace | |
| TIntermBlock *RemoveSwitchFallThrough(TIntermBlock *statementList, | |
| PerformanceDiagnostics *perfDiagnostics) | |
| { | |
| return RemoveSwitchFallThroughTraverser::removeFallThrough(statementList, perfDiagnostics); | |
| } | |
| } // namespace sh |