//
// 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"));
}

// Test that an unreferenced user-defined type is removed.
TEST_F(RemoveUnreferencedVariablesTest, UserDefinedTypeUnreferenced)
{
    const std::string &shaderString =
        R"(
        struct myStructType
        {
            int i;
        } myStructVariable;

        void main()
        {
        })";
    compile(shaderString);

    ASSERT_TRUE(notFoundInCode("myStructType"));
}

// Test that a user-defined type that's only referenced in an unreferenced variable is removed.
TEST_F(RemoveUnreferencedVariablesTest, UserDefinedTypeReferencedInUnreferencedVariable)
{
    const std::string &shaderString =
        R"(
        struct myStructType
        {
            int i;
        };

        void main()
        {
            myStructType myStructVariable;
        })";
    compile(shaderString);

    ASSERT_TRUE(notFoundInCode("myStructType"));
}

// Test that a user-defined type that's declared in an empty declaration and that is only referenced
// in an unreferenced variable is removed also when the shader contains another independent
// user-defined type that's declared in an empty declaration. This tests special case handling of
// reference counting of empty symbols.
TEST_F(RemoveUnreferencedVariablesTest,
       TwoUserDefinedTypesDeclaredInEmptyDeclarationsWithOneOfThemUnreferenced)
{
    const std::string &shaderString =
        R"(
        struct myStructTypeA
        {
            int i;
        };

        struct myStructTypeB
        {
            int j;
        };

        uniform myStructTypeB myStructVariableB;

        void main()
        {
            myStructTypeA myStructVariableA;
        })";
    compile(shaderString);

    ASSERT_TRUE(notFoundInCode("myStructTypeA"));
    ASSERT_TRUE(foundInCode("myStructTypeB"));
}

// Test that a user-defined type that is only referenced in another unreferenced type is removed.
TEST_F(RemoveUnreferencedVariablesTest, UserDefinedTypeChain)
{
    const std::string &shaderString =
        R"(
        struct myInnerStructType
        {
            int i;
        };

        struct myOuterStructType
        {
            myInnerStructType inner;
        } myStructVariable;

        void main()
        {
            myOuterStructType myStructVariable2;
        })";
    compile(shaderString);

    ASSERT_TRUE(notFoundInCode("myInnerStructType"));
}

// Test that a user-defined type that is referenced in another user-defined type that is used is
// kept.
TEST_F(RemoveUnreferencedVariablesTest, UserDefinedTypeChainReferenced)
{
    const std::string &shaderString =
        R"(
        precision mediump float;

        struct myInnerStructType
        {
            int i;
        };

        uniform struct
        {
            myInnerStructType inner;
        } myStructVariable;

        void main()
        {
            if (myStructVariable.inner.i > 0)
            {
                gl_FragColor = vec4(0, 1, 0, 1);
            }
        })";
    compile(shaderString);

    ASSERT_TRUE(foundInCode("struct _umyInnerStructType"));
}

// Test that a struct type that is only referenced in a constructor and function call is kept.
TEST_F(RemoveUnreferencedVariablesTest, UserDefinedTypeReferencedInConstructorAndCall)
{
    const std::string &shaderString =
        R"(
        precision mediump float;

        uniform int ui;

        struct myStructType
        {
            int iMember;
        };

        void func(myStructType myStructParam)
        {
            if (myStructParam.iMember > 0)
            {
                gl_FragColor = vec4(0, 1, 0, 1);
            }
        }

        void main()
        {
            func(myStructType(ui));
        })";
    compile(shaderString);

    ASSERT_TRUE(foundInCode("struct _umyStructType"));
}

// Test that a struct type that is only referenced in a constructor is kept. This assumes that there
// isn't more sophisticated folding of struct field access going on.
TEST_F(RemoveUnreferencedVariablesTest, UserDefinedTypeReferencedInConstructor)
{
    const std::string &shaderString =
        R"(
        precision mediump float;

        uniform int ui;

        struct myStructType
        {
            int iMember;
        };

        void main()
        {
            if (myStructType(ui).iMember > 0)
            {
                gl_FragColor = vec4(0, 1, 0, 1);
            }
        })";
    compile(shaderString);

    ASSERT_TRUE(foundInCode("struct _umyStructType"));
}

// Test that a struct type that is only referenced in an unused function is removed.
TEST_F(RemoveUnreferencedVariablesTest, UserDefinedTypeReferencedInUnusedFunction)
{
    const std::string &shaderString =
        R"(
        precision mediump float;

        struct myStructType
        {
            int iMember;
        };

        void func(myStructType myStructParam)
        {
            if (myStructParam.iMember > 0)
            {
                gl_FragColor = vec4(0, 1, 0, 1);
            }
        }

        void main()
        {
        })";
    compile(shaderString);

    ASSERT_TRUE(notFoundInCode("myStructType"));
}

// Test that a struct type that is only referenced in an unused function is kept in case
// SH_DONT_PRUNE_UNUSED_FUNCTIONS is specified.
TEST_F(RemoveUnreferencedVariablesTest, UserDefinedTypeReferencedInUnusedFunctionThatIsNotPruned)
{
    const std::string &shaderString =
        R"(
        struct myStructType
        {
            int iMember;
        };

        myStructType func()
        {
            return myStructType(0);
        }

        void main()
        {
        })";
    compile(shaderString, SH_DONT_PRUNE_UNUSED_FUNCTIONS);

    ASSERT_TRUE(foundInCode("struct _umyStructType"));

    // Ensure that the struct isn't declared as a part of the function header.
    ASSERT_TRUE(foundInCode("};"));
}

// Test that a struct type that is only referenced as a function return value is kept.
TEST_F(RemoveUnreferencedVariablesTest, UserDefinedTypeReturnedFromFunction)
{
    const std::string &shaderString =
        R"(
        precision mediump float;

        struct myStructType
        {
            int iMember;
        };

        myStructType func()
        {
            gl_FragColor = vec4(0, 1, 0, 1);
            return myStructType(0);
        }

        void main()
        {
            func();
        })";
    compile(shaderString);

    ASSERT_TRUE(foundInCode("struct _umyStructType"));

    // Ensure that the struct isn't declared as a part of the function header.
    ASSERT_TRUE(foundInCode("};"));
}

// Test that a struct type that is only referenced in a uniform block is kept.
TEST_F(RemoveUnreferencedVariablesTest, UserDefinedTypeInUniformBlock)
{
    const std::string &shaderString =
        R"(#version 300 es

        precision highp float;
        out vec4 my_FragColor;

        struct myStructType
        {
            int iMember;
        };

        layout(std140) uniform myBlock {
            myStructType uStruct;
            int ui;
        };

        void main()
        {
            if (ui > 0)
            {
                my_FragColor = vec4(0, 1, 0, 1);
            }
        })";
    compile(shaderString);

    ASSERT_TRUE(foundInCode("struct _umyStructType"));
}

// Test that a struct type that is referenced from an initializer with a constructor can be removed.
TEST_F(RemoveUnreferencedVariablesTest, UserDefinedTypeConstructorInitializer)
{
    const std::string &shaderString =
        R"(#version 300 es

        precision highp float;
        out vec4 my_FragColor;

        struct myStructType
        {
            int iMember;
        };

        uniform int ui;

        void main()
        {
            myStructType S = myStructType(ui);
            my_FragColor = vec4(0, 1, 0, 1);
        })";
    compile(shaderString);

    ASSERT_TRUE(notFoundInCode("myStructType"));
}
