// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#ifndef RUNTIME_VM_IMAGE_SNAPSHOT_H_
#define RUNTIME_VM_IMAGE_SNAPSHOT_H_

#include <memory>
#include <utility>

#include "platform/assert.h"
#include "platform/utils.h"
#include "vm/allocation.h"
#include "vm/compiler/runtime_api.h"
#include "vm/datastream.h"
#include "vm/globals.h"
#include "vm/growable_array.h"
#include "vm/hash_map.h"
#include "vm/object.h"
#include "vm/reusable_handles.h"
#include "vm/so_writer.h"
#include "vm/type_testing_stubs.h"
#include "vm/v8_snapshot_writer.h"

namespace dart {

// Forward declarations.
class BitsContainer;
class Code;
class Dwarf;
class Instructions;
class Object;
class SharedObjectWriter;

class Image : ValueObject {
 public:
  explicit Image(const void* raw_memory)
      : Image(reinterpret_cast<uword>(raw_memory)) {}
  explicit Image(const uword raw_memory)
      : raw_memory_(raw_memory),
        snapshot_size_(FieldValue(raw_memory, HeaderField::ImageSize)),
        extra_info_(ExtraInfo(raw_memory_, snapshot_size_)) {
    ASSERT(Utils::IsAligned(raw_memory, kObjectStartAlignment));
  }

  // Even though an Image is read-only memory, we must return a void* here.
  // All objects in an Image are pre-marked, though, so the GC will not attempt
  // to change the returned memory.
  void* object_start() const {
    return reinterpret_cast<void*>(raw_memory_ + kHeaderSize);
  }

  uword object_size() const { return snapshot_size_ - kHeaderSize; }

  bool contains(uword address) const {
    uword start = reinterpret_cast<uword>(object_start());
    return address >= start && (address - start < object_size());
  }

  // Returns the address of the BSS section, or nullptr if one is not available.
  // Only has meaning for instructions images from precompiled snapshots.
  uword* bss() const;

  // Returns the relocated address of the isolate's instructions, or 0 if
  // one is not available. Only has meaning for instructions images from
  // precompiled snapshots.
  uword instructions_relocated_address() const;

  // Returns the GNU build ID, or nullptr if not available. See
  // build_id_length() for the length of the returned buffer. Only has meaning
  // for instructions images from precompiled snapshots.
  const uint8_t* build_id() const;

  // Returns the length of the GNU build ID returned by build_id(). Only has
  // meaning for instructions images from precompiled snapshots.
  intptr_t build_id_length() const;

  // Returns whether this instructions section was directly compiled to a shared
  // object. Only valid for instructions images from precompiled snapshots.
  bool compiled_to_shared_object() const;

  // For snapshots directly compiled to a shared object, returns a pointer to
  // the beginning of the shared object in memory. Otherwise returns nullptr.
  const uint8_t* shared_object_start() const;

  // Returns whether this instructions section was directly compiled to ELF.
  // Only valid for instructions images from precompiled snapshots.
  bool compiled_to_elf() const;

  // Returns whether this instructions section was directly compiled to MachO.
  // Only valid for instructions images from precompiled snapshots.
  bool compiled_to_macho() const;

  // Constants used to denote special values for the offsets in the Image
  // object header and the fields of the InstructionsSection object.
  static constexpr intptr_t kNoInstructionsSection = 0;
  static constexpr intptr_t kNoBssSection = 0;
  static constexpr intptr_t kNoRelocatedAddress = 0;
  static constexpr intptr_t kNoBuildId = 0;

 private:
  // For snapshots directly compiled to a shared object, returns a pointer to
  // the beginning of the build id container. Otherwise returns nullptr;
  const void* build_id_start() const;

  // Word-sized fields in an Image object header.
  enum class HeaderField : intptr_t {
    // The size of the image (total of header and payload).
    ImageSize,
    // The offset of the InstructionsSection object in the image. Note this
    // offset is from the start of the _image_, _not_ from its payload start,
    // so we can detect images without an InstructionsSection by a 0 value here.
    InstructionsSectionOffset,
    // If adding more fields, updating kHeaderFields below. (However, more
    // fields _can't_ be added on 64-bit architectures, see the restrictions
    // on kHeaderSize below.)
  };

  // Number of fields described by the HeaderField enum.
  static constexpr intptr_t kHeaderFields =
      static_cast<intptr_t>(HeaderField::InstructionsSectionOffset) + 1;

