//===- subzero/src/IceGlobalInits.h - Global declarations -------*- C++ -*-===//
//
//                        The Subzero Code Generator
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief Declares the representation of function declarations, global variable
/// declarations, and the corresponding variable initializers in Subzero.
///
/// Global variable initializers are represented as a sequence of simple
/// initializers.
///
//===----------------------------------------------------------------------===//

#ifndef SUBZERO_SRC_ICEGLOBALINITS_H
#define SUBZERO_SRC_ICEGLOBALINITS_H

#include "IceDefs.h"
#include "IceFixups.h"
#include "IceGlobalContext.h"
#include "IceIntrinsics.h"
#include "IceMangling.h"
#include "IceOperand.h"
#include "IceTypes.h"

#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
#endif // __clang__

#include "llvm/Bitcode/NaCl/NaClBitcodeParser.h" // for NaClBitcodeRecord.
#include "llvm/IR/CallingConv.h"
#include "llvm/IR/GlobalValue.h" // for GlobalValue::LinkageTypes.

#ifdef __clang__
#pragma clang diagnostic pop
#endif // __clang__

#include <memory>
#include <utility>

// TODO(kschimpf): Remove ourselves from using LLVM representation for calling
// conventions and linkage types.

namespace Ice {

/// Base class for global variable and function declarations.
class GlobalDeclaration {
  GlobalDeclaration() = delete;
  GlobalDeclaration(const GlobalDeclaration &) = delete;
  GlobalDeclaration &operator=(const GlobalDeclaration &) = delete;

public:
  /// Discriminator for LLVM-style RTTI.
  enum GlobalDeclarationKind {
    FunctionDeclarationKind,
    VariableDeclarationKind
  };
  GlobalDeclarationKind getKind() const { return Kind; }
  GlobalString getName() const { return Name; }
  void setName(GlobalContext *Ctx, const std::string &NewName) {
    Name = Ctx->getGlobalString(getSuppressMangling() ? NewName
                                                      : mangleName(NewName));
  }
  void setName(GlobalString NewName) { Name = NewName; }
  void setName(GlobalContext *Ctx) {
    Name = GlobalString::createWithoutString(Ctx);
  }
  bool hasName() const { return Name.hasStdString(); }
  bool isInternal() const {
    return Linkage == llvm::GlobalValue::InternalLinkage;
  }
  llvm::GlobalValue::LinkageTypes getLinkage() const { return Linkage; }
  void setLinkage(llvm::GlobalValue::LinkageTypes L) {
    assert(!hasName());
    Linkage = L;
  }
  bool isExternal() const {
    return Linkage == llvm::GlobalValue::ExternalLinkage;
  }
  virtual ~GlobalDeclaration() = default;

  /// Prints out type of the global declaration.
  virtual void dumpType(Ostream &Stream) const = 0;

  /// Prints out the global declaration.
  virtual void dump(Ostream &Stream) const = 0;

  /// Returns true if when emitting names, we should suppress mangling.
  virtual bool getSuppressMangling() const = 0;

  /// Returns textual name of linkage.
  const char *getLinkageName() const {
    return isInternal() ? "internal" : "external";
  }

  /// Returns true if the name of this GlobalDeclaration indicates that it
  /// should have ExternalLinkage (as a special case).
  virtual bool isPNaClABIExternalName(const std::string &Name) const = 0;

protected:
  GlobalDeclaration(GlobalDeclarationKind Kind,
                    llvm::GlobalValue::LinkageTypes Linkage)
      : Kind(Kind), Linkage(Linkage) {}

  /// Returns true if linkage is defined correctly for the global declaration,
  /// based on default rules.
  bool verifyLinkageDefault() const {
    switch (Linkage) {
    default:
      return false;
    case llvm::GlobalValue::InternalLinkage:
      return true;
    case llvm::GlobalValue::ExternalLinkage:
      return getFlags().getAllowExternDefinedSymbols();
    }
  }

  const GlobalDeclarationKind Kind;
  llvm::GlobalValue::LinkageTypes Linkage;
  GlobalString Name;
};

/// Models a function declaration. This includes the type signature of the
/// function, its calling conventions, and its linkage.
class FunctionDeclaration : public GlobalDeclaration {
  FunctionDeclaration() = delete;
  FunctionDeclaration(const FunctionDeclaration &) = delete;
  FunctionDeclaration &operator=(const FunctionDeclaration &) = delete;

public:
  static FunctionDeclaration *create(GlobalContext *Context,
                                     const FuncSigType &Signature,
                                     llvm::CallingConv::ID CallingConv,
                                     llvm::GlobalValue::LinkageTypes Linkage,
                                     bool IsProto) {
    return new (Context->allocate<FunctionDeclaration>())
        FunctionDeclaration(Signature, CallingConv, Linkage, IsProto);
  }
  const FuncSigType &getSignature() const { return Signature; }
  llvm::CallingConv::ID getCallingConv() const { return CallingConv; }
  /// isProto implies that there isn't a (local) definition for the function.
  bool isProto() const { return IsProto; }
  static bool classof(const GlobalDeclaration *Addr) {
    return Addr->getKind() == FunctionDeclarationKind;
  }
  void dumpType(Ostream &Stream) const final;
  void dump(Ostream &Stream) const final;
  bool getSuppressMangling() const final { return isExternal() && IsProto; }

