//===- lld/Core/Simple.h - Simple implementations of Atom and File --------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
/// Provide simple implementations for Atoms and File.
///
//===----------------------------------------------------------------------===//

#ifndef LLD_CORE_SIMPLE_H
#define LLD_CORE_SIMPLE_H

#include "lld/Core/AbsoluteAtom.h"
#include "lld/Core/Atom.h"
#include "lld/Core/DefinedAtom.h"
#include "lld/Core/File.h"
#include "lld/Core/Reference.h"
#include "lld/Core/SharedLibraryAtom.h"
#include "lld/Core/UndefinedAtom.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/ilist.h"
#include "llvm/ADT/ilist_node.h"
#include "llvm/Support/Allocator.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/ErrorHandling.h"
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <functional>

namespace lld {

class SimpleFile : public File {
public:
  SimpleFile(StringRef path, File::Kind kind)
    : File(path, kind) {}

  ~SimpleFile() override {
    _defined.clear();
    _undefined.clear();
    _shared.clear();
    _absolute.clear();
  }

  void addAtom(DefinedAtom &a) {
    _defined.push_back(OwningAtomPtr<DefinedAtom>(&a));
  }
  void addAtom(UndefinedAtom &a) {
    _undefined.push_back(OwningAtomPtr<UndefinedAtom>(&a));
  }
  void addAtom(SharedLibraryAtom &a) {
    _shared.push_back(OwningAtomPtr<SharedLibraryAtom>(&a));
  }
  void addAtom(AbsoluteAtom &a) {
    _absolute.push_back(OwningAtomPtr<AbsoluteAtom>(&a));
  }

  void addAtom(const Atom &atom) {
    if (auto *p = dyn_cast<DefinedAtom>(&atom)) {
      addAtom(const_cast<DefinedAtom &>(*p));
    } else if (auto *p = dyn_cast<UndefinedAtom>(&atom)) {
      addAtom(const_cast<UndefinedAtom &>(*p));
    } else if (auto *p = dyn_cast<SharedLibraryAtom>(&atom)) {
      addAtom(const_cast<SharedLibraryAtom &>(*p));
    } else if (auto *p = dyn_cast<AbsoluteAtom>(&atom)) {
      addAtom(const_cast<AbsoluteAtom &>(*p));
    } else {
      llvm_unreachable("atom has unknown definition kind");
    }
  }

  void removeDefinedAtomsIf(std::function<bool(const DefinedAtom *)> pred) {
    auto &atoms = _defined;
    auto newEnd = std::remove_if(atoms.begin(), atoms.end(),
                                 [&pred](OwningAtomPtr<DefinedAtom> &p) {
                                   return pred(p.get());
                                 });
    atoms.erase(newEnd, atoms.end());
  }

  const AtomRange<DefinedAtom> defined() const override { return _defined; }

  const AtomRange<UndefinedAtom> undefined() const override {
    return _undefined;
  }

  const AtomRange<SharedLibraryAtom> sharedLibrary() const override {
    return _shared;
  }

  const AtomRange<AbsoluteAtom> absolute() const override {
    return _absolute;
  }

  void clearAtoms() override {
    _defined.clear();
    _undefined.clear();
    _shared.clear();
    _absolute.clear();
  }

private:
  AtomVector<DefinedAtom> _defined;
  AtomVector<UndefinedAtom> _undefined;
  AtomVector<SharedLibraryAtom> _shared;
  AtomVector<AbsoluteAtom> _absolute;
};

class SimpleReference : public Reference,
                        public llvm::ilist_node<SimpleReference> {
public:
  SimpleReference(Reference::KindNamespace ns, Reference::KindArch arch,
                  Reference::KindValue value, uint64_t off, const Atom *t,
                  Reference::Addend a)
      : Reference(ns, arch, value), _target(t), _offsetInAtom(off), _addend(a) {
  }
  SimpleReference()
      : Reference(Reference::KindNamespace::all, Reference::KindArch::all, 0),
        _target(nullptr), _offsetInAtom(0), _addend(0) {}

  uint64_t offsetInAtom() const override { return _offsetInAtom; }

  const Atom *target() const override {
    assert(_target);
    return _target;
  }

  Addend addend() const override { return _addend; }
  void setAddend(Addend a) override { _addend = a; }
  void setTarget(const Atom *newAtom) override { _target = newAtom; }

private:
  const Atom *_target;
  uint64_t _offsetInAtom;
  Addend _addend;
};

class SimpleDefinedAtom : public DefinedAtom {
public:
  explicit SimpleDefinedAtom(const File &f)
      : _file(f), _ordinal(f.getNextAtomOrdinalAndIncrement()) {}