  static uword FieldValue(uword raw_memory, HeaderField field) {
    return reinterpret_cast<const uword*>(
        raw_memory)[static_cast<intptr_t>(field)];
  }

  // The size of the Image object header.
  //
  // Note: Image::kHeaderSize is _not_ an architecture-dependent constant,
  // and so there is no compiler::target::Image::kHeaderSize.
  static constexpr intptr_t kHeaderSize = kObjectStartAlignment;
  // Explicitly double-checking kHeaderSize is never changed. Increasing the
  // Image header size would mean objects would not start at a place expected
  // by parts of the VM (like the GC) that use Image pages as Pages.
  static_assert(kHeaderSize == kObjectStartAlignment,
                "Image page cannot be used as Page");
  // Make sure that the number of fields in the Image header fit both on the
  // host and target architectures.
  static_assert(kHeaderFields * kWordSize <= kHeaderSize,
                "Too many fields in Image header for host architecture");
  static_assert(kHeaderFields * compiler::target::kWordSize <= kHeaderSize,
                "Too many fields in Image header for target architecture");

  // We don't use a handle or the tagged pointer because this object cannot be
  // moved in memory by the GC.
  static const UntaggedInstructionsSection* ExtraInfo(const uword raw_memory,
                                                      const uword size);

  // Most internal uses would cast this to uword, so just store it as such.
  const uword raw_memory_;
  const intptr_t snapshot_size_;
  const UntaggedInstructionsSection* const extra_info_;

  // For access to private constants.
  friend class AssemblyImageWriter;
  friend class BlobImageWriter;
  friend class ImageWriter;
  friend class SharedObjectWriter;
  friend class MachOHeader;  // For kHeaderSize.

  DISALLOW_COPY_AND_ASSIGN(Image);
};

class ImageReader : public ZoneObject {
 public:
  ImageReader(const uint8_t* data_image, const uint8_t* instructions_image);

  char* VerifyAlignment() const;

  ONLY_IN_PRECOMPILED(uword GetBareInstructionsAt(uint32_t offset) const);
  ONLY_IN_PRECOMPILED(uword GetBareInstructionsEnd() const);
  InstructionsPtr GetInstructionsAt(uint32_t offset) const;
  ObjectPtr GetObjectAt(uint32_t offset) const;

 private:
  const uint8_t* data_image_;
  const uint8_t* instructions_image_;

  DISALLOW_COPY_AND_ASSIGN(ImageReader);
};

struct ObjectOffsetPair {
 public:
  ObjectOffsetPair() : ObjectOffsetPair(nullptr, 0) {}
  ObjectOffsetPair(ObjectPtr obj, int32_t off) : object(obj), offset(off) {}

  ObjectPtr object;
  int32_t offset;
};

class ObjectOffsetTrait {
 public:
  // Typedefs needed for the DirectChainedHashMap template.
  typedef ObjectPtr Key;
  typedef int32_t Value;
  typedef ObjectOffsetPair Pair;

  static Key KeyOf(Pair kv) { return kv.object; }
  static Value ValueOf(Pair kv) { return kv.offset; }
  static uword Hash(Key key);
  static inline bool IsKeyEqual(Pair pair, Key key);
};

typedef DirectChainedHashMap<ObjectOffsetTrait> ObjectOffsetMap;

// A command which instructs the image writer to emit something into the ".text"
// segment.
//
// For now this supports
//
//   * emitting the instructions of a [Code] object
//   * emitting a trampoline of a certain size
//   * emitting a padding of a certain size
//
struct ImageWriterCommand {
  enum Opcode {
    InsertInstructionOfCode,
    InsertBytesOfTrampoline,
    InsertPadding,
  };

  ImageWriterCommand(intptr_t expected_offset, CodePtr code)
      : expected_offset(expected_offset),
        op(ImageWriterCommand::InsertInstructionOfCode),
        insert_instruction_of_code({code}) {}

  ImageWriterCommand(intptr_t expected_offset,
                     uint8_t* trampoline_bytes,
                     intptr_t trampoline_length)
      : expected_offset(expected_offset),
        op(ImageWriterCommand::InsertBytesOfTrampoline),
        insert_trampoline_bytes({trampoline_bytes, trampoline_length}) {}

  ImageWriterCommand(intptr_t expected_offset, intptr_t padding_length)
      : expected_offset(expected_offset),
        op(ImageWriterCommand::InsertPadding),
        insert_padding({padding_length}) {}

  // The offset (relative to the very first [ImageWriterCommand]) we expect
  // this [ImageWriterCommand] to have.
  intptr_t expected_offset;

