//
// Copyright 2016 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.
//
// TranslatorSPIRV:
//   A set of transformations that prepare the AST to be compatible with GL_KHR_vulkan_glsl followed
//   by a pass that generates SPIR-V.
//   See: https://www.khronos.org/registry/vulkan/specs/misc/GL_KHR_vulkan_glsl.txt
//

#include "compiler/translator/spirv/TranslatorSPIRV.h"
#include "common/unsafe_buffers.h"

#include "common/PackedEnums.h"
#include "common/utilities.h"
#include "compiler/translator/ImmutableStringBuilder.h"
#include "compiler/translator/IntermNode.h"
#include "compiler/translator/StaticType.h"
#include "compiler/translator/spirv/BuiltinsWorkaround.h"
#include "compiler/translator/spirv/OutputSPIRV.h"
#include "compiler/translator/tree_ops/DeclarePerVertexBlocks.h"
#include "compiler/translator/tree_ops/GatherDefaultUniforms.h"
#include "compiler/translator/tree_ops/MonomorphizeUnsupportedFunctions.h"
#include "compiler/translator/tree_ops/RemoveAtomicCounterBuiltins.h"
#include "compiler/translator/tree_ops/RemoveInvariantDeclaration.h"
#include "compiler/translator/tree_ops/RewriteArrayOfArrayOfOpaqueUniforms.h"
#include "compiler/translator/tree_ops/RewriteAtomicCounters.h"
#include "compiler/translator/tree_ops/RewriteDfdy.h"
#include "compiler/translator/tree_ops/RewriteStructSamplers.h"
#include "compiler/translator/tree_ops/SeparateStructFromUniformDeclarations.h"
#include "compiler/translator/tree_ops/spirv/ClampGLLayer.h"
#include "compiler/translator/tree_ops/spirv/EmulateAdvancedBlendEquations.h"
#include "compiler/translator/tree_ops/spirv/EmulateFragColorData.h"
#include "compiler/translator/tree_ops/spirv/EmulateFramebufferFetch.h"
#include "compiler/translator/tree_ops/spirv/EmulateYUVBuiltIns.h"
#include "compiler/translator/tree_ops/spirv/FlagSamplersWithTexelFetch.h"
#include "compiler/translator/tree_ops/spirv/ReswizzleYUVOps.h"
#include "compiler/translator/tree_ops/spirv/RewriteInterpolateAtOffset.h"
#include "compiler/translator/tree_ops/spirv/RewriteR32fImages.h"
#include "compiler/translator/tree_ops/spirv/RewriteSamplerExternalTexelFetch.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/RewriteSampleMaskVariable.h"
#include "compiler/translator/tree_util/RunAtTheEndOfShader.h"
#include "compiler/translator/util.h"

namespace sh
{

namespace
{
constexpr ImmutableString kFlippedPointCoordName    = ImmutableString("flippedPointCoord");
constexpr ImmutableString kFlippedSamplePositionName = ImmutableString("flippedSamplePosition");
constexpr ImmutableString kFlippedFragCoordName     = ImmutableString("flippedFragCoord");
constexpr ImmutableString kDefaultUniformsBlockName = ImmutableString("defaultUniforms");

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

