458be23fa7
Fixes https://github.com/dart-lang/sdk/issues/45683 TEST=pkg-*-try bots Cq-Include-Trybots: luci.dart.try:pkg-linux-release-try,pkg-mac-release-try,pkg-win-release-try Change-Id: Ie10f313da9778d001f9c4fb618997e3b3c781dd0 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/195263 Commit-Queue: Vyacheslav Egorov <vegorov@google.com> Reviewed-by: Tess Strickland <sstrickl@google.com>
632 lines
21 KiB
Dart
632 lines
21 KiB
Dart
// Copyright (c) 2020, 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.
|
|
|
|
/// This library contains utilities for reading and analyzing snapshot profiles
|
|
/// produced by `--write-v8-snapshot-profile-to` VM flag.
|
|
library vm_snapshot_analysis.v8_profile;
|
|
|
|
import 'package:collection/collection.dart';
|
|
|
|
import 'package:vm_snapshot_analysis/src/dominators.dart' as dominators;
|
|
import 'package:vm_snapshot_analysis/name.dart';
|
|
import 'package:vm_snapshot_analysis/program_info.dart';
|
|
|
|
/// This class represents snapshot graph.
|
|
///
|
|
/// Note that we do not eagerly deserialize the graph, instead we provide helper
|
|
/// methods and wrapper objects to work with serialized representation.
|
|
class Snapshot {
|
|
final Meta meta;
|
|
final int nodeCount;
|
|
final int edgeCount;
|
|
|
|
/// Serialized flat representation of nodes in the graph. Each node occupies
|
|
/// [meta.nodeFieldCount] consecutive elements of the list.
|
|
final List _nodes;
|
|
|
|
/// Serialized flat representation of edges between nodes. Each edge occupies
|
|
/// [meta.edgeFieldCount] consecutive elements of the list. All outgoing edges
|
|
/// for a node are serialized consecutively, number of outgoing edges is given
|
|
/// by the value at index [meta.nodeEdgeCountIndex] inside the node.
|
|
final List _edges;
|
|
|
|
/// Auxiliary array which gives starting index of edges (in the [_edges] list)
|
|
/// for the given node index.
|
|
final List<int> _edgesStartIndexForNode;
|
|
|
|
late final List<int> _dominators = _computeDominators(this);
|
|
|
|
final List strings;
|
|
|
|
Snapshot._(this.meta, this.nodeCount, this.edgeCount, this._nodes,
|
|
this._edges, this.strings, this._edgesStartIndexForNode);
|
|
|
|
/// Return node with the given index.
|
|
Node nodeAt(int index) {
|
|
assert(index >= 0, 'Node index should be positive: $index');
|
|
return Node._(snapshot: this, index: index);
|
|
}
|
|
|
|
/// Return all nodes in the snapshot.
|
|
Iterable<Node> get nodes => Iterable.generate(nodeCount, nodeAt);
|
|
|
|
/// Return dominator node for the given node [n].
|
|
Node dominatorOf(Node n) {
|
|
return nodeAt(_dominators[n.index]);
|
|
}
|
|
|
|
/// Returns true if the given JSON object is likely to be a serialized
|
|
/// snapshot using V8 heap snapshot format.
|
|
static bool isV8HeapSnapshot(Object m) =>
|
|
m is Map<String, dynamic> && m.containsKey('snapshot');
|
|
|
|
/// Construct [Snapshot] object from the given JSON object.
|
|
factory Snapshot.fromJson(Map<String, dynamic> m) {
|
|
// Extract meta information first.
|
|
final meta = Meta._fromJson(m['snapshot']['meta']);
|
|
|
|
final nodes = (m['nodes'] as List<dynamic>).cast<int>();
|
|
|
|
// Build an array of starting indexes of edges for each node.
|
|
final edgesStartIndexForNode = <int>[0];
|
|
int nextStartIndex = 0;
|
|
for (var i = meta.nodeEdgeCountIndex;
|
|
i < nodes.length;
|
|
i += meta.nodeFieldCount) {
|
|
nextStartIndex += nodes[i];
|
|
edgesStartIndexForNode.add(nextStartIndex);
|
|
}
|
|
|
|
return Snapshot._(
|
|
meta,
|
|
m['snapshot']['node_count'],
|
|
m['snapshot']['edge_count'],
|
|
m['nodes'],
|
|
m['edges'],
|
|
m['strings'],
|
|
edgesStartIndexForNode);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
final buffer = StringBuffer();
|
|
buffer
|
|
..write("Node count: ")
|
|
..writeln(nodeCount)
|
|
..write("Edge count: ")
|
|
..writeln(edgeCount);
|
|
buffer.write("Nodes:");
|
|
for (final node in nodes) {
|
|
buffer
|
|
..writeln()
|
|
..write(node.index)
|
|
..write(': ')
|
|
..writeln(node);
|
|
}
|
|
return buffer.toString();
|
|
}
|
|
}
|
|
|
|
/// Meta-information about the serialized snapshot.
|
|
///
|
|
/// Describes the structure of serialized nodes and edges by giving indexes of
|
|
/// the various fields.
|
|
class Meta {
|
|
final int nodeTypeIndex;
|
|
final int nodeNameIndex;
|
|
final int nodeIdIndex;
|
|
final int nodeSelfSizeIndex;
|
|
final int nodeEdgeCountIndex;
|
|
final int nodeFieldCount;
|
|
|
|
final int edgeTypeIndex;
|
|
final int edgeNameOrIndexIndex;
|
|
final int edgeToNodeIndex;
|
|
final int edgeFieldCount;
|
|
|
|
final List<String> nodeTypes;
|
|
final List<String> edgeTypes;
|
|
|
|
Meta._(
|
|
{required this.nodeTypeIndex,
|
|
required this.nodeNameIndex,
|
|
required this.nodeIdIndex,
|
|
required this.nodeSelfSizeIndex,
|
|
required this.nodeEdgeCountIndex,
|
|
required this.nodeFieldCount,
|
|
required this.edgeTypeIndex,
|
|
required this.edgeNameOrIndexIndex,
|
|
required this.edgeToNodeIndex,
|
|
required this.edgeFieldCount,
|
|
required this.nodeTypes,
|
|
required this.edgeTypes});
|
|
|
|
factory Meta._fromJson(Map<String, dynamic> m) {
|
|
final nodeFields = m['node_fields'];
|
|
final nodeTypes = m['node_types'].first.cast<String>();
|
|
final edgeFields = m['edge_fields'];
|
|
final edgeTypes = m['edge_types'].first.cast<String>();
|
|
return Meta._(
|
|
nodeTypeIndex: nodeFields.indexOf('type'),
|
|
nodeNameIndex: nodeFields.indexOf('name'),
|
|
nodeIdIndex: nodeFields.indexOf('id'),
|
|
nodeSelfSizeIndex: nodeFields.indexOf('self_size'),
|
|
nodeEdgeCountIndex: nodeFields.indexOf('edge_count'),
|
|
nodeFieldCount: nodeFields.length,
|
|
edgeTypeIndex: edgeFields.indexOf('type'),
|
|
edgeNameOrIndexIndex: edgeFields.indexOf('name_or_index'),
|
|
edgeToNodeIndex: edgeFields.indexOf('to_node'),
|
|
edgeFieldCount: edgeFields.length,
|
|
nodeTypes: nodeTypes,
|
|
edgeTypes: edgeTypes);
|
|
}
|
|
}
|
|
|
|
/// Edge from [Node] to [Node] in the [Snapshot] graph.
|
|
class Edge {
|
|
final Snapshot snapshot;
|
|
|
|
/// Index of this [Edge] within the [snapshot].
|
|
final int index;
|
|
|
|
Edge._({required this.snapshot, required this.index});
|
|
|
|
String get type => snapshot
|
|
.meta.edgeTypes[snapshot._edges[_offset + snapshot.meta.edgeTypeIndex]];
|
|
|
|
Node get target {
|
|
return Node._(
|
|
snapshot: snapshot,
|
|
index: snapshot._edges[_offset + snapshot.meta.edgeToNodeIndex] ~/
|
|
snapshot.meta.nodeFieldCount);
|
|
}
|
|
|
|
String get name {
|
|
final nameOrIndex =
|
|
snapshot._edges[_offset + snapshot.meta.edgeNameOrIndexIndex];
|
|
return type == 'property' ? snapshot.strings[nameOrIndex] : '@$nameOrIndex';
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
final nameOrIndex =
|
|
snapshot._edges[_offset + snapshot.meta.edgeNameOrIndexIndex];
|
|
return {
|
|
'type': type,
|
|
'nameOrIndex':
|
|
type == 'property' ? snapshot.strings[nameOrIndex] : nameOrIndex,
|
|
'toNode': target.index,
|
|
}.toString();
|
|
}
|
|
|
|
/// Offset into [Snapshot._edges] list at which this edge begins.
|
|
int get _offset => index * snapshot.meta.edgeFieldCount;
|
|
}
|
|
|
|
/// Node in the [Snapshot] graph.
|
|
class Node {
|
|
final Snapshot snapshot;
|
|
|
|
/// Index of this [Node] within the [snapshot].
|
|
final int index;
|
|
|
|
Node._({required this.snapshot, required this.index});
|
|
|
|
int get edgeCount =>
|
|
snapshot._nodes[_offset + snapshot.meta.nodeEdgeCountIndex];
|
|
|
|
String get type => snapshot
|
|
.meta.nodeTypes[snapshot._nodes[_offset + snapshot.meta.nodeTypeIndex]];
|
|
|
|
String get name =>
|
|
snapshot.strings[snapshot._nodes[_offset + snapshot.meta.nodeNameIndex]];
|
|
|
|
int get selfSize =>
|
|
snapshot._nodes[_offset + snapshot.meta.nodeSelfSizeIndex];
|
|
|
|
int get id => snapshot._nodes[_offset + snapshot.meta.nodeIdIndex];
|
|
|
|
/// Returns all outgoing edges for this node.
|
|
Iterable<Edge> get edges sync* {
|
|
var firstEdgeIndex = snapshot._edgesStartIndexForNode[index];
|
|
for (var i = 0, n = edgeCount; i < n; i++) {
|
|
yield Edge._(snapshot: snapshot, index: firstEdgeIndex + i);
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return {
|
|
'type': type,
|
|
'name': name,
|
|
'id': id,
|
|
'selfSize': selfSize,
|
|
'edges': edges.toList(),
|
|
}.toString();
|
|
}
|
|
|
|
/// Returns the target of an outgoing edge with the given name (if any).
|
|
Node? operator [](String edgeName) =>
|
|
this.edges.firstWhereOrNull((e) => e.name == edgeName)?.target;
|
|
|
|
@override
|
|
bool operator ==(Object other) {
|
|
return other is Node && other.index == index;
|
|
}
|
|
|
|
@override
|
|
int get hashCode => this.index.hashCode;
|
|
|
|
/// Offset into [Snapshot._nodes] list at which this node begins.
|
|
int get _offset => index * snapshot.meta.nodeFieldCount;
|
|
}
|
|
|
|
/// Class representing information about V8 snapshot profile in relation
|
|
/// to a [ProgramInfo] structure that was derived from it.
|
|
class SnapshotInfo {
|
|
final Snapshot snapshot;
|
|
|
|
final List<ProgramInfoNode> infoNodes;
|
|
final Map<int, int> _ownerOf;
|
|
|
|
SnapshotInfo._(this.snapshot, this.infoNodes, this._ownerOf);
|
|
|
|
ProgramInfoNode ownerOf(Node node) =>
|
|
infoNodes[_ownerOf[node.index] ?? ProgramInfo.unknownId];
|
|
}
|
|
|
|
ProgramInfo toProgramInfo(Snapshot snap,
|
|
{bool collapseAnonymousClosures = false}) {
|
|
return _ProgramInfoBuilder(
|
|
collapseAnonymousClosures: collapseAnonymousClosures)
|
|
.build(snap);
|
|
}
|
|
|
|
class _ProgramInfoBuilder {
|
|
final bool collapseAnonymousClosures;
|
|
|
|
final program = ProgramInfo();
|
|
|
|
final List<ProgramInfoNode> infoNodes = [];
|
|
|
|
/// Mapping between snapshot [Node] index and id of [ProgramInfoNode] which
|
|
/// own this node.
|
|
final Map<int, int> ownerOf = {};
|
|
|
|
/// Mapping between snapshot [Node] indices and corresponding
|
|
/// [ProgramInfoNode] objects. Note that multiple snapshot nodes might be
|
|
/// mapped to a single [ProgramInfoNode] (e.g. when anonymous closures are
|
|
/// collapsed).
|
|
final Map<int, ProgramInfoNode> infoNodeByIndex = {};
|
|
|
|
// Mapping between package names and corresponding [ProgramInfoNode] objects
|
|
// representing those packages.
|
|
final Map<String, ProgramInfoNode> infoNodeForPackage = {};
|
|
|
|
/// Owners of some [Node] are determined by the program structure and not
|
|
/// by their reachability through the graph. For example, an owner of a
|
|
/// function is a class that contains it, even though the function can
|
|
/// also be reachable from another function through object pool.
|
|
final Set<int> nodesWithFrozenOwner = {};
|
|
|
|
/// Cache used to optimize common ancestor operation on [ProgramInfoNode] ids.
|
|
/// See [findCommonAncestor] method.
|
|
final Map<int, int> commonAncestorCache = {};
|
|
|
|
_ProgramInfoBuilder({required this.collapseAnonymousClosures});
|
|
|
|
/// Recover [ProgramInfo] structure from the snapshot profile.
|
|
///
|
|
/// This is done via a simple graph traversal: first all nodes representing
|
|
/// objects with clear ownership (like libraries, classes, functions) are
|
|
/// discovered and corresponding [ProgramInfoNode] objects are created for
|
|
/// them. Then the rest of the snapshot is attributed to one of these nodes
|
|
/// based on reachability (ignoring reachability from normal snapshot roots):
|
|
/// let `R(n)` be a set of [ProgramInfoNode] objects from which a given
|
|
/// snapshot node `n` is reachable. Then we define an owner of `n` to be
|
|
/// a lowest common ancestor of all nodes in `R(n)`.
|
|
///
|
|
/// Nodes which are not reachable from any normal [ProgramInfoNode] are
|
|
/// attributed to special `@unknown` [ProgramInfoNode].
|
|
ProgramInfo build(Snapshot snap) {
|
|
infoNodes.add(program.root);
|
|
infoNodes.add(program.stubs);
|
|
infoNodes.add(program.unknown);
|
|
|
|
// Create ProgramInfoNode for every snapshot node representing an element
|
|
// of the program structure (e.g. a library, a class, a function).
|
|
snap.nodes.forEach(getInfoNodeFor);
|
|
|
|
// Propagate the ownership information across the edges.
|
|
final worklist = ownerOf.keys.toList();
|
|
while (worklist.isNotEmpty) {
|
|
final node = snap.nodeAt(worklist.removeLast());
|
|
final sourceOwner = ownerOf[node.index];
|
|
for (var e in node.edges) {
|
|
final target = e.target;
|
|
if (!nodesWithFrozenOwner.contains(target.index)) {
|
|
final targetOwner = ownerOf[target.index];
|
|
final updatedOwner = findCommonAncestor(sourceOwner, targetOwner);
|
|
if (updatedOwner != targetOwner) {
|
|
ownerOf[target.index] = updatedOwner;
|
|
worklist.add(target.index);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now attribute sizes from the snapshot to nodes that own them.
|
|
for (var node in snap.nodes) {
|
|
if (node.selfSize > 0) {
|
|
final owner = infoNodes[ownerOf[node.index] ?? ProgramInfo.unknownId];
|
|
owner.size = (owner.size ?? 0) + node.selfSize;
|
|
}
|
|
}
|
|
|
|
program.snapshotInfo = SnapshotInfo._(snap, infoNodes, ownerOf);
|
|
|
|
return program;
|
|
}
|
|
|
|
ProgramInfoNode? getInfoNodeFor(Node node) {
|
|
var info = infoNodeByIndex[node.index];
|
|
if (info == null) {
|
|
info = createInfoNodeFor(node);
|
|
if (info != null) {
|
|
// Snapshot nodes which represent the program structure can't change
|
|
// their owner during iteration - their owner is frozen and is given
|
|
// by the program structure.
|
|
// Note that [ProgramInfoNode] owns its corresponding [Snapshot] node
|
|
// because we want the size of the snapshot node to be attributed to
|
|
// the info node itself.
|
|
nodesWithFrozenOwner.add(node.index);
|
|
ownerOf[node.index] = info.id;
|
|
|
|
// Handle some nodes specially.
|
|
switch (node.type) {
|
|
case 'Code':
|
|
// Freeze ownership of the Instructions object.
|
|
final instructions = node['<instructions>']!;
|
|
nodesWithFrozenOwner.add(instructions.index);
|
|
ownerOf[instructions.index] =
|
|
findCommonAncestor(ownerOf[instructions.index], info.id);
|
|
break;
|
|
case 'Library':
|
|
// Freeze ownership of the Script objects owned by this library.
|
|
final scripts = node['owned_scripts_'];
|
|
if (scripts != null) {
|
|
for (var e in scripts.edges) {
|
|
if (e.target.type == 'Script') {
|
|
nodesWithFrozenOwner.add(e.target.index);
|
|
ownerOf[e.target.index] =
|
|
findCommonAncestor(ownerOf[e.target.index]!, info.id);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return info;
|
|
}
|
|
|
|
ProgramInfoNode? createInfoNodeFor(Node node) {
|
|
switch (node.type) {
|
|
case 'Code':
|
|
final owner = node['owner_']!;
|
|
if (owner.type != 'Type') {
|
|
final ownerNode =
|
|
owner.type == 'Null' ? program.stubs : getInfoNodeFor(owner)!;
|
|
if (owner.type == 'Function') {
|
|
// For normal functions we just attribute Code object and all
|
|
// objects dominated by it to the function itself.
|
|
return ownerNode;
|
|
}
|
|
|
|
// For stubs we create a dummy functionNode that is going to own
|
|
// all objects dominated by it.
|
|
return makeInfoNode(node.index,
|
|
name: node.name, parent: ownerNode, type: NodeType.functionNode);
|
|
}
|
|
break;
|
|
|
|
case 'Function':
|
|
if (node.name != '<anonymous signature>') {
|
|
var owner = node['owner_']!;
|
|
|
|
// Artificial nodes may not have a data_ field.
|
|
var data = node['data_'];
|
|
if (data != null && data.type == 'ClosureData') {
|
|
owner = data['parent_function_']!;
|
|
}
|
|
return makeInfoNode(node.index,
|
|
name: node.name,
|
|
parent: getInfoNodeFor(owner)!,
|
|
type: NodeType.functionNode);
|
|
}
|
|
break;
|
|
|
|
case 'PatchClass':
|
|
return getInfoNodeFor(node['patched_class_']!);
|
|
|
|
case 'Class':
|
|
// Default to root node. Some builtin classes (void, dynamic) don't have
|
|
// any information about their library written out.
|
|
var ownerNode = program.root;
|
|
if (node['library_'] != null) {
|
|
ownerNode = getInfoNodeFor(node['library_']!) ?? ownerNode;
|
|
}
|
|
|
|
return makeInfoNode(node.index,
|
|
name: node.name, parent: ownerNode, type: NodeType.classNode);
|
|
|
|
case 'Library':
|
|
// Create fake owner node for the package which contains this library.
|
|
final packageName = packageOf(node.name);
|
|
return makeInfoNode(node.index,
|
|
name: node.name,
|
|
parent: packageName != node.name
|
|
? packageOwner(packageName)
|
|
: program.root,
|
|
type: NodeType.libraryNode);
|
|
|
|
case 'Field':
|
|
return makeInfoNode(node.index,
|
|
name: node.name,
|
|
parent: getInfoNodeFor(node['owner_']!)!,
|
|
type: NodeType.other);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
ProgramInfoNode makeInfoNode(int? index,
|
|
{required ProgramInfoNode parent,
|
|
required String name,
|
|
required NodeType type}) {
|
|
name = Name(name).scrubbed;
|
|
if (collapseAnonymousClosures) {
|
|
name = Name.collapse(name);
|
|
}
|
|
|
|
final node = program.makeNode(name: name, parent: parent, type: type);
|
|
if (node.id == infoNodes.length) {
|
|
infoNodes.add(node);
|
|
}
|
|
if (index != null) {
|
|
assert(!infoNodeByIndex.containsKey(index));
|
|
infoNodeByIndex[index] = node;
|
|
}
|
|
return node;
|
|
}
|
|
|
|
ProgramInfoNode packageOwner(String packageName) =>
|
|
infoNodeForPackage.putIfAbsent(
|
|
packageName,
|
|
() => makeInfoNode(null,
|
|
name: packageName,
|
|
type: NodeType.packageNode,
|
|
parent: program.root));
|
|
|
|
/// Create a single key from two node ids.
|
|
/// Note that this operation is commutative, because common ancestor of A and
|
|
/// B is the same as common ancestor of B and A.
|
|
static int ancestorCacheKey(int a, int b) {
|
|
if (a > b) {
|
|
return b << 32 | a;
|
|
} else {
|
|
return a << 32 | b;
|
|
}
|
|
}
|
|
|
|
/// Returns id of a common ancestor between [ProgramInfoNode] with [idA] and
|
|
/// [idB]. At least either [idA] or [idB] are expected to be not null.
|
|
int findCommonAncestor(int? idA, int? idB) {
|
|
if (idA == null) {
|
|
return idB!;
|
|
}
|
|
if (idB == null) {
|
|
return idA;
|
|
}
|
|
if (idA == idB) {
|
|
return idA;
|
|
}
|
|
|
|
// If either are shared - then result is shared.
|
|
if (idA == ProgramInfo.rootId || idB == ProgramInfo.rootId) {
|
|
return ProgramInfo.rootId;
|
|
}
|
|
|
|
final infoA = infoNodes[idA];
|
|
final infoB = infoNodes[idB];
|
|
|
|
final key = ancestorCacheKey(idA, idB);
|
|
var ancestor = commonAncestorCache[key];
|
|
if (ancestor == null) {
|
|
commonAncestorCache[key] =
|
|
ancestor = findCommonAncestorImpl(infoA, infoB).id;
|
|
}
|
|
return ancestor;
|
|
}
|
|
|
|
static List<ProgramInfoNode> pathToRoot(ProgramInfoNode node) {
|
|
final path = <ProgramInfoNode>[];
|
|
for (ProgramInfoNode? n = node; n != null; n = n.parent) {
|
|
path.add(n);
|
|
}
|
|
return path;
|
|
}
|
|
|
|
static ProgramInfoNode findCommonAncestorImpl(
|
|
ProgramInfoNode a, ProgramInfoNode b) {
|
|
final pathA = pathToRoot(a);
|
|
final pathB = pathToRoot(b);
|
|
var i = pathA.length - 1, j = pathB.length - 1;
|
|
while (i > 0 && j > 0 && (pathA[i - 1] == pathB[j - 1])) {
|
|
i--;
|
|
j--;
|
|
}
|
|
assert(pathA[i] == pathB[j]);
|
|
return pathA[i];
|
|
}
|
|
}
|
|
|
|
final bucketLegend = '''
|
|
|
|
--------------------------------------------------------------------------------
|
|
IMPORTANT: Dart AOT snapshot is a serialized representation of Dart VM heap.
|
|
Outside of few specific cases (e.g. an object representing a library clearly
|
|
originates from the library it represents) there is no well defined relationship
|
|
between snapshot bytes and a specific method/class/library to which these
|
|
bytes can be attributed with certainty. This snapshot analysis tool tries
|
|
to attribute bytes to specific program structure elements based on their
|
|
reachability from objects with well defined origin - meaning that this analysis
|
|
has some margin of error and imprecision.
|
|
|
|
- @other bucket denotes bytes attributed to entities outside of the current
|
|
granularity. For example, when breaking down the size by method name there
|
|
might be bytes which exist outside of any specific symbol - in which case
|
|
they will be attributed to @other.
|
|
- @stubs bucket accumulates bytes attributed to stubs (pieces of machine code
|
|
produced by the VM for internal purposes).
|
|
- @shared bucket accumulates bytes shared between otherwise unrelated program
|
|
entities
|
|
- @unknown bucket accumulates bytes which are not reachable from any program
|
|
structure nodes (usually VM internal objects).
|
|
--------------------------------------------------------------------------------
|
|
''';
|
|
|
|
/// Compute dominator tree of the graph.
|
|
///
|
|
/// The code for dominator tree computation is taken verbatim from the
|
|
/// native compiler (see runtime/vm/compiler/backend/flow_graph.cc).
|
|
List<int> _computeDominators(Snapshot snap) {
|
|
final predecessors = List<Object?>.filled(snap.nodeCount, null);
|
|
void addPred(int n, int p) {
|
|
final pred = predecessors[n];
|
|
if (pred == null) {
|
|
predecessors[n] = p;
|
|
} else if (pred is int) {
|
|
predecessors[n] = <int>[pred, p];
|
|
} else {
|
|
(pred as List<int>).add(p);
|
|
}
|
|
}
|
|
|
|
Iterable<int> predOf(int n) sync* {
|
|
final ps = predecessors[n];
|
|
if (ps is int) {
|
|
yield ps;
|
|
} else if (ps is List<int>) {
|
|
yield* ps;
|
|
}
|
|
}
|
|
|
|
return dominators.computeDominators(
|
|
size: snap.nodeCount,
|
|
root: snap.nodes.first.index,
|
|
succ: (n) => snap.nodeAt(n).edges.map((e) => e.target.index),
|
|
predOf: predOf,
|
|
handleEdge: addPred);
|
|
}
|