Add a compiler option to prune unused function and prototypes

Also adds a simple unit test checking the pruning

BUG=angleproject:937
BUG=395048

Change-Id: I88440378f66178dcebebcd596f8f80235903f20e
Reviewed-on: https://chromium-review.googlesource.com/264568
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Tested-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/compiler/translator/Compiler.cpp b/src/compiler/translator/Compiler.cpp
index 8c5e50f..ad3dfc5 100644
--- a/src/compiler/translator/Compiler.cpp
+++ b/src/compiler/translator/Compiler.cpp
@@ -246,6 +246,9 @@
             success = tagUsedFunctions();
         }
 
+        if (success && !(compileOptions & SH_DONT_PRUNE_UNUSED_FUNCTIONS))
+            success = pruneUnusedFunctions(root);
+
         if (success && shaderVersion == 300 && shaderType == GL_FRAGMENT_SHADER)
             success = validateOutputs(root);
 
@@ -572,6 +575,59 @@
     }
 }
 
+// A predicate for the stl that returns if a top-level node is unused
+class TCompiler::UnusedPredicate
+{
+  public:
+    UnusedPredicate(const CallDAG *callDag, const std::vector<FunctionMetadata> *metadatas)
+        : mCallDag(callDag),
+          mMetadatas(metadatas)
+    {
+    }
+
+    bool operator ()(TIntermNode *node)
+    {
+        const TIntermAggregate *asAggregate = node->getAsAggregate();
+
+        if (asAggregate == nullptr)
+        {
+            return false;
+        }
+
+        if (!(asAggregate->getOp() == EOpFunction || asAggregate->getOp() == EOpPrototype))
+        {
+            return false;
+        }
+
+        size_t callDagIndex = mCallDag->findIndex(asAggregate);
+        if (callDagIndex == CallDAG::InvalidIndex)
+        {
+            // This happens only for unimplemented prototypes which are thus unused
+            ASSERT(asAggregate->getOp() == EOpPrototype);
+            return true;
+        }
+
+        ASSERT(callDagIndex < mMetadatas->size());
+        return !(*mMetadatas)[callDagIndex].used;
+    }
+
+  private:
+    const CallDAG *mCallDag;
+    const std::vector<FunctionMetadata> *mMetadatas;
+};
+
+bool TCompiler::pruneUnusedFunctions(TIntermNode *root)
+{
+    TIntermAggregate *rootNode = root->getAsAggregate();
+    ASSERT(rootNode != nullptr);
+
+    UnusedPredicate isUnused(&mCallDag, &functionMetadata);
+    TIntermSequence *sequence = rootNode->getSequence();
+    sequence->erase(std::remove_if(sequence->begin(), sequence->end(), isUnused), sequence->end());
+
+    return true;
+}
+
 bool TCompiler::validateOutputs(TIntermNode* root)
 {
     ValidateOutputs validateOutputs(infoSink.info, compileResources.MaxDrawBuffers);
diff --git a/src/compiler/translator/Compiler.h b/src/compiler/translator/Compiler.h
index 6b7dfe6..eda856b 100644
--- a/src/compiler/translator/Compiler.h
+++ b/src/compiler/translator/Compiler.h
@@ -165,6 +165,10 @@
     bool tagUsedFunctions();
     void internalTagUsedFunction(size_t index);
 
+    // Removes unused function declarations and prototypes from the AST
+    class UnusedPredicate;
+    bool pruneUnusedFunctions(TIntermNode *root);
+
     TIntermNode *compileTreeImpl(const char* const shaderStrings[],
         size_t numStrings, int compileOptions);
 
diff --git a/src/tests/angle_unittests.gypi b/src/tests/angle_unittests.gypi
index 3d0e49a..e5250a8 100644
--- a/src/tests/angle_unittests.gypi
+++ b/src/tests/angle_unittests.gypi
@@ -30,8 +30,9 @@
             '<(angle_path)/src/tests/compiler_tests/DebugShaderPrecision_test.cpp',
             '<(angle_path)/src/tests/compiler_tests/ExpressionLimit_test.cpp',
             '<(angle_path)/src/tests/compiler_tests/MalformedShader_test.cpp',
-            '<(angle_path)/src/tests/compiler_tests/ShaderExtension_test.cpp',
             '<(angle_path)/src/tests/compiler_tests/NV_draw_buffers_test.cpp',
+            '<(angle_path)/src/tests/compiler_tests/PruneUnusedFunctions_test.cpp',
+            '<(angle_path)/src/tests/compiler_tests/ShaderExtension_test.cpp',
             '<(angle_path)/src/tests/compiler_tests/ShaderVariable_test.cpp',
             '<(angle_path)/src/tests/compiler_tests/TypeTracking_test.cpp',
             '<(angle_path)/src/tests/preprocessor_tests/char_test.cpp',
diff --git a/src/tests/compiler_tests/PruneUnusedFunctions_test.cpp b/src/tests/compiler_tests/PruneUnusedFunctions_test.cpp
new file mode 100644
index 0000000..b5d7d52
--- /dev/null
+++ b/src/tests/compiler_tests/PruneUnusedFunctions_test.cpp
@@ -0,0 +1,137 @@
+//
+// Copyright (c) 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.
+//
+// PruneUnusedFunctions_test.cpp:
+//   Test for the pruning of unused function with the SH_PRUNE_UNUSED_FUNCTIONS compile flag
+//
+
+#include "angle_gl.h"
+#include "gtest/gtest.h"
+#include "GLSLANG/ShaderLang.h"
+#include "compiler/translator/TranslatorESSL.h"
+
+namespace
+{
+
+class PruneUnusedFunctionsTest : public testing::Test
+{
+  public:
+    PruneUnusedFunctionsTest() {}
+
+  protected:
+    void SetUp() override
+    {
+        ShBuiltInResources resources;
+        ShInitBuiltInResources(&resources);
+        resources.FragmentPrecisionHigh = 1;
+
+        mTranslator = new TranslatorESSL(GL_FRAGMENT_SHADER, SH_GLES3_SPEC);
+        ASSERT_TRUE(mTranslator->Init(resources));
+    }
+
+    void TearDown() override
+    {
+        SafeDelete(mTranslator);
+    }
+
+    void compile(const std::string &shaderString, bool prune)
+    {
+        const char *shaderStrings[] = { shaderString.c_str() };
+        int compilationFlags = SH_VARIABLES | SH_OBJECT_CODE | (prune ? 0 : SH_DONT_PRUNE_UNUSED_FUNCTIONS);
+        bool compilationSuccess = mTranslator->compile(shaderStrings, 1, compilationFlags);
+        TInfoSink &infoSink = mTranslator->getInfoSink();
+        if (!compilationSuccess)
+        {
+            FAIL() << "Shader compilation failed " << infoSink.info.str();
+        }
+        mTranslatedSource = infoSink.obj.str();
+    }
+
+    bool kept(const char *functionName, int nOccurences) const
+    {
+        size_t currentPos = 0;
+        while (nOccurences-- > 0)
+        {
+            auto position = mTranslatedSource.find(functionName, currentPos);
+            if (position == std::string::npos)
+            {
+                return false;
+            }
+            currentPos = position + 1;
+        }
+        return mTranslatedSource.find(functionName, currentPos) == std::string::npos;
+    }
+
+    bool removed(const char* functionName) const
+    {
+        return mTranslatedSource.find(functionName) == std::string::npos;
+    }
+
+  private:
+    TranslatorESSL *mTranslator;
+    std::string mTranslatedSource;
+};
+
+// Check that unused function and prototypes are removed iff the options is set
+TEST_F(PruneUnusedFunctionsTest, UnusedFunctionAndProto)
+{
+    const std::string &shaderString =
+        "precision mediump float;\n"
+        "float unused(float a);\n"
+        "void main() {\n"
+        "    gl_FragColor = vec4(1.0);\n"
+        "}\n"
+        "float unused(float a) {\n"
+        "    return a;\n"
+        "}\n";
+    compile(shaderString, true);
+    EXPECT_TRUE(removed("unused("));
+    EXPECT_TRUE(kept("main(", 1));
+
+    compile(shaderString, false);
+    EXPECT_TRUE(kept("unused(", 2));
+    EXPECT_TRUE(kept("main(", 1));
+}
+
+// Check that unimplemented prototypes are removed iff the options is set
+TEST_F(PruneUnusedFunctionsTest, UnimplementedPrototype)
+{
+    const std::string &shaderString =
+        "precision mediump float;\n"
+        "float unused(float a);\n"
+        "void main() {\n"
+        "    gl_FragColor = vec4(1.0);\n"
+        "}\n";
+    compile(shaderString, true);
+    EXPECT_TRUE(removed("unused("));
+    EXPECT_TRUE(kept("main(", 1));
+
+    compile(shaderString, false);
+    EXPECT_TRUE(kept("unused(", 1));
+    EXPECT_TRUE(kept("main(", 1));
+}
+
+// Check that used functions are not prunued (duh)
+TEST_F(PruneUnusedFunctionsTest, UsedFunction)
+{
+    const std::string &shaderString =
+        "precision mediump float;\n"
+        "float used(float a);\n"
+        "void main() {\n"
+        "    gl_FragColor = vec4(used(1.0));\n"
+        "}\n"
+        "float used(float a) {\n"
+        "    return a;\n"
+        "}\n";
+    compile(shaderString, true);
+    EXPECT_TRUE(kept("used(", 3));
+    EXPECT_TRUE(kept("main(", 1));
+
+    compile(shaderString, false);
+    EXPECT_TRUE(kept("used(", 3));
+    EXPECT_TRUE(kept("main(", 1));
+}
+
+}