//===- lib/ReaderWriter/ELF/SegmentChunks.h -------------------------------===//
//
//                             The LLVM Linker
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#ifndef LLD_READER_WRITER_ELF_SEGMENT_CHUNKS_H
#define LLD_READER_WRITER_ELF_SEGMENT_CHUNKS_H

#include "Chunk.h"
#include "Layout.h"
#include "SectionChunks.h"
#include "Writer.h"

#include "lld/Core/range.h"
#include "lld/ReaderWriter/Writer.h"

#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Object/ELF.h"
#include "llvm/Support/Allocator.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/ELF.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FileOutputBuffer.h"
#include <memory>

namespace lld {
namespace elf {

template <typename ELFT> class DefaultLayout;
template <typename ELFT> class ScriptLayout;

/// \brief A segment can be divided into segment slices
///        depending on how the segments can be split
template<class ELFT>
class SegmentSlice {
public:
  typedef typename std::vector<Chunk<ELFT> *>::iterator SectionIter;

  SegmentSlice() { }

  /// Set the segment slice so that it begins at the offset specified
  /// by file offset and set the start of the slice to be s and the end
  /// of the slice to be e
  void set(uint64_t fileoffset, int32_t s, int e) {
    _startSection = s;
    _endSection = e + 1;
    _offset = fileoffset;
  }

  // Set the segment slice start and end iterators. This is used to walk through
  // the sections that are part of the Segment slice
  inline void setSections(range<SectionIter> sections) {
    _sections = sections;
  }

  // Return the fileOffset of the slice
  inline uint64_t fileOffset() const { return _offset; }

  // Return the size of the slice
  inline uint64_t fileSize() const { return _size; }

  // Return the start of the slice
  inline int32_t startSection() const { return _startSection; }

  // Return the start address of the slice
  inline uint64_t virtualAddr() const { return _addr; }

  // Return the memory size of the slice
  inline uint64_t memSize() const { return _memSize; }

  // Return the alignment of the slice
  inline uint64_t align2() const { return _align2; }

  inline void setSize(uint64_t sz) { _size = sz; }

  inline void setMemSize(uint64_t memsz) { _memSize = memsz; }

  inline void setVAddr(uint64_t addr) { _addr = addr; }

  inline void setAlign(uint64_t align) { _align2 = align; }

  static bool compare_slices(SegmentSlice<ELFT> *a, SegmentSlice<ELFT> *b) {
    return a->startSection() < b->startSection();
  }

  inline range<SectionIter> sections() {
    return _sections;
  }

private:
  int32_t _startSection;
  int32_t _endSection;
  range<SectionIter> _sections;
  uint64_t _addr;
  uint64_t _offset;
  uint64_t _size;
  uint64_t _align2;
  uint64_t _memSize;
};

/// \brief A segment contains a set of sections, that have similar properties
//  the sections are already separated based on different flags and properties
//  the segment is just a way to concatenate sections to segments
template<class ELFT>
class Segment : public Chunk<ELFT> {
public:
  typedef typename std::vector<SegmentSlice<ELFT> *>::iterator SliceIter;
  typedef typename std::vector<Chunk<ELFT> *>::iterator SectionIter;

  Segment(const ELFLinkingContext &context, StringRef name,
          const Layout::SegmentType type);

  /// \brief the Order of segments that appear in the output file
  enum SegmentOrder {
    permUnknown,
    permRWX,
    permRX,
    permR,
    permRWL,
    permRW,
    permNonAccess
  };

  /// append a section to a segment
  virtual void append(Section<ELFT> *chunk);

  /// append a chunk to a segment, this function
  /// is used by the ProgramHeader segment
  virtual void append(Chunk<ELFT> *chunk) {}

  /// Sort segments depending on the property
  /// If we have a Program Header segment, it should appear first
  /// If we have a INTERP segment, that should appear after the Program Header
  /// All Loadable segments appear next in this order
  /// All Read Write Execute segments follow
  /// All Read Execute segments appear next
  /// All Read only segments appear first
  /// All Write execute segments follow
  static bool compareSegments(Segment<ELFT> *sega, Segment<ELFT> *segb);

  /// \brief Start assigning file offset to the segment chunks The fileoffset
  /// needs to be page at the start of the segment and in addition the
  /// fileoffset needs to be aligned to the max section alignment within the
  /// segment. This is required so that the ELF property p_poffset % p_align =
  /// p_vaddr mod p_align holds true.
  /// The algorithm starts off by assigning the startOffset thats passed in as
  /// parameter to the first section in the segment, if the difference between
  /// the newly computed offset is greater than a page, then we create a segment
  /// slice, as it would be a waste of virtual memory just to be filled with
  /// zeroes
  void assignOffsets(uint64_t startOffset);

