[vm/aot] Fill in names of properties in snapshot profile.

Change-Id: Ic709a3dc40390463ffb5fd76fd8eb63ebd87acc0
Cq-Include-Trybots: luci.dart.try:vm-kernel-optcounter-threshold-linux-release-x64-try, vm-kernel-precomp-linux-debug-x64-try, vm-kernel-precomp-linux-release-simarm-try, vm-kernel-precomp-linux-release-simarm64-try, vm-kernel-precomp-linux-release-x64-try, vm-kernel-precomp-mac-release-simarm64-try
Reviewed-on: https://dart-review.googlesource.com/c/85292
Commit-Queue: Samir Jindel <sjindel@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
diff --git a/runtime/tests/vm/dart/v8_snapshot_profile_writer_test.dart b/runtime/tests/vm/dart/v8_snapshot_profile_writer_test.dart
index ede572c..000ddcc 100644
--- a/runtime/tests/vm/dart/v8_snapshot_profile_writer_test.dart
+++ b/runtime/tests/vm/dart/v8_snapshot_profile_writer_test.dart
@@ -12,20 +12,13 @@
   return "/" + segments.join("/");
 }
 
-test(bool use_elf) async {
+test(String sdkRoot, {bool useElf: false}) async {
   if (Platform.isWindows) return;
-  if (Platform.isMacOS && use_elf) return;
-
-  final String outputDir = Platform.isMacOS ? "xcodebuild" : "out";
-
-  final List<String> sdkBaseSegments =
-      Uri.file(Platform.resolvedExecutable).pathSegments.toList();
-  sdkBaseSegments.replaceRange(
-      sdkBaseSegments.indexOf(outputDir), sdkBaseSegments.length, []);
+  if (Platform.isMacOS && useElf) return;
 
   // Generate the snapshot profile.
-  final String thisTestPath = path(sdkBaseSegments) +
-      "/runtime/tests/vm/dart/v8_snapshot_profile_writer_test.dart";
+  final String thisTestPath =
+      "$sdkRoot/runtime/tests/vm/dart/v8_snapshot_profile_writer_test.dart";
 
   final Directory temp = await Directory.systemTemp.createTemp();
   final String snapshotPath = temp.path + "/test.snap";
@@ -36,20 +29,20 @@
     snapshotPath,
   ];
 
-  if (use_elf) {
+  if (useElf) {
     precompiler2Args.insert(0, "--build-elf");
   }
 
   final ProcessResult result = await Process.run(
     "pkg/vm/tool/precompiler2",
     precompiler2Args,
-    workingDirectory: path(sdkBaseSegments),
+    workingDirectory: sdkRoot,
     runInShell: true,
   );
 
   // The precompiler2 script tried using GCC for the wrong architecture. We
   // don't have a workaround for this now.
-  if (use_elf &&
+  if (useElf &&
       result.exitCode != 0 &&
       result.stderr.contains("Assembler messages")) {
     return;
@@ -91,7 +84,7 @@
   // Verify that the actual size of the snapshot is close to the sum of the
   // shallow sizes of all objects in the profile. They will not be exactly equal
   // because of global headers and padding.
-  if (use_elf) {
+  if (useElf) {
     await Process.run("strip", [snapshotPath]);
   }
   final int actual = await File(snapshotPath).length();
@@ -99,7 +92,85 @@
   Expect.isTrue((actual - expected).abs() / actual < 0.01);
 }
 
+Match matchComplete(RegExp regexp, String line) {
+  Match match = regexp.firstMatch(line);
+  if (match == null) return match;
+  if (match.start != 0 || match.end != line.length) return null;
+  return match;
+}
+
+// All fields of "Raw..." classes defined in "raw_object.h" must be included in
+// the giant macro in "raw_object_fields.cc". This function attempts to check
+// that with some basic regexes.
+testMacros(String sdkRoot) async {
+  const String className = "([a-z0-9A-Z]+)";
+  const String rawClass = "Raw$className";
+  const String fieldName = "([a-z0-9A-Z_]+)";
+
+  final Map<String, Set<String>> fields = {};
+
+  final String rawObjectFieldsPath = "$sdkRoot/runtime/vm/raw_object_fields.cc";
+  final RegExp fieldEntry = RegExp(" *F\\($className, $fieldName\\) *\\\\?");
+
+  await for (String line in File(rawObjectFieldsPath)
+      .openRead()
+      .transform(utf8.decoder)
+      .transform(LineSplitter())) {
+    Match match = matchComplete(fieldEntry, line);
+    if (match != null) {
+      fields
+          .putIfAbsent(match.group(1), () => Set<String>())
+          .add(match.group(2));
+    }
+  }
+
+  final RegExp classStart = RegExp("class $rawClass : public $rawClass {");
+  final RegExp classEnd = RegExp("}");
+  final RegExp field = RegExp("  $rawClass. +$fieldName;.*");
+
+  final String rawObjectPath = "$sdkRoot/runtime/vm/raw_object.h";
+
+  String currentClass;
+  bool hasMissingFields = false;
+  await for (String line in File(rawObjectPath)
+      .openRead()
+      .transform(utf8.decoder)
+      .transform(LineSplitter())) {
+    Match match = matchComplete(classStart, line);
+    if (match != null) {
+      currentClass = match.group(1);
+      continue;
+    }
+
+    match = matchComplete(classEnd, line);
+    if (match != null) {
+      currentClass = null;
+      continue;
+    }
+
+    match = matchComplete(field, line);
+    if (match != null && currentClass != null) {
+      if (!fields[currentClass].contains(match.group(2))) {
+        hasMissingFields = true;
+        print("$currentClass is missing ${match.group(2)}.");
+      }
+    }
+  }
+
+  if (hasMissingFields) {
+    Expect.fail(
+        "runtime/vm/raw_object_fields.cc misses some fields. Please update it to match raw_object.h.");
+  }
+}
+
 main() async {
-  test(false);
-  test(true);
+  final List<String> sdkBaseSegments =
+      Uri.file(Platform.resolvedExecutable).pathSegments.toList();
+  sdkBaseSegments
+      .replaceRange(sdkBaseSegments.length - 3, sdkBaseSegments.length, []);
+  String sdkRoot = path(sdkBaseSegments);
+
+  test(sdkRoot, useElf: false);
+  test(sdkRoot, useElf: true);
+  testMacros(sdkRoot);
 }
diff --git a/runtime/vm/clustered_snapshot.cc b/runtime/vm/clustered_snapshot.cc
index acbd08a..118143ed 100644
--- a/runtime/vm/clustered_snapshot.cc
+++ b/runtime/vm/clustered_snapshot.cc
@@ -299,9 +299,9 @@
       s->Write<bool>(type_args->IsCanonical());
       intptr_t hash = Smi::Value(type_args->ptr()->hash_);
       s->Write<int32_t>(hash);
-      s->WriteRef(type_args->ptr()->instantiations_);
+      WriteField(type_args, instantiations_);
       for (intptr_t j = 0; j < length; j++) {
-        s->WriteRef(type_args->ptr()->types()[j]);
+        s->WriteElementRef(type_args->ptr()->types()[j], j);
       }
     }
   }
@@ -641,11 +641,11 @@
       RawClosureData* data = objects_[i];
       AutoTraceObject(data);
       if (s->kind() != Snapshot::kFullAOT) {
-        s->WriteRef(data->ptr()->context_scope_);
+        WriteField(data, context_scope_);
       }
-      s->WriteRef(data->ptr()->parent_function_);
-      s->WriteRef(data->ptr()->signature_type_);
-      s->WriteRef(data->ptr()->closure_);
+      WriteField(data, parent_function_);
+      WriteField(data, signature_type_);
+      WriteField(data, closure_);
     }
   }
 
