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

#include "compiler/translator/TranslatorMetalDirect.h"

#include "angle_gl.h"
#include "common/utilities.h"
#include "compiler/translator/BuiltinsWorkaroundGLSL.h"
#include "compiler/translator/ImmutableStringBuilder.h"
#include "compiler/translator/OutputVulkanGLSL.h"
#include "compiler/translator/StaticType.h"
#include "compiler/translator/TranslatorMetalDirect/AddExplicitTypeCasts.h"
#include "compiler/translator/TranslatorMetalDirect/AstHelpers.h"
#include "compiler/translator/TranslatorMetalDirect/ConstantNames.h"
#include "compiler/translator/TranslatorMetalDirect/EmitMetal.h"
#include "compiler/translator/TranslatorMetalDirect/FixTypeConstructors.h"
#include "compiler/translator/TranslatorMetalDirect/HoistConstants.h"
#include "compiler/translator/TranslatorMetalDirect/IntroduceVertexIndexID.h"
#include "compiler/translator/TranslatorMetalDirect/Name.h"
#include "compiler/translator/TranslatorMetalDirect/NameEmbeddedUniformStructsMetal.h"
#include "compiler/translator/TranslatorMetalDirect/ReduceInterfaceBlocks.h"
#include "compiler/translator/TranslatorMetalDirect/RewriteCaseDeclarations.h"
#include "compiler/translator/TranslatorMetalDirect/RewriteGlobalQualifierDecls.h"
#include "compiler/translator/TranslatorMetalDirect/RewriteKeywords.h"
#include "compiler/translator/TranslatorMetalDirect/RewriteOutArgs.h"
#include "compiler/translator/TranslatorMetalDirect/RewritePipelines.h"
#include "compiler/translator/TranslatorMetalDirect/RewriteUnaddressableReferences.h"
#include "compiler/translator/TranslatorMetalDirect/SeparateCompoundExpressions.h"
#include "compiler/translator/TranslatorMetalDirect/SeparateCompoundStructDeclarations.h"
#include "compiler/translator/TranslatorMetalDirect/SymbolEnv.h"
#include "compiler/translator/TranslatorMetalDirect/ToposortStructs.h"
#include "compiler/translator/TranslatorMetalDirect/TranslatorMetalUtils.h"
#include "compiler/translator/TranslatorMetalDirect/WrapMain.h"
#include "compiler/translator/tree_ops/InitializeVariables.h"
#include "compiler/translator/tree_ops/NameNamelessUniformBuffers.h"
#include "compiler/translator/tree_ops/RemoveAtomicCounterBuiltins.h"
#include "compiler/translator/tree_ops/RemoveInactiveInterfaceVariables.h"
#include "compiler/translator/tree_ops/RewriteAtomicCounters.h"
#include "compiler/translator/tree_ops/RewriteCubeMapSamplersAs2DArray.h"
#include "compiler/translator/tree_ops/RewriteDfdy.h"
#include "compiler/translator/tree_ops/RewriteRowMajorMatrices.h"
#include "compiler/translator/tree_ops/RewriteStructSamplers.h"
#include "compiler/translator/tree_util/BuiltIn.h"
#include "compiler/translator/tree_util/DriverUniform.h"
#include "compiler/translator/tree_util/FindFunction.h"
#include "compiler/translator/tree_util/FindMain.h"
#include "compiler/translator/tree_util/FindSymbolNode.h"
#include "compiler/translator/tree_util/IntermNode_util.h"
#include "compiler/translator/tree_util/ReplaceClipCullDistanceVariable.h"
#include "compiler/translator/tree_util/ReplaceVariable.h"
#include "compiler/translator/tree_util/RunAtTheEndOfShader.h"
#include "compiler/translator/tree_util/SpecializationConstant.h"
#include "compiler/translator/util.h"

namespace sh
{

namespace
{

constexpr Name kSampleMaskWriteFuncName("writeSampleMask", SymbolType::AngleInternal);
constexpr Name kFlippedPointCoordName("flippedPointCoord", SymbolType::UserDefined);
constexpr Name kFlippedFragCoordName("flippedFragCoord", SymbolType::UserDefined);

constexpr const TVariable kgl_VertexIDMetal(BuiltInId::gl_VertexID,
                                            ImmutableString("gl_VertexID"),
                                            SymbolType::BuiltIn,
                                            TExtension::UNDEFINED,
                                            StaticType::Get<EbtUInt, EbpHigh, EvqVertexID, 1, 1>());

class DeclareStructTypesTraverser : public TIntermTraverser
{
  public:
    explicit DeclareStructTypesTraverser(TOutputMSL *outputMSL)
        : TIntermTraverser(true, false, false), mOutputMSL(outputMSL)
    {}

    bool visitDeclaration(Visit visit, TIntermDeclaration *node) override
    {
        ASSERT(visit == PreVisit);
        if (!mInGlobalScope)
        {
            return false;
        }

        const TIntermSequence &sequence = *(node->getSequence());
        TIntermTyped *declarator        = sequence.front()->getAsTyped();
        const TType &type               = declarator->getType();

        if (type.isStructSpecifier())
        {
            const TStructure *structure = type.getStruct();

            // Embedded structs should be parsed away by now.
            ASSERT(structure->symbolType() != SymbolType::Empty);
            // outputMSL->writeStructType(structure);

            TIntermSymbol *symbolNode = declarator->getAsSymbolNode();
            if (symbolNode && symbolNode->variable().symbolType() == SymbolType::Empty)
            {
                // Remove the struct specifier declaration from the tree so it isn't parsed again.
                TIntermSequence emptyReplacement;
                mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node,
                                                std::move(emptyReplacement));
            }
        }
        // TODO: REMOVE, used to remove 'unsued' warning
        mOutputMSL = nullptr;

        return false;
    }