  /// Returns true if linkage is correct for the function declaration.
  bool verifyLinkageCorrect(const GlobalContext *Ctx) const {
    if (getName().hasStdString()) {
      if (isPNaClABIExternalName(getName().toString()) ||
          isIntrinsicName(Ctx)) {
        return Linkage == llvm::GlobalValue::ExternalLinkage;
      }
    }
    return verifyLinkageDefault();
  }

  /// Validates that the type signature of the function is correct. Returns true
  /// if valid.
  bool validateTypeSignature(const GlobalContext *Ctx) const {
    bool IsIntrinsic;
    if (const Intrinsics::FullIntrinsicInfo *Info =
            getIntrinsicInfo(Ctx, &IsIntrinsic))
      return validateIntrinsicTypeSignature(Info);
    return !IsIntrinsic && validateRegularTypeSignature();
  }

  /// Generates an error message describing why validateTypeSignature returns
  /// false.
  std::string getTypeSignatureError(const GlobalContext *Ctx);

  /// Returns corresponding PNaCl intrisic information.
  const Intrinsics::FullIntrinsicInfo *
  getIntrinsicInfo(const GlobalContext *Ctx) const {
    bool BadIntrinsic;
    return getIntrinsicInfo(Ctx, &BadIntrinsic);
  }

  /// Same as above, except IsIntrinsic is true if the function is intrinsic
  /// (even if not a PNaCl intrinsic).
  const Intrinsics::FullIntrinsicInfo *
  getIntrinsicInfo(const GlobalContext *Ctx, bool *IsIntrinsic) const;

private:
  const Ice::FuncSigType Signature;
  llvm::CallingConv::ID CallingConv;
  const bool IsProto;

  FunctionDeclaration(const FuncSigType &Signature,
                      llvm::CallingConv::ID CallingConv,
                      llvm::GlobalValue::LinkageTypes Linkage, bool IsProto)
      : GlobalDeclaration(FunctionDeclarationKind, Linkage),
        Signature(Signature), CallingConv(CallingConv), IsProto(IsProto) {}

  bool isPNaClABIExternalName(const std::string &Name) const override {
    return Name == "_start";
  }

  bool isIntrinsicName(const GlobalContext *Ctx) const {
    bool IsIntrinsic;
    getIntrinsicInfo(Ctx, &IsIntrinsic);
    return IsIntrinsic;
  }

  bool validateRegularTypeSignature() const;

  bool validateIntrinsicTypeSignature(
      const Intrinsics::FullIntrinsicInfo *Info) const;
};

/// Models a global variable declaration, and its initializers.
class VariableDeclaration : public GlobalDeclaration {
  VariableDeclaration(const VariableDeclaration &) = delete;
  VariableDeclaration &operator=(const VariableDeclaration &) = delete;

public:
  /// Base class for a global variable initializer.
  class Initializer {
    Initializer(const Initializer &) = delete;
    Initializer &operator=(const Initializer &) = delete;

  public:
    /// Discriminator for LLVM-style RTTI.
    enum InitializerKind {
      DataInitializerKind,
      ZeroInitializerKind,
      RelocInitializerKind
    };
    InitializerKind getKind() const { return Kind; }
    virtual SizeT getNumBytes() const = 0;
    virtual void dump(Ostream &Stream) const = 0;
    virtual void dumpType(Ostream &Stream) const;

  protected:
    explicit Initializer(InitializerKind Kind) : Kind(Kind) {}

  private:
    const InitializerKind Kind;
  };
  static_assert(std::is_trivially_destructible<Initializer>::value,
                "Initializer must be trivially destructible.");

  /// Models the data in a data initializer.
  using DataVecType = char *;

  /// Defines a sequence of byte values as a data initializer.
  class DataInitializer : public Initializer {
    DataInitializer(const DataInitializer &) = delete;
    DataInitializer &operator=(const DataInitializer &) = delete;

  public:
    template <class... Args>
    static DataInitializer *create(VariableDeclarationList *VDL,
                                   Args &&... TheArgs) {
      return new (VDL->allocate_initializer<DataInitializer>())
          DataInitializer(VDL, std::forward<Args>(TheArgs)...);
    }