  /// \brief Assign virtual addresses to the slices
  void assignVirtualAddress(uint64_t &addr);

  // Write the Segment
  void write(ELFWriter *writer, TargetLayout<ELFT> &layout,
             llvm::FileOutputBuffer &buffer);

  int64_t flags() const;

  /// Prepend a generic chunk to the segment.
  void prepend(Chunk<ELFT> *c) {
    _sections.insert(_sections.begin(), c);
  }

  /// Finalize the segment before assigning File Offsets / Virtual addresses
  inline void doPreFlight() {}

  /// Finalize the segment, before we want to write the segment header
  /// information
  inline void finalize() {
    // We want to finalize the segment values for now only for non loadable
    // segments, since those values are not set in the Layout
    if (_segmentType == llvm::ELF::PT_LOAD)
      return;
    // The size is the difference of the
    // last section to the first section, especially for TLS because
    // the TLS segment contains both .tdata/.tbss
    this->setFileOffset(_sections.front()->fileOffset());
    this->setVAddr(_sections.front()->virtualAddr());
    size_t startFileOffset = _sections.front()->fileOffset();
    size_t startAddr = _sections.front()->virtualAddr();
    for (auto ai : _sections) {
      this->_fsize = ai->fileOffset() + ai->fileSize() - startFileOffset;
      this->_msize = ai->virtualAddr() + ai->memSize() - startAddr;
    }
  }

  // For LLVM RTTI
  static inline bool classof(const Chunk<ELFT> *c) {
    return c->kind() == Chunk<ELFT>::Kind::ELFSegment;
  }

  // Getters
  inline int32_t sectionCount() const {
    return _sections.size();
  }

  /// \brief, this function returns the type of segment (PT_*)
  inline Layout::SegmentType segmentType() { return _segmentType; }

  /// \brief return the segment type depending on the content,
  /// If the content corresponds to Code, this will return Segment::Code
  /// If the content corresponds to Data, this will return Segment::Data
  /// If the content corresponds to TLS, this will return Segment::TLS
  virtual int getContentType() const {
    int64_t fl = flags();
    switch (_segmentType) {
    case llvm::ELF::PT_LOAD: {
      if (fl && llvm::ELF::PF_X)
        return Chunk<ELFT>::ContentType::Code;
      if (fl && llvm::ELF::PF_W)
        return Chunk<ELFT>::ContentType::Data;
    }
    case llvm::ELF::PT_TLS:
      return Chunk<ELFT>::ContentType::TLS;
    case llvm::ELF::PT_NOTE:
      return Chunk<ELFT>::ContentType::Note;
    default:
      return Chunk<ELFT>::ContentType::Unknown;
    }
  }

  inline int pageSize() const { return this->_context.getPageSize(); }

  inline int rawflags() const { return _atomflags; }

  inline int64_t atomflags() const {
    switch (_atomflags) {

    case DefinedAtom::permUnknown:
      return permUnknown;

    case DefinedAtom::permRWX:
      return permRWX;

    case DefinedAtom::permR_X:
      return permRX;

    case DefinedAtom::permR__:
      return permR;

    case DefinedAtom::permRW_L:
      return permRWL;

    case DefinedAtom::permRW_:
      return permRW;

    case DefinedAtom::perm___:
    default:
      return permNonAccess;
    }
  }

  inline int64_t numSlices() const { return _segmentSlices.size(); }

  inline range<SliceIter> slices() { return _segmentSlices; }

  // These two accessors are still needed for a call to std::stable_sort.
  // Consider adding wrappers for two iterator algorithms.
  inline SliceIter slices_begin() {
    return _segmentSlices.begin();
  }

  inline SliceIter slices_end() {
    return _segmentSlices.end();
  }

  Chunk<ELFT> *firstSection() { return _sections[0]; }

private:

  /// \brief Check if the chunk needs to be aligned
  bool needAlign(Chunk<ELFT> *chunk) const {
    if (chunk->getContentType() == Chunk<ELFT>::ContentType::Data &&
        _outputMagic == ELFLinkingContext::OutputMagic::NMAGIC)
      return true;
    return false;
  }