@@ -882,39 +882,39 @@
     intptr_t count = objects_.length();
     for (intptr_t i = 0; i < count; i++) {
       RawField* field = objects_[i];
-      AutoTraceObject(field);
+      AutoTraceObjectName(field, field->ptr()->name_);
 
-      s->WriteRef(field->ptr()->name_);
-      s->WriteRef(field->ptr()->owner_);
-      s->WriteRef(field->ptr()->type_);
+      WriteField(field, name_);
+      WriteField(field, owner_);
+      WriteField(field, type_);
       // Write out the initial static value or field offset.
       if (Field::StaticBit::decode(field->ptr()->kind_bits_)) {
         if (kind == Snapshot::kFullAOT) {
           // For precompiled static fields, the value was already reset and
           // initializer_ now contains a Function.
-          s->WriteRef(field->ptr()->value_.static_value_);
+          WriteField(field, value_.static_value_);
         } else if (Field::ConstBit::decode(field->ptr()->kind_bits_)) {
           // Do not reset const fields.
-          s->WriteRef(field->ptr()->value_.static_value_);
+          WriteField(field, value_.static_value_);
         } else {
           // Otherwise, for static fields we write out the initial static value.
-          s->WriteRef(field->ptr()->initializer_.saved_value_);
+          WriteField(field, initializer_.saved_value_);
         }
       } else {
-        s->WriteRef(field->ptr()->value_.offset_);
+        WriteField(field, value_.offset_);
       }
       // Write out the initializer function or saved initial value.
       if (kind == Snapshot::kFullAOT) {
-        s->WriteRef(field->ptr()->initializer_.precompiled_);
+        WriteField(field, initializer_.precompiled_);
       } else {
-        s->WriteRef(field->ptr()->initializer_.saved_value_);
+        WriteField(field, initializer_.saved_value_);
       }
       if (kind != Snapshot::kFullAOT) {
         // Write out the guarded list length.
-        s->WriteRef(field->ptr()->guarded_list_length_);
+        WriteField(field, guarded_list_length_);
       }
       if (kind == Snapshot::kFullJIT) {
-        s->WriteRef(field->ptr()->dependent_code_);
+        WriteField(field, dependent_code_);
       }
 
       if (kind != Snapshot::kFullAOT) {
@@ -1369,29 +1369,29 @@
         s->WriteInstructions(code->ptr()->active_instructions_, code);
       }
 
-      s->WriteRef(code->ptr()->object_pool_);
-      s->WriteRef(code->ptr()->owner_);
-      s->WriteRef(code->ptr()->exception_handlers_);
-      s->WriteRef(code->ptr()->pc_descriptors_);
+      WriteField(code, object_pool_);
+      WriteField(code, owner_);
+      WriteField(code, exception_handlers_);
+      WriteField(code, pc_descriptors_);
 #if defined(DART_PRECOMPILED_RUNTIME) || defined(DART_PRECOMPILER)
-      s->WriteRef(code->ptr()->catch_entry_.catch_entry_moves_maps_);
+      WriteField(code, catch_entry_.catch_entry_moves_maps_);
 #else
-      s->WriteRef(code->ptr()->catch_entry_.variables_);
+      WriteField(code, catch_entry_.variables_);
 #endif
-      s->WriteRef(code->ptr()->stackmaps_);
+      WriteField(code, stackmaps_);
       if (FLAG_dwarf_stack_traces) {
-        s->WriteRef(Array::null());
-        s->WriteRef(CodeSourceMap::null());
+        WriteFieldValue(inlined_id_to_function_, Array::null());
+        WriteFieldValue(code_source_map_, CodeSourceMap::null());
       } else {
-        s->WriteRef(code->ptr()->inlined_id_to_function_);
-        s->WriteRef(code->ptr()->code_source_map_);
+        WriteField(code, inlined_id_to_function_);
+        WriteField(code, code_source_map_);
       }
       if (kind == Snapshot::kFullJIT) {
-        s->WriteRef(code->ptr()->deopt_info_array_);
-        s->WriteRef(code->ptr()->static_calls_target_table_);
+        WriteField(code, deopt_info_array_);
+        WriteField(code, static_calls_target_table_);
       }
-      NOT_IN_PRODUCT(s->WriteRef(code->ptr()->await_token_positions_));
-      NOT_IN_PRODUCT(s->WriteRef(code->ptr()->return_address_metadata_));
+      NOT_IN_PRODUCT(WriteField(code, await_token_positions_));
+      NOT_IN_PRODUCT(WriteField(code, return_address_metadata_));
 
       s->Write<int32_t>(code->ptr()->state_bits_);
     }
@@ -1607,11 +1607,11 @@
               // Natives can run while precompiling, becoming linked and
               // switching their stub. Reset to the initial stub used for
               // lazy-linking.
-              s->WriteRef(StubCode::CallBootstrapNative().raw());
+              s->WriteElementRef(StubCode::CallBootstrapNative().raw(), j);
               break;
             }
 #endif
-            s->WriteRef(entry.raw_obj_);
+            s->WriteElementRef(entry.raw_obj_, j);
             break;
           }
           case ObjectPool::kImmediate: {
@@ -1631,7 +1631,7 @@
               payload->trampoline = NULL;
               payload->native_function = NULL;
             }
-            s->WriteRef(raw);
+            s->WriteElementRef(raw, j);
             break;
           }
           case ObjectPool::kNativeFunction:
@@ -1758,13 +1758,18 @@
     for (intptr_t i = 0; i < count; i++) {
       RawObject* object = objects_[i];
       s->AssignRef(object);
-      AutoTraceObject(object);
+      if (cid_ == kOneByteStringCid || cid_ == kTwoByteStringCid) {
+        s->TraceStartWritingObject(name(), object, String::RawCast(object));
+      } else {
+        s->TraceStartWritingObject(name(), object, nullptr);
+      }
       uint32_t offset = s->GetDataOffset(object);
       s->TraceDataOffset(offset);
       ASSERT(Utils::IsAligned(offset, kObjectAlignment));
       ASSERT(offset > running_offset);
       s->WriteUnsigned((offset - running_offset) >> kObjectAlignmentLog2);
       running_offset = offset;
+      s->TraceEndWritingObject();
     }
   }
 
@@ -1838,7 +1843,7 @@
       AutoTraceObject(handlers);
       intptr_t length = handlers->ptr()->num_entries_;
       s->WriteUnsigned(length);
-      s->WriteRef(handlers->ptr()->handled_types_data_);
+      WriteField(handlers, handled_types_data_);
       for (intptr_t j = 0; j < length; j++) {
         const ExceptionHandlerInfo& info = handlers->ptr()->data()[j];
         s->Write<uint32_t>(info.handler_pc_offset);
@@ -1934,9 +1939,9 @@
       AutoTraceObject(context);
       intptr_t length = context->ptr()->num_variables_;
       s->WriteUnsigned(length);
-      s->WriteRef(context->ptr()->parent_);
+      WriteField(context, parent_);
       for (intptr_t j = 0; j < length; j++) {
-        s->WriteRef(context->ptr()->data()[j]);
+        s->WriteElementRef(context->ptr()->data()[j], j);
       }
     }
   }
@@ -2296,7 +2301,7 @@
     for (intptr_t i = 0; i < count; i++) {
       RawSubtypeTestCache* cache = objects_[i];
       AutoTraceObject(cache);
-      s->WriteRef(cache->ptr()->cache_);
+      WriteField(cache, cache_);
     }
   }
 
@@ -2525,7 +2530,7 @@
       while (offset < next_field_offset) {
         RawObject* raw_obj = *reinterpret_cast<RawObject**>(
             reinterpret_cast<uword>(instance->ptr()) + offset);
-        s->WriteRef(raw_obj);
+        s->WriteElementRef(raw_obj, offset);
         offset += kWordSize;
       }
     }
@@ -3810,7 +3815,7 @@
       AutoTraceObject(map);
       s->Write<bool>(map->IsCanonical());
 
-      s->WriteRef(map->ptr()->type_arguments_);
+      WriteField(map, type_arguments_);
 
       const intptr_t used_data = Smi::Value(map->ptr()->used_data_);
       ASSERT((used_data & 1) == 0);  // Keys + values, so must be even.
@@ -3825,8 +3830,8 @@
         RawObject* key = data_elements[i];
         if (key != data_array) {
           RawObject* value = data_elements[i + 1];
-          s->WriteRef(key);
-          s->WriteRef(value);
+          s->WriteElementRef(key, i);
+          s->WriteElementRef(value, i + 1);
         }
       }
     }
@@ -3934,9 +3939,9 @@
       intptr_t length = Smi::Value(array->ptr()->length_);
       s->WriteUnsigned(length);
       s->Write<bool>(array->IsCanonical());
-      s->WriteRef(array->ptr()->type_arguments_);
+      WriteField(array, type_arguments_);
       for (intptr_t j = 0; j < length; j++) {
-        s->WriteRef(array->ptr()->data()[j]);
+        s->WriteElementRef(array->ptr()->data()[j], j);
       }
     }
   }
@@ -4191,6 +4196,9 @@
   for (intptr_t i = 0; i < num_cids_; i++) {
     clusters_by_cid_[i] = NULL;
   }
+  if (profile_writer_ != nullptr) {
+    offsets_table_ = new (zone_) OffsetsTable(zone_);
+  }
 }
 
 Serializer::~Serializer() {
@@ -4202,11 +4210,14 @@
                                          RawString* name) {
   if (profile_writer_ == nullptr) return;
 
+  intptr_t cid = -1;
   intptr_t id = 0;
   if (obj->IsHeapObject()) {
     id = heap_->GetObjectId(obj);
+    cid = obj->GetClassId();
   } else {
     id = smi_ids_.Lookup(Smi::RawCast(obj))->id_;
+    cid = Smi::kClassId;
   }
   ASSERT(id != 0);
 
@@ -4217,20 +4228,21 @@
     name_str = str.ToCString();
   }
 
-  object_currently_writing_id_ = id;
+  object_currently_writing_.object_ = obj;
+  object_currently_writing_.id_ = id;
+  object_currently_writing_.stream_start_ = stream_.Position();
+  object_currently_writing_.cid_ = cid;
   profile_writer_->SetObjectTypeAndName(
       {V8SnapshotProfileWriter::kSnapshot, id}, type, name_str);
-  object_currently_writing_start_ = stream_.Position();
 }
 
 void Serializer::TraceEndWritingObject() {
   if (profile_writer_ != nullptr) {
-    ASSERT(object_currently_writing_id_ != 0);
+    ASSERT(object_currently_writing_.id_ != 0);
     profile_writer_->AttributeBytesTo(
-        {V8SnapshotProfileWriter::kSnapshot, object_currently_writing_id_},
-        stream_.Position() - object_currently_writing_start_);
-    object_currently_writing_id_ = 0;
-    object_currently_writing_start_ = 0;
+        {V8SnapshotProfileWriter::kSnapshot, object_currently_writing_.id_},
+        stream_.Position() - object_currently_writing_.stream_start_);
+    object_currently_writing_ = ProfilingObject();
   }
 }
 
