//
// Copyright 2019 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.
//
// RemoveInactiveInterfaceVariables.h:
//  Drop shader interface variable declarations for those that are inactive.
//

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

#include "compiler/translator/SymbolTable.h"
#include "compiler/translator/tree_util/IntermTraverse.h"
#include "compiler/translator/util.h"

namespace sh
{

namespace
{

// Traverser that removes all declarations that correspond to inactive variables.
class RemoveInactiveInterfaceVariablesTraverser : public TIntermTraverser
{
  public:
    RemoveInactiveInterfaceVariablesTraverser(
        TSymbolTable *symbolTable,
        const std::vector<sh::ShaderVariable> &attributes,
        const std::vector<sh::ShaderVariable> &inputVaryings,
        const std::vector<sh::ShaderVariable> &outputVariables,
        const std::vector<sh::ShaderVariable> &uniforms,
        const std::vector<sh::InterfaceBlock> &interfaceBlocks,
        bool removeFragmentOutputs);

    bool visitDeclaration(Visit visit, TIntermDeclaration *node) override;
    bool visitBinary(Visit visit, TIntermBinary *node) override;

  private:
    const std::vector<sh::ShaderVariable> &mAttributes;
    const std::vector<sh::ShaderVariable> &mInputVaryings;
    const std::vector<sh::ShaderVariable> &mOutputVariables;
    const std::vector<sh::ShaderVariable> &mUniforms;
    const std::vector<sh::InterfaceBlock> &mInterfaceBlocks;
    bool mRemoveFragmentOutputs;
};

RemoveInactiveInterfaceVariablesTraverser::RemoveInactiveInterfaceVariablesTraverser(
    TSymbolTable *symbolTable,
    const std::vector<sh::ShaderVariable> &attributes,
    const std::vector<sh::ShaderVariable> &inputVaryings,
    const std::vector<sh::ShaderVariable> &outputVariables,
    const std::vector<sh::ShaderVariable> &uniforms,
    const std::vector<sh::InterfaceBlock> &interfaceBlocks,
    bool removeFragmentOutputs)
    : TIntermTraverser(true, false, false, symbolTable),
      mAttributes(attributes),
      mInputVaryings(inputVaryings),
      mOutputVariables(outputVariables),
      mUniforms(uniforms),
      mInterfaceBlocks(interfaceBlocks),
      mRemoveFragmentOutputs(removeFragmentOutputs)
{}

template <typename Variable>
bool IsVariableActive(const std::vector<Variable> &mVars, const ImmutableString &name)
{
    for (const Variable &var : mVars)
    {
        if (name == var.name)
        {
            return var.active;
        }
    }
    ASSERT(false);
    return true;
}

bool IsIoBlockVariableActive(const std::vector<ShaderVariable> &mVars, const ImmutableString &name)
{
    for (const ShaderVariable &var : mVars)
    {
        if (name == var.structOrBlockName)
        {
            return var.active;
        }
    }
    ASSERT(false);
    return true;
}

bool RemoveInactiveInterfaceVariablesTraverser::visitDeclaration(Visit visit,
                                                                 TIntermDeclaration *node)
{
    // SeparateDeclarations should have already been run.
    ASSERT(node->getSequence()->size() == 1u);

    TIntermTyped *declarator = node->getSequence()->front()->getAsTyped();
    ASSERT(declarator);

    TIntermSymbol *asSymbol = declarator->getAsSymbolNode();
    if (!asSymbol)
    {
        return false;
    }

    const TType &type = declarator->getType();

    // Remove all shader interface variables except outputs, i.e. uniforms, interface blocks and
    // inputs.
    //
    // Imagine a situation where the VS doesn't write to a varying but the FS reads from it.  This
    // is allowed, though the value of the varying is undefined.  If the varying is removed here,
    // the situation is changed to VS not declaring the varying, but the FS reading from it, which
    // is not allowed.  That's why inactive shader outputs are not removed.
    //
    // Inactive fragment shader outputs can be removed though, as there is no next stage.
    bool removeDeclaration     = false;
    const TQualifier qualifier = type.getQualifier();

    if (type.isInterfaceBlock() && !IsShaderIoBlock(type.getQualifier()))
    {
        removeDeclaration = !IsVariableActive(mInterfaceBlocks, type.getInterfaceBlock()->name());
    }
    else if (qualifier == EvqUniform)
    {
        removeDeclaration = !IsVariableActive(mUniforms, asSymbol->getName());
    }
    else if (qualifier == EvqAttribute || qualifier == EvqVertexIn)
    {
        removeDeclaration = !IsVariableActive(mAttributes, asSymbol->getName());
    }
    else if (IsShaderIn(qualifier) && qualifier != EvqPerVertexIn && qualifier != EvqPerVertexOut)
    {
        // Match I/O blocks by the block name; the instance name may be empty.  Also note that
        // CollectVariables() does not collect gl_PerVertex at all if not active.  Those are not
        // removed for simplicity.
        if (type.getInterfaceBlock() != nullptr)
        {
            removeDeclaration =
                !IsIoBlockVariableActive(mInputVaryings, type.getInterfaceBlock()->name());
        }
        else
        {
            removeDeclaration = !IsVariableActive(mInputVaryings, asSymbol->getName());
        }
    }
    else if (qualifier == EvqFragmentOut || qualifier == EvqFragmentInOut)
    {
        removeDeclaration =
            !IsVariableActive(mOutputVariables, asSymbol->getName()) && mRemoveFragmentOutputs;
    }

    if (removeDeclaration)
    {
        TIntermSequence replacement;

        // If the declaration was of a struct, keep the struct declaration itself.
        if (type.isStructSpecifier())
        {
            TType *structSpecifierType      = new TType(type.getStruct(), true);
            TVariable *emptyVariable        = new TVariable(mSymbolTable, kEmptyImmutableString,
                                                            structSpecifierType, SymbolType::Empty);
            TIntermDeclaration *declaration = new TIntermDeclaration();
            declaration->appendDeclarator(new TIntermSymbol(emptyVariable));
            replacement.push_back(declaration);
        }

        mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node,
                                        std::move(replacement));
    }