  ~SimpleDefinedAtom() override {
    _references.clearAndLeakNodesUnsafely();
  }

  const File &file() const override { return _file; }

  StringRef name() const override { return StringRef(); }

  uint64_t ordinal() const override { return _ordinal; }

  Scope scope() const override { return DefinedAtom::scopeLinkageUnit; }

  Interposable interposable() const override {
    return DefinedAtom::interposeNo;
  }

  Merge merge() const override { return DefinedAtom::mergeNo; }

  Alignment alignment() const override { return 1; }

  SectionChoice sectionChoice() const override {
    return DefinedAtom::sectionBasedOnContent;
  }

  StringRef customSectionName() const override { return StringRef(); }
  DeadStripKind deadStrip() const override {
    return DefinedAtom::deadStripNormal;
  }

  DefinedAtom::reference_iterator begin() const override {
    const void *it =
        reinterpret_cast<const void *>(_references.begin().getNodePtr());
    return reference_iterator(*this, it);
  }

  DefinedAtom::reference_iterator end() const override {
    const void *it =
        reinterpret_cast<const void *>(_references.end().getNodePtr());
    return reference_iterator(*this, it);
  }

  const Reference *derefIterator(const void *it) const override {
    return &*RefList::const_iterator(
        *reinterpret_cast<const llvm::ilist_node<SimpleReference> *>(it));
  }

  void incrementIterator(const void *&it) const override {
    RefList::const_iterator ref(
        *reinterpret_cast<const llvm::ilist_node<SimpleReference> *>(it));
    it = reinterpret_cast<const void *>(std::next(ref).getNodePtr());
  }

  void addReference(Reference::KindNamespace ns,
                    Reference::KindArch arch,
                    Reference::KindValue kindValue, uint64_t off,
                    const Atom *target, Reference::Addend a) override {
    assert(target && "trying to create reference to nothing");
    auto node = new (_file.allocator())
        SimpleReference(ns, arch, kindValue, off, target, a);
    _references.push_back(node);
  }

  /// Sort references in a canonical order (by offset, then by kind).
  void sortReferences() const {
    // Cannot sort a linked  list, so move elements into a temporary vector,
    // sort the vector, then reconstruct the list.
    llvm::SmallVector<SimpleReference *, 16> elements;
    for (SimpleReference &node : _references) {
      elements.push_back(&node);
    }
    std::sort(elements.begin(), elements.end(),
        [] (const SimpleReference *lhs, const SimpleReference *rhs) -> bool {
          uint64_t lhsOffset = lhs->offsetInAtom();
          uint64_t rhsOffset = rhs->offsetInAtom();
          if (rhsOffset != lhsOffset)
            return (lhsOffset < rhsOffset);
          if (rhs->kindNamespace() != lhs->kindNamespace())
            return (lhs->kindNamespace() < rhs->kindNamespace());
          if (rhs->kindArch() != lhs->kindArch())
            return (lhs->kindArch() < rhs->kindArch());
          return (lhs->kindValue() < rhs->kindValue());
        });
    _references.clearAndLeakNodesUnsafely();
    for (SimpleReference *node : elements) {
      _references.push_back(node);
    }
  }

  void setOrdinal(uint64_t ord) { _ordinal = ord; }

private:
  typedef llvm::ilist<SimpleReference> RefList;

  const File &_file;
  uint64_t _ordinal;
  mutable RefList _references;
};

class SimpleUndefinedAtom : public UndefinedAtom {
public:
  SimpleUndefinedAtom(const File &f, StringRef name) : _file(f), _name(name) {
    assert(!name.empty() && "UndefinedAtoms must have a name");
  }

  ~SimpleUndefinedAtom() override = default;

  /// file - returns the File that produced/owns this Atom
  const File &file() const override { return _file; }

  /// name - The name of the atom. For a function atom, it is the (mangled)
  /// name of the function.
  StringRef name() const override { return _name; }

  CanBeNull canBeNull() const override { return UndefinedAtom::canBeNullNever; }

private:
  const File &_file;
  StringRef _name;
};

} // end namespace lld

#endif // LLD_CORE_SIMPLE_H
