//
// Copyright 2002 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//

#include "compiler/translator/tree_ops/InitializeVariables.h"

#include "angle_gl.h"
#include "common/debug.h"
#include "common/hash_containers.h"
#include "compiler/translator/Compiler.h"
#include "compiler/translator/StaticType.h"
#include "compiler/translator/SymbolTable.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/IntermTraverse.h"
#include "compiler/translator/util.h"

namespace sh
{

namespace
{

bool IsNamelessStruct(const TType &type)
{
    // There are two kinds of nameless structs that need to be handled here.  When SymbolType is
    // Empty, it's a struct that can take a temporary name.  When the struct is "nameless", it
    // _must_ stay without a name (because it's part of the shader's interface).
    return type.getStruct() != nullptr &&
           (type.getStruct()->symbolType() == SymbolType::Empty || type.getStruct()->isNameless());
}

void AddArrayZeroInitSequence(const TIntermTyped *initializedNode,
                              bool canUseLoopsToInitialize,
                              TIntermSequence *initSequenceOut,
                              TSymbolTable *symbolTable);

void AddStructZeroInitSequence(const TIntermTyped *initializedNode,
                               bool canUseLoopsToInitialize,
                               TIntermSequence *initSequenceOut,
                               TSymbolTable *symbolTable);

TIntermBinary *CreateZeroInitAssignment(const TIntermTyped *initializedNode)
{
    TIntermTyped *zero = CreateZeroNode(initializedNode->getType());
    return new TIntermBinary(EOpAssign, initializedNode->deepCopy(), zero);
}

void AddZeroInitSequence(const TIntermTyped *initializedNode,
                         bool canUseLoopsToInitialize,
                         TIntermSequence *initSequenceOut,
                         TSymbolTable *symbolTable)
{
    if (initializedNode->isArray())
    {
        AddArrayZeroInitSequence(initializedNode, canUseLoopsToInitialize, initSequenceOut,
                                 symbolTable);
    }
    else if (initializedNode->getType().isStructureContainingArrays() ||
             IsNamelessStruct(initializedNode->getType()))
    {
        AddStructZeroInitSequence(initializedNode, canUseLoopsToInitialize, initSequenceOut,
                                  symbolTable);
    }
    else if (initializedNode->getType().isInterfaceBlock())
    {
        const TType &type                     = initializedNode->getType();
        const TInterfaceBlock &interfaceBlock = *type.getInterfaceBlock();
        const TFieldList &fieldList           = interfaceBlock.fields();
        for (size_t fieldIndex = 0; fieldIndex < fieldList.size(); ++fieldIndex)
        {
            const TField &field         = *fieldList[fieldIndex];
            TIntermTyped *fieldIndexRef = CreateIndexNode(static_cast<int>(fieldIndex));
            TIntermTyped *fieldReference =
                new TIntermBinary(TOperator::EOpIndexDirectInterfaceBlock,
                                  initializedNode->deepCopy(), fieldIndexRef);
            TIntermTyped *fieldZero = CreateZeroNode(*field.type());
            TIntermTyped *assignment =
                new TIntermBinary(TOperator::EOpAssign, fieldReference, fieldZero);
            initSequenceOut->push_back(assignment);
        }
    }
    else
    {
        initSequenceOut->push_back(CreateZeroInitAssignment(initializedNode));
    }
}

void AddStructZeroInitSequence(const TIntermTyped *initializedNode,
                               bool canUseLoopsToInitialize,
                               TIntermSequence *initSequenceOut,
                               TSymbolTable *symbolTable)
{
    ASSERT(initializedNode->getBasicType() == EbtStruct);
    const TStructure *structType = initializedNode->getType().getStruct();
    for (int i = 0; i < static_cast<int>(structType->fields().size()); ++i)
    {
        TIntermBinary *element = new TIntermBinary(EOpIndexDirectStruct,
                                                   initializedNode->deepCopy(), CreateIndexNode(i));
        // Structs can't be defined inside structs, so the type of a struct field can't be a
        // nameless struct.
        ASSERT(!IsNamelessStruct(element->getType()));
        AddZeroInitSequence(element, canUseLoopsToInitialize, initSequenceOut, symbolTable);
    }
}

void AddArrayZeroInitStatementList(const TIntermTyped *initializedNode,
                                   bool canUseLoopsToInitialize,
                                   TIntermSequence *initSequenceOut,
                                   TSymbolTable *symbolTable)
{
    for (unsigned int i = 0; i < initializedNode->getOutermostArraySize(); ++i)
    {
        TIntermBinary *element =
            new TIntermBinary(EOpIndexDirect, initializedNode->deepCopy(), CreateIndexNode(i));
        AddZeroInitSequence(element, canUseLoopsToInitialize, initSequenceOut, symbolTable);
    }
}

void AddArrayZeroInitForLoop(const TIntermTyped *initializedNode,
                             TIntermSequence *initSequenceOut,
                             TSymbolTable *symbolTable)
{
    ASSERT(initializedNode->isArray());
    TVariable *indexVariable =
        CreateTempVariable(symbolTable, StaticType::Get<EbtInt, EbpHigh, EvqTemporary, 1, 1>());

    TIntermSymbol *indexSymbolNode = CreateTempSymbolNode(indexVariable);
    TIntermDeclaration *indexInit =
        CreateTempInitDeclarationNode(indexVariable, CreateZeroNode(indexVariable->getType()));
    TIntermConstantUnion *arraySizeNode = CreateIndexNode(initializedNode->getOutermostArraySize());
    TIntermBinary *indexSmallerThanSize =
        new TIntermBinary(EOpLessThan, indexSymbolNode->deepCopy(), arraySizeNode);
    TIntermUnary *indexIncrement =
        new TIntermUnary(EOpPreIncrement, indexSymbolNode->deepCopy(), nullptr);

    TIntermBlock *forLoopBody       = new TIntermBlock();
    TIntermSequence *forLoopBodySeq = forLoopBody->getSequence();

    TIntermBinary *element = new TIntermBinary(EOpIndexIndirect, initializedNode->deepCopy(),
                                               indexSymbolNode->deepCopy());
    AddZeroInitSequence(element, true, forLoopBodySeq, symbolTable);

    TIntermLoop *forLoop =
        new TIntermLoop(ELoopFor, indexInit, indexSmallerThanSize, indexIncrement, forLoopBody);
    initSequenceOut->push_back(forLoop);
}

void AddArrayZeroInitSequence(const TIntermTyped *initializedNode,
                              bool canUseLoopsToInitialize,
                              TIntermSequence *initSequenceOut,
                              TSymbolTable *symbolTable)
{
    // The array elements are assigned one by one to keep the AST compatible with ESSL 1.00 which
    // doesn't have array assignment. We'll do this either with a for loop or just a list of
    // statements assigning to each array index. Note that it is important to have the array init in
    // the right order to workaround http://crbug.com/40514481
    bool isSmallArray = initializedNode->getOutermostArraySize() <= 1u ||
                        (initializedNode->getBasicType() != EbtStruct &&
                         !initializedNode->getType().isArrayOfArrays() &&
                         initializedNode->getOutermostArraySize() <= 3u);
    if (initializedNode->getQualifier() == EvqFragData ||
        initializedNode->getQualifier() == EvqFragmentOut || isSmallArray ||
        !canUseLoopsToInitialize)
    {
        // Fragment outputs should not be indexed by non-constant indices.
        // Also it doesn't make sense to use loops to initialize very small arrays.
        AddArrayZeroInitStatementList(initializedNode, canUseLoopsToInitialize, initSequenceOut,
                                      symbolTable);
    }
    else
    {
        AddArrayZeroInitForLoop(initializedNode, initSequenceOut, symbolTable);
    }
}

void InsertInitCode(TCompiler *compiler,
                    TIntermBlock *root,
                    const InitVariableList &variables,
                    TSymbolTable *symbolTable,
                    int shaderVersion,
                    const TExtensionBehavior &extensionBehavior,
                    bool canUseLoopsToInitialize)
{
    const bool secondaryFragDataUsed = symbolTable->isSecondaryFragDataUsed();

    TIntermSequence *mainBody = FindMainBody(root)->getSequence();
    for (const TVariable *var : variables)
    {
        TIntermTyped *initializedSymbol = nullptr;

        if (var->symbolType() == SymbolType::Empty)
        {
            // Must be a nameless interface block.
            ASSERT(var->getType().getInterfaceBlock() != nullptr);
            ASSERT(!var->getType().getInterfaceBlock()->name().empty());

            const TInterfaceBlock *block = var->getType().getInterfaceBlock();
            for (const TField *field : block->fields())
            {
                initializedSymbol = ReferenceGlobalVariable(field->name(), *symbolTable);

                TIntermSequence initCode;
                CreateInitCode(initializedSymbol, canUseLoopsToInitialize, &initCode, symbolTable);
                mainBody->insert(mainBody->begin(), initCode.begin(), initCode.end());
            }

            // All done with the interface block
            continue;
        }

        const TQualifier qualifier = var->getType().getQualifier();

        initializedSymbol = new TIntermSymbol(var);
        if (qualifier == EvqFragData &&
            (!IsExtensionEnabled(extensionBehavior, TExtension::EXT_draw_buffers) ||
             secondaryFragDataUsed))
        {
            // If GL_EXT_draw_buffers is disabled, only the 0th index of gl_FragData can be
            // written to.  Same with if dual source blending is used.  Note that
            // MaxDualSourceDrawBuffers is never larger than 1.
            ASSERT(compiler->getBuiltInResources().MaxDualSourceDrawBuffers <= 1);
            initializedSymbol =
                new TIntermBinary(EOpIndexDirect, initializedSymbol, CreateIndexNode(0));
        }

        TIntermSequence initCode;
        CreateInitCode(initializedSymbol, canUseLoopsToInitialize, &initCode, symbolTable);
        mainBody->insert(mainBody->begin(), initCode.begin(), initCode.end());
    }
}

TFunction *CloneFunctionHeader(TSymbolTable *symbolTable, const TFunction *function)
{
    TFunction *newFunction =
        new TFunction(symbolTable, function->name(), function->symbolType(),
                      &function->getReturnType(), function->isKnownToNotHaveSideEffects());

    if (function->isDefined())
    {
        newFunction->setDefined();
    }
    if (function->hasPrototypeDeclaration())
    {
        newFunction->setHasPrototypeDeclaration();
    }
    return newFunction;
}

class InitializeLocalsTraverser final : public TIntermTraverser
{
  public:
    InitializeLocalsTraverser(int shaderVersion,
                              TSymbolTable *symbolTable,
                              bool canUseLoopsToInitialize)
        : TIntermTraverser(true, false, false, symbolTable),
          mShaderVersion(shaderVersion),
          mCanUseLoopsToInitialize(canUseLoopsToInitialize)
    {}