    // Create a symbol reference to our new variable that will hold the modified builtin.
    TType *type = new TType(builtin->getType());
    type->setQualifier(EvqGlobal);
    type->setPrimarySize(builtin->getType().getNominalSize());
    TVariable *replacementVar =
        new TVariable(symbolTable, flippedVariableName, 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 "(swapXY ? builtin.yx : builtin.xy)"
    TIntermTyped *builtinXY = new TIntermSwizzle(builtinRef, {0, 1});
    TIntermTyped *builtinYX = new TIntermSwizzle(builtinRef->deepCopy(), {1, 0});

    builtinXY = new TIntermTernary(swapXY, builtinYX, builtinXY);

    // Create the expression "(builtin.xy - pivot) * flipXY + pivot
    TIntermBinary *removePivot = new TIntermBinary(EOpSub, builtinXY, 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.
    TIntermBinary *assignment =
        new TIntermBinary(EOpAssign, flippedBuiltinRef, builtinRef->deepCopy());

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

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

    return compiler->validateAST(root);
}

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

// Declares a new variable to replace gl_DepthRange, its values are fed from a driver uniform.
[[nodiscard]] 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
    TIntermTyped *angleEmulatedDepthRangeRef = driverUniforms->getDepthRange();

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

// Declares a new variable to replace gl_BoundingBoxEXT, its values are fed from a global temporary
// variable.
[[nodiscard]] bool ReplaceGLBoundingBoxWithGlobal(TCompiler *compiler,
                                                  TIntermBlock *root,
                                                  TSymbolTable *symbolTable,
                                                  int shaderVersion)
{
    // Declare the replacement bounding box variable type
    TType *emulatedBoundingBoxDeclType = new TType(EbtFloat, EbpHigh, EvqGlobal, 4);
    emulatedBoundingBoxDeclType->makeArray(2u);

    TVariable *ANGLEBoundingBoxVar = new TVariable(
        symbolTable->nextUniqueId(), ImmutableString("ANGLEBoundingBox"), SymbolType::AngleInternal,
        TExtension::EXT_primitive_bounding_box, emulatedBoundingBoxDeclType);

    DeclareGlobalVariable(root, ANGLEBoundingBoxVar);

    const TVariable *builtinBoundingBoxVar;
    bool replacementResult = true;

    // Create a symbol reference to "gl_BoundingBoxEXT"
    builtinBoundingBoxVar = static_cast<const TVariable *>(
        symbolTable->findBuiltIn(ImmutableString("gl_BoundingBoxEXT"), shaderVersion));
    if (builtinBoundingBoxVar != nullptr)
    {
        // Use the replacement variable instead of builtin gl_BoundingBoxEXT everywhere.
        replacementResult &=
            ReplaceVariable(compiler, root, builtinBoundingBoxVar, ANGLEBoundingBoxVar);
    }

    // Create a symbol reference to "gl_BoundingBoxOES"
    builtinBoundingBoxVar = static_cast<const TVariable *>(
        symbolTable->findBuiltIn(ImmutableString("gl_BoundingBoxOES"), shaderVersion));
    if (builtinBoundingBoxVar != nullptr)
    {
        // Use the replacement variable instead of builtin gl_BoundingBoxOES everywhere.
        replacementResult &=
            ReplaceVariable(compiler, root, builtinBoundingBoxVar, ANGLEBoundingBoxVar);
    }

    if (shaderVersion >= 320)
    {
        // Create a symbol reference to "gl_BoundingBox"
        builtinBoundingBoxVar = static_cast<const TVariable *>(
            symbolTable->findBuiltIn(ImmutableString("gl_BoundingBox"), shaderVersion));
        if (builtinBoundingBoxVar != nullptr)
        {
            // Use the replacement variable instead of builtin gl_BoundingBox everywhere.
            replacementResult &=
                ReplaceVariable(compiler, root, builtinBoundingBoxVar, ANGLEBoundingBoxVar);
        }
    }
    return replacementResult;
}

[[nodiscard]] bool AddXfbEmulationSupport(TranslatorSPIRV *compiler,
                                          TIntermBlock *root,
                                          TSymbolTable *symbolTable,
                                          const DriverUniform *driverUniforms)
{
    // Generate the following function and place it before main().  This function takes a "strides"
    // parameter that is determined at link time, and calculates for each transform feedback buffer
    // (of which there are a maximum of four) what the starting index is to write to the output
    // buffer.
    //
    //     ivec4 ANGLEGetXfbOffsets(ivec4 strides)
    //     {
    //         int xfbIndex = gl_VertexIndex
    //                      + gl_InstanceIndex * ANGLEUniforms.xfbVerticesPerInstance;
    //         return ANGLEUniforms.xfbBufferOffsets + xfbIndex * strides;
    //     }

    constexpr uint32_t kMaxXfbBuffers = 4;

    const TType *ivec4Type = StaticType::GetBasic<EbtInt, EbpHigh, kMaxXfbBuffers>();
    TType *stridesType     = new TType(*ivec4Type);
    stridesType->setQualifier(EvqParamConst);

    // Create the parameter variable.
    TVariable *stridesVar = new TVariable(symbolTable, ImmutableString("strides"), stridesType,
                                          SymbolType::AngleInternal);
    TIntermSymbol *stridesSymbol = new TIntermSymbol(stridesVar);

    // Create references to gl_VertexIndex, gl_InstanceIndex, ANGLEUniforms.xfbVerticesPerInstance
    // and ANGLEUniforms.xfbBufferOffsets.
    TIntermSymbol *vertexIndex           = new TIntermSymbol(BuiltInVariable::gl_VertexIndex());
    TIntermSymbol *instanceIndex         = new TIntermSymbol(BuiltInVariable::gl_InstanceIndex());
    TIntermTyped *xfbVerticesPerInstance = driverUniforms->getXfbVerticesPerInstance();
    TIntermTyped *xfbBufferOffsets       = driverUniforms->getXfbBufferOffsets();

    // gl_InstanceIndex * ANGLEUniforms.xfbVerticesPerInstance
    TIntermBinary *xfbInstanceIndex =
        new TIntermBinary(EOpMul, instanceIndex, xfbVerticesPerInstance);

    // gl_VertexIndex + |xfbInstanceIndex|
    TIntermBinary *xfbIndex = new TIntermBinary(EOpAdd, vertexIndex, xfbInstanceIndex);

    // |xfbIndex| * |strides|
    TIntermBinary *xfbStrides = new TIntermBinary(EOpVectorTimesScalar, xfbIndex, stridesSymbol);

    // ANGLEUniforms.xfbBufferOffsets + |xfbStrides|
    TIntermBinary *xfbOffsets = new TIntermBinary(EOpAdd, xfbBufferOffsets, xfbStrides);

    // Create the function body, which has a single return statement.  Note that the `xfbIndex`
    // variable declared in the comment at the beginning of this function is simply replaced in the
    // return statement for brevity.
    TIntermBlock *body = new TIntermBlock;
    body->appendStatement(new TIntermBranch(EOpReturn, xfbOffsets));

    // Declare the function
    TFunction *getOffsetsFunction =
        new TFunction(symbolTable, ImmutableString("ANGLEGetXfbOffsets"), SymbolType::AngleInternal,
                      ivec4Type, true);
    getOffsetsFunction->addParameter(stridesVar);

    compiler->assignSpirvId(getOffsetsFunction->uniqueId(),
                            vk::spirv::kIdXfbEmulationGetOffsetsFunction);

    TIntermFunctionDefinition *functionDef =
        CreateInternalFunctionDefinitionNode(*getOffsetsFunction, body);

    // Insert the function declaration before main().
    const size_t mainIndex = FindMainIndex(root);
    root->insertChildNodes(mainIndex, {functionDef});

    // Generate the following function and place it before main().  This function will be filled
    // with transform feedback capture code at link time.
    //
    //     void ANGLECaptureXfb()
    //     {
    //     }
    const TType *voidType = StaticType::GetBasic<EbtVoid, EbpUndefined>();

    // Create the function body, which is empty.
    body = new TIntermBlock;

    // Declare the function
    TFunction *xfbCaptureFunction = new TFunction(symbolTable, ImmutableString("ANGLECaptureXfb"),
                                                  SymbolType::AngleInternal, voidType, false);

    compiler->assignSpirvId(xfbCaptureFunction->uniqueId(),
                            vk::spirv::kIdXfbEmulationCaptureFunction);

    // Insert the function declaration before main().
    root->insertChildNodes(mainIndex,
                           {CreateInternalFunctionDefinitionNode(*xfbCaptureFunction, body)});

    // Create the following logic and add it at the end of main():
    //
    //     ANGLECaptureXfb();
    //

    // Create the function call
    TIntermAggregate *captureXfbCall =
        TIntermAggregate::CreateFunctionCall(*xfbCaptureFunction, {});

    // Run it at the end of the shader.
    if (!RunAtTheEndOfShader(compiler, root, captureXfbCall, symbolTable))
    {
        return false;
    }

    // Additionally, generate the following storage buffer declarations used to capture transform
    // feedback output.  Again, there's a maximum of four buffers.
    //
    //     buffer ANGLEXfbBuffer0
    //     {
    //         float xfbOut[];
    //     } ANGLEXfb0;
    //     buffer ANGLEXfbBuffer1
    //     {
    //         float xfbOut[];
    //     } ANGLEXfb1;
    //     ...

    for (uint32_t bufferIndex = 0; bufferIndex < kMaxXfbBuffers; ++bufferIndex)
    {
        TFieldList *fieldList = new TFieldList;
        TType *xfbOutType     = new TType(EbtFloat, EbpHigh, EvqGlobal);
        xfbOutType->makeArray(0);

        TField *field = new TField(xfbOutType, ImmutableString("xfbOut"), TSourceLoc(),
                                   SymbolType::AngleInternal);

        fieldList->push_back(field);

        static_assert(
            kMaxXfbBuffers < 10,
            "ImmutableStringBuilder memory size below needs to accomodate the number of buffers");

        ImmutableString blockName = BuildConcatenatedImmutableString("ANGLEXfbBuffer", bufferIndex);
        ImmutableString varName   = BuildConcatenatedImmutableString("ANGLEXfb", bufferIndex);

        TLayoutQualifier layoutQualifier = TLayoutQualifier::Create();
        layoutQualifier.blockStorage     = EbsStd430;

        const TInterfaceBlock *interfaceBlock =
            DeclareInterfaceBlock(symbolTable, fieldList, layoutQualifier, blockName);
        const TVariable *xfbBuffer =
            DeclareInterfaceBlockVariable(root, symbolTable, EvqBuffer, interfaceBlock,
                                          layoutQualifier, TMemoryQualifier::Create(), 0, varName);

        static_assert(vk::spirv::kIdXfbEmulationBufferBlockOne ==
                      vk::spirv::kIdXfbEmulationBufferBlockZero + 1);
        static_assert(vk::spirv::kIdXfbEmulationBufferBlockTwo ==
                      vk::spirv::kIdXfbEmulationBufferBlockZero + 2);
        static_assert(vk::spirv::kIdXfbEmulationBufferBlockThree ==
                      vk::spirv::kIdXfbEmulationBufferBlockZero + 3);

        static_assert(vk::spirv::kIdXfbEmulationBufferVarOne ==
                      vk::spirv::kIdXfbEmulationBufferVarZero + 1);
        static_assert(vk::spirv::kIdXfbEmulationBufferVarTwo ==
                      vk::spirv::kIdXfbEmulationBufferVarZero + 2);
        static_assert(vk::spirv::kIdXfbEmulationBufferVarThree ==
                      vk::spirv::kIdXfbEmulationBufferVarZero + 3);

        compiler->assignSpirvId(xfbBuffer->getType().getInterfaceBlock()->uniqueId(),
                                vk::spirv::kIdXfbEmulationBufferBlockZero + bufferIndex);
        compiler->assignSpirvId(xfbBuffer->uniqueId(),
                                vk::spirv::kIdXfbEmulationBufferVarZero + bufferIndex);
    }

    return compiler->validateAST(root);
}

[[nodiscard]] bool AddXfbExtensionSupport(TranslatorSPIRV *compiler,
                                          TIntermBlock *root,
                                          TSymbolTable *symbolTable,
                                          const DriverUniform *driverUniforms)
{
    // Generate the following output varying declaration used to capture transform feedback output
    // from gl_Position, as it can't be captured directly due to changes that are applied to it for
    // clip-space correction and pre-rotation.
    //
    //     out vec4 ANGLEXfbPosition;

    const TType *vec4Type = nullptr;

    switch (compiler->getShaderType())
    {
        case GL_VERTEX_SHADER:
            vec4Type = StaticType::Get<EbtFloat, EbpHigh, EvqVertexOut, 4, 1>();
            break;
        case GL_TESS_EVALUATION_SHADER_EXT:
            vec4Type = StaticType::Get<EbtFloat, EbpHigh, EvqTessEvaluationOut, 4, 1>();
            break;
        case GL_GEOMETRY_SHADER_EXT:
            vec4Type = StaticType::Get<EbtFloat, EbpHigh, EvqGeometryOut, 4, 1>();
            break;
        default:
            UNREACHABLE();
    }

    TVariable *varyingVar = new TVariable(symbolTable, ImmutableString("ANGLEXfbPosition"),
                                          vec4Type, SymbolType::AngleInternal);

    compiler->assignSpirvId(varyingVar->uniqueId(), vk::spirv::kIdXfbExtensionPosition);

    TIntermDeclaration *varyingDecl = new TIntermDeclaration();
    varyingDecl->appendDeclarator(new TIntermSymbol(varyingVar));

    // Insert the varying declaration before the first function.
    const size_t firstFunctionIndex = FindFirstFunctionDefinitionIndex(root);
    root->insertChildNodes(firstFunctionIndex, {varyingDecl});

    return compiler->validateAST(root);
}

[[nodiscard]] bool AddVertexTransformationSupport(TranslatorSPIRV *compiler,
                                                  const ShCompileOptions &compileOptions,
                                                  TIntermBlock *root,
                                                  TSymbolTable *symbolTable,
                                                  const DriverUniform *driverUniforms)
{
    // In GL the viewport transformation is slightly different - see the GL 2.0 spec section "2.12.1
    // Controlling the Viewport".  In Vulkan the corresponding spec section is currently "23.4.
    // Coordinate Transformations".  The following transformation needs to be done:
    //
    //     z_vk = 0.5 * (w_gl + z_gl)
    //
    // where z_vk is the depth output of a Vulkan geometry-stage shader and z_gl is the same for GL.
    //
    // Generate the following function and place it before main().  This function takes
    // gl_Position and rotates xy, and adjusts z (if necessary).
    //
    //     vec4 ANGLETransformPosition(vec4 position)
    //     {
    //         return vec4((swapXY ? position.yx : position.xy) * flipXY,
    //                     transformDepth ? (gl_Position.z + gl_Position.w) / 2 : gl_Position.z,
    //                     gl_Postion.w);
    //     }

    const TType *vec4Type = StaticType::GetBasic<EbtFloat, EbpHigh, 4>();
    TType *positionType   = new TType(*vec4Type);
    positionType->setQualifier(EvqParamConst);

    // Create the parameter variable.
    TVariable *positionVar = new TVariable(symbolTable, ImmutableString("position"), positionType,
                                           SymbolType::AngleInternal);
    TIntermSymbol *positionSymbol = new TIntermSymbol(positionVar);

    // swapXY ? position.yx : position.xy
    TIntermTyped *swapXY = driverUniforms->getSwapXY();

    TIntermTyped *xy        = new TIntermSwizzle(positionSymbol, {0, 1});
    TIntermTyped *swappedXY = new TIntermSwizzle(positionSymbol->deepCopy(), {1, 0});
    TIntermTyped *rotatedXY = new TIntermTernary(swapXY, swappedXY, xy);

    // (swapXY ? position.yx : position.xy) * flipXY
    TIntermTyped *flipXY = driverUniforms->getFlipXY(symbolTable, DriverUniformFlip::PreFragment);
    TIntermTyped *rotatedFlippedXY = new TIntermBinary(EOpMul, rotatedXY, flipXY);

    // (gl_Position.z + gl_Position.w) / 2
    TIntermTyped *z = new TIntermSwizzle(positionSymbol->deepCopy(), {2});
    TIntermTyped *w = new TIntermSwizzle(positionSymbol->deepCopy(), {3});

    TIntermTyped *transformedDepth = z;
    if (compileOptions.addVulkanDepthCorrection)
    {
        TIntermBinary *zPlusW = new TIntermBinary(EOpAdd, z, w->deepCopy());
        TIntermBinary *halfZPlusW =
            new TIntermBinary(EOpMul, zPlusW, CreateFloatNode(0.5, EbpMedium));

        // transformDepth ? (gl_Position.z + gl_Position.w) / 2 : gl_Position.z,
        TIntermTyped *transformDepth = driverUniforms->getTransformDepth();
        transformedDepth = new TIntermTernary(transformDepth, halfZPlusW, z->deepCopy());
    }

    // vec4(...);
    TIntermSequence args = {
        rotatedFlippedXY,
        transformedDepth,
        w,
    };
    TIntermTyped *transformedPosition = TIntermAggregate::CreateConstructor(*vec4Type, &args);

    // Create the function body, which has a single return statement.
    TIntermBlock *body = new TIntermBlock;
    body->appendStatement(new TIntermBranch(EOpReturn, transformedPosition));

    // Declare the function
    TFunction *transformPositionFunction =
        new TFunction(symbolTable, ImmutableString("ANGLETransformPosition"),
                      SymbolType::AngleInternal, vec4Type, true);
    transformPositionFunction->addParameter(positionVar);

    compiler->assignSpirvId(transformPositionFunction->uniqueId(),
                            vk::spirv::kIdTransformPositionFunction);

    TIntermFunctionDefinition *functionDef =
        CreateInternalFunctionDefinitionNode(*transformPositionFunction, body);

    // Insert the function declaration before main().
    const size_t mainIndex = FindMainIndex(root);
    root->insertChildNodes(mainIndex, {functionDef});

    return compiler->validateAST(root);
}

[[nodiscard]] bool InsertFragCoordCorrection(TCompiler *compiler,
                                             const ShCompileOptions &compileOptions,
                                             TIntermBlock *root,
                                             TIntermSequence *insertSequence,
                                             TSymbolTable *symbolTable,
                                             const DriverUniform *driverUniforms)
{
    TIntermTyped *flipXY = driverUniforms->getFlipXY(symbolTable, DriverUniformFlip::Fragment);
    TIntermTyped *pivot  = driverUniforms->getHalfRenderArea();

    TIntermTyped *swapXY = driverUniforms->getSwapXY();

    const TVariable *fragCoord = static_cast<const TVariable *>(
        symbolTable->findBuiltIn(ImmutableString("gl_FragCoord"), compiler->getShaderVersion()));
    return RotateAndFlipBuiltinVariable(compiler, root, insertSequence, swapXY, flipXY, symbolTable,
                                        fragCoord, kFlippedFragCoordName, pivot);
}

bool HasFramebufferFetch(const TExtensionBehavior &extBehavior,
                         const ShCompileOptions &compileOptions)
{
    return IsExtensionEnabled(extBehavior, TExtension::EXT_shader_framebuffer_fetch) ||
           IsExtensionEnabled(extBehavior, TExtension::EXT_shader_framebuffer_fetch_non_coherent) ||
           IsExtensionEnabled(extBehavior, TExtension::ARM_shader_framebuffer_fetch) ||
           IsExtensionEnabled(extBehavior,
                              TExtension::ARM_shader_framebuffer_fetch_depth_stencil) ||
           (compileOptions.pls.type == ShPixelLocalStorageType::FramebufferFetch &&
            IsExtensionEnabled(extBehavior, TExtension::ANGLE_shader_pixel_local_storage));
}

template <typename Variable>
Variable *FindShaderVariable(std::vector<Variable> *vars, const ImmutableString &name)
{
    for (Variable &var : *vars)
    {
        if (name == var.name)
        {
            return &var;
        }
    }
    UNREACHABLE();
    return nullptr;
}

ShaderVariable *FindIOBlockShaderVariable(std::vector<ShaderVariable> *vars,
                                          const ImmutableString &name)
{
    for (ShaderVariable &var : *vars)
    {
        if (name == var.structOrBlockName)
        {
            return &var;
        }
    }
    UNREACHABLE();
    return nullptr;
}

ShaderVariable *FindUniformFieldShaderVariable(std::vector<ShaderVariable> *vars,
                                               const ImmutableString &name,
                                               const char *prefix)
{
    for (ShaderVariable &var : *vars)
    {
        // The name of the sampler is derived from the uniform name + fields
        // that reach the uniform, concatenated with '_' per RewriteStructSamplers.
        std::string varName = prefix;
        varName += '_';
        varName += var.name;

        if (name == varName)
        {
            return &var;
        }

        ShaderVariable *field = FindUniformFieldShaderVariable(&var.fields, name, varName.c_str());
        if (field != nullptr)
        {
            return field;
        }
    }
    return nullptr;
}

ShaderVariable *FindUniformShaderVariable(std::vector<ShaderVariable> *vars,
                                          const ImmutableString &name)
{
    for (ShaderVariable &var : *vars)
    {
        if (name == var.name)
        {
            return &var;
        }

        // Note: samplers in structs are moved out.  Such samplers will be found in the fields of
        // the struct uniform.
        ShaderVariable *field = FindUniformFieldShaderVariable(&var.fields, name, var.name.c_str());
        if (field != nullptr)
        {
            return field;
        }
    }
    UNREACHABLE();
    return nullptr;
}

void SetSpirvIdInFields(uint32_t id, std::vector<ShaderVariable> *fields)
{
    for (ShaderVariable &field : *fields)
    {
        field.id = id;
        SetSpirvIdInFields(id, &field.fields);
    }
}
}  // anonymous namespace

TranslatorSPIRV::TranslatorSPIRV(sh::GLenum type, ShShaderSpec spec)
    : TCompiler(type, spec, SH_SPIRV_VULKAN_OUTPUT), mFirstUnusedSpirvId(0)
{}

bool TranslatorSPIRV::translateImpl(TIntermBlock *root,
                                    const ShCompileOptions &compileOptions,
                                    PerformanceDiagnostics * /*perfDiagnostics*/,
                                    DriverUniform *driverUniforms)
{
    // Write out default uniforms into a uniform block assigned to a specific set/binding.
    int defaultUniformCount           = 0;
    int aggregateTypesUsedForUniforms = 0;
    int r32fImageCount                = 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::IsImageType(uniform.type) && uniform.imageUnitFormat == GL_R32F)
        {
            ++r32fImageCount;
        }

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