  // Cached value of outputMagic
  ELFLinkingContext::OutputMagic _outputMagic;

protected:
  /// \brief Section or some other chunk type.
  std::vector<Chunk<ELFT> *> _sections;
  std::vector<SegmentSlice<ELFT> *> _segmentSlices;
  Layout::SegmentType _segmentType;
  uint64_t _flags;
  int64_t _atomflags;
  llvm::BumpPtrAllocator _segmentAllocate;
};

/// \brief A Program Header segment contains a set of chunks instead of sections
/// The segment doesn't contain any slice
template <class ELFT> class ProgramHeaderSegment : public Segment<ELFT> {
public:
  ProgramHeaderSegment(const ELFLinkingContext &context)
      : Segment<ELFT>(context, "PHDR", llvm::ELF::PT_PHDR) {
    this->_align2 = 8;
    this->_flags = (llvm::ELF::SHF_ALLOC | llvm::ELF::SHF_EXECINSTR);
  }

  /// append a section to a segment
  void append(Chunk<ELFT> *chunk) { _sections.push_back(chunk); }

  /// Finalize the segment, before we want to write the segment header
  /// information
  inline void finalize() {
    // If the segment is of type Program Header, then the values fileOffset
    // and the fileSize need to be picked up from the last section, the first
    // section points to the ELF header and the second chunk points to the
    // actual program headers
    this->setFileOffset(_sections.back()->fileOffset());
    this->setVAddr(_sections.back()->virtualAddr());
    this->_fsize = _sections.back()->fileSize();
    this->_msize = _sections.back()->memSize();
  }

protected:
  /// \brief Section or some other chunk type.
  std::vector<Chunk<ELFT> *> _sections;
};

template <class ELFT>
Segment<ELFT>::Segment(const ELFLinkingContext &context, StringRef name,
                       const Layout::SegmentType type)
    : Chunk<ELFT>(name, Chunk<ELFT>::Kind::ELFSegment, context),
      _segmentType(type), _flags(0), _atomflags(0) {
  this->_align2 = 0;
  this->_fsize = 0;
  _outputMagic = context.getOutputMagic();
}

// This function actually is used, but not in all instantiations of Segment.
LLVM_ATTRIBUTE_UNUSED
static DefinedAtom::ContentPermissions toAtomPerms(uint64_t flags) {
  switch (flags & (SHF_ALLOC | SHF_WRITE | SHF_EXECINSTR)) {
  case SHF_ALLOC | SHF_WRITE | SHF_EXECINSTR:
    return DefinedAtom::permRWX;
  case SHF_ALLOC | SHF_EXECINSTR:
    return DefinedAtom::permR_X;
  case SHF_ALLOC:
    return DefinedAtom::permR__;
  case SHF_ALLOC | SHF_WRITE:
    return DefinedAtom::permRW_;
  default:
    return DefinedAtom::permUnknown;
  }
}

template <class ELFT> void Segment<ELFT>::append(Section<ELFT> *section) {
  _sections.push_back(section);
  if (_flags < section->getFlags())
    _flags |= section->getFlags();
  if (_atomflags < toAtomPerms(_flags))
    _atomflags = toAtomPerms(_flags);
  if (this->_align2 < section->align2())
    this->_align2 = section->align2();
}

template <class ELFT>
bool Segment<ELFT>::compareSegments(Segment<ELFT> *sega, Segment<ELFT> *segb) {
  int64_t type1 = sega->segmentType();
  int64_t type2 = segb->segmentType();

  // The single PT_PHDR segment is required to precede any loadable
  // segment.  We simply make it always first.
  if (type1 == llvm::ELF::PT_PHDR)
    return true;
  if (type2 == llvm::ELF::PT_PHDR)
    return false;

  // The single PT_INTERP segment is required to precede any loadable
  // segment.  We simply make it always second.
  if (type1 == llvm::ELF::PT_INTERP)
    return true;
  if (type2 == llvm::ELF::PT_INTERP)
    return false;

  // We then put PT_LOAD segments before any other segments.
  if (type1 == llvm::ELF::PT_LOAD && type2 != llvm::ELF::PT_LOAD)
    return true;
  if (type2 == llvm::ELF::PT_LOAD && type1 != llvm::ELF::PT_LOAD)
    return false;

  // We put the PT_TLS segment last except for the PT_GNU_RELRO
  // segment, because that is where the dynamic linker expects to find
  if (type1 == llvm::ELF::PT_TLS && type2 != llvm::ELF::PT_TLS &&
      type2 != llvm::ELF::PT_GNU_RELRO)
    return false;
  if (type2 == llvm::ELF::PT_TLS && type1 != llvm::ELF::PT_TLS &&
      type1 != llvm::ELF::PT_GNU_RELRO)
    return true;

  // We put the PT_GNU_RELRO segment last, because that is where the
  // dynamic linker expects to find it
  if (type1 == llvm::ELF::PT_GNU_RELRO && type2 != llvm::ELF::PT_GNU_RELRO)
    return false;
  if (type2 == llvm::ELF::PT_GNU_RELRO && type1 != llvm::ELF::PT_GNU_RELRO)
    return true;

  if (type1 == type2)
    return sega->atomflags() < segb->atomflags();
  return false;
}

template <class ELFT> void Segment<ELFT>::assignOffsets(uint64_t startOffset) {
  int startSection = 0;
  int currSection = 0;
  SectionIter startSectionIter, endSectionIter;
  // slice align is set to the max alignment of the chunks that are
  // contained in the slice
  uint64_t sliceAlign = 0;
  // Current slice size
  uint64_t curSliceSize = 0;
  // Current Slice File Offset
  uint64_t curSliceFileOffset = 0;

  startSectionIter = _sections.begin();
  endSectionIter = _sections.end();
  startSection = 0;
  bool isFirstSection = true;
  bool isDataPageAlignedForNMagic = false;
  for (auto si = _sections.begin(); si != _sections.end(); ++si) {
    if (isFirstSection) {
      // If the linker outputmagic is set to OutputMagic::NMAGIC, align the Data
      // to a page boundary
      if (!isDataPageAlignedForNMagic && needAlign(*si)) {
        startOffset =
            llvm::RoundUpToAlignment(startOffset, this->_context.getPageSize());
        isDataPageAlignedForNMagic = true;
      }
      // align the startOffset to the section alignment
      uint64_t newOffset =
        llvm::RoundUpToAlignment(startOffset, (*si)->align2());
      curSliceFileOffset = newOffset;
      sliceAlign = (*si)->align2();
      this->setFileOffset(startOffset);
      (*si)->setFileOffset(newOffset);
      curSliceSize = (*si)->fileSize();
      isFirstSection = false;
    } else {
      uint64_t curOffset = curSliceFileOffset + curSliceSize;
      // If the linker outputmagic is set to OutputMagic::NMAGIC, align the Data
      // to a page boundary
      if (!isDataPageAlignedForNMagic && needAlign(*si)) {
        curOffset =
            llvm::RoundUpToAlignment(curOffset, this->_context.getPageSize());
        isDataPageAlignedForNMagic = true;
      }
      uint64_t newOffset = llvm::RoundUpToAlignment(curOffset, (*si)->align2());
      SegmentSlice<ELFT> *slice = nullptr;
      // If the newOffset computed is more than a page away, let's create
      // a separate segment, so that memory is not used up while running
      if (((newOffset - curOffset) > this->_context.getPageSize()) &&
          (_outputMagic != ELFLinkingContext::OutputMagic::NMAGIC &&
           _outputMagic != ELFLinkingContext::OutputMagic::OMAGIC)) {

        // TODO: use std::find here
        for (auto s : slices()) {
          if (s->startSection() == startSection) {
            slice = s;
            break;
          }
        }
        if (!slice) {
          slice = new (_segmentAllocate.Allocate<SegmentSlice<ELFT>>())
            SegmentSlice<ELFT>();
          _segmentSlices.push_back(slice);
        }
        slice->set(curSliceFileOffset, startSection, currSection);
        slice->setSections(make_range(startSectionIter, endSectionIter));
        slice->setSize(curSliceSize);
        slice->setAlign(sliceAlign);
        uint64_t newPageOffset =
            llvm::RoundUpToAlignment(curOffset, this->_context.getPageSize());
        newOffset = llvm::RoundUpToAlignment(newPageOffset, (*si)->align2());
        curSliceFileOffset = newOffset;
        startSectionIter = endSectionIter;
        startSection = currSection;
        (*si)->setFileOffset(curSliceFileOffset);
        curSliceSize = newOffset - curSliceFileOffset + (*si)->fileSize();
        sliceAlign = (*si)->align2();
      } else {
        if (sliceAlign < (*si)->align2())
          sliceAlign = (*si)->align2();
        (*si)->setFileOffset(newOffset);
        curSliceSize = newOffset - curSliceFileOffset + (*si)->fileSize();
      }
    }
    currSection++;
    endSectionIter = si;
  }
  SegmentSlice<ELFT> *slice = nullptr;
  for (auto s : slices()) {
    // TODO: add std::find
    if (s->startSection() == startSection) {
      slice = s;
      break;
    }
  }
  if (!slice) {
    slice = new (_segmentAllocate.Allocate<SegmentSlice<ELFT>>())
      SegmentSlice<ELFT>();
    _segmentSlices.push_back(slice);
  }
  slice->set(curSliceFileOffset, startSection, currSection);
  slice->setSections(make_range(startSectionIter, _sections.end()));
  slice->setSize(curSliceSize);
  slice->setAlign(sliceAlign);
  this->_fsize = curSliceFileOffset - startOffset + curSliceSize;
  std::stable_sort(slices_begin(), slices_end(),
                   SegmentSlice<ELFT>::compare_slices);
}

/// \brief Assign virtual addresses to the slices
template <class ELFT> void Segment<ELFT>::assignVirtualAddress(uint64_t &addr) {
  bool isTLSSegment = false;
  bool isDataPageAlignedForNMagic = false;
  uint64_t tlsStartAddr = 0;

  for (auto slice : slices()) {
    // Align to a page only if the output is not
    // OutputMagic::NMAGIC/OutputMagic::OMAGIC
    if (_outputMagic != ELFLinkingContext::OutputMagic::NMAGIC &&
        _outputMagic != ELFLinkingContext::OutputMagic::OMAGIC)
      addr = llvm::RoundUpToAlignment(addr, this->_context.getPageSize());

    // Align to the slice alignment
    addr = llvm::RoundUpToAlignment(addr, slice->align2());

    bool virtualAddressSet = false;
    for (auto section : slice->sections()) {
      // If the linker outputmagic is set to OutputMagic::NMAGIC, align the Data
      // to a page boundary
      if (!isDataPageAlignedForNMagic && needAlign(section)) {
        addr = llvm::RoundUpToAlignment(addr, this->_context.getPageSize());
        isDataPageAlignedForNMagic = true;
      }
      // Align the section address
      addr = llvm::RoundUpToAlignment(addr, section->align2());
      // Check if the segment is of type TLS
      // The sections that belong to the TLS segment have their
      // virtual addresses that are relative To TP
      Section<ELFT> *currentSection = dyn_cast<Section<ELFT> >(section);
      if (currentSection)
        isTLSSegment = (currentSection->getSegmentType() == llvm::ELF::PT_TLS);

      tlsStartAddr = (isTLSSegment)
                     ? llvm::RoundUpToAlignment(tlsStartAddr, section->align2())
                     : 0;
      if (!virtualAddressSet) {
        slice->setVAddr(addr);
        virtualAddressSet = true;
      }
      section->setVAddr(addr);
      if (auto s = dyn_cast<Section<ELFT> >(section)) {
        if (isTLSSegment)
          s->assignVirtualAddress(tlsStartAddr);
        else
          s->assignVirtualAddress(addr);
      }
      if (isTLSSegment)
        tlsStartAddr += section->memSize();
      section->setMemSize(addr + section->memSize() - section->virtualAddr());
      // TBSS section is special in that it doesn't contribute to memory of any
      // segment. If we see a tbss section, don't add memory size to addr
      // The fileOffset is automatically taken care of since TBSS section does
      // not end up using file size
      if (section->order() != DefaultLayout<ELFT>::ORDER_TBSS)
        addr += section->memSize();
    }
    slice->setMemSize(addr - slice->virtualAddr());
  }
}

// Write the Segment
template <class ELFT>
void Segment<ELFT>::write(ELFWriter *writer, TargetLayout<ELFT> &layout,
                          llvm::FileOutputBuffer &buffer) {
  for (auto slice : slices())
    for (auto section : slice->sections())
      section->write(writer, layout, buffer);
}

template<class ELFT>
int64_t
Segment<ELFT>::flags() const {
  int64_t fl = 0;
  if (_flags & llvm::ELF::SHF_ALLOC)
    fl |= llvm::ELF::PF_R;
  if (_flags & llvm::ELF::SHF_WRITE)
    fl |= llvm::ELF::PF_W;
  if (_flags & llvm::ELF::SHF_EXECINSTR)
    fl |= llvm::ELF::PF_X;
  return fl;
}
} // end namespace elf
} // end namespace lld

#endif
