//
// Copyright 2017 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.
//
// Symbol.h: Symbols representing variables, functions, structures and interface blocks.
//

#ifndef COMPILER_TRANSLATOR_SYMBOL_H_
#define COMPILER_TRANSLATOR_SYMBOL_H_

#include "common/angleutils.h"
#include "compiler/translator/ExtensionBehavior.h"
#include "compiler/translator/ImmutableString.h"
#include "compiler/translator/IntermNode.h"
#include "compiler/translator/SymbolUniqueId.h"

namespace sh
{

class TSymbolTable;

// Symbol base class. (Can build functions or variables out of these...)
class TSymbol : angle::NonCopyable
{
  public:
    POOL_ALLOCATOR_NEW_DELETE
    TSymbol(TSymbolTable *symbolTable,
            const ImmutableString &name,
            SymbolType symbolType,
            SymbolClass symbolClass,
            TExtension extension = TExtension::UNDEFINED);

    TSymbol(TSymbolTable *symbolTable,
            const ImmutableString &name,
            SymbolType symbolType,
            SymbolClass symbolClass,
            const std::array<TExtension, 3u> &extensions);

    // Note that we can't have a virtual destructor in order to support constexpr symbols. Data is
    // either statically allocated or pool allocated.
    ~TSymbol() = default;

    // Calling name() for empty symbols (symbolType == SymbolType::Empty) generates a similar name
    // as for internal variables.
    ImmutableString name() const;
    // Don't call getMangledName() for empty symbols (symbolType == SymbolType::Empty).
    ImmutableString getMangledName() const;

    bool isFunction() const { return mSymbolClass == SymbolClass::Function; }
    bool isVariable() const { return mSymbolClass == SymbolClass::Variable; }
    bool isStruct() const { return mSymbolClass == SymbolClass::Struct; }
    bool isInterfaceBlock() const { return mSymbolClass == SymbolClass::InterfaceBlock; }

    const TSymbolUniqueId &uniqueId() const { return mUniqueId; }
    SymbolType symbolType() const { return mSymbolType; }
    const std::array<TExtension, 3u> extensions() const { return mExtensions; }

    template <size_t ExtensionCount>
    constexpr const std::array<TExtension, 3u> CreateExtensionList(
        const std::array<TExtension, ExtensionCount> &extensions)
    {
        switch (extensions.size())
        {
            case 1:
                return std::array<TExtension, 3u>{
                    {extensions[0], TExtension::UNDEFINED, TExtension::UNDEFINED}};
            case 2:
                return std::array<TExtension, 3u>{
                    {extensions[0], extensions[1], TExtension::UNDEFINED}};
            case 3:
                return std::array<TExtension, 3u>{{extensions[0], extensions[1], extensions[2]}};
            default:
                UNREACHABLE();
                return std::array<TExtension, 3u>{
                    {TExtension::UNDEFINED, TExtension::UNDEFINED, TExtension::UNDEFINED}};
        }
    }

  protected:
    template <size_t ExtensionCount>
    constexpr TSymbol(const TSymbolUniqueId &id,
                      const ImmutableString &name,
                      SymbolType symbolType,
                      const std::array<TExtension, ExtensionCount> &extensions,
                      SymbolClass symbolClass)
        : mName(name),
          mUniqueId(id),
          mExtensions(CreateExtensionList(extensions)),
          mSymbolType(symbolType),
          mSymbolClass(symbolClass)
    {}

    const ImmutableString mName;

  private:
    const TSymbolUniqueId mUniqueId;
    const std::array<TExtension, 3u> mExtensions;
    const SymbolType mSymbolType : 4;

    // We use this instead of having virtual functions for querying the class in order to support
    // constexpr symbols.
    const SymbolClass mSymbolClass : 4;
};

static_assert(sizeof(TSymbol) <= 24, "Size check failed");

// Variable.
// May store the value of a constant variable of any type (float, int, bool or struct).
class TVariable : public TSymbol
{
  public:
    TVariable(TSymbolTable *symbolTable,
              const ImmutableString &name,
              const TType *type,
              SymbolType symbolType,
              TExtension ext = TExtension::UNDEFINED);

    TVariable(TSymbolTable *symbolTable,
              const ImmutableString &name,
              const TType *type,
              SymbolType symbolType,
              const std::array<TExtension, 3u> &extensions);

    const TType &getType() const { return *mType; }

    const TConstantUnion *getConstPointer() const { return unionArray; }

    void shareConstPointer(const TConstantUnion *constArray) { unionArray = constArray; }