@@ -4377,24 +4389,33 @@
   // course, the space taken for the reference is profiled.
   if (profile_writer_ != nullptr && offset >= 0) {
     // Instructions cannot be roots.
-    ASSERT(object_currently_writing_id_ != 0);
+    ASSERT(object_currently_writing_.id_ != 0);
     auto offset_space = vm_ ? V8SnapshotProfileWriter::kVmText
                             : V8SnapshotProfileWriter::kIsolateText;
+    V8SnapshotProfileWriter::ObjectId to_object = {
+        offset_space, offset < 0 ? -offset : offset};
+    V8SnapshotProfileWriter::ObjectId from_object = {
+        V8SnapshotProfileWriter::kSnapshot, object_currently_writing_.id_};
     profile_writer_->AttributeReferenceTo(
-        {V8SnapshotProfileWriter::kSnapshot, object_currently_writing_id_},
-        {offset_space, offset < 0 ? -offset : offset});
+        from_object, {to_object, V8SnapshotProfileWriter::Reference::kProperty,
+                      profile_writer_->EnsureString("<instructions>")});
   }
 }
 
 void Serializer::TraceDataOffset(uint32_t offset) {
   if (profile_writer_ != nullptr) {
     // ROData cannot be roots.
-    ASSERT(object_currently_writing_id_ != 0);
+    ASSERT(object_currently_writing_.id_ != 0);
     auto offset_space = vm_ ? V8SnapshotProfileWriter::kVmData
                             : V8SnapshotProfileWriter::kIsolateData;
+    V8SnapshotProfileWriter::ObjectId from_object = {
+        V8SnapshotProfileWriter::kSnapshot, object_currently_writing_.id_};
+    V8SnapshotProfileWriter::ObjectId to_object = {offset_space, offset};
+    // TODO(sjindel): Give this edge a more appropriate type than element
+    // (internal, maybe?).
     profile_writer_->AttributeReferenceTo(
-        {V8SnapshotProfileWriter::kSnapshot, object_currently_writing_id_},
-        {offset_space, offset});
+        from_object,
+        {to_object, V8SnapshotProfileWriter::Reference::kElement, 0});
   }
 }
 
diff --git a/runtime/vm/clustered_snapshot.h b/runtime/vm/clustered_snapshot.h
index 79bdd8d..412ffb5 100644
--- a/runtime/vm/clustered_snapshot.h
+++ b/runtime/vm/clustered_snapshot.h
@@ -14,6 +14,7 @@
 #include "vm/hash_map.h"
 #include "vm/heap/heap.h"
 #include "vm/object.h"
+#include "vm/raw_object_fields.h"
 #include "vm/snapshot.h"
 #include "vm/type_testing_stubs.h"
 #include "vm/v8_snapshot_writer.h"
@@ -236,7 +237,8 @@
   }
   void Align(intptr_t alignment) { stream_.Align(alignment); }
 
-  void WriteRef(RawObject* object, bool is_root = false) {
+ private:
+  intptr_t WriteRefId(RawObject* object) {
     intptr_t id = 0;
     if (!object->IsHeapObject()) {
       RawSmi* smi = Smi::RawCast(object);
@@ -251,35 +253,75 @@
       id = heap_->GetObjectId(object);
       if (id == 0) {
         if (object->IsCode() && !Snapshot::IncludesCode(kind_)) {
-          WriteRef(Object::null());
-          return;
+          return WriteRefId(Object::null());
         }
 #if !defined(DART_PRECOMPILED_RUNTIME)
         if (object->IsBytecode() && !Snapshot::IncludesBytecode(kind_)) {
-          WriteRef(Object::null());
-          return;
+          return WriteRefId(Object::null());
         }
 #endif  // !DART_PRECOMPILED_RUNTIME
         if (object->IsSendPort()) {
           // TODO(rmacnak): Do a better job of resetting fields in
           // precompilation and assert this is unreachable.
-          WriteRef(Object::null());
-          return;
+          return WriteRefId(Object::null());
         }
         FATAL("Missing ref");
       }
     }
+    return id;
+  }
 
+ public:
+  void WriteRootRef(RawObject* object) {
+    intptr_t id = WriteRefId(object);
     WriteUnsigned(id);
-
     if (profile_writer_ != nullptr) {
-      if (object_currently_writing_id_ != 0) {
+      profile_writer_->AddRoot({V8SnapshotProfileWriter::kSnapshot, id});
+    }
+  }
+
+  void WriteElementRef(RawObject* object, intptr_t index) {
+    intptr_t id = WriteRefId(object);
+    WriteUnsigned(id);
+    if (profile_writer_ != nullptr) {
+      profile_writer_->AttributeReferenceTo(
+          {V8SnapshotProfileWriter::kSnapshot, object_currently_writing_.id_},
+          {{V8SnapshotProfileWriter::kSnapshot, id},
+           V8SnapshotProfileWriter::Reference::kElement,
+           index});
+    }
+  }
+
+  void WritePropertyRef(RawObject* object, const char* property) {
+    intptr_t id = WriteRefId(object);
+    WriteUnsigned(id);
+    if (profile_writer_ != nullptr) {
+      profile_writer_->AttributeReferenceTo(
+          {V8SnapshotProfileWriter::kSnapshot, object_currently_writing_.id_},
+          {{V8SnapshotProfileWriter::kSnapshot, id},
+           V8SnapshotProfileWriter::Reference::kProperty,
+           profile_writer_->EnsureString(property)});
+    }
+  }
+
+  void WriteOffsetRef(RawObject* object, intptr_t offset) {
+    intptr_t id = WriteRefId(object);
+    WriteUnsigned(id);
+    if (profile_writer_ != nullptr) {
+      const char* property = offsets_table_->FieldNameForOffset(
+          object_currently_writing_.cid_, offset);
+      if (property != nullptr) {
         profile_writer_->AttributeReferenceTo(
-            {V8SnapshotProfileWriter::kSnapshot, object_currently_writing_id_},
-            {V8SnapshotProfileWriter::kSnapshot, id});
+            {V8SnapshotProfileWriter::kSnapshot, object_currently_writing_.id_},
+            {{V8SnapshotProfileWriter::kSnapshot, id},
+             V8SnapshotProfileWriter::Reference::kProperty,
+             profile_writer_->EnsureString(property)});
       } else {
-        ASSERT(is_root);
-        profile_writer_->AddRoot({V8SnapshotProfileWriter::kSnapshot, id});
+        profile_writer_->AttributeReferenceTo(
+            {V8SnapshotProfileWriter::kSnapshot, object_currently_writing_.id_},
+            {{V8SnapshotProfileWriter::kSnapshot, id},
+             V8SnapshotProfileWriter::Reference::kElement,
+             offset});
       }
     }
   }
@@ -289,7 +331,8 @@
     RawObject** from = obj->from();
     RawObject** to = obj->to_snapshot(kind(), args...);
     for (RawObject** p = from; p <= to; p++) {
-      WriteRef(*p);
+      WriteOffsetRef(*p, (p - reinterpret_cast<RawObject**>(obj->ptr())) *
+                             sizeof(RawObject*));
     }
   }
 
@@ -302,10 +345,6 @@
     }
   }
 
