//
// Copyright 2002 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.
//

//
// Build the intermediate representation.
//

#include <float.h>
#include <limits.h>
#include <math.h>
#include <stdlib.h>
#include <algorithm>
#include <vector>

#include "common/mathutil.h"
#include "common/matrix_utils.h"
#include "common/utilities.h"
#include "compiler/translator/Diagnostics.h"
#include "compiler/translator/ImmutableString.h"
#include "compiler/translator/IntermNode.h"
#include "compiler/translator/SymbolTable.h"
#include "compiler/translator/util.h"

namespace sh
{

namespace
{

const float kPi                         = 3.14159265358979323846f;
const float kDegreesToRadiansMultiplier = kPi / 180.0f;
const float kRadiansToDegreesMultiplier = 180.0f / kPi;

TPrecision GetHigherPrecision(TPrecision left, TPrecision right)
{
    return left > right ? left : right;
}

TConstantUnion *Vectorize(const TConstantUnion &constant, size_t size)
{
    TConstantUnion *constUnion = new TConstantUnion[size];
    for (size_t i = 0; i < size; ++i)
        constUnion[i] = constant;

    return constUnion;
}

void UndefinedConstantFoldingError(const TSourceLoc &loc,
                                   const TFunction *function,
                                   TBasicType basicType,
                                   TDiagnostics *diagnostics,
                                   TConstantUnion *result)
{
    diagnostics->warning(loc, "operation result is undefined for the values passed in",
                         function->name().data());

    switch (basicType)
    {
        case EbtFloat:
            result->setFConst(0.0f);
            break;
        case EbtInt:
            result->setIConst(0);
            break;
        case EbtUInt:
            result->setUConst(0u);
            break;
        case EbtBool:
            result->setBConst(false);
            break;
        default:
            break;
    }
}

float VectorLength(const TConstantUnion *paramArray, size_t paramArraySize)
{
    float result = 0.0f;
    for (size_t i = 0; i < paramArraySize; i++)
    {
        float f = paramArray[i].getFConst();
        result += f * f;
    }
    return sqrtf(result);
}

float VectorDotProduct(const TConstantUnion *paramArray1,
                       const TConstantUnion *paramArray2,
                       size_t paramArraySize)
{
    float result = 0.0f;
    for (size_t i = 0; i < paramArraySize; i++)
        result += paramArray1[i].getFConst() * paramArray2[i].getFConst();
    return result;
}

TIntermTyped *CreateFoldedNode(const TConstantUnion *constArray, const TIntermTyped *originalNode)
{
    ASSERT(constArray != nullptr);
    // Note that we inherit whatever qualifier the folded node had. Nodes may be constant folded
    // without being qualified as constant.
    TIntermTyped *folded = new TIntermConstantUnion(constArray, originalNode->getType());
    folded->setLine(originalNode->getLine());
    return folded;
}

angle::Matrix<float> GetMatrix(const TConstantUnion *paramArray,
                               const unsigned int rows,
                               const unsigned int cols)
{
    std::vector<float> elements;
    for (size_t i = 0; i < rows * cols; i++)
        elements.push_back(paramArray[i].getFConst());
    // Transpose is used since the Matrix constructor expects arguments in row-major order,
    // whereas the paramArray is in column-major order. Rows/cols parameters are also flipped below
    // so that the created matrix will have the expected dimensions after the transpose.
    return angle::Matrix<float>(elements, cols, rows).transpose();
}

angle::Matrix<float> GetMatrix(const TConstantUnion *paramArray, const unsigned int size)
{
    std::vector<float> elements;
    for (size_t i = 0; i < size * size; i++)
        elements.push_back(paramArray[i].getFConst());
    // Transpose is used since the Matrix constructor expects arguments in row-major order,
    // whereas the paramArray is in column-major order.
    return angle::Matrix<float>(elements, size).transpose();
}

void SetUnionArrayFromMatrix(const angle::Matrix<float> &m, TConstantUnion *resultArray)
{
    // Transpose is used since the input Matrix is in row-major order,
    // whereas the actual result should be in column-major order.
    angle::Matrix<float> result       = m.transpose();
    std::vector<float> resultElements = result.elements();
    for (size_t i = 0; i < resultElements.size(); i++)
        resultArray[i].setFConst(resultElements[i]);
}

bool CanFoldAggregateBuiltInOp(TOperator op)
{
    switch (op)
    {
        case EOpAtan:
        case EOpPow:
        case EOpMod:
        case EOpMin:
        case EOpMax:
        case EOpClamp:
        case EOpMix:
        case EOpStep:
        case EOpSmoothstep:
        case EOpFma:
        case EOpLdexp:
        case EOpMatrixCompMult:
        case EOpOuterProduct:
        case EOpEqualComponentWise:
        case EOpNotEqualComponentWise:
        case EOpLessThanComponentWise:
        case EOpLessThanEqualComponentWise:
        case EOpGreaterThanComponentWise:
        case EOpGreaterThanEqualComponentWise:
        case EOpDistance:
        case EOpDot:
        case EOpCross:
        case EOpFaceforward:
        case EOpReflect:
        case EOpRefract:
        case EOpBitfieldExtract:
        case EOpBitfieldInsert:
        case EOpDFdx:
        case EOpDFdy:
        case EOpFwidth:
            return true;
        default:
            return false;
    }
}

void PropagatePrecisionIfApplicable(TIntermTyped *node, TPrecision precision)
{
    if (precision == EbpUndefined || node->getPrecision() != EbpUndefined)
    {
        return;
    }

    if (IsPrecisionApplicableToType(node->getBasicType()))
    {
        node->propagatePrecision(precision);
    }
}

}  // namespace

////////////////////////////////////////////////////////////////
//
// Member functions of the nodes used for building the tree.
//
////////////////////////////////////////////////////////////////

TIntermExpression::TIntermExpression(const TType &t) : TIntermTyped(), mType(t) {}

#define REPLACE_IF_IS(node, conversionFunc, original, replacement)                             \
    do                                                                                         \
    {                                                                                          \
        if (node == original)                                                                  \
        {                                                                                      \
            if (replacement == nullptr)                                                        \
            {                                                                                  \
                node = nullptr;                                                                \
            }                                                                                  \
            else                                                                               \
            {                                                                                  \
                auto replacementCasted = replacement->conversionFunc();                        \
                if (replacementCasted == nullptr)                                              \
                {                                                                              \
                    FATAL() << "Replacing a node with a node of invalid type: calling "        \
                               "replacement." #conversionFunc "() should not return nullptr."; \
                    return false;                                                              \
                }                                                                              \
                node = replacementCasted;                                                      \
            }                                                                                  \
            return true;                                                                       \
        }                                                                                      \
    } while (0)

size_t TIntermSymbol::getChildCount() const
{
    return 0;
}

TIntermNode *TIntermSymbol::getChildNode(size_t index) const
{
    UNREACHABLE();
    return nullptr;
}

size_t TIntermConstantUnion::getChildCount() const
{
    return 0;
}

TIntermNode *TIntermConstantUnion::getChildNode(size_t index) const
{
    UNREACHABLE();
    return nullptr;
}

size_t TIntermLoop::getChildCount() const
{
    return (mInit ? 1 : 0) + (mCond ? 1 : 0) + (mExpr ? 1 : 0) + 1;
}

TIntermNode *TIntermLoop::getChildNode(size_t index) const
{
    TIntermNode *children[4];
    unsigned int childIndex = 0;
    if (mInit)
    {
        children[childIndex] = mInit;
        ++childIndex;
    }
    if (mCond)
    {
        children[childIndex] = mCond;
        ++childIndex;
    }
    if (mExpr)
    {
        children[childIndex] = mExpr;
        ++childIndex;
    }
    children[childIndex] = mBody;
    ++childIndex;

    ASSERT(index < childIndex);
    return children[index];
}

bool TIntermLoop::replaceChildNode(TIntermNode *original, TIntermNode *replacement)
{
    ASSERT(original != nullptr);  // This risks replacing multiple children.
    REPLACE_IF_IS(mInit, getAsNode, original, replacement);
    REPLACE_IF_IS(mCond, getAsTyped, original, replacement);
    REPLACE_IF_IS(mExpr, getAsTyped, original, replacement);
    REPLACE_IF_IS(mBody, getAsBlock, original, replacement);
    return false;
}

TIntermBranch::TIntermBranch(const TIntermBranch &node)
    : TIntermBranch(node.mFlowOp, node.mExpression ? node.mExpression->deepCopy() : nullptr)
{}

size_t TIntermBranch::getChildCount() const
{
    return (mExpression ? 1 : 0);
}

TIntermNode *TIntermBranch::getChildNode(size_t index) const
{
    ASSERT(mExpression);
    ASSERT(index == 0);
    return mExpression;
}

bool TIntermBranch::replaceChildNode(TIntermNode *original, TIntermNode *replacement)
{
    REPLACE_IF_IS(mExpression, getAsTyped, original, replacement);
    return false;
}

size_t TIntermSwizzle::getChildCount() const
{
    return 1;
}

TIntermNode *TIntermSwizzle::getChildNode(size_t index) const
{
    ASSERT(mOperand);
    ASSERT(index == 0);
    return mOperand;
}

bool TIntermSwizzle::replaceChildNode(TIntermNode *original, TIntermNode *replacement)
{
    ASSERT(original->getAsTyped()->getType() == replacement->getAsTyped()->getType());
    REPLACE_IF_IS(mOperand, getAsTyped, original, replacement);
    return false;
}

size_t TIntermBinary::getChildCount() const
{
    return 2;
}

TIntermNode *TIntermBinary::getChildNode(size_t index) const
{
    ASSERT(index < 2);
    if (index == 0)
    {
        return mLeft;
    }
    return mRight;
}

bool TIntermBinary::replaceChildNode(TIntermNode *original, TIntermNode *replacement)
{
    REPLACE_IF_IS(mLeft, getAsTyped, original, replacement);
    REPLACE_IF_IS(mRight, getAsTyped, original, replacement);
    return false;
}

size_t TIntermUnary::getChildCount() const
{
    return 1;
}

TIntermNode *TIntermUnary::getChildNode(size_t index) const
{
    ASSERT(mOperand);
    ASSERT(index == 0);
    return mOperand;
}

bool TIntermUnary::replaceChildNode(TIntermNode *original, TIntermNode *replacement)
{
    // gl_ClipDistance and gl_CullDistance arrays may be replaced with an adjusted
    // array size. Allow mismatching types for the length() operation in this case.
    ASSERT(original->getAsTyped()->getType() == replacement->getAsTyped()->getType() ||
           (mOp == EOpArrayLength && (original->getAsTyped()->getQualifier() == EvqClipDistance ||
                                      original->getAsTyped()->getQualifier() == EvqCullDistance)));
    REPLACE_IF_IS(mOperand, getAsTyped, original, replacement);
    return false;
}

size_t TIntermGlobalQualifierDeclaration::getChildCount() const
{
    return 1;
}

TIntermNode *TIntermGlobalQualifierDeclaration::getChildNode(size_t index) const
{
    ASSERT(mSymbol);
    ASSERT(index == 0);
    return mSymbol;
}

bool TIntermGlobalQualifierDeclaration::replaceChildNode(TIntermNode *original,
                                                         TIntermNode *replacement)
{
    REPLACE_IF_IS(mSymbol, getAsSymbolNode, original, replacement);
    return false;
}

size_t TIntermFunctionDefinition::getChildCount() const
{
    return 2;
}

TIntermNode *TIntermFunctionDefinition::getChildNode(size_t index) const
{
    ASSERT(index < 2);
    if (index == 0)
    {
        return mPrototype;
    }
    return mBody;
}

bool TIntermFunctionDefinition::replaceChildNode(TIntermNode *original, TIntermNode *replacement)
{
    REPLACE_IF_IS(mPrototype, getAsFunctionPrototypeNode, original, replacement);
    REPLACE_IF_IS(mBody, getAsBlock, original, replacement);
    return false;
}

size_t TIntermAggregate::getChildCount() const
{
    return mArguments.size();
}

TIntermNode *TIntermAggregate::getChildNode(size_t index) const
{
    return mArguments[index];
}

bool TIntermAggregate::replaceChildNode(TIntermNode *original, TIntermNode *replacement)
{
    return replaceChildNodeInternal(original, replacement);
}

TIntermBlock::TIntermBlock(const TIntermBlock &node)
{
    for (TIntermNode *intermNode : node.mStatements)
    {
        mStatements.push_back(intermNode->deepCopy());
    }

    ASSERT(!node.mIsTreeRoot);
    mIsTreeRoot = false;
}

TIntermBlock::TIntermBlock(std::initializer_list<TIntermNode *> stmts)
{
    for (TIntermNode *stmt : stmts)
    {
        appendStatement(stmt);
    }
}

size_t TIntermBlock::getChildCount() const
{
    return mStatements.size();
}

TIntermNode *TIntermBlock::getChildNode(size_t index) const
{
    return mStatements[index];
}

bool TIntermBlock::replaceChildNode(TIntermNode *original, TIntermNode *replacement)
{
    return replaceChildNodeInternal(original, replacement);
}

void TIntermBlock::replaceAllChildren(const TIntermSequence &newStatements)
{
    mStatements.clear();
    mStatements.insert(mStatements.begin(), newStatements.begin(), newStatements.end());
}

size_t TIntermFunctionPrototype::getChildCount() const
{
    return 0;
}

TIntermNode *TIntermFunctionPrototype::getChildNode(size_t index) const
{
    UNREACHABLE();
    return nullptr;
}

bool TIntermFunctionPrototype::replaceChildNode(TIntermNode *original, TIntermNode *replacement)
{
    return false;
}

TIntermDeclaration::TIntermDeclaration(const TVariable *var, TIntermTyped *initExpr)
{
    if (initExpr)
    {
        appendDeclarator(
            new TIntermBinary(TOperator::EOpInitialize, new TIntermSymbol(var), initExpr));
    }
    else
    {
        appendDeclarator(new TIntermSymbol(var));
    }
}

TIntermDeclaration::TIntermDeclaration(std::initializer_list<const TVariable *> declarators)
    : TIntermDeclaration()
{
    for (const TVariable *d : declarators)
    {
        appendDeclarator(new TIntermSymbol(d));
    }
}

TIntermDeclaration::TIntermDeclaration(std::initializer_list<TIntermTyped *> declarators)
    : TIntermDeclaration()
{
    for (TIntermTyped *d : declarators)
    {
        appendDeclarator(d);
    }
}

size_t TIntermDeclaration::getChildCount() const
{
    return mDeclarators.size();
}

TIntermNode *TIntermDeclaration::getChildNode(size_t index) const
{
    return mDeclarators[index];
}

bool TIntermDeclaration::replaceChildNode(TIntermNode *original, TIntermNode *replacement)
{
    return replaceChildNodeInternal(original, replacement);
}

TIntermDeclaration::TIntermDeclaration(const TIntermDeclaration &node)
{
    for (TIntermNode *intermNode : node.mDeclarators)
    {
        mDeclarators.push_back(intermNode->deepCopy());
    }
}

bool TIntermAggregateBase::replaceChildNodeInternal(TIntermNode *original, TIntermNode *replacement)
{
    for (size_t ii = 0; ii < getSequence()->size(); ++ii)
    {
        REPLACE_IF_IS((*getSequence())[ii], getAsNode, original, replacement);
    }
    return false;
}

bool TIntermAggregateBase::replaceChildNodeWithMultiple(TIntermNode *original,
                                                        const TIntermSequence &replacements)
{
    for (auto it = getSequence()->begin(); it < getSequence()->end(); ++it)
    {
        if (*it == original)
        {
            it = getSequence()->erase(it);
            getSequence()->insert(it, replacements.begin(), replacements.end());
            return true;
        }
    }
    return false;
}

bool TIntermAggregateBase::insertChildNodes(TIntermSequence::size_type position,
                                            const TIntermSequence &insertions)
{
    if (position > getSequence()->size())
    {
        return false;
    }
    auto it = getSequence()->begin() + position;
    getSequence()->insert(it, insertions.begin(), insertions.end());
    return true;
}

TIntermSymbol::TIntermSymbol(const TVariable *variable) : TIntermTyped(), mVariable(variable) {}

bool TIntermSymbol::hasConstantValue() const
{
    return variable().getConstPointer() != nullptr;
}

const TConstantUnion *TIntermSymbol::getConstantValue() const
{
    return variable().getConstPointer();
}

const TSymbolUniqueId &TIntermSymbol::uniqueId() const
{
    return mVariable->uniqueId();
}

ImmutableString TIntermSymbol::getName() const
{
    return mVariable->name();
}

const TType &TIntermSymbol::getType() const
{
    return mVariable->getType();
}

void TIntermSymbol::propagatePrecision(TPrecision precision)
{
    // Every declared variable should already have a precision.  Some built-ins don't have a defined
    // precision.  This is not asserted however:
    //
    // - A shader with no precision specified either globally or on a variable will fail with a
    //   compilation error later on.
    // - Transformations declaring variables without precision will be caught by AST validation.
}

TIntermAggregate *TIntermAggregate::CreateFunctionCall(const TFunction &func,
                                                       TIntermSequence *arguments)
{
    return new TIntermAggregate(&func, func.getReturnType(), EOpCallFunctionInAST, arguments);
}

TIntermAggregate *TIntermAggregate::CreateRawFunctionCall(const TFunction &func,
                                                          TIntermSequence *arguments)
{
    return new TIntermAggregate(&func, func.getReturnType(), EOpCallInternalRawFunction, arguments);
}

TIntermAggregate *TIntermAggregate::CreateBuiltInFunctionCall(const TFunction &func,
                                                              TIntermSequence *arguments)
{
    // Every built-in function should have an op.
    ASSERT(func.getBuiltInOp() != EOpNull);
    return new TIntermAggregate(&func, func.getReturnType(), func.getBuiltInOp(), arguments);
}

TIntermAggregate *TIntermAggregate::CreateConstructor(const TType &type, TIntermSequence *arguments)
{
    return new TIntermAggregate(nullptr, type, EOpConstruct, arguments);
}

TIntermAggregate *TIntermAggregate::CreateConstructor(
    const TType &type,
    const std::initializer_list<TIntermNode *> &arguments)
{
    TIntermSequence argSequence(arguments);
    return CreateConstructor(type, &argSequence);
}

TIntermAggregate::TIntermAggregate(const TFunction *func,
                                   const TType &type,
                                   TOperator op,
                                   TIntermSequence *arguments)
    : TIntermOperator(op, type), mUseEmulatedFunction(false), mFunction(func)
{
    if (arguments != nullptr)
    {
        mArguments.swap(*arguments);
    }
    ASSERT(mFunction == nullptr || mFunction->symbolType() != SymbolType::Empty);
    setPrecisionAndQualifier();
}

void TIntermAggregate::setPrecisionAndQualifier()
{
    mType.setQualifier(EvqTemporary);
    if ((!BuiltInGroup::IsBuiltIn(mOp) && !isFunctionCall()) || BuiltInGroup::IsMath(mOp))
    {
        if (areChildrenConstQualified())
        {
            mType.setQualifier(EvqConst);
        }
    }

    propagatePrecision(derivePrecision());
}

bool TIntermAggregate::areChildrenConstQualified()
{
    for (TIntermNode *arg : mArguments)
    {
        TIntermTyped *typedArg = arg->getAsTyped();
        if (typedArg && typedArg->getQualifier() != EvqConst)
        {
            return false;
        }
    }
    return true;
}

// Derive precision from children nodes
TPrecision TIntermAggregate::derivePrecision() const
{
    if (getBasicType() == EbtBool || getBasicType() == EbtVoid || getBasicType() == EbtStruct)
    {
        return EbpUndefined;
    }

    // For AST function calls, take the qualifier from the declared one.
    if (isFunctionCall())
    {
        return mType.getPrecision();
    }

    // Some built-ins explicitly specify their precision.
    switch (mOp)
    {
        case EOpBitfieldExtract:
            return mArguments[0]->getAsTyped()->getPrecision();
        case EOpBitfieldInsert:
            return GetHigherPrecision(mArguments[0]->getAsTyped()->getPrecision(),
                                      mArguments[1]->getAsTyped()->getPrecision());
        case EOpTextureSize:
        case EOpImageSize:
        case EOpUaddCarry:
        case EOpUsubBorrow:
        case EOpUmulExtended:
        case EOpImulExtended:
        case EOpFrexp:
        case EOpLdexp:
            return EbpHigh;
        default:
            break;
    }

    // The rest of the math operations and constructors get their precision from their arguments.
    if (BuiltInGroup::IsMath(mOp) || mOp == EOpConstruct)
    {
        TPrecision precision = EbpUndefined;
        for (TIntermNode *argument : mArguments)
        {
            precision = GetHigherPrecision(argument->getAsTyped()->getPrecision(), precision);
        }
        return precision;
    }

    // Atomic operations return highp.
    if (BuiltInGroup::IsImageAtomic(mOp) || BuiltInGroup::IsAtomicCounter(mOp) ||
        BuiltInGroup::IsAtomicMemory(mOp))
    {
        return EbpHigh;
    }

    // Texture functions return the same precision as that of the sampler (textureSize returns
    // highp, but that's handled above).  imageLoad similar takes the precision of the image.  The
    // same is true for dFd*, interpolateAt* and subpassLoad operations.
    if (BuiltInGroup::IsTexture(mOp) || BuiltInGroup::IsImageLoad(mOp) ||
        BuiltInGroup::IsDerivativesFS(mOp) || BuiltInGroup::IsInterpolationFS(mOp) ||
        mOp == EOpSubpassLoad || mOp == EOpInterpolateAtCenter)
    {
        return mArguments[0]->getAsTyped()->getPrecision();
    }

    // Every possibility must be explicitly handled.
    return EbpUndefined;
}

// Propagate precision to children nodes that don't already have it defined.
void TIntermAggregate::propagatePrecision(TPrecision precision)
{
    mType.setPrecision(precision);

    // For constructors, propagate precision to arguments.
    if (isConstructor())
    {
        for (TIntermNode *arg : mArguments)
        {
            PropagatePrecisionIfApplicable(arg->getAsTyped(), precision);
        }
        return;
    }

    // For function calls, propagate precision of each parameter to its corresponding argument.
    if (isFunctionCall())
    {
        for (size_t paramIndex = 0; paramIndex < mFunction->getParamCount(); ++paramIndex)
        {
            const TVariable *paramVariable = mFunction->getParam(paramIndex);
            PropagatePrecisionIfApplicable(mArguments[paramIndex]->getAsTyped(),
                                           paramVariable->getType().getPrecision());
        }
        return;
    }

    // Some built-ins explicitly specify the precision of their parameters.
    switch (mOp)
    {
        case EOpUaddCarry:
        case EOpUsubBorrow:
        case EOpUmulExtended:
        case EOpImulExtended:
            PropagatePrecisionIfApplicable(mArguments[0]->getAsTyped(), EbpHigh);
            PropagatePrecisionIfApplicable(mArguments[1]->getAsTyped(), EbpHigh);
            break;
        case EOpFindMSB:
        case EOpFrexp:
        case EOpLdexp:
            PropagatePrecisionIfApplicable(mArguments[0]->getAsTyped(), EbpHigh);
            break;
        default:
            break;
    }
}

const char *TIntermAggregate::functionName() const
{
    ASSERT(!isConstructor());
    switch (mOp)
    {
        case EOpCallInternalRawFunction:
        case EOpCallFunctionInAST:
            return mFunction->name().data();
        default:
            if (BuiltInGroup::IsBuiltIn(mOp))
            {
                return mFunction->name().data();
            }
            return GetOperatorString(mOp);
    }
}

bool TIntermAggregate::hasConstantValue() const
{
    if (!isConstructor())
    {
        return false;
    }
    for (TIntermNode *constructorArg : mArguments)
    {
        if (!constructorArg->getAsTyped()->hasConstantValue())
        {
            return false;
        }
    }
    return true;
}

bool TIntermAggregate::isConstantNullValue() const
{
    if (!isConstructor())
    {
        return false;
    }
    for (TIntermNode *constructorArg : mArguments)
    {
        if (!constructorArg->getAsTyped()->isConstantNullValue())
        {
            return false;
        }
    }
    return true;
}

const TConstantUnion *TIntermAggregate::getConstantValue() const
{
    if (!hasConstantValue())
    {
        return nullptr;
    }
    ASSERT(isConstructor());
    ASSERT(mArguments.size() > 0u);

    TConstantUnion *constArray = nullptr;
    if (isArray())
    {
        size_t elementSize = mArguments.front()->getAsTyped()->getType().getObjectSize();
        constArray         = new TConstantUnion[elementSize * getOutermostArraySize()];

        size_t elementOffset = 0u;
        for (TIntermNode *constructorArg : mArguments)
        {
            const TConstantUnion *elementConstArray =
                constructorArg->getAsTyped()->getConstantValue();
            ASSERT(elementConstArray);
            size_t elementSizeBytes = sizeof(TConstantUnion) * elementSize;
            memcpy(static_cast<void *>(&constArray[elementOffset]),
                   static_cast<const void *>(elementConstArray), elementSizeBytes);
            elementOffset += elementSize;
        }
        return constArray;
    }

    size_t resultSize    = getType().getObjectSize();
    constArray           = new TConstantUnion[resultSize];
    TBasicType basicType = getBasicType();

    size_t resultIndex = 0u;

    if (mArguments.size() == 1u)
    {
        TIntermNode *argument                       = mArguments.front();
        TIntermTyped *argumentTyped                 = argument->getAsTyped();
        const TConstantUnion *argumentConstantValue = argumentTyped->getConstantValue();
        // Check the special case of constructing a matrix diagonal from a single scalar,
        // or a vector from a single scalar.
        if (argumentTyped->getType().getObjectSize() == 1u)
        {
            if (isMatrix())
            {
                const uint8_t resultCols = getType().getCols();
                const uint8_t resultRows = getType().getRows();
                for (uint8_t col = 0; col < resultCols; ++col)
                {
                    for (uint8_t row = 0; row < resultRows; ++row)
                    {
                        if (col == row)
                        {
                            constArray[resultIndex].cast(basicType, argumentConstantValue[0]);
                        }
                        else
                        {
                            constArray[resultIndex].setFConst(0.0f);
                        }
                        ++resultIndex;
                    }
                }
            }
            else
            {
                while (resultIndex < resultSize)
                {
                    constArray[resultIndex].cast(basicType, argumentConstantValue[0]);
                    ++resultIndex;
                }
            }
            ASSERT(resultIndex == resultSize);
            return constArray;
        }
        else if (isMatrix() && argumentTyped->isMatrix())
        {
            // The special case of constructing a matrix from a matrix.
            const uint8_t argumentCols = argumentTyped->getType().getCols();
            const uint8_t argumentRows = argumentTyped->getType().getRows();
            const uint8_t resultCols   = getType().getCols();
            const uint8_t resultRows   = getType().getRows();
            for (uint8_t col = 0; col < resultCols; ++col)
            {
                for (uint8_t row = 0; row < resultRows; ++row)
                {
                    if (col < argumentCols && row < argumentRows)
                    {
                        constArray[resultIndex].cast(
                            basicType, argumentConstantValue[col * argumentRows + row]);
                    }
                    else if (col == row)
                    {
                        constArray[resultIndex].setFConst(1.0f);
                    }
                    else
                    {
                        constArray[resultIndex].setFConst(0.0f);
                    }
                    ++resultIndex;
                }
            }
            ASSERT(resultIndex == resultSize);
            return constArray;
        }
    }

    for (TIntermNode *argument : mArguments)
    {
        TIntermTyped *argumentTyped                 = argument->getAsTyped();
        size_t argumentSize                         = argumentTyped->getType().getObjectSize();
        const TConstantUnion *argumentConstantValue = argumentTyped->getConstantValue();
        for (size_t i = 0u; i < argumentSize; ++i)
        {
            if (resultIndex >= resultSize)
                break;
            constArray[resultIndex].cast(basicType, argumentConstantValue[i]);
            ++resultIndex;
        }
    }
    ASSERT(resultIndex == resultSize);
    return constArray;
}

bool TIntermAggregate::hasSideEffects() const
{
    if (getQualifier() == EvqConst)
    {
        return false;
    }

    // If the function itself is known to have a side effect, the expression has a side effect.
    const bool calledFunctionHasSideEffects =
        mFunction != nullptr && !mFunction->isKnownToNotHaveSideEffects();

    if (calledFunctionHasSideEffects)
    {
        return true;
    }

    // Otherwise it only has a side effect if one of the arguments does.
    for (TIntermNode *arg : mArguments)
    {
        if (arg->getAsTyped()->hasSideEffects())
        {
            return true;
        }
    }
    return false;
}

void TIntermBlock::appendStatement(TIntermNode *statement)
{
    // Declaration nodes with no children can appear if it was an empty declaration or if all the
    // declarators just added constants to the symbol table instead of generating code. We still
    // need to add the declaration to the AST in that case because it might be relevant to the
    // validity of switch/case.
    if (statement != nullptr)
    {
        mStatements.push_back(statement);
    }
}

void TIntermBlock::insertStatement(size_t insertPosition, TIntermNode *statement)
{
    ASSERT(statement != nullptr);
    mStatements.insert(mStatements.begin() + insertPosition, statement);
}

void TIntermDeclaration::appendDeclarator(TIntermTyped *declarator)
{
    ASSERT(declarator != nullptr);
    ASSERT(declarator->getAsSymbolNode() != nullptr ||
           (declarator->getAsBinaryNode() != nullptr &&
            declarator->getAsBinaryNode()->getOp() == EOpInitialize));
    ASSERT(mDeclarators.empty() ||
           declarator->getType().sameNonArrayType(mDeclarators.back()->getAsTyped()->getType()));
    mDeclarators.push_back(declarator);
}

size_t TIntermTernary::getChildCount() const
{
    return 3;
}

TIntermNode *TIntermTernary::getChildNode(size_t index) const
{
    ASSERT(index < 3);
    if (index == 0)
    {
        return mCondition;
    }
    if (index == 1)
    {
        return mTrueExpression;
    }
    return mFalseExpression;
}

bool TIntermTernary::replaceChildNode(TIntermNode *original, TIntermNode *replacement)
{
    REPLACE_IF_IS(mCondition, getAsTyped, original, replacement);
    REPLACE_IF_IS(mTrueExpression, getAsTyped, original, replacement);
    REPLACE_IF_IS(mFalseExpression, getAsTyped, original, replacement);
    return false;
}

size_t TIntermIfElse::getChildCount() const
{
    return 1 + (mTrueBlock ? 1 : 0) + (mFalseBlock ? 1 : 0);
}

TIntermNode *TIntermIfElse::getChildNode(size_t index) const
{
    if (index == 0)
    {
        return mCondition;
    }
    if (mTrueBlock && index == 1)
    {
        return mTrueBlock;
    }
    return mFalseBlock;
}

bool TIntermIfElse::replaceChildNode(TIntermNode *original, TIntermNode *replacement)
{
    REPLACE_IF_IS(mCondition, getAsTyped, original, replacement);
    REPLACE_IF_IS(mTrueBlock, getAsBlock, original, replacement);
    REPLACE_IF_IS(mFalseBlock, getAsBlock, original, replacement);
    return false;
}

size_t TIntermSwitch::getChildCount() const
{
    return 2;
}

TIntermNode *TIntermSwitch::getChildNode(size_t index) const
{
    ASSERT(index < 2);
    if (index == 0)
    {
        return mInit;
    }
    return mStatementList;
}

bool TIntermSwitch::replaceChildNode(TIntermNode *original, TIntermNode *replacement)
{
    REPLACE_IF_IS(mInit, getAsTyped, original, replacement);
    REPLACE_IF_IS(mStatementList, getAsBlock, original, replacement);
    ASSERT(mStatementList);
    return false;
}

TIntermCase::TIntermCase(const TIntermCase &node) : TIntermCase(node.mCondition->deepCopy()) {}

size_t TIntermCase::getChildCount() const
{
    return (mCondition ? 1 : 0);
}

TIntermNode *TIntermCase::getChildNode(size_t index) const
{
    ASSERT(index == 0);
    ASSERT(mCondition);
    return mCondition;
}

bool TIntermCase::replaceChildNode(TIntermNode *original, TIntermNode *replacement)
{
    REPLACE_IF_IS(mCondition, getAsTyped, original, replacement);
    return false;
}

TIntermTyped::TIntermTyped() : mIsPrecise(false) {}
TIntermTyped::TIntermTyped(const TIntermTyped &node) : TIntermTyped()
{
    // Copy constructor is disallowed for TIntermNode in order to disallow it for subclasses that
    // don't explicitly allow it, so normal TIntermNode constructor is used to construct the copy.
    // We need to manually copy any fields of TIntermNode.
    mLine = node.mLine;

    // Once deteremined, the tree is not expected to transform.
    ASSERT(!mIsPrecise);
}

bool TIntermTyped::hasConstantValue() const
{
    return false;
}

bool TIntermTyped::isConstantNullValue() const
{
    return false;
}

const TConstantUnion *TIntermTyped::getConstantValue() const
{
    return nullptr;
}

TPrecision TIntermTyped::derivePrecision() const
{
    UNREACHABLE();
    return EbpUndefined;
}

void TIntermTyped::propagatePrecision(TPrecision precision)
{
    UNREACHABLE();
}

TIntermConstantUnion::TIntermConstantUnion(const TIntermConstantUnion &node)
    : TIntermExpression(node)
{
    mUnionArrayPointer = node.mUnionArrayPointer;
}

TIntermFunctionPrototype::TIntermFunctionPrototype(const TFunction *function)
    : TIntermTyped(), mFunction(function)
{
    ASSERT(mFunction->symbolType() != SymbolType::Empty);
}

const TType &TIntermFunctionPrototype::getType() const
{
    return mFunction->getReturnType();
}

TIntermAggregate::TIntermAggregate(const TIntermAggregate &node)
    : TIntermOperator(node),
      mUseEmulatedFunction(node.mUseEmulatedFunction),
      mFunction(node.mFunction)
{
    for (TIntermNode *arg : node.mArguments)
    {
        TIntermTyped *typedArg = arg->getAsTyped();
        ASSERT(typedArg != nullptr);
        TIntermTyped *argCopy = typedArg->deepCopy();
        mArguments.push_back(argCopy);
    }
}

TIntermAggregate *TIntermAggregate::shallowCopy() const
{
    TIntermSequence copySeq;
    copySeq.insert(copySeq.begin(), getSequence()->begin(), getSequence()->end());
    TIntermAggregate *copyNode = new TIntermAggregate(mFunction, mType, mOp, &copySeq);
    copyNode->setLine(mLine);
    return copyNode;
}

TIntermSwizzle::TIntermSwizzle(const TIntermSwizzle &node) : TIntermExpression(node)
{
    TIntermTyped *operandCopy = node.mOperand->deepCopy();
    ASSERT(operandCopy != nullptr);
    mOperand                   = operandCopy;
    mSwizzleOffsets            = node.mSwizzleOffsets;
    mHasFoldedDuplicateOffsets = node.mHasFoldedDuplicateOffsets;
}

TIntermBinary::TIntermBinary(const TIntermBinary &node) : TIntermOperator(node)
{
    TIntermTyped *leftCopy  = node.mLeft->deepCopy();
    TIntermTyped *rightCopy = node.mRight->deepCopy();
    ASSERT(leftCopy != nullptr && rightCopy != nullptr);
    mLeft  = leftCopy;
    mRight = rightCopy;
}

TIntermUnary::TIntermUnary(const TIntermUnary &node)
    : TIntermOperator(node),
      mUseEmulatedFunction(node.mUseEmulatedFunction),
      mFunction(node.mFunction)
{
    TIntermTyped *operandCopy = node.mOperand->deepCopy();
    ASSERT(operandCopy != nullptr);
    mOperand = operandCopy;
}

TIntermTernary::TIntermTernary(const TIntermTernary &node) : TIntermExpression(node)
{
    TIntermTyped *conditionCopy = node.mCondition->deepCopy();
    TIntermTyped *trueCopy      = node.mTrueExpression->deepCopy();
    TIntermTyped *falseCopy     = node.mFalseExpression->deepCopy();
    ASSERT(conditionCopy != nullptr && trueCopy != nullptr && falseCopy != nullptr);
    mCondition       = conditionCopy;
    mTrueExpression  = trueCopy;
    mFalseExpression = falseCopy;
}

bool TIntermOperator::isAssignment() const
{
    return IsAssignment(mOp);
}

bool TIntermOperator::isMultiplication() const
{
    switch (mOp)
    {
        case EOpMul:
        case EOpMatrixTimesMatrix:
        case EOpMatrixTimesVector:
        case EOpMatrixTimesScalar:
        case EOpVectorTimesMatrix:
        case EOpVectorTimesScalar:
            return true;
        default:
            return false;
    }
}

bool TIntermOperator::isConstructor() const
{
    return (mOp == EOpConstruct);
}

bool TIntermOperator::isFunctionCall() const
{
    switch (mOp)
    {
        case EOpCallFunctionInAST:
        case EOpCallInternalRawFunction:
            return true;
        default:
            return false;
    }
}

TOperator TIntermBinary::GetMulOpBasedOnOperands(const TType &left, const TType &right)
{
    if (left.isMatrix())
    {
        if (right.isMatrix())
        {
            return EOpMatrixTimesMatrix;
        }
        else
        {
            if (right.isVector())
            {
                return EOpMatrixTimesVector;
            }
            else
            {
                return EOpMatrixTimesScalar;
            }
        }
    }
    else
    {
        if (right.isMatrix())
        {
            if (left.isVector())
            {
                return EOpVectorTimesMatrix;
            }
            else
            {
                return EOpMatrixTimesScalar;
            }
        }
        else
        {
            // Neither operand is a matrix.
            if (left.isVector() == right.isVector())
            {
                // Leave as component product.
                return EOpMul;
            }
            else
            {
                return EOpVectorTimesScalar;
            }
        }
    }
}

TOperator TIntermBinary::GetMulAssignOpBasedOnOperands(const TType &left, const TType &right)
{
    if (left.isMatrix())
    {
        if (right.isMatrix())
        {
            return EOpMatrixTimesMatrixAssign;
        }
        else
        {
            // right should be scalar, but this may not be validated yet.
            return EOpMatrixTimesScalarAssign;
        }
    }
    else
    {
        if (right.isMatrix())
        {
            // Left should be a vector, but this may not be validated yet.
            return EOpVectorTimesMatrixAssign;
        }
        else
        {
            // Neither operand is a matrix.
            if (left.isVector() == right.isVector())
            {
                // Leave as component product.
                return EOpMulAssign;
            }
            else
            {
                // left should be vector and right should be scalar, but this may not be validated
                // yet.
                return EOpVectorTimesScalarAssign;
            }
        }
    }
}

//
// Make sure the type of a unary operator is appropriate for its
// combination of operation and operand type.
//
void TIntermUnary::promote()
{
    if (mOp == EOpArrayLength)
    {
        // Special case: the qualifier of .length() doesn't depend on the operand qualifier.
        setType(TType(EbtInt, EbpHigh, EvqConst));
        return;
    }

    TQualifier resultQualifier = EvqTemporary;
    if (mOperand->getQualifier() == EvqConst)
        resultQualifier = EvqConst;

    TType resultType = mOperand->getType();
    resultType.setQualifier(resultQualifier);

    // Result is an intermediate value, so make sure it's identified as such.
    resultType.setInterfaceBlock(nullptr);

    // Override type properties for special built-ins.  Precision is determined later by
    // |derivePrecision|.
    switch (mOp)
    {
        case EOpFloatBitsToInt:
            resultType.setBasicType(EbtInt);
            break;
        case EOpFloatBitsToUint:
            resultType.setBasicType(EbtUInt);
            break;
        case EOpIntBitsToFloat:
        case EOpUintBitsToFloat:
            resultType.setBasicType(EbtFloat);
            break;
        case EOpPackSnorm2x16:
        case EOpPackUnorm2x16:
        case EOpPackHalf2x16:
        case EOpPackUnorm4x8:
        case EOpPackSnorm4x8:
            resultType.setBasicType(EbtUInt);
            resultType.setPrimarySize(1);
            break;
        case EOpUnpackSnorm2x16:
        case EOpUnpackUnorm2x16:
        case EOpUnpackHalf2x16:
            resultType.setBasicType(EbtFloat);
            resultType.setPrimarySize(2);
            break;
        case EOpUnpackUnorm4x8:
        case EOpUnpackSnorm4x8:
            resultType.setBasicType(EbtFloat);
            resultType.setPrimarySize(4);
            break;
        case EOpAny:
        case EOpAll:
            resultType.setBasicType(EbtBool);
            resultType.setPrimarySize(1);
            break;
        case EOpLength:
        case EOpDeterminant:
            resultType.setBasicType(EbtFloat);
            resultType.setPrimarySize(1);
            resultType.setSecondarySize(1);
            break;
        case EOpTranspose:
            ASSERT(resultType.getBasicType() == EbtFloat);
            resultType.setPrimarySize(mOperand->getType().getRows());
            resultType.setSecondarySize(mOperand->getType().getCols());
            break;
        case EOpIsinf:
        case EOpIsnan:
            resultType.setBasicType(EbtBool);
            break;
        case EOpBitCount:
        case EOpFindLSB:
        case EOpFindMSB:
            resultType.setBasicType(EbtInt);
            break;
        default:
            break;
    }

    setType(resultType);
    propagatePrecision(derivePrecision());
}

// Derive precision from children nodes
TPrecision TIntermUnary::derivePrecision() const
{
    // Unary operators generally derive their precision from their operand, except for a few
    // built-ins where this is overriden.
    switch (mOp)
    {
        case EOpArrayLength:
        case EOpFloatBitsToInt:
        case EOpFloatBitsToUint:
        case EOpIntBitsToFloat:
        case EOpUintBitsToFloat:
        case EOpPackSnorm2x16:
        case EOpPackUnorm2x16:
        case EOpPackHalf2x16:
        case EOpPackUnorm4x8:
        case EOpPackSnorm4x8:
        case EOpUnpackSnorm2x16:
        case EOpUnpackUnorm2x16:
        case EOpBitfieldReverse:
            return EbpHigh;
        case EOpUnpackHalf2x16:
        case EOpUnpackUnorm4x8:
        case EOpUnpackSnorm4x8:
            return EbpMedium;
        case EOpBitCount:
        case EOpFindLSB:
        case EOpFindMSB:
            return EbpLow;
        case EOpAny:
        case EOpAll:
        case EOpIsinf:
        case EOpIsnan:
            return EbpUndefined;
        default:
            return mOperand->getPrecision();
    }
}

void TIntermUnary::propagatePrecision(TPrecision precision)
{
    mType.setPrecision(precision);

    // Generally precision of the operand and the precision of the result match.  A few built-ins
    // are exceptional.
    switch (mOp)
    {
        case EOpArrayLength:
        case EOpPackSnorm2x16:
        case EOpPackUnorm2x16:
        case EOpPackUnorm4x8:
        case EOpPackSnorm4x8:
        case EOpPackHalf2x16:
        case EOpBitCount:
        case EOpFindLSB:
        case EOpFindMSB:
        case EOpIsinf:
        case EOpIsnan:
            // Precision of result does not affect the operand in any way.
            break;
        case EOpFloatBitsToInt:
        case EOpFloatBitsToUint:
        case EOpIntBitsToFloat:
        case EOpUintBitsToFloat:
        case EOpUnpackSnorm2x16:
        case EOpUnpackUnorm2x16:
        case EOpUnpackUnorm4x8:
        case EOpUnpackSnorm4x8:
        case EOpUnpackHalf2x16:
        case EOpBitfieldReverse:
            PropagatePrecisionIfApplicable(mOperand, EbpHigh);
            break;
        default:
            PropagatePrecisionIfApplicable(mOperand, precision);
    }
}

TIntermSwizzle::TIntermSwizzle(TIntermTyped *operand, const TVector<int> &swizzleOffsets)
    : TIntermExpression(TType(EbtFloat, EbpUndefined)),
      mOperand(operand),
      mSwizzleOffsets(swizzleOffsets),
      mHasFoldedDuplicateOffsets(false)
{
    ASSERT(mOperand);
    ASSERT(mOperand->getType().isVector());
    ASSERT(mSwizzleOffsets.size() <= 4);
    promote();
}

TIntermUnary::TIntermUnary(TOperator op, TIntermTyped *operand, const TFunction *function)
    : TIntermOperator(op), mOperand(operand), mUseEmulatedFunction(false), mFunction(function)
{
    ASSERT(mOperand);
    ASSERT(!BuiltInGroup::IsBuiltIn(op) || (function != nullptr && function->getBuiltInOp() == op));
    promote();
}

TIntermBinary::TIntermBinary(TOperator op, TIntermTyped *left, TIntermTyped *right)
    : TIntermOperator(op), mLeft(left), mRight(right)
{
    ASSERT(mLeft);
    ASSERT(mRight);
    promote();
}

TIntermBinary *TIntermBinary::CreateComma(TIntermTyped *left,
                                          TIntermTyped *right,
                                          int shaderVersion)
{
    TIntermBinary *node = new TIntermBinary(EOpComma, left, right);
    node->getTypePointer()->setQualifier(GetCommaQualifier(shaderVersion, left, right));
    return node;
}

TIntermGlobalQualifierDeclaration::TIntermGlobalQualifierDeclaration(TIntermSymbol *symbol,
                                                                     bool isPrecise,
                                                                     const TSourceLoc &line)
    : TIntermNode(), mSymbol(symbol), mIsPrecise(isPrecise)
{
    ASSERT(symbol);
    setLine(line);
}

TIntermGlobalQualifierDeclaration::TIntermGlobalQualifierDeclaration(
    const TIntermGlobalQualifierDeclaration &node)
    : TIntermGlobalQualifierDeclaration(static_cast<TIntermSymbol *>(node.mSymbol->deepCopy()),
                                        node.mIsPrecise,
                                        node.mLine)
{}

TIntermTernary::TIntermTernary(TIntermTyped *cond,
                               TIntermTyped *trueExpression,
                               TIntermTyped *falseExpression)
    : TIntermExpression(trueExpression->getType()),
      mCondition(cond),
      mTrueExpression(trueExpression),
      mFalseExpression(falseExpression)
{
    ASSERT(mCondition);
    ASSERT(mTrueExpression);
    ASSERT(mFalseExpression);
    getTypePointer()->setQualifier(
        TIntermTernary::DetermineQualifier(cond, trueExpression, falseExpression));

    propagatePrecision(derivePrecision());
}

TIntermLoop::TIntermLoop(TLoopType type,
                         TIntermNode *init,
                         TIntermTyped *cond,
                         TIntermTyped *expr,
                         TIntermBlock *body)
    : mType(type), mInit(init), mCond(cond), mExpr(expr), mBody(EnsureBody(body))
{
    // Declaration nodes with no children can appear if all the declarators just added constants to
    // the symbol table instead of generating code. They're no-ops so don't add them to the tree.
    if (mInit && mInit->getAsDeclarationNode() &&
        mInit->getAsDeclarationNode()->getSequence()->empty())
    {
        mInit = nullptr;
    }
}

TIntermLoop::TIntermLoop(const TIntermLoop &node)
    : TIntermLoop(node.mType,
                  node.mInit ? node.mInit->deepCopy() : nullptr,
                  node.mCond ? node.mCond->deepCopy() : nullptr,
                  node.mExpr ? node.mExpr->deepCopy() : nullptr,
                  node.mBody->deepCopy())
{}

TIntermIfElse::TIntermIfElse(TIntermTyped *cond, TIntermBlock *trueB, TIntermBlock *falseB)
    : TIntermNode(), mCondition(cond), mTrueBlock(trueB), mFalseBlock(falseB)
{
    ASSERT(mCondition);
    // Prune empty false blocks so that there won't be unnecessary operations done on it.
    if (mFalseBlock && mFalseBlock->getSequence()->empty())
    {
        mFalseBlock = nullptr;
    }
}

TIntermIfElse::TIntermIfElse(const TIntermIfElse &node)
    : TIntermIfElse(node.mCondition->deepCopy(),
                    node.mTrueBlock->deepCopy(),
                    node.mFalseBlock ? node.mFalseBlock->deepCopy() : nullptr)
{}

TIntermSwitch::TIntermSwitch(TIntermTyped *init, TIntermBlock *statementList)
    : TIntermNode(), mInit(init), mStatementList(statementList)
{
    ASSERT(mInit);
    ASSERT(mStatementList);
}

TIntermSwitch::TIntermSwitch(const TIntermSwitch &node)
    : TIntermSwitch(node.mInit->deepCopy(), node.mStatementList->deepCopy())
{}

void TIntermSwitch::setStatementList(TIntermBlock *statementList)
{
    ASSERT(statementList);
    mStatementList = statementList;
}

// static
TQualifier TIntermTernary::DetermineQualifier(TIntermTyped *cond,
                                              TIntermTyped *trueExpression,
                                              TIntermTyped *falseExpression)
{
    if (cond->getQualifier() == EvqConst && trueExpression->getQualifier() == EvqConst &&
        falseExpression->getQualifier() == EvqConst)
    {
        return EvqConst;
    }
    return EvqTemporary;
}

// Derive precision from children nodes
TPrecision TIntermTernary::derivePrecision() const
{
    return GetHigherPrecision(mTrueExpression->getPrecision(), mFalseExpression->getPrecision());
}

void TIntermTernary::propagatePrecision(TPrecision precision)
{
    mType.setPrecision(precision);

    PropagatePrecisionIfApplicable(mTrueExpression, precision);
    PropagatePrecisionIfApplicable(mFalseExpression, precision);
}

TIntermTyped *TIntermTernary::fold(TDiagnostics * /* diagnostics */)
{
    if (mCondition->getAsConstantUnion())
    {
        if (mCondition->getAsConstantUnion()->getBConst(0))
        {
            return mTrueExpression;
        }
        else
        {
            return mFalseExpression;
        }
    }
    return this;
}

void TIntermSwizzle::promote()
{
    TQualifier resultQualifier = EvqTemporary;
    if (mOperand->getQualifier() == EvqConst)
        resultQualifier = EvqConst;

    size_t numFields = mSwizzleOffsets.size();
    setType(TType(mOperand->getBasicType(), EbpUndefined, resultQualifier,
                  static_cast<uint8_t>(numFields)));
    propagatePrecision(derivePrecision());
}

// Derive precision from children nodes
TPrecision TIntermSwizzle::derivePrecision() const
{
    return mOperand->getPrecision();
}

void TIntermSwizzle::propagatePrecision(TPrecision precision)
{
    mType.setPrecision(precision);

    PropagatePrecisionIfApplicable(mOperand, precision);
}

bool TIntermSwizzle::hasDuplicateOffsets() const
{
    if (mHasFoldedDuplicateOffsets)
    {
        return true;
    }
    int offsetCount[4] = {0u, 0u, 0u, 0u};
    for (const auto offset : mSwizzleOffsets)
    {
        offsetCount[offset]++;
        if (offsetCount[offset] > 1)
        {
            return true;
        }
    }
    return false;
}

void TIntermSwizzle::setHasFoldedDuplicateOffsets(bool hasFoldedDuplicateOffsets)
{
    mHasFoldedDuplicateOffsets = hasFoldedDuplicateOffsets;
}

bool TIntermSwizzle::offsetsMatch(int offset) const
{
    return mSwizzleOffsets.size() == 1 && mSwizzleOffsets[0] == offset;
}

void TIntermSwizzle::writeOffsetsAsXYZW(TInfoSinkBase *out) const
{
    for (const int offset : mSwizzleOffsets)
    {
        switch (offset)
        {
            case 0:
                *out << "x";
                break;
            case 1:
                *out << "y";
                break;
            case 2:
                *out << "z";
                break;
            case 3:
                *out << "w";
                break;
            default:
                UNREACHABLE();
        }
    }
}

TQualifier TIntermBinary::GetCommaQualifier(int shaderVersion,
                                            const TIntermTyped *left,
                                            const TIntermTyped *right)
{
    // ESSL3.00 section 12.43: The result of a sequence operator is not a constant-expression.
    if (shaderVersion >= 300 || left->getQualifier() != EvqConst ||
        right->getQualifier() != EvqConst)
    {
        return EvqTemporary;
    }
    return EvqConst;
}

// Establishes the type of the result of the binary operation.
void TIntermBinary::promote()
{
    ASSERT(!isMultiplication() ||
           mOp == GetMulOpBasedOnOperands(mLeft->getType(), mRight->getType()));

    // Comma is handled as a special case. Note that the comma node qualifier depends on the shader
    // version and so is not being set here.
    if (mOp == EOpComma)
    {
        setType(mRight->getType());
        return;
    }

    // Base assumption:  just make the type the same as the left
    // operand.  Then only deviations from this need be coded.
    setType(mLeft->getType());

    TQualifier resultQualifier = EvqConst;
    // Binary operations results in temporary variables unless both
    // operands are const.  If initializing a specialization constant, make the declarator also
    // EvqSpecConst.
    const bool isSpecConstInit = mOp == EOpInitialize && mLeft->getQualifier() == EvqSpecConst;
    const bool isEitherNonConst =
        mLeft->getQualifier() != EvqConst || mRight->getQualifier() != EvqConst;
    if (!isSpecConstInit && isEitherNonConst)
    {
        resultQualifier = EvqTemporary;
        getTypePointer()->setQualifier(EvqTemporary);
    }

    // Result is an intermediate value, so make sure it's identified as such.  That's not true for
    // interface block arrays being indexed.
    if (mOp != EOpIndexDirect && mOp != EOpIndexIndirect)
    {
        getTypePointer()->setInterfaceBlock(nullptr);
    }

    // Handle indexing ops.
    switch (mOp)
    {
        case EOpIndexDirect:
        case EOpIndexIndirect:
            if (mLeft->isArray())
            {
                mType.toArrayElementType();
            }
            else if (mLeft->isMatrix())
            {
                mType.toMatrixColumnType();
            }
            else if (mLeft->isVector())
            {
                mType.toComponentType();
            }
            else
            {
                UNREACHABLE();
            }
            return;
        case EOpIndexDirectStruct:
        {
            const TFieldList &fields = mLeft->getType().getStruct()->fields();
            const int fieldIndex     = mRight->getAsConstantUnion()->getIConst(0);
            setType(*fields[fieldIndex]->type());
            getTypePointer()->setQualifier(resultQualifier);
            return;
        }
        case EOpIndexDirectInterfaceBlock:
        {
            const TFieldList &fields = mLeft->getType().getInterfaceBlock()->fields();
            const int fieldIndex     = mRight->getAsConstantUnion()->getIConst(0);
            setType(*fields[fieldIndex]->type());
            getTypePointer()->setQualifier(resultQualifier);
            return;
        }
        default:
            break;
    }

    ASSERT(mLeft->isArray() == mRight->isArray());

    const uint8_t nominalSize = std::max(mLeft->getNominalSize(), mRight->getNominalSize());

    switch (mOp)
    {
        case EOpMul:
            break;
        case EOpMatrixTimesScalar:
            if (mRight->isMatrix())
            {
                getTypePointer()->setPrimarySize(mRight->getCols());
                getTypePointer()->setSecondarySize(mRight->getRows());
            }
            break;
        case EOpMatrixTimesVector:
            getTypePointer()->setPrimarySize(mLeft->getRows());
            getTypePointer()->setSecondarySize(1);
            break;
        case EOpMatrixTimesMatrix:
            getTypePointer()->setPrimarySize(mRight->getCols());
            getTypePointer()->setSecondarySize(mLeft->getRows());
            break;
        case EOpVectorTimesScalar:
            getTypePointer()->setPrimarySize(nominalSize);
            break;
        case EOpVectorTimesMatrix:
            getTypePointer()->setPrimarySize(mRight->getCols());
            ASSERT(getType().getSecondarySize() == 1);
            break;
        case EOpMulAssign:
        case EOpVectorTimesScalarAssign:
        case EOpVectorTimesMatrixAssign:
        case EOpMatrixTimesScalarAssign:
        case EOpMatrixTimesMatrixAssign:
            ASSERT(mOp == GetMulAssignOpBasedOnOperands(mLeft->getType(), mRight->getType()));
            break;
        case EOpAssign:
        case EOpInitialize:
            ASSERT((mLeft->getNominalSize() == mRight->getNominalSize()) &&
                   (mLeft->getSecondarySize() == mRight->getSecondarySize()));
            break;
        case EOpAdd:
        case EOpSub:
        case EOpDiv:
        case EOpIMod:
        case EOpBitShiftLeft:
        case EOpBitShiftRight:
        case EOpBitwiseAnd:
        case EOpBitwiseXor:
        case EOpBitwiseOr:
        case EOpAddAssign:
        case EOpSubAssign:
        case EOpDivAssign:
        case EOpIModAssign:
        case EOpBitShiftLeftAssign:
        case EOpBitShiftRightAssign:
        case EOpBitwiseAndAssign:
        case EOpBitwiseXorAssign:
        case EOpBitwiseOrAssign:
        {
            ASSERT(!mLeft->isArray() && !mRight->isArray());
            const uint8_t secondarySize =
                std::max(mLeft->getSecondarySize(), mRight->getSecondarySize());
            getTypePointer()->setPrimarySize(nominalSize);
            getTypePointer()->setSecondarySize(secondarySize);
            break;
        }
        case EOpEqual:
        case EOpNotEqual:
        case EOpLessThan:
        case EOpGreaterThan:
        case EOpLessThanEqual:
        case EOpGreaterThanEqual:
            ASSERT((mLeft->getNominalSize() == mRight->getNominalSize()) &&
                   (mLeft->getSecondarySize() == mRight->getSecondarySize()));
            setType(TType(EbtBool, EbpUndefined, resultQualifier));
            break;

        //
        // And and Or operate on conditionals
        //
        case EOpLogicalAnd:
        case EOpLogicalXor:
        case EOpLogicalOr:
            ASSERT(mLeft->getBasicType() == EbtBool && mRight->getBasicType() == EbtBool);
            break;

        case EOpIndexDirect:
        case EOpIndexIndirect:
        case EOpIndexDirectInterfaceBlock:
        case EOpIndexDirectStruct:
            // These ops should be already fully handled.
            UNREACHABLE();
            break;
        default:
            UNREACHABLE();
            break;
    }

    propagatePrecision(derivePrecision());
}

// Derive precision from children nodes
TPrecision TIntermBinary::derivePrecision() const
{
    // Assignments use the type and precision of the lvalue-expression
    // GLSL ES spec section 5.8: Assignments
    // "The assignment operator stores the value of rvalue-expression into the l-value and returns
    // an r-value with the type and precision of lvalue-expression."
    if (IsAssignment(mOp))
    {
        return mLeft->getPrecision();
    }

    const TPrecision higherPrecision =
        GetHigherPrecision(mLeft->getPrecision(), mRight->getPrecision());

    switch (mOp)
    {
        case EOpComma:
            // Comma takes the right node's value.
            return mRight->getPrecision();

        case EOpIndexDirect:
        case EOpIndexIndirect:
        case EOpBitShiftLeft:
        case EOpBitShiftRight:
            // When indexing an array, the precision of the array is preserved (which is the left
            // node).
            // For shift operations, the precision is derived from the expression being shifted
            // (which is also the left node).
            return mLeft->getPrecision();

        case EOpIndexDirectStruct:
        case EOpIndexDirectInterfaceBlock:
        {
            // When selecting the field of a block, the precision is taken from the field's
            // declaration.
            const TFieldList &fields = mOp == EOpIndexDirectStruct
                                           ? mLeft->getType().getStruct()->fields()
                                           : mLeft->getType().getInterfaceBlock()->fields();
            const int fieldIndex     = mRight->getAsConstantUnion()->getIConst(0);
            return fields[fieldIndex]->type()->getPrecision();
        }

        case EOpEqual:
        case EOpNotEqual:
        case EOpLessThan:
        case EOpGreaterThan:
        case EOpLessThanEqual:
        case EOpGreaterThanEqual:
        case EOpLogicalAnd:
        case EOpLogicalXor:
        case EOpLogicalOr:
            // No precision specified on bool results.
            return EbpUndefined;

        default:
            // All other operations are evaluated at the higher of the two operands' precisions.
            return higherPrecision;
    }
}

void TIntermBinary::propagatePrecision(TPrecision precision)
{
    getTypePointer()->setPrecision(precision);

    if (mOp != EOpComma)
    {
        PropagatePrecisionIfApplicable(mLeft, precision);
    }

    if (mOp != EOpIndexDirect && mOp != EOpIndexIndirect && mOp != EOpIndexDirectStruct &&
        mOp != EOpIndexDirectInterfaceBlock)
    {
        PropagatePrecisionIfApplicable(mRight, precision);
    }

    // For indices, always apply highp.  This is purely for the purpose of making sure constant and
    // constructor nodes are also given a precision, so if they are hoisted to a temp variable,
    // there would be a precision to apply to that variable.
    if (mOp == EOpIndexDirect || mOp == EOpIndexIndirect)
    {
        PropagatePrecisionIfApplicable(mRight, EbpHigh);
    }
}

bool TIntermConstantUnion::hasConstantValue() const
{
    return true;
}

bool TIntermConstantUnion::isConstantNullValue() const
{
    const size_t size = mType.getObjectSize();
    for (size_t index = 0; index < size; ++index)
    {
        if (!mUnionArrayPointer[index].isZero())
        {
            return false;
        }
    }
    return true;
}

const TConstantUnion *TIntermConstantUnion::getConstantValue() const
{
    return mUnionArrayPointer;
}

const TConstantUnion *TIntermConstantUnion::FoldIndexing(const TType &type,
                                                         const TConstantUnion *constArray,
                                                         int index)
{
    if (type.isArray())
    {
        ASSERT(index < static_cast<int>(type.getOutermostArraySize()));
        TType arrayElementType(type);
        arrayElementType.toArrayElementType();
        size_t arrayElementSize = arrayElementType.getObjectSize();
        return &constArray[arrayElementSize * index];
    }
    else if (type.isMatrix())
    {
        ASSERT(index < type.getCols());
        const uint8_t size = type.getRows();
        return &constArray[size * index];
    }
    else if (type.isVector())
    {
        ASSERT(index < type.getNominalSize());
        return &constArray[index];
    }
    else
    {
        UNREACHABLE();
        return nullptr;
    }
}

TIntermTyped *TIntermSwizzle::fold(TDiagnostics * /* diagnostics */)
{
    TIntermSwizzle *operandSwizzle = mOperand->getAsSwizzleNode();
    if (operandSwizzle)
    {
        // We need to fold the two swizzles into one, so that repeated swizzling can't cause stack
        // overflow in ParseContext::checkCanBeLValue().
        bool hadDuplicateOffsets = operandSwizzle->hasDuplicateOffsets();
        TVector<int> foldedOffsets;
        for (int offset : mSwizzleOffsets)
        {
            // Offset should already be validated.
            ASSERT(static_cast<size_t>(offset) < operandSwizzle->mSwizzleOffsets.size());
            foldedOffsets.push_back(operandSwizzle->mSwizzleOffsets[offset]);
        }
        operandSwizzle->mSwizzleOffsets = foldedOffsets;
        operandSwizzle->setType(getType());
        operandSwizzle->setHasFoldedDuplicateOffsets(hadDuplicateOffsets);
        return operandSwizzle;
    }
    TIntermConstantUnion *operandConstant = mOperand->getAsConstantUnion();
    if (operandConstant == nullptr)
    {
        return this;
    }

    TConstantUnion *constArray = new TConstantUnion[mSwizzleOffsets.size()];
    for (size_t i = 0; i < mSwizzleOffsets.size(); ++i)
    {
        constArray[i] = *TIntermConstantUnion::FoldIndexing(
            operandConstant->getType(), operandConstant->getConstantValue(), mSwizzleOffsets.at(i));
    }
    return CreateFoldedNode(constArray, this);
}

TIntermTyped *TIntermBinary::fold(TDiagnostics *diagnostics)
{
    const TConstantUnion *rightConstant = mRight->getConstantValue();
    switch (mOp)
    {
        case EOpComma:
        {
            if (mLeft->hasSideEffects())
            {
                return this;
            }
            return mRight;
        }
        case EOpIndexDirect:
        case EOpIndexDirectStruct:
        {
            if (rightConstant == nullptr)
            {
                return this;
            }
            size_t index                    = static_cast<size_t>(rightConstant->getIConst());
            TIntermAggregate *leftAggregate = mLeft->getAsAggregate();
            if (leftAggregate && leftAggregate->isConstructor() && leftAggregate->isArray() &&
                !leftAggregate->hasSideEffects())
            {
                ASSERT(index < leftAggregate->getSequence()->size());
                // This transformation can't add complexity as we're eliminating the constructor
                // entirely.
                return leftAggregate->getSequence()->at(index)->getAsTyped();
            }

            // If the indexed value is already a constant union, we can't increase duplication of
            // data by folding the indexing. Also fold the node in case it's generally beneficial to
            // replace this type of node with a constant union even if that would mean duplicating
            // data.
            if (mLeft->getAsConstantUnion() || getType().canReplaceWithConstantUnion())
            {
                const TConstantUnion *constantValue = getConstantValue();
                if (constantValue != nullptr)
                {
                    return CreateFoldedNode(constantValue, this);
                }
            }

            // If the indexed value is a swizzle, then the swizzle can be adjusted instead.
            TIntermSwizzle *leftSwizzle = mLeft->getAsSwizzleNode();
            if (leftSwizzle != nullptr)
            {
                const TVector<int> &swizzleOffsets = leftSwizzle->getSwizzleOffsets();
                ASSERT(index < swizzleOffsets.size());

                int remappedIndex = swizzleOffsets[index];
                return new TIntermSwizzle(leftSwizzle->getOperand(), {remappedIndex});
            }

            return this;
        }
        case EOpIndexIndirect:
        case EOpIndexDirectInterfaceBlock:
        case EOpInitialize:
            // Can never be constant folded.
            return this;
        default:
        {
            if (rightConstant == nullptr)
            {
                return this;
            }
            const TConstantUnion *leftConstant = mLeft->getConstantValue();
            if (leftConstant == nullptr)
            {
                return this;
            }
            const TConstantUnion *constArray =
                TIntermConstantUnion::FoldBinary(mOp, leftConstant, mLeft->getType(), rightConstant,
                                                 mRight->getType(), diagnostics, mLeft->getLine());
            if (!constArray)
            {
                return this;
            }
            return CreateFoldedNode(constArray, this);
        }
    }
}

bool TIntermBinary::hasConstantValue() const
{
    switch (mOp)
    {
        case EOpIndexDirect:
        case EOpIndexDirectStruct:
        {
            if (mLeft->hasConstantValue() && mRight->hasConstantValue())
            {
                return true;
            }
            break;
        }
        default:
            break;
    }
    return false;
}

const TConstantUnion *TIntermBinary::getConstantValue() const
{
    if (!hasConstantValue())
    {
        return nullptr;
    }

    const TConstantUnion *leftConstantValue   = mLeft->getConstantValue();
    int index                                 = mRight->getConstantValue()->getIConst();
    const TConstantUnion *constIndexingResult = nullptr;
    if (mOp == EOpIndexDirect)
    {
        constIndexingResult =
            TIntermConstantUnion::FoldIndexing(mLeft->getType(), leftConstantValue, index);
    }
    else
    {
        ASSERT(mOp == EOpIndexDirectStruct);
        const TFieldList &fields = mLeft->getType().getStruct()->fields();

        size_t previousFieldsSize = 0;
        for (int i = 0; i < index; ++i)
        {
            previousFieldsSize += fields[i]->type()->getObjectSize();
        }
        constIndexingResult = leftConstantValue + previousFieldsSize;
    }
    return constIndexingResult;
}

const ImmutableString &TIntermBinary::getIndexStructFieldName() const
{
    ASSERT(mOp == EOpIndexDirectStruct);

    const TType &lhsType        = mLeft->getType();
    const TStructure *structure = lhsType.getStruct();
    const int index             = mRight->getAsConstantUnion()->getIConst(0);

    return structure->fields()[index]->name();
}

TIntermTyped *TIntermUnary::fold(TDiagnostics *diagnostics)
{
    TConstantUnion *constArray = nullptr;

    if (mOp == EOpArrayLength)
    {
        // The size of runtime-sized arrays may only be determined at runtime.
        // This operation is folded for clip/cull distance arrays in RemoveArrayLengthMethod.
        if (mOperand->hasSideEffects() || mOperand->getType().isUnsizedArray() ||
            mOperand->getQualifier() == EvqClipDistance ||
            mOperand->getQualifier() == EvqCullDistance)
        {
            return this;
        }
        constArray = new TConstantUnion[1];
        constArray->setIConst(mOperand->getOutermostArraySize());
    }
    else
    {
        TIntermConstantUnion *operandConstant = mOperand->getAsConstantUnion();
        if (operandConstant == nullptr)
        {
            return this;
        }

        switch (mOp)
        {
            case EOpAny:
            case EOpAll:
            case EOpLength:
            case EOpTranspose:
            case EOpDeterminant:
            case EOpInverse:
            case EOpPackSnorm2x16:
            case EOpUnpackSnorm2x16:
            case EOpPackUnorm2x16:
            case EOpUnpackUnorm2x16:
            case EOpPackHalf2x16:
            case EOpUnpackHalf2x16:
            case EOpPackUnorm4x8:
            case EOpPackSnorm4x8:
            case EOpUnpackUnorm4x8:
            case EOpUnpackSnorm4x8:
                constArray = operandConstant->foldUnaryNonComponentWise(mOp);
                break;
            default:
                constArray = operandConstant->foldUnaryComponentWise(mOp, mFunction, diagnostics);
                break;
        }
    }
    if (constArray == nullptr)
    {
        return this;
    }
    return CreateFoldedNode(constArray, this);
}

TIntermTyped *TIntermAggregate::fold(TDiagnostics *diagnostics)
{
    // Make sure that all params are constant before actual constant folding.
    for (auto *param : *getSequence())
    {
        if (param->getAsConstantUnion() == nullptr)
        {
            return this;
        }
    }
    const TConstantUnion *constArray = nullptr;
    if (isConstructor())
    {
        if (mType.canReplaceWithConstantUnion())
        {
            constArray = getConstantValue();
            if (constArray && mType.getBasicType() == EbtUInt)
            {
                // Check if we converted a negative float to uint and issue a warning in that case.
                size_t sizeRemaining = mType.getObjectSize();
                for (TIntermNode *arg : mArguments)
                {
                    TIntermTyped *typedArg = arg->getAsTyped();
                    if (typedArg->getBasicType() == EbtFloat)
                    {
                        const TConstantUnion *argValue = typedArg->getConstantValue();
                        size_t castSize =
                            std::min(typedArg->getType().getObjectSize(), sizeRemaining);
                        for (size_t i = 0; i < castSize; ++i)
                        {
                            if (argValue[i].getFConst() < 0.0f)
                            {
                                // ESSL 3.00.6 section 5.4.1.
                                diagnostics->warning(
                                    mLine, "casting a negative float to uint is undefined",
                                    mType.getBuiltInTypeNameString());
                            }
                        }
                    }
                    sizeRemaining -= typedArg->getType().getObjectSize();
                }
            }
        }
    }
    else if (CanFoldAggregateBuiltInOp(mOp))
    {
        constArray = TIntermConstantUnion::FoldAggregateBuiltIn(this, diagnostics);
    }
    if (constArray == nullptr)
    {
        return this;
    }
    return CreateFoldedNode(constArray, this);
}

//
// The fold functions see if an operation on a constant can be done in place,
// without generating run-time code.
//
// Returns the constant value to keep using or nullptr.
//
const TConstantUnion *TIntermConstantUnion::FoldBinary(TOperator op,
                                                       const TConstantUnion *leftArray,
                                                       const TType &leftType,
                                                       const TConstantUnion *rightArray,
                                                       const TType &rightType,
                                                       TDiagnostics *diagnostics,
                                                       const TSourceLoc &line)
{
    ASSERT(leftArray && rightArray);

    size_t objectSize = leftType.getObjectSize();

    // for a case like float f = vec4(2, 3, 4, 5) + 1.2;
    if (rightType.getObjectSize() == 1 && objectSize > 1)
    {
        rightArray = Vectorize(*rightArray, objectSize);
    }
    else if (rightType.getObjectSize() > 1 && objectSize == 1)
    {
        // for a case like float f = 1.2 + vec4(2, 3, 4, 5);
        leftArray  = Vectorize(*leftArray, rightType.getObjectSize());
        objectSize = rightType.getObjectSize();
    }

    TConstantUnion *resultArray = nullptr;

    switch (op)
    {
        case EOpAdd:
            resultArray = new TConstantUnion[objectSize];
            for (size_t i = 0; i < objectSize; i++)
                resultArray[i] =
                    TConstantUnion::add(leftArray[i], rightArray[i], diagnostics, line);
            break;
        case EOpSub:
            resultArray = new TConstantUnion[objectSize];
            for (size_t i = 0; i < objectSize; i++)
                resultArray[i] =
                    TConstantUnion::sub(leftArray[i], rightArray[i], diagnostics, line);
            break;

        case EOpMul:
        case EOpVectorTimesScalar:
        case EOpMatrixTimesScalar:
            resultArray = new TConstantUnion[objectSize];
            for (size_t i = 0; i < objectSize; i++)
                resultArray[i] =
                    TConstantUnion::mul(leftArray[i], rightArray[i], diagnostics, line);
            break;

        case EOpMatrixTimesMatrix:
        {
            // TODO(jmadll): This code should check for overflows.
            ASSERT(leftType.getBasicType() == EbtFloat && rightType.getBasicType() == EbtFloat);

            const uint8_t leftCols   = leftType.getCols();
            const uint8_t leftRows   = leftType.getRows();
            const uint8_t rightCols  = rightType.getCols();
            const uint8_t rightRows  = rightType.getRows();
            const uint8_t resultCols = rightCols;
            const uint8_t resultRows = leftRows;

            resultArray = new TConstantUnion[resultCols * resultRows];
            for (uint8_t row = 0; row < resultRows; row++)
            {
                for (uint8_t column = 0; column < resultCols; column++)
                {
                    resultArray[resultRows * column + row].setFConst(0.0f);
                    for (uint8_t i = 0; i < leftCols; i++)
                    {
                        resultArray[resultRows * column + row].setFConst(
                            resultArray[resultRows * column + row].getFConst() +
                            leftArray[i * leftRows + row].getFConst() *
                                rightArray[column * rightRows + i].getFConst());
                    }
                }
            }
        }
        break;

        case EOpDiv:
        case EOpIMod:
        {
            resultArray = new TConstantUnion[objectSize];
            for (size_t i = 0; i < objectSize; i++)
            {
                if (leftType.getBasicType() == EbtFloat)
                {
                    // Float division requested, possibly with implicit conversion
                    ASSERT(op == EOpDiv);
                    float dividend = leftArray[i].getFConst();
                    float divisor  = rightArray[i].getFConst();

                    if (divisor == 0.0f)
                    {
                        if (dividend == 0.0f)
                        {
                            diagnostics->warning(line,
                                                 "Zero divided by zero during constant "
                                                 "folding generated NaN",
                                                 "/");
                            resultArray[i].setFConst(std::numeric_limits<float>::quiet_NaN());
                        }
                        else
                        {
                            diagnostics->warning(line, "Divide by zero during constant folding",
                                                 "/");
                            bool negativeResult = std::signbit(dividend) != std::signbit(divisor);
                            resultArray[i].setFConst(negativeResult
                                                         ? -std::numeric_limits<float>::infinity()
                                                         : std::numeric_limits<float>::infinity());
                        }
                    }
                    else if (gl::isInf(dividend) && gl::isInf(divisor))
                    {
                        diagnostics->warning(line,
                                             "Infinity divided by infinity during constant "
                                             "folding generated NaN",
                                             "/");
                        resultArray[i].setFConst(std::numeric_limits<float>::quiet_NaN());
                    }
                    else
                    {
                        float result = dividend / divisor;
                        if (!gl::isInf(dividend) && gl::isInf(result))
                        {
                            diagnostics->warning(
                                line, "Constant folded division overflowed to infinity", "/");
                        }
                        resultArray[i].setFConst(result);
                    }
                }
                else
                {
                    // Types are either both int or both uint
                    switch (leftType.getBasicType())
                    {
                        case EbtInt:
                        {
                            if (rightArray[i] == 0)
                            {
                                diagnostics->warning(
                                    line, "Divide by zero error during constant folding", "/");
                                resultArray[i].setIConst(INT_MAX);
                            }
                            else
                            {
                                int lhs     = leftArray[i].getIConst();
                                int divisor = rightArray[i].getIConst();
                                if (op == EOpDiv)
                                {
                                    // Check for the special case where the minimum
                                    // representable number is divided by -1. If left alone this
                                    // leads to integer overflow in C++. 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."
                                    if (lhs == -0x7fffffff - 1 && divisor == -1)
                                    {
                                        resultArray[i].setIConst(0x7fffffff);
                                    }
                                    else
                                    {
                                        resultArray[i].setIConst(lhs / divisor);
                                    }
                                }
                                else
                                {
                                    ASSERT(op == EOpIMod);
                                    if (lhs < 0 || divisor < 0)
                                    {
                                        // ESSL 3.00.6 section 5.9: Results of modulus are
                                        // undefined when either one of the operands is
                                        // negative.
                                        diagnostics->warning(line,
                                                             "Negative modulus operator operand "
                                                             "encountered during constant folding. "
                                                             "Results are undefined.",
                                                             "%");
                                        resultArray[i].setIConst(0);
                                    }
                                    else
                                    {
                                        resultArray[i].setIConst(lhs % divisor);
                                    }
                                }
                            }
                            break;
                        }
                        case EbtUInt:
                        {
                            if (rightArray[i] == 0)
                            {
                                diagnostics->warning(
                                    line, "Divide by zero error during constant folding", "/");
                                resultArray[i].setUConst(UINT_MAX);
                            }
                            else
                            {
                                if (op == EOpDiv)
                                {
                                    resultArray[i].setUConst(leftArray[i].getUConst() /
                                                             rightArray[i].getUConst());
                                }
                                else
                                {
                                    ASSERT(op == EOpIMod);
                                    resultArray[i].setUConst(leftArray[i].getUConst() %
                                                             rightArray[i].getUConst());
                                }
                            }
                            break;
                        }
                        default:
                            UNREACHABLE();
                            return nullptr;
                    }
                }
            }
        }
        break;

        case EOpMatrixTimesVector:
        {
            // TODO(jmadll): This code should check for overflows.
            ASSERT(rightType.getBasicType() == EbtFloat);

            const uint8_t matrixCols = leftType.getCols();
            const uint8_t matrixRows = leftType.getRows();

            resultArray = new TConstantUnion[matrixRows];

            for (uint8_t matrixRow = 0; matrixRow < matrixRows; matrixRow++)
            {
                resultArray[matrixRow].setFConst(0.0f);
                for (uint8_t col = 0; col < matrixCols; col++)
                {
                    resultArray[matrixRow].setFConst(
                        resultArray[matrixRow].getFConst() +
                        leftArray[col * matrixRows + matrixRow].getFConst() *
                            rightArray[col].getFConst());
                }
            }
        }
        break;

        case EOpVectorTimesMatrix:
        {
            // TODO(jmadll): This code should check for overflows.
            ASSERT(leftType.getBasicType() == EbtFloat);

            const uint8_t matrixCols = rightType.getCols();
            const uint8_t matrixRows = rightType.getRows();

            resultArray = new TConstantUnion[matrixCols];

            for (uint8_t matrixCol = 0; matrixCol < matrixCols; matrixCol++)
            {
                resultArray[matrixCol].setFConst(0.0f);
                for (uint8_t matrixRow = 0; matrixRow < matrixRows; matrixRow++)
                {
                    resultArray[matrixCol].setFConst(
                        resultArray[matrixCol].getFConst() +
                        leftArray[matrixRow].getFConst() *
                            rightArray[matrixCol * matrixRows + matrixRow].getFConst());
                }
            }
        }
        break;

        case EOpLogicalAnd:
        {
            resultArray = new TConstantUnion[objectSize];
            for (size_t i = 0; i < objectSize; i++)
            {
                resultArray[i] = leftArray[i] && rightArray[i];
            }
        }
        break;

        case EOpLogicalOr:
        {
            resultArray = new TConstantUnion[objectSize];
            for (size_t i = 0; i < objectSize; i++)
            {
                resultArray[i] = leftArray[i] || rightArray[i];
            }
        }
        break;

        case EOpLogicalXor:
        {
            ASSERT(leftType.getBasicType() == EbtBool);
            resultArray = new TConstantUnion[objectSize];
            for (size_t i = 0; i < objectSize; i++)
            {
                resultArray[i].setBConst(leftArray[i] != rightArray[i]);
            }
        }
        break;

        case EOpBitwiseAnd:
            resultArray = new TConstantUnion[objectSize];
            for (size_t i = 0; i < objectSize; i++)
                resultArray[i] = leftArray[i] & rightArray[i];
            break;
        case EOpBitwiseXor:
            resultArray = new TConstantUnion[objectSize];
            for (size_t i = 0; i < objectSize; i++)
                resultArray[i] = leftArray[i] ^ rightArray[i];
            break;
        case EOpBitwiseOr:
            resultArray = new TConstantUnion[objectSize];
            for (size_t i = 0; i < objectSize; i++)
                resultArray[i] = leftArray[i] | rightArray[i];
            break;
        case EOpBitShiftLeft:
            resultArray = new TConstantUnion[objectSize];
            for (size_t i = 0; i < objectSize; i++)
                resultArray[i] =
                    TConstantUnion::lshift(leftArray[i], rightArray[i], diagnostics, line);
            break;
        case EOpBitShiftRight:
            resultArray = new TConstantUnion[objectSize];
            for (size_t i = 0; i < objectSize; i++)
                resultArray[i] =
                    TConstantUnion::rshift(leftArray[i], rightArray[i], diagnostics, line);
            break;

        case EOpLessThan:
            ASSERT(objectSize == 1);
            resultArray = new TConstantUnion[1];
            resultArray->setBConst(*leftArray < *rightArray);
            break;

        case EOpGreaterThan:
            ASSERT(objectSize == 1);
            resultArray = new TConstantUnion[1];
            resultArray->setBConst(*leftArray > *rightArray);
            break;

        case EOpLessThanEqual:
            ASSERT(objectSize == 1);
            resultArray = new TConstantUnion[1];
            resultArray->setBConst(!(*leftArray > *rightArray));
            break;

        case EOpGreaterThanEqual:
            ASSERT(objectSize == 1);
            resultArray = new TConstantUnion[1];
            resultArray->setBConst(!(*leftArray < *rightArray));
            break;

        case EOpEqual:
        case EOpNotEqual:
        {
            resultArray = new TConstantUnion[1];
            bool equal  = true;
            for (size_t i = 0; i < objectSize; i++)
            {
                if (leftArray[i] != rightArray[i])
                {
                    equal = false;
                    break;  // break out of for loop
                }
            }
            if (op == EOpEqual)
            {
                resultArray->setBConst(equal);
            }
            else
            {
                resultArray->setBConst(!equal);
            }
        }
        break;

        default:
            UNREACHABLE();
            return nullptr;
    }
    return resultArray;
}

// The fold functions do operations on a constant at GLSL compile time, without generating run-time
// code. Returns the constant value to keep using. Nullptr should not be returned.
TConstantUnion *TIntermConstantUnion::foldUnaryNonComponentWise(TOperator op)
{
    // Do operations where the return type may have a different number of components compared to the
    // operand type.

    const TConstantUnion *operandArray = getConstantValue();
    ASSERT(operandArray);

    size_t objectSize           = getType().getObjectSize();
    TConstantUnion *resultArray = nullptr;
    switch (op)
    {
        case EOpAny:
            ASSERT(getType().getBasicType() == EbtBool);
            resultArray = new TConstantUnion();
            resultArray->setBConst(false);
            for (size_t i = 0; i < objectSize; i++)
            {
                if (operandArray[i].getBConst())
                {
                    resultArray->setBConst(true);
                    break;
                }
            }
            break;

        case EOpAll:
            ASSERT(getType().getBasicType() == EbtBool);
            resultArray = new TConstantUnion();
            resultArray->setBConst(true);
            for (size_t i = 0; i < objectSize; i++)
            {
                if (!operandArray[i].getBConst())
                {
                    resultArray->setBConst(false);
                    break;
                }
            }
            break;

        case EOpLength:
            ASSERT(getType().getBasicType() == EbtFloat);
            resultArray = new TConstantUnion();
            resultArray->setFConst(VectorLength(operandArray, objectSize));
            break;

        case EOpTranspose:
        {
            ASSERT(getType().getBasicType() == EbtFloat);
            resultArray = new TConstantUnion[objectSize];
            angle::Matrix<float> result =
                GetMatrix(operandArray, getType().getRows(), getType().getCols()).transpose();
            SetUnionArrayFromMatrix(result, resultArray);
            break;
        }

        case EOpDeterminant:
        {
            ASSERT(getType().getBasicType() == EbtFloat);
            const uint8_t size = getType().getNominalSize();
            ASSERT(size >= 2 && size <= 4);
            resultArray = new TConstantUnion();
            resultArray->setFConst(GetMatrix(operandArray, size).determinant());
            break;
        }

        case EOpInverse:
        {
            ASSERT(getType().getBasicType() == EbtFloat);
            const uint8_t size = getType().getNominalSize();
            ASSERT(size >= 2 && size <= 4);
            resultArray                 = new TConstantUnion[objectSize];
            angle::Matrix<float> result = GetMatrix(operandArray, size).inverse();
            SetUnionArrayFromMatrix(result, resultArray);
            break;
        }

        case EOpPackSnorm2x16:
            ASSERT(getType().getBasicType() == EbtFloat);
            ASSERT(getType().getNominalSize() == 2);
            resultArray = new TConstantUnion();
            resultArray->setUConst(
                gl::packSnorm2x16(operandArray[0].getFConst(), operandArray[1].getFConst()));
            break;

        case EOpUnpackSnorm2x16:
        {
            ASSERT(getType().getBasicType() == EbtUInt);
            resultArray = new TConstantUnion[2];
            float f1, f2;
            gl::unpackSnorm2x16(operandArray[0].getUConst(), &f1, &f2);
            resultArray[0].setFConst(f1);
            resultArray[1].setFConst(f2);
            break;
        }

        case EOpPackUnorm2x16:
            ASSERT(getType().getBasicType() == EbtFloat);
            ASSERT(getType().getNominalSize() == 2);
            resultArray = new TConstantUnion();
            resultArray->setUConst(
                gl::packUnorm2x16(operandArray[0].getFConst(), operandArray[1].getFConst()));
            break;

        case EOpUnpackUnorm2x16:
        {
            ASSERT(getType().getBasicType() == EbtUInt);
            resultArray = new TConstantUnion[2];
            float f1, f2;
            gl::unpackUnorm2x16(operandArray[0].getUConst(), &f1, &f2);
            resultArray[0].setFConst(f1);
            resultArray[1].setFConst(f2);
            break;
        }

        case EOpPackHalf2x16:
            ASSERT(getType().getBasicType() == EbtFloat);
            ASSERT(getType().getNominalSize() == 2);
            resultArray = new TConstantUnion();
            resultArray->setUConst(
                gl::packHalf2x16(operandArray[0].getFConst(), operandArray[1].getFConst()));
            break;

        case EOpUnpackHalf2x16:
        {
            ASSERT(getType().getBasicType() == EbtUInt);
            resultArray = new TConstantUnion[2];
            float f1, f2;
            gl::unpackHalf2x16(operandArray[0].getUConst(), &f1, &f2);
            resultArray[0].setFConst(f1);
            resultArray[1].setFConst(f2);
            break;
        }

        case EOpPackUnorm4x8:
        {
            ASSERT(getType().getBasicType() == EbtFloat);
            resultArray = new TConstantUnion();
            resultArray->setUConst(
                gl::PackUnorm4x8(operandArray[0].getFConst(), operandArray[1].getFConst(),
                                 operandArray[2].getFConst(), operandArray[3].getFConst()));
            break;
        }
        case EOpPackSnorm4x8:
        {
            ASSERT(getType().getBasicType() == EbtFloat);
            resultArray = new TConstantUnion();
            resultArray->setUConst(
                gl::PackSnorm4x8(operandArray[0].getFConst(), operandArray[1].getFConst(),
                                 operandArray[2].getFConst(), operandArray[3].getFConst()));
            break;
        }
        case EOpUnpackUnorm4x8:
        {
            ASSERT(getType().getBasicType() == EbtUInt);
            resultArray = new TConstantUnion[4];
            float f[4];
            gl::UnpackUnorm4x8(operandArray[0].getUConst(), f);
            for (size_t i = 0; i < 4; ++i)
            {
                resultArray[i].setFConst(f[i]);
            }
            break;
        }
        case EOpUnpackSnorm4x8:
        {
            ASSERT(getType().getBasicType() == EbtUInt);
            resultArray = new TConstantUnion[4];
            float f[4];
            gl::UnpackSnorm4x8(operandArray[0].getUConst(), f);
            for (size_t i = 0; i < 4; ++i)
            {
                resultArray[i].setFConst(f[i]);
            }
            break;
        }

        default:
            UNREACHABLE();
            break;
    }

    return resultArray;
}

TConstantUnion *TIntermConstantUnion::foldUnaryComponentWise(TOperator op,
                                                             const TFunction *function,
                                                             TDiagnostics *diagnostics)
{
    // Do unary operations where each component of the result is computed based on the corresponding
    // component of the operand. Also folds normalize, though the divisor in that case takes all
    // components into account.

    const TConstantUnion *operandArray = getConstantValue();
    ASSERT(operandArray);

    size_t objectSize = getType().getObjectSize();

    TConstantUnion *resultArray = new TConstantUnion[objectSize];
    for (size_t i = 0; i < objectSize; i++)
    {
        switch (op)
        {
            case EOpNegative:
                switch (getType().getBasicType())
                {
                    case EbtFloat:
                        resultArray[i].setFConst(-operandArray[i].getFConst());
                        break;
                    case EbtInt:
                        if (operandArray[i] == std::numeric_limits<int>::min())
                        {
                            // The minimum representable integer doesn't have a positive
                            // counterpart, rather the negation overflows and in ESSL is supposed to
                            // wrap back to the minimum representable integer. Make sure that we
                            // don't actually let the negation overflow, which has undefined
                            // behavior in C++.
                            resultArray[i].setIConst(std::numeric_limits<int>::min());
                        }
                        else
                        {
                            resultArray[i].setIConst(-operandArray[i].getIConst());
                        }
                        break;
                    case EbtUInt:
                        if (operandArray[i] == 0x80000000u)
                        {
                            resultArray[i].setUConst(0x80000000u);
                        }
                        else
                        {
                            resultArray[i].setUConst(static_cast<unsigned int>(
                                -static_cast<int>(operandArray[i].getUConst())));
                        }
                        break;
                    default:
                        UNREACHABLE();
                        return nullptr;
                }
                break;

            case EOpPositive:
                switch (getType().getBasicType())
                {
                    case EbtFloat:
                        resultArray[i].setFConst(operandArray[i].getFConst());
                        break;
                    case EbtInt:
                        resultArray[i].setIConst(operandArray[i].getIConst());
                        break;
                    case EbtUInt:
                        resultArray[i].setUConst(static_cast<unsigned int>(
                            static_cast<int>(operandArray[i].getUConst())));
                        break;
                    default:
                        UNREACHABLE();
                        return nullptr;
                }
                break;

            case EOpLogicalNot:
                switch (getType().getBasicType())
                {
                    case EbtBool:
                        resultArray[i].setBConst(!operandArray[i].getBConst());
                        break;
                    default:
                        UNREACHABLE();
                        return nullptr;
                }
                break;

            case EOpBitwiseNot:
                switch (getType().getBasicType())
                {
                    case EbtInt:
                        resultArray[i].setIConst(~operandArray[i].getIConst());
                        break;
                    case EbtUInt:
                        resultArray[i].setUConst(~operandArray[i].getUConst());
                        break;
                    default:
                        UNREACHABLE();
                        return nullptr;
                }
                break;

            case EOpRadians:
                ASSERT(getType().getBasicType() == EbtFloat);
                resultArray[i].setFConst(kDegreesToRadiansMultiplier * operandArray[i].getFConst());
                break;

            case EOpDegrees:
                ASSERT(getType().getBasicType() == EbtFloat);
                resultArray[i].setFConst(kRadiansToDegreesMultiplier * operandArray[i].getFConst());
                break;

            case EOpSin:
                foldFloatTypeUnary(operandArray[i], &sinf, &resultArray[i]);
                break;

            case EOpCos:
                foldFloatTypeUnary(operandArray[i], &cosf, &resultArray[i]);
                break;

            case EOpTan:
                foldFloatTypeUnary(operandArray[i], &tanf, &resultArray[i]);
                break;

            case EOpAsin:
                // For asin(x), results are undefined if |x| > 1, we are choosing to set result to
                // 0.
                if (fabsf(operandArray[i].getFConst()) > 1.0f)
                    UndefinedConstantFoldingError(getLine(), function, getType().getBasicType(),
                                                  diagnostics, &resultArray[i]);
                else
                    foldFloatTypeUnary(operandArray[i], &asinf, &resultArray[i]);
                break;

            case EOpAcos:
                // For acos(x), results are undefined if |x| > 1, we are choosing to set result to
                // 0.
                if (fabsf(operandArray[i].getFConst()) > 1.0f)
                    UndefinedConstantFoldingError(getLine(), function, getType().getBasicType(),
                                                  diagnostics, &resultArray[i]);
                else
                    foldFloatTypeUnary(operandArray[i], &acosf, &resultArray[i]);
                break;

            case EOpAtan:
                foldFloatTypeUnary(operandArray[i], &atanf, &resultArray[i]);
                break;

            case EOpSinh:
                foldFloatTypeUnary(operandArray[i], &sinhf, &resultArray[i]);
                break;

            case EOpCosh:
                foldFloatTypeUnary(operandArray[i], &coshf, &resultArray[i]);
                break;

            case EOpTanh:
                foldFloatTypeUnary(operandArray[i], &tanhf, &resultArray[i]);
                break;

            case EOpAsinh:
                foldFloatTypeUnary(operandArray[i], &asinhf, &resultArray[i]);
                break;

            case EOpAcosh:
                // For acosh(x), results are undefined if x < 1, we are choosing to set result to 0.
                if (operandArray[i].getFConst() < 1.0f)
                    UndefinedConstantFoldingError(getLine(), function, getType().getBasicType(),
                                                  diagnostics, &resultArray[i]);
                else
                    foldFloatTypeUnary(operandArray[i], &acoshf, &resultArray[i]);
                break;

            case EOpAtanh:
                // For atanh(x), results are undefined if |x| >= 1, we are choosing to set result to
                // 0.
                if (fabsf(operandArray[i].getFConst()) >= 1.0f)
                    UndefinedConstantFoldingError(getLine(), function, getType().getBasicType(),
                                                  diagnostics, &resultArray[i]);
                else
                    foldFloatTypeUnary(operandArray[i], &atanhf, &resultArray[i]);
                break;

            case EOpAbs:
                switch (getType().getBasicType())
                {
                    case EbtFloat:
                        resultArray[i].setFConst(fabsf(operandArray[i].getFConst()));
                        break;
                    case EbtInt:
                        resultArray[i].setIConst(abs(operandArray[i].getIConst()));
                        break;
                    default:
                        UNREACHABLE();
                        return nullptr;
                }
                break;

            case EOpSign:
                switch (getType().getBasicType())
                {
                    case EbtFloat:
                    {
                        float fConst  = operandArray[i].getFConst();
                        float fResult = 0.0f;
                        if (fConst > 0.0f)
                            fResult = 1.0f;
                        else if (fConst < 0.0f)
                            fResult = -1.0f;
                        resultArray[i].setFConst(fResult);
                        break;
                    }
                    case EbtInt:
                    {
                        int iConst  = operandArray[i].getIConst();
                        int iResult = 0;
                        if (iConst > 0)
                            iResult = 1;
                        else if (iConst < 0)
                            iResult = -1;
                        resultArray[i].setIConst(iResult);
                        break;
                    }
                    default:
                        UNREACHABLE();
                        return nullptr;
                }
                break;

            case EOpFloor:
                foldFloatTypeUnary(operandArray[i], &floorf, &resultArray[i]);
                break;

            case EOpTrunc:
                foldFloatTypeUnary(operandArray[i], &truncf, &resultArray[i]);
                break;

            case EOpRound:
                foldFloatTypeUnary(operandArray[i], &roundf, &resultArray[i]);
                break;

            case EOpRoundEven:
            {
                ASSERT(getType().getBasicType() == EbtFloat);
                float x = operandArray[i].getFConst();
                float result;
                float fractPart = modff(x, &result);
                if (fabsf(fractPart) == 0.5f)
                    result = 2.0f * roundf(x / 2.0f);
                else
                    result = roundf(x);
                resultArray[i].setFConst(result);
                break;
            }

            case EOpCeil:
                foldFloatTypeUnary(operandArray[i], &ceilf, &resultArray[i]);
                break;

            case EOpFract:
            {
                ASSERT(getType().getBasicType() == EbtFloat);
                float x = operandArray[i].getFConst();
                resultArray[i].setFConst(x - floorf(x));
                break;
            }

            case EOpIsnan:
                ASSERT(getType().getBasicType() == EbtFloat);
                resultArray[i].setBConst(gl::isNaN(operandArray[i].getFConst()));
                break;

            case EOpIsinf:
                ASSERT(getType().getBasicType() == EbtFloat);
                resultArray[i].setBConst(gl::isInf(operandArray[i].getFConst()));
                break;

            case EOpFloatBitsToInt:
                ASSERT(getType().getBasicType() == EbtFloat);
                resultArray[i].setIConst(gl::bitCast<int32_t>(operandArray[i].getFConst()));
                break;

            case EOpFloatBitsToUint:
                ASSERT(getType().getBasicType() == EbtFloat);
                resultArray[i].setUConst(gl::bitCast<uint32_t>(operandArray[i].getFConst()));
                break;

            case EOpIntBitsToFloat:
                ASSERT(getType().getBasicType() == EbtInt);
                resultArray[i].setFConst(gl::bitCast<float>(operandArray[i].getIConst()));
                break;

            case EOpUintBitsToFloat:
                ASSERT(getType().getBasicType() == EbtUInt);
                resultArray[i].setFConst(gl::bitCast<float>(operandArray[i].getUConst()));
                break;

            case EOpExp:
                foldFloatTypeUnary(operandArray[i], &expf, &resultArray[i]);
                break;

            case EOpLog:
                // For log(x), results are undefined if x <= 0, we are choosing to set result to 0.
                if (operandArray[i].getFConst() <= 0.0f)
                    UndefinedConstantFoldingError(getLine(), function, getType().getBasicType(),
                                                  diagnostics, &resultArray[i]);
                else
                    foldFloatTypeUnary(operandArray[i], &logf, &resultArray[i]);
                break;

            case EOpExp2:
                foldFloatTypeUnary(operandArray[i], &exp2f, &resultArray[i]);
                break;

            case EOpLog2:
                // For log2(x), results are undefined if x <= 0, we are choosing to set result to 0.
                // And log2f is not available on some plarforms like old android, so just using
                // log(x)/log(2) here.
                if (operandArray[i].getFConst() <= 0.0f)
                    UndefinedConstantFoldingError(getLine(), function, getType().getBasicType(),
                                                  diagnostics, &resultArray[i]);
                else
                {
                    foldFloatTypeUnary(operandArray[i], &logf, &resultArray[i]);
                    resultArray[i].setFConst(resultArray[i].getFConst() / logf(2.0f));
                }
                break;

            case EOpSqrt:
                // For sqrt(x), results are undefined if x < 0, we are choosing to set result to 0.
                if (operandArray[i].getFConst() < 0.0f)
                    UndefinedConstantFoldingError(getLine(), function, getType().getBasicType(),
                                                  diagnostics, &resultArray[i]);
                else
                    foldFloatTypeUnary(operandArray[i], &sqrtf, &resultArray[i]);
                break;

            case EOpInversesqrt:
                // There is no stdlib built-in function equavalent for GLES built-in inversesqrt(),
                // so getting the square root first using builtin function sqrt() and then taking
                // its inverse.
                // Also, for inversesqrt(x), results are undefined if x <= 0, we are choosing to set
                // result to 0.
                if (operandArray[i].getFConst() <= 0.0f)
                    UndefinedConstantFoldingError(getLine(), function, getType().getBasicType(),
                                                  diagnostics, &resultArray[i]);
                else
                {
                    foldFloatTypeUnary(operandArray[i], &sqrtf, &resultArray[i]);
                    resultArray[i].setFConst(1.0f / resultArray[i].getFConst());
                }
                break;

            case EOpNotComponentWise:
                ASSERT(getType().getBasicType() == EbtBool);
                resultArray[i].setBConst(!operandArray[i].getBConst());
                break;

            case EOpNormalize:
            {
                ASSERT(getType().getBasicType() == EbtFloat);
                float x      = operandArray[i].getFConst();
                float length = VectorLength(operandArray, objectSize);
                if (length != 0.0f)
                    resultArray[i].setFConst(x / length);
                else
                    UndefinedConstantFoldingError(getLine(), function, getType().getBasicType(),
                                                  diagnostics, &resultArray[i]);
                break;
            }
            case EOpBitfieldReverse:
            {
                uint32_t value;
                if (getType().getBasicType() == EbtInt)
                {
                    value = static_cast<uint32_t>(operandArray[i].getIConst());
                }
                else
                {
                    ASSERT(getType().getBasicType() == EbtUInt);
                    value = operandArray[i].getUConst();
                }
                uint32_t result = gl::BitfieldReverse(value);
                if (getType().getBasicType() == EbtInt)
                {
                    resultArray[i].setIConst(static_cast<int32_t>(result));
                }
                else
                {
                    resultArray[i].setUConst(result);
                }
                break;
            }
            case EOpBitCount:
            {
                uint32_t value;
                if (getType().getBasicType() == EbtInt)
                {
                    value = static_cast<uint32_t>(operandArray[i].getIConst());
                }
                else
                {
                    ASSERT(getType().getBasicType() == EbtUInt);
                    value = operandArray[i].getUConst();
                }
                int result = gl::BitCount(value);
                resultArray[i].setIConst(result);
                break;
            }
            case EOpFindLSB:
            {
                uint32_t value;
                if (getType().getBasicType() == EbtInt)
                {
                    value = static_cast<uint32_t>(operandArray[i].getIConst());
                }
                else
                {
                    ASSERT(getType().getBasicType() == EbtUInt);
                    value = operandArray[i].getUConst();
                }
                resultArray[i].setIConst(gl::FindLSB(value));
                break;
            }
            case EOpFindMSB:
            {
                uint32_t value;
                if (getType().getBasicType() == EbtInt)
                {
                    int intValue = operandArray[i].getIConst();
                    value        = static_cast<uint32_t>(intValue);
                    if (intValue < 0)
                    {
                        // Look for zero instead of one in value. This also handles the intValue ==
                        // -1 special case, where the return value needs to be -1.
                        value = ~value;
                    }
                }
                else
                {
                    ASSERT(getType().getBasicType() == EbtUInt);
                    value = operandArray[i].getUConst();
                }
                resultArray[i].setIConst(gl::FindMSB(value));
                break;
            }

            default:
                return nullptr;
        }
    }

    return resultArray;
}

void TIntermConstantUnion::foldFloatTypeUnary(const TConstantUnion &parameter,
                                              FloatTypeUnaryFunc builtinFunc,
                                              TConstantUnion *result) const
{
    ASSERT(builtinFunc);

    ASSERT(getType().getBasicType() == EbtFloat);
    result->setFConst(builtinFunc(parameter.getFConst()));
}

void TIntermConstantUnion::propagatePrecision(TPrecision precision)
{
    mType.setPrecision(precision);
}

// static
TConstantUnion *TIntermConstantUnion::FoldAggregateBuiltIn(TIntermAggregate *aggregate,
                                                           TDiagnostics *diagnostics)
{
    const TOperator op         = aggregate->getOp();
    const TFunction *function  = aggregate->getFunction();
    TIntermSequence *arguments = aggregate->getSequence();
    unsigned int argsCount     = static_cast<unsigned int>(arguments->size());
    std::vector<const TConstantUnion *> unionArrays(argsCount);
    std::vector<size_t> objectSizes(argsCount);
    size_t maxObjectSize = 0;
    TBasicType basicType = EbtVoid;
    TSourceLoc loc;
    for (unsigned int i = 0; i < argsCount; i++)
    {
        TIntermConstantUnion *argConstant = (*arguments)[i]->getAsConstantUnion();
        ASSERT(argConstant != nullptr);  // Should be checked already.

        if (i == 0)
        {
            basicType = argConstant->getType().getBasicType();
            loc       = argConstant->getLine();
        }
        unionArrays[i] = argConstant->getConstantValue();
        objectSizes[i] = argConstant->getType().getObjectSize();
        if (objectSizes[i] > maxObjectSize)
            maxObjectSize = objectSizes[i];
    }

    if (!(*arguments)[0]->getAsTyped()->isMatrix() && aggregate->getOp() != EOpOuterProduct)
    {
        for (unsigned int i = 0; i < argsCount; i++)
            if (objectSizes[i] != maxObjectSize)
                unionArrays[i] = Vectorize(*unionArrays[i], maxObjectSize);
    }

    TConstantUnion *resultArray = nullptr;

    switch (op)
    {
        case EOpAtan:
        {
            ASSERT(basicType == EbtFloat);
            resultArray = new TConstantUnion[maxObjectSize];
            for (size_t i = 0; i < maxObjectSize; i++)
            {
                float y = unionArrays[0][i].getFConst();
                float x = unionArrays[1][i].getFConst();
                // Results are undefined if x and y are both 0.
                if (x == 0.0f && y == 0.0f)
                    UndefinedConstantFoldingError(loc, function, basicType, diagnostics,
                                                  &resultArray[i]);
                else
                    resultArray[i].setFConst(atan2f(y, x));
            }
            break;
        }

        case EOpPow:
        {
            ASSERT(basicType == EbtFloat);
            resultArray = new TConstantUnion[maxObjectSize];
            for (size_t i = 0; i < maxObjectSize; i++)
            {
                float x = unionArrays[0][i].getFConst();
                float y = unionArrays[1][i].getFConst();
                // Results are undefined if x < 0.
                // Results are undefined if x = 0 and y <= 0.
                if (x < 0.0f)
                    UndefinedConstantFoldingError(loc, function, basicType, diagnostics,
                                                  &resultArray[i]);
                else if (x == 0.0f && y <= 0.0f)
                    UndefinedConstantFoldingError(loc, function, basicType, diagnostics,
                                                  &resultArray[i]);
                else
                    resultArray[i].setFConst(powf(x, y));
            }
            break;
        }

        case EOpMod:
        {
            ASSERT(basicType == EbtFloat);
            resultArray = new TConstantUnion[maxObjectSize];
            for (size_t i = 0; i < maxObjectSize; i++)
            {
                float x = unionArrays[0][i].getFConst();
                float y = unionArrays[1][i].getFConst();
                resultArray[i].setFConst(x - y * floorf(x / y));
            }
            break;
        }

        case EOpMin:
        {
            resultArray = new TConstantUnion[maxObjectSize];
            for (size_t i = 0; i < maxObjectSize; i++)
            {
                switch (basicType)
                {
                    case EbtFloat:
                        resultArray[i].setFConst(
                            std::min(unionArrays[0][i].getFConst(), unionArrays[1][i].getFConst()));
                        break;
                    case EbtInt:
                        resultArray[i].setIConst(
                            std::min(unionArrays[0][i].getIConst(), unionArrays[1][i].getIConst()));
                        break;
                    case EbtUInt:
                        resultArray[i].setUConst(
                            std::min(unionArrays[0][i].getUConst(), unionArrays[1][i].getUConst()));
                        break;
                    default:
                        UNREACHABLE();
                        break;
                }
            }
            break;
        }

        case EOpMax:
        {
            resultArray = new TConstantUnion[maxObjectSize];
            for (size_t i = 0; i < maxObjectSize; i++)
            {
                switch (basicType)
                {
                    case EbtFloat:
                        resultArray[i].setFConst(
                            std::max(unionArrays[0][i].getFConst(), unionArrays[1][i].getFConst()));
                        break;
                    case EbtInt:
                        resultArray[i].setIConst(
                            std::max(unionArrays[0][i].getIConst(), unionArrays[1][i].getIConst()));
                        break;
                    case EbtUInt:
                        resultArray[i].setUConst(
                            std::max(unionArrays[0][i].getUConst(), unionArrays[1][i].getUConst()));
                        break;
                    default:
                        UNREACHABLE();
                        break;
                }
            }
            break;
        }

        case EOpStep:
        {
            ASSERT(basicType == EbtFloat);
            resultArray = new TConstantUnion[maxObjectSize];
            for (size_t i = 0; i < maxObjectSize; i++)
                resultArray[i].setFConst(
                    unionArrays[1][i].getFConst() < unionArrays[0][i].getFConst() ? 0.0f : 1.0f);
            break;
        }

        case EOpLessThanComponentWise:
        {
            resultArray = new TConstantUnion[maxObjectSize];
            for (size_t i = 0; i < maxObjectSize; i++)
            {
                switch (basicType)
                {
                    case EbtFloat:
                        resultArray[i].setBConst(unionArrays[0][i].getFConst() <
                                                 unionArrays[1][i].getFConst());
                        break;
                    case EbtInt:
                        resultArray[i].setBConst(unionArrays[0][i].getIConst() <
                                                 unionArrays[1][i].getIConst());
                        break;
                    case EbtUInt:
                        resultArray[i].setBConst(unionArrays[0][i].getUConst() <
                                                 unionArrays[1][i].getUConst());
                        break;
                    default:
                        UNREACHABLE();
                        break;
                }
            }
            break;
        }

        case EOpLessThanEqualComponentWise:
        {
            resultArray = new TConstantUnion[maxObjectSize];
            for (size_t i = 0; i < maxObjectSize; i++)
            {
                switch (basicType)
                {
                    case EbtFloat:
                        resultArray[i].setBConst(unionArrays[0][i].getFConst() <=
                                                 unionArrays[1][i].getFConst());
                        break;
                    case EbtInt:
                        resultArray[i].setBConst(unionArrays[0][i].getIConst() <=
                                                 unionArrays[1][i].getIConst());
                        break;
                    case EbtUInt:
                        resultArray[i].setBConst(unionArrays[0][i].getUConst() <=
                                                 unionArrays[1][i].getUConst());
                        break;
                    default:
                        UNREACHABLE();
                        break;
                }
            }
            break;
        }

        case EOpGreaterThanComponentWise:
        {
            resultArray = new TConstantUnion[maxObjectSize];
            for (size_t i = 0; i < maxObjectSize; i++)
            {
                switch (basicType)
                {
                    case EbtFloat:
                        resultArray[i].setBConst(unionArrays[0][i].getFConst() >
                                                 unionArrays[1][i].getFConst());
                        break;
                    case EbtInt:
                        resultArray[i].setBConst(unionArrays[0][i].getIConst() >
                                                 unionArrays[1][i].getIConst());
                        break;
                    case EbtUInt:
                        resultArray[i].setBConst(unionArrays[0][i].getUConst() >
                                                 unionArrays[1][i].getUConst());
                        break;
                    default:
                        UNREACHABLE();
                        break;
                }
            }
            break;
        }
        case EOpGreaterThanEqualComponentWise:
        {
            resultArray = new TConstantUnion[maxObjectSize];
            for (size_t i = 0; i < maxObjectSize; i++)
            {
                switch (basicType)
                {
                    case EbtFloat:
                        resultArray[i].setBConst(unionArrays[0][i].getFConst() >=
                                                 unionArrays[1][i].getFConst());
                        break;
                    case EbtInt:
                        resultArray[i].setBConst(unionArrays[0][i].getIConst() >=
                                                 unionArrays[1][i].getIConst());
                        break;
                    case EbtUInt:
                        resultArray[i].setBConst(unionArrays[0][i].getUConst() >=
                                                 unionArrays[1][i].getUConst());
                        break;
                    default:
                        UNREACHABLE();
                        break;
                }
            }
        }
        break;

        case EOpEqualComponentWise:
        {
            resultArray = new TConstantUnion[maxObjectSize];
            for (size_t i = 0; i < maxObjectSize; i++)
            {
                switch (basicType)
                {
                    case EbtFloat:
                        resultArray[i].setBConst(unionArrays[0][i].getFConst() ==
                                                 unionArrays[1][i].getFConst());
                        break;
                    case EbtInt:
                        resultArray[i].setBConst(unionArrays[0][i].getIConst() ==
                                                 unionArrays[1][i].getIConst());
                        break;
                    case EbtUInt:
                        resultArray[i].setBConst(unionArrays[0][i].getUConst() ==
                                                 unionArrays[1][i].getUConst());
                        break;
                    case EbtBool:
                        resultArray[i].setBConst(unionArrays[0][i].getBConst() ==
                                                 unionArrays[1][i].getBConst());
                        break;
                    default:
                        UNREACHABLE();
                        break;
                }
            }
            break;
        }

        case EOpNotEqualComponentWise:
        {
            resultArray = new TConstantUnion[maxObjectSize];
            for (size_t i = 0; i < maxObjectSize; i++)
            {
                switch (basicType)
                {
                    case EbtFloat:
                        resultArray[i].setBConst(unionArrays[0][i].getFConst() !=
                                                 unionArrays[1][i].getFConst());
                        break;
                    case EbtInt:
                        resultArray[i].setBConst(unionArrays[0][i].getIConst() !=
                                                 unionArrays[1][i].getIConst());
                        break;
                    case EbtUInt:
                        resultArray[i].setBConst(unionArrays[0][i].getUConst() !=
                                                 unionArrays[1][i].getUConst());
                        break;
                    case EbtBool:
                        resultArray[i].setBConst(unionArrays[0][i].getBConst() !=
                                                 unionArrays[1][i].getBConst());
                        break;
                    default:
                        UNREACHABLE();
                        break;
                }
            }
            break;
        }

        case EOpDistance:
        {
            ASSERT(basicType == EbtFloat);
            TConstantUnion *distanceArray = new TConstantUnion[maxObjectSize];
            resultArray                   = new TConstantUnion();
            for (size_t i = 0; i < maxObjectSize; i++)
            {
                float x = unionArrays[0][i].getFConst();
                float y = unionArrays[1][i].getFConst();
                distanceArray[i].setFConst(x - y);
            }
            resultArray->setFConst(VectorLength(distanceArray, maxObjectSize));
            break;
        }

        case EOpDot:
            ASSERT(basicType == EbtFloat);
            resultArray = new TConstantUnion();
            resultArray->setFConst(VectorDotProduct(unionArrays[0], unionArrays[1], maxObjectSize));
            break;

        case EOpCross:
        {
            ASSERT(basicType == EbtFloat && maxObjectSize == 3);
            resultArray = new TConstantUnion[maxObjectSize];
            float x0    = unionArrays[0][0].getFConst();
            float x1    = unionArrays[0][1].getFConst();
            float x2    = unionArrays[0][2].getFConst();
            float y0    = unionArrays[1][0].getFConst();
            float y1    = unionArrays[1][1].getFConst();
            float y2    = unionArrays[1][2].getFConst();
            resultArray[0].setFConst(x1 * y2 - y1 * x2);
            resultArray[1].setFConst(x2 * y0 - y2 * x0);
            resultArray[2].setFConst(x0 * y1 - y0 * x1);
            break;
        }

        case EOpReflect:
        {
            ASSERT(basicType == EbtFloat);
            // genType reflect (genType I, genType N) :
            //     For the incident vector I and surface orientation N, returns the reflection
            //     direction:
            //     I - 2 * dot(N, I) * N.
            resultArray      = new TConstantUnion[maxObjectSize];
            float dotProduct = VectorDotProduct(unionArrays[1], unionArrays[0], maxObjectSize);
            for (size_t i = 0; i < maxObjectSize; i++)
            {
                float result = unionArrays[0][i].getFConst() -
                               2.0f * dotProduct * unionArrays[1][i].getFConst();
                resultArray[i].setFConst(result);
            }
            break;
        }

        case EOpMatrixCompMult:
        {
            ASSERT(basicType == EbtFloat && (*arguments)[0]->getAsTyped()->isMatrix() &&
                   (*arguments)[1]->getAsTyped()->isMatrix());
            // Perform component-wise matrix multiplication.
            resultArray                 = new TConstantUnion[maxObjectSize];
            const uint8_t rows          = (*arguments)[0]->getAsTyped()->getRows();
            const uint8_t cols          = (*arguments)[0]->getAsTyped()->getCols();
            angle::Matrix<float> lhs    = GetMatrix(unionArrays[0], rows, cols);
            angle::Matrix<float> rhs    = GetMatrix(unionArrays[1], rows, cols);
            angle::Matrix<float> result = lhs.compMult(rhs);
            SetUnionArrayFromMatrix(result, resultArray);
            break;
        }

        case EOpOuterProduct:
        {
            ASSERT(basicType == EbtFloat);
            size_t numRows = (*arguments)[0]->getAsTyped()->getType().getObjectSize();
            size_t numCols = (*arguments)[1]->getAsTyped()->getType().getObjectSize();
            resultArray    = new TConstantUnion[numRows * numCols];
            angle::Matrix<float> result =
                GetMatrix(unionArrays[0], static_cast<int>(numRows), 1)
                    .outerProduct(GetMatrix(unionArrays[1], 1, static_cast<int>(numCols)));
            SetUnionArrayFromMatrix(result, resultArray);
            break;
        }

        case EOpClamp:
        {
            resultArray = new TConstantUnion[maxObjectSize];
            for (size_t i = 0; i < maxObjectSize; i++)
            {
                switch (basicType)
                {
                    case EbtFloat:
                    {
                        float x   = unionArrays[0][i].getFConst();
                        float min = unionArrays[1][i].getFConst();
                        float max = unionArrays[2][i].getFConst();
                        // Results are undefined if min > max.
                        if (min > max)
                            UndefinedConstantFoldingError(loc, function, basicType, diagnostics,
                                                          &resultArray[i]);
                        else
                            resultArray[i].setFConst(gl::clamp(x, min, max));
                        break;
                    }

                    case EbtInt:
                    {
                        int x   = unionArrays[0][i].getIConst();
                        int min = unionArrays[1][i].getIConst();
                        int max = unionArrays[2][i].getIConst();
                        // Results are undefined if min > max.
                        if (min > max)
                            UndefinedConstantFoldingError(loc, function, basicType, diagnostics,
                                                          &resultArray[i]);
                        else
                            resultArray[i].setIConst(gl::clamp(x, min, max));
                        break;
                    }
                    case EbtUInt:
                    {
                        unsigned int x   = unionArrays[0][i].getUConst();
                        unsigned int min = unionArrays[1][i].getUConst();
                        unsigned int max = unionArrays[2][i].getUConst();
                        // Results are undefined if min > max.
                        if (min > max)
                            UndefinedConstantFoldingError(loc, function, basicType, diagnostics,
                                                          &resultArray[i]);
                        else
                            resultArray[i].setUConst(gl::clamp(x, min, max));
                        break;
                    }
                    default:
                        UNREACHABLE();
                        break;
                }
            }
            break;
        }

        case EOpMix:
        {
            resultArray = new TConstantUnion[maxObjectSize];
            for (size_t i = 0; i < maxObjectSize; i++)
            {
                TBasicType type = (*arguments)[2]->getAsTyped()->getType().getBasicType();
                if (type == EbtFloat)
                {
                    ASSERT(basicType == EbtFloat);
                    float x = unionArrays[0][i].getFConst();
                    float y = unionArrays[1][i].getFConst();

                    // Returns the linear blend of x and y, i.e., x * (1 - a) + y * a.
                    float a = unionArrays[2][i].getFConst();
                    resultArray[i].setFConst(x * (1.0f - a) + y * a);
                }
                else  // 3rd parameter is EbtBool
                {
                    ASSERT(type == EbtBool);
                    // Selects which vector each returned component comes from.
                    // For a component of a that is false, the corresponding component of x is
                    // returned.
                    // For a component of a that is true, the corresponding component of y is
                    // returned.
                    bool a = unionArrays[2][i].getBConst();
                    switch (basicType)
                    {
                        case EbtFloat:
                        {
                            float x = unionArrays[0][i].getFConst();
                            float y = unionArrays[1][i].getFConst();
                            resultArray[i].setFConst(a ? y : x);
                        }
                        break;
                        case EbtInt:
                        {
                            int x = unionArrays[0][i].getIConst();
                            int y = unionArrays[1][i].getIConst();
                            resultArray[i].setIConst(a ? y : x);
                        }
                        break;
                        case EbtUInt:
                        {
                            unsigned int x = unionArrays[0][i].getUConst();
                            unsigned int y = unionArrays[1][i].getUConst();
                            resultArray[i].setUConst(a ? y : x);
                        }
                        break;
                        case EbtBool:
                        {
                            bool x = unionArrays[0][i].getBConst();
                            bool y = unionArrays[1][i].getBConst();
                            resultArray[i].setBConst(a ? y : x);
                        }
                        break;
                        default:
                            UNREACHABLE();
                            break;
                    }
                }
            }
            break;
        }

        case EOpSmoothstep:
        {
            ASSERT(basicType == EbtFloat);
            resultArray = new TConstantUnion[maxObjectSize];
            for (size_t i = 0; i < maxObjectSize; i++)
            {
                float edge0 = unionArrays[0][i].getFConst();
                float edge1 = unionArrays[1][i].getFConst();
                float x     = unionArrays[2][i].getFConst();
                // Results are undefined if edge0 >= edge1.
                if (edge0 >= edge1)
                {
                    UndefinedConstantFoldingError(loc, function, basicType, diagnostics,
                                                  &resultArray[i]);
                }
                else
                {
                    // Returns 0.0 if x <= edge0 and 1.0 if x >= edge1 and performs smooth
                    // Hermite interpolation between 0 and 1 when edge0 < x < edge1.
                    float t = gl::clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f);
                    resultArray[i].setFConst(t * t * (3.0f - 2.0f * t));
                }
            }
            break;
        }

        case EOpFma:
        {
            ASSERT(basicType == EbtFloat);
            resultArray = new TConstantUnion[maxObjectSize];
            for (size_t i = 0; i < maxObjectSize; i++)
            {
                float a = unionArrays[0][i].getFConst();
                float b = unionArrays[1][i].getFConst();
                float c = unionArrays[2][i].getFConst();

                // Returns a * b + c.
                resultArray[i].setFConst(a * b + c);
            }
            break;
        }

        case EOpLdexp:
        {
            resultArray = new TConstantUnion[maxObjectSize];
            for (size_t i = 0; i < maxObjectSize; i++)
            {
                float x = unionArrays[0][i].getFConst();
                int exp = unionArrays[1][i].getIConst();
                if (exp > 128)
                {
                    UndefinedConstantFoldingError(loc, function, basicType, diagnostics,
                                                  &resultArray[i]);
                }
                else
                {
                    resultArray[i].setFConst(gl::Ldexp(x, exp));
                }
            }
            break;
        }

        case EOpFaceforward:
        {
            ASSERT(basicType == EbtFloat);
            // genType faceforward(genType N, genType I, genType Nref) :
            //     If dot(Nref, I) < 0 return N, otherwise return -N.
            resultArray      = new TConstantUnion[maxObjectSize];
            float dotProduct = VectorDotProduct(unionArrays[2], unionArrays[1], maxObjectSize);
            for (size_t i = 0; i < maxObjectSize; i++)
            {
                if (dotProduct < 0)
                    resultArray[i].setFConst(unionArrays[0][i].getFConst());
                else
                    resultArray[i].setFConst(-unionArrays[0][i].getFConst());
            }
            break;
        }

        case EOpRefract:
        {
            ASSERT(basicType == EbtFloat);
            // genType refract(genType I, genType N, float eta) :
            //     For the incident vector I and surface normal N, and the ratio of indices of
            //     refraction eta,
            //     return the refraction vector. The result is computed by
            //         k = 1.0 - eta * eta * (1.0 - dot(N, I) * dot(N, I))
            //         if (k < 0.0)
            //             return genType(0.0)
            //         else
            //             return eta * I - (eta * dot(N, I) + sqrt(k)) * N
            resultArray      = new TConstantUnion[maxObjectSize];
            float dotProduct = VectorDotProduct(unionArrays[1], unionArrays[0], maxObjectSize);
            for (size_t i = 0; i < maxObjectSize; i++)
            {
                float eta = unionArrays[2][i].getFConst();
                float k   = 1.0f - eta * eta * (1.0f - dotProduct * dotProduct);
                if (k < 0.0f)
                    resultArray[i].setFConst(0.0f);
                else
                    resultArray[i].setFConst(eta * unionArrays[0][i].getFConst() -
                                             (eta * dotProduct + sqrtf(k)) *
                                                 unionArrays[1][i].getFConst());
            }
            break;
        }
        case EOpBitfieldExtract:
        {
            resultArray = new TConstantUnion[maxObjectSize];
            for (size_t i = 0; i < maxObjectSize; ++i)
            {
                int offset = unionArrays[1][0].getIConst();
                int bits   = unionArrays[2][0].getIConst();
                if (bits == 0)
                {
                    if (aggregate->getBasicType() == EbtInt)
                    {
                        resultArray[i].setIConst(0);
                    }
                    else
                    {
                        ASSERT(aggregate->getBasicType() == EbtUInt);
                        resultArray[i].setUConst(0);
                    }
                }
                else if (offset < 0 || bits < 0 || offset >= 32 || bits > 32 || offset + bits > 32)
                {
                    UndefinedConstantFoldingError(loc, function, aggregate->getBasicType(),
                                                  diagnostics, &resultArray[i]);
                }
                else
                {
                    // bits can be 32 here, so we need to avoid bit shift overflow.
                    uint32_t maskMsb = 1u << (bits - 1);
                    uint32_t mask    = ((maskMsb - 1u) | maskMsb) << offset;
                    if (aggregate->getBasicType() == EbtInt)
                    {
                        uint32_t value = static_cast<uint32_t>(unionArrays[0][i].getIConst());
                        uint32_t resultUnsigned = (value & mask) >> offset;
                        if ((resultUnsigned & maskMsb) != 0)
                        {
                            // The most significant bits (from bits+1 to the most significant bit)
                            // should be set to 1.
                            uint32_t higherBitsMask = ((1u << (32 - bits)) - 1u) << bits;
                            resultUnsigned |= higherBitsMask;
                        }
                        resultArray[i].setIConst(static_cast<int32_t>(resultUnsigned));
                    }
                    else
                    {
                        ASSERT(aggregate->getBasicType() == EbtUInt);
                        uint32_t value = unionArrays[0][i].getUConst();
                        resultArray[i].setUConst((value & mask) >> offset);
                    }
                }
            }
            break;
        }
        case EOpBitfieldInsert:
        {
            resultArray = new TConstantUnion[maxObjectSize];
            for (size_t i = 0; i < maxObjectSize; ++i)
            {
                int offset = unionArrays[2][0].getIConst();
                int bits   = unionArrays[3][0].getIConst();
                if (bits == 0)
                {
                    if (aggregate->getBasicType() == EbtInt)
                    {
                        int32_t base = unionArrays[0][i].getIConst();
                        resultArray[i].setIConst(base);
                    }
                    else
                    {
                        ASSERT(aggregate->getBasicType() == EbtUInt);
                        uint32_t base = unionArrays[0][i].getUConst();
                        resultArray[i].setUConst(base);
                    }
                }
                else if (offset < 0 || bits < 0 || offset >= 32 || bits > 32 || offset + bits > 32)
                {
                    UndefinedConstantFoldingError(loc, function, aggregate->getBasicType(),
                                                  diagnostics, &resultArray[i]);
                }
                else
                {
                    // bits can be 32 here, so we need to avoid bit shift overflow.
                    uint32_t maskMsb    = 1u << (bits - 1);
                    uint32_t insertMask = ((maskMsb - 1u) | maskMsb) << offset;
                    uint32_t baseMask   = ~insertMask;
                    if (aggregate->getBasicType() == EbtInt)
                    {
                        uint32_t base   = static_cast<uint32_t>(unionArrays[0][i].getIConst());
                        uint32_t insert = static_cast<uint32_t>(unionArrays[1][i].getIConst());
                        uint32_t resultUnsigned =
                            (base & baseMask) | ((insert << offset) & insertMask);
                        resultArray[i].setIConst(static_cast<int32_t>(resultUnsigned));
                    }
                    else
                    {
                        ASSERT(aggregate->getBasicType() == EbtUInt);
                        uint32_t base   = unionArrays[0][i].getUConst();
                        uint32_t insert = unionArrays[1][i].getUConst();
                        resultArray[i].setUConst((base & baseMask) |
                                                 ((insert << offset) & insertMask));
                    }
                }
            }
            break;
        }
        case EOpDFdx:
        case EOpDFdy:
        case EOpFwidth:
            ASSERT(basicType == EbtFloat);
            resultArray = new TConstantUnion[maxObjectSize];
            for (size_t i = 0; i < maxObjectSize; i++)
            {
                // Derivatives of constant arguments should be 0.
                resultArray[i].setFConst(0.0f);
            }
            break;

        default:
            UNREACHABLE();
            return nullptr;
    }
    return resultArray;
}

// TIntermPreprocessorDirective implementation.
TIntermPreprocessorDirective::TIntermPreprocessorDirective(PreprocessorDirective directive,
                                                           ImmutableString command)
    : mDirective(directive), mCommand(std::move(command))
{}

TIntermPreprocessorDirective::TIntermPreprocessorDirective(const TIntermPreprocessorDirective &node)
    : TIntermPreprocessorDirective(node.mDirective, node.mCommand)
{}

TIntermPreprocessorDirective::~TIntermPreprocessorDirective() = default;

size_t TIntermPreprocessorDirective::getChildCount() const
{
    return 0;
}

TIntermNode *TIntermPreprocessorDirective::getChildNode(size_t index) const
{
    UNREACHABLE();
    return nullptr;
}
}  // namespace sh