    const llvm::StringRef getContents() const {
      return llvm::StringRef(Contents, ContentsSize);
    }
    SizeT getNumBytes() const final { return ContentsSize; }
    void dump(Ostream &Stream) const final;
    static bool classof(const Initializer *D) {
      return D->getKind() == DataInitializerKind;
    }

  private:
    DataInitializer(VariableDeclarationList *VDL,
                    const llvm::NaClBitcodeRecord::RecordVector &Values)
        : Initializer(DataInitializerKind), ContentsSize(Values.size()),
          // ugh, we should actually do new char[], but this may involve
          // implementation-specific details. Given that Contents is arena
          // allocated, and never delete[]d, just use char --
          // AllocOwner->allocate_array will allocate a buffer with the right
          // size.
          Contents(new (VDL->allocate_initializer<char>(ContentsSize)) char) {
      for (SizeT I = 0; I < Values.size(); ++I)
        Contents[I] = static_cast<int8_t>(Values[I]);
    }

    DataInitializer(VariableDeclarationList *VDL, const char *Str,
                    size_t StrLen)
        : Initializer(DataInitializerKind), ContentsSize(StrLen),
          Contents(new (VDL->allocate_initializer<char>(ContentsSize)) char) {
      for (size_t i = 0; i < StrLen; ++i)
        Contents[i] = Str[i];
    }

    /// The byte contents of the data initializer.
    const SizeT ContentsSize;
    DataVecType Contents;
  };
  static_assert(std::is_trivially_destructible<DataInitializer>::value,
                "DataInitializer must be trivially destructible.");

  /// Defines a sequence of bytes initialized to zero.
  class ZeroInitializer : public Initializer {
    ZeroInitializer(const ZeroInitializer &) = delete;
    ZeroInitializer &operator=(const ZeroInitializer &) = delete;

  public:
    static ZeroInitializer *create(VariableDeclarationList *VDL, SizeT Size) {
      return new (VDL->allocate_initializer<ZeroInitializer>())
          ZeroInitializer(Size);
    }
    SizeT getNumBytes() const final { return Size; }
    void dump(Ostream &Stream) const final;
    static bool classof(const Initializer *Z) {
      return Z->getKind() == ZeroInitializerKind;
    }

  private:
    explicit ZeroInitializer(SizeT Size)
        : Initializer(ZeroInitializerKind), Size(Size) {}

    /// The number of bytes to be zero initialized.
    SizeT Size;
  };
  static_assert(std::is_trivially_destructible<ZeroInitializer>::value,
                "ZeroInitializer must be trivially destructible.");

  /// Defines the relocation value of another global declaration.
  class RelocInitializer : public Initializer {
    RelocInitializer(const RelocInitializer &) = delete;
    RelocInitializer &operator=(const RelocInitializer &) = delete;

  public:
    static RelocInitializer *create(VariableDeclarationList *VDL,
                                    const GlobalDeclaration *Declaration,
                                    const RelocOffsetArray &OffsetExpr) {
      constexpr bool NoFixup = false;
      return new (VDL->allocate_initializer<RelocInitializer>())
          RelocInitializer(VDL, Declaration, OffsetExpr, NoFixup);
    }

    static RelocInitializer *create(VariableDeclarationList *VDL,
                                    const GlobalDeclaration *Declaration,
                                    const RelocOffsetArray &OffsetExpr,
                                    FixupKind Fixup) {
      constexpr bool HasFixup = true;
      return new (VDL->allocate_initializer<RelocInitializer>())
          RelocInitializer(VDL, Declaration, OffsetExpr, HasFixup, Fixup);
    }

    RelocOffsetT getOffset() const {
      RelocOffsetT Offset = 0;
      for (SizeT i = 0; i < OffsetExprSize; ++i) {
        Offset += OffsetExpr[i]->getOffset();
      }
      return Offset;
    }

    bool hasFixup() const { return HasFixup; }
    FixupKind getFixup() const {
      assert(HasFixup);
      return Fixup;
    }

    const GlobalDeclaration *getDeclaration() const { return Declaration; }
    SizeT getNumBytes() const final { return RelocAddrSize; }
    void dump(Ostream &Stream) const final;
    void dumpType(Ostream &Stream) const final;
    static bool classof(const Initializer *R) {
      return R->getKind() == RelocInitializerKind;
    }

  private:
    RelocInitializer(VariableDeclarationList *VDL,
                     const GlobalDeclaration *Declaration,
                     const RelocOffsetArray &OffsetExpr, bool HasFixup,
                     FixupKind Fixup = 0)
        : Initializer(RelocInitializerKind),
          Declaration(Declaration), // The global declaration used in the reloc.
          OffsetExprSize(OffsetExpr.size()),
          OffsetExpr(new (VDL->allocate_initializer<RelocOffset *>(
              OffsetExprSize)) RelocOffset *),
          HasFixup(HasFixup), Fixup(Fixup) {
      for (SizeT i = 0; i < OffsetExprSize; ++i) {
        this->OffsetExpr[i] = OffsetExpr[i];
      }
    }