  private:
    TOutputMSL *mOutputMSL;
};

class DeclareDefaultUniformsTraverser : public TIntermTraverser
{
  public:
    DeclareDefaultUniformsTraverser(TInfoSinkBase *sink,
                                    ShHashFunction64 hashFunction,
                                    NameMap *nameMap)
        : TIntermTraverser(true, true, true),
          mSink(sink),
          mHashFunction(hashFunction),
          mNameMap(nameMap),
          mInDefaultUniform(false)
    {}

    bool visitDeclaration(Visit visit, TIntermDeclaration *node) override
    {
        const TIntermSequence &sequence = *(node->getSequence());

        // TODO(jmadill): Compound declarations.
        ASSERT(sequence.size() == 1);

        TIntermTyped *variable = sequence.front()->getAsTyped();
        const TType &type      = variable->getType();
        bool isUniform         = type.getQualifier() == EvqUniform && !type.isInterfaceBlock() &&
                         !IsOpaqueType(type.getBasicType());

        if (visit == PreVisit)
        {
            if (isUniform)
            {
                (*mSink) << "    " << GetTypeName(type, mHashFunction, mNameMap) << " ";
                mInDefaultUniform = true;
            }
        }
        else if (visit == InVisit)
        {
            mInDefaultUniform = isUniform;
        }
        else if (visit == PostVisit)
        {
            if (isUniform)
            {
                (*mSink) << ";\n";

                // Remove the uniform declaration from the tree so it isn't parsed again.
                TIntermSequence emptyReplacement;
                mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node,
                                                std::move(emptyReplacement));
            }

            mInDefaultUniform = false;
        }
        return true;
    }

    void visitSymbol(TIntermSymbol *symbol) override
    {
        if (mInDefaultUniform)
        {
            const ImmutableString &name = symbol->variable().name();
            ASSERT(!name.beginsWith("gl_"));
            (*mSink) << HashName(&symbol->variable(), mHashFunction, mNameMap)
                     << ArrayString(symbol->getType());
        }
    }

  private:
    TInfoSinkBase *mSink;
    ShHashFunction64 mHashFunction;
    NameMap *mNameMap;
    bool mInDefaultUniform;
};

// Declares a new variable to replace gl_DepthRange, its values are fed from a driver uniform.
ANGLE_NO_DISCARD bool ReplaceGLDepthRangeWithDriverUniform(TCompiler *compiler,
                                                           TIntermBlock *root,
                                                           const DriverUniform *driverUniforms,
                                                           TSymbolTable *symbolTable)
{
    // Create a symbol reference to "gl_DepthRange"
    const TVariable *depthRangeVar = static_cast<const TVariable *>(
        symbolTable->findBuiltIn(ImmutableString("gl_DepthRange"), 0));

    // ANGLEUniforms.depthRange
    TIntermBinary *angleEmulatedDepthRangeRef = driverUniforms->getDepthRangeRef();

    // Use this variable instead of gl_DepthRange everywhere.
    return ReplaceVariableWithTyped(compiler, root, depthRangeVar, angleEmulatedDepthRangeRef);
}

TIntermSequence *GetMainSequence(TIntermBlock *root)
{
    TIntermFunctionDefinition *main = FindMain(root);
    return main->getBody()->getSequence();
}

// This operation performs Android pre-rotation and y-flip.  For Android (and potentially other
// platforms), the device may rotate, such that the orientation of the application is rotated
// relative to the native orientation of the device.  This is corrected in part by multiplying
// gl_Position by a mat2.
// The equations reduce to an expression:
//
//     gl_Position.xy = gl_Position.xy * preRotation
ANGLE_NO_DISCARD bool AppendPreRotation(TCompiler *compiler,
                                        TIntermBlock *root,
                                        TSymbolTable *symbolTable,
                                        SpecConst *specConst,
                                        const DriverUniform *driverUniforms)
{
    TIntermTyped *preRotationRef = specConst->getPreRotationMatrix();
    if (!preRotationRef)
    {
        preRotationRef = driverUniforms->getPreRotationMatrixRef();
    }
    TIntermSymbol *glPos         = new TIntermSymbol(BuiltInVariable::gl_Position());
    TVector<int> swizzleOffsetXY = {0, 1};
    TIntermSwizzle *glPosXY      = new TIntermSwizzle(glPos, swizzleOffsetXY);

    // Create the expression "(gl_Position.xy * preRotation)"
    TIntermBinary *zRotated = new TIntermBinary(EOpMatrixTimesVector, preRotationRef, glPosXY);

    // Create the assignment "gl_Position.xy = (gl_Position.xy * preRotation)"
    TIntermBinary *assignment =
        new TIntermBinary(TOperator::EOpAssign, glPosXY->deepCopy(), zRotated);

    // Append the assignment as a statement at the end of the shader.
    return RunAtTheEndOfShader(compiler, root, assignment, symbolTable);
}

