| // 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/v8_snapshot_writer.h" |
| |
| #include "vm/dart.h" |
| #include "vm/os.h" |
| |
| namespace dart { |
| |
| const V8SnapshotProfileWriter::ObjectId |
| V8SnapshotProfileWriter::kArtificialRootId{IdSpace::kArtificial, 0}; |
| |
| #if defined(DART_PRECOMPILER) |
| |
| V8SnapshotProfileWriter::V8SnapshotProfileWriter(Zone* zone) |
| : zone_(zone), |
| nodes_(zone_), |
| node_types_(zone_), |
| edge_types_(zone_), |
| strings_(zone_), |
| roots_(zone_) { |
| intptr_t idx = edge_types_.Add("context"); |
| ASSERT_EQUAL(idx, static_cast<intptr_t>(Edge::Type::kContext)); |
| idx = edge_types_.Add("element"); |
| ASSERT_EQUAL(idx, static_cast<intptr_t>(Edge::Type::kElement)); |
| idx = edge_types_.Add("property"); |
| ASSERT_EQUAL(idx, static_cast<intptr_t>(Edge::Type::kProperty)); |
| idx = edge_types_.Add("internal"); |
| ASSERT_EQUAL(idx, static_cast<intptr_t>(Edge::Type::kInternal)); |
| |
| SetObjectTypeAndName(kArtificialRootId, "ArtificialRoot", |
| "<artificial root>"); |
| } |
| |
| void V8SnapshotProfileWriter::SetObjectTypeAndName(const ObjectId& object_id, |
| const char* type, |
| const char* name) { |
| ASSERT(type != nullptr); |
| NodeInfo* info = EnsureId(object_id); |
| const intptr_t type_index = node_types_.Add(type); |
| if (info->type != kInvalidString && info->type != type_index) { |
| FATAL("Attempting to assign mismatching type %s to node %s", type, |
| info->ToCString(this, zone_)); |
| } |
| info->type = type_index; |
| // Don't overwrite any existing name. |
| if (info->name == kInvalidString) { |
| info->name = strings_.Add(name); |
| } |
| } |
| |
| void V8SnapshotProfileWriter::AttributeBytesTo(const ObjectId& object_id, |
| size_t num_bytes) { |
| EnsureId(object_id)->self_size += num_bytes; |
| } |
| |
| void V8SnapshotProfileWriter::AttributeReferenceTo( |
| const ObjectId& from_object_id, |
| const Reference& reference, |
| const ObjectId& to_object_id) { |
| ASSERT(reference.IsElement() ? reference.offset >= 0 |
| : reference.name != nullptr); |
| EnsureId(to_object_id); |
| const Edge edge(this, reference); |
| EnsureId(from_object_id)->AddEdge(edge, to_object_id); |
| } |
| |
| void V8SnapshotProfileWriter::AttributeDroppedReferenceTo( |
| const ObjectId& from_object_id, |
| const Reference& reference, |
| const ObjectId& to_object_id, |
| const ObjectId& replacement_object_id) { |
| ASSERT(to_object_id.IsArtificial()); |
| ASSERT(!replacement_object_id.IsArtificial()); |
| ASSERT(reference.IsElement() ? reference.offset >= 0 |
| : reference.name != nullptr); |
| |
| // The target node is added normally. |
| AttributeReferenceTo(from_object_id, reference, to_object_id); |
| |
| EnsureId(replacement_object_id); |
| // Put the replacement node at an invalid offset or name that can still be |
| // associated with the real one. For offsets, this is the negative offset. |
| // For names, it's the name prefixed with ":replacement_". |
| Reference replacement_reference = |
| reference.IsElement() ? Reference::Element(-reference.offset) |
| : Reference::Property(OS::SCreate( |
| zone_, ":replacement_%s", reference.name)); |
| const Edge replacement_edge(this, replacement_reference); |
| EnsureId(from_object_id)->AddEdge(replacement_edge, replacement_object_id); |
| } |
| |
| bool V8SnapshotProfileWriter::HasId(const ObjectId& object_id) { |
| return nodes_.HasKey(object_id); |
| } |
| |
| V8SnapshotProfileWriter::NodeInfo* V8SnapshotProfileWriter::EnsureId( |
| const ObjectId& object_id) { |
| if (!HasId(object_id)) { |
| nodes_.Insert(NodeInfo(this, object_id)); |
| } |
| return nodes_.Lookup(object_id); |
| } |
| |
| const char* V8SnapshotProfileWriter::NodeInfo::ToCString( |
| V8SnapshotProfileWriter* profile_writer, |
| Zone* zone) const { |
| JSONWriter writer; |
| WriteDebug(profile_writer, &writer); |
| return OS::SCreate(zone, "%s", writer.buffer()->buffer()); |
| } |
| |
| void V8SnapshotProfileWriter::NodeInfo::Write( |
| V8SnapshotProfileWriter* profile_writer, |
| JSONWriter* writer) const { |
| ASSERT(id.space() != IdSpace::kInvalid); |
| if (type == kInvalidString) { |
| FATAL("No type given for node %s", id.ToCString(profile_writer->zone_)); |
| } |
| writer->PrintValue(type); |
| if (name != kInvalidString) { |
| writer->PrintValue(name); |
| } else { |
| ASSERT(profile_writer != nullptr); |
| // If we don't already have a name for the node, we lazily create a default |
| // one. This is safe since the strings table is written out after the nodes. |
| const intptr_t name = profile_writer->strings_.AddFormatted( |
| "Unnamed [%s] (nil)", profile_writer->node_types_.At(type)); |
| writer->PrintValue(name); |
| } |
| id.Write(writer); |
| writer->PrintValue(self_size); |
| writer->PrintValue64(edges->Length()); |
| } |
| |
| void V8SnapshotProfileWriter::NodeInfo::WriteDebug( |
| V8SnapshotProfileWriter* profile_writer, |
| JSONWriter* writer) const { |
| writer->OpenObject(); |
| if (type != kInvalidString) { |
| writer->PrintProperty("type", profile_writer->node_types_.At(type)); |
| } |
| if (name != kInvalidString) { |
| writer->PrintProperty("name", profile_writer->strings_.At(name)); |
| } |
| id.WriteDebug(writer, "id"); |
| writer->PrintProperty("self_size", self_size); |
| edges->WriteDebug(profile_writer, writer, "edges"); |
| writer->CloseObject(); |
| } |
| |
| const char* V8SnapshotProfileWriter::ObjectId::ToCString(Zone* zone) const { |
| JSONWriter writer; |
| WriteDebug(&writer); |
| return OS::SCreate(zone, "%s", writer.buffer()->buffer()); |
| } |
| |
| void V8SnapshotProfileWriter::ObjectId::Write(JSONWriter* writer, |
| const char* property) const { |
| if (property != nullptr) { |
| writer->PrintProperty64(property, encoded_); |
| } else { |
| writer->PrintValue64(encoded_); |
| } |
| } |
| |
| void V8SnapshotProfileWriter::ObjectId::WriteDebug(JSONWriter* writer, |
| const char* property) const { |
| writer->OpenObject(property); |
| writer->PrintProperty("space", IdSpaceToCString(space())); |
| writer->PrintProperty64("nonce", nonce()); |
| writer->CloseObject(); |
| } |
| |
| const char* V8SnapshotProfileWriter::ObjectId::IdSpaceToCString(IdSpace space) { |
| switch (space) { |
| case IdSpace::kInvalid: |
| return "Invalid"; |
| case IdSpace::kSnapshot: |
| return "Snapshot"; |
| case IdSpace::kVmText: |
| return "VmText"; |
| case IdSpace::kIsolateText: |
| return "IsolateText"; |
| case IdSpace::kVmData: |
| return "VmData"; |
| case IdSpace::kIsolateData: |
| return "IsolateData"; |
| case IdSpace::kArtificial: |
| return "Artificial"; |
| default: |
| UNREACHABLE(); |
| } |
| } |
| |
| const char* V8SnapshotProfileWriter::EdgeMap::ToCString( |
| V8SnapshotProfileWriter* profile_writer, |
| Zone* zone) const { |
| JSONWriter writer; |
| WriteDebug(profile_writer, &writer); |
| return OS::SCreate(zone, "%s", writer.buffer()->buffer()); |
| } |
| |
| void V8SnapshotProfileWriter::EdgeMap::WriteDebug( |
| V8SnapshotProfileWriter* profile_writer, |
| JSONWriter* writer, |
| const char* property) const { |
| writer->OpenArray(property); |
| auto edge_it = GetIterator(); |
| while (auto const pair = edge_it.Next()) { |
| pair->edge.WriteDebug(profile_writer, writer, pair->target); |
| } |
| writer->CloseArray(); |
| } |
| |
| void V8SnapshotProfileWriter::Edge::Write( |
| V8SnapshotProfileWriter* profile_writer, |
| JSONWriter* writer, |
| const ObjectId& target_id) const { |
| ASSERT(type != Type::kInvalid); |
| writer->PrintValue64(static_cast<intptr_t>(type)); |
| writer->PrintValue64(name_or_offset); |
| auto const target = profile_writer->nodes_.LookupValue(target_id); |
| writer->PrintValue64(target.offset()); |
| } |
| |
| void V8SnapshotProfileWriter::Edge::WriteDebug( |
| V8SnapshotProfileWriter* profile_writer, |
| JSONWriter* writer, |
| const ObjectId& target_id) const { |
| writer->OpenObject(); |
| if (type != Type::kInvalid) { |
| writer->PrintProperty( |
| "type", profile_writer->edge_types_.At(static_cast<intptr_t>(type))); |
| } |
| if (type == Type::kProperty) { |
| writer->PrintProperty("name", profile_writer->strings_.At(name_or_offset)); |
| } else { |
| writer->PrintProperty64("offset", name_or_offset); |
| } |
| auto const target = profile_writer->nodes_.LookupValue(target_id); |
| target.id.WriteDebug(writer, "target"); |
| writer->CloseObject(); |
| } |
| |
| void V8SnapshotProfileWriter::AddRoot(const ObjectId& object_id, |
| const char* name) { |
| // HeapSnapshotWorker.HeapSnapshot.calculateDistances (from HeapSnapshot.js) |
| // assumes that the root does not have more than one edge to any other node |
| // (most likely an oversight). |
| if (roots_.HasKey(object_id)) return; |
| roots_.Insert(object_id); |
| |
| auto const str_index = strings_.Add(name); |
| auto const root = nodes_.Lookup(kArtificialRootId); |
| ASSERT(root != nullptr); |
| root->AddEdge(str_index != kInvalidString |
| ? Edge(this, Edge::Type::kProperty, str_index) |
| : Edge(this, Edge::Type::kInternal, root->edges->Length()), |
| object_id); |
| } |
| |
| intptr_t V8SnapshotProfileWriter::StringsTable::Add(const char* str) { |
| if (str == nullptr) return kInvalidString; |
| if (auto const kv = index_map_.Lookup(str)) { |
| return kv->value; |
| } |
| const char* new_str = OS::SCreate(zone_, "%s", str); |
| const intptr_t index = strings_.length(); |
| strings_.Add(new_str); |
| index_map_.Insert({new_str, index}); |
| return index; |
| } |
| |
| intptr_t V8SnapshotProfileWriter::StringsTable::AddFormatted(const char* fmt, |
| ...) { |
| va_list args; |
| va_start(args, fmt); |
| const char* str = OS::VSCreate(zone_, fmt, args); |
| va_end(args); |
| if (auto const kv = index_map_.Lookup(str)) { |
| return kv->value; |
| } |
| const intptr_t index = strings_.length(); |
| strings_.Add(str); |
| index_map_.Insert({str, index}); |
| return index; |
| } |
| |
| const char* V8SnapshotProfileWriter::StringsTable::At(intptr_t index) const { |
| if (index > strings_.length()) return nullptr; |
| return strings_[index]; |
| } |
| |
| void V8SnapshotProfileWriter::StringsTable::Write(JSONWriter* writer, |
| const char* property) const { |
| writer->OpenArray(property); |
| for (auto const str : strings_) { |
| writer->PrintValue(str); |
| writer->PrintNewline(); |
| } |
| writer->CloseArray(); |
| } |
| |
| void V8SnapshotProfileWriter::Write(JSONWriter* writer) { |
| writer->OpenObject(); |
| |
| writer->OpenObject("snapshot"); |
| { |
| writer->OpenObject("meta"); |
| |
| { |
| writer->OpenArray("node_fields"); |
| writer->PrintValue("type"); |
| writer->PrintValue("name"); |
| writer->PrintValue("id"); |
| writer->PrintValue("self_size"); |
| writer->PrintValue("edge_count"); |
| writer->CloseArray(); |
| } |
| |
| { |
| writer->OpenArray("node_types"); |
| node_types_.Write(writer); |
| writer->CloseArray(); |
| } |
| |
| { |
| writer->OpenArray("edge_fields"); |
| writer->PrintValue("type"); |
| writer->PrintValue("name_or_index"); |
| writer->PrintValue("to_node"); |
| writer->CloseArray(); |
| } |
| |
| { |
| writer->OpenArray("edge_types"); |
| edge_types_.Write(writer); |
| writer->CloseArray(); |
| } |
| |
| writer->CloseObject(); |
| |
| writer->PrintProperty64("node_count", nodes_.Size()); |
| { |
| intptr_t edge_count = 0; |
| auto nodes_it = nodes_.GetIterator(); |
| while (auto const info = nodes_it.Next()) { |
| // All nodes should have an edge map, though it may be empty. |
| ASSERT(info->edges != nullptr); |
| edge_count += info->edges->Length(); |
| } |
| writer->PrintProperty64("edge_count", edge_count); |
| } |
| } |
| writer->CloseObject(); |
| |
| { |
| writer->OpenArray("nodes"); |
| // Always write the information for the artificial root first. |
| auto const root = nodes_.Lookup(kArtificialRootId); |
| ASSERT(root != nullptr); |
| intptr_t offset = 0; |
| root->set_offset(offset); |
| root->Write(this, writer); |
| offset += kNumNodeFields; |
| auto nodes_it = nodes_.GetIterator(); |
| for (auto entry = nodes_it.Next(); entry != nullptr; |
| entry = nodes_it.Next()) { |
| if (entry->id == kArtificialRootId) continue; |
| entry->set_offset(offset); |
| entry->Write(this, writer); |
| offset += kNumNodeFields; |
| } |
| writer->CloseArray(); |
| } |
| |
| { |
| auto write_edges = [&](const NodeInfo& info) { |
| auto edges_it = info.edges->GetIterator(); |
| while (auto const pair = edges_it.Next()) { |
| pair->edge.Write(this, writer, pair->target); |
| } |
| }; |
| writer->OpenArray("edges"); |
| // Always write the information for the artificial root first. |
| auto const root = nodes_.Lookup(kArtificialRootId); |
| ASSERT(root != nullptr); |
| write_edges(*root); |
| auto nodes_it = nodes_.GetIterator(); |
| while (auto const entry = nodes_it.Next()) { |
| if (entry->id == kArtificialRootId) continue; |
| write_edges(*entry); |
| } |
| writer->CloseArray(); |
| } |
| |
| // Must happen after any calls to WriteNodeInfo, as those calls may add more |
| // strings. |
| strings_.Write(writer, "strings"); |
| |
| writer->CloseObject(); |
| } |
| |
| void V8SnapshotProfileWriter::Write(const char* filename) { |
| JSONWriter json; |
| Write(&json); |
| |
| auto file_open = Dart::file_open_callback(); |
| auto file_write = Dart::file_write_callback(); |
| auto file_close = Dart::file_close_callback(); |
| if ((file_open == nullptr) || (file_write == nullptr) || |
| (file_close == nullptr)) { |
| OS::PrintErr("warning: Could not access file callbacks."); |
| return; |
| } |
| |
| auto file = file_open(filename, /*write=*/true); |
| if (file == nullptr) { |
| OS::PrintErr("warning: Failed to write snapshot profile: %s\n", filename); |
| } else { |
| char* output = nullptr; |
| intptr_t output_length = 0; |
| json.Steal(&output, &output_length); |
| file_write(output, output_length, file); |
| free(output); |
| file_close(file); |
| } |
| } |
| |
| #endif |
| |
| } // namespace dart |