| // Copyright (c) 2014, 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. | |
| library object_graph; | |
| import 'dart:async'; | |
| import 'dart:convert'; | |
| import 'dart:typed_data'; | |
| /// Decodes and analyzes heap snapshots produced by the Dart VM. | |
| abstract class SnapshotReader { | |
| factory SnapshotReader() => _SnapshotReader._new(); | |
| void add(Uint8List chunk); | |
| Future<SnapshotGraph> close(); | |
| Future<SnapshotGraph> get done; | |
| Stream<String> get onProgress; | |
| } | |
| class _SnapshotReader implements SnapshotReader { | |
| bool _closed = false; | |
| List<Uint8List>? _chunks = <Uint8List>[]; | |
| final _onProgress = new StreamController<String>.broadcast(); | |
| final _done = new Completer<SnapshotGraph>(); | |
| _SnapshotReader._new(); | |
| void add(Uint8List chunk) { | |
| if (_closed) { | |
| throw new StateError("Stream is closed"); | |
| } | |
| _chunks!.add(chunk); | |
| _onProgress.add("Receiving snapshot chunk ${_chunks!.length}..."); | |
| // TODO(rmacnak): Incremental loading. | |
| } | |
| Future<SnapshotGraph> close() { | |
| if (_closed) { | |
| throw new StateError("Stream is closed"); | |
| } | |
| _closed = true; | |
| var graph = new _SnapshotGraph._new(); | |
| var chunks = _chunks!; | |
| _chunks = null; // Let the binary chunks be GCable. | |
| _done.complete(graph._load(chunks, _onProgress)); | |
| return _done.future; | |
| } | |
| Future<SnapshotGraph> get done => _done.future; | |
| Stream<String> get onProgress => _onProgress.stream; | |
| } | |
| Uint8List _newUint8Array(int size) { | |
| try { | |
| return new Uint8List(size); | |
| } on ArgumentError { | |
| // JS throws a misleading invalid argument error. Convert to a more user-friendly message. | |
| throw new Exception( | |
| "OutOfMemoryError: Not enough memory available to analyze the snapshot.", | |
| ); | |
| } | |
| } | |
| Uint16List _newUint16Array(int size) { | |
| try { | |
| return new Uint16List(size); | |
| } on ArgumentError { | |
| // JS throws a misleading invalid argument error. Convert to a more user-friendly message. | |
| throw new Exception( | |
| "OutOfMemoryError: Not enough memory available to analyze the snapshot.", | |
| ); | |
| } | |
| } | |
| Uint32List _newUint32Array(int size) { | |
| try { | |
| return new Uint32List(size); | |
| } on ArgumentError { | |
| // JS throws a misleading invalid argument error. Convert to a more user-friendly message. | |
| throw new Exception( | |
| "OutOfMemoryError: Not enough memory available to analyze the snapshot.", | |
| ); | |
| } | |
| } | |
| class _ReadStream { | |
| final List<Uint8List> _buffers; | |
| Uint8List _currentBuffer = Uint8List(0); | |
| int _bufferIndex = 0; | |
| int _byteIndex = 0; | |
| _ReadStream._new(this._buffers); | |
| bool atEnd() { | |
| return _bufferIndex >= _buffers.length && | |
| _byteIndex >= _currentBuffer.length; | |
| } | |
| int readByte() { | |
| int i = _byteIndex; | |
| Uint8List b = _currentBuffer; | |
| if (i < b.length) { | |
| int r = b[i]; | |
| _byteIndex = i + 1; | |
| return r; | |
| } | |
| return _readByteSlowPath(); | |
| } | |
| int _readByteSlowPath() { | |
| int i = _byteIndex; | |
| Uint8List b = _currentBuffer; | |
| while (i >= b.length) { | |
| if (_bufferIndex >= _buffers.length) { | |
| throw new StateError("Attempt to read past the end of a stream"); | |
| } | |
| b = _currentBuffer = _buffers[_bufferIndex++]; | |
| i = 0; | |
| } | |
| int r = b[i]; | |
| _byteIndex = i + 1; | |
| return r; | |
| } | |
| /// Read one ULEB128 number. | |
| int readUnsigned() { | |
| int result = 0; | |
| int shift = 0; | |
| for (;;) { | |
| int part = readByte(); | |
| result |= (part & 0x7F) << shift; | |
| if ((part & 0x80) == 0) { | |
| break; | |
| } | |
| shift += 7; | |
| } | |
| return result; | |
| } | |
| /// Read one SLEB128 number. | |
| int readSigned() { | |
| int result = 0; | |
| int shift = 0; | |
| for (;;) { | |
| int part = readByte(); | |
| result |= (part & 0x7F) << shift; | |
| shift += 7; | |
| if ((part & 0x80) == 0) { | |
| if ((part & 0x40) != 0) { | |
| result |= (-1 << shift); | |
| } | |
| break; | |
| } | |
| } | |
| return result; | |
| } | |
| double readFloat64() { | |
| final bytes = _newUint8Array(8); | |
| for (var i = 0; i < 8; i++) { | |
| bytes[i] = readByte(); | |
| } | |
| return new Float64List.view(bytes.buffer)[0]; | |
| } | |
| String readUtf8() { | |
| final len = readUnsigned(); | |
| final bytes = _newUint8Array(len); | |
| for (var i = 0; i < len; i++) { | |
| bytes[i] = readByte(); | |
| } | |
| return new Utf8Codec(allowMalformed: true).decode(bytes); | |
| } | |
| String readLatin1() { | |
| final len = readUnsigned(); | |
| final codeUnits = _newUint8Array(len); | |
| for (var i = 0; i < len; i++) { | |
| codeUnits[i] = readByte(); | |
| } | |
| return new String.fromCharCodes(codeUnits); | |
| } | |
| String readUtf16() { | |
| final len = readUnsigned(); | |
| final codeUnits = _newUint16Array(len); | |
| for (var i = 0; i < len; i++) { | |
| codeUnits[i] = readByte() | (readByte() << 8); | |
| } | |
| return new String.fromCharCodes(codeUnits); | |
| } | |
| } | |
| // Node indices for the root and sentinel nodes. Note that using 0 as the | |
| // sentinel means a newly allocated typed array comes initialized with all | |
| // elements as the sentinel. | |
| const _ROOT = 1; | |
| const _SENTINEL = 0; | |
| /// An object in a heap snapshot. | |
| abstract class SnapshotObject { | |
| /// The identity hash code of this object, used to compare objects across | |
| /// snapshots. If [identityHashCode] is 0, this object cannot be compared to | |
| /// other objects. | |
| int get identityHashCode; | |
| /// If this object has been obtained from [successors] or [predecessors], the | |
| /// name of slot. Otherwise, the empty string. | |
| String get label; | |
| /// The value for primitives. Otherwise, the class name. | |
| String get description; | |
| /// [internalSize] + [externalSize]. | |
| int get shallowSize; | |
| /// The number of bytes in the Dart heap occupied by this object. May be 0 | |
| /// for objects that are in another heap but referenced from the heap of | |
| /// interest. May also be 0 for synthetic objects such as the root. | |
| int get internalSize; | |
| /// The sum of all external allocations associated with this object. | |
| /// See Dart_NewFinalizableHandle and Dart_NewWeakPersistentHandle. | |
| int get externalSize; | |
| /// The [shallowSize] of this object, plus the retainedSize of all its | |
| /// children in the dominator tree. This is the amount of memory that would | |
| /// be freed if the last reference to this object was erased. | |
| int get retainedSize; | |
| SnapshotClass get klass; | |
| /// The objects directly referenced by this object. The [SnapshotObject]s | |
| /// returned by this iterable have their [label] set to name of the slot | |
| /// if it is available. | |
| Iterable<SnapshotObject> get successors; | |
| /// The objects directly referencing this object. The [SnapshotObject]s | |
| /// returned by this iterable have their [label] set to name of the slot | |
| /// if it is available. | |
| Iterable<SnapshotObject> get predecessors; | |
| /// The immediate dominator of this object. For the root object, returns self. | |
| /// | |
| /// See https://en.wikipedia.org/wiki/Dominator_(graph_theory). | |
| SnapshotObject get parent; | |
| /// The objects for which this object is the immediate dominator. | |
| /// | |
| /// See https://en.wikipedia.org/wiki/Dominator_(graph_theory). | |
| Iterable<SnapshotObject> get children; | |
| /// An iterable containing only this object. For polymorphism with | |
| /// SnapshotMergedDominators. | |
| Iterable<SnapshotObject> get objects; | |
| } | |
| class _SnapshotObject implements SnapshotObject { | |
| final int _id; | |
| final _SnapshotGraph _graph; | |
| final String label; | |
| _SnapshotObject._new(this._id, this._graph, this.label); | |
| bool operator ==(Object other) { | |
| if (other is _SnapshotObject) { | |
| return _id == other._id && _graph == other._graph; | |
| } | |
| return false; | |
| } | |
| int get hashCode => _id ^ _graph.hashCode; | |
| int get identityHashCode => _graph._identityHashes![_id]; | |
| int get shallowSize => internalSize + externalSize; | |
| int get internalSize => _graph._internalSizes![_id]; | |
| int get externalSize => _graph._externalSizes![_id]; | |
| int get retainedSize => _graph._retainedSizes![_id]; | |
| String get description => _graph._describeObject(_id); | |
| SnapshotClass get klass => _graph._classes![_graph._cids![_id]]!; | |
| Iterable<SnapshotObject> get successors sync* { | |
| final id = _id; | |
| final cid = _graph._cids![id]; | |
| final startSuccIndex = _graph._firstSuccs![id]; | |
| final limitSuccIndex = _graph._firstSuccs![id + 1]; | |
| for ( | |
| var nextSuccIndex = startSuccIndex; | |
| nextSuccIndex < limitSuccIndex; | |
| nextSuccIndex++ | |
| ) { | |
| final index = nextSuccIndex - startSuccIndex; | |
| final succId = _graph._succs![nextSuccIndex]; | |
| final name = _graph._edgeName(cid, index); | |
| yield _SnapshotObject._new(succId, _graph, name); | |
| } | |
| } | |
| Iterable<SnapshotObject> get predecessors sync* { | |
| var firstSuccs = _graph._firstSuccs!; | |
| var succs = _graph._succs!; | |
| var id = _id; | |
| var N = _graph._N!; | |
| for (var predId = 1; predId <= N; predId++) { | |
| var base = firstSuccs[predId]; | |
| var limit = firstSuccs[predId + 1]; | |
| for (var i = base; i < limit; i++) { | |
| if (succs[i] == id) { | |
| var cid = _graph._cids![predId]; | |
| var name = _graph._edgeName(cid, i - base); | |
| yield _SnapshotObject._new(predId, _graph, name); | |
| } | |
| } | |
| } | |
| } | |
| SnapshotObject get parent { | |
| if (_id == _ROOT) { | |
| return this; | |
| } | |
| return _SnapshotObject._new(_graph._doms![_id], _graph, ""); | |
| } | |
| Iterable<SnapshotObject> get children sync* { | |
| var N = _graph._N!; | |
| var doms = _graph._doms!; | |
| var parentId = _id; | |
| for (var childId = _ROOT; childId <= N; childId++) { | |
| if (doms[childId] == parentId) { | |
| yield _SnapshotObject._new(childId, _graph, ""); | |
| } | |
| } | |
| } | |
| Iterable<SnapshotObject> get objects sync* { | |
| yield this; | |
| } | |
| } | |
| class _SyntheticSnapshotObject implements SnapshotObject { | |
| late String _description; | |
| late SnapshotClass _klass; | |
| late int _internalSize; | |
| late int _externalSize; | |
| late int _retainedSize; | |
| late List<SnapshotObject> _successors; | |
| late List<SnapshotObject> _predecessors; | |
| late SnapshotObject _parent; | |
| late List<SnapshotObject> _children; | |
| int get identityHashCode => 0; | |
| String get label => ""; | |
| String get description => _description; | |
| SnapshotClass get klass => _klass; | |
| int get shallowSize => internalSize + externalSize; | |
| int get internalSize => _internalSize; | |
| int get externalSize => _externalSize; | |
| int get retainedSize => _retainedSize; | |
| Iterable<SnapshotObject> get successors => _successors; | |
| Iterable<SnapshotObject> get predecessors => _predecessors; | |
| SnapshotObject get parent => _parent; | |
| Iterable<SnapshotObject> get children => _children; | |
| Iterable<SnapshotObject> get objects sync* { | |
| yield this; | |
| } | |
| } | |
| /// A set of sibling objects in the graph's dominator tree that have the same | |
| /// class. | |
| abstract class SnapshotMergedDominator { | |
| SnapshotClass get klass; | |
| /// "n instances of Class". | |
| String get description; | |
| /// [internalSize] + [externalSize]. | |
| int get shallowSize; | |
| /// The sum of [internalSize] for all objects in this set. | |
| int get internalSize; | |
| /// The sum of [externalSize] for all objects in this set. | |
| int get externalSize; | |
| /// The sum of [externalSize] for all objects in this set. | |
| /// This is the amount of memory that would be freed if all references to | |
| /// objects in this set were erased. | |
| int get retainedSize; | |
| /// The number of objects in this set. Polymorphic with | |
| /// [SnapshotClass.instanceCount]. | |
| int get instanceCount; | |
| SnapshotMergedDominator get parent; | |
| Iterable<SnapshotMergedDominator> get children; | |
| Iterable<SnapshotObject> get objects; | |
| } | |
| // A node in the dominator tree where siblings with the same class are merged. | |
| // That is, a set of objects with the same cid whose parent chains in the | |
| // dominator tree have the same cids at each level. [id_] is the representative | |
| // object of this set. The other members of the set are found by walking the | |
| // mergedDomNext links until finding the sentinel node or a node with a | |
| // different class. | |
| class _SnapshotMergedDominator implements SnapshotMergedDominator { | |
| final int _id; | |
| final _SnapshotGraph _graph; | |
| final _SnapshotMergedDominator? _parent; | |
| _SnapshotMergedDominator._new(this._id, this._graph, this._parent); | |
| bool operator ==(Object other) { | |
| if (other is _SnapshotMergedDominator) { | |
| return _id == other._id && _graph == other._graph; | |
| } | |
| return false; | |
| } | |
| int get hashCode => _id ^ _graph.hashCode; | |
| String get description { | |
| return _id == _ROOT | |
| ? "Live Objects + External" | |
| : "$instanceCount instances of ${klass.name}"; | |
| } | |
| SnapshotClass get klass => _graph._classes![_graph._cids![_id]]!; | |
| int get shallowSize => internalSize + externalSize; | |
| int get internalSize { | |
| var cids = _graph._cids!; | |
| var internalSizes = _graph._internalSizes!; | |
| var mergedDomNext = _graph._mergedDomNext!; | |
| var size = 0; | |
| var sibling = _id; | |
| while (sibling != _SENTINEL && cids[sibling] == cids[_id]) { | |
| size += internalSizes[sibling]; | |
| sibling = mergedDomNext[sibling]; | |
| } | |
| return size; | |
| } | |
| int get externalSize { | |
| var cids = _graph._cids!; | |
| var externalSizes = _graph._externalSizes!; | |
| var mergedDomNext = _graph._mergedDomNext!; | |
| var size = 0; | |
| var sibling = _id; | |
| while (sibling != _SENTINEL && cids[sibling] == cids[_id]) { | |
| size += externalSizes[sibling]; | |
| sibling = mergedDomNext[sibling]; | |
| } | |
| return size; | |
| } | |
| int get retainedSize { | |
| var cids = _graph._cids!; | |
| var retainedSizes = _graph._retainedSizes!; | |
| var mergedDomNext = _graph._mergedDomNext!; | |
| var size = 0; | |
| var sibling = _id; | |
| while (sibling != _SENTINEL && cids[sibling] == cids[_id]) { | |
| size += retainedSizes[sibling]; | |
| sibling = mergedDomNext[sibling]; | |
| } | |
| return size; | |
| } | |
| int get instanceCount { | |
| var cids = _graph._cids!; | |
| var mergedDomNext = _graph._mergedDomNext!; | |
| var count = 0; | |
| var sibling = _id; | |
| while (sibling != _SENTINEL && cids[sibling] == cids[_id]) { | |
| count++; | |
| sibling = mergedDomNext[sibling]; | |
| } | |
| return count; | |
| } | |
| Iterable<SnapshotObject> get objects sync* { | |
| var cids = _graph._cids!; | |
| var mergedDomNext = _graph._mergedDomNext!; | |
| var sibling = _id; | |
| while (sibling != _SENTINEL && cids[sibling] == cids[_id]) { | |
| yield _SnapshotObject._new(sibling, _graph, ""); | |
| sibling = mergedDomNext[sibling]; | |
| } | |
| } | |
| SnapshotMergedDominator get parent => _parent ?? this; | |
| Iterable<SnapshotMergedDominator> get children sync* { | |
| var next = _graph._mergedDomNext!; | |
| var cids = _graph._cids!; | |
| var prev = _SENTINEL; | |
| var child = _graph._mergedDomHead![_id]; | |
| // Walk the list of children and look for the representative objects, i.e. | |
| // the first sibling of each cid. | |
| while (child != _SENTINEL) { | |
| if (prev == _SENTINEL || cids[prev] != cids[child]) { | |
| yield _SnapshotMergedDominator._new(child, _graph, this); | |
| } | |
| prev = child; | |
| child = next[child]; | |
| } | |
| } | |
| } | |
| class _SyntheticSnapshotMergedDominator implements SnapshotMergedDominator { | |
| late String _description; | |
| late SnapshotClass _klass; | |
| late int _internalSize; | |
| late int _externalSize; | |
| late int _retainedSize; | |
| late List<SnapshotObject> _objects; | |
| late SnapshotMergedDominator _parent; | |
| late List<SnapshotMergedDominator> _children; | |
| SnapshotClass get klass => _klass; | |
| String get description => _description; | |
| int get shallowSize => internalSize + externalSize; | |
| int get internalSize => _internalSize; | |
| int get externalSize => _externalSize; | |
| int get retainedSize => _retainedSize; | |
| int get instanceCount => _objects.length; | |
| SnapshotMergedDominator get parent => _parent; | |
| Iterable<SnapshotMergedDominator> get children => _children; | |
| Iterable<SnapshotObject> get objects => _objects; | |
| } | |
| /// A class in a heap snapshot. | |
| abstract class SnapshotClass { | |
| String get name; | |
| String get qualifiedName; | |
| int get shallowSize; | |
| int get externalSize; | |
| int get internalSize; | |
| int get ownedSize; | |
| int get instanceCount; | |
| Iterable<SnapshotObject> get instances; | |
| } | |
| class _SnapshotClass implements SnapshotClass { | |
| final _SnapshotGraph _graph; | |
| final int _cid; | |
| final String name; | |
| String get qualifiedName => "$libUri $name"; | |
| final String libName; | |
| final String libUri; | |
| final Map<int, String> fields = new Map<int, String>(); | |
| int totalExternalSize = 0; | |
| int totalInternalSize = 0; | |
| int totalInstanceCount = 0; | |
| int ownedSize = 0; | |
| int liveExternalSize = 0; | |
| int liveInternalSize = 0; | |
| int liveInstanceCount = 0; | |
| int get shallowSize => internalSize + externalSize; | |
| int get internalSize => liveInternalSize; | |
| int get externalSize => liveExternalSize; | |
| int get instanceCount => liveInstanceCount; | |
| Iterable<SnapshotObject> get instances sync* { | |
| final N = _graph._N!; | |
| final cids = _graph._cids!; | |
| final retainedSizes = _graph._retainedSizes!; | |
| for (var id = 1; id <= N; id++) { | |
| if (cids[id] == _cid && retainedSizes[id] > 0) { | |
| yield _SnapshotObject._new(id, _graph, ""); | |
| } | |
| } | |
| } | |
| _SnapshotClass._new( | |
| this._graph, | |
| this._cid, | |
| this.name, | |
| this.libName, | |
| this.libUri, | |
| ); | |
| } | |
| class _SyntheticSnapshotClass implements SnapshotClass { | |
| final _SyntheticSnapshotObject _node; | |
| _SyntheticSnapshotClass(this._node); | |
| String get name => _node.description; | |
| String get qualifiedName => _node.description; | |
| int get shallowSize => _node.shallowSize; | |
| int get externalSize => _node.externalSize; | |
| int get internalSize => _node.internalSize; | |
| int get ownedSize => 0; | |
| int get instanceCount => 1; | |
| Iterable<SnapshotObject> get instances => <SnapshotObject>[_node]; | |
| } | |
| /// The analyzed graph from a heap snapshot. | |
| abstract class SnapshotGraph { | |
| String get description; | |
| int get internalSize; | |
| int get externalSize; | |
| // [internalSize] + [externalSize] | |
| int get size; | |
| // The amount of memory reserved for the heap. [internalSize] will always be | |
| // less than or equal to [capacity]. | |
| int get capacity; | |
| Iterable<SnapshotClass> get classes; | |
| Iterable<SnapshotObject> get objects; | |
| SnapshotObject get root; | |
| SnapshotObject get extendedRoot; | |
| SnapshotMergedDominator get mergedRoot; | |
| SnapshotMergedDominator get extendedMergedRoot; | |
| // TODO: Insist that the client remember the chunks if needed? Always keeping | |
| // this increasing the peak memory usage during analysis. | |
| List<Uint8List> get chunks; | |
| } | |
| const _tagNone = 0; | |
| const _tagNull = 1; | |
| const _tagBool = 2; | |
| const _tagInt = 3; | |
| const _tagDouble = 4; | |
| const _tagLatin1 = 5; | |
| const _tagUtf16 = 6; | |
| const _tagLength = 7; | |
| const _tagName = 8; | |
| const _kSentinelName = "<omitted-object>"; | |
| const _kRootName = "Live Objects + External"; | |
| const _kUnknownFieldName = "<unknown>"; | |
| class _SnapshotGraph implements SnapshotGraph { | |
| List<Uint8List>? _chunks; | |
| List<Uint8List> get chunks => _chunks!; | |
| _SnapshotGraph._new(); | |
| String get description => _description!; | |
| int get size => _liveInternalSize! + _liveExternalSize!; | |
| int get internalSize => _liveInternalSize!; | |
| int get externalSize => _liveExternalSize!; | |
| int get capacity => _capacity!; | |
| SnapshotObject get root => _SnapshotObject._new(_ROOT, this, "Root"); | |
| SnapshotMergedDominator get mergedRoot => | |
| _SnapshotMergedDominator._new(_ROOT, this, null); | |
| SnapshotObject? _extendedRoot; | |
| SnapshotObject get extendedRoot { | |
| if (_extendedRoot == null) { | |
| _createExtended(); | |
| } | |
| return _extendedRoot!; | |
| } | |
| SnapshotMergedDominator? _extendedMergedRoot; | |
| SnapshotMergedDominator get extendedMergedRoot { | |
| if (_extendedMergedRoot == null) { | |
| _createExtended(); | |
| } | |
| return _extendedMergedRoot!; | |
| } | |
| void _createExtended() { | |
| var capacity = new _SyntheticSnapshotObject(); | |
| var uncollected = new _SyntheticSnapshotObject(); | |
| var fragmentation = new _SyntheticSnapshotObject(); | |
| var live = root; | |
| var mcapacity = new _SyntheticSnapshotMergedDominator(); | |
| var muncollected = new _SyntheticSnapshotMergedDominator(); | |
| var mfragmentation = new _SyntheticSnapshotMergedDominator(); | |
| var mlive = mergedRoot; | |
| capacity._description = "Capacity + External"; | |
| capacity._klass = new _SyntheticSnapshotClass(capacity); | |
| capacity._internalSize = 0; // No shallow size. | |
| capacity._externalSize = 0; // No shallow size. | |
| capacity._retainedSize = _capacity! + _totalExternalSize!; | |
| capacity._successors = <SnapshotObject>[live, uncollected, fragmentation]; | |
| capacity._predecessors = <SnapshotObject>[]; | |
| capacity._children = <SnapshotObject>[live, uncollected, fragmentation]; | |
| mcapacity._description = "Capacity + External"; | |
| mcapacity._klass = capacity._klass; | |
| mcapacity._internalSize = 0; // No shallow size. | |
| mcapacity._externalSize = 0; // No shallow size. | |
| mcapacity._retainedSize = _capacity! + _totalExternalSize!; | |
| mcapacity._children = <SnapshotMergedDominator>[ | |
| mlive, | |
| muncollected, | |
| mfragmentation, | |
| ]; | |
| mcapacity._objects = <SnapshotObject>[capacity]; | |
| uncollected._description = "Uncollected Garbage"; | |
| uncollected._klass = new _SyntheticSnapshotClass(uncollected); | |
| uncollected._internalSize = _totalInternalSize! - _liveInternalSize!; | |
| uncollected._externalSize = _totalExternalSize! - _liveExternalSize!; | |
| uncollected._retainedSize = | |
| uncollected._internalSize + uncollected._externalSize; | |
| uncollected._successors = <SnapshotObject>[]; | |
| uncollected._predecessors = <SnapshotObject>[capacity]; | |
| uncollected._parent = capacity; | |
| uncollected._children = <SnapshotObject>[]; | |
| muncollected._description = "Uncollected Garbage"; | |
| muncollected._klass = uncollected._klass; | |
| muncollected._internalSize = _totalInternalSize! - _liveInternalSize!; | |
| muncollected._externalSize = _totalExternalSize! - _liveExternalSize!; | |
| muncollected._retainedSize = | |
| muncollected._internalSize + muncollected._externalSize; | |
| muncollected._parent = mcapacity; | |
| muncollected._children = <SnapshotMergedDominator>[]; | |
| muncollected._objects = <SnapshotObject>[uncollected]; | |
| fragmentation._description = "Free"; | |
| fragmentation._klass = new _SyntheticSnapshotClass(fragmentation); | |
| fragmentation._internalSize = _capacity! - _totalInternalSize!; | |
| fragmentation._externalSize = 0; | |
| fragmentation._retainedSize = fragmentation._internalSize; | |
| fragmentation._successors = <SnapshotObject>[]; | |
| fragmentation._predecessors = <SnapshotObject>[capacity]; | |
| fragmentation._parent = capacity; | |
| fragmentation._children = <SnapshotObject>[]; | |
| mfragmentation._description = "Free"; | |
| mfragmentation._klass = fragmentation._klass; | |
| mfragmentation._internalSize = _capacity! - _totalInternalSize!; | |
| mfragmentation._externalSize = 0; | |
| mfragmentation._retainedSize = mfragmentation._internalSize; | |
| mfragmentation._parent = mcapacity; | |
| mfragmentation._children = <SnapshotMergedDominator>[]; | |
| mfragmentation._objects = <SnapshotObject>[fragmentation]; | |
| _extendedRoot = capacity; | |
| _extendedMergedRoot = mcapacity; | |
| } | |
| Iterable<SnapshotObject> get objects sync* { | |
| final N = _N!; | |
| final retainedSizes = _retainedSizes!; | |
| for (var id = 1; id <= N; id++) { | |
| if (retainedSizes[id] > 0) { | |
| yield _SnapshotObject._new(id, this, ""); | |
| } | |
| } | |
| } | |
| String _describeObject(int oid) { | |
| if (oid == _SENTINEL) { | |
| return _kSentinelName; | |
| } | |
| if (oid == _ROOT) { | |
| return _kRootName; | |
| } | |
| var cls = _className(oid); | |
| var data = _nonReferenceData![oid]; | |
| if (data == null) { | |
| return cls; | |
| } else { | |
| return "$cls($data)"; | |
| } | |
| } | |
| String _className(int oid) { | |
| var cid = _cids![oid]; | |
| var cls = _classes![cid]; | |
| if (cls == null) { | |
| return "Class$cid"; | |
| } | |
| return cls.name; | |
| } | |
| String _edgeName(int cid, int index) { | |
| var c = _classes![cid]; | |
| if (c == null) { | |
| return _kUnknownFieldName; | |
| } | |
| var n = c.fields[index]; | |
| if (n == null) { | |
| return _kUnknownFieldName; | |
| } | |
| return n; | |
| } | |
| Iterable<SnapshotClass> get classes sync* { | |
| for (final c in _classes!) { | |
| // Not all CIDs are occupied. | |
| if (c != null) { | |
| yield c; | |
| } | |
| } | |
| } | |
| Future<SnapshotGraph> _load( | |
| List<Uint8List>? chunks, | |
| StreamController<String> onProgress, | |
| ) async { | |
| _chunks = chunks; | |
| _ReadStream? stream = _ReadStream._new(chunks!); | |
| chunks = null; | |
| // The phases of loading are placed in explicit `new Future(computation)` so | |
| // they will be deferred to the message loop. Ordinary async-await will only | |
| // defer to the microtask loop. | |
| onProgress.add("Loading classes..."); | |
| await new Future(() => _readClasses(stream!)); | |
| onProgress.add("Loading objects..."); | |
| await new Future(() => _readObjects(stream!)); | |
| onProgress.add("Loading external properties..."); | |
| await new Future(() => _readExternalProperties(stream!)); | |
| onProgress.add("Loading object identity hash codes..."); | |
| await new Future(() => _readObjectIdentityHashes(stream!)); | |
| stream = null; | |
| onProgress.add("Compute class table..."); | |
| await new Future(() => _computeClassTable()); | |
| onProgress.add("Finding depth-first order..."); | |
| await new Future(() => _dfs()); | |
| onProgress.add("Finding predecessors..."); | |
| await new Future(() => _buildPredecessors()); | |
| onProgress.add("Finding dominators..."); | |
| await new Future(() => _buildDominators()); | |
| _semi = null; | |
| _parent = null; | |
| onProgress.add("Finding in-degree(1) groups..."); | |
| await new Future(() => _buildOwnedSizes()); | |
| _firstPreds = null; | |
| _preds = null; | |
| onProgress.add("Finding retained sizes..."); | |
| await new Future(() => _calculateRetainedSizes()); | |
| _vertex = null; | |
| onProgress.add("Linking dominator tree children..."); | |
| await new Future(() => _linkDominatorChildren()); | |
| onProgress.add("Sorting dominator tree children..."); | |
| await new Future(() => _sortDominatorChildren()); | |
| onProgress.add("Merging dominator tree siblings..."); | |
| await new Future(() => _mergeDominatorSiblings()); | |
| onProgress.add("Loaded"); | |
| // We await here so SnapshotReader clients see all progress events before | |
| // seeing the done future as completed. | |
| await onProgress.close(); | |
| return this; | |
| } | |
| String? _description; | |
| int? _kStackCid; | |
| int? _kFieldCid; | |
| int? _N; // Objects in the snapshot. | |
| int? _Nconnected; // Objects reachable from root. | |
| int? _E; // References in the snapshot. | |
| int? _capacity; | |
| int? _liveInternalSize; | |
| int? _liveExternalSize; | |
| int? _totalInternalSize; | |
| int? _totalExternalSize; | |
| List<_SnapshotClass?>? _classes; | |
| // Indexed by node id, with id 0 representing invalid/uninitialized. | |
| // From snapshot. | |
| List? _nonReferenceData; | |
| Uint16List? _cids; | |
| Uint32List? _internalSizes; | |
| Uint32List? _externalSizes; | |
| Uint32List? _firstSuccs; | |
| Uint32List? _succs; | |
| Uint32List? _identityHashes; | |
| // Intermediates. | |
| Uint32List? _vertex; | |
| Uint32List? _parent; | |
| Uint32List? _semi; | |
| Uint32List? _firstPreds; // Offset into preds. | |
| Uint32List? _preds; | |
| // Outputs. | |
| Uint32List? _doms; | |
| Uint32List? _retainedSizes; | |
| Uint32List? _mergedDomHead; | |
| Uint32List? _mergedDomNext; | |
| void _readClasses(_ReadStream stream) { | |
| for (var i = 0; i < 8; i++) { | |
| stream.readByte(); // Magic value. | |
| } | |
| stream.readUnsigned(); // Flags | |
| _description = stream.readUtf8(); | |
| _totalInternalSize = stream.readUnsigned(); | |
| _capacity = stream.readUnsigned(); | |
| _totalExternalSize = stream.readUnsigned(); | |
| var K = stream.readUnsigned(); | |
| var classes = new List<_SnapshotClass?>.filled(K + 1, null); | |
| classes[0] = _SnapshotClass._new(this, 0, "Root", "", ""); | |
| for (var cid = 1; cid <= K; cid++) { | |
| // ignore: unused_local_variable | |
| int flags = stream.readUnsigned(); | |
| String name = stream.readUtf8(); | |
| String libName = stream.readUtf8(); | |
| String libUri = stream.readUtf8(); | |
| // ignore: unused_local_variable | |
| String reserved = stream.readUtf8(); | |
| final cls = _SnapshotClass._new(this, cid, name, libName, libUri); | |
| int edgeCount = stream.readUnsigned(); | |
| for (int i = 0; i < edgeCount; i++) { | |
| // ignore: unused_local_variable | |
| int flags = stream.readUnsigned(); | |
| int index = stream.readUnsigned(); | |
| String fieldName = stream.readUtf8(); | |
| // ignore: unused_local_variable | |
| String reserved = stream.readUtf8(); | |
| cls.fields[index] = fieldName; | |
| } | |
| classes[cid] = cls; | |
| } | |
| _classes = classes; | |
| } | |
| void _readObjects(_ReadStream stream) { | |
| final E = stream.readUnsigned(); | |
| final N = stream.readUnsigned(); | |
| // The negative check accounts for int64 overflow in readUnsigned. | |
| const maxUint32 = 0xFFFFFFFF; | |
| if (N < 0 || N + 2 >= maxUint32) { | |
| throw new Exception("Snapshot contains too many objects: $N"); | |
| } | |
| if (E < 0 || E + 2 >= maxUint32) { | |
| throw new Exception("Snapshot contains too many references: $E"); | |
| } | |
| _N = N; | |
| _E = E; | |
| var internalSizes = _newUint32Array(N + 1); | |
| var cids = _newUint16Array(N + 1); | |
| var nonReferenceData = new List<dynamic>.filled(N + 1, null); | |
| var firstSuccs = _newUint32Array(N + 2); | |
| var succs = _newUint32Array(E); | |
| var eid = 0; | |
| for (var oid = 1; oid <= N; oid++) { | |
| var cid = stream.readUnsigned(); | |
| cids[oid] = cid; | |
| var internalSize = stream.readUnsigned(); | |
| internalSizes[oid] = internalSize; | |
| var nonReferenceDataTag = stream.readUnsigned(); | |
| switch (nonReferenceDataTag) { | |
| case _tagNone: | |
| break; | |
| case _tagNull: | |
| nonReferenceData[oid] = "null"; | |
| break; | |
| case _tagBool: | |
| nonReferenceData[oid] = stream.readByte() != 0; | |
| break; | |
| case _tagInt: | |
| nonReferenceData[oid] = stream.readSigned(); | |
| break; | |
| case _tagDouble: | |
| nonReferenceData[oid] = stream.readFloat64(); | |
| break; | |
| case _tagLatin1: | |
| var len = stream.readUnsigned(); | |
| var str = stream.readLatin1(); | |
| if (str.length < len) { | |
| nonReferenceData[oid] = '$str...'; | |
| } else { | |
| nonReferenceData[oid] = str; | |
| } | |
| break; | |
| case _tagUtf16: | |
| int len = stream.readUnsigned(); | |
| var str = stream.readUtf16(); | |
| if (str.length < len) { | |
| nonReferenceData[oid] = '$str...'; | |
| } else { | |
| nonReferenceData[oid] = str; | |
| } | |
| break; | |
| case _tagLength: | |
| nonReferenceData[oid] = stream.readUnsigned(); // Length | |
| break; | |
| case _tagName: | |
| nonReferenceData[oid] = stream.readUtf8(); // Name | |
| break; | |
| default: | |
| throw "Unknown tag $nonReferenceDataTag"; | |
| } | |
| firstSuccs[oid] = eid; | |
| var referenceCount = stream.readUnsigned(); | |
| while (referenceCount > 0) { | |
| var childOid = stream.readUnsigned(); | |
| succs[eid] = childOid; | |
| eid++; | |
| referenceCount--; | |
| } | |
| } | |
| firstSuccs[N + 1] = eid; | |
| assert(eid <= E); | |
| _E = eid; | |
| _internalSizes = internalSizes; | |
| _cids = cids; | |
| _nonReferenceData = nonReferenceData; | |
| _firstSuccs = firstSuccs; | |
| _succs = succs; | |
| } | |
| void _readExternalProperties(_ReadStream stream) { | |
| final N = _N!; | |
| final externalPropertyCount = stream.readUnsigned(); | |
| final externalSizes = _newUint32Array(N + 1); | |
| for (var i = 0; i < externalPropertyCount; i++) { | |
| final oid = stream.readUnsigned(); | |
| final externalSize = stream.readUnsigned(); | |
| // ignore: unused_local_variable | |
| final name = stream.readUtf8(); | |
| externalSizes[oid] += externalSize; | |
| } | |
| _externalSizes = externalSizes; | |
| } | |
| void _readObjectIdentityHashes(_ReadStream stream) { | |
| final N = _N!; | |
| final identityHashes = _newUint32Array(N + 1); | |
| for (int oid = 1; oid <= N; ++oid) { | |
| identityHashes[oid] = stream.readUnsigned(); | |
| } | |
| _identityHashes = identityHashes; | |
| } | |
| void _computeClassTable() { | |
| final N = _N!; | |
| final classes = _classes!; | |
| final cids = _cids!; | |
| final internalSizes = _internalSizes!; | |
| final externalSizes = _externalSizes!; | |
| var totalInternalSize = 0; | |
| var totalExternalSize = 0; | |
| for (var oid = 1; oid <= N; oid++) { | |
| var internalSize = internalSizes[oid]; | |
| totalInternalSize += internalSize; | |
| var externalSize = externalSizes[oid]; | |
| totalExternalSize += externalSize; | |
| var cls = classes[cids[oid]]!; | |
| cls.totalInternalSize += internalSize; | |
| cls.totalExternalSize += externalSize; | |
| cls.totalInstanceCount++; | |
| } | |
| _totalInternalSize = totalInternalSize; | |
| _totalExternalSize = totalExternalSize; | |
| } | |
| void _dfs() { | |
| final N = _N!; | |
| final firstSuccs = _firstSuccs!; | |
| final succs = _succs!; | |
| final stackNodes = _newUint32Array(N); | |
| final stackCurrentEdgePos = _newUint32Array(N); | |
| final vertex = _newUint32Array(N + 1); | |
| final semi = _newUint32Array(N + 1); | |
| final parent = _newUint32Array(N + 1); | |
| var dfsNumber = 0; | |
| var stackTop = 0; | |
| // Push root. | |
| stackNodes[0] = _ROOT; | |
| stackCurrentEdgePos[0] = firstSuccs[_ROOT]; | |
| while (stackTop >= 0) { | |
| var v = stackNodes[stackTop]; | |
| var edgePos = stackCurrentEdgePos[stackTop]; | |
| if (semi[v] == 0) { | |
| // First visit. | |
| dfsNumber++; | |
| semi[v] = dfsNumber; | |
| vertex[dfsNumber] = v; | |
| } | |
| if (edgePos < firstSuccs[v + 1]) { | |
| var childId = succs[edgePos]; | |
| edgePos++; | |
| stackCurrentEdgePos[stackTop] = edgePos; | |
| if (childId == _SENTINEL) { | |
| // Omitted target. | |
| } else if (semi[childId] == 0) { | |
| parent[childId] = v; | |
| // Push child. | |
| stackTop++; | |
| stackNodes[stackTop] = childId; | |
| stackCurrentEdgePos[stackTop] = firstSuccs[childId]; | |
| } | |
| } else { | |
| // Done with all children. | |
| stackTop--; | |
| } | |
| } | |
| if (dfsNumber != N) { | |
| // This may happen in filtered snapshots. | |
| print('Heap snapshot contains ${N - dfsNumber} unreachable nodes.'); | |
| } | |
| assert(() { | |
| for (var i = 1; i <= dfsNumber; i++) { | |
| var v = vertex[i]; | |
| assert(semi[v] != _SENTINEL); | |
| } | |
| assert(parent[1] == _SENTINEL); | |
| for (var i = 2; i <= dfsNumber; i++) { | |
| var v = vertex[i]; | |
| assert(parent[v] != _SENTINEL); | |
| } | |
| return true; | |
| }()); | |
| if (dfsNumber != N) { | |
| // Remove successors of unconnected nodes | |
| for (var i = _ROOT + 1; i <= N; i++) { | |
| if (parent[i] == _SENTINEL) { | |
| var startSuccIndex = firstSuccs[i]; | |
| var limitSuccIndex = firstSuccs[i + 1]; | |
| for ( | |
| var succIndex = startSuccIndex; | |
| succIndex < limitSuccIndex; | |
| succIndex++ | |
| ) { | |
| succs[succIndex] = _SENTINEL; | |
| } | |
| } | |
| } | |
| } | |
| _Nconnected = dfsNumber; | |
| _vertex = vertex; | |
| _semi = semi; | |
| _parent = parent; | |
| } | |
| void _buildPredecessors() { | |
| final N = _N!; | |
| final Nconnected = _Nconnected!; | |
| final E = _E!; | |
| final firstSuccs = _firstSuccs!; | |
| final succs = _succs!; | |
| // This is first filled with the predecessor counts, then reused to hold the | |
| // offset to the first predecessor (see alias below). | |
| // + 1 because 0 is a sentinel | |
| // + 1 so the number of predecessors can be found from the difference with | |
| // the next node's offset. | |
| final numPreds = _newUint32Array(N + 2); | |
| final preds = _newUint32Array(E); | |
| // Count predecessors of each node. | |
| for (var succIndex = 0; succIndex < E; succIndex++) { | |
| final succId = succs[succIndex]; | |
| if (succId != _SENTINEL) { | |
| numPreds[succId]++; | |
| } | |
| } | |
| // Assign indices into predecessors array. | |
| final firstPreds = numPreds; // Alias. | |
| final nextPreds = _newUint32Array(N + 1); | |
| var predIndex = 0; | |
| for (var i = 1; i <= N; i++) { | |
| var thisPredIndex = predIndex; | |
| predIndex += numPreds[i]; | |
| firstPreds[i] = thisPredIndex; | |
| nextPreds[i] = thisPredIndex; | |
| } | |
| if (N == Nconnected) { | |
| assert(predIndex == E); | |
| } | |
| firstPreds[N + 1] = predIndex; // Extra entry for cheap boundary detection. | |
| // Fill predecessors array. | |
| for (var i = 1; i <= N; i++) { | |
| var startSuccIndex = firstSuccs[i]; | |
| var limitSuccIndex = firstSuccs[i + 1]; | |
| for ( | |
| var succIndex = startSuccIndex; | |
| succIndex < limitSuccIndex; | |
| succIndex++ | |
| ) { | |
| var succId = succs[succIndex]; | |
| if (succId != _SENTINEL) { | |
| var predIndex = nextPreds[succId]++; | |
| preds[predIndex] = i; | |
| } | |
| } | |
| } | |
| _firstPreds = firstPreds; | |
| _preds = preds; | |
| } | |
| // Fold the size of any object with in-degree(1) into its parent. | |
| // Requires the DFS numbering and predecessor lists. | |
| void _buildOwnedSizes() { | |
| final N = _N!; | |
| final Nconnected = _Nconnected!; | |
| final kStackCid = _kStackCid; | |
| final kFieldCid = _kFieldCid; | |
| final cids = _cids!; | |
| final internalSizes = _internalSizes!; | |
| final externalSizes = _externalSizes!; | |
| final vertex = _vertex!; | |
| final firstPreds = _firstPreds!; | |
| final preds = _preds!; | |
| final ownedSizes = _newUint32Array(N + 1); | |
| for (var i = 1; i <= Nconnected; i++) { | |
| final v = vertex[i]; | |
| ownedSizes[v] = internalSizes[v] + externalSizes[v]; | |
| } | |
| for (var i = Nconnected; i > 1; i--) { | |
| var w = vertex[i]; | |
| assert(w != _ROOT); | |
| var onlyPred = _SENTINEL; | |
| var startPred = firstPreds[w]; | |
| var limitPred = firstPreds[w + 1]; | |
| for (var predIndex = startPred; predIndex < limitPred; predIndex++) { | |
| var v = preds[predIndex]; | |
| if (v == w) { | |
| // Ignore self-predecessor. | |
| } else if (onlyPred == _SENTINEL) { | |
| onlyPred = v; | |
| } else if (onlyPred == v) { | |
| // Repeated predecessor. | |
| } else { | |
| // Multiple-predecessors. | |
| onlyPred = _SENTINEL; | |
| break; | |
| } | |
| } | |
| // If this object has a single precessor which is not a Field, Stack or | |
| // the root, blame its size against the precessor. | |
| if ((onlyPred != _SENTINEL) && | |
| (onlyPred != _ROOT) && | |
| (cids[onlyPred] != kStackCid) && | |
| (cids[onlyPred] != kFieldCid)) { | |
| assert(onlyPred != w); | |
| ownedSizes[onlyPred] += ownedSizes[w]; | |
| ownedSizes[w] = 0; | |
| } | |
| } | |
| // TODO(rmacnak): Maybe keep the per-objects sizes to be able to provide | |
| // examples of large owners for each class. | |
| final classes = _classes!; | |
| for (var i = 1; i <= Nconnected; i++) { | |
| final v = vertex[i]; | |
| final cid = cids[v]; | |
| final cls = classes[cid]!; | |
| cls.ownedSize += ownedSizes[v]; | |
| } | |
| } | |
| static int _eval( | |
| int v, | |
| Uint32List ancestor, | |
| Uint32List semi, | |
| Uint32List label, | |
| Uint32List stackNode, | |
| Uint8List stackState, | |
| ) { | |
| if (ancestor[v] == _SENTINEL) { | |
| return label[v]; | |
| } else { | |
| { | |
| // Inlined 'compress' with an explicit stack to prevent JS stack | |
| // overflow. | |
| var top = 0; | |
| stackNode[top] = v; | |
| stackState[top] = 0; | |
| while (top >= 0) { | |
| var v = stackNode[top]; | |
| var state = stackState[top]; | |
| if (state == 0) { | |
| assert(ancestor[v] != 0); | |
| if (ancestor[ancestor[v]] != 0) { | |
| stackState[top] = 1; | |
| // Recurse with ancestor[v] | |
| top++; | |
| stackNode[top] = ancestor[v]; | |
| stackState[top] = 0; | |
| } else { | |
| top--; | |
| } | |
| } else { | |
| assert(state == 1); | |
| if (semi[label[ancestor[v]]] < semi[label[v]]) { | |
| label[v] = label[ancestor[v]]; | |
| } | |
| ancestor[v] = ancestor[ancestor[v]]; | |
| top--; | |
| } | |
| } | |
| } | |
| if (semi[label[ancestor[v]]] >= semi[label[v]]) { | |
| return label[v]; | |
| } else { | |
| return label[ancestor[v]]; | |
| } | |
| } | |
| } | |
| // Note the version in the main text of Lengauer & Tarjan incorrectly | |
| // uses parent instead of ancestor. The correct version is in Appendix B. | |
| static void _link( | |
| int v, | |
| int w, | |
| Uint32List size, | |
| Uint32List label, | |
| Uint32List semi, | |
| Uint32List child, | |
| Uint32List ancestor, | |
| ) { | |
| assert(size[0] == 0); | |
| assert(label[0] == 0); | |
| assert(semi[0] == 0); | |
| var s = w; | |
| while (semi[label[w]] < semi[label[child[s]]]) { | |
| if (size[s] + size[child[child[s]]] >= 2 * size[child[s]]) { | |
| ancestor[child[s]] = s; | |
| child[s] = child[child[s]]; | |
| } else { | |
| size[child[s]] = size[s]; | |
| s = ancestor[s] = child[s]; | |
| } | |
| } | |
| label[s] = label[w]; | |
| size[v] = size[v] + size[w]; | |
| if (size[v] < 2 * size[w]) { | |
| var tmp = s; | |
| s = child[v]; | |
| child[v] = tmp; | |
| } | |
| while (s != 0) { | |
| ancestor[s] = v; | |
| s = child[s]; | |
| } | |
| } | |
| // T. Lengauer and R. E. Tarjan. "A Fast Algorithm for Finding Dominators | |
| // in a Flowgraph." | |
| void _buildDominators() { | |
| final N = _N!; | |
| final Nconnected = _Nconnected!; | |
| final vertex = _vertex!; | |
| final semi = _semi!; | |
| final parent = _parent!; | |
| final firstPreds = _firstPreds!; | |
| final preds = _preds!; | |
| final dom = _newUint32Array(N + 1); | |
| final ancestor = _newUint32Array(N + 1); | |
| final label = _newUint32Array(N + 1); | |
| for (var i = 1; i <= N; i++) { | |
| label[i] = i; | |
| } | |
| final buckets = new List<dynamic>.filled(N + 1, null); | |
| final child = _newUint32Array(N + 1); | |
| final size = _newUint32Array(N + 1); | |
| for (var i = 1; i <= N; i++) { | |
| size[i] = 1; | |
| } | |
| final stackNode = _newUint32Array(N + 1); | |
| final stackState = _newUint8Array(N + 1); | |
| for (var i = Nconnected; i > 1; i--) { | |
| var w = vertex[i]; | |
| assert(w != _ROOT); | |
| // Lengauer & Tarjan Step 2. | |
| var startPred = firstPreds[w]; | |
| var limitPred = firstPreds[w + 1]; | |
| for (var predIndex = startPred; predIndex < limitPred; predIndex++) { | |
| var v = preds[predIndex]; | |
| var u = _eval(v, ancestor, semi, label, stackNode, stackState); | |
| if (semi[u] < semi[w]) { | |
| semi[w] = semi[u]; | |
| } | |
| } | |
| // w.semi.bucket.add(w); | |
| var tmp = vertex[semi[w]]; | |
| if (buckets[tmp] == null) { | |
| buckets[tmp] = []; | |
| } | |
| buckets[tmp].add(w); | |
| _link(parent[w], w, size, label, semi, child, ancestor); | |
| // Lengauer & Tarjan Step 3. | |
| tmp = parent[w]; | |
| var bucket = buckets[tmp]; | |
| buckets[tmp] = null; | |
| if (bucket != null) { | |
| for (var v in bucket) { | |
| var u = _eval(v, ancestor, semi, label, stackNode, stackState); | |
| dom[v] = semi[u] < semi[v] ? u : parent[w]; | |
| } | |
| } | |
| } | |
| for (var i = _ROOT; i <= N; i++) { | |
| assert(buckets[i] == null); | |
| } | |
| // Lengauer & Tarjan Step 4. | |
| for (var i = 2; i <= Nconnected; i++) { | |
| var w = vertex[i]; | |
| if (dom[w] != vertex[semi[w]]) { | |
| dom[w] = dom[dom[w]]; | |
| } | |
| } | |
| _doms = dom; | |
| } | |
| void _calculateRetainedSizes() { | |
| final N = _N!; | |
| final Nconnected = _Nconnected!; | |
| var liveInternalSize = 0; | |
| var liveExternalSize = 0; | |
| final classes = _classes!; | |
| final cids = _cids!; | |
| final internalSizes = _internalSizes!; | |
| final externalSizes = _externalSizes!; | |
| final vertex = _vertex!; | |
| final doms = _doms!; | |
| // Sum internal and external sizes. | |
| for (var i = 1; i <= Nconnected; i++) { | |
| var v = vertex[i]; | |
| var internalSize = internalSizes[v]; | |
| var externalSize = externalSizes[v]; | |
| liveInternalSize += internalSize; | |
| liveExternalSize += externalSize; | |
| var cls = classes[cids[v]]!; | |
| cls.liveInternalSize += internalSize; | |
| cls.liveExternalSize += externalSize; | |
| cls.liveInstanceCount++; | |
| } | |
| // Start with retained size as shallow size + external size. For reachable | |
| // objects only; leave unreachable objects with a retained size of 0 so | |
| // they can be filtered during graph iterations. | |
| var retainedSizes = new Uint32List(N + 1); | |
| assert(Nconnected <= N); | |
| for (var i = 0; i <= Nconnected; i++) { | |
| var v = vertex[i]; | |
| retainedSizes[v] = internalSizes[v] + externalSizes[v]; | |
| } | |
| // In post order (bottom up), add retained size to dominator's retained | |
| // size, skipping root. | |
| for (var i = Nconnected; i > 1; i--) { | |
| var v = vertex[i]; | |
| assert(v != _ROOT); | |
| retainedSizes[doms[v]] += retainedSizes[v]; | |
| } | |
| // Root retains everything. | |
| assert(retainedSizes[_ROOT] == (liveInternalSize + liveExternalSize)); | |
| _retainedSizes = retainedSizes; | |
| _liveInternalSize = liveInternalSize; | |
| _liveExternalSize = liveExternalSize; | |
| print("internal-garbage: ${_totalInternalSize! - _liveInternalSize!}"); | |
| print("external-garbage: ${_totalExternalSize! - _liveExternalSize!}"); | |
| print("fragmentation: ${_capacity! - _totalInternalSize!}"); | |
| assert(_liveInternalSize! <= _totalInternalSize!); | |
| assert(_liveExternalSize! <= _totalExternalSize!); | |
| assert(_totalInternalSize! <= _capacity!); | |
| } | |
| // Build linked lists of the children for each node in the dominator tree. | |
| void _linkDominatorChildren() { | |
| final N = _N!; | |
| final doms = _doms!; | |
| final head = _newUint32Array(N + 1); | |
| final next = _newUint32Array(N + 1); | |
| for (var child = _ROOT; child <= N; child++) { | |
| var parent = doms[child]; | |
| next[child] = head[parent]; | |
| head[parent] = child; | |
| } | |
| _mergedDomHead = head; | |
| _mergedDomNext = next; | |
| } | |
| // Merge the given lists according to the given key in ascending order. | |
| // Returns the head of the merged list. | |
| static int _mergeSorted( | |
| int head1, | |
| int head2, | |
| Uint32List next, | |
| Uint16List key, | |
| ) { | |
| var head = head1; | |
| var beforeInsert = _SENTINEL; | |
| var afterInsert = head1; | |
| var startInsert = head2; | |
| while (startInsert != _SENTINEL) { | |
| while ((afterInsert != _SENTINEL) && | |
| (key[afterInsert] <= key[startInsert])) { | |
| beforeInsert = afterInsert; | |
| afterInsert = next[beforeInsert]; | |
| } | |
| var endInsert = startInsert; | |
| var peek = next[endInsert]; | |
| while ((peek != _SENTINEL) && (key[peek] < key[afterInsert])) { | |
| endInsert = peek; | |
| peek = next[endInsert]; | |
| } | |
| assert(endInsert != _SENTINEL); | |
| if (beforeInsert == _SENTINEL) { | |
| head = startInsert; | |
| } else { | |
| next[beforeInsert] = startInsert; | |
| } | |
| next[endInsert] = afterInsert; | |
| startInsert = peek; | |
| beforeInsert = endInsert; | |
| } | |
| return head; | |
| } | |
| void _sortDominatorChildren() { | |
| final N = _N!; | |
| final cids = _cids!; | |
| final head = _mergedDomHead!; | |
| final next = _mergedDomNext!; | |
| // Returns the new head of the sorted list. | |
| int sort(int head) { | |
| if (head == _SENTINEL) return _SENTINEL; | |
| if (next[head] == _SENTINEL) return head; | |
| // Find the middle of the list. | |
| int head1 = head; | |
| int slow = head; | |
| int fast = head; | |
| while (next[fast] != _SENTINEL && next[next[fast]] != _SENTINEL) { | |
| slow = next[slow]; | |
| fast = next[next[fast]]; | |
| } | |
| // Split the list in half. | |
| int head2 = next[slow]; | |
| next[slow] = _SENTINEL; | |
| // Recursively sort the sublists and merge. | |
| assert(head1 != head2); | |
| int newHead1 = sort(head1); | |
| int newHead2 = sort(head2); | |
| return _mergeSorted(newHead1, newHead2, next, cids); | |
| } | |
| // Sort all list of dominator tree children by cid. | |
| for (var parent = _ROOT; parent <= N; parent++) { | |
| head[parent] = sort(head[parent]); | |
| } | |
| } | |
| void _mergeDominatorSiblings() { | |
| var N = _N!; | |
| var cids = _cids!; | |
| var head = _mergedDomHead!; | |
| var next = _mergedDomNext!; | |
| var workStack = _newUint32Array(N); | |
| var workStackTop = 0; | |
| mergeChildrenAndSort(parent1, end) { | |
| assert(parent1 != _SENTINEL); | |
| if (next[parent1] == end) return; | |
| // Find the middle of the list. | |
| int slow = parent1; | |
| int fast = parent1; | |
| while (next[fast] != end && next[next[fast]] != end) { | |
| slow = next[slow]; | |
| fast = next[next[fast]]; | |
| } | |
| int parent2 = next[slow]; | |
| assert(parent2 != _SENTINEL); | |
| assert(parent1 != parent2); | |
| assert(cids[parent1] == cids[parent2]); | |
| // Recursively sort the sublists. | |
| mergeChildrenAndSort(parent1, parent2); | |
| mergeChildrenAndSort(parent2, end); | |
| // Merge sorted sublists. | |
| head[parent1] = _mergeSorted(head[parent1], head[parent2], next, cids); | |
| // Children moved to parent1. | |
| head[parent2] = _SENTINEL; | |
| } | |
| // Push root. | |
| workStack[workStackTop++] = _ROOT; | |
| while (workStackTop > 0) { | |
| var parent = workStack[--workStackTop]; | |
| var child = head[parent]; | |
| while (child != _SENTINEL) { | |
| // Push child. | |
| workStack[workStackTop++] = child; | |
| // Find next sibling with a different cid. | |
| var after = child; | |
| while (after != _SENTINEL && cids[after] == cids[child]) { | |
| after = next[after]; | |
| } | |
| // From all the siblings between child and after, take their children, | |
| // merge them and given to child. | |
| mergeChildrenAndSort(child, after); | |
| child = after; | |
| } | |
| } | |
| } | |
| } |