// Replaces a builtin variable with a version that is rotated and corrects the X and Y coordinates.
ANGLE_NO_DISCARD bool RotateAndFlipBuiltinVariable(TCompiler *compiler,
                                                   TIntermBlock *root,
                                                   TIntermSequence *insertSequence,
                                                   TIntermTyped *flipXY,
                                                   TSymbolTable *symbolTable,
                                                   const TVariable *builtin,
                                                   const Name &flippedVariableName,
                                                   TIntermTyped *pivot,
                                                   TIntermTyped *fragRotation)
{
    // Create a symbol reference to 'builtin'.
    TIntermSymbol *builtinRef = new TIntermSymbol(builtin);

    // Create a swizzle to "builtin.xy"
    TVector<int> swizzleOffsetXY = {0, 1};
    TIntermSwizzle *builtinXY    = new TIntermSwizzle(builtinRef, swizzleOffsetXY);

    // Create a symbol reference to our new variable that will hold the modified builtin.
    const TType *type = StaticType::GetForVec<EbtFloat>(
        EvqGlobal, static_cast<unsigned char>(builtin->getType().getNominalSize()));
    TVariable *replacementVar =
        new TVariable(symbolTable, flippedVariableName.rawName(), type, SymbolType::AngleInternal);
    DeclareGlobalVariable(root, replacementVar);
    TIntermSymbol *flippedBuiltinRef = new TIntermSymbol(replacementVar);

    // Use this new variable instead of 'builtin' everywhere.
    if (!ReplaceVariable(compiler, root, builtin, replacementVar))
    {
        return false;
    }

    // Create the expression "(builtin.xy * fragRotation)"
    TIntermTyped *rotatedXY;
    if (fragRotation)
    {
        rotatedXY = new TIntermBinary(EOpMatrixTimesVector, fragRotation, builtinXY);
    }
    else
    {
        // No rotation applied, use original variable.
        rotatedXY = builtinXY;
    }

    // Create the expression "(builtin.xy - pivot) * flipXY + pivot
    TIntermBinary *removePivot = new TIntermBinary(EOpSub, rotatedXY, pivot);
    TIntermBinary *inverseXY   = new TIntermBinary(EOpMul, removePivot, flipXY);
    TIntermBinary *plusPivot   = new TIntermBinary(EOpAdd, inverseXY, pivot->deepCopy());

    // Create the corrected variable and copy the value of the original builtin.
    TIntermSequence sequence;
    sequence.push_back(builtinRef->deepCopy());
    TIntermAggregate *aggregate =
        TIntermAggregate::CreateConstructor(builtin->getType(), &sequence);
    TIntermBinary *assignment = new TIntermBinary(EOpInitialize, flippedBuiltinRef, aggregate);

    // Create an assignment to the replaced variable's .xy.
    TIntermSwizzle *correctedXY =
        new TIntermSwizzle(flippedBuiltinRef->deepCopy(), swizzleOffsetXY);
    TIntermBinary *assignToY = new TIntermBinary(EOpAssign, correctedXY, plusPivot);

    // Add this assigment at the beginning of the main function
    insertSequence->insert(insertSequence->begin(), assignToY);
    insertSequence->insert(insertSequence->begin(), assignment);

    return compiler->validateAST(root);
}

ANGLE_NO_DISCARD bool InsertFragCoordCorrection(TCompiler *compiler,
                                                ShCompileOptions compileOptions,
                                                TIntermBlock *root,
                                                TIntermSequence *insertSequence,
                                                TSymbolTable *symbolTable,
                                                SpecConst *specConst,
                                                const DriverUniform *driverUniforms)
{
    TIntermTyped *flipXY = specConst->getFlipXY();
    if (!flipXY)
    {
        flipXY = driverUniforms->getFlipXYRef();
    }

    TIntermBinary *pivot = specConst->getHalfRenderArea();
    if (!pivot)
    {
        pivot = driverUniforms->getHalfRenderAreaRef();
    }

    TIntermTyped *fragRotation = nullptr;
    if ((compileOptions & SH_ADD_PRE_ROTATION) != 0)
    {
        fragRotation = specConst->getFragRotationMatrix();
        if (!fragRotation)
        {
            fragRotation = driverUniforms->getFragRotationMatrixRef();
        }
    }
    return RotateAndFlipBuiltinVariable(compiler, root, insertSequence, flipXY, symbolTable,
                                        BuiltInVariable::gl_FragCoord(), kFlippedFragCoordName,
                                        pivot, fragRotation);
}

void DeclareRightBeforeMain(TIntermBlock &root, const TVariable &var)
{
    root.insertChildNodes(FindMainIndex(&root), {new TIntermDeclaration{&var}});
}

void AddFragColorDeclaration(TIntermBlock &root, TSymbolTable &symbolTable)
{
    root.insertChildNodes(FindMainIndex(&root),
                          TIntermSequence{new TIntermDeclaration{BuiltInVariable::gl_FragColor()}});
}

void AddFragDepthDeclaration(TIntermBlock &root, TSymbolTable &symbolTable)
{
    root.insertChildNodes(FindMainIndex(&root),
                          TIntermSequence{new TIntermDeclaration{BuiltInVariable::gl_FragDepth()}});
}

void AddFragDepthEXTDeclaration(TCompiler &compiler, TIntermBlock &root, TSymbolTable &symbolTable)
{
    const TIntermSymbol *glFragDepthExt = FindSymbolNode(&root, ImmutableString("gl_FragDepthEXT"));
    ASSERT(glFragDepthExt);
    // Replace gl_FragData with our globally defined fragdata.
    if (!ReplaceVariable(&compiler, &root, &(glFragDepthExt->variable()),
                         BuiltInVariable::gl_FragDepth()))
    {
        return;
    }
    AddFragDepthDeclaration(root, symbolTable);
}
void AddSampleMaskDeclaration(TIntermBlock &root, TSymbolTable &symbolTable)
{
    TType *gl_SampleMaskType = new TType(EbtUInt, EbpHigh, EvqSampleMask, 1, 1);
    const TVariable *gl_SampleMask =
        new TVariable(&symbolTable, ImmutableString("gl_SampleMask"), gl_SampleMaskType,
                      SymbolType::BuiltIn, TExtension::UNDEFINED);
    root.insertChildNodes(FindMainIndex(&root),
                          TIntermSequence{new TIntermDeclaration{gl_SampleMask}});
}

