| // 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_ |