    // If there are any function calls that take array-of-array of opaque uniform parameters, or
    // other opaque uniforms that need special handling in Vulkan, such as atomic counters,
    // monomorphize the functions by removing said parameters and replacing them in the function
    // body with the call arguments.
    //
    // This has a few benefits:
    //
    // - It dramatically simplifies future transformations w.r.t to samplers in structs, array of
    //   arrays of opaque types, atomic counters etc.
    // - Avoids the need for shader*ArrayDynamicIndexing Vulkan features.
    if (!compileOptions.useIR)
    {
        UnsupportedFunctionArgsBitSet args{UnsupportedFunctionArgs::StructContainingSamplers,
                                           UnsupportedFunctionArgs::ArrayOfArrayOfSamplerOrImage,
                                           UnsupportedFunctionArgs::AtomicCounter,
                                           UnsupportedFunctionArgs::Image};
        if (!MonomorphizeUnsupportedFunctions(this, root, &getSymbolTable(), args))
        {
            return false;
        }
    }

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

        int removedUniformsCount;

        if (!RewriteStructSamplers(this, root, &getSymbolTable(), &removedUniformsCount))
        {
            return false;
        }
        defaultUniformCount -= removedUniformsCount;
    }

    // Replace array of array of opaque uniforms with a flattened array.  This is run after
    // MonomorphizeUnsupportedFunctions and RewriteStructSamplers so that it's not possible for an
    // array of array of opaque type to be partially subscripted and passed to a function.
    if (!RewriteArrayOfArrayOfOpaqueUniforms(this, root, &getSymbolTable()))
    {
        return false;
    }

