//===- lib/ReaderWriter/YAML/ReaderWriterYAML.cpp -------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

#include "lld/Core/AbsoluteAtom.h"
#include "lld/Core/ArchiveLibraryFile.h"
#include "lld/Core/Atom.h"
#include "lld/Core/DefinedAtom.h"
#include "lld/Core/Error.h"
#include "lld/Core/File.h"
#include "lld/Core/LinkingContext.h"
#include "lld/Core/Reader.h"
#include "lld/Core/Reference.h"
#include "lld/Core/SharedLibraryAtom.h"
#include "lld/Core/Simple.h"
#include "lld/Core/UndefinedAtom.h"
#include "lld/Core/Writer.h"
#include "lld/ReaderWriter/YamlContext.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
#include "llvm/BinaryFormat/Magic.h"
#include "llvm/Support/Allocator.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/ErrorOr.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/YAMLTraits.h"
#include "llvm/Support/raw_ostream.h"
#include <cassert>
#include <cstdint>
#include <cstring>
#include <memory>
#include <string>
#include <system_error>
#include <vector>

using llvm::file_magic;
using llvm::yaml::MappingTraits;
using llvm::yaml::ScalarEnumerationTraits;
using llvm::yaml::ScalarTraits;
using llvm::yaml::IO;
using llvm::yaml::SequenceTraits;
using llvm::yaml::DocumentListTraits;

using namespace lld;

/// The conversion of Atoms to and from YAML uses LLVM's YAML I/O.  This
/// file just defines template specializations on the lld types which control
/// how the mapping is done to and from YAML.

namespace {

/// Used when writing yaml files.
/// In most cases, atoms names are unambiguous, so references can just
/// use the atom name as the target (e.g. target: foo).  But in a few
/// cases that does not work, so ref-names are added.  These are labels
/// used only in yaml.  The labels do not exist in the Atom model.
///
/// One need for ref-names are when atoms have no user supplied name
/// (e.g. c-string literal).  Another case is when two object files with
/// identically named static functions are merged (ld -r) into one object file.
/// In that case referencing the function by name is ambiguous, so a unique
/// ref-name is added.
class RefNameBuilder {
public:
  RefNameBuilder(const lld::File &file)
      : _collisionCount(0), _unnamedCounter(0) {
    // visit all atoms
    for (const lld::DefinedAtom *atom : file.defined()) {
      // Build map of atoms names to detect duplicates
      if (!atom->name().empty())
        buildDuplicateNameMap(*atom);

      // Find references to unnamed atoms and create ref-names for them.
      for (const lld::Reference *ref : *atom) {
        // create refname for any unnamed reference target
        const lld::Atom *target = ref->target();
        if ((target != nullptr) && target->name().empty()) {
          std::string storage;
          llvm::raw_string_ostream buffer(storage);
          buffer << llvm::format("L%03d", _unnamedCounter++);
          StringRef newName = copyString(buffer.str());
          _refNames[target] = newName;
          DEBUG_WITH_TYPE("WriterYAML",
                          llvm::dbgs() << "unnamed atom: creating ref-name: '"
                                       << newName << "' ("
                                       << (const void *)newName.data() << ", "
                                       << newName.size() << ")\n");
        }
      }
    }
    for (const lld::UndefinedAtom *undefAtom : file.undefined()) {
      buildDuplicateNameMap(*undefAtom);
    }
    for (const lld::SharedLibraryAtom *shlibAtom : file.sharedLibrary()) {
      buildDuplicateNameMap(*shlibAtom);
    }
    for (const lld::AbsoluteAtom *absAtom : file.absolute()) {
      if (!absAtom->name().empty())
        buildDuplicateNameMap(*absAtom);
    }
  }

  void buildDuplicateNameMap(const lld::Atom &atom) {
    assert(!atom.name().empty());
    NameToAtom::iterator pos = _nameMap.find(atom.name());
    if (pos != _nameMap.end()) {
      // Found name collision, give each a unique ref-name.
      std::string Storage;
      llvm::raw_string_ostream buffer(Storage);
      buffer << atom.name() << llvm::format(".%03d", ++_collisionCount);
      StringRef newName = copyString(buffer.str());
      _refNames[&atom] = newName;
      DEBUG_WITH_TYPE("WriterYAML",
                      llvm::dbgs() << "name collsion: creating ref-name: '"
                                   << newName << "' ("
                                   << (const void *)newName.data()
                                   << ", " << newName.size() << ")\n");
      const lld::Atom *prevAtom = pos->second;
      AtomToRefName::iterator pos2 = _refNames.find(prevAtom);
      if (pos2 == _refNames.end()) {
        // Only create ref-name for previous if none already created.
        std::string Storage2;
        llvm::raw_string_ostream buffer2(Storage2);
        buffer2 << prevAtom->name() << llvm::format(".%03d", ++_collisionCount);
        StringRef newName2 = copyString(buffer2.str());
        _refNames[prevAtom] = newName2;
        DEBUG_WITH_TYPE("WriterYAML",
                        llvm::dbgs() << "name collsion: creating ref-name: '"
                                     << newName2 << "' ("
                                     << (const void *)newName2.data() << ", "
                                     << newName2.size() << ")\n");
      }
    } else {
      // First time we've seen this name, just add it to map.
      _nameMap[atom.name()] = &atom;
      DEBUG_WITH_TYPE("WriterYAML", llvm::dbgs()
                                        << "atom name seen for first time: '"
                                        << atom.name() << "' ("
                                        << (const void *)atom.name().data()
                                        << ", " << atom.name().size() << ")\n");
    }
  }

  bool hasRefName(const lld::Atom *atom) { return _refNames.count(atom); }

  StringRef refName(const lld::Atom *atom) {
    return _refNames.find(atom)->second;
  }

private:
  typedef llvm::StringMap<const lld::Atom *> NameToAtom;
  typedef llvm::DenseMap<const lld::Atom *, std::string> AtomToRefName;

  // Allocate a new copy of this string in _storage, so the strings
  // can be freed when RefNameBuilder is destroyed.
  StringRef copyString(StringRef str) {
    char *s = _storage.Allocate<char>(str.size());
    memcpy(s, str.data(), str.size());
    return StringRef(s, str.size());
  }

  unsigned int                         _collisionCount;
  unsigned int                         _unnamedCounter;
  NameToAtom                           _nameMap;
  AtomToRefName                        _refNames;
  llvm::BumpPtrAllocator               _storage;
};

/// Used when reading yaml files to find the target of a reference
/// that could be a name or ref-name.
class RefNameResolver {
public:
  RefNameResolver(const lld::File *file, IO &io);

  const lld::Atom *lookup(StringRef name) const {
    NameToAtom::const_iterator pos = _nameMap.find(name);
    if (pos != _nameMap.end())
      return pos->second;
    _io.setError(Twine("no such atom name: ") + name);
    return nullptr;
  }

private:
  typedef llvm::StringMap<const lld::Atom *> NameToAtom;

  void add(StringRef name, const lld::Atom *atom) {
    if (_nameMap.count(name)) {
      _io.setError(Twine("duplicate atom name: ") + name);
    } else {
      _nameMap[name] = atom;
    }
  }

