mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 10:49:00 +00:00
0c07cd6bc7
Remove unnecessary members, reduce field sizes and reduce initial map capacities. /usr/bin/time -v gen_snapshot --snapshot-kind=app-aot-elf --write-v8-snapshot-profile-to=... dart2js.dill Maximum resident set size (kbytes): 1347220 -> 902280 (-33.0%) TEST=ci Bug: b/196510517 Change-Id: I2d4dd55ac6db8ccbe9f8a834bab9fe906ba1228d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/212269 Reviewed-by: Siva Annamalai <asiva@google.com> Commit-Queue: Ryan Macnak <rmacnak@google.com>
337 lines
12 KiB
C++
337 lines
12 KiB
C++
// 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_
|