//
// Copyright (c) 2002-2014 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/Compiler.h"

#include <sstream>

#include "angle_gl.h"
#include "common/utilities.h"
#include "compiler/translator/AddAndTrueToLoopCondition.h"
#include "compiler/translator/Cache.h"
#include "compiler/translator/CallDAG.h"
#include "compiler/translator/DeferGlobalInitializers.h"
#include "compiler/translator/EmulateGLFragColorBroadcast.h"
#include "compiler/translator/EmulatePrecision.h"
#include "compiler/translator/Initialize.h"
#include "compiler/translator/InitializeParseContext.h"
#include "compiler/translator/InitializeVariables.h"
#include "compiler/translator/ParseContext.h"
#include "compiler/translator/PruneEmptyDeclarations.h"
#include "compiler/translator/RegenerateStructNames.h"
#include "compiler/translator/RemoveInvariantDeclaration.h"
#include "compiler/translator/RemovePow.h"
#include "compiler/translator/RewriteDoWhile.h"
#include "compiler/translator/ScalarizeVecAndMatConstructorArgs.h"
#include "compiler/translator/UnfoldShortCircuitAST.h"
#include "compiler/translator/UseInterfaceBlockFields.h"
#include "compiler/translator/ValidateLimitations.h"
#include "compiler/translator/ValidateMaxParameters.h"
#include "compiler/translator/ValidateOutputs.h"
#include "compiler/translator/VariablePacker.h"
#include "third_party/compiler/ArrayBoundsClamper.h"

namespace sh
{

namespace
{

#if defined(ANGLE_ENABLE_FUZZER_CORPUS_OUTPUT)
void DumpFuzzerCase(char const *const *shaderStrings,
                    size_t numStrings,
                    uint32_t type,
                    uint32_t spec,
                    uint32_t output,
                    uint64_t options)
{
    static int fileIndex = 0;

    std::ostringstream o;
    o << "corpus/" << fileIndex++ << ".sample";
    std::string s = o.str();

    // Must match the input format of the fuzzer
    FILE *f = fopen(s.c_str(), "w");
    fwrite(&type, sizeof(type), 1, f);
    fwrite(&spec, sizeof(spec), 1, f);
    fwrite(&output, sizeof(output), 1, f);
    fwrite(&options, sizeof(options), 1, f);

    char zero[128 - 20] = {0};
    fwrite(&zero, 128 - 20, 1, f);

    for (size_t i = 0; i < numStrings; i++)
    {
        fwrite(shaderStrings[i], sizeof(char), strlen(shaderStrings[i]), f);
    }
    fwrite(&zero, 1, 1, f);

    fclose(f);
}
#endif  // defined(ANGLE_ENABLE_FUZZER_CORPUS_OUTPUT)
}  // anonymous namespace

bool IsWebGLBasedSpec(ShShaderSpec spec)
{
    return (spec == SH_WEBGL_SPEC || spec == SH_WEBGL2_SPEC || spec == SH_WEBGL3_SPEC);
}

bool IsGLSL130OrNewer(ShShaderOutput output)
{
    return (output == SH_GLSL_130_OUTPUT || output == SH_GLSL_140_OUTPUT ||
            output == SH_GLSL_150_CORE_OUTPUT || output == SH_GLSL_330_CORE_OUTPUT ||
            output == SH_GLSL_400_CORE_OUTPUT || output == SH_GLSL_410_CORE_OUTPUT ||
            output == SH_GLSL_420_CORE_OUTPUT || output == SH_GLSL_430_CORE_OUTPUT ||
            output == SH_GLSL_440_CORE_OUTPUT || output == SH_GLSL_450_CORE_OUTPUT);
}

bool IsGLSL420OrNewer(ShShaderOutput output)
{
    return (output == SH_GLSL_420_CORE_OUTPUT || output == SH_GLSL_430_CORE_OUTPUT ||
            output == SH_GLSL_440_CORE_OUTPUT || output == SH_GLSL_450_CORE_OUTPUT);
}

bool IsGLSL410OrOlder(ShShaderOutput output)
{
    return (output == SH_GLSL_130_OUTPUT || output == SH_GLSL_140_OUTPUT ||
            output == SH_GLSL_150_CORE_OUTPUT || output == SH_GLSL_330_CORE_OUTPUT ||
            output == SH_GLSL_400_CORE_OUTPUT || output == SH_GLSL_410_CORE_OUTPUT);
}

bool RemoveInvariant(sh::GLenum shaderType,
                     int shaderVersion,
                     ShShaderOutput outputType,
                     ShCompileOptions compileOptions)
{
    if ((compileOptions & SH_DONT_REMOVE_INVARIANT_FOR_FRAGMENT_INPUT) == 0 &&
        shaderType == GL_FRAGMENT_SHADER && IsGLSL420OrNewer(outputType))
        return true;

    if ((compileOptions & SH_REMOVE_INVARIANT_AND_CENTROID_FOR_ESSL3) != 0 &&
        shaderVersion >= 300 && shaderType == GL_VERTEX_SHADER)
        return true;

    return false;
}

size_t GetGlobalMaxTokenSize(ShShaderSpec spec)
{
    // WebGL defines a max token length of 256, while ES2 leaves max token
    // size undefined. ES3 defines a max size of 1024 characters.
    switch (spec)
    {
        case SH_WEBGL_SPEC:
            return 256;
        default:
            return 1024;
    }
}

namespace
{

class TScopedPoolAllocator
{
  public:
    TScopedPoolAllocator(TPoolAllocator *allocator) : mAllocator(allocator)
    {
        mAllocator->push();
        SetGlobalPoolAllocator(mAllocator);
    }
    ~TScopedPoolAllocator()
    {
        SetGlobalPoolAllocator(NULL);
        mAllocator->pop();
    }

