Commit 87c35883 by Olli Etuaho Committed by Commit Bot

Prune no-op statements with a single traverser

We put pruning literal statements and pruning empty declarations in the same traverser, as some of the required logic is the same. This pruning of no-ops is always done as one of the first processing steps after parsing, so further processing of the AST is simpler. Since we now prune pure literals before removing no-op cases from the end of switch statements, we also don't need any sort of special handling for switch statements in pruning pure literals. BUG=angleproject:2181 TEST=angle_unittests Change-Id: I2d86efaeb80baab63ac3cc803f3fd9e7ec02908a Reviewed-on: https://chromium-review.googlesource.com/727803Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarCorentin Wallez <cwallez@chromium.org> Commit-Queue: Olli Etuaho <oetuaho@nvidia.com>
parent 65c56dd9
...@@ -97,10 +97,8 @@ ...@@ -97,10 +97,8 @@
'compiler/translator/PoolAlloc.cpp', 'compiler/translator/PoolAlloc.cpp',
'compiler/translator/PoolAlloc.h', 'compiler/translator/PoolAlloc.h',
'compiler/translator/Pragma.h', 'compiler/translator/Pragma.h',
'compiler/translator/PruneEmptyDeclarations.cpp', 'compiler/translator/PruneNoOps.cpp',
'compiler/translator/PruneEmptyDeclarations.h', 'compiler/translator/PruneNoOps.h',
'compiler/translator/PrunePureLiteralStatements.cpp',
'compiler/translator/PrunePureLiteralStatements.h',
'compiler/translator/QualifierTypes.h', 'compiler/translator/QualifierTypes.h',
'compiler/translator/QualifierTypes.cpp', 'compiler/translator/QualifierTypes.cpp',
'compiler/translator/RecordConstantPrecision.cpp', 'compiler/translator/RecordConstantPrecision.cpp',
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
#include "compiler/translator/IsASTDepthBelowLimit.h" #include "compiler/translator/IsASTDepthBelowLimit.h"
#include "compiler/translator/OutputTree.h" #include "compiler/translator/OutputTree.h"
#include "compiler/translator/ParseContext.h" #include "compiler/translator/ParseContext.h"
#include "compiler/translator/PruneEmptyDeclarations.h" #include "compiler/translator/PruneNoOps.h"
#include "compiler/translator/RegenerateStructNames.h" #include "compiler/translator/RegenerateStructNames.h"
#include "compiler/translator/RemoveArrayLengthMethod.h" #include "compiler/translator/RemoveArrayLengthMethod.h"
#include "compiler/translator/RemoveInvariantDeclaration.h" #include "compiler/translator/RemoveInvariantDeclaration.h"
...@@ -375,17 +375,21 @@ TIntermBlock *TCompiler::compileTreeImpl(const char *const shaderStrings[], ...@@ -375,17 +375,21 @@ TIntermBlock *TCompiler::compileTreeImpl(const char *const shaderStrings[],
if (success && (compileOptions & SH_LIMIT_EXPRESSION_COMPLEXITY)) if (success && (compileOptions & SH_LIMIT_EXPRESSION_COMPLEXITY))
success = limitExpressionComplexity(root); success = limitExpressionComplexity(root);
// Prune empty declarations to work around driver bugs and to keep declaration output // We prune no-ops to work around driver bugs and to keep AST processing and output simple.
// simple. We want to do this before most other operations since TIntermDeclaration nodes // The following kinds of no-ops are pruned:
// without any children are tricky to work with. // 1. Empty declarations "int;".
// 2. Literal statements: "1.0;". The ESSL output doesn't define a default precision
// for float, so float literal statements would end up with no precision which is
// invalid ESSL.
// After this empty declarations are not allowed in the AST.
if (success) if (success)
PruneEmptyDeclarations(root); PruneNoOps(root);
// In case the last case inside a switch statement is a certain type of no-op, GLSL // In case the last case inside a switch statement is a certain type of no-op, GLSL
// compilers in drivers may not accept it. In this case we clean up the dead code from the // compilers in drivers may not accept it. In this case we clean up the dead code from the
// end of switch statements. This is also required because PruneEmptyDeclarations may have // end of switch statements. This is also required because PruneNoOps may have left switch
// left switch statements that only contained an empty declaration inside the final case in // statements that only contained an empty declaration inside the final case in an invalid
// an invalid state. Relies on that PruneEmptyDeclarations has already been run. // state. Relies on that PruneNoOps has already been run.
if (success) if (success)
RemoveNoOpCasesFromEndOfSwitchStatements(root, &symbolTable); RemoveNoOpCasesFromEndOfSwitchStatements(root, &symbolTable);
......
...@@ -216,6 +216,7 @@ class TIntermLoop : public TIntermNode ...@@ -216,6 +216,7 @@ class TIntermLoop : public TIntermNode
TIntermTyped *getExpression() { return mExpr; } TIntermTyped *getExpression() { return mExpr; }
TIntermBlock *getBody() { return mBody; } TIntermBlock *getBody() { return mBody; }
void setInit(TIntermNode *init) { mInit = init; }
void setCondition(TIntermTyped *condition) { mCond = condition; } void setCondition(TIntermTyped *condition) { mCond = condition; }
void setExpression(TIntermTyped *expression) { mExpr = expression; } void setExpression(TIntermTyped *expression) { mExpr = expression; }
void setBody(TIntermBlock *body) { mBody = body; } void setBody(TIntermBlock *body) { mBody = body; }
......
//
// Copyright (c) 2002-2015 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.
//
// The PruneEmptyDeclarations function prunes unnecessary empty declarations and declarators from
// the AST.
#ifndef COMPILER_TRANSLATOR_PRUNEEMPTYDECLARATIONS_H_
#define COMPILER_TRANSLATOR_PRUNEEMPTYDECLARATIONS_H_
namespace sh
{
class TIntermBlock;
void PruneEmptyDeclarations(TIntermBlock *root);
}
#endif // COMPILER_TRANSLATOR_PRUNEEMPTYDECLARATIONS_H_
...@@ -3,10 +3,15 @@ ...@@ -3,10 +3,15 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// //
// The PruneEmptyDeclarations function prunes unnecessary empty declarations and declarators from // PruneNoOps.cpp: The PruneNoOps function prunes:
// the AST. // 1. Empty declarations "int;". Empty declarators will be pruned as well, so for example:
// int , a;
// is turned into
// int a;
// 2. Literal statements: "1.0;". The ESSL output doesn't define a default precision for float,
// so float literal statements would end up with no precision which is invalid ESSL.
#include "compiler/translator/PruneEmptyDeclarations.h" #include "compiler/translator/PruneNoOps.h"
#include "compiler/translator/IntermTraverse.h" #include "compiler/translator/IntermTraverse.h"
...@@ -16,29 +21,45 @@ namespace sh ...@@ -16,29 +21,45 @@ namespace sh
namespace namespace
{ {
class PruneEmptyDeclarationsTraverser : private TIntermTraverser bool IsNoOp(TIntermNode *node)
{
if (node->getAsConstantUnion() != nullptr)
{
return true;
}
bool isEmptyDeclaration = node->getAsDeclarationNode() != nullptr &&
node->getAsDeclarationNode()->getSequence()->empty();
if (isEmptyDeclaration)
{
return true;
}
return false;
}
class PruneNoOpsTraverser : private TIntermTraverser
{ {
public: public:
static void apply(TIntermBlock *root); static void apply(TIntermBlock *root);
private: private:
PruneEmptyDeclarationsTraverser(); PruneNoOpsTraverser();
bool visitDeclaration(Visit, TIntermDeclaration *node) override; bool visitDeclaration(Visit, TIntermDeclaration *node) override;
bool visitBlock(Visit visit, TIntermBlock *node) override;
bool visitLoop(Visit visit, TIntermLoop *loop) override;
}; };
void PruneEmptyDeclarationsTraverser::apply(TIntermBlock *root) void PruneNoOpsTraverser::apply(TIntermBlock *root)
{ {
PruneEmptyDeclarationsTraverser prune; PruneNoOpsTraverser prune;
root->traverse(&prune); root->traverse(&prune);
prune.updateTree(); prune.updateTree();
} }
PruneEmptyDeclarationsTraverser::PruneEmptyDeclarationsTraverser() PruneNoOpsTraverser::PruneNoOpsTraverser() : TIntermTraverser(true, false, false)
: TIntermTraverser(true, false, false)
{ {
} }
bool PruneEmptyDeclarationsTraverser::visitDeclaration(Visit, TIntermDeclaration *node) bool PruneNoOpsTraverser::visitDeclaration(Visit, TIntermDeclaration *node)
{ {
TIntermSequence *sequence = node->getSequence(); TIntermSequence *sequence = node->getSequence();
if (sequence->size() >= 1) if (sequence->size() >= 1)
...@@ -61,23 +82,22 @@ bool PruneEmptyDeclarationsTraverser::visitDeclaration(Visit, TIntermDeclaration ...@@ -61,23 +82,22 @@ bool PruneEmptyDeclarationsTraverser::visitDeclaration(Visit, TIntermDeclaration
} }
else if (sym->getBasicType() != EbtStruct) else if (sym->getBasicType() != EbtStruct)
{ {
// Single struct declarations may just declare the struct type and no variables, so // If there are entirely empty non-struct declarations, they result in
// they should not be pruned. If there are entirely empty non-struct declarations, // TIntermDeclaration nodes without any children in the parsing stage. These are
// they result in TIntermDeclaration nodes without any children in the parsing // handled in visitBlock and visitLoop.
// stage. This will be handled further down in the code.
UNREACHABLE(); UNREACHABLE();
} }
else if (sym->getType().getQualifier() != EvqGlobal && else if (sym->getType().getQualifier() != EvqGlobal &&
sym->getType().getQualifier() != EvqTemporary) sym->getType().getQualifier() != EvqTemporary)
{ {
// We've hit an empty struct declaration with a qualifier, for example like // Single struct declarations may just declare the struct type and no variables, so
// this: // they should not be pruned. Here we handle an empty struct declaration with a
// const struct a { int i; }; // qualifier, for example like this:
// NVIDIA GL driver version 367.27 doesn't accept this kind of declarations, so // const struct a { int i; };
// we convert the declaration to a regular struct declaration. This is okay, // NVIDIA GL driver version 367.27 doesn't accept this kind of declarations, so we
// since ESSL 1.00 spec section 4.1.8 says about structs that "The optional // convert the declaration to a regular struct declaration. This is okay, since ESSL
// qualifiers only apply to any declarators, and are not part of the type being // 1.00 spec section 4.1.8 says about structs that "The optional qualifiers only
// defined for name." // apply to any declarators, and are not part of the type being defined for name."
if (mInGlobalScope) if (mInGlobalScope)
{ {
...@@ -90,31 +110,47 @@ bool PruneEmptyDeclarationsTraverser::visitDeclaration(Visit, TIntermDeclaration ...@@ -90,31 +110,47 @@ bool PruneEmptyDeclarationsTraverser::visitDeclaration(Visit, TIntermDeclaration
} }
} }
} }
else return false;
}
bool PruneNoOpsTraverser::visitBlock(Visit visit, TIntermBlock *node)
{
TIntermSequence *statements = node->getSequence();
for (TIntermNode *statement : *statements)
{ {
// We have a declaration with no declarators. if (IsNoOp(statement))
// The declaration may be either inside a block or in a loop init expression.
TIntermBlock *parentAsBlock = getParentNode()->getAsBlock();
ASSERT(parentAsBlock != nullptr || getParentNode()->getAsLoopNode() != nullptr);
if (parentAsBlock)
{ {
TIntermSequence emptyReplacement; TIntermSequence emptyReplacement;
mMultiReplacements.push_back( mMultiReplacements.push_back(
NodeReplaceWithMultipleEntry(parentAsBlock, node, emptyReplacement)); NodeReplaceWithMultipleEntry(node, statement, emptyReplacement));
}
else
{
queueReplacement(nullptr, OriginalNode::IS_DROPPED);
} }
} }
return false;
return true;
}
bool PruneNoOpsTraverser::visitLoop(Visit visit, TIntermLoop *loop)
{
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;
} }
} // namespace } // namespace
void PruneEmptyDeclarations(TIntermBlock *root) void PruneNoOps(TIntermBlock *root)
{ {
PruneEmptyDeclarationsTraverser::apply(root); PruneNoOpsTraverser::apply(root);
} }
} // namespace sh } // namespace sh
//
// Copyright (c) 2002-2015 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.h: The PruneNoOps function prunes:
// 1. Empty declarations "int;". Empty declarators will be pruned as well, so for example:
// int , a;
// is turned into
// int a;
// 2. Literal statements: "1.0;". The ESSL output doesn't define a default precision for float,
// so float literal statements would end up with no precision which is invalid ESSL.
#ifndef COMPILER_TRANSLATOR_PRUNENOOPS_H_
#define COMPILER_TRANSLATOR_PRUNENOOPS_H_
namespace sh
{
class TIntermBlock;
void PruneNoOps(TIntermBlock *root);
}
#endif // COMPILER_TRANSLATOR_PRUNENOOPS_H_
//
// 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.
//
// PrunePureLiteralStatements.cpp: Removes statements that are literals and nothing else.
#include "compiler/translator/PrunePureLiteralStatements.h"
#include "compiler/translator/IntermTraverse.h"
namespace sh
{
namespace
{
class PrunePureLiteralStatementsTraverser : public TIntermTraverser
{
public:
PrunePureLiteralStatementsTraverser() : TIntermTraverser(true, false, false) {}
bool visitBlock(Visit visit, TIntermBlock *node) override
{
TIntermSequence *statements = node->getSequence();
if (statements == nullptr)
{
return false;
}
// Empty case statements at the end of a switch are invalid: if the last statements
// of a block was a pure literal, also delete all the case statements directly preceding it.
bool deleteCaseStatements = false;
for (int i = static_cast<int>(statements->size()); i-- > 0;)
{
TIntermNode *statement = (*statements)[i];
if (statement->getAsConstantUnion() != nullptr)
{
TIntermSequence emptyReplacement;
mMultiReplacements.push_back(
NodeReplaceWithMultipleEntry(node, statement, emptyReplacement));
if (i == static_cast<int>(statements->size()) - 1)
{
deleteCaseStatements = true;
}
continue;
}
if (deleteCaseStatements)
{
if (statement->getAsCaseNode() != nullptr)
{
TIntermSequence emptyReplacement;
mMultiReplacements.push_back(
NodeReplaceWithMultipleEntry(node, statement, emptyReplacement));
}
else
{
deleteCaseStatements = false;
}
}
}
return true;
}
bool visitLoop(Visit visit, TIntermLoop *loop) override
{
TIntermTyped *expr = loop->getExpression();
if (expr != nullptr && expr->getAsConstantUnion() != nullptr)
{
loop->setExpression(nullptr);
}
return true;
}
};
} // namespace
void PrunePureLiteralStatements(TIntermNode *root)
{
PrunePureLiteralStatementsTraverser prune;
root->traverse(&prune);
prune.updateTree();
}
} // namespace sh
//
// 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.
//
// PrunePureLiteralStatements.h: Removes statements that are literals and nothing else.
#ifndef COMPILER_TRANSLATOR_PRUNEPURELITERALSTATEMENTS_H_
#define COMPILER_TRANSLATOR_PRUNEPURELITERALSTATEMENTS_H_
namespace sh
{
class TIntermNode;
void PrunePureLiteralStatements(TIntermNode *root);
}
#endif // COMPILER_TRANSLATOR_PRUNEPURELITERALSTATEMENTS_H_
...@@ -36,11 +36,13 @@ bool IsEmptyBlock(TIntermNode *node) ...@@ -36,11 +36,13 @@ bool IsEmptyBlock(TIntermNode *node)
// here. Note that declarations for struct types do contain a nameless child node. // here. Note that declarations for struct types do contain a nameless child node.
ASSERT(node->getAsDeclarationNode() == nullptr || ASSERT(node->getAsDeclarationNode() == nullptr ||
!node->getAsDeclarationNode()->getSequence()->empty()); !node->getAsDeclarationNode()->getSequence()->empty());
// Pure literal statements should also already be pruned.
ASSERT(node->getAsConstantUnion() == nullptr);
return false; return false;
} }
// Return true if all statements in "statements" starting from index i consist only of empty blocks // Return true if all statements in "statements" starting from index i consist only of empty blocks
// and empty declarations. Returns true also if there are no statements. // and no-op statements. Returns true also if there are no statements.
bool AreEmptyBlocks(TIntermSequence *statements, size_t i) bool AreEmptyBlocks(TIntermSequence *statements, size_t i)
{ {
for (; i < statements->size(); ++i) for (; i < statements->size(); ++i)
......
...@@ -97,10 +97,10 @@ void RemoveSwitchFallThroughTraverser::visitSymbol(TIntermSymbol *node) ...@@ -97,10 +97,10 @@ void RemoveSwitchFallThroughTraverser::visitSymbol(TIntermSymbol *node)
void RemoveSwitchFallThroughTraverser::visitConstantUnion(TIntermConstantUnion *node) void RemoveSwitchFallThroughTraverser::visitConstantUnion(TIntermConstantUnion *node)
{ {
// Conditions of case labels are not traversed, so this is some other constant // Conditions of case labels are not traversed, so this is a constant statement like "0;".
// Could be just a statement like "0;" // These are no-ops so there's no need to add them back to the statement list. Should have
mPreviousCase->getSequence()->push_back(node); // already been pruned out of the AST, in fact.
mLastStatementWasBreak = false; UNREACHABLE();
} }
bool RemoveSwitchFallThroughTraverser::visitDeclaration(Visit, TIntermDeclaration *node) bool RemoveSwitchFallThroughTraverser::visitDeclaration(Visit, TIntermDeclaration *node)
......
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
#include "compiler/translator/BuiltInFunctionEmulatorGLSL.h" #include "compiler/translator/BuiltInFunctionEmulatorGLSL.h"
#include "compiler/translator/EmulatePrecision.h" #include "compiler/translator/EmulatePrecision.h"
#include "compiler/translator/PrunePureLiteralStatements.h"
#include "compiler/translator/RecordConstantPrecision.h" #include "compiler/translator/RecordConstantPrecision.h"
#include "compiler/translator/OutputESSL.h" #include "compiler/translator/OutputESSL.h"
#include "angle_gl.h" #include "angle_gl.h"
...@@ -34,10 +33,6 @@ void TranslatorESSL::translate(TIntermBlock *root, ...@@ -34,10 +33,6 @@ void TranslatorESSL::translate(TIntermBlock *root,
ShCompileOptions compileOptions, ShCompileOptions compileOptions,
PerformanceDiagnostics * /*perfDiagnostics*/) PerformanceDiagnostics * /*perfDiagnostics*/)
{ {
// The ESSL output doesn't define a default precision for float, so float literal statements
// end up with no precision which is invalid ESSL.
PrunePureLiteralStatements(root);
TInfoSinkBase &sink = getInfoSink().obj; TInfoSinkBase &sink = getInfoSink().obj;
int shaderVer = getShaderVersion(); int shaderVer = getShaderVersion();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment