Commit 3d70ca9c by Olli Etuaho Committed by Commit Bot

Remove unreferenced variables from the AST

Unreferenced local and global variables are now pruned from the AST. They will be removed unless their initializer has side effects. The CollectVariables step needs to be run after the pruning, as the pruning may affect which interface variables are statically used. It's also good to gather built-ins that need to be emulated after the pruning, so unnecessary built-in emulation functions are not added to the translator output. This will help handle some dEQP tests for arrays of arrays that have extremely large local arrays that are only used in an array length query. By constant folding the length and pruning unused variables we will avoid adding a large amount of array initialization code to the generated shaders. BUG=angleproject:2166 TEST=angle_unittests, angle_end2end_tests Change-Id: Ic918bfe8f16460bcd6101d73a7a674145f5aeecd Reviewed-on: https://chromium-review.googlesource.com/766434 Commit-Queue: Olli Etuaho <oetuaho@nvidia.com> Reviewed-by: 's avatarCorentin Wallez <cwallez@chromium.org>
parent 2c7f34c8
......@@ -115,6 +115,8 @@
'compiler/translator/RemoveNoOpCasesFromEndOfSwitchStatements.h',
'compiler/translator/RemovePow.cpp',
'compiler/translator/RemovePow.h',
'compiler/translator/RemoveUnreferencedVariables.cpp',
'compiler/translator/RemoveUnreferencedVariables.h',
'compiler/translator/RewriteDoWhile.cpp',
'compiler/translator/RewriteDoWhile.h',
'compiler/translator/RewriteTexelFetchOffset.cpp',
......
......@@ -32,6 +32,7 @@
#include "compiler/translator/RemoveInvariantDeclaration.h"
#include "compiler/translator/RemoveNoOpCasesFromEndOfSwitchStatements.h"
#include "compiler/translator/RemovePow.h"
#include "compiler/translator/RemoveUnreferencedVariables.h"
#include "compiler/translator/RewriteDoWhile.h"
#include "compiler/translator/ScalarizeVecAndMatConstructorArgs.h"
#include "compiler/translator/SeparateDeclarations.h"
......@@ -468,13 +469,6 @@ bool TCompiler::checkAndSimplifyAST(TIntermBlock *root,
return false;
}
// Built-in function emulation needs to happen after validateLimitations pass.
// TODO(jmadill): Remove global pool allocator.
GetGlobalPoolAllocator()->lock();
initBuiltInFunctionEmulator(&builtInFunctionEmulator, compileOptions);
GetGlobalPoolAllocator()->unlock();
builtInFunctionEmulator.markBuiltInFunctionsForEmulation(root);
// Clamping uniform array bounds needs to happen after validateLimitations pass.
if (compileOptions & SH_CLAMP_INDIRECT_ARRAY_BOUNDS)
{
......@@ -508,6 +502,51 @@ bool TCompiler::checkAndSimplifyAST(TIntermBlock *root,
RemovePow(root);
}
if (compileOptions & SH_REGENERATE_STRUCT_NAMES)
{
RegenerateStructNames gen(&symbolTable, shaderVersion);
root->traverse(&gen);
}
if (shaderType == GL_FRAGMENT_SHADER && shaderVersion == 100 &&
compileResources.EXT_draw_buffers && compileResources.MaxDrawBuffers > 1 &&
IsExtensionEnabled(extensionBehavior, TExtension::EXT_draw_buffers))
{
EmulateGLFragColorBroadcast(root, compileResources.MaxDrawBuffers, &outputVariables,
&symbolTable, shaderVersion);
}
// Split multi declarations and remove calls to array length().
// Note that SimplifyLoopConditions needs to be run before any other AST transformations
// that may need to generate new statements from loop conditions or loop expressions.
SimplifyLoopConditions(
root,
IntermNodePatternMatcher::kMultiDeclaration | IntermNodePatternMatcher::kArrayLengthMethod,
&getSymbolTable(), getShaderVersion());
// Note that separate declarations need to be run before other AST transformations that
// generate new statements from expressions.
SeparateDeclarations(root);
SplitSequenceOperator(root, IntermNodePatternMatcher::kArrayLengthMethod, &getSymbolTable(),
getShaderVersion());
RemoveArrayLengthMethod(root);
RemoveUnreferencedVariables(root, &symbolTable);
// Built-in function emulation needs to happen after validateLimitations pass.
// TODO(jmadill): Remove global pool allocator.
GetGlobalPoolAllocator()->lock();
initBuiltInFunctionEmulator(&builtInFunctionEmulator, compileOptions);
GetGlobalPoolAllocator()->unlock();
builtInFunctionEmulator.markBuiltInFunctionsForEmulation(root);
if (compileOptions & SH_SCALARIZE_VEC_AND_MAT_CONSTRUCTOR_ARGS)
{
ScalarizeVecAndMatConstructorArgs(root, shaderType, fragmentPrecisionHigh, &symbolTable);
}
if (shouldCollectVariables(compileOptions))
{
ASSERT(!variablesCollected);
......@@ -536,6 +575,13 @@ bool TCompiler::checkAndSimplifyAST(TIntermBlock *root,
}
}
// Removing invariant declarations must be done after collecting variables.
// Otherwise, built-in invariant declarations don't apply.
if (RemoveInvariant(shaderType, shaderVersion, outputType, compileOptions))
{
RemoveInvariantDeclaration(root);
}
// gl_Position is always written in compatibility output mode.
// It may have been already initialized among other output variables, in that case we don't
// need to initialize it twice.
......@@ -546,49 +592,6 @@ bool TCompiler::checkAndSimplifyAST(TIntermBlock *root,
mGLPositionInitialized = true;
}
// Removing invariant declarations must be done after collecting variables.
// Otherwise, built-in invariant declarations don't apply.
if (RemoveInvariant(shaderType, shaderVersion, outputType, compileOptions))
{
sh::RemoveInvariantDeclaration(root);
}
if (compileOptions & SH_SCALARIZE_VEC_AND_MAT_CONSTRUCTOR_ARGS)
{
ScalarizeVecAndMatConstructorArgs(root, shaderType, fragmentPrecisionHigh, &symbolTable);
}
if (compileOptions & SH_REGENERATE_STRUCT_NAMES)
{
RegenerateStructNames gen(&symbolTable, shaderVersion);
root->traverse(&gen);
}
if (shaderType == GL_FRAGMENT_SHADER && shaderVersion == 100 &&
compileResources.EXT_draw_buffers && compileResources.MaxDrawBuffers > 1 &&
IsExtensionEnabled(extensionBehavior, TExtension::EXT_draw_buffers))
{
EmulateGLFragColorBroadcast(root, compileResources.MaxDrawBuffers, &outputVariables,
&symbolTable, shaderVersion);
}
// Split multi declarations and remove calls to array length().
// Note that SimplifyLoopConditions needs to be run before any other AST transformations
// that may need to generate new statements from loop conditions or loop expressions.
SimplifyLoopConditions(
root,
IntermNodePatternMatcher::kMultiDeclaration | IntermNodePatternMatcher::kArrayLengthMethod,
&getSymbolTable(), getShaderVersion());
// Note that separate declarations need to be run before other AST transformations that
// generate new statements from expressions.
SeparateDeclarations(root);
SplitSequenceOperator(root, IntermNodePatternMatcher::kArrayLengthMethod, &getSymbolTable(),
getShaderVersion());
RemoveArrayLengthMethod(root);
// DeferGlobalInitializers needs to be run before other AST transformations that generate new
// statements from expressions. But it's fine to run DeferGlobalInitializers after the above
// SplitSequenceOperator and RemoveArrayLengthMethod since they only have an effect on the AST
......@@ -598,7 +601,6 @@ bool TCompiler::checkAndSimplifyAST(TIntermBlock *root,
bool canUseLoopsToInitialize = !(compileOptions & SH_DONT_USE_LOOPS_TO_INITIALIZE_VARIABLES);
DeferGlobalInitializers(root, initializeLocalsAndGlobals, canUseLoopsToInitialize, &symbolTable);
if (initializeLocalsAndGlobals)
{
// Initialize uninitialized local variables.
......
//
// 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.
//
// RemoveUnreferencedVariables.cpp:
// Drop variables that are declared but never referenced in the AST. This avoids adding unnecessary
// initialization code for them.
//
#include "compiler/translator/RemoveUnreferencedVariables.h"
#include "compiler/translator/IntermTraverse.h"
#include "compiler/translator/SymbolTable.h"
namespace sh
{
namespace
{
class CollectVariableRefCountsTraverser : public TIntermTraverser
{
public:
CollectVariableRefCountsTraverser();
using RefCountMap = std::unordered_map<int, unsigned int>;
RefCountMap &getSymbolIdRefCounts() { return mSymbolIdRefCounts; }
void visitSymbol(TIntermSymbol *node) override;
private:
RefCountMap mSymbolIdRefCounts;
};
CollectVariableRefCountsTraverser::CollectVariableRefCountsTraverser()
: TIntermTraverser(true, false, false)
{
}
void CollectVariableRefCountsTraverser::visitSymbol(TIntermSymbol *node)
{
auto iter = mSymbolIdRefCounts.find(node->getId());
if (iter == mSymbolIdRefCounts.end())
{
mSymbolIdRefCounts[node->getId()] = 1u;
return;
}
++(iter->second);
}
// Traverser that removes all unreferenced variables on one traversal.
class RemoveUnreferencedVariablesTraverser : public TIntermTraverser
{
public:
RemoveUnreferencedVariablesTraverser(
CollectVariableRefCountsTraverser::RefCountMap *symbolIdRefCounts,
TSymbolTable *symbolTable);
bool visitDeclaration(Visit visit, TIntermDeclaration *node) override;
void visitSymbol(TIntermSymbol *node) override;
// Traverse loop and block nodes in reverse order. Note that this traverser does not track
// parent block positions, so insertStatementInParentBlock is unusable!
void traverseBlock(TIntermBlock *block) override;
void traverseLoop(TIntermLoop *loop) override;
private:
void removeDeclaration(TIntermDeclaration *node, TIntermTyped *declarator);
CollectVariableRefCountsTraverser::RefCountMap *mSymbolIdRefCounts;
bool mRemoveReferences;
};
RemoveUnreferencedVariablesTraverser::RemoveUnreferencedVariablesTraverser(
CollectVariableRefCountsTraverser::RefCountMap *symbolIdRefCounts,
TSymbolTable *symbolTable)
: TIntermTraverser(true, false, true, symbolTable),
mSymbolIdRefCounts(symbolIdRefCounts),
mRemoveReferences(false)
{
}
void RemoveUnreferencedVariablesTraverser::removeDeclaration(TIntermDeclaration *node,
TIntermTyped *declarator)
{
if (declarator->getType().isStructSpecifier() && !declarator->getType().isNamelessStruct())
{
// We don't count references to struct types, so if this declaration declares a named struct
// type, we'll keep it. We can still change the declarator though so that it doesn't declare
// a variable.
queueReplacementWithParent(
node, declarator,
new TIntermSymbol(mSymbolTable->getEmptySymbolId(), TString(""), declarator->getType()),
OriginalNode::IS_DROPPED);
return;
}
if (getParentNode()->getAsBlock())
{
TIntermSequence emptyReplacement;
mMultiReplacements.push_back(
NodeReplaceWithMultipleEntry(getParentNode()->getAsBlock(), node, emptyReplacement));
}
else
{
ASSERT(getParentNode()->getAsLoopNode());
queueReplacement(nullptr, OriginalNode::IS_DROPPED);
}
}
bool RemoveUnreferencedVariablesTraverser::visitDeclaration(Visit visit, TIntermDeclaration *node)
{
if (visit == PreVisit)
{
// SeparateDeclarations should have already been run.
ASSERT(node->getSequence()->size() == 1u);
TIntermTyped *declarator = node->getSequence()->back()->getAsTyped();
ASSERT(declarator);
// We can only remove variables that are not a part of the shader interface.
TQualifier qualifier = declarator->getQualifier();
if (qualifier != EvqTemporary && qualifier != EvqGlobal)
{
return true;
}
bool canRemove = false;
TIntermSymbol *symbolNode = declarator->getAsSymbolNode();
if (symbolNode != nullptr)
{
canRemove = (*mSymbolIdRefCounts)[symbolNode->getId()] == 1u;
}
TIntermBinary *initNode = declarator->getAsBinaryNode();
if (initNode != nullptr)
{
ASSERT(initNode->getLeft()->getAsSymbolNode());
int symbolId = initNode->getLeft()->getAsSymbolNode()->getId();
canRemove =
(*mSymbolIdRefCounts)[symbolId] == 1u && !initNode->getRight()->hasSideEffects();
}
if (canRemove)
{
removeDeclaration(node, declarator);
mRemoveReferences = true;
}
return true;
}
ASSERT(visit == PostVisit);
mRemoveReferences = false;
return true;
}
void RemoveUnreferencedVariablesTraverser::visitSymbol(TIntermSymbol *node)
{
if (mRemoveReferences)
{
ASSERT(mSymbolIdRefCounts->find(node->getId()) != mSymbolIdRefCounts->end());
--(*mSymbolIdRefCounts)[node->getId()];
}
}
void RemoveUnreferencedVariablesTraverser::traverseBlock(TIntermBlock *node)
{
// We traverse blocks in reverse order. This way reference counts can be decremented when
// removing initializers, and variables that become unused when initializers are removed can be
// removed on the same traversal.
ScopedNodeInTraversalPath addToPath(this, node);
bool visit = true;
TIntermSequence *sequence = node->getSequence();
if (preVisit)
visit = visitBlock(PreVisit, node);
if (visit)
{
for (auto iter = sequence->rbegin(); iter != sequence->rend(); ++iter)
{
(*iter)->traverse(this);
if (visit && inVisit)
{
if ((iter + 1) != sequence->rend())
visit = visitBlock(InVisit, node);
}
}
}
if (visit && postVisit)
visitBlock(PostVisit, node);
}
void RemoveUnreferencedVariablesTraverser::traverseLoop(TIntermLoop *node)
{
// We traverse loops in reverse order as well. The loop body gets traversed before the init
// node.
ScopedNodeInTraversalPath addToPath(this, node);
bool visit = true;
if (preVisit)
visit = visitLoop(PreVisit, node);
if (visit)
{
// We don't need to traverse loop expressions or conditions since they can't be declarations
// in the AST (loops which have a declaration in their condition get transformed in the
// parsing stage).
ASSERT(node->getExpression() == nullptr ||
node->getExpression()->getAsDeclarationNode() == nullptr);
ASSERT(node->getCondition() == nullptr ||
node->getCondition()->getAsDeclarationNode() == nullptr);
if (node->getBody())
node->getBody()->traverse(this);
if (node->getInit())
node->getInit()->traverse(this);
}
if (visit && postVisit)
visitLoop(PostVisit, node);
}
} // namespace
void RemoveUnreferencedVariables(TIntermBlock *root, TSymbolTable *symbolTable)
{
CollectVariableRefCountsTraverser collector;
root->traverse(&collector);
RemoveUnreferencedVariablesTraverser traverser(&collector.getSymbolIdRefCounts(), symbolTable);
root->traverse(&traverser);
traverser.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.
//
// RemoveUnreferencedVariables.h:
// Drop variables that are declared but never referenced in the AST. This avoids adding unnecessary
// initialization code for them.
//
#ifndef COMPILER_TRANSLATOR_REMOVEUNREFERENCEDVARIABLES_H_
#define COMPILER_TRANSLATOR_REMOVEUNREFERENCEDVARIABLES_H_
namespace sh
{
class TIntermBlock;
class TSymbolTable;
void RemoveUnreferencedVariables(TIntermBlock *root, TSymbolTable *symbolTable);
} // namespace sh
#endif // COMPILER_TRANSLATOR_REMOVEUNREFERENCEDVARIABLES_H_
......@@ -79,6 +79,7 @@
'<(angle_path)/src/tests/compiler_tests/QualificationOrder_test.cpp',
'<(angle_path)/src/tests/compiler_tests/RecordConstantPrecision_test.cpp',
'<(angle_path)/src/tests/compiler_tests/RemovePow_test.cpp',
'<(angle_path)/src/tests/compiler_tests/RemoveUnreferencedVariables_test.cpp',
'<(angle_path)/src/tests/compiler_tests/RewriteDoWhile_test.cpp',
'<(angle_path)/src/tests/compiler_tests/SamplerMultisample_test.cpp',
'<(angle_path)/src/tests/compiler_tests/ShaderExtension_test.cpp',
......
//
// 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.
//
// RemoveUnreferencedVariables_test.cpp:
// Tests for removing unreferenced variables from the AST.
//
#include "GLSLANG/ShaderLang.h"
#include "angle_gl.h"
#include "gtest/gtest.h"
#include "tests/test_utils/compiler_test.h"
using namespace sh;
class RemoveUnreferencedVariablesTest : public MatchOutputCodeTest
{
public:
RemoveUnreferencedVariablesTest() : MatchOutputCodeTest(GL_FRAGMENT_SHADER, 0, SH_ESSL_OUTPUT)
{
}
};
// Test that a simple unreferenced declaration is pruned.
TEST_F(RemoveUnreferencedVariablesTest, SimpleDeclaration)
{
const std::string &shaderString =
R"(precision mediump float;
void main()
{
vec4 myUnreferencedVec;
})";
compile(shaderString);
ASSERT_TRUE(notFoundInCode("myUnreferencedVec"));
}
// Test that a simple unreferenced global declaration is pruned.
TEST_F(RemoveUnreferencedVariablesTest, SimpleGlobalDeclaration)
{
const std::string &shaderString =
R"(precision mediump float;
vec4 myUnreferencedVec;
void main()
{
})";
compile(shaderString);
ASSERT_TRUE(notFoundInCode("myUnreferencedVec"));
}
// Test that a simple unreferenced variable with an initializer is pruned.
TEST_F(RemoveUnreferencedVariablesTest, SimpleInitializer)
{
const std::string &shaderString =
R"(precision mediump float;
uniform vec4 uVec;
void main()
{
vec4 myUnreferencedVec = uVec;
})";
compile(shaderString);
ASSERT_TRUE(notFoundInCode("myUnreferencedVec"));
}
// Test that a user-defined function call inside an unreferenced variable initializer is retained.
TEST_F(RemoveUnreferencedVariablesTest, SideEffectInInitializer)
{
const std::string &shaderString =
R"(precision mediump float;
vec4 sideEffect(int i)
{
gl_FragColor = vec4(0, i, 0, 1);
return vec4(0);
}
void main()
{
vec4 myUnreferencedVec = sideEffect(1);
})";
compile(shaderString);
// We're happy as long as the function with side effects is called.
ASSERT_TRUE(foundInCode("sideEffect(1)"));
}
// Test that a modf call inside an unreferenced variable initializer is retained.
TEST_F(RemoveUnreferencedVariablesTest, BuiltInSideEffectInInitializer)
{
const std::string &shaderString =
R"(#version 300 es
precision mediump float;
uniform float uF;
out vec4 my_FragColor;
void main()
{
float iPart = 0.0;
float myUnreferencedFloat = modf(uF, iPart);
my_FragColor = vec4(0.0, iPart, 0.0, 1.0);
})";
compile(shaderString);
// We're happy as long as the function with side effects is called.
ASSERT_TRUE(foundInCode("modf("));
}
// Test that an imageStore call inside an unreferenced variable initializer is retained.
TEST_F(RemoveUnreferencedVariablesTest, ImageStoreSideEffectInInitializer)
{
const std::string &shaderString =
R"(#version 310 es
precision highp float;
layout(rgba32i) uniform highp writeonly iimage2D img;
void main()
{
float myUnreferencedFloat = (imageStore(img, ivec2(0), ivec4(1)), 1.0);
})";
compile(shaderString);
// We're happy as long as the function with side effects is called.
ASSERT_TRUE(foundInCode("imageStore("));
}
// Test that multiple variables that are chained but otherwise are unreferenced are removed.
TEST_F(RemoveUnreferencedVariablesTest, MultipleVariablesChained)
{
const std::string &shaderString =
R"(precision mediump float;
uniform vec4 uVec;
void main()
{
vec4 myUnreferencedVec1 = uVec;
vec4 myUnreferencedVec2 = myUnreferencedVec1 * 2.0;
vec4 myUnreferencedVec3 = myUnreferencedVec2 + 1.0;
})";
compile(shaderString);
ASSERT_TRUE(notFoundInCode("myUnreferencedVec3"));
ASSERT_TRUE(notFoundInCode("myUnreferencedVec2"));
ASSERT_TRUE(notFoundInCode("myUnreferencedVec1"));
}
// Test that multiple variables that are chained with the last one being referenced are kept.
TEST_F(RemoveUnreferencedVariablesTest, MultipleVariablesChainedReferenced)
{
const std::string &shaderString =
R"(precision mediump float;
uniform vec4 uVec;
void main()
{
vec4 myReferencedVec1 = uVec;
vec4 myReferencedVec2 = myReferencedVec1 * 2.0;
vec4 myReferencedVec3 = myReferencedVec2 + 1.0;
gl_FragColor = myReferencedVec3;
})";
compile(shaderString);
ASSERT_TRUE(foundInCode("myReferencedVec3"));
ASSERT_TRUE(foundInCode("myReferencedVec2"));
ASSERT_TRUE(foundInCode("myReferencedVec1"));
}
// Test that multiple variables that are chained within two scopes but otherwise are unreferenced
// are removed.
TEST_F(RemoveUnreferencedVariablesTest, MultipleVariablesChainedTwoScopes)
{
const std::string &shaderString =
R"(precision mediump float;
uniform vec4 uVec;
void main()
{
vec4 myUnreferencedVec1 = uVec;
vec4 myUnreferencedVec2 = myUnreferencedVec1 * 2.0;
if (uVec.x > 0.0)
{
vec4 myUnreferencedVec3 = myUnreferencedVec2 + 1.0;
}
})";
compile(shaderString);
ASSERT_TRUE(notFoundInCode("myUnreferencedVec3"));
ASSERT_TRUE(notFoundInCode("myUnreferencedVec2"));
ASSERT_TRUE(notFoundInCode("myUnreferencedVec1"));
}
// Test that multiple variables that are chained with the last one being referenced in an inner
// scope are kept.
TEST_F(RemoveUnreferencedVariablesTest, VariableReferencedInAnotherScope)
{
const std::string &shaderString =
R"(precision mediump float;
uniform vec4 uVec;
void main()
{
vec4 myReferencedVec1 = uVec;
vec4 myReferencedVec2 = myReferencedVec1 * 2.0;
if (uVec.x > 0.0)
{
vec4 myReferencedVec3 = myReferencedVec2 + 1.0;
gl_FragColor = myReferencedVec3;
}
})";
compile(shaderString);
ASSERT_TRUE(foundInCode("myReferencedVec3"));
ASSERT_TRUE(foundInCode("myReferencedVec2"));
ASSERT_TRUE(foundInCode("myReferencedVec1"));
}
// Test that if there are two variables with the same name, one of them can be removed and another
// one kept.
TEST_F(RemoveUnreferencedVariablesTest, TwoVariablesWithSameNameInDifferentScopes)
{
const std::string &shaderString =
R"(precision mediump float;
uniform vec4 uVec;
void main()
{
vec4 myVec = uVec; // This one is unreferenced.
if (uVec.x > 0.0)
{
vec4 myVec = uVec * 2.0; // This one is referenced.
gl_FragColor = myVec;
}
vec4 myUnreferencedVec = myVec;
})";
compile(shaderString);
ASSERT_TRUE(foundInCode("myVec", 2));
}
// Test that an unreferenced variable declared in a for loop header is removed.
TEST_F(RemoveUnreferencedVariablesTest, UnreferencedVariableDeclaredInForLoopHeader)
{
const std::string &shaderString =
R"(#version 300 es
precision highp float;
uniform int ui;
out vec4 my_FragColor;
void main()
{
my_FragColor = vec4(0.0);
int index = 0;
for (int unreferencedInt = ui; index < 10; ++index)
{
my_FragColor += vec4(0.0, float(index) * 0.01, 0.0, 0.0);
}
})";
compile(shaderString);
ASSERT_TRUE(foundInCode("index"));
ASSERT_TRUE(notFoundInCode("unreferencedInt"));
}
// Test that a loop condition is kept even if it declares an unreferenced variable.
TEST_F(RemoveUnreferencedVariablesTest, UnreferencedVariableDeclaredInWhileLoopCondition)
{
const std::string &shaderString =
R"(#version 300 es
precision highp float;
uniform int ui;
out vec4 my_FragColor;
void main()
{
my_FragColor = vec4(0.0);
int index = 0;
while (bool b = (index < 10))
{
my_FragColor += vec4(0.0, float(index) * 0.01, 0.0, 0.0);
++index;
}
})";
compile(shaderString);
ASSERT_TRUE(foundInCode("index < 10"));
}
// Test that a variable declared in a for loop header that is only referenced in an unreferenced
// variable initializer is removed.
TEST_F(RemoveUnreferencedVariablesTest,
VariableDeclaredInForLoopHeaderAccessedInUnreferencedVariableInitializer)
{
const std::string &shaderString =
R"(#version 300 es
precision highp float;
uniform int ui;
out vec4 my_FragColor;
void main()
{
my_FragColor = vec4(0.0);
int index = 0;
for (int unreferencedInt1 = ui; index < 10; ++index)
{
int unreferencedInt2 = unreferencedInt1;
my_FragColor += vec4(0.0, float(index) * 0.01, 0.0, 0.0);
}
})";
compile(shaderString);
ASSERT_TRUE(foundInCode("index"));
ASSERT_TRUE(notFoundInCode("unreferencedInt2"));
ASSERT_TRUE(notFoundInCode("unreferencedInt1"));
}
// Test that a user-defined type (struct) declaration that's used is not removed, but that the
// variable that's declared in the same declaration is removed.
TEST_F(RemoveUnreferencedVariablesTest, UserDefinedTypeReferencedAndVariableNotReferenced)
{
const std::string &shaderString =
R"(#version 300 es
precision highp float;
uniform float uF;
out vec4 my_FragColor;
void main()
{
struct myStruct { float member; } unreferencedStruct;
myStruct usedStruct = myStruct(uF);
my_FragColor = vec4(usedStruct.member);
})";
compile(shaderString);
ASSERT_TRUE(foundInCode("myStruct"));
ASSERT_TRUE(foundInCode("usedStruct"));
ASSERT_TRUE(notFoundInCode("unreferencedStruct"));
}
// Test that a nameless user-defined type (struct) declaration is removed entirely.
TEST_F(RemoveUnreferencedVariablesTest, NamelessUserDefinedTypeUnreferenced)
{
const std::string &shaderString =
R"(#version 300 es
precision highp float;
void main()
{
struct { float member; } unreferencedStruct;
})";
compile(shaderString);
ASSERT_TRUE(notFoundInCode("unreferencedStruct"));
ASSERT_TRUE(notFoundInCode("member"));
}
// Test that a variable that's only referenced in a unused function is removed.
TEST_F(RemoveUnreferencedVariablesTest, VariableOnlyReferencedInUnusedFunction)
{
const std::string &shaderString =
R"(
int onlyReferencedInUnusedFunction = 0;
void unusedFunc() {
onlyReferencedInUnusedFunction++;
}
void main()
{
})";
compile(shaderString);
ASSERT_TRUE(notFoundInCode("onlyReferencedInUnusedFunction"));
}
// Test that a variable that's only referenced in an array length() method call is removed.
TEST_F(RemoveUnreferencedVariablesTest, VariableOnlyReferencedInLengthMethod)
{
const std::string &shaderString =
R"(#version 300 es
precision highp float;
out vec4 my_FragColor;
void main()
{
float onlyReferencedInLengthMethodCall[1];
int len = onlyReferencedInLengthMethodCall.length();
my_FragColor = vec4(0, len, 0, 1);
})";
compile(shaderString);
ASSERT_TRUE(notFoundInCode("onlyReferencedInLengthMethodCall"));
}
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