  Opcode op;
  union {
    struct {
      CodePtr code;
    } insert_instruction_of_code;
    struct {
      uint8_t* buffer;
      intptr_t buffer_length;
    } insert_trampoline_bytes;

    struct {
      intptr_t padding_length;
    } insert_padding;
  };
};

#if defined(DART_PRECOMPILER)
template <typename T>
class Trie : public ZoneObject {
 public:
  // Returns whether [key] is a valid trie key (that is, a C string that
  // contains only characters for which charIndex returns a non-negative value).
  static bool IsValidKey(const char* key) {
    for (intptr_t i = 0; key[i] != '\0'; i++) {
      if (ChildIndex(key[i]) < 0) return false;
    }
    return true;
  }

  // Adds a binding of [key] to [value] in [trie]. Assumes that the string in
  // [key] is a valid trie key and does not already have a value in [trie].
  //
  // If [trie] is nullptr, then a new trie is created and a pointer to the new
  // trie is returned. Otherwise, [trie] will be returned.
  static Trie<T>* AddString(Zone* zone,
                            Trie<T>* trie,
                            const char* key,
                            const T* value);

  // Adds a binding of [key] to [value]. Assumes that the string in [key] is a
  // valid trie key and does not already have a value in this trie.
  void AddString(Zone* zone, const char* key, const T* value) {
    AddString(zone, this, key, value);
  }

  // Looks up the value stored for [key] in [trie]. If one is not found, then
  // nullptr is returned.
  //
  // If [end] is not nullptr, then the longest prefix of [key] that is a valid
  // trie key prefix will be used for the lookup and the value pointed to by
  // [end] is set to the index after that prefix. Otherwise, the whole [key]
  // is used.
  static const T* Lookup(const Trie<T>* trie,
                         const char* key,
                         intptr_t* end = nullptr);

  // Looks up the value stored for [key]. If one is not found, then nullptr is
  // returned.
  //
  // If [end] is not nullptr, then the longest prefix of [key] that is a valid
  // trie key prefix will be used for the lookup and the value pointed to by
  // [end] is set to the index after that prefix. Otherwise, the whole [key]
  // is used.
  const T* Lookup(const char* key, intptr_t* end = nullptr) const {
    return Lookup(this, key, end);
  }

 private:
  // Currently, only the following characters can appear in obfuscated names:
  // '_', '@', '0-9', 'a-z', 'A-Z'
  static constexpr intptr_t kNumValidChars = 64;

  Trie() {
    for (intptr_t i = 0; i < kNumValidChars; i++) {
      children_[i] = nullptr;
    }
  }

  static intptr_t ChildIndex(char c) {
    if (c == '_') return 0;
    if (c == '@') return 1;
    if (c >= '0' && c <= '9') return ('9' - c) + 2;
    if (c >= 'a' && c <= 'z') return ('z' - c) + 12;
    if (c >= 'A' && c <= 'Z') return ('Z' - c) + 38;
    return -1;
  }

  const T* value_ = nullptr;
  Trie<T>* children_[kNumValidChars];
};

template <typename T>
Trie<T>* Trie<T>::AddString(Zone* zone,
                            Trie<T>* trie,
                            const char* key,
                            const T* value) {
  ASSERT(key != nullptr);
  if (trie == nullptr) {
    trie = new (zone) Trie<T>();
  }
  if (*key == '\0') {
    ASSERT(trie->value_ == nullptr);
    trie->value_ = value;
  } else {
    auto const index = ChildIndex(*key);
    ASSERT(index >= 0 && index < kNumValidChars);
    trie->children_[index] =
        AddString(zone, trie->children_[index], key + 1, value);
  }

  return trie;
}

template <typename T>
const T* Trie<T>::Lookup(const Trie<T>* trie, const char* key, intptr_t* end) {
  intptr_t i = 0;
  for (; key[i] != '\0'; i++) {
    auto const index = ChildIndex(key[i]);
    ASSERT(index < kNumValidChars);
    if (index < 0) {
      if (end == nullptr) return nullptr;
      break;
    }
    // Still find the longest valid trie prefix when no stored value.
    if (trie == nullptr) continue;
    trie = trie->children_[index];
  }
  if (end != nullptr) {
    *end = i;
  }
  if (trie == nullptr) return nullptr;
  return trie->value_;
}
#endif

class ImageWriter : public ValueObject {
 public:
#if defined(DART_PRECOMPILER)
  ImageWriter(Thread* thread,
              bool generates_assembly,
              bool needs_unique_names,
              const Trie<const char>* deobfuscation_trie = nullptr);
#else
  ImageWriter(Thread* thread, bool generates_assembly, bool needs_unique_names);
#endif
  virtual ~ImageWriter() {}