    if (!FlagSamplersForTexelFetch(this, root, &getSymbolTable(), &mUniforms))
    {
        return false;
    }

    gl::ShaderType packedShaderType = gl::FromGLenum<gl::ShaderType>(getShaderType());

    if (defaultUniformCount > 0)
    {
        const TVariable *uniformBlock;
        if (!GatherDefaultUniforms(this, root, &getSymbolTable(), packedShaderType,
                                   kDefaultUniformsBlockName, ImmutableString(""), &uniformBlock))
        {
            return false;
        }
        ASSERT(uniformBlock);

        assignSpirvId(uniformBlock->getType().getInterfaceBlock()->uniqueId(),
                      vk::spirv::kIdDefaultUniformsBlock);
    }

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

    assignSpirvId(
        driverUniforms->getDriverUniformsVariable()->getType().getInterfaceBlock()->uniqueId(),
        vk::spirv::kIdDriverUniformsBlock);

    if (getShaderType() == GL_VERTEX_SHADER)
    {
        if (!ShaderBuiltinsWorkaround(this, root, driverUniforms, &getSymbolTable(),
                                      compileOptions))
        {
            return false;
        }
    }

    if (r32fImageCount > 0 && compileOptions.emulateR32fImageAtomicExchange)
    {
        if (!RewriteR32fImages(this, root, &getSymbolTable()))
        {
            return false;
        }
    }