    void collectUnnamedOutFunctions(TIntermBlock &root)
    {
        TIntermSequence &sequence = *root.getSequence();
        const size_t count        = sequence.size();
        for (size_t i = 0; i < count; ++i)
        {
            const TIntermFunctionDefinition *functionDefinition =
                sequence[i]->getAsFunctionDefinition();
            if (!functionDefinition)
            {
                continue;
            }
            const TFunction *function = functionDefinition->getFunction();
            TFunction *newFunction    = nullptr;
            for (size_t p = 0; p < function->getParamCount(); ++p)
            {
                const TVariable *param = function->getParam(p);
                const TType &type      = param->getType();
                if (param->symbolType() == SymbolType::Empty)
                {
                    if (!newFunction)
                    {
                        newFunction                   = CloneFunctionHeader(mSymbolTable, function);
                        mFunctionsToReplace[function] = newFunction;
                        for (size_t z = 0; z < p; ++z)
                        {
                            newFunction->addParameter(function->getParam(z));
                        }
                    }
                    param = new TVariable(mSymbolTable, kEmptyImmutableString, &type,
                                          SymbolType::AngleInternal, param->extensions());
                }
                if (newFunction)
                {
                    newFunction->addParameter(param);
                }
            }
        }
    }

  protected:
    bool visitDeclaration(Visit visit, TIntermDeclaration *node) override
    {
        for (TIntermNode *declarator : *node->getSequence())
        {
            if (!mInGlobalScope && !declarator->getAsBinaryNode())
            {
                TIntermSymbol *symbol = declarator->getAsSymbolNode();
                ASSERT(symbol);
                if (symbol->variable().symbolType() == SymbolType::Empty)
                {
                    continue;
                }

                // Arrays may need to be initialized one element at a time, since ESSL 1.00 does not
                // support array constructors or assigning arrays.
                bool arrayConstructorUnavailable =
                    (symbol->isArray() || symbol->getType().isStructureContainingArrays()) &&
                    mShaderVersion == 100;
                // Nameless struct constructors can't be referred to, so they also need to be
                // initialized one element at a time.
                // TODO(oetuaho): Check if it makes sense to initialize using a loop, even if we
                // could use an initializer. It could at least reduce code size for very large
                // arrays, but could hurt runtime performance.
                if (arrayConstructorUnavailable || IsNamelessStruct(symbol->getType()))
                {
                    // SimplifyLoopConditions should have been run so the parent node of this node
                    // should not be a loop.
                    ASSERT(getParentNode()->getAsLoopNode() == nullptr);
                    // SeparateDeclarations should have already been run, so we don't need to worry
                    // about further declarators in this declaration depending on the effects of
                    // this declarator.
                    ASSERT(node->getSequence()->size() == 1);
                    TIntermSequence initCode;
                    CreateInitCode(symbol, mCanUseLoopsToInitialize, &initCode, mSymbolTable);
                    insertStatementsInParentBlock(TIntermSequence(), initCode);
                }
                else
                {
                    TIntermBinary *init =
                        new TIntermBinary(EOpInitialize, symbol, CreateZeroNode(symbol->getType()));
                    queueReplacementWithParent(node, symbol, init, OriginalNode::BECOMES_CHILD);
                }
            }
        }
        // Must recurse in the cases which had initializers, because the initializiers might
        // call the function that was rewritten.
        return true;
    }