  // Alignment constants used in writing shared object or assembly snapshots.

  // BSS sections contain word-sized data.
  static constexpr intptr_t kBssAlignment = compiler::target::kWordSize;
  // ROData sections contain objects wrapped in an Image object.
  static constexpr intptr_t kRODataAlignment = kObjectStartAlignment;
  // Text sections contain objects (even in bare instructions mode) wrapped
  // in an Image object.
  static constexpr intptr_t kTextAlignment = kObjectStartAlignment;

  void ResetOffsets() {
    next_data_offset_ = Image::kHeaderSize;
    next_text_offset_ = Image::kHeaderSize;
#if defined(DART_PRECOMPILER)
    if (FLAG_precompiled_mode) {
      // We reserve space for the initial InstructionsSection object. It is
      // manually serialized since it includes offsets to other snapshot parts.
      // It contains all the payloads which start directly after the header.
      next_text_offset_ += compiler::target::InstructionsSection::HeaderSize();
    }
#endif
    objects_.Clear();
    instructions_.Clear();
  }

  // Will start preparing the ".text" segment by interpreting the provided
  // [ImageWriterCommand]s.
  void PrepareForSerialization(GrowableArray<ImageWriterCommand>* commands);

  bool IsROSpace() const {
    return offset_space_ == IdSpace::kVmData ||
           offset_space_ == IdSpace::kVmText ||
           offset_space_ == IdSpace::kIsolateData ||
           offset_space_ == IdSpace::kIsolateText;
  }
  int32_t GetTextOffsetFor(InstructionsPtr instructions, CodePtr code);
#if defined(SNAPSHOT_BACKTRACE)
  uint32_t GetDataOffsetFor(ObjectPtr raw_object, ObjectPtr raw_parent);
#else
  uint32_t GetDataOffsetFor(ObjectPtr raw_object);
#endif

  uint32_t AddBytesToData(uint8_t* bytes, intptr_t length);

  void Write(NonStreamingWriteStream* clustered_stream, bool vm);
  intptr_t data_size() const { return next_data_offset_; }
  intptr_t text_size() const { return next_text_offset_; }
  intptr_t GetTextObjectCount() const;
  void GetTrampolineInfo(intptr_t* count, intptr_t* size) const;

  void DumpStatistics();

  void SetProfileWriter(V8SnapshotProfileWriter* profile_writer) {
    profile_writer_ = profile_writer;
  }

  void ClearProfileWriter() { profile_writer_ = nullptr; }

  void TraceInstructions(const Instructions& instructions);

  static intptr_t SizeInSnapshot(ObjectPtr object);
  static intptr_t SizeInSnapshot(const Object& object) {
    return SizeInSnapshot(object.ptr());
  }

  // Returns nullptr if there is no profile writer.
  const char* ObjectTypeForProfile(const Object& object) const;
  static const char* TagObjectTypeAsReadOnly(Zone* zone, const char* type);

  enum class ProgramSection {
    Text,     // Instructions.
    Data,     // Read-only data.
    Bss,      // Statically allocated variables initialized at load.
    BuildId,  // GNU build ID (when applicable)
    // Adjust kNumProgramSections below to use last enum value added.
  };

  static constexpr intptr_t kNumProgramSections =
      static_cast<int>(ProgramSection::BuildId) + 1;

#if defined(DART_PRECOMPILER)
  // Returns a predetermined label for the given section in the VM isolate
  // (if vm is true) or application isolate (otherwise) section. Some sections
  // are shared by both.
  static constexpr intptr_t SectionLabel(ProgramSection section, bool vm) {
    switch (section) {
      case ProgramSection::Text:
        return SharedObjectWriter::kIsolateInstructionsLabel;
      case ProgramSection::Data:
        return SharedObjectWriter::kIsolateDataLabel;
      case ProgramSection::Bss:
        return SharedObjectWriter::kIsolateBssLabel;
      case ProgramSection::BuildId:
        // Both vm and isolate share the build id section.
        return SharedObjectWriter::kBuildIdLabel;
    }
    UNREACHABLE();
    return 0;
  }

  static Trie<const char>* CreateReverseObfuscationTrie(Thread* thread);
  static const char* Deobfuscate(Zone* zone,
                                 const Trie<const char>* trie,
                                 const char* str);
#endif