    if (atomicCounterCount > 0)
    {
        // ANGLEUniforms.acbBufferOffsets
        const TIntermTyped *acbBufferOffsets = driverUniforms->getAcbBufferOffsets();
        const TVariable *atomicCounters      = nullptr;
        if (!RewriteAtomicCounters(this, root, &getSymbolTable(), acbBufferOffsets,
                                   &atomicCounters))
        {
            return false;
        }
        assignSpirvId(atomicCounters->getType().getInterfaceBlock()->uniqueId(),
                      vk::spirv::kIdAtomicCounterBlock);
    }
    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 (packedShaderType != gl::ShaderType::Compute)
    {
        if (!ReplaceGLDepthRangeWithDriverUniform(this, root, driverUniforms, &getSymbolTable()))
        {
            return false;
        }

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

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

    if (gl::ShaderTypeSupportsTransformFeedback(packedShaderType))
    {
        if (compileOptions.addVulkanXfbExtensionSupportCode)
        {
            // Add support code for transform feedback extension.
            if (!AddXfbExtensionSupport(this, root, &getSymbolTable(), driverUniforms))
            {
                return false;
            }
        }

        // Add support code for pre-rotation and depth correction in the vertex processing stages.
        if (!AddVertexTransformationSupport(this, compileOptions, root, &getSymbolTable(),
                                            driverUniforms))
        {
            return false;
        }
    }

    if (IsExtensionEnabled(getExtensionBehavior(), TExtension::EXT_YUV_target))
    {
        if (!EmulateYUVBuiltIns(this, root, &getSymbolTable()))
        {
            return false;
        }

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

    if (IsExtensionEnabled(getExtensionBehavior(), TExtension::EXT_YUV_target) ||
        IsExtensionEnabled(getExtensionBehavior(), TExtension::OES_EGL_image_external) ||
        IsExtensionEnabled(getExtensionBehavior(), TExtension::OES_EGL_image_external_essl3))
    {
        if (!RewriteSamplerExternalTexelFetch(this, root, &getSymbolTable()))
        {
            return false;
        }
    }

    switch (packedShaderType)
    {
        case gl::ShaderType::Fragment:
        {
            bool usesPointCoord    = false;
            bool usesFragCoord     = false;
            bool usesSampleMaskIn  = false;
            bool useSamplePosition = false;

            // Search for the gl_PointCoord usage, if its used, we need to flip the y coordinate.
            for (const ShaderVariable &inputVarying : mInputVaryings)
            {
                if (!inputVarying.isBuiltIn())
                {
                    continue;
                }

                if (inputVarying.name == "gl_SampleMaskIn")
                {
                    usesSampleMaskIn = true;
                    continue;
                }

                if (inputVarying.name == "gl_SamplePosition")
                {
                    useSamplePosition = true;
                    continue;
                }

                if (inputVarying.name == "gl_SampleID")
                {
                    const TVariable *sampleID =
                        static_cast<const TVariable *>(getSymbolTable().findBuiltIn(
                            ImmutableString("gl_SampleID"), getShaderVersion()));
                    assignSpirvId(sampleID->uniqueId(), vk::spirv::kIdSampleID);
                    continue;
                }

                if (inputVarying.name == "gl_PointCoord")
                {
                    usesPointCoord = true;
                    continue;
                }

                if (inputVarying.name == "gl_FragCoord")
                {
                    usesFragCoord = true;
                    const TVariable *fragCoord =
                        static_cast<const TVariable *>(getSymbolTable().findBuiltIn(
                            ImmutableString("gl_FragCoord"), getShaderVersion()));
                    assignSpirvId(fragCoord->uniqueId(), vk::spirv::kIdFragCoord);
                    continue;
                }
            }

            bool hasGLSampleMask           = false;
            bool hasGLSecondaryFragData    = false;
            const TIntermSymbol *yuvOutput = nullptr;

            for (const ShaderVariable &outputVar : mOutputVariables)
            {
                if (outputVar.name == "gl_SampleMask")
                {
                    ASSERT(!hasGLSampleMask);
                    hasGLSampleMask = true;
                    continue;
                }
                if (outputVar.name == "gl_SecondaryFragDataEXT")
                {
                    ASSERT(!hasGLSecondaryFragData);
                    hasGLSecondaryFragData = true;
                    continue;
                }
                if (outputVar.yuv)
                {
                    // We can only have one yuv output
                    ASSERT(yuvOutput == nullptr);
                    yuvOutput = FindSymbolNode(root, ImmutableString(outputVar.name));
                    continue;
                }
            }

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

            if (usesPointCoord)
            {
                TIntermTyped *flipNegXY =
                    driverUniforms->getNegFlipXY(&getSymbolTable(), DriverUniformFlip::Fragment);
                TIntermConstantUnion *pivot = CreateFloatNode(0.5f, EbpMedium);
                TIntermTyped *swapXY        = driverUniforms->getSwapXY();

                if (!RotateAndFlipBuiltinVariable(
                        this, root, GetMainSequence(root), swapXY, flipNegXY, &getSymbolTable(),
                        BuiltInVariable::gl_PointCoord(), kFlippedPointCoordName, pivot))
                {
                    return false;
                }
            }

            if (useSamplePosition)
            {
                TIntermTyped *flipXY =
                    driverUniforms->getFlipXY(&getSymbolTable(), DriverUniformFlip::Fragment);
                TIntermConstantUnion *pivot = CreateFloatNode(0.5f, EbpMedium);
                TIntermTyped *swapXY        = driverUniforms->getSwapXY();

                const TVariable *samplePositionBuiltin =
                    static_cast<const TVariable *>(getSymbolTable().findBuiltIn(
                        ImmutableString("gl_SamplePosition"), getShaderVersion()));
                if (!RotateAndFlipBuiltinVariable(this, root, GetMainSequence(root), swapXY, flipXY,
                                                  &getSymbolTable(), samplePositionBuiltin,
                                                  kFlippedSamplePositionName, pivot))
                {
                    return false;
                }
            }

            if (usesFragCoord)
            {
                if (!InsertFragCoordCorrection(this, compileOptions, root, GetMainSequence(root),
                                               &getSymbolTable(), driverUniforms))
                {
                    return false;
                }
                mMetadataFlags[MetadataFlags::HasFragCoord] = true;
            }

            // Emulate gl_FragColor and gl_FragData with normal output variables.
            if (!EmulateFragColorData(this, root, &getSymbolTable(), hasGLSecondaryFragData))
            {
                return false;
            }

            InputAttachmentMap inputAttachmentMap;

            // Emulate framebuffer fetch if used.
            if (HasFramebufferFetch(getExtensionBehavior(), compileOptions))
            {
                if (!EmulateFramebufferFetch(this, root, &inputAttachmentMap))
                {
                    return false;
                }
            }

            // This should be operated after doing ReplaceLastFragData and ReplaceInOutVariables,
            // because they will create the input attachment variables. AddBlendMainCaller will
            // check the existing input attachment variables and if there is no existing input
            // attachment variable then create a new one.
            if (getAdvancedBlendEquations().any() &&
                compileOptions.addAdvancedBlendEquationsEmulation &&
                !EmulateAdvancedBlendEquations(this, root, &getSymbolTable(),
                                               getAdvancedBlendEquations(), driverUniforms,
                                               &inputAttachmentMap))
            {
                return false;
            }

            // Input attachments are potentially added in framebuffer fetch and advanced blend
            // emulation.  Declare their SPIR-V ids.
            assignInputAttachmentIds(inputAttachmentMap);

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

            if (!RewriteInterpolateAtOffset(this, root, &getSymbolTable(), getShaderVersion(),
                                            driverUniforms))
            {
                return false;
            }

            if (usesSampleMaskIn && !RewriteSampleMaskIn(this, root, &getSymbolTable()))
            {
                return false;
            }

            if (hasGLSampleMask)
            {
                TIntermTyped *numSamples = driverUniforms->getNumSamples();
                if (!RewriteSampleMask(this, root, &getSymbolTable(), numSamples))
                {
                    return false;
                }
            }

            {
                const TVariable *numSamplesVar =
                    static_cast<const TVariable *>(getSymbolTable().findBuiltIn(
                        ImmutableString("gl_NumSamples"), getShaderVersion()));
                TIntermTyped *numSamples = driverUniforms->getNumSamples();
                if (numSamplesVar &&
                    !ReplaceVariableWithTyped(this, root, numSamplesVar, numSamples))
                {
                    return false;
                }
            }

            if (IsExtensionEnabled(getExtensionBehavior(), TExtension::EXT_YUV_target))
            {
                if (yuvOutput != nullptr &&
                    !AdjustYUVOutput(this, root, &getSymbolTable(), *yuvOutput))
                {
                    return false;
                }
            }

            break;
        }

        case gl::ShaderType::Vertex:
        {
            if (compileOptions.addVulkanXfbEmulationSupportCode)
            {
                // Add support code for transform feedback emulation.  Only applies to vertex shader
                // as tessellation and geometry shader transform feedback capture require
                // VK_EXT_transform_feedback.
                if (!AddXfbEmulationSupport(this, root, &getSymbolTable(), driverUniforms))
                {
                    return false;
                }
            }

            break;
        }

        case gl::ShaderType::Geometry:
            if (!ClampGLLayer(this, root, &getSymbolTable(), driverUniforms))
            {
                return false;
            }
            break;

        case gl::ShaderType::TessControl:
        {
            if (!ReplaceGLBoundingBoxWithGlobal(this, root, &getSymbolTable(), getShaderVersion()))
            {
                return false;
            }
            break;
        }

        case gl::ShaderType::TessEvaluation:
            break;

        case gl::ShaderType::Compute:
            break;

        default:
            UNREACHABLE();
            break;
    }

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

    // Make sure function call validation is not accidentally left off anywhere.
    ASSERT(mValidateASTOptions.validateFunctionCall);
    ASSERT(mValidateASTOptions.validateNoRawFunctionCalls);

    // Declare the implicitly defined gl_PerVertex I/O blocks if not already.  This will help SPIR-V
    // generation treat them mostly like usual I/O blocks.
    const TVariable *inputPerVertex  = nullptr;
    const TVariable *outputPerVertex = nullptr;
    if (!DeclarePerVertexBlocks(this, root, &getSymbolTable(), &inputPerVertex, &outputPerVertex))
    {
        return false;
    }

    if (inputPerVertex)
    {
        assignSpirvId(inputPerVertex->getType().getInterfaceBlock()->uniqueId(),
                      vk::spirv::kIdInputPerVertexBlock);
    }
    if (outputPerVertex)
    {
        assignSpirvId(outputPerVertex->getType().getInterfaceBlock()->uniqueId(),
                      vk::spirv::kIdOutputPerVertexBlock);
        assignSpirvId(outputPerVertex->uniqueId(), vk::spirv::kIdOutputPerVertexVar);
    }

    // Now that all transformations are done, assign SPIR-V ids to whatever shader variable is still
    // present in the shader in some form.  This should be the last thing done in this function.
    assignSpirvIds(root);

    return true;
}

bool TranslatorSPIRV::translate(TIntermBlock *root,
                                const ShCompileOptions &compileOptions,
                                PerformanceDiagnostics *perfDiagnostics)
{
    mUniqueToSpirvIdMap.clear();
    mFirstUnusedSpirvId = 0;

    DriverUniform driverUniforms(DriverUniformMode::InterfaceBlock);
    DriverUniformExtended driverUniformsExt(DriverUniformMode::InterfaceBlock);

    const bool useExtendedDriverUniforms = compileOptions.addVulkanXfbEmulationSupportCode;

    DriverUniform *uniforms = useExtendedDriverUniforms ? &driverUniformsExt : &driverUniforms;

    if (!translateImpl(root, compileOptions, perfDiagnostics, uniforms))
    {
        return false;
    }

    return OutputSPIRV(this, root, compileOptions, mUniqueToSpirvIdMap, mFirstUnusedSpirvId);
}

bool TranslatorSPIRV::shouldFlattenPragmaStdglInvariantAll()
{
    // Not necessary.
    return false;
}

void TranslatorSPIRV::assignSpirvId(TSymbolUniqueId uniqueId, uint32_t spirvId)
{
    ASSERT(mUniqueToSpirvIdMap.find(uniqueId.get()) == mUniqueToSpirvIdMap.end());
    mUniqueToSpirvIdMap[uniqueId.get()] = spirvId;
}

void TranslatorSPIRV::assignInputAttachmentIds(const InputAttachmentMap &inputAttachmentMap)
{
    for (auto &iter : inputAttachmentMap.color)
    {
        const uint32_t index = iter.first;
        const TVariable *var = iter.second;
        ASSERT(var != nullptr);

        assignSpirvId(var->uniqueId(), vk::spirv::kIdInputAttachment0 + index);

        const MetadataFlags flag = static_cast<MetadataFlags>(
            static_cast<uint32_t>(MetadataFlags::HasInputAttachment0) + index);
        mMetadataFlags.set(flag);
    }

    if (inputAttachmentMap.depth != nullptr)
    {
        assignSpirvId(inputAttachmentMap.depth->uniqueId(), vk::spirv::kIdDepthInputAttachment);
        mMetadataFlags.set(MetadataFlags::HasDepthInputAttachment);
    }

    if (inputAttachmentMap.stencil != nullptr)
    {
        assignSpirvId(inputAttachmentMap.stencil->uniqueId(), vk::spirv::kIdStencilInputAttachment);
        mMetadataFlags.set(MetadataFlags::HasStencilInputAttachment);
    }
}

void TranslatorSPIRV::assignSpirvIds(TIntermBlock *root)
{
    // Match the declarations with collected variables and assign a new id to each, starting from
    // the first unreserved id.  This makes sure that the reserved ids for internal variables and
    // ids for shader variables form a minimal contiguous range.  The Vulkan backend takes advantage
    // of this fact for optimal hashing.
    mFirstUnusedSpirvId = vk::spirv::kIdFirstUnreserved;

    for (TIntermNode *node : *root->getSequence())
    {
        TIntermDeclaration *decl = node->getAsDeclarationNode();
        if (decl == nullptr)
        {
            continue;
        }

        TIntermSymbol *symbol = decl->getSequence()->front()->getAsSymbolNode();
        if (symbol == nullptr)
        {
            continue;
        }

        const TType &type          = symbol->getType();
        const TQualifier qualifier = type.getQualifier();

        // Skip internal symbols, which already have a reserved id.
        const TSymbolUniqueId uniqueId =
            type.isInterfaceBlock() ? type.getInterfaceBlock()->uniqueId() : symbol->uniqueId();
        if (mUniqueToSpirvIdMap.find(uniqueId.get()) != mUniqueToSpirvIdMap.end())
        {
            continue;
        }

        uint32_t *variableId                = nullptr;
        std::vector<ShaderVariable> *fields = nullptr;
        if (type.isInterfaceBlock())
        {
            if (qualifier == EvqPerVertexIn)
            {
                assignSpirvId(uniqueId, vk::spirv::kIdInputPerVertexBlock);
            }
            else if (qualifier == EvqPerVertexOut)
            {
                assignSpirvId(uniqueId, vk::spirv::kIdOutputPerVertexBlock);
                assignSpirvId(symbol->uniqueId(), vk::spirv::kIdOutputPerVertexVar);
            }
            else if (IsVaryingIn(qualifier))
            {
                ShaderVariable *varying =
                    FindIOBlockShaderVariable(&mInputVaryings, type.getInterfaceBlock()->name());
                variableId = &varying->id;
                fields     = &varying->fields;
            }
            else if (IsVaryingOut(qualifier))
            {
                ShaderVariable *varying =
                    FindIOBlockShaderVariable(&mOutputVaryings, type.getInterfaceBlock()->name());
                variableId = &varying->id;
                fields     = &varying->fields;
            }
            else if (IsStorageBuffer(qualifier))
            {
                InterfaceBlock *block =
                    FindShaderVariable(&mShaderStorageBlocks, type.getInterfaceBlock()->name());
                variableId = &block->id;
            }
            else
            {
                InterfaceBlock *block =
                    FindShaderVariable(&mUniformBlocks, type.getInterfaceBlock()->name());
                variableId = &block->id;
            }
        }
        else if (qualifier == EvqUniform)
        {
            ShaderVariable *uniform = FindUniformShaderVariable(&mUniforms, symbol->getName());
            variableId              = &uniform->id;
        }
        else if (qualifier == EvqAttribute || qualifier == EvqVertexIn)
        {
            ShaderVariable *attribute = FindShaderVariable(&mAttributes, symbol->getName());
            variableId                = &attribute->id;
        }
        else if (IsShaderIn(qualifier))
        {
            ShaderVariable *varying = FindShaderVariable(&mInputVaryings, symbol->getName());
            variableId              = &varying->id;
            fields                  = &varying->fields;
        }
        else if (qualifier == EvqFragmentOut)
        {
            // webgl_FragColor, webgl_FragData, webgl_SecondaryFragColor and webgl_SecondaryFragData
            // are recorded with their original names (starting with gl_)
            ImmutableString name(symbol->getName());
            if (angle::BeginsWith(name.data(), "webgl_") &&
                symbol->variable().symbolType() == SymbolType::AngleInternal)
            {
                name = ImmutableString(ANGLE_UNSAFE_TODO(name.data() + 3), name.length() - 3);
            }

            ShaderVariable *output = FindShaderVariable(&mOutputVariables, name);
            variableId             = &output->id;
        }
        else if (IsShaderOut(qualifier))
        {
            ShaderVariable *varying = FindShaderVariable(&mOutputVaryings, symbol->getName());
            variableId              = &varying->id;
            fields                  = &varying->fields;
        }

        if (variableId == nullptr)
        {
            continue;
        }

        ASSERT(variableId != nullptr);
        assignSpirvId(uniqueId, mFirstUnusedSpirvId);
        *variableId = mFirstUnusedSpirvId;

        // Propagate the id to the first field of structs/blocks too.  The front-end gathers
        // varyings as fields, and the transformer needs to infer the variable id (of struct type)
        // just by looking at the fields.
        if (fields != nullptr)
        {
            SetSpirvIdInFields(mFirstUnusedSpirvId, fields);
        }

        ++mFirstUnusedSpirvId;
    }
}
}  // namespace sh