  IO &_io;
  NameToAtom _nameMap;
};

/// Mapping of Atoms.
template <typename T> class AtomList {
  using Ty = std::vector<OwningAtomPtr<T>>;

public:
  typename Ty::iterator begin() { return _atoms.begin(); }
  typename Ty::iterator end() { return _atoms.end(); }
  Ty _atoms;
};

/// Mapping of kind: field in yaml files.
enum FileKinds {
  fileKindObjectAtoms, // atom based object file encoded in yaml
  fileKindArchive,     // static archive library encoded in yaml
  fileKindObjectMachO  // mach-o object files encoded in yaml
};

struct ArchMember {
  FileKinds         _kind;
  StringRef         _name;
  const lld::File  *_content;
};

// The content bytes in a DefinedAtom are just uint8_t but we want
// special formatting, so define a strong type.
LLVM_YAML_STRONG_TYPEDEF(uint8_t, ImplicitHex8)

// SharedLibraryAtoms have a bool canBeNull() method which we'd like to be
// more readable than just true/false.
LLVM_YAML_STRONG_TYPEDEF(bool, ShlibCanBeNull)

// lld::Reference::Kind is a tuple of <namespace, arch, value>.
// For yaml, we just want one string that encapsulates the tuple.
struct RefKind {
  Reference::KindNamespace  ns;
  Reference::KindArch       arch;
  Reference::KindValue      value;
};

} // end anonymous namespace

LLVM_YAML_IS_SEQUENCE_VECTOR(ArchMember)
LLVM_YAML_IS_SEQUENCE_VECTOR(const lld::Reference *)
// Always write DefinedAtoms content bytes as a flow sequence.
LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(ImplicitHex8)

