runtime/heap-profiler.cpp (565 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com)
#include "heap-profiler.h"
#include <cerrno>
#include "file.h"
#include "handles.h"
#include "os.h"
#include "runtime.h"
#include "thread.h"
namespace py {
const char HeapProfiler::kBytearrayClassName[] = "byte[]";
const char HeapProfiler::kDoubleArrayClassName[] = "double[]";
const char HeapProfiler::kInvalid[] = "<INVALID>";
const char HeapProfiler::kOverflow[] = "<OVERFLOW>";
const char HeapProfiler::kJavaLangClass[] = "java.lang.Class";
const char HeapProfiler::kJavaLangClassLoader[] = "java.lang.ClassLoader";
const char HeapProfiler::kJavaLangObject[] = "java.lang.Object";
const char HeapProfiler::kJavaLangString[] = "java.lang.String";
const char HeapProfiler::kLongArrayClassName[] = "long[]";
const char HeapProfiler::kObjectArrayClassName[] = "java.lang.Object[]";
HeapProfiler::HeapProfiler(Thread* thread, HeapProfilerWriteCallback callback,
                           void* stream)
    : thread_(thread), output_stream_(stream), write_callback_(callback) {}
void HeapProfiler::write(const void* data, word size) {
  (*write_callback_)(data, size, output_stream_);
}
// LOAD CLASS - 0x02
//
// Format:
//   u4 - class serial number (always > 0)
//   ID - class object ID
//   u4 - stack trace serial number
//   ID - class name string ID
void HeapProfiler::writeFakeLoadClass(FakeClass fake_class,
                                      const char* class_name) {
  Record record(kLoadClass, this);
  // class serial number (always > 0)
  record.write32(1);
  // class object ID
  record.writeObjectId(static_cast<uword>(fake_class));
  // stack trace serial number
  record.write32(0);
  // TODO(T61807224): Dump type names discriminated by layout ID
  // class name string ID
  record.writeObjectId(cStringId(class_name));
}
// CLASS DUMP - 0x20
//
// Format:
//   u4 - class serial number (always > 0)
//   ID - class object ID
//   u4 - stack trace serial number
//   ID - class name string ID
void HeapProfiler::writeFakeClassDump(FakeClass fake_class,
                                      const char* class_name,
                                      FakeClass fake_super_class) {
  writeFakeLoadClass(fake_class, class_name);
  CHECK(!class_dump_table_.add(static_cast<uword>(fake_class)),
        "cannot dump object twice");
  SubRecord sub(Subtag::kClassDump, current_record_);
  // class object ID
  sub.writeObjectId(static_cast<uword>(fake_class));
  // stack trace serial number
  sub.write32(0);
  // super class object ID
  sub.writeObjectId(static_cast<uword>(fake_super_class));
  // class loader object ID
  sub.writeObjectId(0);
  // signers object ID
  sub.writeObjectId(0);
  // protection domain object ID
  sub.writeObjectId(0);
  // reserved
  sub.writeObjectId(0);
  // reserved
  sub.writeObjectId(0);
  // instance size (in bytes)
  sub.write32(0);
  // size of constant pool and number of records that follow
  sub.write16(0);
  // Number of static fields
  sub.write16(0);
  // Number of instance fields (not include super class's)
  sub.write16(0);
}
// STACK TRACE - 0x05
//
//  u4 - stack trace serial number
//  u4 - thread serial number
//  u4 - number of frames
//  [ID]* - series of stack frame ID's
void HeapProfiler::writeFakeStackTrace() {
  Record record(kStackTrace, this);
  // stack trace serial number
  record.write32(0);
  // thread serial number
  // TODO(T70833159): Support multiple threads in heap dumper.
  record.write32(0);
  // number of frames
  record.write32(0);
}
void HeapProfiler::writeHeader() {
  const char magic[] = "JAVA PROFILE 1.0.2";
  write(magic, sizeof(magic));
  write32(kPointerSize);
  double seconds_double = OS::currentTime();
  uint64_t seconds = static_cast<uint64_t>(seconds_double);
  seconds += (seconds_double - seconds);
  uint64_t milliseconds = seconds * kMillisecondsPerSecond;
  uint32_t hi =
      static_cast<uint32_t>((milliseconds >> 32) & 0x00000000FFFFFFFF);
  write32(hi);
  uint32_t lo = static_cast<uint32_t>(milliseconds & 0x00000000FFFFFFFF);
  write32(lo);
}
// LOAD CLASS - 0x02
//
// Format:
//   u4 - class serial number (always > 0)
//   ID - class object ID
//   u4 - stack trace serial number
//   ID - class name string ID
void HeapProfiler::writeLoadClass(RawLayout layout) {
  CHECK(!load_class_table_.add(layout.raw()), "cannot dump object twice");
  Record record(kLoadClass, this);
  // class serial number (always > 0)
  record.write32(1);
  // class object ID
  record.writeObjectId(objectId(layout));
  // stack trace serial number
  record.write32(0);
  // class name string ID
  HandleScope scope(thread_);
  Type type(&scope, thread_->runtime()->concreteTypeAt(layout.id()));
  Str name(&scope, type.name());
  record.writeObjectId(stringId(*name));
}
// CLASS DUMP - 0x20
//
// Format:
//  ID - class object ID
//  u4 - stack trace serial number
//  ID - super class object ID
//  ID - class loader object ID
//  ID - signers object ID
//  ID - protection domain object ID
//  ID - reserved
//  ID - reserved
//  u4 - instance size (in bytes)
//  u2 - size of constant pool and number of records that follow
//  u2 - constant pool index
//  u1 - type of entry: (See Basic Type)
//  value - value of entry (u1, u2, u4, or u8 based on type of entry)
//  u2 - Number of static fields:
//  ID - static field name string ID
//  u1 - type of field: (See Basic Type)
//  value - value of entry (u1, u2, u4, or u8 based on type of field)
//  u2 - Number of instance fields (not including super class's)
//  ID - field name string ID
//  u1 - type of field: (See Basic Type)
void HeapProfiler::writeClassDump(RawLayout layout) {
  CHECK(!class_dump_table_.add(layout.raw()), "cannot dump object twice");
  SubRecord sub(kClassDump, current_record_);
  // class object ID
  sub.writeObjectId(classId(layout));
  // stack trace serial number
  sub.write32(0);
  // super class object ID
  if (layout.id() == LayoutId::kObject) {
    // Superclass == 0 => object is java.lang.Object
    sub.writeObjectId(0);
  } else {
    // Since there is not much of a concept of inheritance in the Layout
    // system, pretend all Layouts' super is "object". This allows much easier
    // dumping of attributes.
    // TODO(emacs): Figure out how to dump class hierarchies
    RawLayout super_layout =
        Layout::cast(thread_->runtime()->layoutAt(LayoutId::kObject));
    sub.writeObjectId(classId(super_layout));
  }
  // class loader object ID
  sub.writeObjectId(0);
  // signers object ID
  sub.writeObjectId(0);
  // protection domain object ID
  sub.writeObjectId(0);
  // reserved
  sub.writeObjectId(0);
  // reserved
  sub.writeObjectId(0);
  // instance size (in bytes)
  sub.write32(layout.instanceSize());
  // size of constant pool and number of records that follow
  // Constant pool is variable-length and empty here
  sub.write16(0);
  // number of static fields
  // Static fields are variable-length and empty here
  sub.write16(0);
  Runtime* runtime = thread_->runtime();
  if (layout.id() == LayoutId::kComplex) {
    // Two instance fields: "real", "imag"
    sub.write16(2);
    sub.writeObjectId(stringId(Str::cast(runtime->symbols()->at(ID(real)))));
    sub.write8(BasicType::kDouble);
    sub.writeObjectId(stringId(Str::cast(runtime->symbols()->at(ID(imag)))));
    sub.write8(BasicType::kDouble);
    return;
  }
  if (layout.id() == LayoutId::kFloat) {
    // One instance field: "value"
    sub.write16(1);
    sub.writeObjectId(stringId(Str::cast(runtime->symbols()->at(ID(value)))));
    sub.write8(BasicType::kDouble);
    return;
  }
  // number of instance fields (not include super class's)
  RawTuple in_object = Tuple::cast(layout.inObjectAttributes());
  word num_in_object = in_object.length();
  bool has_tuple_overflow = layout.hasTupleOverflow();
  word num_overflow = has_tuple_overflow ? 1 : 0;
  word num_attributes = num_in_object + num_overflow;
  sub.write16(num_attributes);
  // instance fields
  for (word i = 0; i < num_in_object; i++) {
    // allocated on the layout for an attribute
    RawObject name = Tuple::cast(in_object.at(i)).at(0);
    if (name == SmallInt::fromWord(0)) {
      sub.writeObjectId(cStringId(kInvalid));
    } else {
      sub.writeObjectId(stringId(Str::cast(name)));
    }
    sub.write8(BasicType::kObject);
  }
  // TODO(emacs): Remove this special case once tuple overflow fits neatly into
  // the allocated in-object attributes
  if (has_tuple_overflow) {
    sub.writeObjectId(cStringId(kOverflow));
    sub.write8(BasicType::kObject);
  }
}
void HeapProfiler::writeInstanceDump(RawInstance obj) {
  CHECK(!heap_object_table_.add(obj.raw()), "cannot dump object twice");
  SubRecord sub(kInstanceDump, current_record_);
  RawLayout layout = Layout::cast(Thread::current()->runtime()->layoutOf(obj));
  RawTuple in_object = Tuple::cast(layout.inObjectAttributes());
  word num_in_object = in_object.length();
  bool has_tuple_overflow = layout.hasTupleOverflow();
  word num_overflow = has_tuple_overflow ? 1 : 0;
  word num_attributes = num_in_object + num_overflow;
  sub.beginInstanceDump(obj, /*stack_trace=*/0, num_attributes * kPointerSize,
                        classId(layout));
  // write in-object attributes
  for (word i = 0; i < num_in_object; i++) {
    RawTuple elt = Tuple::cast(in_object.at(i));
    AttributeInfo info(elt.at(1));
    sub.writeObjectId(
        objectId(Instance::cast(obj).instanceVariableAt(info.offset())));
  }
  // write tuple overflow (dict overflow is in-object)
  if (has_tuple_overflow) {
    sub.writeObjectId(objectId(
        Instance::cast(obj).instanceVariableAt(layout.overflowOffset())));
  }
}
void HeapProfiler::writeImmediate(RawObject obj) {
  DCHECK(!obj.isHeapObject(), "obj must be an immediate");
  SubRecord sub(kInstanceDump, current_record_);
  RawLayout layout = Layout::cast(Thread::current()->runtime()->layoutOf(obj));
  sub.beginInstanceDump(obj, /*stack_trace=*/0, /*num_bytes=*/0,
                        classId(layout));
}
class ImmediateVisitor : public WordVisitor {
 public:
  ImmediateVisitor(HeapProfiler* profiler) : profiler_(profiler) {}
  void visit(uword element) { profiler_->writeImmediate(RawObject{element}); }
 private:
  HeapProfiler* profiler_;
};
void HeapProfiler::writeImmediates() {
  ImmediateVisitor visitor(this);
  immediate_table_.visitElements(&visitor);
}
// OBJECT ARRAY DUMP - 0x22
//
// Format:
//  ID - array object ID
//  u4 - stack trace serial number
//  u4 - number of elements
//  ID - array class object id
//  [ID]* - elements
void HeapProfiler::writeObjectArray(RawTuple tuple) {
  SubRecord sub(kObjectArrayDump, current_record_);
  // array object id
  sub.writeObjectId(objectId(tuple));
  // stack trace serial number
  sub.write32(0);
  // number of elements
  word length = tuple.length();
  CHECK(length < kMaxUint32, "length %ld too big for Java length field",
        length);
  sub.write32(static_cast<uint32_t>(length));
  // array class object id
  sub.writeObjectId(static_cast<uword>(FakeClass::kObjectArray));
  // elements
  for (word i = 0; i < length; i++) {
    sub.writeObjectId(objectId(tuple.at(i)));
  }
}
void HeapProfiler::writeBytes(RawBytes bytes) {
  CHECK(!heap_object_table_.add(bytes.raw()), "cannot dump object twice");
  SubRecord sub(kPrimitiveArrayDump, current_record_);
  sub.beginPrimitiveArrayDump(objectId(bytes), /*stack_trace=*/0,
                              bytes.length(), BasicType::kByte);
  for (word i = 0; i < bytes.length(); i++) {
    sub.write8(bytes.byteAt(i));
  }
}
void HeapProfiler::writeLargeStr(RawLargeStr str) {
  CHECK(!heap_object_table_.add(str.raw()), "cannot dump object twice");
  SubRecord sub(kPrimitiveArrayDump, current_record_);
  word length = str.length();
  sub.beginPrimitiveArrayDump(objectId(str), /*stack_trace=*/0, length,
                              BasicType::kByte);
  for (word i = 0; i < length; i++) {
    sub.write8(str.byteAt(i));
  }
}
void HeapProfiler::writeComplex(RawComplex obj) {
  CHECK(!heap_object_table_.add(obj.raw()), "cannot dump object twice");
  SubRecord sub(kInstanceDump, current_record_);
  RawLayout layout = Layout::cast(Thread::current()->runtime()->layoutOf(obj));
  sub.beginInstanceDump(obj, /*stack_trace=*/0, 2 * kDoubleSize,
                        classId(layout));
  sub.write64(bit_cast<uint64_t>(obj.real()));
  sub.write64(bit_cast<uint64_t>(obj.imag()));
}
void HeapProfiler::writeEllipsis(RawEllipsis obj) {
  CHECK(!heap_object_table_.add(obj.raw()), "cannot dump object twice");
  SubRecord sub(kInstanceDump, current_record_);
  RawLayout layout =
      Layout::cast(thread_->runtime()->layoutAt(LayoutId::kEllipsis));
  sub.beginInstanceDump(obj, /*stack_trace=*/0, layout.instanceSize(),
                        classId(layout));
  for (word i = 0; i < layout.instanceSize(); i += kPointerSize) {
    sub.writeObjectId(objectId(Unbound::object()));
  }
}
void HeapProfiler::writeFloat(RawFloat obj) {
  CHECK(!heap_object_table_.add(obj.raw()), "cannot dump object twice");
  SubRecord sub(kInstanceDump, current_record_);
  RawLayout layout = Layout::cast(Thread::current()->runtime()->layoutOf(obj));
  sub.beginInstanceDump(obj, /*stack_trace=*/0, kDoubleSize, classId(layout));
  sub.write64(bit_cast<uint64_t>(obj.value()));
}
void HeapProfiler::writeLargeInt(RawLargeInt obj) {
  CHECK(!heap_object_table_.add(obj.raw()), "cannot dump object twice");
  SubRecord sub(kPrimitiveArrayDump, current_record_);
  sub.beginPrimitiveArrayDump(objectId(obj), /*stack_trace=*/0, obj.numDigits(),
                              BasicType::kLong);
  for (word i = 0; i < obj.numDigits(); i++) {
    sub.write64(obj.digitAt(i));
  }
}
// HEAP DUMP SEGMENT - 0x1C
void HeapProfiler::setRecord(Record* current_record) {
  DCHECK(current_record != nullptr, "record should be non-null");
  DCHECK(current_record_ == nullptr, "current record already exists");
  current_record_ = current_record;
}
void HeapProfiler::clearRecord() {
  DCHECK(current_record_ != nullptr, "current record does not exist");
  current_record_ = nullptr;
}
// HEAP DUMP END - 0x2C
void HeapProfiler::writeHeapDumpEnd() { Record record(kHeapDumpEnd, this); }
uword HeapProfiler::objectId(RawObject obj) {
  uword id = obj.raw();
  if (obj.isHeader()) {
    std::fprintf(stderr,
                 "objectId called on header @ 0x%lx, indicating misalignment\n",
                 id);
  }
  if (!obj.isError() && !obj.isHeapObject()) {
    immediate_table_.add(id);
  }
  return id;
}
uword HeapProfiler::classId(RawLayout layout) {
  uword id = layout.raw();
  if (!layout_table_.add(id)) {
    writeLoadClass(layout);
  }
  return id;
}
uword HeapProfiler::cStringId(const char* c_str) {
  uword id = reinterpret_cast<uword>(c_str);
  if (!string_table_.add(id)) {
    writeCStringInUTF8(c_str);
  }
  return id;
}
uword HeapProfiler::stringId(RawStr str) {
  uword id = objectId(str);
  if (!string_table_.add(id)) {
    writeStringInUTF8(str);
  }
  return id;
}
// ROOT UNKNOWN - 0xFF
//
// Describes a root of unknown provenance.
//
// Format:
//   ID - object ID
void HeapProfiler::writeRuntimeRoot(RawObject obj) {
  SubRecord sub(kRootUnknown, current_record_);
  // object ID
  sub.writeObjectId(objectId(obj));
}
// ROOT STICKY CLASS - 0x05
//
// Describes a built-in Layout.
//
// Format:
//   ID - object ID
void HeapProfiler::writeStickyClassRoot(RawObject obj) {
  SubRecord sub(kRootStickyClass, current_record_);
  // object ID
  sub.writeObjectId(objectId(obj));
}
// ROOT JAVA FRAME - 0x03
//
// Describes a value found in a Frame in the Python stack.
//
// Format:
//   ID - object ID
//   u4 - thread serial number
//   u4 - frame number in stack trace (-1 for empty)
void HeapProfiler::writeStackRoot(RawObject obj) {
  SubRecord sub(kRootJavaFrame, current_record_);
  // object ID
  sub.writeObjectId(objectId(obj));
  // thread serial number
  // TODO(T70833159): Support multiple threads in heap dumper.
  sub.write32(0);
  // frame number in stack trace
  sub.write32(-1);
}
// ROOT THREAD OBJECT - 0x08
//
// Describes a Thread object.
//
// Format:
//   ID - object ID
//   u4 - thread serial number
//   u4 - stack trace serial number
void HeapProfiler::writeThreadRoot(Thread* thread) {
  SubRecord sub(kRootThreadObject, current_record_);
  // object ID
  sub.writeObjectId(reinterpret_cast<uword>(thread));
  // thread serial number
  // TODO(T70833159): Support multiple threads in heap dumper.
  sub.write32(0);
  // stack trace serial number
  sub.write32(0);
}
// ROOT JNI GLOBAL - 0x01
//
// Describes an object wrapped in an ApiHandle.
//
// Format:
//   ID - object ID
//   ID - ApiHandle address
void HeapProfiler::writeApiHandleRoot(void* handle, RawObject obj) {
  SubRecord sub(kRootJniGlobal, current_record_);
  // object ID
  sub.writeObjectId(objectId(obj));
  // ApiHandle address
  sub.writeObjectId(reinterpret_cast<uword>(handle));
}
// ROOT UNKNOWN - 0xFF
//
// Describes an object of unknown provenance (typically Runtime or Thread root).
//
// Format:
//   ID - object ID
void HeapProfiler::writeUnknownRoot(RawObject obj) {
  SubRecord sub(kRootUnknown, current_record_);
  // object ID
  sub.writeObjectId(objectId(obj));
}
// ROOT NATIVE STACK - 0x04
//
// Describes an object inside a native frame (in a Handle).
//
// Format:
//   ID - object ID
//   u4 - thread serial number
void HeapProfiler::writeHandleRoot(RawObject obj) {
  SubRecord sub(kRootNativeStack, current_record_);
  // object ID
  sub.writeObjectId(objectId(obj));
  // thread serial number
  // TODO(T70833159): Support multiple threads in heap dumper.
  sub.write32(0);
}
// STRING IN UTF8 - 0x01
//
// Format:
//   ID - ID for this string
//   [u1]* - UTF8 characters for string (NOT NULL terminated)
void HeapProfiler::writeStringInUTF8(RawStr str) {
  Record record(kStringInUtf8, this);
  record.writeObjectId(str.raw());
  for (word i = 0, length = str.length(); i < length; i++) {
    record.write8(str.byteAt(i));
  }
}
void HeapProfiler::writeCStringInUTF8(const char* c_str) {
  Record record(kStringInUtf8, this);
  record.writeObjectId(reinterpret_cast<uword>(c_str));
  for (; *c_str != '\0'; ++c_str) {
    record.write8(*c_str);
  }
}
HeapProfiler::Buffer::Buffer() : data_(Vector<uint8_t>()) {}
void HeapProfiler::Buffer::write(const uint8_t* data, word size) {
  for (word i = 0; i < size; i++) {
    data_.push_back(data[i]);
  }
}
void HeapProfiler::write8(uint8_t value) { write(&value, sizeof(value)); }
void HeapProfiler::write16(uint16_t value) {
  for (word i = 1; i >= 0; i--) {
    write8((value >> (i * kBitsPerByte)) & 0xff);
  }
}
void HeapProfiler::write32(uint32_t value) {
  for (word i = 3; i >= 0; i--) {
    write8((value >> (i * kBitsPerByte)) & 0xff);
  }
}
void HeapProfiler::write64(uint64_t value) {
  for (word i = 7; i >= 0; i--) {
    write8((value >> (i * kBitsPerByte)) & 0xff);
  }
}
HeapProfiler::Record::Record(Tag tag, HeapProfiler* profiler)
    : tag_(tag), profiler_(profiler) {}
// Record
//
// Format:
//   u1 - TAG: denoting the type of the record
//   u4 - TIME: number of microseconds since the time stamp in the header
//   u4 - LENGTH: number of bytes that follow this u4 field and belong
//        to this record
//   [u1]* - BODY: as many bytes as specified in the above u4 field
HeapProfiler::Record::~Record() {
  if (profiler_) {
    profiler_->write8(tag());
    profiler_->write32(time());
    profiler_->write32(length());
    if (length() > 0) {
      profiler_->write(body(), length());
    }
  }
}
void HeapProfiler::Record::write(const byte* value, word size) {
  body_.write(value, size);
}
void HeapProfiler::Record::write8(uint8_t value) {
  body_.write(&value, sizeof(value));
}
void HeapProfiler::Record::write16(uint16_t value) {
  for (word i = 1; i >= 0; i--) {
    write8((value >> (i * kBitsPerByte)) & 0xff);
  }
}
void HeapProfiler::Record::write32(uint32_t value) {
  for (word i = 3; i >= 0; i--) {
    write8((value >> (i * kBitsPerByte)) & 0xff);
  }
}
void HeapProfiler::Record::write64(uint64_t value) {
  for (word i = 7; i >= 0; i--) {
    write8((value >> (i * kBitsPerByte)) & 0xff);
  }
}
void HeapProfiler::Record::writeObjectId(uword value) { write64(value); }
HeapProfiler::SubRecord::SubRecord(Subtag sub_tag, Record* record)
    : record_(record) {
  DCHECK(record_ != nullptr, "heap dump segment does not exist");
  record_->write8(sub_tag);
}
void HeapProfiler::SubRecord::beginInstanceDump(RawObject obj,
                                                uword stack_trace,
                                                uword num_bytes,
                                                uword layout_id) {
  // TODO(emacs): This is a hack that works around MAT expecting ClassLoader at
  // 0. Once we have modified MAT to dump ClassLoader at a different location
  // than 0, we should just dump SmallInt 0 normally.
  word id = obj.raw() == 0 ? 73 : obj.raw();
  writeObjectId(id);
  write32(stack_trace);
  writeObjectId(layout_id);
  write32(num_bytes);
}
void HeapProfiler::SubRecord::beginPrimitiveArrayDump(uword object_id,
                                                      uword stack_trace,
                                                      uword length,
                                                      BasicType type) {
  writeObjectId(object_id);
  write32(stack_trace);
  CHECK(length < kMaxUint32, "length %ld too big for Java length field",
        length);
  write32(static_cast<uint32_t>(length));
  write8(type);
}
void HeapProfiler::SubRecord::write(const uint8_t* value, intptr_t size) {
  record_->write(value, size);
}
void HeapProfiler::SubRecord::write8(uint8_t value) { record_->write8(value); }
void HeapProfiler::SubRecord::write16(uint16_t value) {
  record_->write16(value);
}
void HeapProfiler::SubRecord::write32(uint32_t value) {
  record_->write32(value);
}
void HeapProfiler::SubRecord::write64(uint64_t value) {
  record_->write64(value);
}
void HeapProfiler::SubRecord::writeObjectId(uword value) {
  record_->writeObjectId(value);
}
static void writeToFileStream(const void* data, word length, void* stream) {
  DCHECK(data != nullptr, "data must not be null");
  DCHECK(length > 0, "length must be positive");
  word fd = static_cast<int>(reinterpret_cast<word>(stream));
  int result = File::write(fd, data, length);
  CHECK(result == length, "could not write the whole chunk to disk");
}
class HeapProfilerHandleVisitor : public HandleVisitor {
 public:
  HeapProfilerHandleVisitor(HeapProfiler* profiler) : profiler_(profiler) {}
  void visitHandle(void* handle, RawObject obj) {
    return profiler_->writeApiHandleRoot(handle, obj);
  }
 protected:
  HeapProfiler* profiler_;
  DISALLOW_COPY_AND_ASSIGN(HeapProfilerHandleVisitor);
};
class HeapProfilerRootVisitor : public PointerVisitor {
 public:
  HeapProfilerRootVisitor(HeapProfiler* profiler) : profiler_(profiler) {}
  void visitPointer(RawObject* pointer, PointerKind kind) {
    // TODO(emacs): This is a hack that works around MAT expecting ClassLoader
    // at 0. Once we have modified MAT to dump ClassLoader at a different
    // location than 0, we should just dump SmallInt 0 normally.
    RawObject obj = RawObject{pointer->raw() == 0 ? 73 : pointer->raw()};
    switch (kind) {
      case PointerKind::kRuntime:
      case PointerKind::kThread:
      case PointerKind::kUnknown:
        return profiler_->writeUnknownRoot(obj);
      case PointerKind::kHandle:
        return profiler_->writeHandleRoot(obj);
      case PointerKind::kStack:
        return profiler_->writeStackRoot(obj);
      case PointerKind::kApiHandle:
        // Should only see handles in `HeapProfilerHandleVisitor`.
        UNREACHABLE("should not be used");
      case PointerKind::kLayout:
        return profiler_->writeStickyClassRoot(obj);
    }
  }
 protected:
  HeapProfiler* profiler_;
  DISALLOW_COPY_AND_ASSIGN(HeapProfilerRootVisitor);
};
class HeapProfilerObjectVisitor : public HeapObjectVisitor {
 public:
  HeapProfilerObjectVisitor(HeapProfiler* profiler) : profiler_(profiler) {}
  void visitHeapObject(RawHeapObject obj) {
    switch (obj.layoutId()) {
      case LayoutId::kLayout:
        return profiler_->writeClassDump(Layout::cast(obj));
      case LayoutId::kLargeInt:
        return profiler_->writeLargeInt(LargeInt::cast(obj));
      case LayoutId::kLargeBytes:
      case LayoutId::kMutableBytes:
        return profiler_->writeBytes(Bytes::cast(obj));
      case LayoutId::kFloat:
        return profiler_->writeFloat(Float::cast(obj));
      case LayoutId::kComplex:
        return profiler_->writeComplex(Complex::cast(obj));
      case LayoutId::kTuple:
      case LayoutId::kMutableTuple:
        return profiler_->writeObjectArray(Tuple::cast(obj));
      case LayoutId::kLargeStr:
        return profiler_->writeLargeStr(LargeStr::cast(obj));
      case LayoutId::kEllipsis:
        return profiler_->writeEllipsis(Ellipsis::cast(obj));
      default:
        CHECK(obj.isInstance(), "obj should be instance, but is %ld",
              obj.layoutId());
        return profiler_->writeInstanceDump(Instance::cast(obj));
    }
  }
 protected:
  HeapProfiler* profiler_;
  DISALLOW_COPY_AND_ASSIGN(HeapProfilerObjectVisitor);
};
RawObject heapDump(Thread* thread, const char* filename) {
  int fd = File::open(
      filename,
      File::kBinaryFlag | File::kCreate | File::kTruncate | File::kWriteOnly,
      0644);
  if (fd < 0) {
    int saved_errno = errno;
    return thread->raiseOSErrorFromErrno(saved_errno);
  }
  HeapProfiler profiler(thread, writeToFileStream, reinterpret_cast<void*>(fd));
  profiler.writeHeader();
  profiler.writeFakeStackTrace();
  {
    HeapProfiler::Record record(HeapProfiler::kHeapDumpSegment, &profiler);
    profiler.setRecord(&record);
    profiler.writeThreadRoot(thread);
    // java.lang.Class
    profiler.writeFakeClassDump(HeapProfiler::FakeClass::kJavaLangClass,
                                HeapProfiler::kJavaLangClass,
                                HeapProfiler::FakeClass::kJavaLangObject);
    // java.lang.ClassLoader
    profiler.writeFakeClassDump(HeapProfiler::FakeClass::kJavaLangClassLoader,
                                HeapProfiler::kJavaLangClassLoader,
                                HeapProfiler::FakeClass::kJavaLangObject);
    // java.lang.Object
    profiler.writeFakeClassDump(HeapProfiler::FakeClass::kJavaLangObject,
                                HeapProfiler::kJavaLangObject,
                                static_cast<HeapProfiler::FakeClass>(0x0));
    // java.lang.String
    profiler.writeFakeClassDump(HeapProfiler::FakeClass::kJavaLangString,
                                HeapProfiler::kJavaLangString,
                                HeapProfiler::FakeClass::kJavaLangObject);
    // byte[]
    profiler.writeFakeClassDump(HeapProfiler::FakeClass::kBytearray,
                                HeapProfiler::kBytearrayClassName,
                                HeapProfiler::FakeClass::kJavaLangObject);
    // double[]
    profiler.writeFakeClassDump(HeapProfiler::FakeClass::kDoubleArray,
                                HeapProfiler::kDoubleArrayClassName,
                                HeapProfiler::FakeClass::kJavaLangObject);
    // long[]
    profiler.writeFakeClassDump(HeapProfiler::FakeClass::kLongArray,
                                HeapProfiler::kLongArrayClassName,
                                HeapProfiler::FakeClass::kJavaLangObject);
    // java.lang.Object[]
    profiler.writeFakeClassDump(HeapProfiler::FakeClass::kObjectArray,
                                HeapProfiler::kObjectArrayClassName,
                                HeapProfiler::FakeClass::kJavaLangObject);
    Runtime* runtime = thread->runtime();
    HeapProfilerRootVisitor root_visitor(&profiler);
    runtime->visitRootsWithoutApiHandles(&root_visitor);
    HeapProfilerHandleVisitor handle_visitor(&profiler);
    visitApiHandles(runtime, &handle_visitor);
    HeapProfilerObjectVisitor object_visitor(&profiler);
    runtime->heap()->visitAllObjects(&object_visitor);
    profiler.writeImmediates();
    profiler.clearRecord();
  }
  profiler.writeHeapDumpEnd();
  int result = File::close(fd);
  CHECK(result == 0, "could not close file '%s'", filename);
  return NoneType::object();
}
}  // namespace py