    // Note: only to be used for built-in variables with autogenerated ids!
    constexpr TVariable(const TSymbolUniqueId &id,
                        const ImmutableString &name,
                        SymbolType symbolType,
                        TExtension extension,
                        const TType *type)
        : TSymbol(id,
                  name,
                  symbolType,
                  std::array<TExtension, 1u>{{extension}},
                  SymbolClass::Variable),
          mType(type),
          unionArray(nullptr)
    {}

    template <size_t ExtensionCount>
    constexpr TVariable(const TSymbolUniqueId &id,
                        const ImmutableString &name,
                        SymbolType symbolType,
                        const std::array<TExtension, ExtensionCount> &extensions,
                        const TType *type)
        : TSymbol(id, name, symbolType, extensions, SymbolClass::Variable),
          mType(type),
          unionArray(nullptr)
    {}

  private:
    const TType *mType;
    const TConstantUnion *unionArray;
};

// Struct type.
class TStructure : public TSymbol, public TFieldListCollection
{
  public:
    TStructure(TSymbolTable *symbolTable,
               const ImmutableString &name,
               const TFieldList *fields,
               SymbolType symbolType);

    // The char arrays passed in must be pool allocated or static.
    void createSamplerSymbols(const char *namePrefix,
                              const TString &apiNamePrefix,
                              TVector<const TVariable *> *outputSymbols,
                              TMap<const TVariable *, TString> *outputSymbolsToAPINames,
                              TSymbolTable *symbolTable) const;

    void setAtGlobalScope(bool atGlobalScope) { mAtGlobalScope = atGlobalScope; }
    bool atGlobalScope() const { return mAtGlobalScope; }

  private:
    friend class TSymbolTable;
    // For creating built-in structs.
    TStructure(const TSymbolUniqueId &id,
               const ImmutableString &name,
               TExtension extension,
               const TFieldList *fields)
        : TSymbol(id,
                  name,
                  SymbolType::BuiltIn,
                  std::array<TExtension, 1u>{{extension}},
                  SymbolClass::Struct),
          TFieldListCollection(fields)
    {}

    template <size_t ExtensionCount>
    TStructure(const TSymbolUniqueId &id,
               const ImmutableString &name,
               const std::array<TExtension, ExtensionCount> &extensions,
               const TFieldList *fields)
        : TSymbol(id, name, SymbolType::BuiltIn, extensions, SymbolClass::Struct),
          TFieldListCollection(fields)
    {}

    // TODO(zmo): Find a way to get rid of the const_cast in function
    // setName().  At the moment keep this function private so only
    // friend class RegenerateStructNames may call it.
    friend class RegenerateStructNamesTraverser;
    void setName(const ImmutableString &name);

    bool mAtGlobalScope;
};

// Interface block. Note that this contains the block name, not the instance name. Interface block
// instances are stored as TVariable.
class TInterfaceBlock : public TSymbol, public TFieldListCollection
{
  public:
    TInterfaceBlock(TSymbolTable *symbolTable,
                    const ImmutableString &name,
                    const TFieldList *fields,
                    const TLayoutQualifier &layoutQualifier,
                    SymbolType symbolType,
                    TExtension extension = TExtension::UNDEFINED);

    TInterfaceBlock(TSymbolTable *symbolTable,
                    const ImmutableString &name,
                    const TFieldList *fields,
                    const TLayoutQualifier &layoutQualifier,
                    SymbolType symbolType,
                    const std::array<TExtension, 3u> &extensions);

    TLayoutBlockStorage blockStorage() const { return mBlockStorage; }
    int blockBinding() const { return mBinding; }

  private:
    friend class TSymbolTable;
    // For creating built-in interface blocks.
    TInterfaceBlock(const TSymbolUniqueId &id,
                    const ImmutableString &name,
                    TExtension extension,
                    const TFieldList *fields)
        : TSymbol(id,
                  name,
                  SymbolType::BuiltIn,
                  std::array<TExtension, 1u>{{extension}},
                  SymbolClass::InterfaceBlock),
          TFieldListCollection(fields),
          mBlockStorage(EbsUnspecified),
          mBinding(0)
    {}

    template <size_t ExtensionCount>
    TInterfaceBlock(const TSymbolUniqueId &id,
                    const ImmutableString &name,
                    const std::array<TExtension, ExtensionCount> &extensions,
                    const TFieldList *fields)
        : TSymbol(id, name, SymbolType::BuiltIn, extensions, SymbolClass::InterfaceBlock),
          TFieldListCollection(fields),
          mBlockStorage(EbsUnspecified),
          mBinding(0)
    {}