// for compatibility with gcc-4.7 in C++11 mode, add extra namespace
namespace llvm {
namespace yaml {

// This is a custom formatter for RefKind
template <> struct ScalarTraits<RefKind> {
  static void output(const RefKind &kind, void *ctxt, raw_ostream &out) {
    assert(ctxt != nullptr);
    YamlContext *info = reinterpret_cast<YamlContext *>(ctxt);
    assert(info->_registry);
    StringRef str;
    if (info->_registry->referenceKindToString(kind.ns, kind.arch, kind.value,
                                               str))
      out << str;
    else
      out << (int)(kind.ns) << "-" << (int)(kind.arch) << "-" << kind.value;
  }

  static StringRef input(StringRef scalar, void *ctxt, RefKind &kind) {
    assert(ctxt != nullptr);
    YamlContext *info = reinterpret_cast<YamlContext *>(ctxt);
    assert(info->_registry);
    if (info->_registry->referenceKindFromString(scalar, kind.ns, kind.arch,
                                                 kind.value))
      return StringRef();
    return StringRef("unknown reference kind");
  }

  static QuotingType mustQuote(StringRef) { return QuotingType::None; }
};

template <> struct ScalarEnumerationTraits<lld::File::Kind> {
  static void enumeration(IO &io, lld::File::Kind &value) {
    io.enumCase(value, "error-object",   lld::File::kindErrorObject);
    io.enumCase(value, "object",         lld::File::kindMachObject);
    io.enumCase(value, "shared-library", lld::File::kindSharedLibrary);
    io.enumCase(value, "static-library", lld::File::kindArchiveLibrary);
  }
};

template <> struct ScalarEnumerationTraits<lld::Atom::Scope> {
  static void enumeration(IO &io, lld::Atom::Scope &value) {
    io.enumCase(value, "global", lld::Atom::scopeGlobal);
    io.enumCase(value, "hidden", lld::Atom::scopeLinkageUnit);
    io.enumCase(value, "static", lld::Atom::scopeTranslationUnit);
  }
};

template <> struct ScalarEnumerationTraits<lld::DefinedAtom::SectionChoice> {
  static void enumeration(IO &io, lld::DefinedAtom::SectionChoice &value) {
    io.enumCase(value, "content", lld::DefinedAtom::sectionBasedOnContent);
    io.enumCase(value, "custom",  lld::DefinedAtom::sectionCustomPreferred);
    io.enumCase(value, "custom-required",
                                 lld::DefinedAtom::sectionCustomRequired);
  }
};

template <> struct ScalarEnumerationTraits<lld::DefinedAtom::Interposable> {
  static void enumeration(IO &io, lld::DefinedAtom::Interposable &value) {
    io.enumCase(value, "no",           DefinedAtom::interposeNo);
    io.enumCase(value, "yes",          DefinedAtom::interposeYes);
    io.enumCase(value, "yes-and-weak", DefinedAtom::interposeYesAndRuntimeWeak);
  }
};

template <> struct ScalarEnumerationTraits<lld::DefinedAtom::Merge> {
  static void enumeration(IO &io, lld::DefinedAtom::Merge &value) {
    io.enumCase(value, "no",           lld::DefinedAtom::mergeNo);
    io.enumCase(value, "as-tentative", lld::DefinedAtom::mergeAsTentative);
    io.enumCase(value, "as-weak",      lld::DefinedAtom::mergeAsWeak);
    io.enumCase(value, "as-addressed-weak",
                                   lld::DefinedAtom::mergeAsWeakAndAddressUsed);
    io.enumCase(value, "by-content",   lld::DefinedAtom::mergeByContent);
    io.enumCase(value, "same-name-and-size",
                lld::DefinedAtom::mergeSameNameAndSize);
    io.enumCase(value, "largest", lld::DefinedAtom::mergeByLargestSection);
  }
};

template <> struct ScalarEnumerationTraits<lld::DefinedAtom::DeadStripKind> {
  static void enumeration(IO &io, lld::DefinedAtom::DeadStripKind &value) {
    io.enumCase(value, "normal", lld::DefinedAtom::deadStripNormal);
    io.enumCase(value, "never",  lld::DefinedAtom::deadStripNever);
    io.enumCase(value, "always", lld::DefinedAtom::deadStripAlways);
  }
};

template <> struct ScalarEnumerationTraits<lld::DefinedAtom::DynamicExport> {
  static void enumeration(IO &io, lld::DefinedAtom::DynamicExport &value) {
    io.enumCase(value, "normal", lld::DefinedAtom::dynamicExportNormal);
    io.enumCase(value, "always", lld::DefinedAtom::dynamicExportAlways);
  }
};

template <> struct ScalarEnumerationTraits<lld::DefinedAtom::CodeModel> {
  static void enumeration(IO &io, lld::DefinedAtom::CodeModel &value) {
    io.enumCase(value, "none", lld::DefinedAtom::codeNA);
    io.enumCase(value, "mips-pic", lld::DefinedAtom::codeMipsPIC);
    io.enumCase(value, "mips-micro", lld::DefinedAtom::codeMipsMicro);
    io.enumCase(value, "mips-micro-pic", lld::DefinedAtom::codeMipsMicroPIC);
    io.enumCase(value, "mips-16", lld::DefinedAtom::codeMips16);
    io.enumCase(value, "arm-thumb", lld::DefinedAtom::codeARMThumb);
    io.enumCase(value, "arm-a", lld::DefinedAtom::codeARM_a);
    io.enumCase(value, "arm-d", lld::DefinedAtom::codeARM_d);
    io.enumCase(value, "arm-t", lld::DefinedAtom::codeARM_t);
  }
};

template <>
struct ScalarEnumerationTraits<lld::DefinedAtom::ContentPermissions> {
  static void enumeration(IO &io, lld::DefinedAtom::ContentPermissions &value) {
    io.enumCase(value, "---",     lld::DefinedAtom::perm___);
    io.enumCase(value, "r--",     lld::DefinedAtom::permR__);
    io.enumCase(value, "r-x",     lld::DefinedAtom::permR_X);
    io.enumCase(value, "rw-",     lld::DefinedAtom::permRW_);
    io.enumCase(value, "rwx",     lld::DefinedAtom::permRWX);
    io.enumCase(value, "rw-l",    lld::DefinedAtom::permRW_L);
    io.enumCase(value, "unknown", lld::DefinedAtom::permUnknown);
  }
};

template <> struct ScalarEnumerationTraits<lld::DefinedAtom::ContentType> {
  static void enumeration(IO &io, lld::DefinedAtom::ContentType &value) {
    io.enumCase(value, "unknown",         DefinedAtom::typeUnknown);
    io.enumCase(value, "code",            DefinedAtom::typeCode);
    io.enumCase(value, "stub",            DefinedAtom::typeStub);
    io.enumCase(value, "constant",        DefinedAtom::typeConstant);
    io.enumCase(value, "data",            DefinedAtom::typeData);
    io.enumCase(value, "quick-data",      DefinedAtom::typeDataFast);
    io.enumCase(value, "zero-fill",       DefinedAtom::typeZeroFill);
    io.enumCase(value, "zero-fill-quick", DefinedAtom::typeZeroFillFast);
    io.enumCase(value, "const-data",      DefinedAtom::typeConstData);
    io.enumCase(value, "got",             DefinedAtom::typeGOT);
    io.enumCase(value, "resolver",        DefinedAtom::typeResolver);
    io.enumCase(value, "branch-island",   DefinedAtom::typeBranchIsland);
    io.enumCase(value, "branch-shim",     DefinedAtom::typeBranchShim);
    io.enumCase(value, "stub-helper",     DefinedAtom::typeStubHelper);
    io.enumCase(value, "c-string",        DefinedAtom::typeCString);
    io.enumCase(value, "utf16-string",    DefinedAtom::typeUTF16String);
    io.enumCase(value, "unwind-cfi",      DefinedAtom::typeCFI);
    io.enumCase(value, "unwind-lsda",     DefinedAtom::typeLSDA);
    io.enumCase(value, "const-4-byte",    DefinedAtom::typeLiteral4);
    io.enumCase(value, "const-8-byte",    DefinedAtom::typeLiteral8);
    io.enumCase(value, "const-16-byte",   DefinedAtom::typeLiteral16);
    io.enumCase(value, "lazy-pointer",    DefinedAtom::typeLazyPointer);
    io.enumCase(value, "lazy-dylib-pointer",
                                          DefinedAtom::typeLazyDylibPointer);
    io.enumCase(value, "cfstring",        DefinedAtom::typeCFString);
    io.enumCase(value, "initializer-pointer",
                                          DefinedAtom::typeInitializerPtr);
    io.enumCase(value, "terminator-pointer",
                                          DefinedAtom::typeTerminatorPtr);
    io.enumCase(value, "c-string-pointer",DefinedAtom::typeCStringPtr);
    io.enumCase(value, "objc-class-pointer",
                                          DefinedAtom::typeObjCClassPtr);
    io.enumCase(value, "objc-category-list",
                                          DefinedAtom::typeObjC2CategoryList);
    io.enumCase(value, "objc-image-info",
                                          DefinedAtom::typeObjCImageInfo);
    io.enumCase(value, "objc-method-list",
                                          DefinedAtom::typeObjCMethodList);
    io.enumCase(value, "objc-class1",     DefinedAtom::typeObjC1Class);
    io.enumCase(value, "dtraceDOF",       DefinedAtom::typeDTraceDOF);
    io.enumCase(value, "interposing-tuples",
                                          DefinedAtom::typeInterposingTuples);
    io.enumCase(value, "lto-temp",        DefinedAtom::typeTempLTO);
    io.enumCase(value, "compact-unwind",  DefinedAtom::typeCompactUnwindInfo);
    io.enumCase(value, "unwind-info",     DefinedAtom::typeProcessedUnwindInfo);
    io.enumCase(value, "tlv-thunk",       DefinedAtom::typeThunkTLV);
    io.enumCase(value, "tlv-data",        DefinedAtom::typeTLVInitialData);
    io.enumCase(value, "tlv-zero-fill",   DefinedAtom::typeTLVInitialZeroFill);
    io.enumCase(value, "tlv-initializer-ptr",
                                          DefinedAtom::typeTLVInitializerPtr);
    io.enumCase(value, "mach_header",     DefinedAtom::typeMachHeader);
    io.enumCase(value, "dso_handle",      DefinedAtom::typeDSOHandle);
    io.enumCase(value, "sectcreate",      DefinedAtom::typeSectCreate);
  }
};

template <> struct ScalarEnumerationTraits<lld::UndefinedAtom::CanBeNull> {
  static void enumeration(IO &io, lld::UndefinedAtom::CanBeNull &value) {
    io.enumCase(value, "never",       lld::UndefinedAtom::canBeNullNever);
    io.enumCase(value, "at-runtime",  lld::UndefinedAtom::canBeNullAtRuntime);
    io.enumCase(value, "at-buildtime",lld::UndefinedAtom::canBeNullAtBuildtime);
  }
};

template <> struct ScalarEnumerationTraits<ShlibCanBeNull> {
  static void enumeration(IO &io, ShlibCanBeNull &value) {
    io.enumCase(value, "never",      false);
    io.enumCase(value, "at-runtime", true);
  }
};

template <>
struct ScalarEnumerationTraits<lld::SharedLibraryAtom::Type> {
  static void enumeration(IO &io, lld::SharedLibraryAtom::Type &value) {
    io.enumCase(value, "code",    lld::SharedLibraryAtom::Type::Code);
    io.enumCase(value, "data",    lld::SharedLibraryAtom::Type::Data);
    io.enumCase(value, "unknown", lld::SharedLibraryAtom::Type::Unknown);
  }
};

/// This is a custom formatter for lld::DefinedAtom::Alignment.  Values look
/// like:
///     8           # 8-byte aligned
///     7 mod 16    # 16-byte aligned plus 7 bytes
template <> struct ScalarTraits<lld::DefinedAtom::Alignment> {
  static void output(const lld::DefinedAtom::Alignment &value, void *ctxt,
                     raw_ostream &out) {
    if (value.modulus == 0) {
      out << llvm::format("%d", value.value);
    } else {
      out << llvm::format("%d mod %d", value.modulus, value.value);
    }
  }

  static StringRef input(StringRef scalar, void *ctxt,
                         lld::DefinedAtom::Alignment &value) {
    value.modulus = 0;
    size_t modStart = scalar.find("mod");
    if (modStart != StringRef::npos) {
      StringRef modStr = scalar.slice(0, modStart);
      modStr = modStr.rtrim();
      unsigned int modulus;
      if (modStr.getAsInteger(0, modulus)) {
        return "malformed alignment modulus";
      }
      value.modulus = modulus;
      scalar = scalar.drop_front(modStart + 3);
      scalar = scalar.ltrim();
    }
    unsigned int power;
    if (scalar.getAsInteger(0, power)) {
      return "malformed alignment power";
    }
    value.value = power;
    if (value.modulus >= power) {
      return "malformed alignment, modulus too large for power";
    }
    return StringRef(); // returning empty string means success
  }

  static QuotingType mustQuote(StringRef) { return QuotingType::None; }
};

template <> struct ScalarEnumerationTraits<FileKinds> {
  static void enumeration(IO &io, FileKinds &value) {
    io.enumCase(value, "object",        fileKindObjectAtoms);
    io.enumCase(value, "archive",       fileKindArchive);
    io.enumCase(value, "object-mach-o", fileKindObjectMachO);
  }
};

template <> struct MappingTraits<ArchMember> {
  static void mapping(IO &io, ArchMember &member) {
    io.mapOptional("kind",    member._kind, fileKindObjectAtoms);
    io.mapOptional("name",    member._name);
    io.mapRequired("content", member._content);
  }
};

// Declare that an AtomList is a yaml sequence.
template <typename T> struct SequenceTraits<AtomList<T> > {
  static size_t size(IO &io, AtomList<T> &seq) { return seq._atoms.size(); }
  static T *&element(IO &io, AtomList<T> &seq, size_t index) {
    if (index >= seq._atoms.size())
      seq._atoms.resize(index + 1);
    return seq._atoms[index].get();
  }
};

// Declare that an AtomRange is a yaml sequence.
template <typename T> struct SequenceTraits<File::AtomRange<T> > {
  static size_t size(IO &io, File::AtomRange<T> &seq) { return seq.size(); }
  static T *&element(IO &io, File::AtomRange<T> &seq, size_t index) {
    assert(io.outputting() && "AtomRange only used when outputting");
    assert(index < seq.size() && "Out of range access");
    return seq[index].get();
  }
};

// Used to allow DefinedAtom content bytes to be a flow sequence of
// two-digit hex numbers without the leading 0x (e.g. FF, 04, 0A)
template <> struct ScalarTraits<ImplicitHex8> {
  static void output(const ImplicitHex8 &val, void *, raw_ostream &out) {
    uint8_t num = val;
    out << llvm::format("%02X", num);
  }

  static StringRef input(StringRef str, void *, ImplicitHex8 &val) {
    unsigned long long n;
    if (getAsUnsignedInteger(str, 16, n))
      return "invalid two-digit-hex number";
    if (n > 0xFF)
      return "out of range two-digit-hex number";
    val = n;
    return StringRef(); // returning empty string means success
  }

  static QuotingType mustQuote(StringRef) { return QuotingType::None; }
};

// YAML conversion for std::vector<const lld::File*>
template <> struct DocumentListTraits<std::vector<const lld::File *> > {
  static size_t size(IO &io, std::vector<const lld::File *> &seq) {
    return seq.size();
  }
  static const lld::File *&element(IO &io, std::vector<const lld::File *> &seq,
                                   size_t index) {
    if (index >= seq.size())
      seq.resize(index + 1);
    return seq[index];
  }
};

// YAML conversion for const lld::File*
template <> struct MappingTraits<const lld::File *> {
  class NormArchiveFile : public lld::ArchiveLibraryFile {
  public:
    NormArchiveFile(IO &io) : ArchiveLibraryFile("") {}

    NormArchiveFile(IO &io, const lld::File *file)
        : ArchiveLibraryFile(file->path()), _path(file->path()) {
      // If we want to support writing archives, this constructor would
      // need to populate _members.
    }

    const lld::File *denormalize(IO &io) { return this; }

    const AtomRange<lld::DefinedAtom> defined() const override {
      return _noDefinedAtoms;
    }

    const AtomRange<lld::UndefinedAtom> undefined() const override {
      return _noUndefinedAtoms;
    }

    const AtomRange<lld::SharedLibraryAtom> sharedLibrary() const override {
      return _noSharedLibraryAtoms;
    }

    const AtomRange<lld::AbsoluteAtom> absolute() const override {
      return _noAbsoluteAtoms;
    }

    void clearAtoms() override {
      _noDefinedAtoms.clear();
      _noUndefinedAtoms.clear();
      _noSharedLibraryAtoms.clear();
      _noAbsoluteAtoms.clear();
    }

    File *find(StringRef name) override {
      for (const ArchMember &member : _members)
        for (const lld::DefinedAtom *atom : member._content->defined())
          if (name == atom->name())
            return const_cast<File *>(member._content);
      return nullptr;
    }

    std::error_code
    parseAllMembers(std::vector<std::unique_ptr<File>> &result) override {
      return std::error_code();
    }

    StringRef               _path;
    std::vector<ArchMember> _members;
  };

  class NormalizedFile : public lld::File {
  public:
    NormalizedFile(IO &io)
      : File("", kindNormalizedObject), _io(io), _rnb(nullptr),
        _definedAtomsRef(_definedAtoms._atoms),
        _undefinedAtomsRef(_undefinedAtoms._atoms),
        _sharedLibraryAtomsRef(_sharedLibraryAtoms._atoms),
        _absoluteAtomsRef(_absoluteAtoms._atoms) {}

    NormalizedFile(IO &io, const lld::File *file)
        : File(file->path(), kindNormalizedObject), _io(io),
          _rnb(new RefNameBuilder(*file)), _path(file->path()),
        _definedAtomsRef(file->defined()),
        _undefinedAtomsRef(file->undefined()),
        _sharedLibraryAtomsRef(file->sharedLibrary()),
        _absoluteAtomsRef(file->absolute()) {
    }

    ~NormalizedFile() override {
    }

    const lld::File *denormalize(IO &io);

    const AtomRange<lld::DefinedAtom> defined() const override {
      return _definedAtomsRef;
    }

    const AtomRange<lld::UndefinedAtom> undefined() const override {
      return _undefinedAtomsRef;
    }

    const AtomRange<lld::SharedLibraryAtom> sharedLibrary() const override {
      return _sharedLibraryAtomsRef;
    }

    const AtomRange<lld::AbsoluteAtom> absolute() const override {
      return _absoluteAtomsRef;
    }

    void clearAtoms() override {
      _definedAtoms._atoms.clear();
      _undefinedAtoms._atoms.clear();
      _sharedLibraryAtoms._atoms.clear();
      _absoluteAtoms._atoms.clear();
    }

    // Allocate a new copy of this string in _storage, so the strings
    // can be freed when File is destroyed.
    StringRef copyString(StringRef str) {
      char *s = _storage.Allocate<char>(str.size());
      memcpy(s, str.data(), str.size());
      return StringRef(s, str.size());
    }

    IO                                  &_io;
    std::unique_ptr<RefNameBuilder>      _rnb;
    StringRef                            _path;
    AtomList<lld::DefinedAtom>           _definedAtoms;
    AtomList<lld::UndefinedAtom>         _undefinedAtoms;
    AtomList<lld::SharedLibraryAtom>     _sharedLibraryAtoms;
    AtomList<lld::AbsoluteAtom>          _absoluteAtoms;
    AtomRange<lld::DefinedAtom>          _definedAtomsRef;
    AtomRange<lld::UndefinedAtom>        _undefinedAtomsRef;
    AtomRange<lld::SharedLibraryAtom>    _sharedLibraryAtomsRef;
    AtomRange<lld::AbsoluteAtom>         _absoluteAtomsRef;
    llvm::BumpPtrAllocator               _storage;
  };

  static void mapping(IO &io, const lld::File *&file) {
    YamlContext *info = reinterpret_cast<YamlContext *>(io.getContext());
    assert(info != nullptr);
    // Let any register tag handler process this.
    if (info->_registry && info->_registry->handleTaggedDoc(io, file))
      return;
    // If no registered handler claims this tag and there is no tag,
    // grandfather in as "!native".
    if (io.mapTag("!native", true) || io.mapTag("tag:yaml.org,2002:map"))
      mappingAtoms(io, file);
  }

  static void mappingAtoms(IO &io, const lld::File *&file) {
    YamlContext *info = reinterpret_cast<YamlContext *>(io.getContext());
    MappingNormalizationHeap<NormalizedFile, const lld::File *>
      keys(io, file, nullptr);
    assert(info != nullptr);
    info->_file = keys.operator->();

    io.mapOptional("path",                 keys->_path);

    if (io.outputting()) {
      io.mapOptional("defined-atoms",        keys->_definedAtomsRef);
      io.mapOptional("undefined-atoms",      keys->_undefinedAtomsRef);
      io.mapOptional("shared-library-atoms", keys->_sharedLibraryAtomsRef);
      io.mapOptional("absolute-atoms",       keys->_absoluteAtomsRef);
    } else {
      io.mapOptional("defined-atoms",        keys->_definedAtoms);
      io.mapOptional("undefined-atoms",      keys->_undefinedAtoms);
      io.mapOptional("shared-library-atoms", keys->_sharedLibraryAtoms);
      io.mapOptional("absolute-atoms",       keys->_absoluteAtoms);
    }
  }

  static void mappingArchive(IO &io, const lld::File *&file) {
    YamlContext *info = reinterpret_cast<YamlContext *>(io.getContext());
    MappingNormalizationHeap<NormArchiveFile, const lld::File *>
      keys(io, file, &info->_file->allocator());

    io.mapOptional("path",    keys->_path);
    io.mapOptional("members", keys->_members);
  }
};

// YAML conversion for const lld::Reference*
template <> struct MappingTraits<const lld::Reference *> {
  class NormalizedReference : public lld::Reference {
  public:
    NormalizedReference(IO &io)
        : lld::Reference(lld::Reference::KindNamespace::all,
                         lld::Reference::KindArch::all, 0),
          _target(nullptr), _offset(0), _addend(0), _tag(0) {}

    NormalizedReference(IO &io, const lld::Reference *ref)
        : lld::Reference(ref->kindNamespace(), ref->kindArch(),
                         ref->kindValue()),
          _target(nullptr), _targetName(targetName(io, ref)),
          _offset(ref->offsetInAtom()), _addend(ref->addend()),
          _tag(ref->tag()) {
      _mappedKind.ns = ref->kindNamespace();
      _mappedKind.arch = ref->kindArch();
      _mappedKind.value = ref->kindValue();
    }

    const lld::Reference *denormalize(IO &io) {
      YamlContext *info = reinterpret_cast<YamlContext *>(io.getContext());
      assert(info != nullptr);
      typedef MappingTraits<const lld::File *>::NormalizedFile NormalizedFile;
      NormalizedFile *f = reinterpret_cast<NormalizedFile *>(info->_file);
      if (!_targetName.empty())
        _targetName = f->copyString(_targetName);
      DEBUG_WITH_TYPE("WriterYAML", llvm::dbgs()
                                        << "created Reference to name: '"
                                        << _targetName << "' ("
                                        << (const void *)_targetName.data()
                                        << ", " << _targetName.size() << ")\n");
      setKindNamespace(_mappedKind.ns);
      setKindArch(_mappedKind.arch);
      setKindValue(_mappedKind.value);
      return this;
    }

    void bind(const RefNameResolver &);
    static StringRef targetName(IO &io, const lld::Reference *ref);

    uint64_t offsetInAtom() const override { return _offset; }
    const lld::Atom *target() const override { return _target; }
    Addend addend() const override { return _addend; }
    void setAddend(Addend a) override { _addend = a; }
    void setTarget(const lld::Atom *a) override { _target = a; }

    const lld::Atom *_target;
    StringRef        _targetName;
    uint32_t         _offset;
    Addend           _addend;
    RefKind          _mappedKind;
    uint32_t         _tag;
  };

  static void mapping(IO &io, const lld::Reference *&ref) {
    YamlContext *info = reinterpret_cast<YamlContext *>(io.getContext());
    MappingNormalizationHeap<NormalizedReference, const lld::Reference *> keys(
        io, ref, &info->_file->allocator());

    io.mapRequired("kind",   keys->_mappedKind);
    io.mapOptional("offset", keys->_offset);
    io.mapOptional("target", keys->_targetName);
    io.mapOptional("addend", keys->_addend, (lld::Reference::Addend)0);
    io.mapOptional("tag",    keys->_tag, 0u);
  }
};

// YAML conversion for const lld::DefinedAtom*
template <> struct MappingTraits<const lld::DefinedAtom *> {

  class NormalizedAtom : public lld::DefinedAtom {
  public:
    NormalizedAtom(IO &io)
        : _file(fileFromContext(io)), _contentType(), _alignment(1) {
      static uint32_t ordinalCounter = 1;
      _ordinal = ordinalCounter++;
    }

    NormalizedAtom(IO &io, const lld::DefinedAtom *atom)
        : _file(fileFromContext(io)), _name(atom->name()),
          _scope(atom->scope()), _interpose(atom->interposable()),
          _merge(atom->merge()), _contentType(atom->contentType()),
          _alignment(atom->alignment()), _sectionChoice(atom->sectionChoice()),
          _deadStrip(atom->deadStrip()), _dynamicExport(atom->dynamicExport()),
          _codeModel(atom->codeModel()),
          _permissions(atom->permissions()), _size(atom->size()),
          _sectionName(atom->customSectionName()),
          _sectionSize(atom->sectionSize()) {
      for (const lld::Reference *r : *atom)
        _references.push_back(r);
      if (!atom->occupiesDiskSpace())
        return;
      ArrayRef<uint8_t> cont = atom->rawContent();
      _content.reserve(cont.size());
      for (uint8_t x : cont)
        _content.push_back(x);
    }

    ~NormalizedAtom() override = default;

    const lld::DefinedAtom *denormalize(IO &io) {
      YamlContext *info = reinterpret_cast<YamlContext *>(io.getContext());
      assert(info != nullptr);
      typedef MappingTraits<const lld::File *>::NormalizedFile NormalizedFile;
      NormalizedFile *f = reinterpret_cast<NormalizedFile *>(info->_file);
      if (!_name.empty())
        _name = f->copyString(_name);
      if (!_refName.empty())
        _refName = f->copyString(_refName);
      if (!_sectionName.empty())
        _sectionName = f->copyString(_sectionName);
      DEBUG_WITH_TYPE("WriterYAML",
                      llvm::dbgs() << "created DefinedAtom named: '" << _name
                                   << "' (" << (const void *)_name.data()
                                   << ", " << _name.size() << ")\n");
      return this;
    }

    void bind(const RefNameResolver &);

    // Extract current File object from YAML I/O parsing context
    const lld::File &fileFromContext(IO &io) {
      YamlContext *info = reinterpret_cast<YamlContext *>(io.getContext());
      assert(info != nullptr);
      assert(info->_file != nullptr);
      return *info->_file;
    }

    const lld::File &file() const override { return _file; }
    StringRef name() const override { return _name; }
    uint64_t size() const override { return _size; }
    Scope scope() const override { return _scope; }
    Interposable interposable() const override { return _interpose; }
    Merge merge() const override { return _merge; }
    ContentType contentType() const override { return _contentType; }
    Alignment alignment() const override { return _alignment; }
    SectionChoice sectionChoice() const override { return _sectionChoice; }
    StringRef customSectionName() const override { return _sectionName; }
    uint64_t sectionSize() const override { return _sectionSize; }
    DeadStripKind deadStrip() const override { return _deadStrip; }
    DynamicExport dynamicExport() const override { return _dynamicExport; }
    CodeModel codeModel() const override { return _codeModel; }
    ContentPermissions permissions() const override { return _permissions; }
    ArrayRef<uint8_t> rawContent() const override {
      if (!occupiesDiskSpace())
        return ArrayRef<uint8_t>();
      return ArrayRef<uint8_t>(
          reinterpret_cast<const uint8_t *>(_content.data()), _content.size());
    }

    uint64_t ordinal() const override { return _ordinal; }

    reference_iterator begin() const override {
      uintptr_t index = 0;
      const void *it = reinterpret_cast<const void *>(index);
      return reference_iterator(*this, it);
    }
    reference_iterator end() const override {
      uintptr_t index = _references.size();
      const void *it = reinterpret_cast<const void *>(index);
      return reference_iterator(*this, it);
    }
    const lld::Reference *derefIterator(const void *it) const override {
      uintptr_t index = reinterpret_cast<uintptr_t>(it);
      assert(index < _references.size());
      return _references[index];
    }
    void incrementIterator(const void *&it) const override {
      uintptr_t index = reinterpret_cast<uintptr_t>(it);
      ++index;
      it = reinterpret_cast<const void *>(index);
    }

    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);
    }

