//===- 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 "IceGlobalContext.h"
#include "IceTypes.h"

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
#pragma clang diagnostic ignored "-Wredundant-move"
#include "llvm/Bitcode/NaCl/NaClBitcodeParser.h" // for NaClBitcodeRecord.
#include "llvm/IR/CallingConv.h"
#include "llvm/IR/GlobalValue.h" // for GlobalValue::LinkageTypes.
#pragma clang diagnostic pop

#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; }
  const IceString &getName() const { return Name; }
  void setName(const IceString &NewName) { Name = NewName; }
  bool hasName() const { return !Name.empty(); }
  bool isInternal() const {
    return Linkage == llvm::GlobalValue::InternalLinkage;
  }
  llvm::GlobalValue::LinkageTypes getLinkage() const { return Linkage; }
  bool isExternal() const {
    return Linkage == llvm::GlobalValue::ExternalLinkage;
  }
  void setLinkage(llvm::GlobalValue::LinkageTypes NewLinkage) {
    Linkage = NewLinkage;
  }
  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(GlobalContext *Ctx, Ostream &Stream) const = 0;
  void dump(Ostream &Stream) const {
    if (!BuildDefs::dump())
      return;
    GlobalContext *const Ctx = nullptr;
    dump(Ctx, Stream);
  }

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

  /// Mangles name for cross tests, unless external and not defined locally (so
  /// that relocations across pnacl-sz and pnacl-llc will work).
  virtual IceString mangleName(GlobalContext *Ctx) const {
    return getSuppressMangling() ? Name : Ctx->mangleName(Name);
  }

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

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 GlobalContext *Ctx) const {
    switch (Linkage) {
    default:
      return false;
    case llvm::GlobalValue::InternalLinkage:
      return true;
    case llvm::GlobalValue::ExternalLinkage:
      return Ctx->getFlags().getAllowExternDefinedSymbols();
    }
  }

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

/// 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(GlobalContext *Ctx, 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 (isPNaClABIExternalName() || isIntrinsicName(Ctx))
      return Linkage == llvm::GlobalValue::ExternalLinkage;
    return verifyLinkageDefault(Ctx);
  }

private:
  const Ice::FuncSigType Signature;
  llvm::CallingConv::ID CallingConv;
  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 {
    const char *Name = getName().c_str();
    return strcmp(Name, "_start") == 0 || strcmp(Name, "__pnacl_pso_root") == 0;
  }

  bool isIntrinsicName(const GlobalContext *Ctx) const {
    if (!hasName())
      return false;
    bool BadIntrinsic;
    return Ctx->getIntrinsicsInfo().find(getName(), BadIntrinsic) &&
           !BadIntrinsic;
  }
};

/// 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 ~Initializer() = default;
    virtual SizeT getNumBytes() const = 0;
    virtual void dump(GlobalContext *Ctx, Ostream &Stream) const = 0;
    void dump(Ostream &Stream) const {
      if (BuildDefs::dump())
        dump(nullptr, Stream);
    }
    virtual void dumpType(Ostream &Stream) const;

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

  private:
    const InitializerKind Kind;
  };

  /// Models the data in a data initializer.
  using DataVecType = std::vector<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 std::unique_ptr<DataInitializer> create(Args &&... TheArgs) {
      return makeUnique<DataInitializer>(std::forward<Args>(TheArgs)...);
    }

    const DataVecType &getContents() const { return Contents; }
    SizeT getNumBytes() const final { return Contents.size(); }
    void dump(GlobalContext *Ctx, Ostream &Stream) const final;
    static bool classof(const Initializer *D) {
      return D->getKind() == DataInitializerKind;
    }

  private:
    ENABLE_MAKE_UNIQUE;

    DataInitializer(const llvm::NaClBitcodeRecord::RecordVector &Values)
        : Initializer(DataInitializerKind), Contents(Values.size()) {
      for (SizeT I = 0; I < Values.size(); ++I)
        Contents[I] = static_cast<int8_t>(Values[I]);
    }

    DataInitializer(const char *Str, size_t StrLen)
        : Initializer(DataInitializerKind), Contents(StrLen) {
      for (size_t i = 0; i < StrLen; ++i)
        Contents[i] = Str[i];
    }

    /// The byte contents of the data initializer.
    DataVecType Contents;
  };

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

  public:
    static std::unique_ptr<ZeroInitializer> create(SizeT Size) {
      return makeUnique<ZeroInitializer>(Size);
    }
    SizeT getNumBytes() const final { return Size; }
    void dump(GlobalContext *Ctx, Ostream &Stream) const final;
    static bool classof(const Initializer *Z) {
      return Z->getKind() == ZeroInitializerKind;
    }

  private:
    ENABLE_MAKE_UNIQUE;

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

    /// The number of bytes to be zero initialized.
    SizeT Size;
  };

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

  public:
    static std::unique_ptr<RelocInitializer>
    create(const GlobalDeclaration *Declaration, RelocOffsetT Offset) {
      return makeUnique<RelocInitializer>(Declaration, Offset);
    }

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

  private:
    ENABLE_MAKE_UNIQUE;

    RelocInitializer(const GlobalDeclaration *Declaration, RelocOffsetT Offset)
        : Initializer(RelocInitializerKind), Declaration(Declaration),
          Offset(Offset) {} // The global declaration used in the relocation.

    const GlobalDeclaration *Declaration;
    /// The offset to add to the relocation.
    const RelocOffsetT Offset;
  };

  /// Models the list of initializers.
  using InitializerListType = std::vector<std::unique_ptr<Initializer>>;

  static VariableDeclaration *create(GlobalContext *Context) {
    return new (Context->allocate<VariableDeclaration>()) VariableDeclaration();
  }

  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].get()));
  }

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

  /// Adds Initializer to the list of initializers. Takes ownership of the
  /// initializer.
  void addInitializer(std::unique_ptr<Initializer> Initializer) {
    Initializers->emplace_back(std::move(Initializer));
    HasInitializer = true;
  }

  /// 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).
  void dump(GlobalContext *Ctx, Ostream &Stream) const final;

  /// Returns true if linkage is correct for the variable declaration.
  bool verifyLinkageCorrect(const GlobalContext *Ctx) const {
    return verifyLinkageDefault(Ctx);
  }

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

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

  void setSuppressMangling() { ForceSuppressMangling = true; }

  void discardInitializers() { Initializers = nullptr; }

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

  VariableDeclaration()
      : GlobalDeclaration(VariableDeclarationKind,
                          llvm::GlobalValue::InternalLinkage),
        Initializers(new InitializerListType), HasInitializer(false),
        Alignment(0), IsConstant(false), ForceSuppressMangling(false) {}
};

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
