|  | // 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. | 
|  |  | 
|  | #ifndef RUNTIME_VM_V8_SNAPSHOT_WRITER_H_ | 
|  | #define RUNTIME_VM_V8_SNAPSHOT_WRITER_H_ | 
|  |  | 
|  | #include <utility> | 
|  |  | 
|  | #include "platform/assert.h" | 
|  | #include "vm/allocation.h" | 
|  | #include "vm/hash_map.h" | 
|  | #include "vm/hash_table.h" | 
|  | #include "vm/json_writer.h" | 
|  | #include "vm/object.h" | 
|  |  | 
|  | namespace dart { | 
|  |  | 
|  | enum class IdSpace : uint8_t { | 
|  | kInvalid = 0,   // So default-constructed ObjectIds are invalid. | 
|  | kSnapshot = 1,  // Can be VM or Isolate heap, they share ids. | 
|  | kVmText = 2, | 
|  | kIsolateText = 3, | 
|  | kVmData = 4, | 
|  | kIsolateData = 5, | 
|  | kArtificial = 6,  // Artificial objects (e.g. the global root). | 
|  | // Change ObjectId::kIdSpaceBits to use last entry if more are added. | 
|  | }; | 
|  |  | 
|  | class V8SnapshotProfileWriter : public ZoneAllocated { | 
|  | public: | 
|  | struct ObjectId { | 
|  | ObjectId() : ObjectId(IdSpace::kInvalid, -1) {} | 
|  | ObjectId(IdSpace space, int64_t nonce) | 
|  | : encoded_((static_cast<uint64_t>(nonce) << kIdSpaceBits) | | 
|  | static_cast<intptr_t>(space)) { | 
|  | ASSERT(Utils::IsInt(kBitsPerInt64 - kIdSpaceBits, nonce)); | 
|  | } | 
|  |  | 
|  | inline bool operator!=(const ObjectId& other) const { | 
|  | return encoded_ != other.encoded_; | 
|  | } | 
|  | inline bool operator==(const ObjectId& other) const { | 
|  | return !(*this != other); | 
|  | } | 
|  |  | 
|  | inline uword Hash() const { return Utils::WordHash(encoded_); } | 
|  | inline int64_t nonce() const { return encoded_ >> kIdSpaceBits; } | 
|  | inline IdSpace space() const { | 
|  | return static_cast<IdSpace>(encoded_ & kIdSpaceMask); | 
|  | } | 
|  | inline bool IsArtificial() const { return space() == IdSpace::kArtificial; } | 
|  |  | 
|  | const char* ToCString(Zone* zone) const; | 
|  | void Write(JSONWriter* writer, const char* property = nullptr) const; | 
|  | void WriteDebug(JSONWriter* writer, const char* property = nullptr) const; | 
|  |  | 
|  | private: | 
|  | static constexpr size_t kIdSpaceBits = | 
|  | Utils::BitLength(static_cast<int64_t>(IdSpace::kArtificial)); | 
|  | static constexpr int64_t kIdSpaceMask = | 
|  | Utils::NBitMask<int64_t>(kIdSpaceBits); | 
|  | static const char* IdSpaceToCString(IdSpace space); | 
|  |  | 
|  | int64_t encoded_; | 
|  | }; | 
|  |  | 
|  | struct Reference { | 
|  | enum class Type { | 
|  | kElement, | 
|  | kProperty, | 
|  | } type; | 
|  | intptr_t offset;   // kElement | 
|  | const char* name;  // kProperty | 
|  |  | 
|  | static Reference Element(intptr_t offset) { | 
|  | return {Type::kElement, offset, nullptr}; | 
|  | } | 
|  | static Reference Property(const char* name) { | 
|  | return {Type::kProperty, 0, name}; | 
|  | } | 
|  |  | 
|  | bool IsElement() const { return type == Type::kElement; } | 
|  | }; | 
|  |  | 
|  | static const ObjectId kArtificialRootId; | 
|  |  | 
|  | #if !defined(DART_PRECOMPILER) | 
|  | explicit V8SnapshotProfileWriter(Zone* zone) {} | 
|  | virtual ~V8SnapshotProfileWriter() {} | 
|  |  | 
|  | void SetObjectTypeAndName(const ObjectId& object_id, | 
|  | const char* type, | 
|  | const char* name) {} | 
|  | void AttributeBytesTo(const ObjectId& object_id, size_t num_bytes) {} | 
|  | void AttributeReferenceTo(const ObjectId& from_object_id, | 
|  | const Reference& reference, | 
|  | const ObjectId& to_object_id) {} | 
|  | void AttributeWeakReferenceTo(const ObjectId& from_object_id, | 
|  | const Reference& reference, | 
|  | const ObjectId& to_object_id, | 
|  | const ObjectId& replacement_object_id) {} | 
|  | void AddRoot(const ObjectId& object_id, const char* name = nullptr) {} | 
|  | bool HasId(const ObjectId& object_id) { return false; } | 
|  | #else | 
|  | explicit V8SnapshotProfileWriter(Zone* zone); | 
|  | virtual ~V8SnapshotProfileWriter() {} | 
|  |  | 
|  | // Records that the object referenced by 'object_id' has type 'type'. The | 
|  | // 'type' for all 'Instance's should be 'Instance', not the user-visible type | 
|  | // and use 'name' for the real type instead. | 
|  | void SetObjectTypeAndName(const ObjectId& object_id, | 
|  | const char* type, | 
|  | const char* name); | 
|  |  | 
|  | // Charges 'num_bytes'-many bytes to 'object_id'. In a clustered snapshot, | 
|  | // objects can have their data spread across multiple sections, so this can be | 
|  | // called multiple times for the same object. | 
|  | void AttributeBytesTo(const ObjectId& object_id, size_t num_bytes); | 
|  |  | 
|  | // Records that a reference to the object with id 'to_object_id' was written | 
|  | // in order to serialize the object with id 'from_object_id'. This does not | 
|  | // affect the number of bytes charged to 'from_object_id'. | 
|  | void AttributeReferenceTo(const ObjectId& from_object_id, | 
|  | const Reference& reference, | 
|  | const ObjectId& to_object_id); | 
|  |  | 
|  | // Records that a weak serialization reference to a dropped object | 
|  | // with id 'to_object_id' was written in order to serialize the object with id | 
|  | // 'from_object_id'. 'to_object_id' must be an artificial node and | 
|  | // 'replacement_object_id' is recorded as the replacement for the | 
|  | // dropped object in the snapshot. This does not affect the number of | 
|  | // bytes charged to 'from_object_id'. | 
|  | void AttributeDroppedReferenceTo(const ObjectId& from_object_id, | 
|  | const Reference& reference, | 
|  | const ObjectId& to_object_id, | 
|  | const ObjectId& replacement_object_id); | 
|  |  | 
|  | // Marks an object as being a root in the graph. Used for analysis of | 
|  | // the graph. | 
|  | void AddRoot(const ObjectId& object_id, const char* name = nullptr); | 
|  |  | 
|  | // Write to a file in the V8 Snapshot Profile (JSON/.heapsnapshot) format. | 
|  | void Write(const char* file); | 
|  |  | 
|  | // Whether the given object ID has been added to the profile (via AddRoot, | 
|  | // SetObjectTypeAndName, etc.). | 
|  | bool HasId(const ObjectId& object_id); | 
|  |  | 
|  | private: | 
|  | static constexpr intptr_t kInvalidString = | 
|  | CStringIntMapKeyValueTrait::kNoValue; | 
|  | static constexpr intptr_t kNumNodeFields = 5; | 
|  | static constexpr intptr_t kNumEdgeFields = 3; | 
|  |  | 
|  | struct Edge { | 
|  | enum class Type : int32_t { | 
|  | kInvalid = -1, | 
|  | kContext = 0, | 
|  | kElement = 1, | 
|  | kProperty = 2, | 
|  | kInternal = 3, | 
|  | kHidden = 4, | 
|  | kShortcut = 5, | 
|  | kWeak = 6, | 
|  | kExtra = 7, | 
|  | }; | 
|  |  | 
|  | Edge() : Edge(nullptr, Type::kInvalid, -1) {} | 
|  | Edge(V8SnapshotProfileWriter* profile_writer, const Reference& reference) | 
|  | : Edge(profile_writer, | 
|  | reference.type == Reference::Type::kElement ? Type::kElement | 
|  | : Type::kProperty, | 
|  | reference.type == Reference::Type::kElement | 
|  | ? reference.offset | 
|  | : profile_writer->strings_.Add(reference.name)) {} | 
|  | Edge(V8SnapshotProfileWriter* profile_writer, | 
|  | Type type, | 
|  | intptr_t name_or_offset) | 
|  | : type(type), name_or_offset(name_or_offset) {} | 
|  |  | 
|  | inline bool operator!=(const Edge& other) { | 
|  | return type != other.type || name_or_offset != other.name_or_offset; | 
|  | } | 
|  | inline bool operator==(const Edge& other) { return !(*this != other); } | 
|  |  | 
|  | void Write(V8SnapshotProfileWriter* profile_writer, | 
|  | JSONWriter* writer, | 
|  | const ObjectId& target_id) const; | 
|  | void WriteDebug(V8SnapshotProfileWriter* profile_writer, | 
|  | JSONWriter* writer, | 
|  | const ObjectId& target_id) const; | 
|  |  | 
|  | Type type; | 
|  | int32_t name_or_offset; | 
|  | }; | 
|  |  | 
|  | struct EdgeToObjectIdMapTrait { | 
|  | using Key = Edge; | 
|  | using Value = ObjectId; | 
|  |  | 
|  | struct Pair { | 
|  | Pair() : edge{}, target(kArtificialRootId) {} | 
|  | Pair(Key key, Value value) : edge(key), target(value) {} | 
|  | Edge edge; | 
|  | ObjectId target; | 
|  | }; | 
|  |  | 
|  | static Key KeyOf(Pair kv) { return kv.edge; } | 
|  | static Value ValueOf(Pair kv) { return kv.target; } | 
|  | static uword Hash(Key key) { | 
|  | return FinalizeHash( | 
|  | CombineHashes(static_cast<intptr_t>(key.type), key.name_or_offset)); | 
|  | } | 
|  | static bool IsKeyEqual(Pair kv, Key key) { return kv.edge == key; } | 
|  | }; | 
|  |  | 
|  | struct EdgeMap : public ZoneDirectChainedHashMap<EdgeToObjectIdMapTrait> { | 
|  | explicit EdgeMap(Zone* zone) | 
|  | : ZoneDirectChainedHashMap<EdgeToObjectIdMapTrait>(zone, 1) {} | 
|  |  | 
|  | const char* ToCString(V8SnapshotProfileWriter* profile_writer, | 
|  | Zone* zone) const; | 
|  | void WriteDebug(V8SnapshotProfileWriter* profile_writer, | 
|  | JSONWriter* writer, | 
|  | const char* property = nullptr) const; | 
|  | }; | 
|  |  | 
|  | struct NodeInfo { | 
|  | NodeInfo() {} | 
|  | NodeInfo(V8SnapshotProfileWriter* profile_writer, | 
|  | const ObjectId& id, | 
|  | intptr_t type = kInvalidString, | 
|  | intptr_t name = kInvalidString) | 
|  | : id(id), | 
|  | edges(new(profile_writer->zone_) EdgeMap(profile_writer->zone_)), | 
|  | type(type), | 
|  | name(name) {} | 
|  |  | 
|  | inline bool operator!=(const NodeInfo& other) { | 
|  | return id != other.id || type != other.type || name != other.name || | 
|  | self_size != other.self_size || edges != other.edges || | 
|  | offset_ != other.offset_; | 
|  | } | 
|  | inline bool operator==(const NodeInfo& other) { return !(*this != other); } | 
|  |  | 
|  | void AddEdge(const Edge& edge, const ObjectId& target) { | 
|  | edges->Insert({edge, target}); | 
|  | } | 
|  | bool HasEdge(const Edge& edge) { return edges->HasKey(edge); } | 
|  |  | 
|  | const char* ToCString(V8SnapshotProfileWriter* profile_writer, | 
|  | Zone* zone) const; | 
|  | void Write(V8SnapshotProfileWriter* profile_writer, | 
|  | JSONWriter* writer) const; | 
|  | void WriteDebug(V8SnapshotProfileWriter* profile_writer, | 
|  | JSONWriter* writer) const; | 
|  |  | 
|  | intptr_t offset() const { return offset_; } | 
|  | void set_offset(intptr_t offset) { | 
|  | ASSERT_EQUAL(offset_, -1); | 
|  | offset_ = offset; | 
|  | } | 
|  |  | 
|  | ObjectId id; | 
|  | EdgeMap* edges = nullptr; | 
|  | intptr_t type = kInvalidString; | 
|  | intptr_t name = kInvalidString; | 
|  | intptr_t self_size = 0; | 
|  |  | 
|  | private: | 
|  | // Populated during serialization. | 
|  | intptr_t offset_ = -1; | 
|  | // 'trace_node_id' isn't supported. | 
|  | // 'edge_count' is computed on-demand. | 
|  | }; | 
|  |  | 
|  | NodeInfo* EnsureId(const ObjectId& object_id); | 
|  | void Write(JSONWriter* writer); | 
|  |  | 
|  | // Class that encapsulates both an array of strings and a mapping from | 
|  | // strings to their index in the array. | 
|  | class StringsTable { | 
|  | public: | 
|  | explicit StringsTable(Zone* zone) | 
|  | : zone_(zone), index_map_(zone), strings_(zone, 2) {} | 
|  |  | 
|  | intptr_t Add(const char* str); | 
|  | intptr_t AddFormatted(const char* fmt, ...) PRINTF_ATTRIBUTE(2, 3); | 
|  | const char* At(intptr_t index) const; | 
|  | void Write(JSONWriter* writer, const char* property = nullptr) const; | 
|  |  | 
|  | private: | 
|  | Zone* zone_; | 
|  | CStringIntMap index_map_; | 
|  | GrowableArray<const char*> strings_; | 
|  | }; | 
|  |  | 
|  | struct ObjectIdToNodeInfoTraits { | 
|  | typedef NodeInfo Pair; | 
|  | typedef ObjectId Key; | 
|  | typedef Pair Value; | 
|  |  | 
|  | static Key KeyOf(const Pair& pair) { return pair.id; } | 
|  |  | 
|  | static Value ValueOf(const Pair& pair) { return pair; } | 
|  |  | 
|  | static uword Hash(const Key& key) { return key.Hash(); } | 
|  |  | 
|  | static bool IsKeyEqual(const Pair& x, const Key& y) { return x.id == y; } | 
|  | }; | 
|  |  | 
|  | struct ObjectIdSetKeyValueTrait { | 
|  | using Pair = ObjectId; | 
|  | using Key = Pair; | 
|  | using Value = Pair; | 
|  |  | 
|  | static Key KeyOf(const Pair& pair) { return pair; } | 
|  | static Value ValueOf(const Pair& pair) { return pair; } | 
|  | static uword Hash(const Key& key) { return key.Hash(); } | 
|  | static bool IsKeyEqual(const Pair& pair, const Key& key) { | 
|  | return pair == key; | 
|  | } | 
|  | }; | 
|  |  | 
|  | Zone* const zone_; | 
|  | DirectChainedHashMap<ObjectIdToNodeInfoTraits> nodes_; | 
|  | StringsTable node_types_; | 
|  | StringsTable edge_types_; | 
|  | StringsTable strings_; | 
|  | DirectChainedHashMap<ObjectIdSetKeyValueTrait> roots_; | 
|  | #endif | 
|  | }; | 
|  |  | 
|  | }  // namespace dart | 
|  |  | 
|  | #endif  //  RUNTIME_VM_V8_SNAPSHOT_WRITER_H_ |