    const lld::File                    &_file;
    StringRef                           _name;
    StringRef                           _refName;
    Scope                               _scope;
    Interposable                        _interpose;
    Merge                               _merge;
    ContentType                         _contentType;
    Alignment                           _alignment;
    SectionChoice                       _sectionChoice;
    DeadStripKind                       _deadStrip;
    DynamicExport                       _dynamicExport;
    CodeModel                           _codeModel;
    ContentPermissions                  _permissions;
    uint32_t                            _ordinal;
    std::vector<ImplicitHex8>           _content;
    uint64_t                            _size;
    StringRef                           _sectionName;
    uint64_t                            _sectionSize;
    std::vector<const lld::Reference *> _references;
  };

  static void mapping(IO &io, const lld::DefinedAtom *&atom) {
    YamlContext *info = reinterpret_cast<YamlContext *>(io.getContext());
    MappingNormalizationHeap<NormalizedAtom, const lld::DefinedAtom *> keys(
        io, atom, &info->_file->allocator());
    if (io.outputting()) {
      // If writing YAML, check if atom needs a ref-name.
      typedef MappingTraits<const lld::File *>::NormalizedFile NormalizedFile;
      assert(info != nullptr);
      NormalizedFile *f = reinterpret_cast<NormalizedFile *>(info->_file);
      assert(f);
      assert(f->_rnb);
      if (f->_rnb->hasRefName(atom)) {
        keys->_refName = f->_rnb->refName(atom);
      }
    }

    io.mapOptional("name",             keys->_name,    StringRef());
    io.mapOptional("ref-name",         keys->_refName, StringRef());
    io.mapOptional("scope",            keys->_scope,
                                         DefinedAtom::scopeTranslationUnit);
    io.mapOptional("type",             keys->_contentType,
                                         DefinedAtom::typeCode);
    io.mapOptional("content",          keys->_content);
    io.mapOptional("size",             keys->_size, (uint64_t)keys->_content.size());
    io.mapOptional("interposable",     keys->_interpose,
                                         DefinedAtom::interposeNo);
    io.mapOptional("merge",            keys->_merge, DefinedAtom::mergeNo);
    io.mapOptional("alignment",        keys->_alignment,
                                         DefinedAtom::Alignment(1));
    io.mapOptional("section-choice",   keys->_sectionChoice,
                                         DefinedAtom::sectionBasedOnContent);
    io.mapOptional("section-name",     keys->_sectionName, StringRef());
    io.mapOptional("section-size",     keys->_sectionSize, (uint64_t)0);
    io.mapOptional("dead-strip",       keys->_deadStrip,
                                         DefinedAtom::deadStripNormal);
    io.mapOptional("dynamic-export",   keys->_dynamicExport,
                                         DefinedAtom::dynamicExportNormal);
    io.mapOptional("code-model",       keys->_codeModel, DefinedAtom::codeNA);
    // default permissions based on content type
    io.mapOptional("permissions",      keys->_permissions,
                                         DefinedAtom::permissions(
                                                          keys->_contentType));
    io.mapOptional("references",       keys->_references);
  }
};