    const GlobalDeclaration *Declaration;
    /// The offset to add to the relocation.
    const SizeT OffsetExprSize;
    RelocOffset **OffsetExpr;
    const bool HasFixup = false;
    const FixupKind Fixup = 0;
  };
  static_assert(std::is_trivially_destructible<RelocInitializer>::value,
                "RelocInitializer must be trivially destructible.");

  /// Models the list of initializers.
  // TODO(jpp): missing allocator.
  using InitializerListType = std::vector<Initializer *>;

  static VariableDeclaration *create(VariableDeclarationList *VDL,
                                     bool SuppressMangling = false,
                                     llvm::GlobalValue::LinkageTypes Linkage =
                                         llvm::GlobalValue::InternalLinkage) {
    return new (VDL->allocate_variable_declaration<VariableDeclaration>())
        VariableDeclaration(Linkage, SuppressMangling);
  }

  static VariableDeclaration *createExternal(VariableDeclarationList *VDL) {
    constexpr bool SuppressMangling = true;
    constexpr llvm::GlobalValue::LinkageTypes Linkage =
        llvm::GlobalValue::ExternalLinkage;
    return create(VDL, SuppressMangling, Linkage);
  }

  const InitializerListType &getInitializers() const { return Initializers; }
  bool getIsConstant() const { return IsConstant; }
  void setIsConstant(bool NewValue) { IsConstant = NewValue; }
  uint32_t getAlignment() const { return Alignment; }
  void setAlignment(uint32_t NewAlignment) { Alignment = NewAlignment; }
  bool hasInitializer() const { return HasInitializer; }
  bool hasNonzeroInitializer() const {
    return !(Initializers.size() == 1 &&
             llvm::isa<ZeroInitializer>(Initializers[0]));
  }

  /// Returns the number of bytes for the initializer of the global address.
  SizeT getNumBytes() const {
    SizeT Count = 0;
    for (const auto *Init : Initializers) {
      Count += Init->getNumBytes();
    }
    return Count;
  }

  /// Adds Initializer to the list of initializers. Takes ownership of the
  /// initializer.
  void addInitializer(Initializer *Initializer) {
    const bool OldSuppressMangling = getSuppressMangling();
    Initializers.emplace_back(Initializer);
    HasInitializer = true;
    // The getSuppressMangling() logic depends on whether the global variable
    // has initializers.  If its value changed as a result of adding an
    // initializer, then make sure we haven't previously set the name based on
    // faulty SuppressMangling logic.
    const bool SameMangling = (OldSuppressMangling == getSuppressMangling());
    (void)SameMangling;
    assert(Name.hasStdString() || SameMangling);
  }

  /// Prints out type for initializer associated with the declaration to Stream.
  void dumpType(Ostream &Stream) const final;

  /// Prints out the definition of the global variable declaration (including
  /// initialization).
  virtual void dump(Ostream &Stream) const override;

  /// Returns true if linkage is correct for the variable declaration.
  bool verifyLinkageCorrect() const {
    if (getName().hasStdString()) {
      if (isPNaClABIExternalName(getName().toString())) {
        return Linkage == llvm::GlobalValue::ExternalLinkage;
      }
    }
    return verifyLinkageDefault();
  }

  static bool classof(const GlobalDeclaration *Addr) {
    return Addr->getKind() == VariableDeclarationKind;
  }

  bool getSuppressMangling() const final {
    if (ForceSuppressMangling)
      return true;
    return isExternal() && !hasInitializer();
  }

  void discardInitializers() { Initializers.clear(); }

  bool isPNaClABIExternalName(const std::string &Name) const override {
    return Name == "__pnacl_pso_root";
  }

private:
  /// List of initializers for the declared variable.
  InitializerListType Initializers;
  bool HasInitializer = false;
  /// The alignment of the declared variable.
  uint32_t Alignment = 0;
  /// True if a declared (global) constant.
  bool IsConstant = false;
  /// If set to true, force getSuppressMangling() to return true.
  const bool ForceSuppressMangling;

  VariableDeclaration(llvm::GlobalValue::LinkageTypes Linkage,
                      bool SuppressMangling)
      : GlobalDeclaration(VariableDeclarationKind, Linkage),
        ForceSuppressMangling(SuppressMangling) {}
};

template <class StreamType>
inline StreamType &operator<<(StreamType &Stream,
                              const VariableDeclaration::Initializer &Init) {
  Init.dump(Stream);
  return Stream;
}

template <class StreamType>
inline StreamType &operator<<(StreamType &Stream,
                              const GlobalDeclaration &Addr) {
  Addr.dump(Stream);
  return Stream;
}

} // end of namespace Ice

#endif // SUBZERO_SRC_ICEGLOBALINITS_H