blob: 4500aea4e482c023090cc7bd8e68edac514a4920 [file] [log] [blame]
//
// 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