template <> struct MappingTraits<lld::DefinedAtom *> {
  static void mapping(IO &io, lld::DefinedAtom *&atom) {
    const lld::DefinedAtom *atomPtr = atom;
    MappingTraits<const lld::DefinedAtom *>::mapping(io, atomPtr);
    atom = const_cast<lld::DefinedAtom *>(atomPtr);
  }
};

// YAML conversion for const lld::UndefinedAtom*
template <> struct MappingTraits<const lld::UndefinedAtom *> {
  class NormalizedAtom : public lld::UndefinedAtom {
  public:
    NormalizedAtom(IO &io)
        : _file(fileFromContext(io)), _canBeNull(canBeNullNever) {}

    NormalizedAtom(IO &io, const lld::UndefinedAtom *atom)
        : _file(fileFromContext(io)), _name(atom->name()),
          _canBeNull(atom->canBeNull()) {}

    ~NormalizedAtom() override = default;

    const lld::UndefinedAtom *denormalize(IO &io) {
      YamlContext *info = reinterpret_cast<YamlContext *>(io.getContext());
      assert(info != nullptr);
      typedef MappingTraits<const lld::File *>::NormalizedFile NormalizedFile;
      NormalizedFile *f = reinterpret_cast<NormalizedFile *>(info->_file);
      if (!_name.empty())
        _name = f->copyString(_name);

      DEBUG_WITH_TYPE("WriterYAML",
                      llvm::dbgs() << "created UndefinedAtom named: '" << _name
                      << "' (" << (const void *)_name.data() << ", "
                      << _name.size() << ")\n");
      return this;
    }

    // Extract current File object from YAML I/O parsing context
    const lld::File &fileFromContext(IO &io) {
      YamlContext *info = reinterpret_cast<YamlContext *>(io.getContext());
      assert(info != nullptr);
      assert(info->_file != nullptr);
      return *info->_file;
    }

    const lld::File &file() const override { return _file; }
    StringRef name() const override { return _name; }
    CanBeNull canBeNull() const override { return _canBeNull; }

    const lld::File     &_file;
    StringRef            _name;
    CanBeNull            _canBeNull;
  };

  static void mapping(IO &io, const lld::UndefinedAtom *&atom) {
    YamlContext *info = reinterpret_cast<YamlContext *>(io.getContext());
    MappingNormalizationHeap<NormalizedAtom, const lld::UndefinedAtom *> keys(
        io, atom, &info->_file->allocator());

    io.mapRequired("name",        keys->_name);
    io.mapOptional("can-be-null", keys->_canBeNull,
                                  lld::UndefinedAtom::canBeNullNever);
  }
};

