Index: runtime/observatory/lib/object_graph.dart |
diff --git a/runtime/observatory/lib/object_graph.dart b/runtime/observatory/lib/object_graph.dart |
index 00355803aa7818925f2d804e417a1e031895d59a..60e720d3ac335b03181a376acfbd6369b4840625 100644 |
--- a/runtime/observatory/lib/object_graph.dart |
+++ b/runtime/observatory/lib/object_graph.dart |
@@ -10,13 +10,83 @@ import 'dart:typed_data'; |
import 'package:logging/logging.dart'; |
+class _JenkinsSmiHash { |
+ static int combine(int hash, int value) { |
+ hash = 0x1fffffff & (hash + value); |
+ hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); |
+ return hash ^ (hash >> 6); |
+ } |
+ |
+ static int finish(int hash) { |
+ hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); |
+ hash = hash ^ (hash >> 11); |
+ return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); |
+ } |
+ |
+ static int hash3(a, b, c) => finish(combine(combine(combine(0, a), b), c)); |
+} |
+ |
+// Map<[uint32, uint32, uint32], uint32> |
+class AddressMapper { |
+ final Uint32List _table; |
+ |
+ // * 4 ~/3 for 75% load factor |
+ // * 4 for four-tuple entries |
+ AddressMapper(int N) : _table = new Uint32List((N * 4 ~/ 3) * 4); |
+ |
+ int _scanFor(int high, int mid, int low) { |
+ var hash = _JenkinsSmiHash.hash3(high, mid, low); |
+ var start = (hash % _table.length) & ~3; |
+ var index = start; |
+ do { |
+ if (_table[index + 3] == 0) return index; |
+ if (_table[index] == high && |
+ _table[index + 1] == mid && |
+ _table[index + 2] == low) return index; |
+ index = (index + 4) % _table.length; |
+ } while (index != start); |
+ |
+ throw new Exception("Interal error: table full"); |
+ } |
+ |
+ int get(int high, int mid, int low) { |
+ int index = _scanFor(high, mid, low); |
+ if (_table[index + 3] == 0) return null; |
+ return _table[index + 3]; |
+ } |
+ |
+ int put(int high, int mid, int low, int id) { |
+ if (id == 0) throw new Exception("Internal error: invalid id"); |
+ |
+ int index = _scanFor(high, mid, low); |
+ if ((_table[index + 3] != 0)) { |
+ throw new Exception("Internal error: attempt to overwrite key"); |
+ } |
+ _table[index] = high; |
+ _table[index + 1] = mid; |
+ _table[index + 2] = low; |
+ _table[index + 3] = id; |
+ return id; |
+ } |
+} |
+ |
+ |
// Port of dart::ReadStream from vm/datastream.h. |
-class _ReadStream { |
+// |
+// The heap snapshot is a series of variable-length unsigned integers. For |
+// each byte in the stream, the high bit marks the last byte of an integer and |
+// the low 7 bits are the payload. The payloads are sent in little endian |
+// order. |
+// The largest values used are 64-bit addresses. |
+// We read in 4 payload chunks (28-bits) to stay in Smi range on Javascript. |
+// We read them into instance variables ('low', 'mid' and 'high') to avoid |
+// allocating a container. |
+class ReadStream { |
int position = 0; |
int _size = 0; |
final List<ByteData> _chunks; |
- _ReadStream(this._chunks) { |
+ ReadStream(this._chunks) { |
int n = _chunks.length; |
for (var i = 0; i < n; i++) { |
var chunk = _chunks[i]; |
@@ -29,21 +99,109 @@ class _ReadStream { |
int get pendingBytes => _size - position; |
- int getUint8(i) { |
+ int _getUint8(i) { |
return _chunks[i >> 20].getUint8(i & 0xFFFFF); |
} |
- int readUnsigned() { |
- int result = 0; |
- int shift = 0; |
- while (getUint8(position) <= maxUnsignedDataPerByte) { |
- result |= getUint8(position) << shift; |
- shift += dataBitsPerByte; |
- position++; |
+ int low = 0; |
+ int mid = 0; |
+ int high = 0; |
+ |
+ int get clampedUint32 { |
+ if (high != 0 || mid > 0xF) { |
+ return 0xFFFFFFFF; |
+ } else { |
+ // Not shift as JS shifts are signed 32-bit. |
+ return mid * 0x10000000 + low; |
} |
- result |= (getUint8(position) & byteMask) << shift; |
- position++; |
- return result; |
+ } |
+ |
+ bool get isZero { |
+ return (high == 0) && (mid == 0) && (low == 0); |
+ } |
+ |
+ void readUnsigned() { |
+ low = 0; |
+ mid = 0; |
+ high = 0; |
+ |
+ // Low 28 bits. |
+ var digit = _getUint8(position++); |
+ if (digit > maxUnsignedDataPerByte) { |
+ low |= (digit & byteMask << 0); |
+ return; |
+ } |
+ low |= (digit << 0); |
+ |
+ digit = _getUint8(position++); |
+ if (digit > maxUnsignedDataPerByte) { |
+ low |= ((digit & byteMask) << 7); |
+ return; |
+ } |
+ low |= (digit << 7); |
+ |
+ digit = _getUint8(position++); |
+ if (digit > maxUnsignedDataPerByte) { |
+ low |= ((digit & byteMask) << 14); |
+ return; |
+ } |
+ low |= (digit << 14); |
+ |
+ digit = _getUint8(position++); |
+ if (digit > maxUnsignedDataPerByte) { |
+ low |= ((digit & byteMask) << 21); |
+ return; |
+ } |
+ low |= (digit << 21); |
+ |
+ // Mid 28 bits. |
+ digit = _getUint8(position++); |
+ if (digit > maxUnsignedDataPerByte) { |
+ mid |= (digit & byteMask << 0); |
+ return; |
+ } |
+ mid |= (digit << 0); |
+ |
+ digit = _getUint8(position++); |
+ if (digit > maxUnsignedDataPerByte) { |
+ mid |= ((digit & byteMask) << 7); |
+ return; |
+ } |
+ mid |= (digit << 7); |
+ |
+ digit = _getUint8(position++); |
+ if (digit > maxUnsignedDataPerByte) { |
+ mid |= ((digit & byteMask) << 14); |
+ return; |
+ } |
+ mid |= (digit << 14); |
+ |
+ digit = _getUint8(position++); |
+ if (digit > maxUnsignedDataPerByte) { |
+ mid |= ((digit & byteMask) << 21); |
+ return; |
+ } |
+ mid |= (digit << 21); |
+ |
+ // High 28 bits. |
+ digit = _getUint8(position++); |
+ if (digit > maxUnsignedDataPerByte) { |
+ high |= (digit & byteMask << 0); |
+ return; |
+ } |
+ high |= (digit << 0); |
+ |
+ digit = _getUint8(position++); |
+ if (digit > maxUnsignedDataPerByte) { |
+ high |= ((digit & byteMask) << 7); |
+ return; |
+ } |
+ high |= (digit << 7); |
+ throw new Exception("Format error: snapshot field exceeds 64 bits"); |
+ } |
+ |
+ void skipUnsigned() { |
+ while (_getUint8(position++) <= maxUnsignedDataPerByte); |
} |
static const int dataBitsPerByte = 7; |
@@ -67,30 +225,52 @@ class ObjectVertex { |
ObjectVertex get dominator => new ObjectVertex._(_graph._doms[_id], _graph); |
int get shallowSize { |
- var stream = new _ReadStream(_graph._chunks); |
+ var stream = new ReadStream(_graph._chunks); |
stream.position = _graph._positions[_id]; |
- stream.readUnsigned(); // addr |
- return stream.readUnsigned(); // shallowSize |
+ stream.skipUnsigned(); // addr |
+ stream.readUnsigned(); // shallowSize |
+ return stream.clampedUint32; |
} |
int get vmCid { |
- var stream = new _ReadStream(_graph._chunks); |
+ var stream = new ReadStream(_graph._chunks); |
stream.position = _graph._positions[_id]; |
- stream.readUnsigned(); // addr |
- stream.readUnsigned(); // shallowSize |
- return stream.readUnsigned(); // cid |
+ stream.skipUnsigned(); // addr |
+ stream.skipUnsigned(); // shallowSize |
+ stream.readUnsigned(); // cid |
+ return stream.clampedUint32; |
} |
get successors => new _SuccessorsIterable(_graph, _id); |
- int get address { |
+ String get address { |
// Note that everywhere else in this file, "address" really means an address |
// scaled down by kObjectAlignment. They were scaled down so they would fit |
// into Smis on the client. |
- var stream = new _ReadStream(_graph._chunks); |
+ var stream = new ReadStream(_graph._chunks); |
stream.position = _graph._positions[_id]; |
- var scaledAddr = stream.readUnsigned(); |
- return scaledAddr * _graph._kObjectAlignment; |
+ stream.readUnsigned(); |
+ |
+ // Complicated way to do (high:mid:low * _kObjectAlignment).toHexString() |
+ // without intermediate values exceeding int32. |
+ |
+ var strAddr = ""; |
+ var carry = 0; |
+ combine4(nibble) { |
+ nibble = nibble * _graph._kObjectAlignment + carry; |
+ carry = nibble >> 4; |
+ nibble = nibble & 0xF; |
+ strAddr = nibble.toRadixString(16) + strAddr; |
+ } |
+ combine28(twentyEightBits) { |
+ for (int shift = 0; shift < 28; shift += 4) { |
+ combine4((twentyEightBits >> shift) & 0xF); |
+ } |
+ } |
+ combine28(stream.low); |
+ combine28(stream.mid); |
+ combine28(stream.high); |
+ return strAddr; |
} |
List<ObjectVertex> dominatorTreeChildren() { |
@@ -121,24 +301,23 @@ class _SuccessorsIterable extends IterableBase<ObjectVertex> { |
class _SuccessorsIterator implements Iterator<ObjectVertex> { |
final ObjectGraph _graph; |
- _ReadStream _stream; |
+ ReadStream _stream; |
ObjectVertex current; |
_SuccessorsIterator(this._graph, int id) { |
- _stream = new _ReadStream(this._graph._chunks); |
+ _stream = new ReadStream(this._graph._chunks); |
_stream.position = _graph._positions[id]; |
- _stream.readUnsigned(); // addr |
- _stream.readUnsigned(); // shallowSize |
- var cid = _stream.readUnsigned(); |
- assert((cid & ~0xFFFF) == 0); // Sanity check: cid's are 16 bit. |
+ _stream.skipUnsigned(); // addr |
+ _stream.skipUnsigned(); // shallowSize |
+ _stream.skipUnsigned(); // cid |
} |
bool moveNext() { |
while (true) { |
- var nextAddr = _stream.readUnsigned(); |
- if (nextAddr == 0) return false; |
- var nextId = _graph._addrToId[nextAddr]; |
+ _stream.readUnsigned(); |
+ if (_stream.isZero) return false; |
+ var nextId = _graph._addrToId.get(_stream.high, _stream.mid, _stream.low); |
if (nextId == null) continue; // Reference to VM isolate's heap. |
current = new ObjectVertex._(nextId, _graph); |
return true; |
@@ -232,7 +411,7 @@ class ObjectGraph { |
int _E; |
int _size; |
- Map<int, int> _addrToId = new Map<int, int>(); |
+ AddressMapper _addrToId; |
// Indexed by node id, with id 0 representing invalid/uninitialized. |
Uint32List _positions; // Position of the node in the snapshot. |
@@ -245,32 +424,34 @@ class ObjectGraph { |
void _buildPositions() { |
var N = _N; |
- var addrToId = _addrToId; |
+ var addrToId = new AddressMapper(N); |
var positions = new Uint32List(N + 1); |
- var stream = new _ReadStream(_chunks); |
- _kObjectAlignment = stream.readUnsigned(); |
+ var stream = new ReadStream(_chunks); |
+ stream.readUnsigned(); |
+ _kObjectAlignment = stream.clampedUint32; |
var id = 1; |
while (stream.pendingBytes > 0) { |
positions[id] = stream.position; |
- var addr = stream.readUnsigned(); |
- stream.readUnsigned(); // shallowSize |
- stream.readUnsigned(); // cid |
- addrToId[addr] = id; |
- |
- var succAddr = stream.readUnsigned(); |
- while (succAddr != 0) { |
- succAddr = stream.readUnsigned(); |
+ stream.readUnsigned(); // addr |
+ addrToId.put(stream.high, stream.mid, stream.low, id); |
+ stream.skipUnsigned(); // shallowSize |
+ stream.skipUnsigned(); // cid |
+ |
+ stream.readUnsigned(); |
+ while (!stream.isZero) { |
+ stream.readUnsigned(); |
} |
id++; |
} |
assert(id == (N + 1)); |
- var root = addrToId[0]; |
+ var root = addrToId.get(0, 0, 0); |
assert(root == 1); |
+ _addrToId = addrToId; |
_positions = positions; |
} |
@@ -292,11 +473,11 @@ class ObjectGraph { |
stackNodes[0] = root; |
- var stream = new _ReadStream(_chunks); |
+ var stream = new ReadStream(_chunks); |
stream.position = positions[root]; |
- stream.readUnsigned(); // addr |
- stream.readUnsigned(); // shallowSize |
- stream.readUnsigned(); // cid |
+ stream.skipUnsigned(); // addr |
+ stream.skipUnsigned(); // shallowSize |
+ stream.skipUnsigned(); // cid |
stackCurrentEdgePos[0] = stream.position; |
visited[root] = 1; |
@@ -305,10 +486,10 @@ class ObjectGraph { |
var edgePos = stackCurrentEdgePos[stackTop]; |
stream.position = edgePos; |
- var childAddr = stream.readUnsigned(); |
- if (childAddr != 0) { |
+ stream.readUnsigned(); // childAddr |
+ if (!stream.isZero) { |
stackCurrentEdgePos[stackTop] = stream.position; |
- var childId = addrToId[childAddr]; |
+ var childId = addrToId.get(stream.high, stream.mid, stream.low); |
if (childId == null) continue; // Reference to VM isolate's heap. |
E++; |
if (visited[childId] == 1) continue; |
@@ -317,9 +498,9 @@ class ObjectGraph { |
stackNodes[stackTop] = childId; |
stream.position = positions[childId]; |
- stream.readUnsigned(); // addr |
- stream.readUnsigned(); // shallowSize |
- stream.readUnsigned(); // cid |
+ stream.skipUnsigned(); // addr |
+ stream.skipUnsigned(); // shallowSize |
+ stream.skipUnsigned(); // cid |
stackCurrentEdgePos[stackTop] = stream.position; // i.e., first edge |
visited[childId] = 1; |
} else { |
@@ -353,21 +534,21 @@ class ObjectGraph { |
var preds = new Uint32List(E); |
// Count predecessors of each node. |
- var stream = new _ReadStream(_chunks); |
+ var stream = new ReadStream(_chunks); |
for (var i = 1; i <= N; i++) { |
stream.position = positions[i]; |
- stream.readUnsigned(); // addr |
- stream.readUnsigned(); // shallowSize |
- stream.readUnsigned(); // cid |
- var succAddr = stream.readUnsigned(); |
- while (succAddr != 0) { |
- var succId = addrToId[succAddr]; |
+ stream.skipUnsigned(); // addr |
+ stream.skipUnsigned(); // shallowSize |
+ stream.skipUnsigned(); // cid |
+ stream.readUnsigned(); // succAddr |
+ while (!stream.isZero) { |
+ var succId = addrToId.get(stream.high, stream.mid, stream.low); |
if (succId != null) { |
numPreds[succId]++; |
} else { |
// Reference to VM isolate's heap. |
} |
- succAddr = stream.readUnsigned(); |
+ stream.readUnsigned(); // succAddr |
} |
} |
@@ -387,19 +568,19 @@ class ObjectGraph { |
// Fill predecessors array. |
for (var i = 1; i <= N; i++) { |
stream.position = positions[i]; |
- stream.readUnsigned(); // addr |
- stream.readUnsigned(); // shallowSize |
- stream.readUnsigned(); // cid |
- var succAddr = stream.readUnsigned(); |
- while (succAddr != 0) { |
- var succId = addrToId[succAddr]; |
+ stream.skipUnsigned(); // addr |
+ stream.skipUnsigned(); // shallowSize |
+ stream.skipUnsigned(); // cid |
+ stream.readUnsigned(); // succAddr |
+ while (!stream.isZero) { |
+ var succId = addrToId.get(stream.high, stream.mid, stream.low); |
if (succId != null) { |
var predIndex = nextPreds[succId]++; |
preds[predIndex] = i; |
} else { |
// Reference to VM isolate's heap. |
} |
- succAddr = stream.readUnsigned(); |
+ stream.readUnsigned(); // succAddr |
} |
} |
@@ -495,11 +676,12 @@ class ObjectGraph { |
var retainedSizes = new Uint32List(N + 1); |
// Start with retained size as shallow size. |
- var reader = new _ReadStream(_chunks); |
+ var reader = new ReadStream(_chunks); |
for (var i = 1; i <= N; i++) { |
reader.position = positions[i]; |
- reader.readUnsigned(); // addr |
- var shallowSize = reader.readUnsigned(); |
+ reader.skipUnsigned(); // addr |
+ reader.readUnsigned(); // shallowSize |
+ var shallowSize = reader.clampedUint32; |
retainedSizes[i] = shallowSize; |
size += shallowSize; |
} |