    void visitFunctionPrototype(TIntermFunctionPrototype *node) override
    {
        if (getParentNode()->getAsFunctionDefinition() != nullptr)
        {
            return;
        }
        auto it = mFunctionsToReplace.find(node->getFunction());
        if (it != mFunctionsToReplace.end())
        {
            queueReplacement(new TIntermFunctionPrototype(it->second), OriginalNode::IS_DROPPED);
        }
    }

    bool visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node) override
    {
        // Initialize output function arguments as well, the parameter passed in at call time may be
        // clobbered if the function doesn't fully write to the argument.

        TIntermSequence initCode;

        const TFunction *function = node->getFunction();
        auto it                   = mFunctionsToReplace.find(function);
        if (it != mFunctionsToReplace.end())
        {
            function                                   = it->second;
            TIntermFunctionPrototype *newPrototypeNode = new TIntermFunctionPrototype(function);
            TIntermFunctionDefinition *newNode =
                new TIntermFunctionDefinition(newPrototypeNode, node->getBody());
            queueReplacement(newNode, OriginalNode::IS_DROPPED);
        }

        for (size_t paramIndex = 0; paramIndex < function->getParamCount(); ++paramIndex)
        {
            const TVariable *paramVariable = function->getParam(paramIndex);
            const TType &paramType         = paramVariable->getType();

            if (paramType.getQualifier() != EvqParamOut)
            {
                continue;
            }

            CreateInitCode(new TIntermSymbol(paramVariable), mCanUseLoopsToInitialize, &initCode,
                           mSymbolTable);
        }

        if (!initCode.empty())
        {
            TIntermSequence *body = node->getBody()->getSequence();
            body->insert(body->begin(), initCode.begin(), initCode.end());
        }

        return true;
    }