template <> struct MappingTraits<lld::UndefinedAtom *> {
  static void mapping(IO &io, lld::UndefinedAtom *&atom) {
    const lld::UndefinedAtom *atomPtr = atom;
    MappingTraits<const lld::UndefinedAtom *>::mapping(io, atomPtr);
    atom = const_cast<lld::UndefinedAtom *>(atomPtr);
  }
};

// YAML conversion for const lld::SharedLibraryAtom*
template <> struct MappingTraits<const lld::SharedLibraryAtom *> {
  class NormalizedAtom : public lld::SharedLibraryAtom {
  public:
    NormalizedAtom(IO &io)
        : _file(fileFromContext(io)), _canBeNull(false),
          _type(Type::Unknown), _size(0) {}

    NormalizedAtom(IO &io, const lld::SharedLibraryAtom *atom)
        : _file(fileFromContext(io)), _name(atom->name()),
          _loadName(atom->loadName()), _canBeNull(atom->canBeNullAtRuntime()),
          _type(atom->type()), _size(atom->size()) {}

    ~NormalizedAtom() override = default;

    const lld::SharedLibraryAtom *denormalize(IO &io) {
      YamlContext *info = reinterpret_cast<YamlContext *>(io.getContext());
      assert(info != nullptr);
      typedef MappingTraits<const lld::File *>::NormalizedFile NormalizedFile;
      NormalizedFile *f = reinterpret_cast<NormalizedFile *>(info->_file);
      if (!_name.empty())
        _name = f->copyString(_name);
      if (!_loadName.empty())
        _loadName = f->copyString(_loadName);

      DEBUG_WITH_TYPE("WriterYAML",
                      llvm::dbgs() << "created SharedLibraryAtom named: '"
                                   << _name << "' ("
                                   << (const void *)_name.data()
                                   << ", " << _name.size() << ")\n");
      return this;
    }

    // Extract current File object from YAML I/O parsing context
    const lld::File &fileFromContext(IO &io) {
      YamlContext *info = reinterpret_cast<YamlContext *>(io.getContext());
      assert(info != nullptr);
      assert(info->_file != nullptr);
      return *info->_file;
    }

    const lld::File &file() const override { return _file; }
    StringRef name() const override { return _name; }
    StringRef loadName() const override { return _loadName; }
    bool canBeNullAtRuntime() const override { return _canBeNull; }
    Type type() const override { return _type; }
    uint64_t size() const override { return _size; }

    const lld::File &_file;
    StringRef        _name;
    StringRef        _loadName;
    ShlibCanBeNull   _canBeNull;
    Type             _type;
    uint64_t         _size;
  };

  static void mapping(IO &io, const lld::SharedLibraryAtom *&atom) {

    YamlContext *info = reinterpret_cast<YamlContext *>(io.getContext());
    MappingNormalizationHeap<NormalizedAtom, const lld::SharedLibraryAtom *>
    keys(io, atom, &info->_file->allocator());

    io.mapRequired("name",        keys->_name);
    io.mapOptional("load-name",   keys->_loadName);
    io.mapOptional("can-be-null", keys->_canBeNull, (ShlibCanBeNull) false);
    io.mapOptional("type",        keys->_type, SharedLibraryAtom::Type::Code);
    io.mapOptional("size",        keys->_size, uint64_t(0));
  }
};