  virtual void Finalize() = 0;

 protected:
  virtual void WriteBss(bool vm) = 0;
  virtual void WriteROData(NonStreamingWriteStream* clustered_stream, bool vm);
  void WriteText(bool vm);

  // Returns the standard Dart dynamic symbol name for the given VM isolate (if
  // vm is true) or application isolate (otherwise) section. Some sections are
  // shared by both.
  static const char* SectionSymbol(ProgramSection section, bool vm);

  static uword GetMarkedTags(classid_t cid,
                             intptr_t size,
                             bool is_canonical = false,
                             bool is_shallow_immutable = false,
                             bool is_deeply_immutable = false);
  static uword GetMarkedTags(const Object& obj);

  void DumpInstructionStats();
  void DumpInstructionsSizes();

  struct InstructionsData {
    InstructionsData(InstructionsPtr insns, CodePtr code, intptr_t text_offset)
        : raw_insns_(insns),
          raw_code_(code),
          text_offset_(text_offset),
          trampoline_bytes(nullptr),
          trampoline_length(0) {}

    InstructionsData(uint8_t* trampoline_bytes,
                     intptr_t trampoline_length,
                     intptr_t text_offset)
        : raw_insns_(nullptr),
          raw_code_(nullptr),
          text_offset_(text_offset),
          trampoline_bytes(trampoline_bytes),
          trampoline_length(trampoline_length) {}

    union {
      InstructionsPtr raw_insns_;
      const Instructions* insns_;
    };
    union {
      CodePtr raw_code_;
      const Code* code_;
    };
    intptr_t text_offset_;

    uint8_t* trampoline_bytes;
    intptr_t trampoline_length;
  };

  struct ObjectData {
#if defined(SNAPSHOT_BACKTRACE)
    explicit ObjectData(ObjectPtr raw_obj, ObjectPtr raw_parent)
        : raw_obj(raw_obj),
          raw_parent(raw_parent),
          flags(IsObjectField::encode(true) |
                IsOriginalObjectField::encode(true)) {}
    ObjectData(uint8_t* buf, intptr_t length)
        : bytes({buf, length}),
          raw_parent(Object::null()),
          flags(IsObjectField::encode(false) |
                IsOriginalObjectField::encode(false)) {}
#else
    explicit ObjectData(ObjectPtr raw_obj)
        : raw_obj(raw_obj),
          flags(IsObjectField::encode(true) |
                IsOriginalObjectField::encode(true)) {}
    ObjectData(uint8_t* buf, intptr_t length)
        : bytes({buf, length}),
          flags(IsObjectField::encode(false) |
                IsOriginalObjectField::encode(false)) {}
#endif

    union {
      struct {
        uint8_t* buf;
        intptr_t length;
      } bytes;
      ObjectPtr raw_obj;
      const Object* obj;
    };
#if defined(SNAPSHOT_BACKTRACE)
    union {
      ObjectPtr raw_parent;
      const Object* parent;
    };
#endif
    uint8_t flags;

    bool is_object() const { return IsObjectField::decode(flags); }
    bool is_original_object() const {
      return IsOriginalObjectField::decode(flags);
    }

    void set_is_object(bool value) {
      flags = IsObjectField::update(value, flags);
    }

    using IsObjectField = BitField<uint8_t, bool>;
    using IsOriginalObjectField =
        BitField<uint8_t, bool, IsObjectField::kNextBit>;
  };

  // Methods abstracting out the particulars of the underlying concrete writer.