ANGLE_NO_DISCARD bool AddFragDataDeclaration(TCompiler &compiler, TIntermBlock &root)
{
    TSymbolTable &symbolTable = compiler.getSymbolTable();
    const int maxDrawBuffers  = compiler.getResources().MaxDrawBuffers;
    TType *gl_FragDataType    = new TType(EbtFloat, EbpMedium, EvqFragData, 4, 1);
    std::vector<const TVariable *> glFragDataSlots;
    TIntermSequence declareGLFragdataSequence;

    // Create gl_FragData_0,1,2,3
    for (int i = 0; i < maxDrawBuffers; i++)
    {
        ImmutableStringBuilder builder(strlen("gl_FragData_") + 2);
        builder << "gl_FragData_";
        builder.appendDecimal(i);
        const TVariable *glFragData =
            new TVariable(&symbolTable, builder, gl_FragDataType, SymbolType::AngleInternal,
                          TExtension::UNDEFINED);
        glFragDataSlots.push_back(glFragData);
        declareGLFragdataSequence.push_back(new TIntermDeclaration{glFragData});
    }
    root.insertChildNodes(FindMainIndex(&root), declareGLFragdataSequence);

    // Create an internal gl_FragData array type, compatible with indexing syntax.
    TType *gl_FragDataTypeArray = new TType(EbtFloat, EbpMedium, EvqGlobal, 4, 1);
    gl_FragDataTypeArray->makeArray(maxDrawBuffers);
    const TVariable *glFragDataGlobal = new TVariable(&symbolTable, ImmutableString("gl_FragData"),
                                                      gl_FragDataTypeArray, SymbolType::BuiltIn);

    DeclareGlobalVariable(&root, glFragDataGlobal);
    const TIntermSymbol *originalGLFragData = FindSymbolNode(&root, ImmutableString("gl_FragData"));
    ASSERT(originalGLFragData);

    // Replace gl_FragData() with our globally defined fragdata
    if (!ReplaceVariable(&compiler, &root, &(originalGLFragData->variable()), glFragDataGlobal))
    {
        return false;
    }

    // Assign each array attribute to an output
    TIntermBlock *insertSequence = new TIntermBlock();
    for (int i = 0; i < maxDrawBuffers; i++)
    {
        TIntermTyped *glFragDataSlot         = new TIntermSymbol(glFragDataSlots[i]);
        TIntermTyped *glFragDataGlobalSymbol = new TIntermSymbol(glFragDataGlobal);
        auto &access                         = AccessIndex(*glFragDataGlobalSymbol, i);
        TIntermBinary *assignment =
            new TIntermBinary(TOperator::EOpAssign, glFragDataSlot, &access);
        insertSequence->appendStatement(assignment);
    }
    return RunAtTheEndOfShader(&compiler, &root, insertSequence, &symbolTable);
}

ANGLE_NO_DISCARD bool EmulateInstanceID(TCompiler &compiler,
                                        TIntermBlock &root,
                                        DriverUniform &driverUniforms)
{
    TIntermBinary *emuInstanceID = driverUniforms.getEmulatedInstanceId();
    const TVariable *instanceID  = BuiltInVariable::gl_InstanceIndex();
    return ReplaceVariableWithTyped(&compiler, &root, instanceID, emuInstanceID);
}

// Unlike Vulkan having auto viewport flipping extension, in Metal we have to flip gl_Position.y
// manually.
// This operation performs flipping the gl_Position.y using this expression:
// gl_Position.y = gl_Position.y * negViewportScaleY
ANGLE_NO_DISCARD bool AppendVertexShaderPositionYCorrectionToMain(TCompiler *compiler,
                                                                  TIntermBlock *root,
                                                                  TSymbolTable *symbolTable,
                                                                  TIntermTyped *negFlipY)
{
    // Create a symbol reference to "gl_Position"
    const TVariable *position  = BuiltInVariable::gl_Position();
    TIntermSymbol *positionRef = new TIntermSymbol(position);

    // Create a swizzle to "gl_Position.y"
    TVector<int> swizzleOffsetY;
    swizzleOffsetY.push_back(1);
    TIntermSwizzle *positionY = new TIntermSwizzle(positionRef, swizzleOffsetY);

    // Create the expression "gl_Position.y * negFlipY"
    TIntermBinary *inverseY = new TIntermBinary(EOpMul, positionY->deepCopy(), negFlipY);

    // Create the assignment "gl_Position.y = gl_Position.y * negViewportScaleY
    TIntermTyped *positionYLHS = positionY->deepCopy();
    TIntermBinary *assignment  = new TIntermBinary(TOperator::EOpAssign, positionYLHS, inverseY);

    // Append the assignment as a statement at the end of the shader.
    return RunAtTheEndOfShader(compiler, root, assignment, symbolTable);
}
}  // namespace

TranslatorMetalDirect::TranslatorMetalDirect(sh::GLenum type,
                                             ShShaderSpec spec,
                                             ShShaderOutput output)
    : TCompiler(type, spec, output)
{}

// static
const char *TranslatorMetalDirect::GetCoverageMaskEnabledConstName()
{
    return constant_names::kCoverageMaskEnabled.rawName().data();
}

// static
const char *TranslatorMetalDirect::GetRasterizationDiscardEnabledConstName()
{
    return constant_names::kRasterizationDiscardEnabled.rawName().data();
}