template <> struct MappingTraits<lld::SharedLibraryAtom *> {
  static void mapping(IO &io, lld::SharedLibraryAtom *&atom) {
    const lld::SharedLibraryAtom *atomPtr = atom;
    MappingTraits<const lld::SharedLibraryAtom *>::mapping(io, atomPtr);
    atom = const_cast<lld::SharedLibraryAtom *>(atomPtr);
  }
};

// YAML conversion for const lld::AbsoluteAtom*
template <> struct MappingTraits<const lld::AbsoluteAtom *> {
  class NormalizedAtom : public lld::AbsoluteAtom {
  public:
    NormalizedAtom(IO &io)
        : _file(fileFromContext(io)), _scope(), _value(0) {}

    NormalizedAtom(IO &io, const lld::AbsoluteAtom *atom)
        : _file(fileFromContext(io)), _name(atom->name()),
          _scope(atom->scope()), _value(atom->value()) {}

    ~NormalizedAtom() override = default;

    const lld::AbsoluteAtom *denormalize(IO &io) {
      YamlContext *info = reinterpret_cast<YamlContext *>(io.getContext());
      assert(info != nullptr);
      typedef MappingTraits<const lld::File *>::NormalizedFile NormalizedFile;
      NormalizedFile *f = reinterpret_cast<NormalizedFile *>(info->_file);
      if (!_name.empty())
        _name = f->copyString(_name);

      DEBUG_WITH_TYPE("WriterYAML",
                      llvm::dbgs() << "created AbsoluteAtom named: '" << _name
                                   << "' (" << (const void *)_name.data()
                                   << ", " << _name.size() << ")\n");
      return this;
    }

    // Extract current File object from YAML I/O parsing context
    const lld::File &fileFromContext(IO &io) {
      YamlContext *info = reinterpret_cast<YamlContext *>(io.getContext());
      assert(info != nullptr);
      assert(info->_file != nullptr);
      return *info->_file;
    }

    const lld::File &file() const override { return _file; }
    StringRef name() const override { return _name; }
    uint64_t value() const override { return _value; }
    Scope scope() const override { return _scope; }

    const lld::File &_file;
    StringRef        _name;
    StringRef        _refName;
    Scope            _scope;
    Hex64            _value;
  };

  static void mapping(IO &io, const lld::AbsoluteAtom *&atom) {
    YamlContext *info = reinterpret_cast<YamlContext *>(io.getContext());
    MappingNormalizationHeap<NormalizedAtom, const lld::AbsoluteAtom *> keys(
        io, atom, &info->_file->allocator());

    if (io.outputting()) {
      typedef MappingTraits<const lld::File *>::NormalizedFile NormalizedFile;
      YamlContext *info = reinterpret_cast<YamlContext *>(io.getContext());
      assert(info != nullptr);
      NormalizedFile *f = reinterpret_cast<NormalizedFile *>(info->_file);
      assert(f);
      assert(f->_rnb);
      if (f->_rnb->hasRefName(atom)) {
        keys->_refName = f->_rnb->refName(atom);
      }
    }

    io.mapRequired("name",     keys->_name);
    io.mapOptional("ref-name", keys->_refName, StringRef());
    io.mapOptional("scope",    keys->_scope);
    io.mapRequired("value",    keys->_value);
  }
};