  // Marks the entrance into a particular ProgramSection for either the VM
  // isolate (if vm is true) or application isolate (if not). Returns false if
  // this section should not be written.
  virtual bool EnterSection(ProgramSection name,
                            bool vm,
                            intptr_t alignment,
                            intptr_t* alignment_padding = nullptr) = 0;
  // Marks the exit from a particular ProgramSection, allowing subclasses to
  // do any post-writing work.
  virtual void ExitSection(ProgramSection name, bool vm, intptr_t size) = 0;
  // Writes a prologue to the text section that describes how to interpret
  // Dart stack frames using DWARF's Call Frame Information (CFI).
  virtual void FrameUnwindPrologue() = 0;
  // Writes an epilogue to the text section that marks the end of instructions
  // covered by the CFI information in the prologue.
  virtual void FrameUnwindEpilogue() = 0;
  // Writes a target uword-sized value to the section contents.
  virtual intptr_t WriteTargetWord(word value) = 0;
  // Writes a sequence of bytes of length [size] from address [bytes] to the
  // section contents.
  virtual intptr_t WriteBytes(const void* bytes, intptr_t size) = 0;
  // Pads the section contents to a given alignment with zeroes.
  virtual intptr_t Align(intptr_t alignment,
                         intptr_t offset,
                         intptr_t position) = 0;
#if defined(DART_PRECOMPILER)
  // Writes a target word-sized value that depends on the final relocated
  // addresses of the sections named by the two symbols. If T is the final
  // relocated address of the target section and S is the final relocated
  // address of the source, the final value is:
  //   (T + target_offset + target_addend) - (S + source_offset)
  virtual intptr_t Relocation(intptr_t section_offset,
                              intptr_t source_label,
                              intptr_t source_offset,
                              intptr_t target_label,
                              intptr_t target_offset) = 0;
  // Writes a target word-sized value that contains the relocated address
  // pointed to by the given symbol.
  virtual intptr_t RelocatedAddress(intptr_t section_offset,
                                    intptr_t label) = 0;
  // Creates a static symbol for the given Code object when appropriate.
  virtual void AddCodeSymbol(const Code& code,
                             const char* symbol,
                             intptr_t section_offset) = 0;
  // Creates a static symbol for a read-only data object when appropriate.
  virtual void AddDataSymbol(const char* symbol,
                             intptr_t section_offset,
                             size_t size) = 0;

  // Overloaded convenience versions of the above virtual methods.

  // An overload of Relocation where the target and source offsets and
  // target addend are 0.
  intptr_t Relocation(intptr_t section_offset,
                      intptr_t source_label,
                      intptr_t target_label) {
    return Relocation(section_offset, source_label, 0, target_label, 0);
  }
#endif
  // Writes a fixed-sized value of type T to the section contents.
  template <typename T>
  intptr_t WriteFixed(T value) {
    return WriteBytes(&value, sizeof(value));
  }
  // Like Align, but instead of padding with zeroes, the appropriate break
  // instruction for the target architecture is used.
  intptr_t AlignWithBreakInstructions(intptr_t alignment, intptr_t offset);

  Thread* const thread_;
  Zone* const zone_;
  intptr_t next_data_offset_;
  intptr_t next_text_offset_;
  GrowableArray<ObjectData> objects_;
  GrowableArray<InstructionsData> instructions_;

#if defined(DART_PRECOMPILER)
  class SnapshotTextObjectNamer : ValueObject {
   public:
    explicit SnapshotTextObjectNamer(Zone* zone,
                                     const Trie<const char>* deobfuscation_trie,
                                     bool for_assembly,
                                     bool create_unique_names)
        : zone_(ASSERT_NOTNULL(zone)),
          deobfuscation_trie_(deobfuscation_trie),
          lib_(Library::Handle(zone)),
          cls_(Class::Handle(zone)),
          parent_(Function::Handle(zone)),
          owner_(Object::Handle(zone)),
          string_(String::Handle(zone)),
          insns_(Instructions::Handle(zone)),
          store_(IsolateGroup::Current()->object_store()),
          for_assembly_(for_assembly),
          create_unique_names_(create_unique_names),
          usage_count_(zone) {}

    const char* StubNameForType(const AbstractType& type) const;

    // Returns a unique name for text data to use in symbols. The name is
    // not assembly-safe and must be appropriately quoted in assembly output.
    // Assumes that code in the InstructionsData has been allocated a handle.
    const char* SnapshotNameFor(const InstructionsData& data);
    // Returns a unique name for read-only data to use in symbols. The name is
    // not assembly-safe and must be appropriately quoted in assembly output.
    // Assumes that the ObjectData has already been converted to object handles.
    const char* SnapshotNameFor(const ObjectData& data);

   private:
    // Returns a unique name for the given code or read-only data object for use
    // in symbols. The name is not assembly-safe and must be appropriately
    // quoted in assembly output.
    const char* SnapshotNameFor(const Object& object);
    // Adds a non-unique name for the given object to the given buffer.
    void AddNonUniqueNameFor(BaseTextBuffer* buffer, const Object& object);
    // Modifies the symbol name in the buffer as needed for assembly use.
    void ModifyForAssembly(BaseTextBuffer* buffer);
    // Ensures the final symbol name is unique.
    void EnsureUniqueName(BaseTextBuffer* buffer);

