blob: a2866f80e8e8a33f3ca9c7ef4a8eda74a01fff78 [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.
//
// 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