template <> struct MappingTraits<lld::AbsoluteAtom *> {
  static void mapping(IO &io, lld::AbsoluteAtom *&atom) {
    const lld::AbsoluteAtom *atomPtr = atom;
    MappingTraits<const lld::AbsoluteAtom *>::mapping(io, atomPtr);
    atom = const_cast<lld::AbsoluteAtom *>(atomPtr);
  }
};

} // end namespace llvm
} // end namespace yaml

RefNameResolver::RefNameResolver(const lld::File *file, IO &io) : _io(io) {
  typedef MappingTraits<const lld::DefinedAtom *>::NormalizedAtom
  NormalizedAtom;
  for (const lld::DefinedAtom *a : file->defined()) {
    const auto *na = (const NormalizedAtom *)a;
    if (!na->_refName.empty())
      add(na->_refName, a);
    else if (!na->_name.empty())
      add(na->_name, a);
  }

  for (const lld::UndefinedAtom *a : file->undefined())
    add(a->name(), a);

  for (const lld::SharedLibraryAtom *a : file->sharedLibrary())
    add(a->name(), a);

  typedef MappingTraits<const lld::AbsoluteAtom *>::NormalizedAtom NormAbsAtom;
  for (const lld::AbsoluteAtom *a : file->absolute()) {
    const auto *na = (const NormAbsAtom *)a;
    if (na->_refName.empty())
      add(na->_name, a);
    else
      add(na->_refName, a);
  }
}

inline const lld::File *
MappingTraits<const lld::File *>::NormalizedFile::denormalize(IO &io) {
  typedef MappingTraits<const lld::DefinedAtom *>::NormalizedAtom
  NormalizedAtom;

  RefNameResolver nameResolver(this, io);
  // Now that all atoms are parsed, references can be bound.
  for (const lld::DefinedAtom *a : this->defined()) {
    auto *normAtom = (NormalizedAtom *)const_cast<DefinedAtom *>(a);
    normAtom->bind(nameResolver);
  }

  return this;
}

inline void MappingTraits<const lld::DefinedAtom *>::NormalizedAtom::bind(
    const RefNameResolver &resolver) {
  typedef MappingTraits<const lld::Reference *>::NormalizedReference
  NormalizedReference;
  for (const lld::Reference *ref : _references) {
    auto *normRef = (NormalizedReference *)const_cast<Reference *>(ref);
    normRef->bind(resolver);
  }
}

inline void MappingTraits<const lld::Reference *>::NormalizedReference::bind(
    const RefNameResolver &resolver) {
  _target = resolver.lookup(_targetName);
}

inline StringRef
MappingTraits<const lld::Reference *>::NormalizedReference::targetName(
    IO &io, const lld::Reference *ref) {
  if (ref->target() == nullptr)
    return StringRef();
  YamlContext *info = reinterpret_cast<YamlContext *>(io.getContext());
  assert(info != nullptr);
  typedef MappingTraits<const lld::File *>::NormalizedFile NormalizedFile;
  NormalizedFile *f = reinterpret_cast<NormalizedFile *>(info->_file);
  RefNameBuilder &rnb = *f->_rnb;
  if (rnb.hasRefName(ref->target()))
    return rnb.refName(ref->target());
  return ref->target()->name();
}

namespace lld {
namespace yaml {

class Writer : public lld::Writer {
public:
  Writer(const LinkingContext &context) : _ctx(context) {}

  llvm::Error writeFile(const lld::File &file, StringRef outPath) override {
    // Create stream to path.
    std::error_code ec;
    llvm::raw_fd_ostream out(outPath, ec, llvm::sys::fs::F_Text);
    if (ec)
      return llvm::errorCodeToError(ec);

    // Create yaml Output writer, using yaml options for context.
    YamlContext yamlContext;
    yamlContext._ctx = &_ctx;
    yamlContext._registry = &_ctx.registry();
    llvm::yaml::Output yout(out, &yamlContext);

    // Write yaml output.
    const lld::File *fileRef = &file;
    yout << fileRef;

    return llvm::Error::success();
  }

private:
  const LinkingContext &_ctx;
};

} // end namespace yaml

namespace {

/// Handles !native tagged yaml documents.
class NativeYamlIOTaggedDocumentHandler : public YamlIOTaggedDocumentHandler {
  bool handledDocTag(llvm::yaml::IO &io, const lld::File *&file) const override {
    if (io.mapTag("!native")) {
      MappingTraits<const lld::File *>::mappingAtoms(io, file);
      return true;
    }
    return false;
  }
};

/// Handles !archive tagged yaml documents.
class ArchiveYamlIOTaggedDocumentHandler : public YamlIOTaggedDocumentHandler {
  bool handledDocTag(llvm::yaml::IO &io, const lld::File *&file) const override {
    if (io.mapTag("!archive")) {
      MappingTraits<const lld::File *>::mappingArchive(io, file);
      return true;
    }
    return false;
  }
};

class YAMLReader : public Reader {
public:
  YAMLReader(const Registry &registry) : _registry(registry) {}

  bool canParse(file_magic magic, MemoryBufferRef mb) const override {
    StringRef name = mb.getBufferIdentifier();
    return name.endswith(".objtxt") || name.endswith(".yaml");
  }

  ErrorOr<std::unique_ptr<File>>
  loadFile(std::unique_ptr<MemoryBuffer> mb,
           const class Registry &) const override {
    // Create YAML Input Reader.
    YamlContext yamlContext;
    yamlContext._registry = &_registry;
    yamlContext._path = mb->getBufferIdentifier();
    llvm::yaml::Input yin(mb->getBuffer(), &yamlContext);

    // Fill vector with File objects created by parsing yaml.
    std::vector<const lld::File *> createdFiles;
    yin >> createdFiles;
    assert(createdFiles.size() == 1);

    // Error out now if there were parsing errors.
    if (yin.error())
      return make_error_code(lld::YamlReaderError::illegal_value);

    std::shared_ptr<MemoryBuffer> smb(mb.release());
    const File *file = createdFiles[0];
    // Note: loadFile() should return vector of *const* File
    File *f = const_cast<File *>(file);
    f->setLastError(std::error_code());
    f->setSharedMemoryBuffer(smb);
    return std::unique_ptr<File>(f);
  }

private:
  const Registry &_registry;
};

} // end anonymous namespace

void Registry::addSupportYamlFiles() {
  add(std::unique_ptr<Reader>(new YAMLReader(*this)));
  add(std::unique_ptr<YamlIOTaggedDocumentHandler>(
                                    new NativeYamlIOTaggedDocumentHandler()));
  add(std::unique_ptr<YamlIOTaggedDocumentHandler>(
                                    new ArchiveYamlIOTaggedDocumentHandler()));
}

std::unique_ptr<Writer> createWriterYAML(const LinkingContext &context) {
  return std::unique_ptr<Writer>(new lld::yaml::Writer(context));
}

} // end namespace lld