-  void WriteRootRef(RawObject* object) {
-    WriteRef(object, /* is_root = */ true);
-  }
-
   void WriteTokenPosition(TokenPosition pos) {
     Write<int32_t>(pos.SnapshotEncode());
   }
@@ -346,8 +385,13 @@
   bool vm_;
 
   V8SnapshotProfileWriter* profile_writer_ = nullptr;
-  intptr_t object_currently_writing_id_ = 0;
-  intptr_t object_currently_writing_start_ = 0;
+  struct ProfilingObject {
+    RawObject* object_ = nullptr;
+    intptr_t id_ = 0;
+    intptr_t stream_start_ = 0;
+    intptr_t cid_ = -1;
+  } object_currently_writing_;
+  OffsetsTable* offsets_table_ = nullptr;
 
 #if defined(SNAPSHOT_BACKTRACE)
   RawObject* current_parent_;
@@ -363,14 +407,14 @@
 #define AutoTraceObjectName(obj, str)                                          \
   SerializerWritingObjectScope scope_##__COUNTER__(s, name(), obj, str)
 
-#define WriteField(obj, field) s->WriteRef(obj->ptr()->field);
-
-#define WriteFieldValue(field, value) s->WriteRef(value);
+#define WriteFieldValue(field, value) s->WritePropertyRef(value, #field);
 
 #define WriteFromTo(obj, ...) s->WriteFromTo(obj, ##__VA_ARGS__);
 
 #define PushFromTo(obj, ...) s->PushFromTo(obj, ##__VA_ARGS__);
 
