Edit

kc3-lang/angle/src/tests/compiler_tests/ConstantFolding_test.cpp

Branch :

  • Show log

    Commit

  • Author : Olli Etuaho
    Date : 2018-01-04 17:09:11
    Hash : ea22b7a5
    Message : Constant fold array indexing and comparison A virtual function to get the constant value of an AST node is added to TIntermTyped. This way a constant value can be retrieved conveniently from multiple different types of nodes. TIntermSymbol nodes pointing to a const variable can return the value associated with the variable, constructor nodes can build a constant value from their arguments, and indexing nodes can index into a constant array. This enables constant folding operations on constant arrays, while making sure that large amounts of data are not duplicated in the output shader. When folding an operation makes sense, the values of the arguments can be retrieved by using the new TIntermTyped::getConstantValue(). When folding an operation would result in duplicating data, the AST can just be left to be written out as is. For example, if the code contains a constant array of arrays, indexing into individual elements of the inner arrays can be folded, but indexing the top level array is left in place and not replaced with duplicated array literals. Constant folding is supported for indexing and comparisons of arrays. In case constant arrays are only referenced through foldable operations, the variable declarations will be pruned from the AST by the RemoveUnreferencedVariables step. BUG=angleproject:2298 TEST=angle_unittests Change-Id: I5b3be237b7e9fdba56aa9bf0a41b691f4d8f01eb Reviewed-on: https://chromium-review.googlesource.com/850973 Reviewed-by: Geoff Lang <geofflang@chromium.org> Commit-Queue: Olli Etuaho <oetuaho@nvidia.com>

  • src/tests/compiler_tests/ConstantFolding_test.cpp
  • //
    // 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.
    //
    // ConstantFolding_test.cpp:
    //   Tests for constant folding
    //
    
    #include "tests/test_utils/ConstantFoldingTest.h"
    
    using namespace sh;
    
    // Test that zero, true or false are not found in AST when they are not expected. This is to make
    // sure that the subsequent tests run correctly.
    TEST_F(ConstantFoldingExpressionTest, FoldFloatTestSanityCheck)
    {
        const std::string &floatString = "1.0";
        evaluateFloat(floatString);
        ASSERT_FALSE(constantFoundInAST(0.0f));
        ASSERT_FALSE(constantFoundInAST(true));
        ASSERT_FALSE(constantFoundInAST(false));
    }
    
    TEST_F(ConstantFoldingTest, FoldIntegerAdd)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out int my_Int;\n"
            "void main() {\n"
            "   const int i = 1124 + 5;\n"
            "   my_Int = i;\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        ASSERT_FALSE(constantFoundInAST(1124));
        ASSERT_FALSE(constantFoundInAST(5));
        ASSERT_TRUE(constantFoundInAST(1129));
    }
    
    TEST_F(ConstantFoldingTest, FoldIntegerSub)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out int my_Int;\n"
            "void main() {\n"
            "   const int i = 1124 - 5;\n"
            "   my_Int = i;\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        ASSERT_FALSE(constantFoundInAST(1124));
        ASSERT_FALSE(constantFoundInAST(5));
        ASSERT_TRUE(constantFoundInAST(1119));
    }
    
    TEST_F(ConstantFoldingTest, FoldIntegerMul)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out int my_Int;\n"
            "void main() {\n"
            "   const int i = 1124 * 5;\n"
            "   my_Int = i;\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        ASSERT_FALSE(constantFoundInAST(1124));
        ASSERT_FALSE(constantFoundInAST(5));
        ASSERT_TRUE(constantFoundInAST(5620));
    }
    
    TEST_F(ConstantFoldingTest, FoldIntegerDiv)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out int my_Int;\n"
            "void main() {\n"
            "   const int i = 1124 / 5;\n"
            "   my_Int = i;\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        ASSERT_FALSE(constantFoundInAST(1124));
        ASSERT_FALSE(constantFoundInAST(5));
        // Rounding mode of division is undefined in the spec but ANGLE can be expected to round down.
        ASSERT_TRUE(constantFoundInAST(224));
    }
    
    TEST_F(ConstantFoldingTest, FoldIntegerModulus)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out int my_Int;\n"
            "void main() {\n"
            "   const int i = 1124 % 5;\n"
            "   my_Int = i;\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        ASSERT_FALSE(constantFoundInAST(1124));
        ASSERT_FALSE(constantFoundInAST(5));
        ASSERT_TRUE(constantFoundInAST(4));
    }
    
    TEST_F(ConstantFoldingTest, FoldVectorCrossProduct)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out vec3 my_Vec3;"
            "void main() {\n"
            "   const vec3 v3 = cross(vec3(1.0f, 1.0f, 1.0f), vec3(1.0f, -1.0f, 1.0f));\n"
            "   my_Vec3 = v3;\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        std::vector<float> input1(3, 1.0f);
        ASSERT_FALSE(constantVectorFoundInAST(input1));
        std::vector<float> input2;
        input2.push_back(1.0f);
        input2.push_back(-1.0f);
        input2.push_back(1.0f);
        ASSERT_FALSE(constantVectorFoundInAST(input2));
        std::vector<float> result;
        result.push_back(2.0f);
        result.push_back(0.0f);
        result.push_back(-2.0f);
        ASSERT_TRUE(constantVectorFoundInAST(result));
    }
    
    // FoldMxNMatrixInverse tests check if the matrix 'inverse' operation
    // on MxN matrix is constant folded when argument is constant expression and also
    // checks the correctness of the result returned by the constant folding operation.
    // All the matrices including matrices in the shader code are in column-major order.
    TEST_F(ConstantFoldingTest, Fold2x2MatrixInverse)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "in float i;\n"
            "out vec2 my_Vec;\n"
            "void main() {\n"
            "   const mat2 m2 = inverse(mat2(2.0f, 3.0f,\n"
            "                                5.0f, 7.0f));\n"
            "   mat2 m = m2 * mat2(i);\n"
            "   my_Vec = m[0];\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        float inputElements[] =
        {
            2.0f, 3.0f,
            5.0f, 7.0f
        };
        std::vector<float> input(inputElements, inputElements + 4);
        ASSERT_FALSE(constantColumnMajorMatrixFoundInAST(input));
        float outputElements[] =
        {
            -7.0f, 3.0f,
            5.0f, -2.0f
        };
        std::vector<float> result(outputElements, outputElements + 4);
        ASSERT_TRUE(constantColumnMajorMatrixFoundInAST(result));
    }
    
    // Check if the matrix 'inverse' operation on 3x3 matrix is constant folded.
    TEST_F(ConstantFoldingTest, Fold3x3MatrixInverse)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "in float i;\n"
            "out vec3 my_Vec;\n"
            "void main() {\n"
            "   const mat3 m3 = inverse(mat3(11.0f, 13.0f, 19.0f,\n"
            "                                23.0f, 29.0f, 31.0f,\n"
            "                                37.0f, 41.0f, 43.0f));\n"
            "   mat3 m = m3 * mat3(i);\n"
            "   my_Vec = m[0];\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        float inputElements[] =
        {
            11.0f, 13.0f, 19.0f,
            23.0f, 29.0f, 31.0f,
            37.0f, 41.0f, 43.0f
        };
        std::vector<float> input(inputElements, inputElements + 9);
        ASSERT_FALSE(constantVectorFoundInAST(input));
        float outputElements[] =
        {
            3.0f / 85.0f, -11.0f / 34.0f, 37.0f / 170.0f,
            -79.0f / 340.0f, 23.0f / 68.0f, -12.0f / 85.0f,
            13.0f / 68.0f, -3.0f / 68.0f, -1.0f / 34.0f
        };
        std::vector<float> result(outputElements, outputElements + 9);
        const float floatFaultTolerance = 0.000001f;
        ASSERT_TRUE(constantVectorNearFoundInAST(result, floatFaultTolerance));
    }
    
    // Check if the matrix 'inverse' operation on 4x4 matrix is constant folded.
    TEST_F(ConstantFoldingTest, Fold4x4MatrixInverse)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "in float i;\n"
            "out vec4 my_Vec;\n"
            "void main() {\n"
            "   const mat4 m4 = inverse(mat4(29.0f, 31.0f, 37.0f, 41.0f,\n"
            "                                43.0f, 47.0f, 53.0f, 59.0f,\n"
            "                                61.0f, 67.0f, 71.0f, 73.0f,\n"
            "                                79.0f, 83.0f, 89.0f, 97.0f));\n"
            "   mat4 m = m4 * mat4(i);\n"
            "   my_Vec = m[0];\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        float inputElements[] =
        {
            29.0f, 31.0f, 37.0f, 41.0f,
            43.0f, 47.0f, 53.0f, 59.0f,
            61.0f, 67.0f, 71.0f, 73.0f,
            79.0f, 83.0f, 89.0f, 97.0f
        };
        std::vector<float> input(inputElements, inputElements + 16);
        ASSERT_FALSE(constantVectorFoundInAST(input));
        float outputElements[] =
        {
            43.0f / 126.0f, -11.0f / 21.0f, -2.0f / 21.0f, 31.0f / 126.0f,
            -5.0f / 7.0f, 9.0f / 14.0f, 1.0f / 14.0f, -1.0f / 7.0f,
            85.0f / 126.0f, -11.0f / 21.0f, 43.0f / 210.0f, -38.0f / 315.0f,
            -2.0f / 7.0f, 5.0f / 14.0f, -6.0f / 35.0f, 3.0f / 70.0f
        };
        std::vector<float> result(outputElements, outputElements + 16);
        const float floatFaultTolerance = 0.00001f;
        ASSERT_TRUE(constantVectorNearFoundInAST(result, floatFaultTolerance));
    }
    
    // FoldMxNMatrixDeterminant tests check if the matrix 'determinant' operation
    // on MxN matrix is constant folded when argument is constant expression and also
    // checks the correctness of the result returned by the constant folding operation.
    // All the matrices including matrices in the shader code are in column-major order.
    TEST_F(ConstantFoldingTest, Fold2x2MatrixDeterminant)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out float my_Float;"
            "void main() {\n"
            "   const float f = determinant(mat2(2.0f, 3.0f,\n"
            "                                    5.0f, 7.0f));\n"
            "   my_Float = f;\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        float inputElements[] =
        {
            2.0f, 3.0f,
            5.0f, 7.0f
        };
        std::vector<float> input(inputElements, inputElements + 4);
        ASSERT_FALSE(constantColumnMajorMatrixFoundInAST(input));
        ASSERT_TRUE(constantFoundInAST(-1.0f));
    }
    
    // Check if the matrix 'determinant' operation on 3x3 matrix is constant folded.
    TEST_F(ConstantFoldingTest, Fold3x3MatrixDeterminant)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out float my_Float;"
            "void main() {\n"
            "   const float f = determinant(mat3(11.0f, 13.0f, 19.0f,\n"
                 "                               23.0f, 29.0f, 31.0f,\n"
            "                                    37.0f, 41.0f, 43.0f));\n"
            "   my_Float = f;\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        float inputElements[] =
        {
            11.0f, 13.0f, 19.0f,
            23.0f, 29.0f, 31.0f,
            37.0f, 41.0f, 43.0f
        };
        std::vector<float> input(inputElements, inputElements + 9);
        ASSERT_FALSE(constantColumnMajorMatrixFoundInAST(input));
        ASSERT_TRUE(constantFoundInAST(-680.0f));
    }
    
    // Check if the matrix 'determinant' operation on 4x4 matrix is constant folded.
    TEST_F(ConstantFoldingTest, Fold4x4MatrixDeterminant)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out float my_Float;"
            "void main() {\n"
            "   const float f = determinant(mat4(29.0f, 31.0f, 37.0f, 41.0f,\n"
            "                                    43.0f, 47.0f, 53.0f, 59.0f,\n"
            "                                    61.0f, 67.0f, 71.0f, 73.0f,\n"
            "                                    79.0f, 83.0f, 89.0f, 97.0f));\n"
            "   my_Float = f;\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        float inputElements[] =
        {
            29.0f, 31.0f, 37.0f, 41.0f,
            43.0f, 47.0f, 53.0f, 59.0f,
            61.0f, 67.0f, 71.0f, 73.0f,
            79.0f, 83.0f, 89.0f, 97.0f
        };
        std::vector<float> input(inputElements, inputElements + 16);
        ASSERT_FALSE(constantColumnMajorMatrixFoundInAST(input));
        ASSERT_TRUE(constantFoundInAST(-2520.0f));
    }
    
    // Check if the matrix 'transpose' operation on 3x3 matrix is constant folded.
    // All the matrices including matrices in the shader code are in column-major order.
    TEST_F(ConstantFoldingTest, Fold3x3MatrixTranspose)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "in float i;\n"
            "out vec3 my_Vec;\n"
            "void main() {\n"
            "   const mat3 m3 = transpose(mat3(11.0f, 13.0f, 19.0f,\n"
            "                                  23.0f, 29.0f, 31.0f,\n"
            "                                  37.0f, 41.0f, 43.0f));\n"
            "   mat3 m = m3 * mat3(i);\n"
            "   my_Vec = m[0];\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        float inputElements[] =
        {
            11.0f, 13.0f, 19.0f,
            23.0f, 29.0f, 31.0f,
            37.0f, 41.0f, 43.0f
        };
        std::vector<float> input(inputElements, inputElements + 9);
        ASSERT_FALSE(constantColumnMajorMatrixFoundInAST(input));
        float outputElements[] =
        {
            11.0f, 23.0f, 37.0f,
            13.0f, 29.0f, 41.0f,
            19.0f, 31.0f, 43.0f
        };
        std::vector<float> result(outputElements, outputElements + 9);
        ASSERT_TRUE(constantColumnMajorMatrixFoundInAST(result));
    }
    
    // Test that 0xFFFFFFFF wraps to -1 when parsed as integer.
    // This is featured in the examples of ESSL3 section 4.1.3. ESSL3 section 12.42
    // means that any 32-bit unsigned integer value is a valid literal.
    TEST_F(ConstantFoldingTest, ParseWrappedHexIntLiteral)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "precision highp int;\n"
            "uniform int inInt;\n"
            "out vec4 my_Vec;\n"
            "void main() {\n"
            "   const int i = 0xFFFFFFFF;\n"
            "   my_Vec = vec4(i * inInt);\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        ASSERT_TRUE(constantFoundInAST(-1));
    }
    
    // Test that 3000000000 wraps to -1294967296 when parsed as integer.
    // This is featured in the examples of GLSL 4.5, and ESSL behavior should match
    // desktop GLSL when it comes to integer parsing.
    TEST_F(ConstantFoldingTest, ParseWrappedDecimalIntLiteral)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "precision highp int;\n"
            "uniform int inInt;\n"
            "out vec4 my_Vec;\n"
            "void main() {\n"
            "   const int i = 3000000000;\n"
            "   my_Vec = vec4(i * inInt);\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        ASSERT_TRUE(constantFoundInAST(-1294967296));
    }
    
    // Test that 0xFFFFFFFFu is parsed correctly as an unsigned integer literal.
    // This is featured in the examples of ESSL3 section 4.1.3. ESSL3 section 12.42
    // means that any 32-bit unsigned integer value is a valid literal.
    TEST_F(ConstantFoldingTest, ParseMaxUintLiteral)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "precision highp int;\n"
            "uniform uint inInt;\n"
            "out vec4 my_Vec;\n"
            "void main() {\n"
            "   const uint i = 0xFFFFFFFFu;\n"
            "   my_Vec = vec4(i * inInt);\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        ASSERT_TRUE(constantFoundInAST(0xFFFFFFFFu));
    }
    
    // Test that unary minus applied to unsigned int is constant folded correctly.
    // This is featured in the examples of ESSL3 section 4.1.3. ESSL3 section 12.42
    // means that any 32-bit unsigned integer value is a valid literal.
    TEST_F(ConstantFoldingTest, FoldUnaryMinusOnUintLiteral)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "precision highp int;\n"
            "uniform uint inInt;\n"
            "out vec4 my_Vec;\n"
            "void main() {\n"
            "   const uint i = -1u;\n"
            "   my_Vec = vec4(i * inInt);\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        ASSERT_TRUE(constantFoundInAST(0xFFFFFFFFu));
    }
    
    // Test that constant mat2 initialization with a mat2 parameter works correctly.
    TEST_F(ConstantFoldingTest, FoldMat2ConstructorTakingMat2)
    {
        const std::string &shaderString =
            "precision mediump float;\n"
            "uniform float mult;\n"
            "void main() {\n"
            "   const mat2 cm = mat2(mat2(0.0, 1.0, 2.0, 3.0));\n"
            "   mat2 m = cm * mult;\n"
            "   gl_FragColor = vec4(m[0], m[1]);\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        float outputElements[] =
        {
            0.0f, 1.0f,
            2.0f, 3.0f
        };
        std::vector<float> result(outputElements, outputElements + 4);
        ASSERT_TRUE(constantColumnMajorMatrixFoundInAST(result));
    }
    
    // Test that constant mat2 initialization with an int parameter works correctly.
    TEST_F(ConstantFoldingTest, FoldMat2ConstructorTakingScalar)
    {
        const std::string &shaderString =
            "precision mediump float;\n"
            "uniform float mult;\n"
            "void main() {\n"
            "   const mat2 cm = mat2(3);\n"
            "   mat2 m = cm * mult;\n"
            "   gl_FragColor = vec4(m[0], m[1]);\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        float outputElements[] =
        {
            3.0f, 0.0f,
            0.0f, 3.0f
        };
        std::vector<float> result(outputElements, outputElements + 4);
        ASSERT_TRUE(constantColumnMajorMatrixFoundInAST(result));
    }
    
    // Test that constant mat2 initialization with a mix of parameters works correctly.
    TEST_F(ConstantFoldingTest, FoldMat2ConstructorTakingMix)
    {
        const std::string &shaderString =
            "precision mediump float;\n"
            "uniform float mult;\n"
            "void main() {\n"
            "   const mat2 cm = mat2(-1, vec2(0.0, 1.0), vec4(2.0));\n"
            "   mat2 m = cm * mult;\n"
            "   gl_FragColor = vec4(m[0], m[1]);\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        float outputElements[] =
        {
            -1.0, 0.0f,
            1.0f, 2.0f
        };
        std::vector<float> result(outputElements, outputElements + 4);
        ASSERT_TRUE(constantColumnMajorMatrixFoundInAST(result));
    }
    
    // Test that constant mat2 initialization with a mat3 parameter works correctly.
    TEST_F(ConstantFoldingTest, FoldMat2ConstructorTakingMat3)
    {
        const std::string &shaderString =
            "precision mediump float;\n"
            "uniform float mult;\n"
            "void main() {\n"
            "   const mat2 cm = mat2(mat3(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0));\n"
            "   mat2 m = cm * mult;\n"
            "   gl_FragColor = vec4(m[0], m[1]);\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        float outputElements[] =
        {
            0.0f, 1.0f,
            3.0f, 4.0f
        };
        std::vector<float> result(outputElements, outputElements + 4);
        ASSERT_TRUE(constantColumnMajorMatrixFoundInAST(result));
    }
    
    // Test that constant mat4x3 initialization with a mat3x2 parameter works correctly.
    TEST_F(ConstantFoldingTest, FoldMat4x3ConstructorTakingMat3x2)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "uniform float mult;\n"
            "out vec4 my_FragColor;\n"
            "void main() {\n"
            "   const mat4x3 cm = mat4x3(mat3x2(1.0, 2.0,\n"
            "                                   3.0, 4.0,\n"
            "                                   5.0, 6.0));\n"
            "   mat4x3 m = cm * mult;\n"
            "   my_FragColor = vec4(m[0], m[1][0]);\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        float outputElements[] =
        {
            1.0f, 2.0f, 0.0f,
            3.0f, 4.0f, 0.0f,
            5.0f, 6.0f, 1.0f,
            0.0f, 0.0f, 0.0f
        };
        std::vector<float> result(outputElements, outputElements + 12);
        ASSERT_TRUE(constantColumnMajorMatrixFoundInAST(result));
    }
    
    
    // Test that constant mat2 initialization with a vec4 parameter works correctly.
    TEST_F(ConstantFoldingTest, FoldMat2ConstructorTakingVec4)
    {
        const std::string &shaderString =
            "precision mediump float;\n"
            "uniform float mult;\n"
            "void main() {\n"
            "   const mat2 cm = mat2(vec4(0.0, 1.0, 2.0, 3.0));\n"
            "   mat2 m = cm * mult;\n"
            "   gl_FragColor = vec4(m[0], m[1]);\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        float outputElements[] =
        {
            0.0f, 1.0f,
            2.0f, 3.0f
        };
        std::vector<float> result(outputElements, outputElements + 4);
        ASSERT_TRUE(constantColumnMajorMatrixFoundInAST(result));
    }
    
    // Test that equality comparison of two different structs with a nested struct inside returns false.
    TEST_F(ConstantFoldingTest, FoldNestedDifferentStructEqualityComparison)
    {
        const std::string &shaderString =
            "precision mediump float;\n"
            "struct nested {\n"
            "    float f\n;"
            "};\n"
            "struct S {\n"
            "    nested a;\n"
            "    float f;\n"
            "};\n"
            "uniform vec4 mult;\n"
            "void main()\n"
            "{\n"
            "    const S s1 = S(nested(0.0), 2.0);\n"
            "    const S s2 = S(nested(0.0), 3.0);\n"
            "    gl_FragColor = (s1 == s2 ? 1.0 : 0.5) * mult;\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        ASSERT_TRUE(constantFoundInAST(0.5f));
    }
    
    // Test that equality comparison of two identical structs with a nested struct inside returns true.
    TEST_F(ConstantFoldingTest, FoldNestedIdenticalStructEqualityComparison)
    {
        const std::string &shaderString =
            "precision mediump float;\n"
            "struct nested {\n"
            "    float f\n;"
            "};\n"
            "struct S {\n"
            "    nested a;\n"
            "    float f;\n"
            "    int i;\n"
            "};\n"
            "uniform vec4 mult;\n"
            "void main()\n"
            "{\n"
            "    const S s1 = S(nested(0.0), 2.0, 3);\n"
            "    const S s2 = S(nested(0.0), 2.0, 3);\n"
            "    gl_FragColor = (s1 == s2 ? 1.0 : 0.5) * mult;\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        ASSERT_TRUE(constantFoundInAST(1.0f));
    }
    
    // Test that right elements are chosen from non-square matrix
    TEST_F(ConstantFoldingTest, FoldNonSquareMatrixIndexing)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out vec4 my_FragColor;\n"
            "void main()\n"
            "{\n"
            "    my_FragColor = mat3x4(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)[1];\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        float outputElements[] = {4.0f, 5.0f, 6.0f, 7.0f};
        std::vector<float> result(outputElements, outputElements + 4);
        ASSERT_TRUE(constantVectorFoundInAST(result));
    }
    
    // Test that folding outer product of vectors with non-matching lengths works.
    TEST_F(ConstantFoldingTest, FoldNonSquareOuterProduct)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out vec4 my_FragColor;\n"
            "void main()\n"
            "{\n"
            "    mat3x2 prod = outerProduct(vec2(2.0, 3.0), vec3(5.0, 7.0, 11.0));\n"
            "    my_FragColor = vec4(prod[0].x);\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        // clang-format off
        float outputElements[] =
        {
            10.0f, 15.0f,
            14.0f, 21.0f,
            22.0f, 33.0f
        };
        // clang-format on
        std::vector<float> result(outputElements, outputElements + 6);
        ASSERT_TRUE(constantColumnMajorMatrixFoundInAST(result));
    }
    
    // Test that folding bit shift left with non-matching signedness works.
    TEST_F(ConstantFoldingTest, FoldBitShiftLeftDifferentSignedness)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out vec4 my_FragColor;\n"
            "void main()\n"
            "{\n"
            "    uint u = 0xffffffffu << 31;\n"
            "    my_FragColor = vec4(u);\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        ASSERT_TRUE(constantFoundInAST(0x80000000u));
    }
    
    // Test that folding bit shift right with non-matching signedness works.
    TEST_F(ConstantFoldingTest, FoldBitShiftRightDifferentSignedness)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out vec4 my_FragColor;\n"
            "void main()\n"
            "{\n"
            "    uint u = 0xffffffffu >> 30;\n"
            "    my_FragColor = vec4(u);\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        ASSERT_TRUE(constantFoundInAST(0x3u));
    }
    
    // Test that folding signed bit shift right extends the sign bit.
    // ESSL 3.00.6 section 5.9 Expressions.
    TEST_F(ConstantFoldingTest, FoldBitShiftRightExtendSignBit)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out vec4 my_FragColor;\n"
            "void main()\n"
            "{\n"
            "    const int i = 0x8fffe000 >> 6;\n"
            "    uint u = uint(i);"
            "    my_FragColor = vec4(u);\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        // The bits of the operand are 0x8fffe000 = 1000 1111 1111 1111 1110 0000 0000 0000
        // After shifting, they become              1111 1110 0011 1111 1111 1111 1000 0000 = 0xfe3fff80
        ASSERT_TRUE(constantFoundInAST(0xfe3fff80u));
    }
    
    // Signed bit shift left should interpret its operand as a bit pattern. As a consequence a number
    // may turn from positive to negative when shifted left.
    // ESSL 3.00.6 section 5.9 Expressions.
    TEST_F(ConstantFoldingTest, FoldBitShiftLeftInterpretedAsBitPattern)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out vec4 my_FragColor;\n"
            "void main()\n"
            "{\n"
            "    const int i = 0x1fffffff << 3;\n"
            "    uint u = uint(i);"
            "    my_FragColor = vec4(u);\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        ASSERT_TRUE(constantFoundInAST(0xfffffff8u));
    }
    
    // Test that dividing the minimum signed integer by -1 works.
    // ESSL 3.00.6 section 4.1.3 Integers:
    // "However, for the case where the minimum representable value is divided by -1, it is allowed to
    // return either the minimum representable value or the maximum representable value."
    TEST_F(ConstantFoldingTest, FoldDivideMinimumIntegerByMinusOne)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out vec4 my_FragColor;\n"
            "void main()\n"
            "{\n"
            "    int i = 0x80000000 / (-1);\n"
            "    my_FragColor = vec4(i);\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        ASSERT_TRUE(constantFoundInAST(0x7fffffff) || constantFoundInAST(-0x7fffffff - 1));
    }
    
    // Test that folding an unsigned integer addition that overflows works.
    // ESSL 3.00.6 section 4.1.3 Integers:
    // "For all precisions, operations resulting in overflow or underflow will not cause any exception,
    // nor will they saturate, rather they will 'wrap' to yield the low-order n bits of the result where
    // n is the size in bits of the integer."
    TEST_F(ConstantFoldingTest, FoldUnsignedIntegerAddOverflow)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out vec4 my_FragColor;\n"
            "void main()\n"
            "{\n"
            "    uint u = 0xffffffffu + 43u;\n"
            "    my_FragColor = vec4(u);\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        ASSERT_TRUE(constantFoundInAST(42u));
    }
    
    // Test that folding a signed integer addition that overflows works.
    // ESSL 3.00.6 section 4.1.3 Integers:
    // "For all precisions, operations resulting in overflow or underflow will not cause any exception,
    // nor will they saturate, rather they will 'wrap' to yield the low-order n bits of the result where
    // n is the size in bits of the integer."
    TEST_F(ConstantFoldingTest, FoldSignedIntegerAddOverflow)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out vec4 my_FragColor;\n"
            "void main()\n"
            "{\n"
            "    int i = 0x7fffffff + 4;\n"
            "    my_FragColor = vec4(i);\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        ASSERT_TRUE(constantFoundInAST(-0x7ffffffd));
    }
    
    // Test that folding an unsigned integer subtraction that overflows works.
    // ESSL 3.00.6 section 4.1.3 Integers:
    // "For all precisions, operations resulting in overflow or underflow will not cause any exception,
    // nor will they saturate, rather they will 'wrap' to yield the low-order n bits of the result where
    // n is the size in bits of the integer."
    TEST_F(ConstantFoldingTest, FoldUnsignedIntegerDiffOverflow)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out vec4 my_FragColor;\n"
            "void main()\n"
            "{\n"
            "    uint u = 0u - 5u;\n"
            "    my_FragColor = vec4(u);\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        ASSERT_TRUE(constantFoundInAST(0xfffffffbu));
    }
    
    // Test that folding a signed integer subtraction that overflows works.
    // ESSL 3.00.6 section 4.1.3 Integers:
    // "For all precisions, operations resulting in overflow or underflow will not cause any exception,
    // nor will they saturate, rather they will 'wrap' to yield the low-order n bits of the result where
    // n is the size in bits of the integer."
    TEST_F(ConstantFoldingTest, FoldSignedIntegerDiffOverflow)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out vec4 my_FragColor;\n"
            "void main()\n"
            "{\n"
            "    int i = -0x7fffffff - 7;\n"
            "    my_FragColor = vec4(i);\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        ASSERT_TRUE(constantFoundInAST(0x7ffffffa));
    }
    
    // Test that folding an unsigned integer multiplication that overflows works.
    // ESSL 3.00.6 section 4.1.3 Integers:
    // "For all precisions, operations resulting in overflow or underflow will not cause any exception,
    // nor will they saturate, rather they will 'wrap' to yield the low-order n bits of the result where
    // n is the size in bits of the integer."
    TEST_F(ConstantFoldingTest, FoldUnsignedIntegerMultiplyOverflow)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out vec4 my_FragColor;\n"
            "void main()\n"
            "{\n"
            "    uint u = 0xffffffffu * 10u;\n"
            "    my_FragColor = vec4(u);\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        ASSERT_TRUE(constantFoundInAST(0xfffffff6u));
    }
    
    // Test that folding a signed integer multiplication that overflows works.
    // ESSL 3.00.6 section 4.1.3 Integers:
    // "For all precisions, operations resulting in overflow or underflow will not cause any exception,
    // nor will they saturate, rather they will 'wrap' to yield the low-order n bits of the result where
    // n is the size in bits of the integer."
    TEST_F(ConstantFoldingTest, FoldSignedIntegerMultiplyOverflow)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out vec4 my_FragColor;\n"
            "void main()\n"
            "{\n"
            "    int i = 0x7fffffff * 42;\n"
            "    my_FragColor = vec4(i);\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        ASSERT_TRUE(constantFoundInAST(-42));
    }
    
    // Test that folding of negating the minimum representable integer works. Note that in the test
    // "0x80000000" is a negative literal, and the minus sign before it is the negation operator.
    // ESSL 3.00.6 section 4.1.3 Integers:
    // "For all precisions, operations resulting in overflow or underflow will not cause any exception,
    // nor will they saturate, rather they will 'wrap' to yield the low-order n bits of the result where
    // n is the size in bits of the integer."
    TEST_F(ConstantFoldingTest, FoldMinimumSignedIntegerNegation)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out vec4 my_FragColor;\n"
            "void main()\n"
            "{\n"
            "    int i = -0x80000000;\n"
            "    my_FragColor = vec4(i);\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        // Negating the minimum signed integer overflows the positive range, so it wraps back to itself.
        ASSERT_TRUE(constantFoundInAST(-0x7fffffff - 1));
    }
    
    // Test that folding of shifting the minimum representable integer works.
    TEST_F(ConstantFoldingTest, FoldMinimumSignedIntegerRightShift)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out vec4 my_FragColor;\n"
            "void main()\n"
            "{\n"
            "    int i = (0x80000000 >> 1);\n"
            "    int j = (0x80000000 >> 7);\n"
            "    my_FragColor = vec4(i, j, i, j);\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        ASSERT_TRUE(constantFoundInAST(-0x40000000));
        ASSERT_TRUE(constantFoundInAST(-0x01000000));
    }
    
    // Test that folding of shifting by 0 works.
    TEST_F(ConstantFoldingTest, FoldShiftByZero)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out vec4 my_FragColor;\n"
            "void main()\n"
            "{\n"
            "    int i = (3 >> 0);\n"
            "    int j = (73 << 0);\n"
            "    my_FragColor = vec4(i, j, i, j);\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        ASSERT_TRUE(constantFoundInAST(3));
        ASSERT_TRUE(constantFoundInAST(73));
    }
    
    // Test that folding IsInf results in true when the parameter is an out-of-range float literal.
    // ESSL 3.00.6 section 4.1.4 Floats:
    // "If the value of the floating point number is too large (small) to be stored as a single
    // precision value, it is converted to positive (negative) infinity."
    // ESSL 3.00.6 section 12.4:
    // "Mandate support for signed infinities."
    TEST_F(ConstantFoldingTest, FoldIsInfOutOfRangeFloatLiteral)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out vec4 my_FragColor;\n"
            "void main()\n"
            "{\n"
            "    bool b = isinf(1.0e2048);\n"
            "    my_FragColor = vec4(b);\n"
            "}\n";
        compileAssumeSuccess(shaderString);
        ASSERT_TRUE(constantFoundInAST(true));
    }
    
    // Test that floats that are too small to be represented get flushed to zero.
    // ESSL 3.00.6 section 4.1.4 Floats:
    // "A value with a magnitude too small to be represented as a mantissa and exponent is converted to
    // zero."
    TEST_F(ConstantFoldingExpressionTest, FoldTooSmallFloat)
    {
        const std::string &floatString = "1.0e-2048";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(0.0f));
    }
    
    // IEEE 754 dictates that behavior of infinity is derived from limiting cases of real arithmetic.
    // lim radians(x) x -> inf = inf
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldRadiansInfinity)
    {
        const std::string &floatString = "radians(1.0e2048)";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(std::numeric_limits<float>::infinity()));
    }
    
    // IEEE 754 dictates that behavior of infinity is derived from limiting cases of real arithmetic.
    // lim degrees(x) x -> inf = inf
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldDegreesInfinity)
    {
        const std::string &floatString = "degrees(1.0e2048)";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(std::numeric_limits<float>::infinity()));
    }
    
    // IEEE 754 dictates that sinh(inf) = inf.
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldSinhInfinity)
    {
        const std::string &floatString = "sinh(1.0e2048)";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(std::numeric_limits<float>::infinity()));
    }
    
    // IEEE 754 dictates that sinh(-inf) = -inf.
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldSinhNegativeInfinity)
    {
        const std::string &floatString = "sinh(-1.0e2048)";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(-std::numeric_limits<float>::infinity()));
    }
    
    // IEEE 754 dictates that cosh(inf) = inf.
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldCoshInfinity)
    {
        const std::string &floatString = "cosh(1.0e2048)";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(std::numeric_limits<float>::infinity()));
    }
    
    // IEEE 754 dictates that cosh(-inf) = inf.
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldCoshNegativeInfinity)
    {
        const std::string &floatString = "cosh(-1.0e2048)";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(std::numeric_limits<float>::infinity()));
    }
    
    // IEEE 754 dictates that asinh(inf) = inf.
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldAsinhInfinity)
    {
        const std::string &floatString = "asinh(1.0e2048)";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(std::numeric_limits<float>::infinity()));
    }
    
    // IEEE 754 dictates that asinh(-inf) = -inf.
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldAsinhNegativeInfinity)
    {
        const std::string &floatString = "asinh(-1.0e2048)";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(-std::numeric_limits<float>::infinity()));
    }
    
    // IEEE 754 dictates that acosh(inf) = inf.
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldAcoshInfinity)
    {
        const std::string &floatString = "acosh(1.0e2048)";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(std::numeric_limits<float>::infinity()));
    }
    
    // IEEE 754 dictates that pow or powr(0, inf) = 0.
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldPowInfinity)
    {
        const std::string &floatString = "pow(0.0, 1.0e2048)";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(0.0f));
    }
    
    // IEEE 754 dictates that exp(inf) = inf.
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldExpInfinity)
    {
        const std::string &floatString = "exp(1.0e2048)";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(std::numeric_limits<float>::infinity()));
    }
    
    // IEEE 754 dictates that exp(-inf) = 0.
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldExpNegativeInfinity)
    {
        const std::string &floatString = "exp(-1.0e2048)";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(0.0f));
    }
    
    // IEEE 754 dictates that log(inf) = inf.
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldLogInfinity)
    {
        const std::string &floatString = "log(1.0e2048)";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(std::numeric_limits<float>::infinity()));
    }
    
    // IEEE 754 dictates that exp2(inf) = inf.
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldExp2Infinity)
    {
        const std::string &floatString = "exp2(1.0e2048)";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(std::numeric_limits<float>::infinity()));
    }
    
    // IEEE 754 dictates that exp2(-inf) = 0.
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldExp2NegativeInfinity)
    {
        const std::string &floatString = "exp2(-1.0e2048)";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(0.0f));
    }
    
    // IEEE 754 dictates that log2(inf) = inf.
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldLog2Infinity)
    {
        const std::string &floatString = "log2(1.0e2048)";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(std::numeric_limits<float>::infinity()));
    }
    
    // IEEE 754 dictates that behavior of infinity is derived from limiting cases of real arithmetic.
    // lim sqrt(x) x -> inf = inf
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldSqrtInfinity)
    {
        const std::string &floatString = "sqrt(1.0e2048)";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(std::numeric_limits<float>::infinity()));
    }
    
    // IEEE 754 dictates that rSqrt(inf) = 0
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldInversesqrtInfinity)
    {
        const std::string &floatString = "inversesqrt(1.0e2048)";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(0.0f));
    }
    
    // IEEE 754 dictates that behavior of infinity is derived from limiting cases of real arithmetic.
    // lim length(x) x -> inf = inf
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldLengthInfinity)
    {
        const std::string &floatString = "length(1.0e2048)";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(std::numeric_limits<float>::infinity()));
    }
    
    // IEEE 754 dictates that behavior of infinity is derived from limiting cases of real arithmetic.
    // lim dot(x, y) x -> inf, y > 0 = inf
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldDotInfinity)
    {
        const std::string &floatString = "dot(1.0e2048, 1.0)";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(std::numeric_limits<float>::infinity()));
    }
    
    // IEEE 754 dictates that behavior of infinity is derived from limiting cases of real arithmetic.
    // lim dot(vec2(x, y), vec2(z)) x -> inf, finite y, z > 0 = inf
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldDotInfinity2)
    {
        const std::string &floatString = "dot(vec2(1.0e2048, -1.0), vec2(1.0))";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(std::numeric_limits<float>::infinity()));
    }
    
    // Faceforward behavior with infinity as a parameter can be derived from dot().
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldFaceForwardInfinity)
    {
        const std::string &floatString = "faceforward(4.0, 1.0e2048, 1.0)";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(-4.0f));
    }
    
    // Faceforward behavior with infinity as a parameter can be derived from dot().
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldFaceForwardInfinity2)
    {
        const std::string &floatString = "faceforward(vec2(4.0), vec2(1.0e2048, -1.0), vec2(1.0)).x";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(-4.0f));
    }
    
    // Test that infinity - finite value evaluates to infinity.
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldInfinityMinusFinite)
    {
        const std::string &floatString = "1.0e2048 - 1.0e20";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(std::numeric_limits<float>::infinity()));
    }
    
    // Test that -infinity + finite value evaluates to -infinity.
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldMinusInfinityPlusFinite)
    {
        const std::string &floatString = "(-1.0e2048) + 1.0e20";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(-std::numeric_limits<float>::infinity()));
    }
    
    // Test that infinity * finite value evaluates to infinity.
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldInfinityMultipliedByFinite)
    {
        const std::string &floatString = "1.0e2048 * 1.0e-20";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(std::numeric_limits<float>::infinity()));
    }
    
    // Test that infinity * infinity evaluates to infinity.
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldInfinityMultipliedByInfinity)
    {
        const std::string &floatString = "1.0e2048 * 1.0e2048";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(std::numeric_limits<float>::infinity()));
    }
    
    // Test that infinity * negative infinity evaluates to negative infinity.
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldInfinityMultipliedByNegativeInfinity)
    {
        const std::string &floatString = "1.0e2048 * (-1.0e2048)";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(-std::numeric_limits<float>::infinity()));
    }
    
    // Test that dividing by minus zero results in the appropriately signed infinity.
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    // "If both positive and negative zeros are implemented, the correctly signed Inf will be
    // generated".
    TEST_F(ConstantFoldingExpressionTest, FoldDivideByNegativeZero)
    {
        const std::string &floatString = "1.0 / (-0.0)";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(-std::numeric_limits<float>::infinity()));
        ASSERT_TRUE(hasWarning());
    }
    
    // Test that infinity divided by zero evaluates to infinity.
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldInfinityDividedByZero)
    {
        const std::string &floatString = "1.0e2048 / 0.0";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(std::numeric_limits<float>::infinity()));
        ASSERT_TRUE(hasWarning());
    }
    
    // Test that negative infinity divided by zero evaluates to negative infinity.
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldMinusInfinityDividedByZero)
    {
        const std::string &floatString = "(-1.0e2048) / 0.0";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(-std::numeric_limits<float>::infinity()));
        ASSERT_TRUE(hasWarning());
    }
    
    // Test that dividing a finite number by infinity results in zero.
    // ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
    TEST_F(ConstantFoldingExpressionTest, FoldDivideByInfinity)
    {
        const std::string &floatString = "1.0e30 / 1.0e2048";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(0.0f));
    }
    
    // Test that unsigned bitfieldExtract is folded correctly.
    TEST_F(ConstantFoldingExpressionTest, FoldUnsignedBitfieldExtract)
    {
        const std::string &uintString = "bitfieldExtract(0x00110000u, 16, 5)";
        evaluateUint(uintString);
        ASSERT_TRUE(constantFoundInAST(0x11u));
    }
    
    // Test that unsigned bitfieldExtract to extract 32 bits is folded correctly.
    TEST_F(ConstantFoldingExpressionTest, FoldUnsignedBitfieldExtract32Bits)
    {
        const std::string &uintString = "bitfieldExtract(0xff0000ffu, 0, 32)";
        evaluateUint(uintString);
        ASSERT_TRUE(constantFoundInAST(0xff0000ffu));
    }
    
    // Test that signed bitfieldExtract is folded correctly. The higher bits should be set to 1 if the
    // most significant bit of the extracted value is 1.
    TEST_F(ConstantFoldingExpressionTest, FoldSignedBitfieldExtract)
    {
        const std::string &intString = "bitfieldExtract(0x00110000, 16, 5)";
        evaluateInt(intString);
        // 0xfffffff1 == -15
        ASSERT_TRUE(constantFoundInAST(-15));
    }
    
    // Test that bitfieldInsert is folded correctly.
    TEST_F(ConstantFoldingExpressionTest, FoldBitfieldInsert)
    {
        const std::string &uintString = "bitfieldInsert(0x04501701u, 0x11u, 8, 5)";
        evaluateUint(uintString);
        ASSERT_TRUE(constantFoundInAST(0x04501101u));
    }
    
    // Test that bitfieldInsert to insert 32 bits is folded correctly.
    TEST_F(ConstantFoldingExpressionTest, FoldBitfieldInsert32Bits)
    {
        const std::string &uintString = "bitfieldInsert(0xff0000ffu, 0x11u, 0, 32)";
        evaluateUint(uintString);
        ASSERT_TRUE(constantFoundInAST(0x11u));
    }
    
    // Test that bitfieldReverse is folded correctly.
    TEST_F(ConstantFoldingExpressionTest, FoldBitfieldReverse)
    {
        const std::string &uintString = "bitfieldReverse((1u << 4u) | (1u << 7u))";
        evaluateUint(uintString);
        uint32_t flag1 = 1u << (31u - 4u);
        uint32_t flag2 = 1u << (31u - 7u);
        ASSERT_TRUE(constantFoundInAST(flag1 | flag2));
    }
    
    // Test that bitCount is folded correctly.
    TEST_F(ConstantFoldingExpressionTest, FoldBitCount)
    {
        const std::string &intString = "bitCount(0x17103121u)";
        evaluateInt(intString);
        ASSERT_TRUE(constantFoundInAST(10));
    }
    
    // Test that findLSB is folded correctly.
    TEST_F(ConstantFoldingExpressionTest, FoldFindLSB)
    {
        const std::string &intString = "findLSB(0x80010000u)";
        evaluateInt(intString);
        ASSERT_TRUE(constantFoundInAST(16));
    }
    
    // Test that findLSB is folded correctly when the operand is zero.
    TEST_F(ConstantFoldingExpressionTest, FoldFindLSBZero)
    {
        const std::string &intString = "findLSB(0u)";
        evaluateInt(intString);
        ASSERT_TRUE(constantFoundInAST(-1));
    }
    
    // Test that findMSB is folded correctly.
    TEST_F(ConstantFoldingExpressionTest, FoldFindMSB)
    {
        const std::string &intString = "findMSB(0x01000008u)";
        evaluateInt(intString);
        ASSERT_TRUE(constantFoundInAST(24));
    }
    
    // Test that findMSB is folded correctly when the operand is zero.
    TEST_F(ConstantFoldingExpressionTest, FoldFindMSBZero)
    {
        const std::string &intString = "findMSB(0u)";
        evaluateInt(intString);
        ASSERT_TRUE(constantFoundInAST(-1));
    }
    
    // Test that findMSB is folded correctly for a negative integer.
    // It is supposed to return the index of the most significant bit set to 0.
    TEST_F(ConstantFoldingExpressionTest, FoldFindMSBNegativeInt)
    {
        const std::string &intString = "findMSB(-8)";
        evaluateInt(intString);
        ASSERT_TRUE(constantFoundInAST(2));
    }
    
    // Test that findMSB is folded correctly for -1.
    TEST_F(ConstantFoldingExpressionTest, FoldFindMSBMinusOne)
    {
        const std::string &intString = "findMSB(-1)";
        evaluateInt(intString);
        ASSERT_TRUE(constantFoundInAST(-1));
    }
    
    // Test that packUnorm4x8 is folded correctly for a vector of zeroes.
    TEST_F(ConstantFoldingExpressionTest, FoldPackUnorm4x8Zero)
    {
        const std::string &intString = "packUnorm4x8(vec4(0.0))";
        evaluateUint(intString);
        ASSERT_TRUE(constantFoundInAST(0u));
    }
    
    // Test that packUnorm4x8 is folded correctly for a vector of ones.
    TEST_F(ConstantFoldingExpressionTest, FoldPackUnorm4x8One)
    {
        const std::string &intString = "packUnorm4x8(vec4(1.0))";
        evaluateUint(intString);
        ASSERT_TRUE(constantFoundInAST(0xffffffffu));
    }
    
    // Test that packSnorm4x8 is folded correctly for a vector of zeroes.
    TEST_F(ConstantFoldingExpressionTest, FoldPackSnorm4x8Zero)
    {
        const std::string &intString = "packSnorm4x8(vec4(0.0))";
        evaluateUint(intString);
        ASSERT_TRUE(constantFoundInAST(0u));
    }
    
    // Test that packSnorm4x8 is folded correctly for a vector of ones.
    TEST_F(ConstantFoldingExpressionTest, FoldPackSnorm4x8One)
    {
        const std::string &intString = "packSnorm4x8(vec4(1.0))";
        evaluateUint(intString);
        ASSERT_TRUE(constantFoundInAST(0x7f7f7f7fu));
    }
    
    // Test that packSnorm4x8 is folded correctly for a vector of minus ones.
    TEST_F(ConstantFoldingExpressionTest, FoldPackSnorm4x8MinusOne)
    {
        const std::string &intString = "packSnorm4x8(vec4(-1.0))";
        evaluateUint(intString);
        ASSERT_TRUE(constantFoundInAST(0x81818181u));
    }
    
    // Test that unpackSnorm4x8 is folded correctly when it needs to clamp the result.
    TEST_F(ConstantFoldingExpressionTest, FoldUnpackSnorm4x8Clamp)
    {
        const std::string &floatString = "unpackSnorm4x8(0x00000080u).x";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(-1.0f));
    }
    
    // Test that unpackUnorm4x8 is folded correctly.
    TEST_F(ConstantFoldingExpressionTest, FoldUnpackUnorm4x8)
    {
        const std::string &floatString = "unpackUnorm4x8(0x007bbeefu).z";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(123.0f / 255.0f));
    }
    
    // Test that ldexp is folded correctly.
    TEST_F(ConstantFoldingExpressionTest, FoldLdexp)
    {
        const std::string &floatString = "ldexp(0.625, 1)";
        evaluateFloat(floatString);
        ASSERT_TRUE(constantFoundInAST(1.25f));
    }
    
    // Fold a ternary operator.
    TEST_F(ConstantFoldingTest, FoldTernary)
    {
        const std::string &shaderString =
            R"(#version 300 es
            precision highp int;
            uniform int u;
            out int my_FragColor;
            void main()
            {
                my_FragColor = (true ? 1 : u);
            })";
        compileAssumeSuccess(shaderString);
        ASSERT_TRUE(constantFoundInAST(1));
        ASSERT_FALSE(symbolFoundInMain("u"));
    }
    
    // Fold a ternary operator inside a consuming expression.
    TEST_F(ConstantFoldingTest, FoldTernaryInsideExpression)
    {
        const std::string &shaderString =
            R"(#version 300 es
            precision highp int;
            uniform int u;
            out int my_FragColor;
            void main()
            {
                my_FragColor = ivec2((true ? 1 : u) + 2, 4).x;
            })";
        compileAssumeSuccess(shaderString);
        ASSERT_TRUE(constantFoundInAST(3));
        ASSERT_FALSE(symbolFoundInMain("u"));
    }
    
    // Fold indexing into an array constructor.
    TEST_F(ConstantFoldingExpressionTest, FoldArrayConstructorIndexing)
    {
        const std::string &floatString = "(float[3](-1.0, 1.0, 2.0))[2]";
        evaluateFloat(floatString);
        ASSERT_FALSE(constantFoundInAST(-1.0f));
        ASSERT_FALSE(constantFoundInAST(1.0f));
        ASSERT_TRUE(constantFoundInAST(2.0f));
    }
    
    // Fold indexing into an array of arrays constructor.
    TEST_F(ConstantFoldingExpressionTest, FoldArrayOfArraysConstructorIndexing)
    {
        const std::string &floatString = "(float[2][2](float[2](-1.0, 1.0), float[2](2.0, 3.0)))[1][0]";
        evaluateFloat(floatString);
        ASSERT_FALSE(constantFoundInAST(-1.0f));
        ASSERT_FALSE(constantFoundInAST(1.0f));
        ASSERT_FALSE(constantFoundInAST(3.0f));
        ASSERT_TRUE(constantFoundInAST(2.0f));
    }
    
    // Fold indexing into a named constant array.
    TEST_F(ConstantFoldingTest, FoldNamedArrayIndexing)
    {
        const std::string &shaderString =
            R"(#version 300 es
            precision highp float;
            const float[3] arr = float[3](-1.0, 1.0, 2.0);
            out float my_FragColor;
            void main()
            {
                my_FragColor = arr[1];
            })";
        compileAssumeSuccess(shaderString);
        ASSERT_FALSE(constantFoundInAST(-1.0f));
        ASSERT_FALSE(constantFoundInAST(2.0f));
        ASSERT_TRUE(constantFoundInAST(1.0f));
        // The variable should be pruned out since after folding the indexing, there are no more
        // references to it.
        ASSERT_FALSE(symbolFoundInAST("arr"));
    }
    
    // Fold indexing into a named constant array of arrays.
    TEST_F(ConstantFoldingTest, FoldNamedArrayOfArraysIndexing)
    {
        const std::string &shaderString =
            R"(#version 310 es
            precision highp float;
            const float[2][2] arr = float[2][2](float[2](-1.0, 1.0), float[2](2.0, 3.0));
            out float my_FragColor;
            void main()
            {
                my_FragColor = arr[0][1];
            })";
        compileAssumeSuccess(shaderString);
        ASSERT_FALSE(constantFoundInAST(-1.0f));
        ASSERT_FALSE(constantFoundInAST(2.0f));
        ASSERT_FALSE(constantFoundInAST(3.0f));
        ASSERT_TRUE(constantFoundInAST(1.0f));
        // The variable should be pruned out since after folding the indexing, there are no more
        // references to it.
        ASSERT_FALSE(symbolFoundInAST("arr"));
    }
    
    // Fold indexing into an array constructor where some of the arguments are constant and others are
    // non-constant but without side effects.
    TEST_F(ConstantFoldingTest, FoldArrayConstructorIndexingWithMixedArguments)
    {
        const std::string &shaderString =
            R"(#version 300 es
            precision highp float;
            uniform float u;
            out float my_FragColor;
            void main()
            {
                my_FragColor = float[2](u, 1.0)[1];
            })";
        compileAssumeSuccess(shaderString);
        ASSERT_TRUE(constantFoundInAST(1.0f));
        ASSERT_FALSE(constantFoundInAST(1));
        ASSERT_FALSE(symbolFoundInMain("u"));
    }
    
    // Indexing into an array constructor where some of the arguments have side effects can't be folded.
    TEST_F(ConstantFoldingTest, CantFoldArrayConstructorIndexingWithSideEffects)
    {
        const std::string &shaderString =
            R"(#version 300 es
            precision highp float;
            out float my_FragColor;
            void main()
            {
                float sideEffectTarget = 0.0;
                float f = float[3](sideEffectTarget = 1.0, 1.0, 2.0)[1];
                my_FragColor = f + sideEffectTarget;
            })";
        compileAssumeSuccess(shaderString);
        // All of the array constructor arguments should be present in the final AST.
        ASSERT_TRUE(constantFoundInAST(1.0f));
        ASSERT_TRUE(constantFoundInAST(2.0f));
    }
    
    // Fold comparing two array constructors.
    TEST_F(ConstantFoldingTest, FoldArrayConstructorEquality)
    {
        const std::string &shaderString =
            R"(#version 300 es
            precision highp float;
            out float my_FragColor;
            void main()
            {
                const bool b = (float[3](2.0, 1.0, -1.0) == float[3](2.0, 1.0, -1.0));
                my_FragColor = b ? 3.0 : 4.0;
            })";
        compileAssumeSuccess(shaderString);
        ASSERT_TRUE(constantFoundInAST(3.0f));
        ASSERT_FALSE(constantFoundInAST(4.0f));
    }
    
    // Fold comparing two named constant arrays.
    TEST_F(ConstantFoldingExpressionTest, FoldNamedArrayEquality)
    {
        const std::string &shaderString =
            R"(#version 300 es
            precision highp float;
            const float[3] arrA = float[3](-1.0, 1.0, 2.0);
            const float[3] arrB = float[3](-1.0, 1.0, 2.0);
            out float my_FragColor;
            void main()
            {
                const bool b = (arrA == arrB);
                my_FragColor = b ? 3.0 : 4.0;
            })";
        compileAssumeSuccess(shaderString);
        ASSERT_TRUE(constantFoundInAST(3.0f));
        ASSERT_FALSE(constantFoundInAST(4.0f));
    }
    
    // Fold comparing two array of arrays constructors.
    TEST_F(ConstantFoldingTest, FoldArrayOfArraysConstructorEquality)
    {
        const std::string &shaderString =
            R"(#version 310 es
            precision highp float;
            out float my_FragColor;
            void main()
            {
                const bool b = (float[2][2](float[2](-1.0, 1.0), float[2](2.0, 3.0)) ==
                                float[2][2](float[2](-1.0, 1.0), float[2](2.0, 1000.0)));
                my_FragColor = b ? 4.0 : 5.0;
            })";
        compileAssumeSuccess(shaderString);
        ASSERT_TRUE(constantFoundInAST(5.0f));
        ASSERT_FALSE(constantFoundInAST(4.0f));
    }