    Zone* const zone_;
    const Trie<const char>* const deobfuscation_trie_;
    Library& lib_;
    Class& cls_;
    Function& parent_;
    Object& owner_;
    String& string_;
    Instructions& insns_;
    ObjectStore* const store_;
    // Avoids naming conventions that have meaning to the assembler.
    bool for_assembly_;
    // Used to decide whether we need to add a uniqueness suffix.
    bool create_unique_names_;
    CStringIntMap usage_count_;

    DISALLOW_COPY_AND_ASSIGN(SnapshotTextObjectNamer);
  };

  SnapshotTextObjectNamer namer_;

  intptr_t next_label_ = SharedObjectWriter::kLastReservedLabel + 1;

#endif

  IdSpace offset_space_ = IdSpace::kSnapshot;
  V8SnapshotProfileWriter* profile_writer_ = nullptr;
  const char* const image_type_;
  const char* const instructions_section_type_;
  const char* const instructions_type_;
  const char* const trampoline_type_;
  const char* const padding_type_;

  template <class T>
  friend class TraceImageObjectScope;

 private:
  static intptr_t SizeInSnapshotForBytes(intptr_t length);

  DISALLOW_COPY_AND_ASSIGN(ImageWriter);
};

#if defined(DART_PRECOMPILER)
static_assert(ImageWriter::SectionLabel(ImageWriter::ProgramSection::Bss,
                                        /*vm=*/false) ==
                  SharedObjectWriter::kIsolateBssLabel,
              "unexpected label for isolate BSS section");
static_assert(ImageWriter::SectionLabel(ImageWriter::ProgramSection::BuildId,
                                        /*vm=*/true) ==
                  SharedObjectWriter::kBuildIdLabel,
              "unexpected label for build id section");
static_assert(ImageWriter::SectionLabel(ImageWriter::ProgramSection::BuildId,
                                        /*vm=*/false) ==
                  SharedObjectWriter::kBuildIdLabel,
              "unexpected label for build id section");

#define AutoTraceImage(object, section_offset, stream)                         \
  TraceImageObjectScope<std::remove_pointer<decltype(stream)>::type>           \
      AutoTraceImageObjectScopeVar##__COUNTER__(this, section_offset, stream,  \
                                                object);

template <typename T>
class TraceImageObjectScope : ValueObject {
 public:
  TraceImageObjectScope(ImageWriter* writer,
                        intptr_t section_offset,
                        const T* stream,
                        const Object& object)
      : writer_(ASSERT_NOTNULL(writer)),
        stream_(ASSERT_NOTNULL(stream)),
        section_offset_(section_offset),
        start_offset_(stream_->Position() - section_offset),
        object_type_(writer->ObjectTypeForProfile(object)),
        object_name_(object.IsString() ? object.ToCString() : nullptr) {}

  ~TraceImageObjectScope() {
    if (writer_->profile_writer_ == nullptr) return;
    ASSERT(writer_->IsROSpace());
    writer_->profile_writer_->SetObjectTypeAndName(
        {writer_->offset_space_, start_offset_}, object_type_, object_name_);
    writer_->profile_writer_->AttributeBytesTo(
        {writer_->offset_space_, start_offset_},
        stream_->Position() - section_offset_ - start_offset_);
  }

 private:
  ImageWriter* const writer_;
  const T* const stream_;
  const intptr_t section_offset_;
  const intptr_t start_offset_;
  const char* const object_type_;
  const char* const object_name_;

  DISALLOW_COPY_AND_ASSIGN(TraceImageObjectScope);
};

class AssemblyImageWriter : public ImageWriter {
 public:
  AssemblyImageWriter(Thread* thread,
                      BaseWriteStream* stream,
                      const Trie<const char>* deobfuscation_trie = nullptr,
                      bool strip = false,
                      SharedObjectWriter* debug_so = nullptr);
  virtual void Finalize();

 private:
  virtual void WriteBss(bool vm);
  virtual void WriteROData(NonStreamingWriteStream* clustered_stream, bool vm);