// Add sample_mask writing to main, guarded by the function constant
// kCoverageMaskEnabledName
ANGLE_NO_DISCARD bool TranslatorMetalDirect::insertSampleMaskWritingLogic(
    TIntermBlock &root,
    DriverUniform &driverUniforms)
{
    TSymbolTable *symbolTable = &getSymbolTable();

    // Create kCoverageMaskEnabled and kSampleMaskWriteFuncName variable references.
    TType *boolType = new TType(EbtBool);
    boolType->setQualifier(EvqConst);
    TVariable *coverageMaskEnabledVar =
        new TVariable(symbolTable, constant_names::kCoverageMaskEnabled.rawName(), boolType,
                      SymbolType::AngleInternal);

    TFunction *sampleMaskWriteFunc = new TFunction(symbolTable, kSampleMaskWriteFuncName.rawName(),
                                                   kSampleMaskWriteFuncName.symbolType(),
                                                   StaticType::GetBasic<EbtVoid>(), false);

    TType *uintType = new TType(EbtUInt);
    TVariable *maskArg =
        new TVariable(symbolTable, ImmutableString("mask"), uintType, SymbolType::AngleInternal);
    sampleMaskWriteFunc->addParameter(maskArg);

    TVariable *gl_SampleMaskArg = new TVariable(symbolTable, ImmutableString("gl_SampleMask"),
                                                uintType, SymbolType::AngleInternal);
    sampleMaskWriteFunc->addParameter(gl_SampleMaskArg);

    // Insert this MSL code to the end of main() in the shader
    // if (ANGLE_CoverageMaskEnabled)
    // {
    //      ANGLE_writeSampleMask(ANGLE_angleUniforms.coverageMask,
    //      ANGLE_fragmentOut.gl_SampleMask);
    // }
    TIntermSequence *args       = new TIntermSequence;
    TIntermBinary *coverageMask = driverUniforms.getCoverageMask();
    args->push_back(coverageMask);
    const TIntermSymbol *gl_SampleMask = FindSymbolNode(&root, ImmutableString("gl_SampleMask"));
    args->push_back(gl_SampleMask->deepCopy());

    TIntermAggregate *callSampleMaskWriteFunc =
        TIntermAggregate::CreateFunctionCall(*sampleMaskWriteFunc, args);
    TIntermBlock *callBlock = new TIntermBlock;
    callBlock->appendStatement(callSampleMaskWriteFunc);

    TIntermSymbol *coverageMaskEnabled = new TIntermSymbol(coverageMaskEnabledVar);
    TIntermIfElse *ifCall              = new TIntermIfElse(coverageMaskEnabled, callBlock, nullptr);

    return RunAtTheEndOfShader(this, &root, ifCall, symbolTable);
}

ANGLE_NO_DISCARD bool TranslatorMetalDirect::insertRasterizationDiscardLogic(TIntermBlock &root)
{
    TSymbolTable *symbolTable = &getSymbolTable();

    TType *boolType = new TType(EbtBool);
    boolType->setQualifier(EvqConst);
    TVariable *discardEnabledVar =
        new TVariable(symbolTable, constant_names::kRasterizationDiscardEnabled.rawName(), boolType,
                      constant_names::kRasterizationDiscardEnabled.symbolType());

    const TVariable *position  = BuiltInVariable::gl_Position();
    TIntermSymbol *positionRef = new TIntermSymbol(position);

    // Create vec4(-3, -3, -3, 1):
    auto vec4Type             = new TType(EbtFloat, 4);
    TIntermSequence *vec4Args = new TIntermSequence();
    vec4Args->push_back(CreateFloatNode(-3.0f));
    vec4Args->push_back(CreateFloatNode(-3.0f));
    vec4Args->push_back(CreateFloatNode(-3.0f));
    vec4Args->push_back(CreateFloatNode(1.0f));
    TIntermAggregate *constVarConstructor =
        TIntermAggregate::CreateConstructor(*vec4Type, vec4Args);

    // Create the assignment "gl_Position = vec4(-3, -3, -3, 1)"
    TIntermBinary *assignment =
        new TIntermBinary(TOperator::EOpAssign, positionRef->deepCopy(), constVarConstructor);

    TIntermBlock *discardBlock = new TIntermBlock;
    discardBlock->appendStatement(assignment);

    TIntermSymbol *discardEnabled = new TIntermSymbol(discardEnabledVar);
    TIntermIfElse *ifCall         = new TIntermIfElse(discardEnabled, discardBlock, nullptr);

    return RunAtTheEndOfShader(this, &root, ifCall, symbolTable);
}

// Metal needs to inverse the depth if depthRange is is reverse order, i.e. depth near > depth far
// This is achieved by multiply the depth value with scale value stored in
// driver uniform's depthRange.reserved
bool TranslatorMetalDirect::transformDepthBeforeCorrection(TIntermBlock *root,
                                                           const DriverUniform *driverUniforms)
{
    // Create a symbol reference to "gl_Position"
    const TVariable *position  = BuiltInVariable::gl_Position();
    TIntermSymbol *positionRef = new TIntermSymbol(position);

    // Create a swizzle to "gl_Position.z"
    TVector<int> swizzleOffsetZ = {2};
    TIntermSwizzle *positionZ   = new TIntermSwizzle(positionRef, swizzleOffsetZ);

    // Create a ref to "depthRange.reserved"
    TIntermBinary *viewportZScale = driverUniforms->getDepthRangeReservedFieldRef();

    // Create the expression "gl_Position.z * depthRange.reserved".
    TIntermBinary *zScale = new TIntermBinary(EOpMul, positionZ->deepCopy(), viewportZScale);

    // Create the assignment "gl_Position.z = gl_Position.z * depthRange.reserved"
    TIntermTyped *positionZLHS = positionZ->deepCopy();
    TIntermBinary *assignment  = new TIntermBinary(TOperator::EOpAssign, positionZLHS, zScale);

    // Append the assignment as a statement at the end of the shader.
    return RunAtTheEndOfShader(this, root, assignment, &getSymbolTable());
}