+#define WriteField(obj, field) s->WritePropertyRef(obj->ptr()->field, #field)
+
 struct SerializerWritingObjectScope {
   SerializerWritingObjectScope(Serializer* serializer,
                                const char* type,
diff --git a/runtime/vm/image_snapshot.cc b/runtime/vm/image_snapshot.cc
index f61c467..cd8ef90 100644
--- a/runtime/vm/image_snapshot.cc
+++ b/runtime/vm/image_snapshot.cc
@@ -294,7 +294,7 @@
 
   for (intptr_t i = 0; i < objects_.length(); i++) {
     const Object& obj = *objects_[i].obj_;
-    AutoTraceImage(section_start, stream, "ROData");
+    AutoTraceImage(obj, section_start, stream);
 
     NoSafepointScope no_safepoint;
     uword start = reinterpret_cast<uword>(obj.raw()) - kHeapObjectTag;
@@ -615,7 +615,7 @@
   NoSafepointScope no_safepoint;
   for (intptr_t i = 0; i < instructions_.length(); i++) {
     const Instructions& insns = *instructions_[i].insns_;
-    AutoTraceImage(0, &this->instructions_blob_stream_, "Instructions");
+    AutoTraceImage(insns, 0, &this->instructions_blob_stream_);
 
     uword beginning = reinterpret_cast<uword>(insns.raw_ptr());
     uword entry = beginning + Instructions::HeaderSize();
diff --git a/runtime/vm/image_snapshot.h b/runtime/vm/image_snapshot.h
index 33c0956..a56c87e 100644
--- a/runtime/vm/image_snapshot.h
+++ b/runtime/vm/image_snapshot.h
@@ -13,6 +13,8 @@
 #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/v8_snapshot_writer.h"
 
 namespace dart {
@@ -184,10 +186,10 @@
   DISALLOW_COPY_AND_ASSIGN(ImageWriter);
 };
 
-#define AutoTraceImage(section_offset, stream, type_name)                      \
+#define AutoTraceImage(object, section_offset, stream)                         \
   auto AutoTraceImagObjectScopeVar##__COUNTER__ =                              \
       TraceImageObjectScope<std::remove_pointer<decltype(stream)>::type>(      \
-          this, section_offset, stream, type_name);
+          this, section_offset, stream, object);
 
 template <typename T>
 class TraceImageObjectScope {
@@ -195,15 +197,22 @@
   TraceImageObjectScope(ImageWriter* writer,
                         intptr_t section_offset,
                         const T* stream,
-                        const char* type)
+                        const Object& object)
       : writer_(writer),
         stream_(stream),
         section_offset_(section_offset),
         start_offset_(stream_->Position() - section_offset) {
     if (writer_->profile_writer_ != nullptr) {
+      Thread* thread = Thread::Current();
+      REUSABLE_CLASS_HANDLESCOPE(thread);
+      REUSABLE_STRING_HANDLESCOPE(thread);
+      Class& klass = thread->ClassHandle();
+      String& name = thread->StringHandle();
+      klass = object.clazz();
+      name = klass.UserVisibleName();
       ASSERT(writer_->offset_space_ != V8SnapshotProfileWriter::kSnapshot);
       writer_->profile_writer_->SetObjectTypeAndName(
-          {writer_->offset_space_, start_offset_}, type, nullptr);
+          {writer_->offset_space_, start_offset_}, name.ToCString(), nullptr);
     }
   }
 
diff --git a/runtime/vm/raw_object.h b/runtime/vm/raw_object.h
index 3f9ef48..01f4cae 100644
--- a/runtime/vm/raw_object.h
+++ b/runtime/vm/raw_object.h
@@ -267,6 +267,7 @@
   friend class InterpreterHelpers;                                             \
   friend class Simulator;                                                      \
   friend class SimulatorHelpers;                                               \
+  friend class OffsetsTable;                                                   \
   DISALLOW_ALLOCATION();                                                       \
   DISALLOW_IMPLICIT_CONSTRUCTORS(Raw##object)
 
@@ -878,6 +879,7 @@
   friend class Precompiler;         // GetClassId
   friend class ObjectOffsetTrait;   // GetClassId
   friend class WriteBarrierUpdateVisitor;  // CheckHeapPointerStore
+  friend class OffsetsTable;
 
   DISALLOW_ALLOCATION();
   DISALLOW_IMPLICIT_CONSTRUCTORS(RawObject);
diff --git a/runtime/vm/raw_object_fields.cc b/runtime/vm/raw_object_fields.cc
new file mode 100644
index 0000000..9e1f8e5
--- /dev/null
+++ b/runtime/vm/raw_object_fields.cc
@@ -0,0 +1,217 @@
+// Copyright (c) 2018, 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.
+
+#include "vm/raw_object_fields.h"
+
+namespace dart {
+
+#if defined(DART_PRECOMPILER)
+
+#define RAW_CLASSES_AND_FIELDS(F)                                              \
+  F(Class, name_)                                                              \
+  F(Class, user_name_)                                                         \
+  F(Class, functions_)                                                         \
+  F(Class, functions_hash_table_)                                              \
+  F(Class, fields_)                                                            \
+  F(Class, offset_in_words_to_field_)                                          \
+  F(Class, interfaces_)                                                        \
+  F(Class, script_)                                                            \
+  F(Class, library_)                                                           \
+  F(Class, type_parameters_)                                                   \
+  F(Class, super_type_)                                                        \
+  F(Class, mixin_)                                                             \
+  F(Class, signature_function_)                                                \
+  F(Class, constants_)                                                         \
+  F(Class, canonical_type_)                                                    \
+  F(Class, invocation_dispatcher_cache_)                                       \
+  F(Class, allocation_stub_)                                                   \
+  F(Class, direct_implementors_)                                               \
+  F(Class, direct_subclasses_)                                                 \
+  F(Class, dependent_code_)                                                    \
+  F(PatchClass, patched_class_)                                                \
+  F(PatchClass, origin_class_)                                                 \
+  F(PatchClass, script_)                                                       \
+  F(PatchClass, library_kernel_data_)                                          \
+  F(Function, name_)                                                           \
+  F(Function, owner_)                                                          \
+  F(Function, result_type_)                                                    \
+  F(Function, parameter_types_)                                                \
+  F(Function, parameter_names_)                                                \
+  F(Function, type_parameters_)                                                \
+  F(Function, data_)                                                           \
+  F(Function, ic_data_array_)                                                  \
+  F(Function, code_)                                                           \
+  F(ClosureData, context_scope_)                                               \
+  F(ClosureData, parent_function_)                                             \
+  F(ClosureData, signature_type_)                                              \
+  F(ClosureData, closure_)                                                     \
+  F(SignatureData, parent_function_)                                           \
+  F(SignatureData, signature_type_)                                            \
+  F(RedirectionData, type_)                                                    \
+  F(RedirectionData, identifier_)                                              \
+  F(RedirectionData, target_)                                                  \
+  F(Field, name_)                                                              \
+  F(Field, owner_)                                                             \
+  F(Field, type_)                                                              \
+  F(Field, guarded_list_length_)                                               \
+  F(Field, dependent_code_)                                                    \
+  F(Field, type_test_cache_)                                                   \
+  F(Script, url_)                                                              \
+  F(Script, resolved_url_)                                                     \
+  F(Script, compile_time_constants_)                                           \
+  F(Script, line_starts_)                                                      \
+  F(Script, debug_positions_)                                                  \
+  F(Script, yield_positions_)                                                  \
+  F(Script, kernel_program_info_)                                              \
+  F(Script, source_)                                                           \
+  F(Library, name_)                                                            \
+  F(Library, url_)                                                             \
+  F(Library, private_key_)                                                     \
+  F(Library, dictionary_)                                                      \
+  F(Library, metadata_)                                                        \
+  F(Library, toplevel_class_)                                                  \
+  F(Library, patch_classes_)                                                   \
+  F(Library, imports_)                                                         \
+  F(Library, exports_)                                                         \
+  F(Library, load_error_)                                                      \
+  F(Library, kernel_data_)                                                     \
+  F(Library, resolved_names_)                                                  \
+  F(Library, exported_names_)                                                  \
+  F(Library, loaded_scripts_)                                                  \
+  F(Namespace, library_)                                                       \
+  F(Namespace, show_names_)                                                    \
+  F(Namespace, hide_names_)                                                    \
+  F(Namespace, metadata_field_)                                                \
+  F(KernelProgramInfo, string_offsets_)                                        \
+  F(KernelProgramInfo, string_data_)                                           \
+  F(KernelProgramInfo, canonical_names_)                                       \
+  F(KernelProgramInfo, metadata_payloads_)                                     \
+  F(KernelProgramInfo, metadata_mappings_)                                     \
+  F(KernelProgramInfo, scripts_)                                               \
+  F(KernelProgramInfo, constants_)                                             \
+  F(KernelProgramInfo, potential_natives_)                                     \
+  F(KernelProgramInfo, potential_pragma_functions_)                            \
+  F(KernelProgramInfo, constants_table_)                                       \
+  F(KernelProgramInfo, libraries_cache_)                                       \
+  F(KernelProgramInfo, classes_cache_)                                         \
+  F(Code, object_pool_)                                                        \
+  F(Code, instructions_)                                                       \
+  F(Code, owner_)                                                              \
+  F(Code, exception_handlers_)                                                 \
+  F(Code, pc_descriptors_)                                                     \
+  F(Code, stackmaps_)                                                          \
+  F(Code, inlined_id_to_function_)                                             \
+  F(Code, code_source_map_)                                                    \
+  F(Bytecode, object_pool_)                                                    \
+  F(Bytecode, instructions_)                                                   \
+  F(Bytecode, function_)                                                       \
+  F(Bytecode, exception_handlers_)                                             \
+  F(Bytecode, pc_descriptors_)                                                 \
+  F(ExceptionHandlers, handled_types_data_)                                    \
+  F(Context, parent_)                                                          \
+  F(SingleTargetCache, target_)                                                \
+  F(UnlinkedCall, target_name_)                                                \
+  F(UnlinkedCall, args_descriptor_)                                            \
+  F(ICData, ic_data_)                                                          \
+  F(ICData, target_name_)                                                      \
+  F(ICData, args_descriptor_)                                                  \
+  F(ICData, owner_)                                                            \
+  F(MegamorphicCache, buckets_)                                                \
+  F(MegamorphicCache, mask_)                                                   \
+  F(MegamorphicCache, target_name_)                                            \
+  F(MegamorphicCache, args_descriptor_)                                        \
+  F(SubtypeTestCache, cache_)                                                  \
+  F(ApiError, message_)                                                        \
+  F(LanguageError, previous_error_)                                            \
+  F(LanguageError, script_)                                                    \
+  F(LanguageError, message_)                                                   \
+  F(LanguageError, formatted_message_)                                         \
+  F(UnhandledException, exception_)                                            \
+  F(UnhandledException, stacktrace_)                                           \
+  F(UnwindError, message_)                                                     \
+  F(LibraryPrefix, name_)                                                      \
+  F(LibraryPrefix, importer_)                                                  \
+  F(LibraryPrefix, imports_)                                                   \
+  F(LibraryPrefix, dependent_code_)                                            \
+  F(TypeArguments, instantiations_)                                            \
+  F(TypeArguments, length_)                                                    \
+  F(TypeArguments, hash_)                                                      \
+  F(Type, type_class_id_)                                                      \
+  F(Type, arguments_)                                                          \
+  F(Type, hash_)                                                               \
+  F(TypeRef, type_)                                                            \
+  F(TypeParameter, name_)                                                      \
+  F(TypeParameter, hash_)                                                      \
+  F(TypeParameter, bound_)                                                     \
+  F(TypeParameter, parameterized_function_)                                    \
+  F(BoundedType, type_)                                                        \
+  F(BoundedType, bound_)                                                       \
+  F(BoundedType, hash_)                                                        \
+  F(BoundedType, type_parameter_)                                              \
+  F(MixinAppType, super_type_)                                                 \
+  F(MixinAppType, mixin_types_)                                                \
+  F(Closure, instantiator_type_arguments_)                                     \
+  F(Closure, function_type_arguments_)                                         \
+  F(Closure, delayed_type_arguments_)                                          \
+  F(Closure, function_)                                                        \
+  F(Closure, context_)                                                         \
+  F(Closure, hash_)                                                            \
+  F(String, length_)                                                           \
+  F(String, hash_)                                                             \
+  F(Array, type_arguments_)                                                    \
+  F(Array, length_)                                                            \
+  F(GrowableObjectArray, type_arguments_)                                      \
+  F(GrowableObjectArray, length_)                                              \
+  F(GrowableObjectArray, data_)                                                \
+  F(LinkedHashMap, type_arguments_)                                            \
+  F(LinkedHashMap, index_)                                                     \
+  F(LinkedHashMap, hash_mask_)                                                 \
+  F(LinkedHashMap, data_)                                                      \
+  F(LinkedHashMap, used_data_)                                                 \
+  F(LinkedHashMap, deleted_keys_)                                              \
+  F(TypedData, length_)                                                        \
+  F(ExternalTypedData, length_)                                                \
+  F(ReceivePort, send_port_)                                                   \
+  F(ReceivePort, handler_)                                                     \
+  F(StackTrace, async_link_)                                                   \
+  F(StackTrace, code_array_)                                                   \
+  F(StackTrace, pc_offset_array_)                                              \
+  F(RegExp, num_bracket_expressions_)                                          \
+  F(RegExp, pattern_)                                                          \
+  F(RegExp, external_one_byte_function_)                                       \
+  F(RegExp, external_two_byte_function_)                                       \
+  F(RegExp, external_one_byte_sticky_function_)                                \
+  F(RegExp, external_two_byte_sticky_function_)                                \
+  F(WeakProperty, key_)                                                        \
+  F(WeakProperty, value_)                                                      \
+  F(MirrorReference, referent_)                                                \
+  F(UserTag, label_)
+
+OffsetsTable::OffsetsTable(Zone* zone) : cached_offsets_(zone) {
+  for (intptr_t i = 0; offsets_table[i].class_id != -1; ++i) {
+    OffsetsTableEntry entry = offsets_table[i];
+    cached_offsets_.Insert({{entry.class_id, entry.offset}, entry.field_name});
+  }
+}
+
+const char* OffsetsTable::FieldNameForOffset(intptr_t class_id,
+                                             intptr_t offset) {
+  return cached_offsets_.LookupValue({class_id, offset});
+}
+
+#define DEFINE_OFFSETS_TABLE_ENTRY(class_name, field_name)                     \
+  {class_name::kClassId, #field_name, OFFSET_OF(Raw##class_name, field_name)},
+
+// clang-format off
+OffsetsTable::OffsetsTableEntry OffsetsTable::offsets_table[] = {
+    RAW_CLASSES_AND_FIELDS(DEFINE_OFFSETS_TABLE_ENTRY)
+    {-1, nullptr, -1}
+};
+// clang-format on
+
+#undef DEFINE_OFFSETS_TABLE_ENTRY
+
+#endif
+
+}  // namespace dart
diff --git a/runtime/vm/raw_object_fields.h b/runtime/vm/raw_object_fields.h
new file mode 100644
index 0000000..9f67643
--- /dev/null
+++ b/runtime/vm/raw_object_fields.h
@@ -0,0 +1,78 @@
+// Copyright (c) 2018, 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.
+//
+// This file (and "raw_object_fields.cc") provide a kind of reflection that
+// allows us to identify the name of fields in hand-written "Raw..." classes
+// (from "raw_object.h") given the class and the offset within the object. This
+// is used for example by the snapshot profile writer ("v8_snapshot_writer.h")
+// to show the property names of these built-in objects in the snapshot profile.
+
+#ifndef RUNTIME_VM_RAW_OBJECT_FIELDS_H_
+#define RUNTIME_VM_RAW_OBJECT_FIELDS_H_
+
+#include <utility>
+
+#include "vm/hash_map.h"
+#include "vm/object.h"
+#include "vm/raw_object.h"
+
+namespace dart {
+
+#if defined(DART_PRECOMPILER)
+
+class OffsetsTable : public ZoneAllocated {
+ public:
+  explicit OffsetsTable(Zone* zone);
+
+  // Returns 'nullptr' if no offset was found.
+  // Otherwise, the returned string is allocated in global static memory.
+  const char* FieldNameForOffset(intptr_t cid, intptr_t offset);
+
+ private:
+  struct OffsetsTableEntry {
+    const intptr_t class_id;
+    const char* field_name;
+    intptr_t offset;
+  };
+
+  static OffsetsTableEntry offsets_table[];
+
+  struct IntAndIntToStringMapTraits {
+    typedef std::pair<intptr_t, intptr_t> Key;
+    typedef const char* Value;
+
+    struct Pair {
+      Key key;
+      Value value;
+      Pair() : key({-1, -1}), value(nullptr) {}
+      Pair(Key k, Value v) : key(k), value(v) {}
+    };
+
+    static Value ValueOf(Pair pair) { return pair.value; }
+    static Key KeyOf(Pair pair) { return pair.key; }
+    static size_t Hashcode(Key key) { return key.first ^ key.second; }
+    static bool IsKeyEqual(Pair x, Key y) {
+      return x.key.first == y.first && x.key.second == y.second;
+    }
+  };
+
+  DirectChainedHashMap<IntAndIntToStringMapTraits> cached_offsets_;
+};
+
+#else
+
+class OffsetsTable : public ZoneAllocated {
+ public:
+  explicit OffsetsTable(Zone* zone) {}
+
+  const char* FieldNameForOffset(intptr_t cid, intptr_t offset) {
+    return nullptr;
+  }
+};
+
+#endif
+
+}  // namespace dart
+
+#endif  // RUNTIME_VM_RAW_OBJECT_FIELDS_H_
diff --git a/runtime/vm/v8_snapshot_writer.cc b/runtime/vm/v8_snapshot_writer.cc
index e945ca0..ce24a25 100644
--- a/runtime/vm/v8_snapshot_writer.cc
+++ b/runtime/vm/v8_snapshot_writer.cc
@@ -57,7 +57,7 @@
   info->type = type_id;
 
   if (name != nullptr) {
-    info->name = EnsureString(name);
+    info->name = EnsureString(OS::SCreate(zone_, "[%s] %s", type, name));
   } else {
     info->name = EnsureString(type);
   }
@@ -69,18 +69,16 @@
 }
 
 void V8SnapshotProfileWriter::AttributeReferenceTo(ObjectId object_id,
-                                                   ObjectId to_object_id) {
-  EnsureId(to_object_id);
+                                                   Reference reference) {
+  EnsureId(reference.to_object_id);
   NodeInfo* info = EnsureId(object_id);
 
-#if defined(DEBUG)
-  // We should never add a reference twice.
-  for (intptr_t i = 0; i < info->edges->length(); ++i) {
-    ASSERT(info->edges->At(i).to_node != object_id);
-  }
-#endif
-
-  info->edges->Add(EdgeInfo{to_object_id});
+  ASSERT(reference.offset_or_name >= 0);
+  info->edges->Add({
+      reference.reference_type == Reference::kElement ? kElement : kProperty,
+      reference.offset_or_name,
+      reference.to_object_id,
+  });
   ++edge_count_;
 }
 
@@ -134,9 +132,8 @@
 
 void V8SnapshotProfileWriter::WriteEdgeInfo(JSONWriter* writer,
                                             const EdgeInfo& info) {
-  writer->PrintValue64(kProperty);  // type, not really used atm
-  writer->PrintValue64(
-      kPropertyString);  // name_or_index, not really used either
+  writer->PrintValue64(info.type);
+  writer->PrintValue64(info.name_or_index);
   writer->PrintValue64(nodes_.LookupValue(info.to_node).offset);
   writer->PrintNewline();
 }
@@ -235,8 +232,8 @@
     writer->OpenArray("edges");
 
     // Write references from the artificial root to the actual roots.
-    for (ObjectId root : roots_) {
-      WriteEdgeInfo(writer, {root});
+    for (intptr_t i = 0; i < roots_.length(); ++i) {
+      WriteEdgeInfo(writer, {kElement, i, roots_[i]});
     }
 
     ObjectIdToNodeInfoTraits::Pair* entry = nullptr;
diff --git a/runtime/vm/v8_snapshot_writer.h b/runtime/vm/v8_snapshot_writer.h
index c506b2e..fa9ee85 100644
--- a/runtime/vm/v8_snapshot_writer.h
+++ b/runtime/vm/v8_snapshot_writer.h
@@ -11,6 +11,7 @@
 #include "vm/allocation.h"
 #include "vm/hash_map.h"
 #include "vm/json_writer.h"
+#include "vm/object.h"
 
 namespace dart {
 
@@ -29,10 +30,7 @@
 
   static Key KeyOf(Pair pair) { return pair.key; }
 
-  static size_t Hashcode(Key key) {
-    /// SAMIR_TODO
-    return 0;
-  }
+  static size_t Hashcode(Key key) { return String::Hash(key, strlen(key)); }
 
   static bool IsKeyEqual(Pair x, Key y) { return strcmp(x.key, y) == 0; }
 };
@@ -51,6 +49,22 @@
 
   typedef std::pair<IdSpace, intptr_t> ObjectId;
 
+  struct Reference {
+    ObjectId to_object_id;
+    enum {
+      kElement,
+      kProperty,
+    } reference_type;
+    intptr_t offset_or_name;
+  };
+
+  enum ConstantStrings {
+    kUnknownString = 0,
+    kPropertyString = 1,
+    kObjectString = 2,
+    kArtificialRootString = 3,
+  };
+
 #if !defined(DART_PRECOMPILER)
   explicit V8SnapshotProfileWriter(Zone* zone) {}
   virtual ~V8SnapshotProfileWriter() {}
@@ -59,8 +73,9 @@
                             const char* type,
                             const char* name) {}
   void AttributeBytesTo(ObjectId object_id, size_t num_bytes) {}
-  void AttributeReferenceTo(ObjectId object_id, ObjectId to_object_id) {}
+  void AttributeReferenceTo(ObjectId object_id, Reference reference) {}
   void AddRoot(ObjectId object_id) {}
+  intptr_t EnsureString(const char* str) { return 0; }
 #else
   explicit V8SnapshotProfileWriter(Zone* zone);
   virtual ~V8SnapshotProfileWriter() {}
@@ -80,7 +95,7 @@
   // Records that a reference to the object with id 'to_object_id' was written
   // in order to serialize the object with id 'object_id'. This does not affect
   // the number of bytes charged to 'object_id'.
-  void AttributeReferenceTo(ObjectId object_id, ObjectId to_object_id);
+  void AttributeReferenceTo(ObjectId object_id, Reference reference);
 
   // Marks an object as being a root in the graph. Used for analysis of the
   // graph.
@@ -89,12 +104,15 @@
   // Write to a file in the V8 Snapshot Profile (JSON/.heapsnapshot) format.
   void Write(const char* file);
 
+  intptr_t EnsureString(const char* str);
+
  private:
   static constexpr intptr_t kNumNodeFields = 5;
   static constexpr intptr_t kNumEdgeFields = 3;
 
   struct EdgeInfo {
-    // 'type' and 'name_or_index' aren't supported yet.
+    intptr_t type;
+    intptr_t name_or_index;
     ObjectId to_node;
   };
 
@@ -131,18 +149,10 @@
   static NodeInfo ArtificialRoot();
 
   NodeInfo* EnsureId(ObjectId object_id);
-  intptr_t EnsureString(const char* str);
   static intptr_t NodeIdFor(ObjectId id) {
     return (id.second << kIdSpaceBits) | id.first;
   }
 
-  enum ConstantStrings {
-    kUnknownString = 0,
-    kPropertyString = 1,
-    kObjectString = 2,
-    kArtificialRootString = 3,
-  };
-
   enum ConstantEdgeTypes {
     kContext = 0,
     kElement = 1,
diff --git a/runtime/vm/vm_sources.gni b/runtime/vm/vm_sources.gni
index b0e7872..a9b02fc 100644
--- a/runtime/vm/vm_sources.gni
+++ b/runtime/vm/vm_sources.gni
@@ -215,6 +215,8 @@
   "random.h",
   "raw_object.cc",
   "raw_object.h",
+  "raw_object_fields.cc",
+  "raw_object_fields.h",
   "raw_object_snapshot.cc",
   "regexp.cc",
   "regexp.h",