    bool visitAggregate(Visit visit, TIntermAggregate *node) override
    {
        const TFunction *function = node->getFunction();
        if (function != nullptr)
        {
            auto it = mFunctionsToReplace.find(function);
            if (it != mFunctionsToReplace.end())
            {
                const TFunction *target = it->second;
                TIntermAggregate *newNode =
                    TIntermAggregate::CreateFunctionCall(*target, node->getSequence());
                queueReplacement(newNode, OriginalNode::IS_DROPPED);
            }
        }
        return true;
    }

  private:
    int mShaderVersion;
    bool mCanUseLoopsToInitialize;
    angle::HashMap<const TFunction *, TFunction *> mFunctionsToReplace;
};

}  // namespace

void CreateInitCode(const TIntermTyped *initializedSymbol,
                    bool canUseLoopsToInitialize,
                    TIntermSequence *initCode,
                    TSymbolTable *symbolTable)
{
    AddZeroInitSequence(initializedSymbol, canUseLoopsToInitialize, initCode, symbolTable);
}

bool InitializeUninitializedLocals(TCompiler *compiler,
                                   TIntermBlock *root,
                                   int shaderVersion,
                                   bool canUseLoopsToInitialize,
                                   TSymbolTable *symbolTable)
{
    InitializeLocalsTraverser traverser(shaderVersion, symbolTable, canUseLoopsToInitialize);
    traverser.collectUnnamedOutFunctions(*root);
    root->traverse(&traverser);
    return traverser.updateTree(compiler, root);
}

bool InitializeVariables(TCompiler *compiler,
                         TIntermBlock *root,
                         const InitVariableList &vars,
                         TSymbolTable *symbolTable,
                         int shaderVersion,
                         const TExtensionBehavior &extensionBehavior,
                         bool canUseLoopsToInitialize)
{
    InsertInitCode(compiler, root, vars, symbolTable, shaderVersion, extensionBehavior,
                   canUseLoopsToInitialize);

    return compiler->validateAST(root);
}

}  // namespace sh