  private:
    TPoolAllocator *mAllocator;
};

class TScopedSymbolTableLevel
{
  public:
    TScopedSymbolTableLevel(TSymbolTable *table) : mTable(table)
    {
        ASSERT(mTable->atBuiltInLevel());
        mTable->push();
    }
    ~TScopedSymbolTableLevel()
    {
        while (!mTable->atBuiltInLevel())
            mTable->pop();
    }

  private:
    TSymbolTable *mTable;
};

int MapSpecToShaderVersion(ShShaderSpec spec)
{
    switch (spec)
    {
        case SH_GLES2_SPEC:
        case SH_WEBGL_SPEC:
            return 100;
        case SH_GLES3_SPEC:
        case SH_WEBGL2_SPEC:
            return 300;
        case SH_GLES3_1_SPEC:
        case SH_WEBGL3_SPEC:
            return 310;
        default:
            UNREACHABLE();
            return 0;
    }
}

}  // namespace

TShHandleBase::TShHandleBase()
{
    allocator.push();
    SetGlobalPoolAllocator(&allocator);
}

TShHandleBase::~TShHandleBase()
{
    SetGlobalPoolAllocator(NULL);
    allocator.popAll();
}

TCompiler::TCompiler(sh::GLenum type, ShShaderSpec spec, ShShaderOutput output)
    : variablesCollected(false),
      shaderType(type),
      shaderSpec(spec),
      outputType(output),
      maxUniformVectors(0),
      maxExpressionComplexity(0),
      maxCallStackDepth(0),
      maxFunctionParameters(0),
      fragmentPrecisionHigh(false),
      clampingStrategy(SH_CLAMP_WITH_CLAMP_INTRINSIC),
      builtInFunctionEmulator(),
      mSourcePath(NULL),
      mComputeShaderLocalSizeDeclared(false),
      mTemporaryIndex(0)
{
    mComputeShaderLocalSize.fill(1);
}

TCompiler::~TCompiler()
{
}

bool TCompiler::shouldRunLoopAndIndexingValidation(ShCompileOptions compileOptions) const
{
    // If compiling an ESSL 1.00 shader for WebGL, or if its been requested through the API,
    // validate loop and indexing as well (to verify that the shader only uses minimal functionality
    // of ESSL 1.00 as in Appendix A of the spec).
    return (IsWebGLBasedSpec(shaderSpec) && shaderVersion == 100) ||
           (compileOptions & SH_VALIDATE_LOOP_INDEXING);
}

bool TCompiler::Init(const ShBuiltInResources &resources)
{
    shaderVersion     = 100;
    maxUniformVectors = (shaderType == GL_VERTEX_SHADER) ? resources.MaxVertexUniformVectors
                                                         : resources.MaxFragmentUniformVectors;
    maxExpressionComplexity = resources.MaxExpressionComplexity;
    maxCallStackDepth       = resources.MaxCallStackDepth;
    maxFunctionParameters   = resources.MaxFunctionParameters;

    SetGlobalPoolAllocator(&allocator);

    // Generate built-in symbol table.
    if (!InitBuiltInSymbolTable(resources))
        return false;
    InitExtensionBehavior(resources, extensionBehavior);
    fragmentPrecisionHigh = resources.FragmentPrecisionHigh == 1;

    arrayBoundsClamper.SetClampingStrategy(resources.ArrayIndexClampingStrategy);
    clampingStrategy = resources.ArrayIndexClampingStrategy;

    hashFunction = resources.HashFunction;

    return true;
}

TIntermBlock *TCompiler::compileTreeForTesting(const char *const shaderStrings[],
                                               size_t numStrings,
                                               ShCompileOptions compileOptions)
{
    return compileTreeImpl(shaderStrings, numStrings, compileOptions);
}

TIntermBlock *TCompiler::compileTreeImpl(const char *const shaderStrings[],
                                         size_t numStrings,
                                         const ShCompileOptions compileOptions)
{
    clearResults();

    ASSERT(numStrings > 0);
    ASSERT(GetGlobalPoolAllocator());

    // Reset the extension behavior for each compilation unit.
    ResetExtensionBehavior(extensionBehavior);

    // First string is path of source file if flag is set. The actual source follows.
    size_t firstSource = 0;
    if (compileOptions & SH_SOURCE_PATH)
    {
        mSourcePath = shaderStrings[0];
        ++firstSource;
    }

    TParseContext parseContext(symbolTable, extensionBehavior, shaderType, shaderSpec,
                               compileOptions, true, infoSink, getResources());

    parseContext.setFragmentPrecisionHighOnESSL1(fragmentPrecisionHigh);
    SetGlobalParseContext(&parseContext);

    // We preserve symbols at the built-in level from compile-to-compile.
    // Start pushing the user-defined symbols at global level.
    TScopedSymbolTableLevel scopedSymbolLevel(&symbolTable);

    // Parse shader.
    bool success = (PaParseStrings(numStrings - firstSource, &shaderStrings[firstSource], nullptr,
                                   &parseContext) == 0) &&
                   (parseContext.getTreeRoot() != nullptr);

    shaderVersion = parseContext.getShaderVersion();
    if (success && MapSpecToShaderVersion(shaderSpec) < shaderVersion)
    {
        infoSink.info.prefix(EPrefixError);
        infoSink.info << "unsupported shader version";
        success = false;
    }

    TIntermBlock *root = nullptr;

    if (success)
    {
        mPragma = parseContext.pragma();
        symbolTable.setGlobalInvariant(mPragma.stdgl.invariantAll);

        mComputeShaderLocalSizeDeclared = parseContext.isComputeShaderLocalSizeDeclared();
        mComputeShaderLocalSize         = parseContext.getComputeShaderLocalSize();

        root = parseContext.getTreeRoot();

        // Highp might have been auto-enabled based on shader version
        fragmentPrecisionHigh = parseContext.getFragmentPrecisionHigh();

        // Disallow expressions deemed too complex.
        if (success && (compileOptions & SH_LIMIT_EXPRESSION_COMPLEXITY))
            success = limitExpressionComplexity(root);

        // Create the function DAG and check there is no recursion
        if (success)
            success = initCallDag(root);

        if (success && (compileOptions & SH_LIMIT_CALL_STACK_DEPTH))
            success = checkCallDepth();

        // Checks which functions are used and if "main" exists
        if (success)
        {
            functionMetadata.clear();
            functionMetadata.resize(mCallDag.size());
            success = tagUsedFunctions();
        }

        if (success && !(compileOptions & SH_DONT_PRUNE_UNUSED_FUNCTIONS))
            success = pruneUnusedFunctions(root);

        // Prune empty declarations to work around driver bugs and to keep declaration output
        // simple.
        if (success)
            PruneEmptyDeclarations(root);

        if (success && shaderVersion == 300 && shaderType == GL_FRAGMENT_SHADER)
            success = validateOutputs(root);

        if (success && shouldRunLoopAndIndexingValidation(compileOptions))
            success = validateLimitations(root);

        // Fail compilation if precision emulation not supported.
        if (success && getResources().WEBGL_debug_shader_precision &&
            getPragma().debugShaderPrecision)
        {
            if (!EmulatePrecision::SupportedInLanguage(outputType))
            {
                infoSink.info.prefix(EPrefixError);
                infoSink.info << "Precision emulation not supported for this output type.";
                success = false;
            }
        }

        // Built-in function emulation needs to happen after validateLimitations pass.
        if (success)
        {
            // TODO(jmadill): Remove global pool allocator.
            GetGlobalPoolAllocator()->lock();
            initBuiltInFunctionEmulator(&builtInFunctionEmulator, compileOptions);
            GetGlobalPoolAllocator()->unlock();
            builtInFunctionEmulator.MarkBuiltInFunctionsForEmulation(root);
        }

        // Clamping uniform array bounds needs to happen after validateLimitations pass.
        if (success && (compileOptions & SH_CLAMP_INDIRECT_ARRAY_BOUNDS))
            arrayBoundsClamper.MarkIndirectArrayBoundsForClamping(root);

        // gl_Position is always written in compatibility output mode
        if (success && shaderType == GL_VERTEX_SHADER &&
            ((compileOptions & SH_INIT_GL_POSITION) ||
             (outputType == SH_GLSL_COMPATIBILITY_OUTPUT)))
            initializeGLPosition(root);

        if (success && RemoveInvariant(shaderType, shaderVersion, outputType, compileOptions))
            sh::RemoveInvariantDeclaration(root);

        // This pass might emit short circuits so keep it before the short circuit unfolding
        if (success && (compileOptions & SH_REWRITE_DO_WHILE_LOOPS))
            RewriteDoWhile(root, getTemporaryIndex());

        if (success && (compileOptions & SH_ADD_AND_TRUE_TO_LOOP_CONDITION))
            sh::AddAndTrueToLoopCondition(root);

        if (success && (compileOptions & SH_UNFOLD_SHORT_CIRCUIT))
        {
            UnfoldShortCircuitAST unfoldShortCircuit;
            root->traverse(&unfoldShortCircuit);
            unfoldShortCircuit.updateTree();
        }

        if (success && (compileOptions & SH_REMOVE_POW_WITH_CONSTANT_EXPONENT))
        {
            RemovePow(root);
        }

        if (success && shouldCollectVariables(compileOptions))
        {
            collectVariables(root);
            if (compileOptions & SH_USE_UNUSED_STANDARD_SHARED_BLOCKS)
            {
                useAllMembersInUnusedStandardAndSharedBlocks(root);
            }
            if (compileOptions & SH_ENFORCE_PACKING_RESTRICTIONS)
            {
                success = enforcePackingRestrictions();
                if (!success)
                {
                    infoSink.info.prefix(EPrefixError);
                    infoSink.info << "too many uniforms";
                }
            }
            if (success && (compileOptions & SH_INIT_OUTPUT_VARIABLES))
            {
                initializeOutputVariables(root);
            }
        }

        if (success && (compileOptions & SH_SCALARIZE_VEC_AND_MAT_CONSTRUCTOR_ARGS))
        {
            ScalarizeVecAndMatConstructorArgs(root, shaderType, fragmentPrecisionHigh,
                                              &mTemporaryIndex);
        }

        if (success && (compileOptions & SH_REGENERATE_STRUCT_NAMES))
        {
            RegenerateStructNames gen(symbolTable, shaderVersion);
            root->traverse(&gen);
        }

        if (success && shaderType == GL_FRAGMENT_SHADER && shaderVersion == 100 &&
            compileResources.EXT_draw_buffers && compileResources.MaxDrawBuffers > 1 &&
            IsExtensionEnabled(extensionBehavior, "GL_EXT_draw_buffers"))
        {
            EmulateGLFragColorBroadcast(root, compileResources.MaxDrawBuffers, &outputVariables);
        }

        if (success)
        {
            DeferGlobalInitializers(root);
        }
    }

    SetGlobalParseContext(NULL);
    if (success)
        return root;

    return NULL;
}

bool TCompiler::compile(const char *const shaderStrings[],
                        size_t numStrings,
                        ShCompileOptions compileOptionsIn)
{
#if defined(ANGLE_ENABLE_FUZZER_CORPUS_OUTPUT)
    DumpFuzzerCase(shaderStrings, numStrings, shaderType, shaderSpec, outputType, compileOptionsIn);
#endif  // defined(ANGLE_ENABLE_FUZZER_CORPUS_OUTPUT)

    if (numStrings == 0)
        return true;

    ShCompileOptions compileOptions = compileOptionsIn;

    // Apply key workarounds.
    if (shouldFlattenPragmaStdglInvariantAll())
    {
        // This should be harmless to do in all cases, but for the moment, do it only conditionally.
        compileOptions |= SH_FLATTEN_PRAGMA_STDGL_INVARIANT_ALL;
    }

    TScopedPoolAllocator scopedAlloc(&allocator);
    TIntermBlock *root = compileTreeImpl(shaderStrings, numStrings, compileOptions);

    if (root)
    {
        if (compileOptions & SH_INTERMEDIATE_TREE)
            TIntermediate::outputTree(root, infoSink.info);

        if (compileOptions & SH_OBJECT_CODE)
            translate(root, compileOptions);

        // The IntermNode tree doesn't need to be deleted here, since the
        // memory will be freed in a big chunk by the PoolAllocator.
        return true;
    }
    return false;
}

bool TCompiler::InitBuiltInSymbolTable(const ShBuiltInResources &resources)
{
    if (resources.MaxDrawBuffers < 1)
    {
        return false;
    }
    if (resources.EXT_blend_func_extended && resources.MaxDualSourceDrawBuffers < 1)
    {
        return false;
    }

    compileResources = resources;
    setResourceString();

    assert(symbolTable.isEmpty());
    symbolTable.push();  // COMMON_BUILTINS
    symbolTable.push();  // ESSL1_BUILTINS
    symbolTable.push();  // ESSL3_BUILTINS
    symbolTable.push();  // ESSL3_1_BUILTINS

    TPublicType integer;
    integer.initializeBasicType(EbtInt);

    TPublicType floatingPoint;
    floatingPoint.initializeBasicType(EbtFloat);

    switch (shaderType)
    {
        case GL_FRAGMENT_SHADER:
            symbolTable.setDefaultPrecision(integer, EbpMedium);
            break;
        case GL_VERTEX_SHADER:
            symbolTable.setDefaultPrecision(integer, EbpHigh);
            symbolTable.setDefaultPrecision(floatingPoint, EbpHigh);
            break;
        case GL_COMPUTE_SHADER:
            symbolTable.setDefaultPrecision(integer, EbpHigh);
            symbolTable.setDefaultPrecision(floatingPoint, EbpHigh);
            break;
        default:
            assert(false && "Language not supported");
    }
    // Set defaults for sampler types that have default precision, even those that are
    // only available if an extension exists.
    // New sampler types in ESSL3 don't have default precision. ESSL1 types do.
    initSamplerDefaultPrecision(EbtSampler2D);
    initSamplerDefaultPrecision(EbtSamplerCube);
    // SamplerExternalOES is specified in the extension to have default precision.
    initSamplerDefaultPrecision(EbtSamplerExternalOES);
    // It isn't specified whether Sampler2DRect has default precision.
    initSamplerDefaultPrecision(EbtSampler2DRect);

    InsertBuiltInFunctions(shaderType, shaderSpec, resources, symbolTable);

    IdentifyBuiltIns(shaderType, shaderSpec, resources, symbolTable);

    return true;
}

void TCompiler::initSamplerDefaultPrecision(TBasicType samplerType)
{
    ASSERT(samplerType > EbtGuardSamplerBegin && samplerType < EbtGuardSamplerEnd);
    TPublicType sampler;
    sampler.initializeBasicType(samplerType);
    symbolTable.setDefaultPrecision(sampler, EbpLow);
}

void TCompiler::setResourceString()
{
    std::ostringstream strstream;

    // clang-format off
    strstream << ":MaxVertexAttribs:" << compileResources.MaxVertexAttribs
        << ":MaxVertexUniformVectors:" << compileResources.MaxVertexUniformVectors
        << ":MaxVaryingVectors:" << compileResources.MaxVaryingVectors
        << ":MaxVertexTextureImageUnits:" << compileResources.MaxVertexTextureImageUnits
        << ":MaxCombinedTextureImageUnits:" << compileResources.MaxCombinedTextureImageUnits
        << ":MaxTextureImageUnits:" << compileResources.MaxTextureImageUnits
        << ":MaxFragmentUniformVectors:" << compileResources.MaxFragmentUniformVectors
        << ":MaxDrawBuffers:" << compileResources.MaxDrawBuffers
        << ":OES_standard_derivatives:" << compileResources.OES_standard_derivatives
        << ":OES_EGL_image_external:" << compileResources.OES_EGL_image_external
        << ":OES_EGL_image_external_essl3:" << compileResources.OES_EGL_image_external_essl3
        << ":NV_EGL_stream_consumer_external:" << compileResources.NV_EGL_stream_consumer_external
        << ":ARB_texture_rectangle:" << compileResources.ARB_texture_rectangle
        << ":EXT_draw_buffers:" << compileResources.EXT_draw_buffers
        << ":FragmentPrecisionHigh:" << compileResources.FragmentPrecisionHigh
        << ":MaxExpressionComplexity:" << compileResources.MaxExpressionComplexity
        << ":MaxCallStackDepth:" << compileResources.MaxCallStackDepth
        << ":MaxFunctionParameters:" << compileResources.MaxFunctionParameters
        << ":EXT_blend_func_extended:" << compileResources.EXT_blend_func_extended
        << ":EXT_frag_depth:" << compileResources.EXT_frag_depth
        << ":EXT_shader_texture_lod:" << compileResources.EXT_shader_texture_lod
        << ":EXT_shader_framebuffer_fetch:" << compileResources.EXT_shader_framebuffer_fetch
        << ":NV_shader_framebuffer_fetch:" << compileResources.NV_shader_framebuffer_fetch
        << ":ARM_shader_framebuffer_fetch:" << compileResources.ARM_shader_framebuffer_fetch
        << ":MaxVertexOutputVectors:" << compileResources.MaxVertexOutputVectors
        << ":MaxFragmentInputVectors:" << compileResources.MaxFragmentInputVectors
        << ":MinProgramTexelOffset:" << compileResources.MinProgramTexelOffset
        << ":MaxProgramTexelOffset:" << compileResources.MaxProgramTexelOffset
        << ":MaxDualSourceDrawBuffers:" << compileResources.MaxDualSourceDrawBuffers
        << ":NV_draw_buffers:" << compileResources.NV_draw_buffers
        << ":WEBGL_debug_shader_precision:" << compileResources.WEBGL_debug_shader_precision
        << ":MaxImageUnits:" << compileResources.MaxImageUnits
        << ":MaxVertexImageUniforms:" << compileResources.MaxVertexImageUniforms
        << ":MaxFragmentImageUniforms:" << compileResources.MaxFragmentImageUniforms
        << ":MaxComputeImageUniforms:" << compileResources.MaxComputeImageUniforms
        << ":MaxCombinedImageUniforms:" << compileResources.MaxCombinedImageUniforms
        << ":MaxCombinedShaderOutputResources:" << compileResources.MaxCombinedShaderOutputResources
        << ":MaxComputeWorkGroupCountX:" << compileResources.MaxComputeWorkGroupCount[0]
        << ":MaxComputeWorkGroupCountY:" << compileResources.MaxComputeWorkGroupCount[1]
        << ":MaxComputeWorkGroupCountZ:" << compileResources.MaxComputeWorkGroupCount[2]
        << ":MaxComputeWorkGroupSizeX:" << compileResources.MaxComputeWorkGroupSize[0]
        << ":MaxComputeWorkGroupSizeY:" << compileResources.MaxComputeWorkGroupSize[1]
        << ":MaxComputeWorkGroupSizeZ:" << compileResources.MaxComputeWorkGroupSize[2]
        << ":MaxComputeUniformComponents:" << compileResources.MaxComputeUniformComponents
        << ":MaxComputeTextureImageUnits:" << compileResources.MaxComputeTextureImageUnits
        << ":MaxComputeAtomicCounters:" << compileResources.MaxComputeAtomicCounters
        << ":MaxComputeAtomicCounterBuffers:" << compileResources.MaxComputeAtomicCounterBuffers
        << ":MaxVertexAtomicCounters:" << compileResources.MaxVertexAtomicCounters
        << ":MaxFragmentAtomicCounters:" << compileResources.MaxFragmentAtomicCounters
        << ":MaxCombinedAtomicCounters:" << compileResources.MaxCombinedAtomicCounters
        << ":MaxAtomicCounterBindings:" << compileResources.MaxAtomicCounterBindings
        << ":MaxVertexAtomicCounterBuffers:" << compileResources.MaxVertexAtomicCounterBuffers
        << ":MaxFragmentAtomicCounterBuffers:" << compileResources.MaxFragmentAtomicCounterBuffers
        << ":MaxCombinedAtomicCounterBuffers:" << compileResources.MaxCombinedAtomicCounterBuffers
        << ":MaxAtomicCounterBufferSize:" << compileResources.MaxAtomicCounterBufferSize;
    // clang-format on

    builtInResourcesString = strstream.str();
}

void TCompiler::clearResults()
{
    arrayBoundsClamper.Cleanup();
    infoSink.info.erase();
    infoSink.obj.erase();
    infoSink.debug.erase();

    attributes.clear();
    outputVariables.clear();
    uniforms.clear();
    expandedUniforms.clear();
    varyings.clear();
    interfaceBlocks.clear();
    variablesCollected = false;

    builtInFunctionEmulator.Cleanup();

    nameMap.clear();

    mSourcePath     = NULL;
    mTemporaryIndex = 0;
}

bool TCompiler::initCallDag(TIntermNode *root)
{
    mCallDag.clear();

    switch (mCallDag.init(root, &infoSink.info))
    {
        case CallDAG::INITDAG_SUCCESS:
            return true;
        case CallDAG::INITDAG_RECURSION:
            infoSink.info.prefix(EPrefixError);
            infoSink.info << "Function recursion detected";
            return false;
        case CallDAG::INITDAG_UNDEFINED:
            infoSink.info.prefix(EPrefixError);
            infoSink.info << "Unimplemented function detected";
            return false;
    }

    UNREACHABLE();
    return true;
}

bool TCompiler::checkCallDepth()
{
    std::vector<int> depths(mCallDag.size());

    for (size_t i = 0; i < mCallDag.size(); i++)
    {
        int depth    = 0;
        auto &record = mCallDag.getRecordFromIndex(i);

        for (auto &calleeIndex : record.callees)
        {
            depth = std::max(depth, depths[calleeIndex] + 1);
        }

        depths[i] = depth;

        if (depth >= maxCallStackDepth)
        {
            // Trace back the function chain to have a meaningful info log.
            infoSink.info.prefix(EPrefixError);
            infoSink.info << "Call stack too deep (larger than " << maxCallStackDepth
                          << ") with the following call chain: " << record.name;

            int currentFunction = static_cast<int>(i);
            int currentDepth    = depth;

            while (currentFunction != -1)
            {
                infoSink.info << " -> " << mCallDag.getRecordFromIndex(currentFunction).name;

                int nextFunction = -1;
                for (auto &calleeIndex : mCallDag.getRecordFromIndex(currentFunction).callees)
                {
                    if (depths[calleeIndex] == currentDepth - 1)
                    {
                        currentDepth--;
                        nextFunction = calleeIndex;
                    }
                }

                currentFunction = nextFunction;
            }

            return false;
        }
    }

    return true;
}

bool TCompiler::tagUsedFunctions()
{
    // Search from main, starting from the end of the DAG as it usually is the root.
    for (size_t i = mCallDag.size(); i-- > 0;)
    {
        if (mCallDag.getRecordFromIndex(i).name == "main(")
        {
            internalTagUsedFunction(i);
            return true;
        }
    }

    infoSink.info.prefix(EPrefixError);
    infoSink.info << "Missing main()\n";
    return false;
}

void TCompiler::internalTagUsedFunction(size_t index)
{
    if (functionMetadata[index].used)
    {
        return;
    }

    functionMetadata[index].used = true;

    for (int calleeIndex : mCallDag.getRecordFromIndex(index).callees)
    {
        internalTagUsedFunction(calleeIndex);
    }
}

// A predicate for the stl that returns if a top-level node is unused
class TCompiler::UnusedPredicate
{
  public:
    UnusedPredicate(const CallDAG *callDag, const std::vector<FunctionMetadata> *metadatas)
        : mCallDag(callDag), mMetadatas(metadatas)
    {
    }