static std::set<ImmutableString> GetMslKeywords()
{
    std::set<ImmutableString> keywords;

    keywords.emplace("alignas");
    keywords.emplace("alignof");
    keywords.emplace("as_type");
    keywords.emplace("auto");
    keywords.emplace("catch");
    keywords.emplace("char");
    keywords.emplace("class");
    keywords.emplace("const_cast");
    keywords.emplace("constant");
    keywords.emplace("constexpr");
    keywords.emplace("decltype");
    keywords.emplace("delete");
    keywords.emplace("device");
    keywords.emplace("dynamic_cast");
    keywords.emplace("enum");
    keywords.emplace("explicit");
    keywords.emplace("export");
    keywords.emplace("extern");
    keywords.emplace("fragment");
    keywords.emplace("friend");
    keywords.emplace("goto");
    keywords.emplace("half");
    keywords.emplace("inline");
    keywords.emplace("int16_t");
    keywords.emplace("int32_t");
    keywords.emplace("int64_t");
    keywords.emplace("int8_t");
    keywords.emplace("kernel");
    keywords.emplace("long");
    keywords.emplace("main0");
    keywords.emplace("metal");
    keywords.emplace("mutable");
    keywords.emplace("namespace");
    keywords.emplace("new");
    keywords.emplace("noexcept");
    keywords.emplace("nullptr_t");
    keywords.emplace("nullptr");
    keywords.emplace("operator");
    keywords.emplace("override");
    keywords.emplace("private");
    keywords.emplace("protected");
    keywords.emplace("ptrdiff_t");
    keywords.emplace("public");
    keywords.emplace("ray_data");
    keywords.emplace("register");
    keywords.emplace("short");
    keywords.emplace("signed");
    keywords.emplace("size_t");
    keywords.emplace("sizeof");
    keywords.emplace("stage_in");
    keywords.emplace("static_assert");
    keywords.emplace("static_cast");
    keywords.emplace("static");
    keywords.emplace("template");
    keywords.emplace("this");
    keywords.emplace("thread_local");
    keywords.emplace("thread");
    keywords.emplace("threadgroup_imageblock");
    keywords.emplace("threadgroup");
    keywords.emplace("throw");
    keywords.emplace("try");
    keywords.emplace("typedef");
    keywords.emplace("typeid");
    keywords.emplace("typename");
    keywords.emplace("uchar");
    keywords.emplace("uint16_t");
    keywords.emplace("uint32_t");
    keywords.emplace("uint64_t");
    keywords.emplace("uint8_t");
    keywords.emplace("union");
    keywords.emplace("unsigned");
    keywords.emplace("ushort");
    keywords.emplace("using");
    keywords.emplace("vertex");
    keywords.emplace("virtual");
    keywords.emplace("volatile");
    keywords.emplace("wchar_t");

    return keywords;
}

static inline MetalShaderType metalShaderTypeFromGLSL(sh::GLenum shaderType)
{
    switch (shaderType)
    {
        case GL_VERTEX_SHADER:
            return MetalShaderType::Vertex;
        case GL_FRAGMENT_SHADER:
            return MetalShaderType::Fragment;
        case GL_COMPUTE_SHADER:
            ASSERT(0 && "compute shaders not currently supported");
            return MetalShaderType::Compute;
        default:
            ASSERT(0 && "Invalid shader type.");
            return MetalShaderType::None;
    }
}

