blob: 405109b98e4c6e47cc84b738117795f71a047234 [file] [log] [blame]
// 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_