mirror of
https://github.com/dart-lang/sdk
synced 2024-09-15 23:19:47 +00:00
Re-land "[vm/aot] Snapshot size analysis."
Minor fixes have been applied, see diff against patchset 1.
This reverts commit 9b937f1226
.
Change-Id: I8e4bbc0b88e33d3b554e91c17d1f849e24a1ccb3
Cq-Include-Trybots: luci.dart.try:vm-kernel-optcounter-threshold-linux-release-x64-try, vm-kernel-precomp-linux-debug-x64-try, vm-kernel-precomp-linux-release-simarm-try, vm-kernel-precomp-linux-release-simarm64-try, vm-kernel-precomp-linux-release-x64-try, vm-kernel-win-product-x64-try, vm-kernel-precomp-win-release-simarm64-try, vm-kernel-precomp-linux-release-x64-try
Reviewed-on: https://dart-review.googlesource.com/c/84845
Commit-Queue: Samir Jindel <sjindel@google.com>
Reviewed-by: Zach Anderson <zra@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Auto-Submit: Samir Jindel <sjindel@google.com>
This commit is contained in:
parent
5ced1f1cd2
commit
cde4270e47
239
pkg/vm/lib/v8_snapshot_profile.dart
Normal file
239
pkg/vm/lib/v8_snapshot_profile.dart
Normal file
|
@ -0,0 +1,239 @@
|
|||
// 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.
|
||||
|
||||
import "package:expect/expect.dart";
|
||||
import "package:dart2js_info/src/graph.dart";
|
||||
|
||||
class _NodeInfo {
|
||||
int type;
|
||||
int name;
|
||||
int id;
|
||||
int selfSize;
|
||||
int edgeCount;
|
||||
_NodeInfo(
|
||||
this.type,
|
||||
this.name,
|
||||
this.id,
|
||||
this.selfSize,
|
||||
this.edgeCount,
|
||||
);
|
||||
}
|
||||
|
||||
const List<String> _kRequiredNodeFields = [
|
||||
"type",
|
||||
"name",
|
||||
"id",
|
||||
"self_size",
|
||||
"edge_count",
|
||||
];
|
||||
|
||||
class _EdgeInfo {
|
||||
int type;
|
||||
int nameOrIndex;
|
||||
int nodeOffset;
|
||||
_EdgeInfo(
|
||||
this.type,
|
||||
this.nameOrIndex,
|
||||
this.nodeOffset,
|
||||
);
|
||||
}
|
||||
|
||||
const List<String> _kRequiredEdgeFields = [
|
||||
"type",
|
||||
"name_or_index",
|
||||
"to_node",
|
||||
];
|
||||
|
||||
class NodeInfo {
|
||||
String type;
|
||||
String name;
|
||||
int id;
|
||||
int selfSize;
|
||||
NodeInfo(
|
||||
this.type,
|
||||
this.name,
|
||||
this.id,
|
||||
this.selfSize,
|
||||
);
|
||||
}
|
||||
|
||||
class V8SnapshotProfile extends Graph<int> {
|
||||
// Indexed by node offset.
|
||||
final Map<int, _NodeInfo> _nodes = {};
|
||||
|
||||
// Indexed by start node offset.
|
||||
final Map<int, List<_EdgeInfo>> _toEdges = {};
|
||||
final Map<int, List<_EdgeInfo>> _fromEdges = {};
|
||||
|
||||
List<String> _nodeFields = [];
|
||||
List<String> _edgeFields = [];
|
||||
|
||||
List<String> _nodeTypes = [];
|
||||
List<String> _edgeTypes = [];
|
||||
|
||||
List<String> _strings = [];
|
||||
|
||||
// Only used to ensure IDs are unique.
|
||||
Set<int> _ids = Set<int>();
|
||||
|
||||
V8SnapshotProfile.fromJson(Map top) {
|
||||
final Map snapshot = top["snapshot"];
|
||||
_parseMetadata(snapshot["meta"]);
|
||||
|
||||
_parseStrings(top["strings"]);
|
||||
Expect.equals(snapshot["node_count"], _parseNodes(top["nodes"]));
|
||||
Expect.equals(snapshot["edge_count"], _parseEdges(top["edges"]));
|
||||
|
||||
_calculateFromEdges();
|
||||
}
|
||||
|
||||
void _parseMetadata(Map meta) {
|
||||
final List nodeFields = meta["node_fields"];
|
||||
nodeFields.forEach(_nodeFields.add);
|
||||
Expect.isTrue(_kRequiredNodeFields.every(_nodeFields.contains));
|
||||
|
||||
final List edgeFields = meta["edge_fields"];
|
||||
edgeFields.forEach(_edgeFields.add);
|
||||
Expect.isTrue(_kRequiredEdgeFields.every(_edgeFields.contains));
|
||||
|
||||
// First entry of "node_types" is an array with the actual node types. IDK
|
||||
// what the other entries are for.
|
||||
List nodeTypes = meta["node_types"];
|
||||
nodeTypes = nodeTypes[0];
|
||||
nodeTypes.forEach(_nodeTypes.add);
|
||||
|
||||
// Same for edges.
|
||||
List edgeTypes = meta["edge_types"];
|
||||
edgeTypes = edgeTypes[0];
|
||||
edgeTypes.forEach(_edgeTypes.add);
|
||||
}
|
||||
|
||||
int _parseNodes(List nodes) {
|
||||
final int typeIndex = _nodeFields.indexOf("type");
|
||||
final int nameIndex = _nodeFields.indexOf("name");
|
||||
final int idIndex = _nodeFields.indexOf("id");
|
||||
final int selfSizeIndex = _nodeFields.indexOf("self_size");
|
||||
final int edgeCountIndex = _nodeFields.indexOf("edge_count");
|
||||
|
||||
int offset = 0;
|
||||
for (; offset < nodes.length; offset += _nodeFields.length) {
|
||||
final int type = nodes[offset + typeIndex];
|
||||
Expect.isTrue(0 <= type && type < _nodeTypes.length);
|
||||
|
||||
final int name = nodes[offset + nameIndex];
|
||||
Expect.isTrue(0 <= name && name < _strings.length);
|
||||
|
||||
final int id = nodes[offset + idIndex];
|
||||
Expect.isTrue(id >= 0);
|
||||
Expect.isFalse(_ids.contains(id));
|
||||
_ids.add(id);
|
||||
|
||||
final int selfSize = nodes[offset + selfSizeIndex];
|
||||
Expect.isTrue(selfSize >= 0);
|
||||
|
||||
final int edgeCount = nodes[offset + edgeCountIndex];
|
||||
Expect.isTrue(edgeCount >= 0);
|
||||
|
||||
_nodes[offset] = _NodeInfo(type, name, id, selfSize, edgeCount);
|
||||
}
|
||||
|
||||
Expect.equals(offset, nodes.length);
|
||||
return offset ~/ _nodeFields.length;
|
||||
}
|
||||
|
||||
int _parseEdges(List edges) {
|
||||
final int typeIndex = _edgeFields.indexOf("type");
|
||||
final int nameOrIndexIndex = _edgeFields.indexOf("name_or_index");
|
||||
final int toNodeIndex = _edgeFields.indexOf("to_node");
|
||||
|
||||
int edgeOffset = 0;
|
||||
for (int nodeOffset = 0;
|
||||
nodeOffset < _nodes.length * _nodeFields.length;
|
||||
nodeOffset += _nodeFields.length) {
|
||||
final int edgeCount = _nodes[nodeOffset].edgeCount;
|
||||
final List<_EdgeInfo> nodeEdges = List<_EdgeInfo>(edgeCount);
|
||||
for (int i = 0; i < edgeCount; ++i, edgeOffset += _edgeFields.length) {
|
||||
final int type = edges[edgeOffset + typeIndex];
|
||||
Expect.isTrue(0 <= type && type < _edgeTypes.length);
|
||||
|
||||
final int nameOrIndex = edges[edgeOffset + nameOrIndexIndex];
|
||||
if (_edgeTypes[type] == "property") {
|
||||
Expect.isTrue(0 <= nameOrIndex && nameOrIndex < _strings.length);
|
||||
} else if (_edgeTypes[type] == "element") {
|
||||
Expect.isTrue(nameOrIndex >= 0);
|
||||
}
|
||||
|
||||
final int toNode = edges[edgeOffset + toNodeIndex];
|
||||
checkNode(toNode);
|
||||
nodeEdges[i] = _EdgeInfo(type, nameOrIndex, toNode);
|
||||
}
|
||||
_toEdges[nodeOffset] = nodeEdges;
|
||||
}
|
||||
|
||||
Expect.equals(edgeOffset, edges.length);
|
||||
return edgeOffset ~/ _edgeFields.length;
|
||||
}
|
||||
|
||||
void checkNode(int offset) {
|
||||
Expect.isTrue(offset >= 0 &&
|
||||
offset % _nodeFields.length == 0 &&
|
||||
offset ~/ _nodeFields.length < _nodes.length);
|
||||
}
|
||||
|
||||
void _calculateFromEdges() {
|
||||
for (final MapEntry<int, List<_EdgeInfo>> entry in _toEdges.entries) {
|
||||
final int fromNode = entry.key;
|
||||
for (final _EdgeInfo edge in entry.value) {
|
||||
final List<_EdgeInfo> backEdges =
|
||||
_fromEdges.putIfAbsent(edge.nodeOffset, () => <_EdgeInfo>[]);
|
||||
backEdges.add(_EdgeInfo(edge.type, edge.nameOrIndex, fromNode));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _parseStrings(List strings) => strings.forEach(_strings.add);
|
||||
|
||||
int get accountedBytes {
|
||||
int sum = 0;
|
||||
for (final _NodeInfo info in _nodes.values) {
|
||||
sum += info.selfSize;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
int get unknownCount {
|
||||
final int unknownType = _nodeTypes.indexOf("Unknown");
|
||||
Expect.isTrue(unknownType >= 0);
|
||||
|
||||
int count = 0;
|
||||
for (final MapEntry<int, _NodeInfo> entry in _nodes.entries) {
|
||||
if (entry.value.type == unknownType) {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
bool get isEmpty => _nodes.isEmpty;
|
||||
int get nodeCount => _nodes.length;
|
||||
|
||||
Iterable<int> get nodes => _nodes.keys;
|
||||
|
||||
Iterable<int> targetsOf(int source) {
|
||||
return _toEdges[source].map((_EdgeInfo i) => i.nodeOffset);
|
||||
}
|
||||
|
||||
Iterable<int> sourcesOf(int source) {
|
||||
return _fromEdges[source].map((_EdgeInfo i) => i.nodeOffset);
|
||||
}
|
||||
|
||||
int get root => 0;
|
||||
|
||||
NodeInfo operator [](int node) {
|
||||
_NodeInfo info = _nodes[node];
|
||||
final type = info.type != null ? _nodeTypes[info.type] : null;
|
||||
final name = info.name != null ? _strings[info.name] : null;
|
||||
return NodeInfo(type, name, info.id, info.selfSize);
|
||||
}
|
||||
}
|
|
@ -13,7 +13,6 @@
|
|||
# passed to Fasta.
|
||||
|
||||
set -e
|
||||
set -x
|
||||
|
||||
OPTIONS=()
|
||||
GEN_KERNEL_OPTIONS=()
|
||||
|
|
102
runtime/tests/vm/dart/v8_snapshot_profile_writer_test.dart
Normal file
102
runtime/tests/vm/dart/v8_snapshot_profile_writer_test.dart
Normal file
|
@ -0,0 +1,102 @@
|
|||
// 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.
|
||||
|
||||
import "dart:convert";
|
||||
import "dart:io";
|
||||
|
||||
import "package:expect/expect.dart";
|
||||
import "package:vm/v8_snapshot_profile.dart";
|
||||
|
||||
String path(List<String> segments) {
|
||||
return "/" + segments.join("/");
|
||||
}
|
||||
|
||||
test(bool use_elf) async {
|
||||
if (Platform.isWindows) return;
|
||||
|
||||
final List<String> sdkBaseSegments =
|
||||
Uri.file(Platform.resolvedExecutable).pathSegments.toList();
|
||||
sdkBaseSegments
|
||||
.replaceRange(sdkBaseSegments.indexOf("out"), sdkBaseSegments.length, []);
|
||||
|
||||
// Generate the snapshot profile.
|
||||
final String thisTestPath = path(sdkBaseSegments) +
|
||||
"/runtime/tests/vm/dart/v8_snapshot_profile_writer_test.dart";
|
||||
|
||||
final Directory temp = await Directory.systemTemp.createTemp();
|
||||
final String snapshotPath = temp.path + "/test.snap";
|
||||
|
||||
final List<String> precompiler2Args = [
|
||||
"--write-v8-snapshot-profile-to=${temp.path}/profile.heapsnapshot",
|
||||
thisTestPath,
|
||||
snapshotPath,
|
||||
];
|
||||
|
||||
if (use_elf) {
|
||||
precompiler2Args.insert(0, "--build-elf");
|
||||
}
|
||||
|
||||
final ProcessResult result = await Process.run(
|
||||
"pkg/vm/tool/precompiler2",
|
||||
precompiler2Args,
|
||||
workingDirectory: path(sdkBaseSegments),
|
||||
runInShell: true,
|
||||
);
|
||||
|
||||
// The precompiler2 script tried using GCC for the wrong architecture. We
|
||||
// don't have a workaround for this now.
|
||||
if (use_elf &&
|
||||
result.exitCode != 0 &&
|
||||
result.stderr.contains("Assembler messages")) {
|
||||
return;
|
||||
}
|
||||
|
||||
print(precompiler2Args);
|
||||
print(result.stderr);
|
||||
print(result.stdout);
|
||||
|
||||
Expect.equals(result.exitCode, 0);
|
||||
Expect.equals(result.stderr, "");
|
||||
Expect.equals(result.stdout, "");
|
||||
|
||||
final V8SnapshotProfile profile = V8SnapshotProfile.fromJson(JsonDecoder()
|
||||
.convert(File("${temp.path}/profile.heapsnapshot").readAsStringSync()));
|
||||
|
||||
// Verify that there are no "unknown" nodes. These are emitted when we see a
|
||||
// reference to an some object but no other metadata about the object was
|
||||
// recorded. We should at least record the type for every object in the graph
|
||||
// (in some cases the shallow size can legitimately be 0, e.g. for "base
|
||||
// objects").
|
||||
for (final int node in profile.nodes) {
|
||||
if (profile[node].type == "Unknown") {
|
||||
print(profile[node].id);
|
||||
}
|
||||
Expect.notEquals(profile[node].type, "Unknown");
|
||||
}
|
||||
|
||||
// Verify that all nodes are reachable from the declared roots.
|
||||
int unreachableNodes = 0;
|
||||
Set<int> nodesReachableFromRoots = profile.preOrder(profile.root).toSet();
|
||||
for (final int node in profile.nodes) {
|
||||
if (!nodesReachableFromRoots.contains(node)) {
|
||||
++unreachableNodes;
|
||||
}
|
||||
}
|
||||
Expect.equals(unreachableNodes, 0);
|
||||
|
||||
// Verify that the actual size of the snapshot is close to the sum of the
|
||||
// shallow sizes of all objects in the profile. They will not be exactly equal
|
||||
// because of global headers and padding.
|
||||
if (use_elf) {
|
||||
await Process.run("strip", [snapshotPath]);
|
||||
}
|
||||
final int actual = await File(snapshotPath).length();
|
||||
final int expected = profile.accountedBytes;
|
||||
Expect.isTrue((actual - expected).abs() / actual < 0.01);
|
||||
}
|
||||
|
||||
main() async {
|
||||
test(false);
|
||||
test(true);
|
||||
}
|
|
@ -314,3 +314,6 @@ dart/stack_overflow_shared_test: SkipSlow # Too slow with --shared-slow-path-tri
|
|||
[ $builder_tag == obfuscated && $compiler == dartkp ]
|
||||
dart/optimized_stacktrace_line_and_column_test: SkipByDesign # Looks for filenames in stacktrace output
|
||||
dart/optimized_stacktrace_line_test: SkipByDesign # Looks for filenames in stacktrace output
|
||||
|
||||
[ $compiler != dartkp || $arch == arm || $arch == arm64 ]
|
||||
dart/v8_snapshot_profile_writer_test: SkipByDesign # Only relevant for AOT. Doesn't work in cross-compilation (has to run on the host).
|
||||
|
|
|
@ -118,9 +118,10 @@ class ClassSerializationCluster : public SerializationCluster {
|
|||
s->WriteUnsigned(count);
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawClass* cls = predefined_[i];
|
||||
s->AssignRef(cls);
|
||||
AutoTraceObject(cls);
|
||||
intptr_t class_id = cls->ptr()->id_;
|
||||
s->WriteCid(class_id);
|
||||
s->AssignRef(cls);
|
||||
}
|
||||
count = objects_.length();
|
||||
s->WriteUnsigned(count);
|
||||
|
@ -142,6 +143,7 @@ class ClassSerializationCluster : public SerializationCluster {
|
|||
}
|
||||
|
||||
void WriteClass(Serializer* s, RawClass* cls) {
|
||||
AutoTraceObjectName(cls, cls->ptr()->name_);
|
||||
Snapshot::Kind kind = s->kind();
|
||||
RawObject** from = cls->from();
|
||||
RawObject** to = cls->to_snapshot(kind);
|
||||
|
@ -302,9 +304,10 @@ class TypeArgumentsSerializationCluster : public SerializationCluster {
|
|||
s->WriteUnsigned(count);
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawTypeArguments* type_args = objects_[i];
|
||||
s->AssignRef(type_args);
|
||||
AutoTraceObject(type_args);
|
||||
intptr_t length = Smi::Value(type_args->ptr()->length_);
|
||||
s->WriteUnsigned(length);
|
||||
s->AssignRef(type_args);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -312,6 +315,7 @@ class TypeArgumentsSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawTypeArguments* type_args = objects_[i];
|
||||
AutoTraceObject(type_args);
|
||||
intptr_t length = Smi::Value(type_args->ptr()->length_);
|
||||
s->WriteUnsigned(length);
|
||||
s->Write<bool>(type_args->IsCanonical());
|
||||
|
@ -400,6 +404,7 @@ class PatchClassSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawPatchClass* cls = objects_[i];
|
||||
AutoTraceObject(cls);
|
||||
RawObject** from = cls->from();
|
||||
RawObject** to = cls->to_snapshot(s->kind());
|
||||
for (RawObject** p = from; p <= to; p++) {
|
||||
|
@ -501,6 +506,7 @@ class FunctionSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawFunction* func = objects_[i];
|
||||
AutoTraceObjectName(func, func->ptr()->name_);
|
||||
RawObject** from = func->from();
|
||||
RawObject** to = func->to_snapshot(s->kind());
|
||||
for (RawObject** p = from; p <= to; p++) {
|
||||
|
@ -689,6 +695,7 @@ class ClosureDataSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawClosureData* data = objects_[i];
|
||||
AutoTraceObject(data);
|
||||
if (s->kind() != Snapshot::kFullAOT) {
|
||||
s->WriteRef(data->ptr()->context_scope_);
|
||||
}
|
||||
|
@ -770,6 +777,7 @@ class SignatureDataSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawSignatureData* data = objects_[i];
|
||||
AutoTraceObject(data);
|
||||
RawObject** from = data->from();
|
||||
RawObject** to = data->to();
|
||||
for (RawObject** p = from; p <= to; p++) {
|
||||
|
@ -847,6 +855,7 @@ class RedirectionDataSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawRedirectionData* data = objects_[i];
|
||||
AutoTraceObject(data);
|
||||
RawObject** from = data->from();
|
||||
RawObject** to = data->to();
|
||||
for (RawObject** p = from; p <= to; p++) {
|
||||
|
@ -955,6 +964,7 @@ class FieldSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawField* field = objects_[i];
|
||||
AutoTraceObject(field);
|
||||
|
||||
s->WriteRef(field->ptr()->name_);
|
||||
s->WriteRef(field->ptr()->owner_);
|
||||
|
@ -1112,6 +1122,7 @@ class ScriptSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawScript* script = objects_[i];
|
||||
AutoTraceObject(script);
|
||||
RawObject** from = script->from();
|
||||
RawObject** to = script->to_snapshot(kind);
|
||||
for (RawObject** p = from; p <= to; p++) {
|
||||
|
@ -1203,6 +1214,7 @@ class LibrarySerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawLibrary* lib = objects_[i];
|
||||
AutoTraceObjectName(lib, lib->ptr()->url_);
|
||||
RawObject** from = lib->from();
|
||||
RawObject** to = lib->to_snapshot(s->kind());
|
||||
for (RawObject** p = from; p <= to; p++) {
|
||||
|
@ -1307,6 +1319,7 @@ class NamespaceSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawNamespace* ns = objects_[i];
|
||||
AutoTraceObject(ns);
|
||||
RawObject** from = ns->from();
|
||||
RawObject** to = ns->to();
|
||||
for (RawObject** p = from; p <= to; p++) {
|
||||
|
@ -1384,6 +1397,7 @@ class KernelProgramInfoSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawKernelProgramInfo* info = objects_[i];
|
||||
AutoTraceObject(info);
|
||||
RawObject** from = info->from();
|
||||
RawObject** to = info->to_snapshot(s->kind());
|
||||
for (RawObject** p = from; p <= to; p++) {
|
||||
|
@ -1494,6 +1508,7 @@ class CodeSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawCode* code = objects_[i];
|
||||
AutoTraceObject(code);
|
||||
|
||||
intptr_t pointer_offsets_length =
|
||||
Code::PtrOffBits::decode(code->ptr()->state_bits_);
|
||||
|
@ -1740,9 +1755,10 @@ class ObjectPoolSerializationCluster : public SerializationCluster {
|
|||
s->WriteUnsigned(count);
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawObjectPool* pool = objects_[i];
|
||||
s->AssignRef(pool);
|
||||
AutoTraceObject(pool);
|
||||
intptr_t length = pool->ptr()->length_;
|
||||
s->WriteUnsigned(length);
|
||||
s->AssignRef(pool);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1750,6 +1766,7 @@ class ObjectPoolSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawObjectPool* pool = objects_[i];
|
||||
AutoTraceObject(pool);
|
||||
intptr_t length = pool->ptr()->length_;
|
||||
s->WriteUnsigned(length);
|
||||
uint8_t* entry_bits = pool->ptr()->entry_bits();
|
||||
|
@ -1900,12 +1917,13 @@ class RODataSerializationCluster : public SerializationCluster {
|
|||
s->WriteUnsigned(count);
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawObject* object = shared_objects_[i];
|
||||
s->AssignRef(object);
|
||||
AutoTraceObject(object);
|
||||
uint32_t offset;
|
||||
if (!s->GetSharedDataOffset(object, &offset)) {
|
||||
UNREACHABLE();
|
||||
}
|
||||
s->WriteUnsigned(offset);
|
||||
s->AssignRef(object);
|
||||
}
|
||||
|
||||
count = objects_.length();
|
||||
|
@ -1913,12 +1931,14 @@ class RODataSerializationCluster : public SerializationCluster {
|
|||
uint32_t running_offset = 0;
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawObject* object = objects_[i];
|
||||
s->AssignRef(object);
|
||||
AutoTraceObject(object);
|
||||
uint32_t offset = s->GetDataOffset(object);
|
||||
s->TraceDataOffset(offset);
|
||||
ASSERT(Utils::IsAligned(offset, kObjectAlignment));
|
||||
ASSERT(offset > running_offset);
|
||||
s->WriteUnsigned((offset - running_offset) >> kObjectAlignmentLog2);
|
||||
running_offset = offset;
|
||||
s->AssignRef(object);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1978,9 +1998,10 @@ class ExceptionHandlersSerializationCluster : public SerializationCluster {
|
|||
s->WriteUnsigned(count);
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawExceptionHandlers* handlers = objects_[i];
|
||||
s->AssignRef(handlers);
|
||||
AutoTraceObject(handlers);
|
||||
intptr_t length = handlers->ptr()->num_entries_;
|
||||
s->WriteUnsigned(length);
|
||||
s->AssignRef(handlers);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1988,6 +2009,7 @@ class ExceptionHandlersSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawExceptionHandlers* handlers = objects_[i];
|
||||
AutoTraceObject(handlers);
|
||||
intptr_t length = handlers->ptr()->num_entries_;
|
||||
s->WriteUnsigned(length);
|
||||
s->WriteRef(handlers->ptr()->handled_types_data_);
|
||||
|
@ -2072,9 +2094,10 @@ class ContextSerializationCluster : public SerializationCluster {
|
|||
s->WriteUnsigned(count);
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawContext* context = objects_[i];
|
||||
s->AssignRef(context);
|
||||
AutoTraceObject(context);
|
||||
intptr_t length = context->ptr()->num_variables_;
|
||||
s->WriteUnsigned(length);
|
||||
s->AssignRef(context);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2082,6 +2105,7 @@ class ContextSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawContext* context = objects_[i];
|
||||
AutoTraceObject(context);
|
||||
intptr_t length = context->ptr()->num_variables_;
|
||||
s->WriteUnsigned(length);
|
||||
s->WriteRef(context->ptr()->parent_);
|
||||
|
@ -2154,9 +2178,10 @@ class ContextScopeSerializationCluster : public SerializationCluster {
|
|||
s->WriteUnsigned(count);
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawContextScope* scope = objects_[i];
|
||||
s->AssignRef(scope);
|
||||
AutoTraceObject(scope);
|
||||
intptr_t length = scope->ptr()->num_variables_;
|
||||
s->WriteUnsigned(length);
|
||||
s->AssignRef(scope);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2164,6 +2189,7 @@ class ContextScopeSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawContextScope* scope = objects_[i];
|
||||
AutoTraceObject(scope);
|
||||
intptr_t length = scope->ptr()->num_variables_;
|
||||
s->WriteUnsigned(length);
|
||||
s->Write<bool>(scope->ptr()->is_implicit_);
|
||||
|
@ -2248,6 +2274,7 @@ class UnlinkedCallSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawUnlinkedCall* unlinked = objects_[i];
|
||||
AutoTraceObject(unlinked);
|
||||
RawObject** from = unlinked->from();
|
||||
RawObject** to = unlinked->to();
|
||||
for (RawObject** p = from; p <= to; p++) {
|
||||
|
@ -2327,6 +2354,7 @@ class ICDataSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawICData* ic = objects_[i];
|
||||
AutoTraceObject(ic);
|
||||
RawObject** from = ic->from();
|
||||
RawObject** to = ic->to_snapshot(kind);
|
||||
for (RawObject** p = from; p <= to; p++) {
|
||||
|
@ -2420,6 +2448,7 @@ class MegamorphicCacheSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawMegamorphicCache* cache = objects_[i];
|
||||
AutoTraceObject(cache);
|
||||
RawObject** from = cache->from();
|
||||
RawObject** to = cache->to();
|
||||
for (RawObject** p = from; p <= to; p++) {
|
||||
|
@ -2496,6 +2525,7 @@ class SubtypeTestCacheSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawSubtypeTestCache* cache = objects_[i];
|
||||
AutoTraceObject(cache);
|
||||
s->WriteRef(cache->ptr()->cache_);
|
||||
}
|
||||
}
|
||||
|
@ -2566,6 +2596,7 @@ class LanguageErrorSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawLanguageError* error = objects_[i];
|
||||
AutoTraceObject(error);
|
||||
RawObject** from = error->from();
|
||||
RawObject** to = error->to();
|
||||
for (RawObject** p = from; p <= to; p++) {
|
||||
|
@ -2650,6 +2681,7 @@ class UnhandledExceptionSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawUnhandledException* exception = objects_[i];
|
||||
AutoTraceObject(exception);
|
||||
RawObject** from = exception->from();
|
||||
RawObject** to = exception->to();
|
||||
for (RawObject** p = from; p <= to; p++) {
|
||||
|
@ -2743,6 +2775,7 @@ class InstanceSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawInstance* instance = objects_[i];
|
||||
AutoTraceObject(instance);
|
||||
s->Write<bool>(instance->IsCanonical());
|
||||
intptr_t offset = Instance::NextFieldOffset();
|
||||
while (offset < next_field_offset) {
|
||||
|
@ -2847,6 +2880,7 @@ class LibraryPrefixSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawLibraryPrefix* prefix = objects_[i];
|
||||
AutoTraceObject(prefix);
|
||||
RawObject** from = prefix->from();
|
||||
RawObject** to = prefix->to_snapshot(kind);
|
||||
for (RawObject** p = from; p <= to; p++) {
|
||||
|
@ -2961,6 +2995,7 @@ class TypeSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = canonical_objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawType* type = canonical_objects_[i];
|
||||
AutoTraceObject(type);
|
||||
RawObject** from = type->from();
|
||||
RawObject** to = type->to();
|
||||
for (RawObject** p = from; p <= to; p++) {
|
||||
|
@ -2977,6 +3012,7 @@ class TypeSerializationCluster : public SerializationCluster {
|
|||
count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawType* type = objects_[i];
|
||||
AutoTraceObject(type);
|
||||
RawObject** from = type->from();
|
||||
RawObject** to = type->to();
|
||||
for (RawObject** p = from; p <= to; p++) {
|
||||
|
@ -3144,6 +3180,7 @@ class TypeRefSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawTypeRef* type = objects_[i];
|
||||
AutoTraceObject(type);
|
||||
RawObject** from = type->from();
|
||||
RawObject** to = type->to();
|
||||
for (RawObject** p = from; p <= to; p++) {
|
||||
|
@ -3253,6 +3290,7 @@ class TypeParameterSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawTypeParameter* type = objects_[i];
|
||||
AutoTraceObject(type);
|
||||
RawObject** from = type->from();
|
||||
RawObject** to = type->to();
|
||||
for (RawObject** p = from; p <= to; p++) {
|
||||
|
@ -3365,6 +3403,7 @@ class BoundedTypeSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawBoundedType* type = objects_[i];
|
||||
AutoTraceObject(type);
|
||||
RawObject** from = type->from();
|
||||
RawObject** to = type->to();
|
||||
for (RawObject** p = from; p <= to; p++) {
|
||||
|
@ -3441,6 +3480,7 @@ class ClosureSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawClosure* closure = objects_[i];
|
||||
AutoTraceObject(closure);
|
||||
s->Write<bool>(closure->IsCanonical());
|
||||
RawObject** from = closure->from();
|
||||
RawObject** to = closure->to();
|
||||
|
@ -3510,15 +3550,17 @@ class MintSerializationCluster : public SerializationCluster {
|
|||
s->WriteUnsigned(smis_.length() + mints_.length());
|
||||
for (intptr_t i = 0; i < smis_.length(); i++) {
|
||||
RawSmi* smi = smis_[i];
|
||||
s->AssignRef(smi);
|
||||
AutoTraceObject(smi);
|
||||
s->Write<bool>(true);
|
||||
s->Write<int64_t>(Smi::Value(smi));
|
||||
s->AssignRef(smi);
|
||||
}
|
||||
for (intptr_t i = 0; i < mints_.length(); i++) {
|
||||
RawMint* mint = mints_[i];
|
||||
s->AssignRef(mint);
|
||||
AutoTraceObject(mint);
|
||||
s->Write<bool>(mint->IsCanonical());
|
||||
s->Write<int64_t>(mint->ptr()->value_);
|
||||
s->AssignRef(mint);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3602,6 +3644,7 @@ class DoubleSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawDouble* dbl = objects_[i];
|
||||
AutoTraceObject(dbl);
|
||||
s->Write<bool>(dbl->IsCanonical());
|
||||
s->Write<double>(dbl->ptr()->value_);
|
||||
}
|
||||
|
@ -3672,6 +3715,7 @@ class GrowableObjectArraySerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawGrowableObjectArray* array = objects_[i];
|
||||
AutoTraceObject(array);
|
||||
s->Write<bool>(array->IsCanonical());
|
||||
RawObject** from = array->from();
|
||||
RawObject** to = array->to();
|
||||
|
@ -3740,9 +3784,10 @@ class TypedDataSerializationCluster : public SerializationCluster {
|
|||
s->WriteUnsigned(count);
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawTypedData* data = objects_[i];
|
||||
s->AssignRef(data);
|
||||
AutoTraceObject(data);
|
||||
intptr_t length = Smi::Value(data->ptr()->length_);
|
||||
s->WriteUnsigned(length);
|
||||
s->AssignRef(data);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3751,6 +3796,7 @@ class TypedDataSerializationCluster : public SerializationCluster {
|
|||
intptr_t element_size = TypedData::ElementSizeInBytes(cid_);
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawTypedData* data = objects_[i];
|
||||
AutoTraceObject(data);
|
||||
intptr_t length = Smi::Value(data->ptr()->length_);
|
||||
s->WriteUnsigned(length);
|
||||
s->Write<bool>(data->IsCanonical());
|
||||
|
@ -3833,6 +3879,7 @@ class ExternalTypedDataSerializationCluster : public SerializationCluster {
|
|||
intptr_t element_size = ExternalTypedData::ElementSizeInBytes(cid_);
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawExternalTypedData* data = objects_[i];
|
||||
AutoTraceObject(data);
|
||||
intptr_t length = Smi::Value(data->ptr()->length_);
|
||||
s->WriteUnsigned(length);
|
||||
uint8_t* cdata = reinterpret_cast<uint8_t*>(data->ptr()->data_);
|
||||
|
@ -3916,6 +3963,7 @@ class StackTraceSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawStackTrace* trace = objects_[i];
|
||||
AutoTraceObject(trace);
|
||||
RawObject** from = trace->from();
|
||||
RawObject** to = trace->to();
|
||||
for (RawObject** p = from; p <= to; p++) {
|
||||
|
@ -3992,6 +4040,7 @@ class RegExpSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawRegExp* regexp = objects_[i];
|
||||
AutoTraceObject(regexp);
|
||||
RawObject** from = regexp->from();
|
||||
RawObject** to = regexp->to();
|
||||
for (RawObject** p = from; p <= to; p++) {
|
||||
|
@ -4073,6 +4122,7 @@ class WeakPropertySerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawWeakProperty* property = objects_[i];
|
||||
AutoTraceObject(property);
|
||||
RawObject** from = property->from();
|
||||
RawObject** to = property->to();
|
||||
for (RawObject** p = from; p <= to; p++) {
|
||||
|
@ -4159,6 +4209,7 @@ class LinkedHashMapSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawLinkedHashMap* map = objects_[i];
|
||||
AutoTraceObject(map);
|
||||
s->Write<bool>(map->IsCanonical());
|
||||
|
||||
s->WriteRef(map->ptr()->type_arguments_);
|
||||
|
@ -4270,9 +4321,10 @@ class ArraySerializationCluster : public SerializationCluster {
|
|||
s->WriteUnsigned(count);
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawArray* array = objects_[i];
|
||||
s->AssignRef(array);
|
||||
AutoTraceObject(array);
|
||||
intptr_t length = Smi::Value(array->ptr()->length_);
|
||||
s->WriteUnsigned(length);
|
||||
s->AssignRef(array);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4280,6 +4332,7 @@ class ArraySerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawArray* array = objects_[i];
|
||||
AutoTraceObject(array);
|
||||
intptr_t length = Smi::Value(array->ptr()->length_);
|
||||
s->WriteUnsigned(length);
|
||||
s->Write<bool>(array->IsCanonical());
|
||||
|
@ -4352,9 +4405,10 @@ class OneByteStringSerializationCluster : public SerializationCluster {
|
|||
s->WriteUnsigned(count);
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawOneByteString* str = objects_[i];
|
||||
s->AssignRef(str);
|
||||
AutoTraceObject(str);
|
||||
intptr_t length = Smi::Value(str->ptr()->length_);
|
||||
s->WriteUnsigned(length);
|
||||
s->AssignRef(str);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4362,6 +4416,7 @@ class OneByteStringSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawOneByteString* str = objects_[i];
|
||||
AutoTraceObject(str);
|
||||
intptr_t length = Smi::Value(str->ptr()->length_);
|
||||
s->WriteUnsigned(length);
|
||||
s->Write<bool>(str->IsCanonical());
|
||||
|
@ -4429,9 +4484,10 @@ class TwoByteStringSerializationCluster : public SerializationCluster {
|
|||
s->WriteUnsigned(count);
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawTwoByteString* str = objects_[i];
|
||||
s->AssignRef(str);
|
||||
AutoTraceObject(str);
|
||||
intptr_t length = Smi::Value(str->ptr()->length_);
|
||||
s->WriteUnsigned(length);
|
||||
s->AssignRef(str);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4439,6 +4495,7 @@ class TwoByteStringSerializationCluster : public SerializationCluster {
|
|||
intptr_t count = objects_.length();
|
||||
for (intptr_t i = 0; i < count; i++) {
|
||||
RawTwoByteString* str = objects_[i];
|
||||
AutoTraceObject(str);
|
||||
intptr_t length = Smi::Value(str->ptr()->length_);
|
||||
s->WriteUnsigned(length);
|
||||
s->Write<bool>(str->IsCanonical());
|
||||
|
@ -4508,7 +4565,9 @@ Serializer::Serializer(Thread* thread,
|
|||
uint8_t** buffer,
|
||||
ReAlloc alloc,
|
||||
intptr_t initial_size,
|
||||
ImageWriter* image_writer)
|
||||
ImageWriter* image_writer,
|
||||
bool vm,
|
||||
V8SnapshotProfileWriter* profile_writer)
|
||||
: StackResource(thread),
|
||||
heap_(thread->isolate()->heap()),
|
||||
zone_(thread->zone()),
|
||||
|
@ -4520,7 +4579,9 @@ Serializer::Serializer(Thread* thread,
|
|||
num_cids_(0),
|
||||
num_base_objects_(0),
|
||||
num_written_objects_(0),
|
||||
next_ref_index_(1)
|
||||
next_ref_index_(1),
|
||||
vm_(vm),
|
||||
profile_writer_(profile_writer)
|
||||
#if defined(SNAPSHOT_BACKTRACE)
|
||||
,
|
||||
current_parent_(Object::null()),
|
||||
|
@ -4538,6 +4599,43 @@ Serializer::~Serializer() {
|
|||
delete[] clusters_by_cid_;
|
||||
}
|
||||
|
||||
void Serializer::TraceStartWritingObject(const char* type,
|
||||
RawObject* obj,
|
||||
RawString* name) {
|
||||
if (profile_writer_ == nullptr) return;
|
||||
|
||||
intptr_t id = 0;
|
||||
if (obj->IsHeapObject()) {
|
||||
id = heap_->GetObjectId(obj);
|
||||
} else {
|
||||
id = smi_ids_.Lookup(Smi::RawCast(obj))->id_;
|
||||
}
|
||||
ASSERT(id != 0);
|
||||
|
||||
const char* name_str = nullptr;
|
||||
if (name != nullptr) {
|
||||
String& str = thread()->StringHandle();
|
||||
str = name;
|
||||
name_str = str.ToCString();
|
||||
}
|
||||
|
||||
object_currently_writing_id_ = id;
|
||||
profile_writer_->SetObjectTypeAndName(
|
||||
{V8SnapshotProfileWriter::kSnapshot, id}, type, name_str);
|
||||
object_currently_writing_start_ = stream_.Position();
|
||||
}
|
||||
|
||||
void Serializer::TraceEndWritingObject() {
|
||||
if (profile_writer_ != nullptr) {
|
||||
ASSERT(object_currently_writing_id_ != 0);
|
||||
profile_writer_->AttributeBytesTo(
|
||||
{V8SnapshotProfileWriter::kSnapshot, object_currently_writing_id_},
|
||||
stream_.Position() - object_currently_writing_start_);
|
||||
object_currently_writing_id_ = 0;
|
||||
object_currently_writing_start_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
SerializationCluster* Serializer::NewClusterForClass(intptr_t cid) {
|
||||
#if defined(DART_PRECOMPILED_RUNTIME)
|
||||
UNREACHABLE();
|
||||
|
@ -4675,6 +4773,31 @@ void Serializer::WriteInstructions(RawInstructions* instr, RawCode* code) {
|
|||
const intptr_t offset = image_writer_->GetTextOffsetFor(instr, code);
|
||||
ASSERT(offset != 0);
|
||||
Write<int32_t>(offset);
|
||||
|
||||
// If offset < 0, it's pointing to a shared instruction. We don't profile
|
||||
// references to shared text/data (since they don't consume any space). Of
|
||||
// course, the space taken for the reference is profiled.
|
||||
if (profile_writer_ != nullptr && offset >= 0) {
|
||||
// Instructions cannot be roots.
|
||||
ASSERT(object_currently_writing_id_ != 0);
|
||||
auto offset_space = vm_ ? V8SnapshotProfileWriter::kVmText
|
||||
: V8SnapshotProfileWriter::kIsolateText;
|
||||
profile_writer_->AttributeReferenceTo(
|
||||
{V8SnapshotProfileWriter::kSnapshot, object_currently_writing_id_},
|
||||
{offset_space, offset < 0 ? -offset : offset});
|
||||
}
|
||||
}
|
||||
|
||||
void Serializer::TraceDataOffset(uint32_t offset) {
|
||||
if (profile_writer_ != nullptr) {
|
||||
// ROData cannot be roots.
|
||||
ASSERT(object_currently_writing_id_ != 0);
|
||||
auto offset_space = vm_ ? V8SnapshotProfileWriter::kVmData
|
||||
: V8SnapshotProfileWriter::kIsolateData;
|
||||
profile_writer_->AttributeReferenceTo(
|
||||
{V8SnapshotProfileWriter::kSnapshot, object_currently_writing_id_},
|
||||
{offset_space, offset});
|
||||
}
|
||||
}
|
||||
|
||||
bool Serializer::GetSharedDataOffset(RawObject* object,
|
||||
|
@ -4919,30 +5042,36 @@ void Serializer::AddVMIsolateBaseObjects() {
|
|||
// These objects are always allocated by Object::InitOnce, so they are not
|
||||
// written into the snapshot.
|
||||
|
||||
AddBaseObject(Object::null());
|
||||
AddBaseObject(Object::sentinel().raw());
|
||||
AddBaseObject(Object::transition_sentinel().raw());
|
||||
AddBaseObject(Object::empty_array().raw());
|
||||
AddBaseObject(Object::zero_array().raw());
|
||||
AddBaseObject(Object::dynamic_type().raw());
|
||||
AddBaseObject(Object::void_type().raw());
|
||||
AddBaseObject(Object::empty_type_arguments().raw());
|
||||
AddBaseObject(Bool::True().raw());
|
||||
AddBaseObject(Bool::False().raw());
|
||||
AddBaseObject(Object::null(), "Null", "<null>");
|
||||
AddBaseObject(Object::sentinel().raw(), "Sentinel");
|
||||
AddBaseObject(Object::transition_sentinel().raw(), "Sentinel");
|
||||
AddBaseObject(Object::empty_array().raw(), "Array", "<empty_array>");
|
||||
AddBaseObject(Object::zero_array().raw(), "Array", "<zero_array>");
|
||||
AddBaseObject(Object::dynamic_type().raw(), "Type", "<dynamic type>");
|
||||
AddBaseObject(Object::void_type().raw(), "Type", "<void type>");
|
||||
AddBaseObject(Object::empty_type_arguments().raw(), "TypeArguments", "[]");
|
||||
AddBaseObject(Bool::True().raw(), "bool", "true");
|
||||
AddBaseObject(Bool::False().raw(), "bool", "false");
|
||||
ASSERT(Object::extractor_parameter_types().raw() != Object::null());
|
||||
AddBaseObject(Object::extractor_parameter_types().raw());
|
||||
AddBaseObject(Object::extractor_parameter_types().raw(), "Array",
|
||||
"<extractor parameter types>");
|
||||
ASSERT(Object::extractor_parameter_names().raw() != Object::null());
|
||||
AddBaseObject(Object::extractor_parameter_names().raw());
|
||||
AddBaseObject(Object::empty_context_scope().raw());
|
||||
AddBaseObject(Object::empty_descriptors().raw());
|
||||
AddBaseObject(Object::empty_var_descriptors().raw());
|
||||
AddBaseObject(Object::empty_exception_handlers().raw());
|
||||
AddBaseObject(Object::extractor_parameter_names().raw(), "Array",
|
||||
"<extractor parameter names>");
|
||||
AddBaseObject(Object::empty_context_scope().raw(), "ContextScope", "<empty>");
|
||||
AddBaseObject(Object::empty_descriptors().raw(), "PcDescriptors", "<empty>");
|
||||
AddBaseObject(Object::empty_var_descriptors().raw(), "LocalVarDescriptors",
|
||||
"<empty>");
|
||||
AddBaseObject(Object::empty_exception_handlers().raw(), "ExceptionHandlers",
|
||||
"<empty>");
|
||||
|
||||
for (intptr_t i = 0; i < ArgumentsDescriptor::kCachedDescriptorCount; i++) {
|
||||
AddBaseObject(ArgumentsDescriptor::cached_args_descriptors_[i]);
|
||||
AddBaseObject(ArgumentsDescriptor::cached_args_descriptors_[i],
|
||||
"ArgumentsDescriptor", "<cached arguments descriptor>");
|
||||
}
|
||||
for (intptr_t i = 0; i < ICData::kCachedICDataArrayCount; i++) {
|
||||
AddBaseObject(ICData::cached_icdata_arrays_[i]);
|
||||
AddBaseObject(ICData::cached_icdata_arrays_[i], "ICData",
|
||||
"<cached icdata>");
|
||||
}
|
||||
|
||||
ClassTable* table = isolate()->class_table();
|
||||
|
@ -4950,15 +5079,15 @@ void Serializer::AddVMIsolateBaseObjects() {
|
|||
// Error has no class object.
|
||||
if (cid != kErrorCid) {
|
||||
ASSERT(table->HasValidClassAt(cid));
|
||||
AddBaseObject(table->At(cid));
|
||||
AddBaseObject(table->At(cid), "Class");
|
||||
}
|
||||
}
|
||||
AddBaseObject(table->At(kDynamicCid));
|
||||
AddBaseObject(table->At(kVoidCid));
|
||||
AddBaseObject(table->At(kDynamicCid), "Class");
|
||||
AddBaseObject(table->At(kVoidCid), "Class");
|
||||
|
||||
if (!Snapshot::IncludesCode(kind_)) {
|
||||
for (intptr_t i = 0; i < StubCode::NumEntries(); i++) {
|
||||
AddBaseObject(StubCode::EntryAt(i).raw());
|
||||
AddBaseObject(StubCode::EntryAt(i).raw(), "Code", "<stub code>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4985,10 +5114,10 @@ intptr_t Serializer::WriteVMSnapshot(const Array& symbols,
|
|||
Serialize();
|
||||
|
||||
// Write roots.
|
||||
WriteRef(symbols.raw());
|
||||
WriteRootRef(symbols.raw());
|
||||
if (Snapshot::IncludesCode(kind_)) {
|
||||
for (intptr_t i = 0; i < StubCode::NumEntries(); i++) {
|
||||
WriteRef(StubCode::EntryAt(i).raw());
|
||||
WriteRootRef(StubCode::EntryAt(i).raw());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5031,7 +5160,7 @@ void Serializer::WriteIsolateSnapshot(intptr_t num_base_objects,
|
|||
|
||||
// Write roots.
|
||||
for (RawObject** p = from; p <= to; p++) {
|
||||
WriteRef(*p);
|
||||
WriteRootRef(*p);
|
||||
}
|
||||
|
||||
#if defined(DEBUG)
|
||||
|
@ -5567,6 +5696,13 @@ class SeedVMIsolateVisitor : public ClassVisitor, public FunctionVisitor {
|
|||
KernelProgramInfo& kernel_program_info_;
|
||||
};
|
||||
|
||||
#if defined(DART_PRECOMPILER)
|
||||
DEFINE_FLAG(charp,
|
||||
write_v8_snapshot_profile_to,
|
||||
NULL,
|
||||
"Write a snapshot profile in V8 format to a file.");
|
||||
#endif
|
||||
|
||||
FullSnapshotWriter::FullSnapshotWriter(Snapshot::Kind kind,
|
||||
uint8_t** vm_snapshot_data_buffer,
|
||||
uint8_t** isolate_snapshot_data_buffer,
|
||||
|
@ -5630,6 +5766,12 @@ FullSnapshotWriter::FullSnapshotWriter(Snapshot::Kind kind,
|
|||
saved_symbol_table_ = object_store->symbol_table();
|
||||
new_vm_symbol_table_ = Dart::vm_isolate()->object_store()->symbol_table();
|
||||
}
|
||||
|
||||
#if defined(DART_PRECOMPILER)
|
||||
if (FLAG_write_v8_snapshot_profile_to != nullptr) {
|
||||
profile_writer_ = new (zone()) V8SnapshotProfileWriter(zone());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
FullSnapshotWriter::~FullSnapshotWriter() {
|
||||
|
@ -5647,7 +5789,8 @@ intptr_t FullSnapshotWriter::WriteVMSnapshot() {
|
|||
|
||||
ASSERT(vm_snapshot_data_buffer_ != NULL);
|
||||
Serializer serializer(thread(), kind_, vm_snapshot_data_buffer_, alloc_,
|
||||
kInitialSize, vm_image_writer_);
|
||||
kInitialSize, vm_image_writer_, /*vm=*/true,
|
||||
profile_writer_);
|
||||
|
||||
serializer.ReserveHeader();
|
||||
serializer.WriteVersionAndFeatures(true);
|
||||
|
@ -5661,10 +5804,12 @@ intptr_t FullSnapshotWriter::WriteVMSnapshot() {
|
|||
clustered_vm_size_ = serializer.bytes_written();
|
||||
|
||||
if (Snapshot::IncludesCode(kind_)) {
|
||||
vm_image_writer_->SetProfileWriter(profile_writer_);
|
||||
vm_image_writer_->Write(serializer.stream(), true);
|
||||
mapped_data_size_ += vm_image_writer_->data_size();
|
||||
mapped_text_size_ += vm_image_writer_->text_size();
|
||||
vm_image_writer_->ResetOffsets();
|
||||
vm_image_writer_->ClearProfileWriter();
|
||||
}
|
||||
|
||||
// The clustered part + the direct mapped data part.
|
||||
|
@ -5677,7 +5822,8 @@ void FullSnapshotWriter::WriteIsolateSnapshot(intptr_t num_base_objects) {
|
|||
thread(), Timeline::GetIsolateStream(), "WriteIsolateSnapshot"));
|
||||
|
||||
Serializer serializer(thread(), kind_, isolate_snapshot_data_buffer_, alloc_,
|
||||
kInitialSize, isolate_image_writer_);
|
||||
kInitialSize, isolate_image_writer_, /*vm=*/false,
|
||||
profile_writer_);
|
||||
ObjectStore* object_store = isolate()->object_store();
|
||||
ASSERT(object_store != NULL);
|
||||
|
||||
|
@ -5690,6 +5836,7 @@ void FullSnapshotWriter::WriteIsolateSnapshot(intptr_t num_base_objects) {
|
|||
clustered_isolate_size_ = serializer.bytes_written();
|
||||
|
||||
if (Snapshot::IncludesCode(kind_)) {
|
||||
isolate_image_writer_->SetProfileWriter(profile_writer_);
|
||||
isolate_image_writer_->Write(serializer.stream(), false);
|
||||
#if defined(DART_PRECOMPILER)
|
||||
isolate_image_writer_->DumpStatistics();
|
||||
|
@ -5698,6 +5845,7 @@ void FullSnapshotWriter::WriteIsolateSnapshot(intptr_t num_base_objects) {
|
|||
mapped_data_size_ += isolate_image_writer_->data_size();
|
||||
mapped_text_size_ += isolate_image_writer_->text_size();
|
||||
isolate_image_writer_->ResetOffsets();
|
||||
isolate_image_writer_->ClearProfileWriter();
|
||||
}
|
||||
|
||||
// The clustered part + the direct mapped data part.
|
||||
|
@ -5726,6 +5874,12 @@ void FullSnapshotWriter::WriteFullSnapshot() {
|
|||
clustered_vm_size_ + clustered_isolate_size_ + mapped_data_size_ +
|
||||
mapped_text_size_);
|
||||
}
|
||||
|
||||
#if defined(DART_PRECOMPILER)
|
||||
if (FLAG_write_v8_snapshot_profile_to != nullptr) {
|
||||
profile_writer_->Write(FLAG_write_v8_snapshot_profile_to);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
FullSnapshotReader::FullSnapshotReader(const Snapshot* snapshot,
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "vm/object.h"
|
||||
#include "vm/snapshot.h"
|
||||
#include "vm/type_testing_stubs.h"
|
||||
#include "vm/v8_snapshot_writer.h"
|
||||
#include "vm/version.h"
|
||||
|
||||
#if defined(DEBUG)
|
||||
|
@ -132,7 +133,9 @@ class Serializer : public StackResource {
|
|||
uint8_t** buffer,
|
||||
ReAlloc alloc,
|
||||
intptr_t initial_size,
|
||||
ImageWriter* image_writer_);
|
||||
ImageWriter* image_writer_,
|
||||
bool vm_,
|
||||
V8SnapshotProfileWriter* profile_writer = nullptr);
|
||||
~Serializer();
|
||||
|
||||
intptr_t WriteVMSnapshot(const Array& symbols,
|
||||
|
@ -142,12 +145,26 @@ class Serializer : public StackResource {
|
|||
|
||||
void AddVMIsolateBaseObjects();
|
||||
|
||||
void AddBaseObject(RawObject* base_object) {
|
||||
AssignRef(base_object);
|
||||
void AddBaseObject(RawObject* base_object,
|
||||
const char* type = nullptr,
|
||||
const char* name = nullptr) {
|
||||
intptr_t ref = AssignRef(base_object);
|
||||
num_base_objects_++;
|
||||
|
||||
if (profile_writer_ != nullptr) {
|
||||
if (type == nullptr) {
|
||||
type = "Unknown";
|
||||
}
|
||||
if (name == nullptr) {
|
||||
name = "<base object>";
|
||||
}
|
||||
profile_writer_->SetObjectTypeAndName(
|
||||
{V8SnapshotProfileWriter::kSnapshot, ref}, type, name);
|
||||
profile_writer_->AddRoot({V8SnapshotProfileWriter::kSnapshot, ref});
|
||||
}
|
||||
}
|
||||
|
||||
void AssignRef(RawObject* object) {
|
||||
intptr_t AssignRef(RawObject* object) {
|
||||
ASSERT(next_ref_index_ != 0);
|
||||
if (object->IsHeapObject()) {
|
||||
// The object id weak table holds image offsets for Instructions instead
|
||||
|
@ -168,7 +185,7 @@ class Serializer : public StackResource {
|
|||
smi_ids_.Insert(new_pair);
|
||||
}
|
||||
}
|
||||
next_ref_index_++;
|
||||
return next_ref_index_++;
|
||||
}
|
||||
|
||||
void Push(RawObject* object);
|
||||
|
@ -202,6 +219,11 @@ class Serializer : public StackResource {
|
|||
WriteStream* stream() { return &stream_; }
|
||||
intptr_t bytes_written() { return stream_.bytes_written(); }
|
||||
|
||||
void TraceStartWritingObject(const char* type,
|
||||
RawObject* obj,
|
||||
RawString* name);
|
||||
void TraceEndWritingObject();
|
||||
|
||||
// Writes raw data to the stream (basic type).
|
||||
// sizeof(T) must be in {1,2,4,8}.
|
||||
template <typename T>
|
||||
|
@ -214,21 +236,19 @@ class Serializer : public StackResource {
|
|||
}
|
||||
void Align(intptr_t alignment) { stream_.Align(alignment); }
|
||||
|
||||
void WriteRef(RawObject* object) {
|
||||
void WriteRef(RawObject* object, bool is_root = false) {
|
||||
intptr_t id = 0;
|
||||
if (!object->IsHeapObject()) {
|
||||
RawSmi* smi = Smi::RawCast(object);
|
||||
intptr_t id = smi_ids_.Lookup(smi)->id_;
|
||||
id = smi_ids_.Lookup(smi)->id_;
|
||||
if (id == 0) {
|
||||
FATAL("Missing ref");
|
||||
}
|
||||
WriteUnsigned(id);
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
// The object id weak table holds image offsets for Instructions instead
|
||||
// of ref indices.
|
||||
ASSERT(!object->IsInstructions());
|
||||
intptr_t id = heap_->GetObjectId(object);
|
||||
id = heap_->GetObjectId(object);
|
||||
if (id == 0) {
|
||||
if (object->IsCode() && !Snapshot::IncludesCode(kind_)) {
|
||||
WriteRef(Object::null());
|
||||
|
@ -241,14 +261,31 @@ class Serializer : public StackResource {
|
|||
}
|
||||
#endif // !DART_PRECOMPILED_RUNTIME
|
||||
if (object->IsSendPort()) {
|
||||
// TODO(rmacnak): Do a better job of resetting fields in precompilation
|
||||
// and assert this is unreachable.
|
||||
// TODO(rmacnak): Do a better job of resetting fields in
|
||||
// precompilation and assert this is unreachable.
|
||||
WriteRef(Object::null());
|
||||
return;
|
||||
}
|
||||
FATAL("Missing ref");
|
||||
}
|
||||
}
|
||||
|
||||
WriteUnsigned(id);
|
||||
|
||||
if (profile_writer_ != nullptr) {
|
||||
if (object_currently_writing_id_ != 0) {
|
||||
profile_writer_->AttributeReferenceTo(
|
||||
{V8SnapshotProfileWriter::kSnapshot, object_currently_writing_id_},
|
||||
{V8SnapshotProfileWriter::kSnapshot, id});
|
||||
} else {
|
||||
ASSERT(is_root);
|
||||
profile_writer_->AddRoot({V8SnapshotProfileWriter::kSnapshot, id});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WriteRootRef(RawObject* object) {
|
||||
WriteRef(object, /* is_root = */ true);
|
||||
}
|
||||
|
||||
void WriteTokenPosition(TokenPosition pos) {
|
||||
|
@ -263,6 +300,7 @@ class Serializer : public StackResource {
|
|||
void WriteInstructions(RawInstructions* instr, RawCode* code);
|
||||
bool GetSharedDataOffset(RawObject* object, uint32_t* offset) const;
|
||||
uint32_t GetDataOffset(RawObject* object) const;
|
||||
void TraceDataOffset(uint32_t offset);
|
||||
intptr_t GetDataSize() const;
|
||||
intptr_t GetTextSize() const;
|
||||
|
||||
|
@ -286,6 +324,13 @@ class Serializer : public StackResource {
|
|||
intptr_t next_ref_index_;
|
||||
SmiObjectIdMap smi_ids_;
|
||||
|
||||
// True if writing VM snapshot, false for Isolate snapshot.
|
||||
bool vm_;
|
||||
|
||||
V8SnapshotProfileWriter* profile_writer_ = nullptr;
|
||||
intptr_t object_currently_writing_id_ = 0;
|
||||
intptr_t object_currently_writing_start_ = 0;
|
||||
|
||||
#if defined(SNAPSHOT_BACKTRACE)
|
||||
RawObject* current_parent_;
|
||||
GrowableArray<Object*> parent_pairs_;
|
||||
|
@ -294,6 +339,27 @@ class Serializer : public StackResource {
|
|||
DISALLOW_IMPLICIT_CONSTRUCTORS(Serializer);
|
||||
};
|
||||
|
||||
struct SerializerWritingObjectScope {
|
||||
SerializerWritingObjectScope(Serializer* serializer,
|
||||
const char* type,
|
||||
RawObject* object,
|
||||
RawString* name)
|
||||
: serializer_(serializer) {
|
||||
serializer_->TraceStartWritingObject(type, object, name);
|
||||
}
|
||||
|
||||
~SerializerWritingObjectScope() { serializer_->TraceEndWritingObject(); }
|
||||
|
||||
private:
|
||||
Serializer* serializer_;
|
||||
};
|
||||
|
||||
#define AutoTraceObject(obj) \
|
||||
SerializerWritingObjectScope scope_##__COUNTER__(s, name(), obj, nullptr)
|
||||
|
||||
#define AutoTraceObjectName(obj, str) \
|
||||
SerializerWritingObjectScope scope_##__COUNTER__(s, name(), obj, str)
|
||||
|
||||
class Deserializer : public StackResource {
|
||||
public:
|
||||
Deserializer(Thread* thread,
|
||||
|
@ -446,6 +512,8 @@ class FullSnapshotWriter {
|
|||
intptr_t mapped_data_size_;
|
||||
intptr_t mapped_text_size_;
|
||||
|
||||
V8SnapshotProfileWriter* profile_writer_ = nullptr;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(FullSnapshotWriter);
|
||||
};
|
||||
|
||||
|
|
|
@ -268,8 +268,12 @@ void ImageWriter::Write(WriteStream* clustered_stream, bool vm) {
|
|||
}
|
||||
|
||||
// Append the direct-mapped RO data objects after the clustered snapshot.
|
||||
offset_space_ = vm ? V8SnapshotProfileWriter::kVmData
|
||||
: V8SnapshotProfileWriter::kIsolateData;
|
||||
WriteROData(clustered_stream);
|
||||
|
||||
offset_space_ = vm ? V8SnapshotProfileWriter::kVmText
|
||||
: V8SnapshotProfileWriter::kIsolateText;
|
||||
WriteText(clustered_stream, vm);
|
||||
}
|
||||
|
||||
|
@ -278,14 +282,19 @@ void ImageWriter::WriteROData(WriteStream* stream) {
|
|||
|
||||
// Heap page starts here.
|
||||
|
||||
intptr_t section_start = stream->Position();
|
||||
|
||||
stream->WriteWord(next_data_offset_); // Data length.
|
||||
COMPILE_ASSERT(OS::kMaxPreferredCodeAlignment >= kObjectAlignment);
|
||||
stream->Align(OS::kMaxPreferredCodeAlignment);
|
||||
|
||||
ASSERT(stream->Position() - section_start == Image::kHeaderSize);
|
||||
|
||||
// Heap page objects start here.
|
||||
|
||||
for (intptr_t i = 0; i < objects_.length(); i++) {
|
||||
const Object& obj = *objects_[i].obj_;
|
||||
AutoTraceImage(section_start, stream, "ROData");
|
||||
|
||||
NoSafepointScope no_safepoint;
|
||||
uword start = reinterpret_cast<uword>(obj.raw()) - kHeapObjectTag;
|
||||
|
@ -370,10 +379,21 @@ void AssemblyImageWriter::WriteText(WriteStream* clustered_stream, bool vm) {
|
|||
ObjectStore* object_store = Isolate::Current()->object_store();
|
||||
|
||||
TypeTestingStubFinder tts;
|
||||
intptr_t offset = Image::kHeaderSize;
|
||||
for (intptr_t i = 0; i < instructions_.length(); i++) {
|
||||
const Instructions& insns = *instructions_[i].insns_;
|
||||
const Code& code = *instructions_[i].code_;
|
||||
|
||||
if (profile_writer_ != nullptr) {
|
||||
ASSERT(offset_space_ != V8SnapshotProfileWriter::kSnapshot);
|
||||
profile_writer_->SetObjectTypeAndName({offset_space_, offset},
|
||||
"Instructions",
|
||||
/*name=*/nullptr);
|
||||
profile_writer_->AttributeBytesTo({offset_space_, offset},
|
||||
insns.raw()->Size());
|
||||
}
|
||||
offset += insns.raw()->Size();
|
||||
|
||||
ASSERT(insns.raw()->Size() % sizeof(uint64_t) == 0);
|
||||
|
||||
// 1. Write from the header to the entry point.
|
||||
|
@ -595,6 +615,7 @@ void BlobImageWriter::WriteText(WriteStream* clustered_stream, bool vm) {
|
|||
NoSafepointScope no_safepoint;
|
||||
for (intptr_t i = 0; i < instructions_.length(); i++) {
|
||||
const Instructions& insns = *instructions_[i].insns_;
|
||||
AutoTraceImage(0, &this->instructions_blob_stream_, "Instructions");
|
||||
|
||||
uword beginning = reinterpret_cast<uword>(insns.raw_ptr());
|
||||
uword entry = beginning + Instructions::HeaderSize();
|
||||
|
|
|
@ -5,12 +5,15 @@
|
|||
#ifndef RUNTIME_VM_IMAGE_SNAPSHOT_H_
|
||||
#define RUNTIME_VM_IMAGE_SNAPSHOT_H_
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "platform/assert.h"
|
||||
#include "vm/allocation.h"
|
||||
#include "vm/datastream.h"
|
||||
#include "vm/globals.h"
|
||||
#include "vm/growable_array.h"
|
||||
#include "vm/hash_map.h"
|
||||
#include "vm/v8_snapshot_writer.h"
|
||||
|
||||
namespace dart {
|
||||
|
||||
|
@ -120,6 +123,14 @@ class ImageWriter : public ValueObject {
|
|||
|
||||
void DumpStatistics();
|
||||
|
||||
void SetProfileWriter(V8SnapshotProfileWriter* profile_writer) {
|
||||
profile_writer_ = profile_writer;
|
||||
}
|
||||
|
||||
void ClearProfileWriter() { profile_writer_ = nullptr; }
|
||||
|
||||
void TraceInstructions(const Instructions& instructions);
|
||||
|
||||
protected:
|
||||
void WriteROData(WriteStream* stream);
|
||||
virtual void WriteText(WriteStream* clustered_stream, bool vm) = 0;
|
||||
|
@ -162,10 +173,56 @@ class ImageWriter : public ValueObject {
|
|||
ObjectOffsetMap shared_instructions_;
|
||||
ObjectOffsetMap reuse_instructions_;
|
||||
|
||||
V8SnapshotProfileWriter::IdSpace offset_space_ =
|
||||
V8SnapshotProfileWriter::kSnapshot;
|
||||
V8SnapshotProfileWriter* profile_writer_ = nullptr;
|
||||
|
||||
template <class T>
|
||||
friend class TraceImageObjectScope;
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(ImageWriter);
|
||||
};
|
||||
|
||||
#define AutoTraceImage(section_offset, stream, type_name) \
|
||||
auto AutoTraceImagObjectScopeVar##__COUNTER__ = \
|
||||
TraceImageObjectScope<std::remove_pointer<decltype(stream)>::type>( \
|
||||
this, section_offset, stream, type_name);
|
||||
|
||||
template <typename T>
|
||||
class TraceImageObjectScope {
|
||||
public:
|
||||
TraceImageObjectScope(ImageWriter* writer,
|
||||
intptr_t section_offset,
|
||||
const T* stream,
|
||||
const char* type)
|
||||
: writer_(writer),
|
||||
stream_(stream),
|
||||
section_offset_(section_offset),
|
||||
start_offset_(stream_->Position() - section_offset) {
|
||||
if (writer_->profile_writer_ != nullptr) {
|
||||
ASSERT(writer_->offset_space_ != V8SnapshotProfileWriter::kSnapshot);
|
||||
writer_->profile_writer_->SetObjectTypeAndName(
|
||||
{writer_->offset_space_, start_offset_}, type, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
~TraceImageObjectScope() {
|
||||
if (writer_->profile_writer_ != nullptr) {
|
||||
ASSERT(writer_->offset_space_ != V8SnapshotProfileWriter::kSnapshot);
|
||||
writer_->profile_writer_->AttributeBytesTo(
|
||||
{writer_->offset_space_, start_offset_},
|
||||
stream_->Position() - section_offset_ - start_offset_);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
ImageWriter* writer_;
|
||||
const T* stream_;
|
||||
intptr_t section_offset_;
|
||||
intptr_t start_offset_;
|
||||
};
|
||||
|
||||
class AssemblyImageWriter : public ImageWriter {
|
||||
public:
|
||||
AssemblyImageWriter(Thread* thread,
|
||||
|
|
|
@ -298,6 +298,10 @@ void JSONWriter::PrintPropertyName(const char* name) {
|
|||
buffer_.AddChar(':');
|
||||
}
|
||||
|
||||
void JSONWriter::PrintNewline() {
|
||||
buffer_.AddChar('\n');
|
||||
}
|
||||
|
||||
void JSONWriter::PrintCommaIfNeeded() {
|
||||
if (NeedComma()) {
|
||||
buffer_.AddChar(',');
|
||||
|
|
|
@ -74,6 +74,8 @@ class JSONWriter : ValueObject {
|
|||
|
||||
void PrintPropertyName(const char* name);
|
||||
|
||||
void PrintNewline();
|
||||
|
||||
void AddEscapedUTF8String(const char* s);
|
||||
void AddEscapedUTF8String(const char* s, intptr_t len);
|
||||
|
||||
|
|
|
@ -4456,6 +4456,7 @@ class Instructions : public Object {
|
|||
friend class Code;
|
||||
friend class AssemblyImageWriter;
|
||||
friend class BlobImageWriter;
|
||||
friend class ImageWriter;
|
||||
};
|
||||
|
||||
class LocalVarDescriptors : public Object {
|
||||
|
|
289
runtime/vm/v8_snapshot_writer.cc
Normal file
289
runtime/vm/v8_snapshot_writer.cc
Normal file
|
@ -0,0 +1,289 @@
|
|||
// 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.
|
||||
|
||||
#if defined(DART_PRECOMPILER)
|
||||
|
||||
#include "vm/v8_snapshot_writer.h"
|
||||
|
||||
#include "vm/dart.h"
|
||||
#include "vm/os.h"
|
||||
|
||||
namespace dart {
|
||||
|
||||
const char* ZoneString(Zone* Z, const char* str) {
|
||||
const intptr_t len = strlen(str) + 1;
|
||||
char* dest = Z->Alloc<char>(len);
|
||||
snprintf(dest, len, "%s", str);
|
||||
return dest;
|
||||
}
|
||||
|
||||
V8SnapshotProfileWriter::V8SnapshotProfileWriter(Zone* zone)
|
||||
: zone_(zone),
|
||||
node_types_(zone_),
|
||||
edge_types_(zone_),
|
||||
strings_(zone),
|
||||
roots_(zone_, 100) {
|
||||
node_types_.Insert({"Unknown", kUnknown});
|
||||
node_types_.Insert({"ArtificialRoot", kArtificialRoot});
|
||||
|
||||
edge_types_.Insert({"context", kContext});
|
||||
edge_types_.Insert({"element", kElement});
|
||||
edge_types_.Insert({"property", kProperty});
|
||||
edge_types_.Insert({"internal", kInternal});
|
||||
edge_types_.Insert({"hidden", kHidden});
|
||||
edge_types_.Insert({"shortcut", kShortcut});
|
||||
edge_types_.Insert({"weak", kWeak});
|
||||
edge_types_.Insert({"extra", kExtra});
|
||||
|
||||
strings_.Insert({"<unknown>", kUnknownString});
|
||||
strings_.Insert({"<object>", kObjectString});
|
||||
strings_.Insert({"<property>", kPropertyString});
|
||||
strings_.Insert({"<artificial root>", kArtificialRootString});
|
||||
}
|
||||
|
||||
void V8SnapshotProfileWriter::SetObjectTypeAndName(ObjectId object_id,
|
||||
const char* type,
|
||||
const char* name) {
|
||||
ASSERT(type != nullptr);
|
||||
NodeInfo* info = EnsureId(object_id);
|
||||
|
||||
if (!node_types_.HasKey(type)) {
|
||||
node_types_.Insert({ZoneString(zone_, type), node_types_.Size()});
|
||||
}
|
||||
|
||||
intptr_t type_id = node_types_.LookupValue(type);
|
||||
ASSERT(info->type == kUnknown || info->type == type_id);
|
||||
info->type = type_id;
|
||||
|
||||
if (name != nullptr) {
|
||||
info->name = EnsureString(name);
|
||||
} else {
|
||||
info->name = EnsureString(type);
|
||||
}
|
||||
}
|
||||
|
||||
void V8SnapshotProfileWriter::AttributeBytesTo(ObjectId object_id,
|
||||
size_t num_bytes) {
|
||||
EnsureId(object_id)->self_size += num_bytes;
|
||||
}
|
||||
|
||||
void V8SnapshotProfileWriter::AttributeReferenceTo(ObjectId object_id,
|
||||
ObjectId to_object_id) {
|
||||
EnsureId(to_object_id);
|
||||
NodeInfo* info = EnsureId(object_id);
|
||||
|
||||
#if defined(DEBUG)
|
||||
// We should never add a reference twice.
|
||||
for (intptr_t i = 0; i < info->edges->length(); ++i) {
|
||||
ASSERT(info->edges->At(i).to_node != object_id);
|
||||
}
|
||||
#endif
|
||||
|
||||
info->edges->Add(EdgeInfo{to_object_id});
|
||||
++edge_count_;
|
||||
}
|
||||
|
||||
V8SnapshotProfileWriter::NodeInfo V8SnapshotProfileWriter::DefaultNode(
|
||||
ObjectId object_id) {
|
||||
return {
|
||||
kUnknown,
|
||||
kUnknownString,
|
||||
object_id,
|
||||
0,
|
||||
new (zone_) ZoneGrowableArray<EdgeInfo>(zone_, 0),
|
||||
};
|
||||
}
|
||||
|
||||
V8SnapshotProfileWriter::NodeInfo V8SnapshotProfileWriter::ArtificialRoot() {
|
||||
return {
|
||||
kArtificialRoot, kArtificialRootString, {kArtificial, 0}, 0, nullptr, 0,
|
||||
};
|
||||
}
|
||||
|
||||
V8SnapshotProfileWriter::NodeInfo* V8SnapshotProfileWriter::EnsureId(
|
||||
ObjectId object_id) {
|
||||
if (!nodes_.HasKey(object_id)) {
|
||||
NodeInfo info = DefaultNode(object_id);
|
||||
nodes_.Insert({object_id, info});
|
||||
}
|
||||
return &nodes_.Lookup(object_id)->value;
|
||||
}
|
||||
|
||||
intptr_t V8SnapshotProfileWriter::EnsureString(const char* str) {
|
||||
if (!strings_.HasKey(str)) {
|
||||
strings_.Insert({ZoneString(zone_, str), strings_.Size()});
|
||||
return strings_.Size() - 1;
|
||||
}
|
||||
return strings_.LookupValue(str);
|
||||
}
|
||||
|
||||
void V8SnapshotProfileWriter::WriteNodeInfo(JSONWriter* writer,
|
||||
const NodeInfo& info) {
|
||||
writer->PrintValue(info.type);
|
||||
writer->PrintValue(info.name);
|
||||
writer->PrintValue(NodeIdFor(info.id));
|
||||
writer->PrintValue(info.self_size);
|
||||
// The artificial root has 'nullptr' edges, it actually points to all the
|
||||
// roots.
|
||||
writer->PrintValue64(info.edges != nullptr ? info.edges->length()
|
||||
: roots_.length());
|
||||
writer->PrintNewline();
|
||||
}
|
||||
|
||||
void V8SnapshotProfileWriter::WriteEdgeInfo(JSONWriter* writer,
|
||||
const EdgeInfo& info) {
|
||||
writer->PrintValue64(kProperty); // type, not really used atm
|
||||
writer->PrintValue64(
|
||||
kPropertyString); // name_or_index, not really used either
|
||||
writer->PrintValue64(nodes_.LookupValue(info.to_node).offset);
|
||||
writer->PrintNewline();
|
||||
}
|
||||
|
||||
void V8SnapshotProfileWriter::AddRoot(ObjectId object_id) {
|
||||
EnsureId(object_id);
|
||||
roots_.Add(object_id);
|
||||
}
|
||||
|
||||
void V8SnapshotProfileWriter::WriteStringsTable(
|
||||
JSONWriter* writer,
|
||||
const DirectChainedHashMap<StringToIntMapTraits>& map) {
|
||||
const char** strings = zone_->Alloc<const char*>(map.Size());
|
||||
StringToIntMapTraits::Pair* pair = nullptr;
|
||||
auto it = map.GetIterator();
|
||||
while ((pair = it.Next()) != nullptr) {
|
||||
ASSERT(pair->value >= 0 && pair->value < map.Size());
|
||||
strings[pair->value] = pair->key;
|
||||
}
|
||||
for (intptr_t i = 0; i < map.Size(); ++i) {
|
||||
writer->PrintValue(strings[i]);
|
||||
writer->PrintNewline();
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
{
|
||||
writer->OpenArray();
|
||||
WriteStringsTable(writer, node_types_);
|
||||
writer->CloseArray();
|
||||
}
|
||||
writer->CloseArray();
|
||||
}
|
||||
|
||||
{
|
||||
writer->OpenArray("edge_fields");
|
||||
writer->PrintValue("type");
|
||||
writer->PrintValue("name_or_index");
|
||||
writer->PrintValue("to_node");
|
||||
writer->CloseArray();
|
||||
}
|
||||
|
||||
{
|
||||
writer->OpenArray("edge_types");
|
||||
{
|
||||
writer->OpenArray();
|
||||
WriteStringsTable(writer, edge_types_);
|
||||
writer->CloseArray();
|
||||
}
|
||||
writer->CloseArray();
|
||||
}
|
||||
|
||||
writer->CloseObject();
|
||||
|
||||
writer->PrintProperty64("node_count",
|
||||
nodes_.Size() + 1 /* artificial root */);
|
||||
writer->PrintProperty64("edge_count", edge_count_ + roots_.length());
|
||||
}
|
||||
writer->CloseObject();
|
||||
|
||||
{
|
||||
writer->OpenArray("nodes");
|
||||
// Write the artificial root node.
|
||||
WriteNodeInfo(writer, ArtificialRoot());
|
||||
intptr_t offset = kNumNodeFields;
|
||||
ObjectIdToNodeInfoTraits::Pair* entry = nullptr;
|
||||
auto it = nodes_.GetIterator();
|
||||
while ((entry = it.Next()) != nullptr) {
|
||||
ASSERT(entry->key == entry->value.id);
|
||||
entry->value.offset = offset;
|
||||
WriteNodeInfo(writer, entry->value);
|
||||
offset += kNumNodeFields;
|
||||
}
|
||||
writer->CloseArray();
|
||||
}
|
||||
|
||||
{
|
||||
writer->OpenArray("edges");
|
||||
|
||||
// Write references from the artificial root to the actual roots.
|
||||
for (ObjectId root : roots_) {
|
||||
WriteEdgeInfo(writer, {root});
|
||||
}
|
||||
|
||||
ObjectIdToNodeInfoTraits::Pair* entry = nullptr;
|
||||
auto it = nodes_.GetIterator();
|
||||
while ((entry = it.Next()) != nullptr) {
|
||||
for (intptr_t i = 0; i < entry->value.edges->length(); ++i) {
|
||||
WriteEdgeInfo(writer, entry->value.edges->At(i));
|
||||
}
|
||||
}
|
||||
|
||||
writer->CloseArray();
|
||||
}
|
||||
|
||||
{
|
||||
writer->OpenArray("strings");
|
||||
WriteStringsTable(writer, strings_);
|
||||
writer->CloseArray();
|
||||
}
|
||||
|
||||
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("Could not access file callbacks to write snapshot profile.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto file = file_open(filename, /*write=*/true);
|
||||
if (file == nullptr) {
|
||||
OS::PrintErr("Failed to open file %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);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dart
|
||||
|
||||
#endif
|
187
runtime/vm/v8_snapshot_writer.h
Normal file
187
runtime/vm/v8_snapshot_writer.h
Normal file
|
@ -0,0 +1,187 @@
|
|||
// 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/json_writer.h"
|
||||
|
||||
namespace dart {
|
||||
|
||||
struct StringToIntMapTraits {
|
||||
typedef char const* Key;
|
||||
typedef intptr_t Value;
|
||||
|
||||
struct Pair {
|
||||
Key key;
|
||||
Value value;
|
||||
Pair() : key(nullptr), value(-1) {}
|
||||
Pair(Key k, Value v) : key(k), value(v) {}
|
||||
};
|
||||
|
||||
static Value ValueOf(Pair pair) { return pair.value; }
|
||||
|
||||
static Key KeyOf(Pair pair) { return pair.key; }
|
||||
|
||||
static size_t Hashcode(Key key) {
|
||||
/// SAMIR_TODO
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool IsKeyEqual(Pair x, Key y) { return strcmp(x.key, y) == 0; }
|
||||
};
|
||||
|
||||
class V8SnapshotProfileWriter : public ZoneAllocated {
|
||||
public:
|
||||
enum IdSpace {
|
||||
kSnapshot = 0, // Can be VM or Isolate heap, they share ids.
|
||||
kVmText = 1,
|
||||
kIsolateText = 2,
|
||||
kVmData = 3,
|
||||
kIsolateData = 4,
|
||||
kArtificial = 5, // Artificial objects (e.g. the global root).
|
||||
kIdSpaceBits = 3,
|
||||
};
|
||||
|
||||
typedef std::pair<IdSpace, intptr_t> ObjectId;
|
||||
|
||||
#if !defined(DART_PRECOMPILER)
|
||||
explicit V8SnapshotProfileWriter(Zone* zone) {}
|
||||
virtual ~V8SnapshotProfileWriter() {}
|
||||
|
||||
void SetObjectTypeAndName(ObjectId object_id,
|
||||
const char* type,
|
||||
const char* name) {}
|
||||
void AttributeBytesTo(ObjectId object_id, size_t num_bytes) {}
|
||||
void AttributeReferenceTo(ObjectId object_id, ObjectId to_object_id) {}
|
||||
void AddRoot(ObjectId object_id) {}
|
||||
#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(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(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 'object_id'. This does not affect
|
||||
// the number of bytes charged to 'object_id'.
|
||||
void AttributeReferenceTo(ObjectId object_id, ObjectId to_object_id);
|
||||
|
||||
// Marks an object as being a root in the graph. Used for analysis of the
|
||||
// graph.
|
||||
void AddRoot(ObjectId object_id);
|
||||
|
||||
// Write to a file in the V8 Snapshot Profile (JSON/.heapsnapshot) format.
|
||||
void Write(const char* file);
|
||||
|
||||
private:
|
||||
static constexpr intptr_t kNumNodeFields = 5;
|
||||
static constexpr intptr_t kNumEdgeFields = 3;
|
||||
|
||||
struct EdgeInfo {
|
||||
// 'type' and 'name_or_index' aren't supported yet.
|
||||
ObjectId to_node;
|
||||
};
|
||||
|
||||
struct NodeInfo {
|
||||
intptr_t type;
|
||||
intptr_t name;
|
||||
ObjectId id;
|
||||
intptr_t self_size;
|
||||
ZoneGrowableArray<EdgeInfo>* edges = nullptr;
|
||||
// Populated during serialization.
|
||||
intptr_t offset = -1;
|
||||
// 'trace_node_id' isn't supported.
|
||||
// 'edge_count' is computed on-demand.
|
||||
|
||||
// Used for testing sentinel in the hashtable.
|
||||
bool operator!=(const NodeInfo& other) { return id != other.id; }
|
||||
bool operator==(const NodeInfo& other) { return !(*this != other); }
|
||||
};
|
||||
|
||||
NodeInfo DefaultNode(ObjectId object_id);
|
||||
static NodeInfo ArtificialRoot();
|
||||
|
||||
NodeInfo* EnsureId(ObjectId object_id);
|
||||
intptr_t EnsureString(const char* str);
|
||||
static intptr_t NodeIdFor(ObjectId id) {
|
||||
return (id.second << kIdSpaceBits) | id.first;
|
||||
}
|
||||
|
||||
enum ConstantStrings {
|
||||
kUnknownString = 0,
|
||||
kPropertyString = 1,
|
||||
kObjectString = 2,
|
||||
kArtificialRootString = 3,
|
||||
};
|
||||
|
||||
enum ConstantEdgeTypes {
|
||||
kContext = 0,
|
||||
kElement = 1,
|
||||
kProperty = 2,
|
||||
kInternal = 3,
|
||||
kHidden = 4,
|
||||
kShortcut = 5,
|
||||
kWeak = 6,
|
||||
kExtra = 7,
|
||||
};
|
||||
|
||||
enum ConstantNodeTypes {
|
||||
kUnknown = 0,
|
||||
kArtificialRoot = 1,
|
||||
};
|
||||
|
||||
struct ObjectIdToNodeInfoTraits {
|
||||
typedef ObjectId Key;
|
||||
typedef NodeInfo Value;
|
||||
|
||||
struct Pair {
|
||||
Key key;
|
||||
Value value;
|
||||
Pair() : key{kSnapshot, -1}, value{0, 0, {kSnapshot, -1}, 0, nullptr} {};
|
||||
Pair(Key k, Value v) : key(k), value(v) {}
|
||||
};
|
||||
|
||||
static Key KeyOf(const Pair& pair) { return pair.key; }
|
||||
|
||||
static Value ValueOf(const Pair& pair) { return pair.value; }
|
||||
|
||||
static size_t Hashcode(Key key) { return NodeIdFor(key); }
|
||||
|
||||
static bool IsKeyEqual(const Pair& x, Key y) { return x.key == y; }
|
||||
};
|
||||
|
||||
Zone* zone_;
|
||||
void Write(JSONWriter* writer);
|
||||
void WriteNodeInfo(JSONWriter* writer, const NodeInfo& info);
|
||||
void WriteEdgeInfo(JSONWriter* writer, const EdgeInfo& info);
|
||||
void WriteStringsTable(JSONWriter* writer,
|
||||
const DirectChainedHashMap<StringToIntMapTraits>& map);
|
||||
|
||||
DirectChainedHashMap<ObjectIdToNodeInfoTraits> nodes_;
|
||||
DirectChainedHashMap<StringToIntMapTraits> node_types_;
|
||||
DirectChainedHashMap<StringToIntMapTraits> edge_types_;
|
||||
DirectChainedHashMap<StringToIntMapTraits> strings_;
|
||||
ZoneGrowableArray<ObjectId> roots_;
|
||||
size_t edge_count_ = 0;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace dart
|
||||
|
||||
#endif // RUNTIME_VM_V8_SNAPSHOT_WRITER_H_
|
|
@ -333,6 +333,8 @@ vm_sources = [
|
|||
"unicode_data.cc",
|
||||
"uri.cc",
|
||||
"uri.h",
|
||||
"v8_snapshot_writer.cc",
|
||||
"v8_snapshot_writer.h",
|
||||
"virtual_memory.cc",
|
||||
"virtual_memory.h",
|
||||
"virtual_memory_android.cc",
|
||||
|
|
Loading…
Reference in a new issue