    bool operator()(TIntermNode *node)
    {
        const TIntermAggregate *asAggregate         = node->getAsAggregate();
        const TIntermFunctionDefinition *asFunction = node->getAsFunctionDefinition();

        const TFunctionSymbolInfo *functionInfo = nullptr;

        if (asFunction)
        {
            functionInfo = asFunction->getFunctionSymbolInfo();
        }
        else if (asAggregate)
        {
            if (asAggregate->getOp() == EOpPrototype)
            {
                functionInfo = asAggregate->getFunctionSymbolInfo();
            }
        }
        if (functionInfo == nullptr)
        {
            return false;
        }

        size_t callDagIndex = mCallDag->findIndex(functionInfo);
        if (callDagIndex == CallDAG::InvalidIndex)
        {
            // This happens only for unimplemented prototypes which are thus unused
            ASSERT(asAggregate && asAggregate->getOp() == EOpPrototype);
            return true;
        }

        ASSERT(callDagIndex < mMetadatas->size());
        return !(*mMetadatas)[callDagIndex].used;
    }

  private:
    const CallDAG *mCallDag;
    const std::vector<FunctionMetadata> *mMetadatas;
};

bool TCompiler::pruneUnusedFunctions(TIntermBlock *root)
{
    UnusedPredicate isUnused(&mCallDag, &functionMetadata);
    TIntermSequence *sequence = root->getSequence();

    if (!sequence->empty())
    {
        sequence->erase(std::remove_if(sequence->begin(), sequence->end(), isUnused),
                        sequence->end());
    }

    return true;
}

bool TCompiler::validateOutputs(TIntermNode *root)
{
    ValidateOutputs validateOutputs(getExtensionBehavior(), compileResources.MaxDrawBuffers);
    root->traverse(&validateOutputs);
    return (validateOutputs.validateAndCountErrors(infoSink.info) == 0);
}

bool TCompiler::validateLimitations(TIntermNode *root)
{
    ValidateLimitations validate(shaderType, &infoSink.info);
    root->traverse(&validate);
    return validate.numErrors() == 0;
}

bool TCompiler::limitExpressionComplexity(TIntermNode *root)
{
    TMaxDepthTraverser traverser(maxExpressionComplexity + 1);
    root->traverse(&traverser);

    if (traverser.getMaxDepth() > maxExpressionComplexity)
    {
        infoSink.info << "Expression too complex.";
        return false;
    }

    if (!ValidateMaxParameters::validate(root, maxFunctionParameters))
    {
        infoSink.info << "Function has too many parameters.";
        return false;
    }

    return true;
}

void TCompiler::collectVariables(TIntermNode *root)
{
    if (!variablesCollected)
    {
        sh::CollectVariables collect(&attributes, &outputVariables, &uniforms, &varyings,
                                     &interfaceBlocks, hashFunction, symbolTable,
                                     extensionBehavior);
        root->traverse(&collect);

        // This is for enforcePackingRestriction().
        sh::ExpandUniforms(uniforms, &expandedUniforms);
        variablesCollected = true;
    }
}

bool TCompiler::shouldCollectVariables(ShCompileOptions compileOptions)
{
    return (compileOptions & SH_VARIABLES) != 0;
}

bool TCompiler::wereVariablesCollected() const
{
    return variablesCollected;
}

bool TCompiler::enforcePackingRestrictions()
{
    VariablePacker packer;
    return packer.CheckVariablesWithinPackingLimits(maxUniformVectors, expandedUniforms);
}

void TCompiler::initializeGLPosition(TIntermNode *root)
{
    InitVariableList list;
    sh::ShaderVariable var(GL_FLOAT_VEC4, 0);
    var.name = "gl_Position";
    list.push_back(var);
    InitializeVariables(root, list, symbolTable);
}

void TCompiler::useAllMembersInUnusedStandardAndSharedBlocks(TIntermNode *root)
{
    sh::InterfaceBlockList list;

    for (auto block : interfaceBlocks)
    {
        if (!block.staticUse &&
            (block.layout == sh::BLOCKLAYOUT_STANDARD || block.layout == sh::BLOCKLAYOUT_SHARED))
        {
            list.push_back(block);
        }
    }

    sh::UseInterfaceBlockFields(root, list, symbolTable);
}

void TCompiler::initializeOutputVariables(TIntermNode *root)
{
    InitVariableList list;
    if (shaderType == GL_VERTEX_SHADER)
    {
        for (auto var : varyings)
        {
            list.push_back(var);
        }
    }
    else
    {
        ASSERT(shaderType == GL_FRAGMENT_SHADER);
        for (auto var : outputVariables)
        {
            list.push_back(var);
        }
    }
    InitializeVariables(root, list, symbolTable);
}

const TExtensionBehavior &TCompiler::getExtensionBehavior() const
{
    return extensionBehavior;
}

const char *TCompiler::getSourcePath() const
{
    return mSourcePath;
}

const ShBuiltInResources &TCompiler::getResources() const
{
    return compileResources;
}

const ArrayBoundsClamper &TCompiler::getArrayBoundsClamper() const
{
    return arrayBoundsClamper;
}

ShArrayIndexClampingStrategy TCompiler::getArrayIndexClampingStrategy() const
{
    return clampingStrategy;
}

const BuiltInFunctionEmulator &TCompiler::getBuiltInFunctionEmulator() const
{
    return builtInFunctionEmulator;
}

void TCompiler::writePragma(ShCompileOptions compileOptions)
{
    if (!(compileOptions & SH_FLATTEN_PRAGMA_STDGL_INVARIANT_ALL))
    {
        TInfoSinkBase &sink = infoSink.obj;
        if (mPragma.stdgl.invariantAll)
            sink << "#pragma STDGL invariant(all)\n";
    }
}

bool TCompiler::isVaryingDefined(const char *varyingName)
{
    ASSERT(variablesCollected);
    for (size_t ii = 0; ii < varyings.size(); ++ii)
    {
        if (varyings[ii].name == varyingName)
        {
            return true;
        }
    }

    return false;
}

}  // namespace sh