    TLayoutBlockStorage mBlockStorage;
    int mBinding;

    // Note that we only record matrix packing on a per-field granularity.
};

// Parameter class used for parsing user-defined function parameters.
struct TParameter
{
    // Destructively converts to TVariable.
    // This method resets name and type to nullptrs to make sure
    // their content cannot be modified after the call.
    const TVariable *createVariable(TSymbolTable *symbolTable)
    {
        const ImmutableString constName(name);
        const TType *constType = type;
        name                   = nullptr;
        type                   = nullptr;
        return new TVariable(symbolTable, constName, constType,
                             constName.empty() ? SymbolType::Empty : SymbolType::UserDefined);
    }

    const char *name;  // either pool allocated or static.
    TType *type;
};

// The function sub-class of a symbol.
class TFunction : public TSymbol
{
  public:
    // User-defined function
    TFunction(TSymbolTable *symbolTable,
              const ImmutableString &name,
              SymbolType symbolType,
              const TType *retType,
              bool knownToNotHaveSideEffects);

    void addParameter(const TVariable *p);
    void shareParameters(const TFunction &parametersSource);

    ImmutableString getFunctionMangledName() const
    {
        ASSERT(symbolType() != SymbolType::BuiltIn);
        if (mMangledName.empty())
        {
            mMangledName = buildMangledName();
        }
        return mMangledName;
    }

    const TType &getReturnType() const { return *returnType; }

    TOperator getBuiltInOp() const { return mOp; }

    void setDefined() { defined = true; }
    bool isDefined() const { return defined; }
    void setHasPrototypeDeclaration() { mHasPrototypeDeclaration = true; }
    bool hasPrototypeDeclaration() const { return mHasPrototypeDeclaration; }
    void setHasVoidParameter() { mHasVoidParameter = true; }
    bool hasVoidParameter() const { return mHasVoidParameter; }

    size_t getParamCount() const { return mParamCount; }
    const TVariable *getParam(size_t i) const { return mParameters[i]; }

    bool isKnownToNotHaveSideEffects() const { return mKnownToNotHaveSideEffects; }

    bool isMain() const;
    bool isImageFunction() const;
    bool isAtomicCounterFunction() const;

    // Note: Only to be used for static built-in functions!
    constexpr TFunction(const TSymbolUniqueId &id,
                        const ImmutableString &name,
                        TExtension extension,
                        const TVariable *const *parameters,
                        size_t paramCount,
                        const TType *retType,
                        TOperator op,
                        bool knownToNotHaveSideEffects)
        : TSymbol(id,
                  name,
                  SymbolType::BuiltIn,
                  std::array<TExtension, 1u>{{extension}},
                  SymbolClass::Function),
          mParametersVector(nullptr),
          mParameters(parameters),
          returnType(retType),
          mMangledName(nullptr),
          mParamCount(paramCount),
          mOp(op),
          defined(false),
          mHasPrototypeDeclaration(false),
          mKnownToNotHaveSideEffects(knownToNotHaveSideEffects),
          mHasVoidParameter(false)
    {}

    template <size_t ExtensionCount>
    constexpr TFunction(const TSymbolUniqueId &id,
                        const ImmutableString &name,
                        const std::array<TExtension, ExtensionCount> &extensions,
                        const TVariable *const *parameters,
                        size_t paramCount,
                        const TType *retType,
                        TOperator op,
                        bool knownToNotHaveSideEffects)
        : TSymbol(id, name, SymbolType::BuiltIn, extensions, SymbolClass::Function),
          mParametersVector(nullptr),
          mParameters(parameters),
          returnType(retType),
          mMangledName(nullptr),
          mParamCount(paramCount),
          mOp(op),
          defined(false),
          mHasPrototypeDeclaration(false),
          mKnownToNotHaveSideEffects(knownToNotHaveSideEffects),
          mHasVoidParameter(false)
    {}

  private:
    ImmutableString buildMangledName() const;

    typedef TVector<const TVariable *> TParamVector;
    TParamVector *mParametersVector;
    const TVariable *const *mParameters;
    const TType *const returnType;
    mutable ImmutableString mMangledName;
    size_t mParamCount : 32;
    const TOperator mOp;  // Only set for built-ins
    bool defined : 1;
    bool mHasPrototypeDeclaration : 1;
    bool mKnownToNotHaveSideEffects : 1;
    // Whether the parameter list of the function starts with void.  This is used to generate an
    // error if any other parameter follows.
    bool mHasVoidParameter : 1;
};

}  // namespace sh

#endif  // COMPILER_TRANSLATOR_SYMBOL_H_