bool TranslatorMetalDirect::translateImpl(TInfoSinkBase &sink,
                                          TIntermBlock *root,
                                          ShCompileOptions compileOptions,
                                          PerformanceDiagnostics * /*perfDiagnostics*/,
                                          SpecConst *specConst,
                                          DriverUniform *driverUniforms)
{
    TSymbolTable &symbolTable = getSymbolTable();
    IdGen idGen;
    ProgramPreludeConfig ppc(metalShaderTypeFromGLSL(getShaderType()));

    if (!WrapMain(*this, idGen, *root))
    {
        return false;
    }

    // Remove declarations of inactive shader interface variables so glslang wrapper doesn't need to
    // replace them.  Note: this is done before extracting samplers from structs, as removing such
    // inactive samplers is not yet supported.  Note also that currently, CollectVariables marks
    // every field of an active uniform that's of struct type as active, i.e. no extracted sampler
    // is inactive.
    if (!RemoveInactiveInterfaceVariables(this, root, &getSymbolTable(), getAttributes(),
                                          getInputVaryings(), getOutputVariables(), getUniforms(),
                                          getInterfaceBlocks()))
    {
        return false;
    }

    // Write out default uniforms into a uniform block assigned to a specific set/binding.
    int defaultUniformCount           = 0;
    int aggregateTypesUsedForUniforms = 0;
    int atomicCounterCount            = 0;
    for (const auto &uniform : getUniforms())
    {
        if (!uniform.isBuiltIn() && uniform.active && !gl::IsOpaqueType(uniform.type))
        {
            ++defaultUniformCount;
        }

        if (uniform.isStruct() || uniform.isArrayOfArrays())
        {
            ++aggregateTypesUsedForUniforms;
        }

        if (uniform.active && gl::IsAtomicCounterType(uniform.type))
        {
            ++atomicCounterCount;
        }
    }

    if (aggregateTypesUsedForUniforms > 0)
    {
        if (!NameEmbeddedStructUniformsMetal(this, root, &symbolTable))
        {
            return false;
        }

        int removedUniformsCount;
        bool rewriteStructSamplersResult =
            RewriteStructSamplers(this, root, &symbolTable, &removedUniformsCount);

        if (!rewriteStructSamplersResult)
        {
            return false;
        }
        defaultUniformCount -= removedUniformsCount;
    }

    if (compileOptions & SH_EMULATE_SEAMFUL_CUBE_MAP_SAMPLING)
    {
        if (!RewriteCubeMapSamplersAs2DArray(this, root, &symbolTable,
                                             getShaderType() == GL_FRAGMENT_SHADER))
        {
            return false;
        }
    }

    if (getShaderType() == GL_COMPUTE_SHADER)
    {
        driverUniforms->addComputeDriverUniformsToShader(root, &getSymbolTable());
    }
    else
    {
        driverUniforms->addGraphicsDriverUniformsToShader(root, &getSymbolTable());
    }

    if (atomicCounterCount > 0)
    {
        const TIntermTyped *acbBufferOffsets = driverUniforms->getAbcBufferOffsets();
        if (!RewriteAtomicCounters(this, root, &symbolTable, acbBufferOffsets))
        {
            return false;
        }
    }
    else if (getShaderVersion() >= 310)
    {
        // Vulkan doesn't support Atomic Storage as a Storage Class, but we've seen
        // cases where builtins are using it even with no active atomic counters.
        // This pass simply removes those builtins in that scenario.
        if (!RemoveAtomicCounterBuiltins(this, root))
        {
            return false;
        }
    }

    if (getShaderType() != GL_COMPUTE_SHADER)
    {
        if (!ReplaceGLDepthRangeWithDriverUniform(this, root, driverUniforms, &getSymbolTable()))
        {
            return false;
        }
    }

    {
        bool usesInstanceId = false;
        bool usesVertexId   = false;
        for (const ShaderVariable &var : mAttributes)
        {
            if (var.isBuiltIn())
            {
                if (var.name == "gl_InstanceID")
                {
                    usesInstanceId = true;
                }
                if (var.name == "gl_VertexID")
                {
                    usesVertexId = true;
                }
            }
        }

        if (usesInstanceId)
        {
            root->insertChildNodes(
                FindMainIndex(root),
                TIntermSequence{new TIntermDeclaration{BuiltInVariable::gl_InstanceID()}});
        }
        if (usesVertexId)
        {
            if (!ReplaceVariable(this, root, BuiltInVariable::gl_VertexID(), &kgl_VertexIDMetal))
            {
                return false;
            }
            DeclareRightBeforeMain(*root, kgl_VertexIDMetal);
        }
    }
    SymbolEnv symbolEnv(*this, *root);
    // Declare gl_FragColor and gl_FragData as webgl_FragColor and webgl_FragData
    // if it's core profile shaders and they are used.
    if (getShaderType() == GL_FRAGMENT_SHADER)
    {
        bool usesPointCoord  = false;
        bool usesFragCoord   = false;
        bool usesFrontFacing = false;
        for (const ShaderVariable &inputVarying : mInputVaryings)
        {
            if (inputVarying.isBuiltIn())
            {
                if (inputVarying.name == "gl_PointCoord")
                {
                    usesPointCoord = true;
                }
                else if (inputVarying.name == "gl_FragCoord")
                {
                    usesFragCoord = true;
                }
                else if (inputVarying.name == "gl_FrontFacing")
                {
                    usesFrontFacing = true;
                }
            }
        }

        bool usesFragColor    = false;
        bool usesFragData     = false;
        bool usesFragDepth    = false;
        bool usesFragDepthEXT = false;
        for (const ShaderVariable &outputVarying : mOutputVariables)
        {
            if (outputVarying.isBuiltIn())
            {
                if (outputVarying.name == "gl_FragColor")
                {
                    usesFragColor = true;
                }
                else if (outputVarying.name == "gl_FragData")
                {
                    usesFragData = true;
                }
                else if (outputVarying.name == "gl_FragDepth")
                {
                    usesFragDepth = true;
                }
                else if (outputVarying.name == "gl_FragDepthEXT")
                {
                    usesFragDepthEXT = true;
                }
            }
        }

        if (usesFragColor && usesFragData)
        {
            return false;
        }

        if (usesFragColor)
        {
            AddFragColorDeclaration(*root, symbolTable);
        }

        if (usesFragData)
        {
            if (!AddFragDataDeclaration(*this, *root))
            {
                return false;
            }
        }
        if (usesFragDepth)
        {
            AddFragDepthDeclaration(*root, symbolTable);
        }
        else if (usesFragDepthEXT)
        {
            AddFragDepthEXTDeclaration(*this, *root, symbolTable);
        }

        // Always add sample_mask. It will be guarded by a function constant decided at runtime.
        bool usesSampleMask = true;
        if (usesSampleMask)
        {
            AddSampleMaskDeclaration(*root, symbolTable);
        }

        bool usePreRotation = compileOptions & SH_ADD_PRE_ROTATION;

        if (usesPointCoord)
        {
            TIntermTyped *flipNegXY = specConst->getNegFlipXY();
            if (!flipNegXY)
            {
                flipNegXY = driverUniforms->getNegFlipXYRef();
            }
            TIntermConstantUnion *pivot = CreateFloatNode(0.5f);
            TIntermTyped *fragRotation  = nullptr;
            if (usePreRotation)
            {
                fragRotation = specConst->getFragRotationMatrix();
                if (!fragRotation)
                {
                    fragRotation = driverUniforms->getFragRotationMatrixRef();
                }
            }
            if (!RotateAndFlipBuiltinVariable(this, root, GetMainSequence(root), flipNegXY,
                                              &getSymbolTable(), BuiltInVariable::gl_PointCoord(),
                                              kFlippedPointCoordName, pivot, fragRotation))
            {
                return false;
            }
            DeclareRightBeforeMain(*root, *BuiltInVariable::gl_PointCoord());
        }

        if (usesFragCoord)
        {
            if (!InsertFragCoordCorrection(this, compileOptions, root, GetMainSequence(root),
                                           &getSymbolTable(), specConst, driverUniforms))
            {
                return false;
            }
            DeclareRightBeforeMain(*root, *BuiltInVariable::gl_FragCoord());
        }

        if (!RewriteDfdy(this, compileOptions, root, getSymbolTable(), getShaderVersion(),
                         specConst, driverUniforms))
        {
            return false;
        }

        if (usesFrontFacing)
        {
            DeclareRightBeforeMain(*root, *BuiltInVariable::gl_FrontFacing());
        }

        EmitEarlyFragmentTestsGLSL(*this, sink);
    }
    else if (getShaderType() == GL_VERTEX_SHADER)
    {
        DeclareRightBeforeMain(*root, *BuiltInVariable::gl_Position());

        if (FindSymbolNode(root, BuiltInVariable::gl_PointSize()->name()))
        {
            DeclareRightBeforeMain(*root, *BuiltInVariable::gl_PointSize());
        }

        if (FindSymbolNode(root, BuiltInVariable::gl_VertexIndex()->name()))
        {
            if (!ReplaceVariable(this, root, BuiltInVariable::gl_VertexIndex(), &kgl_VertexIDMetal))
            {
                return false;
            }
            DeclareRightBeforeMain(*root, kgl_VertexIDMetal);
        }

        // Search for the gl_ClipDistance usage, if its used, we need to do some replacements.
        bool useClipDistance = false;
        for (const ShaderVariable &outputVarying : mOutputVaryings)
        {
            if (outputVarying.name == "gl_ClipDistance")
            {
                useClipDistance = true;
                break;
            }
        }

        if (useClipDistance &&
            !ReplaceClipDistanceAssignments(this, root, &getSymbolTable(), getShaderType(),
                                            driverUniforms->getClipDistancesEnabled()))
        {
            return false;
        }

        if (!transformDepthBeforeCorrection(root, driverUniforms))
        {
            return false;
        }

        if ((compileOptions & SH_ADD_PRE_ROTATION) != 0 &&
            !AppendPreRotation(this, root, &getSymbolTable(), specConst, driverUniforms))
        {
            return false;
        }
    }
    else if (getShaderType() == GL_GEOMETRY_SHADER)
    {
        WriteGeometryShaderLayoutQualifiers(
            sink, getGeometryShaderInputPrimitiveType(), getGeometryShaderInvocations(),
            getGeometryShaderOutputPrimitiveType(), getGeometryShaderMaxVertices());
    }
    else
    {
        ASSERT(getShaderType() == GL_COMPUTE_SHADER);
        EmitWorkGroupSizeGLSL(*this, sink);
    }

    if (getShaderType() == GL_VERTEX_SHADER)
    {
        auto negFlipY = driverUniforms->getNegFlipYRef();

        if (mEmulatedInstanceID)
        {
            if (!EmulateInstanceID(*this, *root, *driverUniforms))
            {
                return false;
            }
        }

        if (!AppendVertexShaderPositionYCorrectionToMain(this, root, &getSymbolTable(), negFlipY))
        {
            return false;
        }

        if (!insertRasterizationDiscardLogic(*root))
        {
            return false;
        }
    }
    else if (getShaderType() == GL_FRAGMENT_SHADER)
    {
        if (!insertSampleMaskWritingLogic(*root, *driverUniforms))
        {
            return false;
        }
    }

    if (!validateAST(root))
    {
        return false;
    }

    if (!RewriteKeywords(*this, *root, idGen, GetMslKeywords()))
    {
        return false;
    }

    if (!ReduceInterfaceBlocks(*this, *root, idGen))
    {
        return false;
    }

    if (!SeparateCompoundStructDeclarations(*this, idGen, *root))
    {
        return false;
    }
    // This is the largest size required to pass all the tests in
    // (dEQP-GLES3.functional.shaders.large_constant_arrays)
    // This value could in principle be smaller.
    const size_t hoistThresholdSize = 256;
    if (!HoistConstants(*this, *root, idGen, hoistThresholdSize))
    {
        return false;
    }

    Invariants invariants;
    if (!RewriteGlobalQualifierDecls(*this, *root, invariants))
    {
        return false;
    }

    if (!AddExplicitTypeCasts(*this, *root, symbolEnv))
    {
        return false;
    }

    PipelineStructs pipelineStructs;
    if (!RewritePipelines(*this, *root, idGen, *driverUniforms, symbolEnv, invariants,
                          pipelineStructs))
    {
        return false;
    }
    if (getShaderType() == GL_VERTEX_SHADER)
    {
        if (!IntroduceVertexAndInstanceIndex(*this, *root))
        {
            return false;
        }
    }
    if (!SeparateCompoundExpressions(*this, symbolEnv, idGen, *root))
    {
        return false;
    }

    if (!RewriteCaseDeclarations(*this, *root))
    {
        return false;
    }

    if (!RewriteUnaddressableReferences(*this, *root, symbolEnv))
    {
        return false;
    }

    if (!RewriteOutArgs(*this, *root, symbolEnv))
    {
        return false;
    }
    if (!FixTypeConstructors(*this, symbolEnv, *root))
    {
        return false;
    }
    if (!ToposortStructs(*this, symbolEnv, *root, ppc))
    {
        return false;
    }
    if (!EmitMetal(*this, *root, idGen, pipelineStructs, invariants, symbolEnv, ppc,
                   &getSymbolTable()))
    {
        return false;
    }

    ASSERT(validateAST(root));

    return true;
}