  virtual bool EnterSection(ProgramSection section,
                            bool vm,
                            intptr_t alignment,
                            intptr_t* alignment_padding = nullptr);
  virtual void ExitSection(ProgramSection name, bool vm, intptr_t size);
  virtual intptr_t WriteTargetWord(word value);
  virtual intptr_t WriteBytes(const void* bytes, intptr_t size);
  virtual intptr_t Align(intptr_t alignment,
                         intptr_t offset,
                         intptr_t position);
  virtual intptr_t Relocation(intptr_t section_offset,
                              intptr_t source_label,
                              intptr_t source_offset,
                              intptr_t target_label,
                              intptr_t target_offset);
  virtual intptr_t RelocatedAddress(intptr_t section_offset, intptr_t label) {
    // Cannot calculate snapshot-relative addresses in assembly snapshots.
    return WriteTargetWord(Image::kNoRelocatedAddress);
  }
  virtual void FrameUnwindPrologue();
  virtual void FrameUnwindEpilogue();
  virtual void AddCodeSymbol(const Code& code,
                             const char* symbol,
                             intptr_t offset);
  virtual void AddDataSymbol(const char* symbol, intptr_t offset, size_t size);

  BaseWriteStream* const assembly_stream_;
  Dwarf* const assembly_dwarf_;
  SharedObjectWriter* const debug_so_;

  // Used in Relocation to output "(.)" for relocations involving the current
  // section position.
  intptr_t current_section_label_ = 0;

  // Used for creating local symbols for code and data objects in the
  // debugging info, if separately written.
  SharedObjectWriter::SymbolDataArray* current_symbols_ = nullptr;

  // Maps labels to the appropriate symbol names for relocations and DWARF
  // output.
  IntMap<const char*> label_to_symbol_name_;

  DISALLOW_COPY_AND_ASSIGN(AssemblyImageWriter);
};
#endif

class BlobImageWriter : public ImageWriter {
 public:
#if defined(DART_PRECOMPILER)
  BlobImageWriter(Thread* thread,
                  NonStreamingWriteStream* vm_instructions,
                  NonStreamingWriteStream* isolate_instructions,
                  const Trie<const char>* deobfuscation_trie = nullptr,
                  SharedObjectWriter* debug_so = nullptr,
                  SharedObjectWriter* so = nullptr,
                  bool needs_unique_names = false);
#else
  BlobImageWriter(Thread* thread,
                  NonStreamingWriteStream* vm_instructions,
                  NonStreamingWriteStream* isolate_instructions,
                  SharedObjectWriter* debug_so = nullptr,
                  SharedObjectWriter* so = nullptr,
                  bool needs_unique_names = false);
#endif

  virtual void Finalize();

 private:
  virtual void WriteBss(bool vm);
  virtual void WriteROData(NonStreamingWriteStream* clustered_stream, bool vm);

  virtual bool EnterSection(ProgramSection section,
                            bool vm,
                            intptr_t alignment,
                            intptr_t* alignment_padding = nullptr);
  virtual void ExitSection(ProgramSection name, bool vm, intptr_t size);
  virtual intptr_t WriteTargetWord(word value);
  virtual intptr_t WriteBytes(const void* bytes, intptr_t size);
  virtual intptr_t Align(intptr_t alignment,
                         intptr_t offset,
                         intptr_t position);
  // TODO(rmacnak): Generate .debug_frame / .eh_frame / .arm.exidx to
  // provide unwinding information.
  virtual void FrameUnwindPrologue() {}
  virtual void FrameUnwindEpilogue() {}
#if defined(DART_PRECOMPILER)
  virtual intptr_t Relocation(intptr_t section_offset,
                              intptr_t source_label,
                              intptr_t source_offset,
                              intptr_t target_label,
                              intptr_t target_offset);
  virtual intptr_t RelocatedAddress(intptr_t section_offset, intptr_t label) {
    return ImageWriter::Relocation(
        section_offset, SharedObjectWriter::Relocation::kSnapshotRelative,
        label);
  }
  virtual void AddCodeSymbol(const Code& code,
                             const char* symbol,
                             intptr_t offset);
  virtual void AddDataSymbol(const char* symbol, intptr_t offset, size_t size);

  // Set on section entrance to a new array containing the relocations for the
  // current section.
  SharedObjectWriter::RelocationArray* current_relocations_ = nullptr;
  // Set on section entrance to a new array containing the local symbol data
  // for the current section.
  SharedObjectWriter::SymbolDataArray* current_symbols_ = nullptr;
#endif

  NonStreamingWriteStream* const vm_instructions_;
  NonStreamingWriteStream* const isolate_instructions_;
  SharedObjectWriter* const so_;
  SharedObjectWriter* const debug_so_;

  // Set on section entrance to the stream that should be used by the writing
  // methods.
  NonStreamingWriteStream* current_section_stream_ = nullptr;

  DISALLOW_COPY_AND_ASSIGN(BlobImageWriter);
};

}  // namespace dart

#endif  // RUNTIME_VM_IMAGE_SNAPSHOT_H_