    return false;
}

bool RemoveInactiveInterfaceVariablesTraverser::visitBinary(Visit visit, TIntermBinary *node)
{
    // Remove any code that initOutputVariables might have added corresponding to inactive
    // output variables.  This code is always in the form of `variable = ...;`.
    if (node->getOp() != EOpAssign)
    {
        // Don't recurse, won't find the initialization nested in another expression.
        return false;
    }

    // Get the symbol being initialized, and check if it's an inactive output.  If it is, this must
    // necessarily be initialization code that ANGLE has added (and wasn't there in the original
    // shader; if it was, the symbol wouldn't have been inactive).
    TIntermSymbol *symbol = node->getLeft()->getAsSymbolNode();
    if (symbol == nullptr)
    {
        return false;
    }

    const TQualifier qualifier = symbol->getType().getQualifier();
    if (qualifier != EvqFragmentOut || IsVariableActive(mOutputVariables, symbol->getName()))
    {
        return false;
    }

    // Drop the initialization code.
    TIntermSequence replacement;
    mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node, std::move(replacement));
    return false;
}

}  // namespace

bool RemoveInactiveInterfaceVariables(TCompiler *compiler,
                                      TIntermBlock *root,
                                      TSymbolTable *symbolTable,
                                      const std::vector<sh::ShaderVariable> &attributes,
                                      const std::vector<sh::ShaderVariable> &inputVaryings,
                                      const std::vector<sh::ShaderVariable> &outputVariables,
                                      const std::vector<sh::ShaderVariable> &uniforms,
                                      const std::vector<sh::InterfaceBlock> &interfaceBlocks,
                                      bool removeFragmentOutputs)
{
    RemoveInactiveInterfaceVariablesTraverser traverser(symbolTable, attributes, inputVaryings,
                                                        outputVariables, uniforms, interfaceBlocks,
                                                        removeFragmentOutputs);
    root->traverse(&traverser);
    return traverser.updateTree(compiler, root);
}

}  // namespace sh