bool TranslatorMetalDirect::translate(TIntermBlock *root,
                                      ShCompileOptions compileOptions,
                                      PerformanceDiagnostics *perfDiagnostics)
{
    if (!root)
    {
        return false;
    }

    TInfoSinkBase &sink = getInfoSink().obj;

    if ((compileOptions & SH_REWRITE_ROW_MAJOR_MATRICES) != 0 && getShaderVersion() >= 300)
    {
        // "Make sure every uniform buffer variable has a name.  The following transformation relies
        // on this." This pass was removed in e196bc85ac2dda0e9f6664cfc2eca0029e33d2d1, but
        // currently finding it still necessary for MSL.
        // TODO(jcunningham): Look into removing the NameNamelessUniformBuffers and fixing the root
        // cause in RewriteRowMajorMatrices
        if (!NameNamelessUniformBuffers(this, root, &getSymbolTable()))
        {
            return false;
        }
        if (!RewriteRowMajorMatrices(this, root, &getSymbolTable()))
        {
            return false;
        }
    }

    bool precisionEmulation = false;
    if (!emulatePrecisionIfNeeded(root, sink, &precisionEmulation, SH_SPIRV_VULKAN_OUTPUT))
    {
        return false;
    }

    SpecConst specConst(&getSymbolTable(), compileOptions, getShaderType());
    DriverUniformExtended driverUniforms;
    if (!translateImpl(sink, root, compileOptions, perfDiagnostics, &specConst, &driverUniforms))
    {
        return false;
    }

    return true;
}
bool TranslatorMetalDirect::shouldFlattenPragmaStdglInvariantAll()
{
    // Not neccesary for MSL transformation.
    return false;
}

}  // namespace sh
