Jelajahi Sumber

Move memory related files into haxelib (#673)

Léo Viguier 1 tahun lalu
induk
melakukan
9381aa7b4c

+ 29 - 0
other/haxelib/.vscode/launch.json

@@ -0,0 +1,29 @@
+{
+    // Use IntelliSense to learn about possible attributes.
+    // Hover to view descriptions of existing attributes.
+    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "name": "HashLink (launch)",
+            "request": "launch",
+            "type": "hl",
+            "cwd": "${workspaceFolder}",
+            "preLaunchTask": {
+                "type": "haxe",
+                "args": "active configuration"
+            }
+        },
+        {
+            "name": "HashLink (attach)",
+            "request": "attach",
+            "port": 6112,
+            "type": "hl",
+            "cwd": "${workspaceFolder}",
+            "preLaunchTask": {
+                "type": "haxe",
+                "args": "active configuration"
+            }
+        }
+    ]
+}

+ 208 - 206
other/memory/Block.hx → other/haxelib/hlmem/Block.hx

@@ -1,206 +1,208 @@
-abstract Pointer(haxe.Int64) {
-
-	public var value(get,never) : haxe.Int64;
-
-	inline function get_value() return this;
-
-	inline function new(v) this = v;
-
-	public inline function isNull() {
-		return this == 0;
-	}
-	public inline function offset( i : Int ) : Pointer {
-		return cast (this + i);
-	}
-	public inline function sub( p : Pointer ) : Int {
-		return haxe.Int64.toInt(this - p.value);
-	}
-	public inline function toString() {
-		return "0x"+(this.high == 0 ? StringTools.hex(this.high, 8) : "")+StringTools.hex(this.low,8);
-	}
-	public inline function shift( k : Int ) : haxe.Int64 {
-		return haxe.Int64.shr(this,k);
-	}
-
-	public static var NULL(get,never) : Pointer;
-	inline static function get_NULL() return new Pointer(0);
-
-}
-
-enum abstract PageKind(Int) {
-	var PDynamic = 0;
-	var PRaw = 1;
-	var PNoPtr = 2;
-	var PFinalizer = 3;
-}
-
-class Page {
-	public var addr : Pointer;
-	public var kind : PageKind;
-	public var size : Int;
-	public var reserved : Int;
-	public var dataPosition : Int = -1;
-
-	public function new() {
-	}
-
-	public inline function memHasPtr() {
-		return kind == PDynamic || kind == PRaw;
-	}
-
-}
-
-class Stack {
-	public var base : Pointer;
-	public var contents : Array<Pointer>;
-	public function new() {
-	}
-}
-
-enum BlockTypeKind {
-	KHeader;
-	KRoot;
-	KAbstractData;
-	KDynObjData;
-	KInferred( t : TType, k : BlockTypeKind );
-}
-
-class BlockSub {
-	public var b : Block;
-	public var fid : Int;
-	public function new(b,fid) {
-		this.b = b;
-		this.fid = fid;
-	}
-}
-
-class Block {
-	public static var MARK_UID = 0;
-
-	public var page : Page;
-	public var addr : Pointer;
-	public var size : Int;
-	public var typePtr : Pointer;
-	public var owner : Block;
-	public var type(default, set) : TType;
-	public var typeKind : BlockTypeKind;
-
-	public var depth : Int = -1;
-	public var mark : Int = -1;
-
-	public var subs : Array<BlockSub>; // can be null
-	public var parents : Array<Block>; // if multiple owners
-
-	public function new() {
-	}
-
-	inline function set_type( t : TType ) {
-		return type = t;
-	}
-
-	public function addParent(b:Block,fid:Int=0) {
-		if( owner == null ) {
-			owner = b;
-		} else {
-			if( parents == null ) parents = [owner];
-			parents.push(b);
-		}
-		if( b.subs == null ) b.subs = [];
-		b.subs.push(new BlockSub(this,fid));
-	}
-
-	public function makeTID( prev : Block, withField : Bool ) {
-		if( type == null )
-			return 0;
-		if( withField )
-			for( s in subs )
-				if( s.b == prev )
-					return type.tid | (s.fid << 24);
-		return type.tid;
-	}
-
-	public function getParents() {
-		return parents != null ? parents : owner == null ? [] : [owner];
-	}
-
-	public function markDepth() {
-		var d = depth + 1;
-		var all = subs;
-		if( all == null ) return;
-		while( all.length > 0 ) {
-			var out = [];
-			for( b in all ) {
-				var b = b.b;
-				if( b.depth < 0 || b.depth > d ) {
-					b.depth = d;
-					if( b.subs != null ) for( s in b.subs ) out.push(s);
-				}
-			}
-			all = out;
-			d++;
-		}
-	}
-
-	public function finalize() {
-		if( parents == null ) return;
-
-		inline function getPriority(b:Block) {
-			var d = -b.depth * 5;
-			if( b.type == null ) return d-2;
-			if( !b.type.hasPtr ) return d-1; // false positive
-			return switch( b.type.t ) {
-			case HFun(_): d;
-			case HVirtual(_): d+1;
-			default: d+2;
-			}
-		}
-
-		parents.sort(function(p1, p2) return getPriority(p2) - getPriority(p1));
-		owner = parents[0];
-	}
-
-	function removeParent( p : Block ) {
-		if( parents != null ) {
-			parents.remove(p);
-			if( parents.length == 0 ) parents = null;
-		}
-		if( owner == p )
-			owner = parents == null ? null : parents[0];
-	}
-
-	public function removeChildren() {
-		if( subs != null ) {
-			for( s in subs )
-				s.b.removeParent(this);
-			subs = null;
-		}
-	}
-
-}
-
-@:generic
-class PointerMap<T> {
-
-	var lookup : Map<Int,Map<Int,T>>;
-
-	public function new() {
-		lookup = new Map();
-	}
-
-	public function set( p : Pointer, v : T ) {
-		var c = lookup.get(p.value.high);
-		if( c == null ) {
-			c = new Map();
-			lookup.set(p.value.high, c);
-		}
-		c.set(p.value.low, v);
-	}
-
-	public function get( p : Pointer ) : T {
-		if( p.isNull() ) return null;
-		var c = lookup.get(p.value.high);
-		if( c == null ) return null;
-		return c.get(p.value.low);
-	}
-
-}
+package hlmem;
+
+abstract Pointer(haxe.Int64) {
+
+	public var value(get,never) : haxe.Int64;
+
+	inline function get_value() return this;
+
+	inline function new(v) this = v;
+
+	public inline function isNull() {
+		return this == 0;
+	}
+	public inline function offset( i : Int ) : Pointer {
+		return cast (this + i);
+	}
+	public inline function sub( p : Pointer ) : Int {
+		return haxe.Int64.toInt(this - p.value);
+	}
+	public inline function toString() {
+		return "0x"+(this.high == 0 ? StringTools.hex(this.high, 8) : "")+StringTools.hex(this.low,8);
+	}
+	public inline function shift( k : Int ) : haxe.Int64 {
+		return haxe.Int64.shr(this,k);
+	}
+
+	public static var NULL(get,never) : Pointer;
+	inline static function get_NULL() return new Pointer(0);
+
+}
+
+enum abstract PageKind(Int) {
+	var PDynamic = 0;
+	var PRaw = 1;
+	var PNoPtr = 2;
+	var PFinalizer = 3;
+}
+
+class Page {
+	public var addr : Pointer;
+	public var kind : PageKind;
+	public var size : Int;
+	public var reserved : Int;
+	public var dataPosition : Int = -1;
+
+	public function new() {
+	}
+
+	public inline function memHasPtr() {
+		return kind == PDynamic || kind == PRaw;
+	}
+
+}
+
+class Stack {
+	public var base : Pointer;
+	public var contents : Array<Pointer>;
+	public function new() {
+	}
+}
+
+enum BlockTypeKind {
+	KHeader;
+	KRoot;
+	KAbstractData;
+	KDynObjData;
+	KInferred( t : TType, k : BlockTypeKind );
+}
+
+class BlockSub {
+	public var b : Block;
+	public var fid : Int;
+	public function new(b,fid) {
+		this.b = b;
+		this.fid = fid;
+	}
+}
+
+class Block {
+	public static var MARK_UID = 0;
+
+	public var page : Page;
+	public var addr : Pointer;
+	public var size : Int;
+	public var typePtr : Pointer;
+	public var owner : Block;
+	public var type(default, set) : TType;
+	public var typeKind : BlockTypeKind;
+
+	public var depth : Int = -1;
+	public var mark : Int = -1;
+
+	public var subs : Array<BlockSub>; // can be null
+	public var parents : Array<Block>; // if multiple owners
+
+	public function new() {
+	}
+
+	inline function set_type( t : TType ) {
+		return type = t;
+	}
+
+	public function addParent(b:Block,fid:Int=0) {
+		if( owner == null ) {
+			owner = b;
+		} else {
+			if( parents == null ) parents = [owner];
+			parents.push(b);
+		}
+		if( b.subs == null ) b.subs = [];
+		b.subs.push(new BlockSub(this,fid));
+	}
+
+	public function makeTID( prev : Block, withField : Bool ) {
+		if( type == null )
+			return 0;
+		if( withField )
+			for( s in subs )
+				if( s.b == prev )
+					return type.tid | (s.fid << 24);
+		return type.tid;
+	}
+
+	public function getParents() {
+		return parents != null ? parents : owner == null ? [] : [owner];
+	}
+
+	public function markDepth() {
+		var d = depth + 1;
+		var all = subs;
+		if( all == null ) return;
+		while( all.length > 0 ) {
+			var out = [];
+			for( b in all ) {
+				var b = b.b;
+				if( b.depth < 0 || b.depth > d ) {
+					b.depth = d;
+					if( b.subs != null ) for( s in b.subs ) out.push(s);
+				}
+			}
+			all = out;
+			d++;
+		}
+	}
+
+	public function finalize() {
+		if( parents == null ) return;
+
+		inline function getPriority(b:Block) {
+			var d = -b.depth * 5;
+			if( b.type == null ) return d-2;
+			if( !b.type.hasPtr ) return d-1; // false positive
+			return switch( b.type.t ) {
+			case HFun(_): d;
+			case HVirtual(_): d+1;
+			default: d+2;
+			}
+		}
+
+		parents.sort(function(p1, p2) return getPriority(p2) - getPriority(p1));
+		owner = parents[0];
+	}
+
+	function removeParent( p : Block ) {
+		if( parents != null ) {
+			parents.remove(p);
+			if( parents.length == 0 ) parents = null;
+		}
+		if( owner == p )
+			owner = parents == null ? null : parents[0];
+	}
+
+	public function removeChildren() {
+		if( subs != null ) {
+			for( s in subs )
+				s.b.removeParent(this);
+			subs = null;
+		}
+	}
+
+}
+
+@:generic
+class PointerMap<T> {
+
+	var lookup : Map<Int,Map<Int,T>>;
+
+	public function new() {
+		lookup = new Map();
+	}
+
+	public function set( p : Pointer, v : T ) {
+		var c = lookup.get(p.value.high);
+		if( c == null ) {
+			c = new Map();
+			lookup.set(p.value.high, c);
+		}
+		c.set(p.value.low, v);
+	}
+
+	public function get( p : Pointer ) : T {
+		if( p.isNull() ) return null;
+		var c = lookup.get(p.value.high);
+		if( c == null ) return null;
+		return c.get(p.value.low);
+	}
+
+}

+ 1103 - 1003
other/memory/Memory.hx → other/haxelib/hlmem/Memory.hx

@@ -1,1004 +1,1104 @@
-import format.hl.Data;
-using format.hl.Tools;
-import Block;
-
-class Stats {
-	var mem : Memory;
-	var byT = new Map();
-	var allT = [];
-
-	public function new(mem) {
-		this.mem = mem;
-	}
-
-	public function add( t : TType, mem : Int ) {
-		return addPath([t == null ? 0 : t.tid], mem);
-	}
-
-	public inline function makeID( t : TType, field : Int ) {
-		return t.tid | (field << 24);
-	}
-
-	public function addPath( tl : Array<Int>, mem : Int ) {
-		var key = tl.join(" ");
-		var inf = byT.get(key);
-		if( inf == null ) {
-			inf = { tl : tl, count : 0, mem : 0 };
-			byT.set(key, inf);
-			allT.push(inf);
-		}
-		inf.count++;
-		inf.mem += mem;
-	}
-
-	public function print( withSum = false ) {
-		if( @:privateAccess mem.sortByCount )
-			allT.sort(function(i1, i2) return i1.count - i2.count);
-		else
-			allT.sort(function(i1, i2) return i1.mem - i2.mem);
-		var totCount = 0;
-		var totMem = 0;
-		var max = @:privateAccess mem.maxLines;
-		if( max > 0 && allT.length > max ) {
-			mem.log("<ignore "+(allT.length - max)+" lines>");
-			allT = allT.slice(allT.length - max);
-		}
-		for( i in allT ) {
-			totCount += i.count;
-			totMem += i.mem;
-			var tpath = [];
-			for( tid in i.tl ) {
-				var t = mem.types[tid & 0xFFFFFF];
-				var tstr = t.toString();
-				var fid = tid >>> 24;
-				if( fid > 0 ) {
-					var f = t.memFieldsNames[fid-1];
-					if( f != null ) tstr += "." + f;
-				}
-				tpath.push(tstr);
-			}
-			mem.log(Memory.withColor(i.count + " count, " + Memory.MB(i.mem) + " ", 33) + tpath.join(${Memory.withColor(' > ', 36)}));
-		}
-		if( withSum )
-			mem.log("Total: "+totCount+" count, "+Memory.MB(totMem));
-	}
-
-}
-
-enum FieldsMode {
-	Full;
-	Parents;
-	None;
-}
-
-enum FilterMode {
-	None;
-	Intersect;	// Will display only blocks present in all memories
-	Unique;		// Will display only blocks not present in other memories
-}
-
-class Memory {
-
-	public var memoryDump : sys.io.FileInput;
-
-	public var is64 : Bool;
-	public var bool32 : Bool;
-	public var ptrBits : Int;
-
-	public var types : Array<TType>;
-
-	var otherMems : Array<Memory>;
-	var filterMode: FilterMode = None;
-
-	var memFile : String;
-
-	var privateData : Int;
-	var markData : Int;
-
-	var sortByCount : Bool;
-	var displayFields : FieldsMode = Full;
-	var displayProgress = true;
-
-	var code : format.hl.Data;
-	var pages : Array<Page>;
-	var roots : Array<Pointer>;
-	var stacks : Array<Stack>;
-	var typesPointers : Array<Pointer>;
-	var closuresPointers : Array<Pointer>;
-	var blocks : Array<Block>;
-	var filteredBlocks : Array<Block> = [];
-	var baseTypes : Array<{ t : HLType, p : Pointer }>;
-	var all : Block;
-
-	var toProcess : Array<Block>;
-	var tdynObj : TType;
-	var tdynObjData : TType;
-	var pointerBlock : PointerMap<Block>;
-	var pointerType : PointerMap<TType>;
-	var falseCandidates : Array<{ b : Block, f : Block, idx : Int }>;
-
-	var currentTypeIndex = 0;
-	var resolveCache : Map<String,TType> = new Map();
-	var maxLines : Int = 100;
-
-	function new() {
-	}
-
-	public function typeSize( t : HLType ) {
-		return switch( t ) {
-		case HVoid: 0;
-		case HUi8: 1;
-		case HUi16: 2;
-		case HI32, HF32: 4;
-		case HI64, HF64: 8;
-		case HBool:
-			return bool32 ? 4 : 1;
-		case HPacked(_), HAt(_):
-			throw "assert";
-		default:
-			return is64 ? 8 : 4;
-		}
-	}
-
-	public function getType( t : HLType, isNull = false ) : TType {
-		// this is quite slow, but we can't use a Map, maybe try a more per-type specific approach ?
-		for( t2 in types )
-			if( t == t2.t )
-				return t2;
-		if( isNull )
-			return null;
-		throw "Type not found " + t.toString();
-		return null;
-	}
-
-	function loadBytecode( arg : String ) {
-		if( code != null ) throw "Duplicate code";
-		code = new format.hl.Reader(false).read(new haxe.io.BytesInput(sys.io.File.getBytes(arg)));
-		log(arg + " code loaded");
-	}
-
-	inline function readInt() {
-		return memoryDump.readInt32();
-	}
-
-	inline function readPointer() : Pointer {
-		var low = memoryDump.readInt32();
-		var high = is64 ? memoryDump.readInt32() : 0;
-		return cast haxe.Int64.make(high,low);
-	}
-
-	public static function MB( v : Float ) {
-		if( v < 1000 )
-			return Std.int(v) + "B";
-		if( v < 1024 * 1000 )
-			return (Math.round(v * 10 / 1024) / 10)+"KB";
-		return (Math.round(v * 10 / (1024 * 1024)) / 10)+"MB";
-	}
-
-	function loadMemory( arg : String ) {
-		memFile = arg;
-		memoryDump = sys.io.File.read(arg);
-		if( memoryDump.read(3).toString() != "HMD" )
-			throw "Invalid memory dump file";
-
-		var version = memoryDump.readByte() - "0".code;
-
-		if( version != 1 )
-			throw "Unsupported format version "+version;
-
-		var flags = readInt();
-		is64 = (flags & 1) != 0;
-		bool32 = (flags & 2) != 0;
-		ptrBits = is64 ? 3 : 2;
-		var ptrSize = 1 << ptrBits;
-
-		privateData = readInt();
-		markData = readInt();
-
-		// load pages
-		var count = readInt();
-		pages = [];
-		blocks = [];
-		for( i in 0...count ) {
-			var addr = readPointer();
-			var p = new Page();
-			p.addr = addr;
-			p.kind = cast readInt();
-			p.size = readInt();
-			p.reserved = readInt();
-
-			var readPtr = !p.memHasPtr();
-			while( true ) {
-				var ptr = readPointer();
-				if( ptr.isNull() ) break;
-				var size = readInt();
-				var b = new Block();
-				b.page = p;
-				b.addr = ptr;
-				b.size = size;
-				if( readPtr && size >= ptrSize ) b.typePtr = readPointer();
-				blocks.push(b);
-			}
-
-			if( p.memHasPtr() ) {
-				p.dataPosition = memoryDump.tell();
-				memoryDump.seek(p.size, SeekCur);
-			}
-			pages.push(p);
-		}
-
-		// load roots
-		roots = [for( i in 0...readInt() ) readPointer()];
-
-		// load stacks
-		stacks = [];
-		for( i in 0...readInt() ) {
-			var s = new Stack();
-			s.base = readPointer();
-			s.contents = [for( i in 0...readInt() ) readPointer()];
-			stacks.push(s);
-		}
-
-		// load types
-
-		baseTypes = [];
-		while( true ) {
-			var tid = readInt();
-			if( tid < 0 ) break;
-			var ptr = readPointer();
-			baseTypes.push({ t : Type.createEnumIndex(HLType, tid), p : ptr });
-		}
-
-		typesPointers = [for( i in 0...readInt() ) readPointer()];
-		closuresPointers = [for( i in 0...readInt() ) readPointer()];
-	}
-
-	function printStats() {
-		var pagesSize = 0, reserved = 0;
-		var used = 0, gc = 0;
-		var fUsed = 0;
-		for( p in pages ) {
-			pagesSize += p.size;
-			reserved += p.reserved;
-		}
-		for( b in blocks )
-			used += b.size;
-		for (b in filteredBlocks)
-			fUsed += b.size;
-		log(withColor("--- " + memFile + " ---", 36));
-		log(pages.length + " pages, " + MB(pagesSize) + " memory");
-		log(roots.length + " roots, "+ stacks.length + " stacks");
-		log(code.types.length + " types, " + closuresPointers.length + " closures");
-		log(blocks.length + " live blocks " + MB(used) + " used, " + MB(pagesSize - used - reserved) + " free, "+MB(privateData + markData)+" gc");
-		if (filterMode != None)
-			log(filteredBlocks.length + " blocks in filter " + MB(fUsed) + " used");
-	}
-
-	function getTypeNull( t : TType ) {
-		if( t.nullWrap != null )
-			return t.nullWrap;
-		for( t2 in types )
-			switch( t2.t ) {
-			case HNull(base) if( base == t.t ):
-				t.nullWrap = t2;
-				return t2;
-			default:
-			}
-		var r = new TType(types.length, HNull(t.t));
-		t.nullWrap = r;
-		types.push(r);
-		return r;
-	}
-
-	function goto( b : Block ) {
-		var p = b.page.dataPosition;
-		if( p < 0 ) throw "assert";
-		memoryDump.seek(p + b.addr.sub(b.page.addr), SeekBegin);
-	}
-
-	function check() {
-		if( code == null ) throw "Missing .hl file";
-		if( memoryDump == null ) throw "Missing .dump file";
-		if( code.types.length != this.typesPointers.length ) throw "Types count mismatch";
-
-		pointerType = new PointerMap();
-		var cid = 0;
-		types = [for( i in 0...code.types.length ) new TType(i, code.types[i])];
-		for( i in 0...typesPointers.length ) {
-			pointerType.set(typesPointers[i], types[i]);
-			switch( code.types[i] ) {
-			case HFun(f):
-				var tid = types.length;
-				var args = f.args.copy();
-				var clparam = args.shift();
-				if( clparam == null ) {
-					cid++;
-					continue;
-				}
-				switch( clparam ) {
-				case HEnum(p) if( p.name == "" ):
-					p.name = '<closure$i context>';
-				default:
-				}
-				var ct = new TType(tid, HFun({ args : args, ret : f.ret }), clparam);
-				types.push(ct);
-				var pt = closuresPointers[cid++];
-				if( !pt.isNull() )
-					pointerType.set(pt, ct);
-			case HObj(o), HStruct(o):
-				if( o.tsuper != null ) {
-					var found = false;
-					for( j in 0...types.length )
-						if( types[j].t == o.tsuper ) {
-							types[i].parentClass = types[j];
-							found = true;
-							break;
-						}
-					if( !found ) throw "Missing parent class";
-				}
-			default:
-			}
-		}
-
-		for( b in baseTypes ) {
-			var t = getType(b.t, true);
-			if( t == null ) {
-				t = new TType(types.length, b.t);
-				types.push(t);
-			}
-			pointerType.set(b.p, t);
-		}
-
-		var progress = 0;
-		pointerBlock = new PointerMap();
-
-		var missingTypes = 0;
-		for( b in blocks ) {
-			progress++;
-			if( displayProgress && progress % 1000 == 0 )
-				Sys.print((Std.int((progress / blocks.length) * 1000.0) / 10) + "%  \r");
-			if( b.page.kind == PDynamic ) {
-				goto(b);
-				b.typePtr = readPointer();
-			}
-			b.type = pointerType.get(b.typePtr);
-			if( b.page.kind == PDynamic && b.type == null && b.typePtr != Pointer.NULL )
-				missingTypes++; // types that we don't have in our dump
-			b.typePtr = Pointer.NULL;
-			if( b.type != null ) {
-				switch( b.page.kind ) {
-				case PDynamic:
-				case PNoPtr:
-					if( b.type.hasPtr ) {
-						if( b.type.t.match(HEnum(_)) ) {
-							// most likely one of the constructor without pointer parameter
-						} else
-							b.type = null; // false positive
-					}
-				case PRaw, PFinalizer:
-					if( b.type.isDyn )
-						b.type = null; // false positive
-				}
-			}
-			if( b.type != null && !b.type.isDyn )
-				b.type = getTypeNull(b.type);
-			if( b.type != null )
-				b.typeKind = KHeader;
-			pointerBlock.set(b.addr, b);
-		}
-
-		//Sys.println(missingTypes+" blocks with unresolved type");
-
-		printStats();
-
-		// look in roots (higher ownership priority)
-		all = new Block();
-
-		var broot = new Block();
-		broot.type = new TType(types.length, HAbstract("roots"));
-		types.push(broot.type);
-		broot.depth = 0;
-		broot.addParent(all);
-
-		for( r in roots ) {
-			var b = pointerBlock.get(r);
-			if( b == null ) continue;
-			b.addParent(broot);
-			if( b.type == null )
-				b.typeKind = KRoot;
-		}
-
-		var tinvalid = new TType(types.length, HAbstract("invalid"));
-		types.push(tinvalid);
-		for( t in types )
-			t.buildTypes(this, tinvalid);
-
-		var tunknown = new TType(types.length, HAbstract("unknown"));
-		types.push(tunknown);
-
-		tdynObj = getType(HDynObj);
-		tdynObjData = new TType(types.length, HAbstract("dynobjdata"));
-		types.push(tdynObjData);
-
-		toProcess = blocks.copy();
-		falseCandidates = [];
-		while( toProcess.length > 0 )
-			buildHierarchy();
-
-		// look in stacks (low priority of ownership)
-		var tstacks = new TType(types.length, HAbstract("stack"));
-		var bstacks = [];
-		types.push(tstacks);
-		for( s in stacks ) {
-			var bstack = new Block();
-			bstack.depth = 10000;
-			bstack.type = tstacks;
-			bstack.addParent(all);
-			bstacks.push(bstack);
-			for( r in s.contents ) {
-				var b = pointerBlock.get(r);
-				if( b != null )
-					b.addParent(bstack);
-			}
-		}
-
-		for( f in falseCandidates )
-			if( f.f.owner == null ) {
-				f.f.addParent(f.b);
-				f.b.type.falsePositive++;
-				f.b.type.falsePositiveIndexes[f.idx]++;
-			}
-
-		// precompute Arrays (no NativeArray intermediate)
-		function shortCircuit( native, haxe ) {
-			var tnat = resolveType(native, false);
-			var tarr = resolveType(haxe, false);
-			if( tnat == null || tarr == null ) return;
-			for( b in blocks ) {
-				if( b.type == tnat && b.owner != null && b.owner.type == tarr && b.subs != null ) {
-					for( s in b.subs )
-						s.b.addParent(b.owner);
-				}
-			}
-		}
-		shortCircuit("hl.NativeArray","Array<T>");
-		// disable for now, this generates unknowns and "Void" links
-		//shortCircuit("hl_bytes_map","Map<String,Dynamic>");
-		//shortCircuit("hl_int_map","Map<Int,Dynamic>");
-		//shortCircuit("hl_obj_map","Map<{},Dynamic>");
-
-		// assign depths
-
-		Sys.println("Computing depths...");
-		broot.markDepth();
-		for( b in bstacks ) b.markDepth();
-
-		var changed = -1;
-		while( changed != 0 ) {
-			changed = 0;
-			for( b in blocks ) {
-				var minD = -1;
-				if( b.parents == null ) {
-					if( b.owner != null && b.owner.depth >= 0 )
-						minD = b.owner.depth;
-				} else {
-					for( p in b.parents )
-						if( p.depth >= 0 && (minD < 0 || p.depth < minD) )
-							minD = p.depth;
-				}
-				if( minD >= 0 ) {
-					minD++;
-					if( b.depth < 0 || b.depth > minD ) {
-						b.depth = minD;
-						changed++;
-					}
-				}
-			}
-		}
-
-		for( b in blocks )
-			b.finalize();
-
-		var unk = 0, unkMem = 0, unRef = 0;
-		for( b in blocks ) {
-			if( b.owner == null ) {
-				unRef++;
-				if( unRef < 100 )
-					log("  "+b.addr.toString()+"["+b.size+"] is not referenced");
-				continue;
-			}
-
-			if( b.type != null )
-				continue;
-
-			var o = b.owner;
-			while( o != null && o.type == null )
-				o = o.owner;
-			if( o != null )
-				switch( o.type.t ) {
-				case HAbstract(_):
-					b.type = o.type; // data inside this
-					b.typeKind = KAbstractData;
-					continue;
-				default:
-				}
-
-			b.type = tunknown;
-		}
-
-		var falseCount = 0;
-		for( t in types )
-			falseCount += t.falsePositive;
-
-		log("Hierarchy built, "+falseCount+" false positives, "+unRef+" unreferenced");
-	}
-
-	function printFalsePositives( ?typeStr : String ) {
-		var falses = [for( t in types ) if( t.falsePositive > 0 && (typeStr == null || t.toString().indexOf(typeStr) >= 0) ) t];
-		falses.sort(function(t1, t2) return t1.falsePositive - t2.falsePositive);
-		for( f in falses )
-			log(f.falsePositive+" count " + f + " "+f.falsePositiveIndexes+"\n    "+[for( f in f.memFields ) f.t.toString()]);
-	}
-
-	function printUnknown() {
-		var byT = new Map();
-		for( b in blocks ) {
-			if( b.type != null ) continue;
-
-			var o = b;
-			while( o != null && o.type == null )
-				o = o.owner;
-
-			var t = o == null ? null : o.type;
-			var tid = t == null ? -1 : t.tid;
-			var inf = byT.get(tid);
-			if( inf == null ) {
-				inf = { t : t, count : 0, mem : 0 };
-				byT.set(tid, inf);
-			}
-			inf.count++;
-			inf.mem += b.size;
-		}
-		var all = [for( k in byT ) k];
-		all.sort(function(i1, i2) return i1.count - i2.count);
-		for( a in all )
-			log("Unknown "+a.count + " count, " + MB(a.mem)+" "+(a.t == null ? "" : a.t.toString()));
-	}
-
-	function buildHierarchy() {
-		var progress = 0;
-		var blocks = toProcess;
-		toProcess = [];
-
-		for( b in blocks )
-			b.removeChildren();
-
-		for( b in blocks ) {
-			progress++;
-			if( displayProgress && progress % 10000 == 0 )
-				Sys.print((Std.int(progress * 1000.0 / blocks.length) / 10) + "%  \r");
-
-			if( !b.page.memHasPtr() )
-				continue;
-
-			if( b.type != null && !b.type.hasPtr )
-				log("  Scanning "+b.type+" "+b.addr.toString());
-
-			goto(b);
-			var fields = null;
-			var start = 0;
-			var ptrTags = null;
-			var hasFieldNames = false;
-			if( b.type != null ) {
-				hasFieldNames = b.type.memFieldsNames != null;
-				fields = b.type.memFields;
-				ptrTags = b.type.ptrTags;
-				// enum
-				if( b.type.constructs != null ) {
-					readPointer(); // type
-					var index = readInt();
-					fields = b.type.constructs[index];
-					if( is64 ) readInt(); // skip, not a pointer anyway
-					start += 2;
-				}
-			}
-
-			for( i in start...(b.size >> ptrBits) ) {
-				var r = readPointer();
-
-				var bs = pointerBlock.get(r);
-				if( bs == null ) continue;
-				var ft = fields != null ? fields[i] : null;
-
-				if( ptrTags != null && ((ptrTags.get(i >> 3) >>> (i & 7)) & 1) == 0 && !b.type.t.match(HVirtual(_)) )
-					continue;
-
-				if( b.type == tdynObj && (i == 1 || i == 2 || i == 3) ) {
-					if( bs.typeKind != KHeader && (bs.typeKind != null || bs.type != null) )
-						trace(bs.typeKind, bs.type);
-					else {
-						bs.type = tdynObjData;
-						bs.typeKind = KDynObjData;
-					}
-				}
-
-				if( ft != null && !ft.t.isPtr() ) {
-					falseCandidates.push({ b : b, f:bs, idx : i });
-					continue;
-				}
-				bs.addParent(b,hasFieldNames ? (i+1) : 0);
-
-				if( bs.type == null && ft != null ) {
-					if( ft.t.match(HDyn | HObj(_)) ) {
-						// we can't infer with a polymorph type
-						continue;
-					}
-					bs.type = ft;
-					bs.typeKind = KInferred(b.type, b.typeKind);
-					if( bs.subs != null )
-						toProcess.push(bs);
-				}
-			}
-		}
-	}
-
-	function printByType() {
-		var ctx = new Stats(this);
-		for( b in filteredBlocks )
-			ctx.add(b.type, b.size);
-		ctx.print();
-	}
-
-	function resolveType( str, showError = true ) {
-		var t = resolveCache.get(str);
-		if( t != null )
-			return t;
-		for( i in currentTypeIndex...types.length ) {
-			var t = types[i];
-			var tstr = t.toString();
-			if (tstr != null) {
-				resolveCache.set(tstr, t);
-				currentTypeIndex = i + 1;
-				if( tstr == str )
-					return t;
-			}
-		}
-		if( showError )
-			log("Type not found '"+str+"'");
-		return null;
-	}
-
-	function locate( tstr : String, up = 0 ) {
-		var lt = resolveType(tstr);
-		if( lt == null ) return;
-
-		inline function isVirtualField(t) { t >>>= 24; return t == 1 || t == 2; }
-
-		var ctx = new Stats(this);
-		for( b in filteredBlocks )
-			if( b.type != null && b.type.match(lt) ) {
-				var tl = [];
-				var owner = b.owner;
-				// skip first virtual field
-				if( lt.t != HDynObj && owner != null && owner.type != null && owner.type.t.match(HVirtual(_)) && isVirtualField(owner.makeTID(b,true)) )
-					owner = owner.owner;
-
-				if( owner != null ) {
-					tl.push(owner.makeTID(b,displayFields == Full));
-					var k : Int = up;
-					while( owner.owner != null && k-- > 0 && owner.owner != all ) {
-						var tag = owner.owner.makeTID(owner,displayFields != None);
-						owner = owner.owner;
-						// remove recursive sequence
-						for( i => tag2 in tl )
-							if( tag2 == tag ) {
-								var seq = true;
-								for( n in 0...i ) {
-									if( tl[n] != tl[i+1+n] )
-										seq = false;
-								}
-								if( seq ) {
-									for( k in 0...i ) tl.shift();
-									tag = -1;
-									k += i + 1;
-								}
-								break;
-							}
-						// don't display virtual wrappers
-						if( displayFields != None && owner.type != null && isVirtualField(tag) && owner.type.t.match(HVirtual(_)) ) {
-							tag = -1;
-							k++;
-						}
-						if( tag != -1 )
-							tl.unshift(tag);
-					}
-				}
-				ctx.addPath(tl, b.size);
-			}
-		ctx.print();
-	}
-
-	function count( tstr : String, excludes : Array<String> ) {
-		var t = resolveType(tstr);
-		if( t == null ) return;
-		var texclude = [];
-		for( e in excludes ) {
-			var t = resolveType(e);
-			if( t == null ) return;
-			texclude.push(t);
-		}
-		var ctx = new Stats(this);
-		Block.MARK_UID++;
-		var mark = [];
-		for( b in filteredBlocks )
-			if( b.type == t )
-				visitRec(b,ctx,[],mark);
-		while( mark.length > 0 ) {
-			var b = mark.pop();
-			for( s in b.subs )
-				visitRec(s.b,ctx,texclude,mark);
-		}
-		ctx.print(true);
-	}
-
-	function visitRec( b : Block, ctx : Stats, exclude : Array<TType>, mark : Array<Block> ) {
-		if( b.mark == Block.MARK_UID ) return;
-		b.mark = Block.MARK_UID;
-		if( b.type != null ) for( t in exclude ) if( b.type.match(t) ) return;
-		ctx.addPath(b.type == null ? [] : [b.type.tid],b.size);
-		if( b.subs != null )
-			mark.push(b);
-	}
-
-	function parents( tstr : String, up = 0 ) {
-		var lt = null;
-		for( t in types )
-			if( t.t.toString() == tstr ) {
-				lt = t;
-				break;
-			}
-		if( lt == null ) {
-			log("Type not found");
-			return;
-		}
-
-		var ctx = new Stats(this);
-		for( b in filteredBlocks )
-			if( b.type == lt )
-				for( b in b.getParents() )
-					ctx.addPath([if( b.type == null ) 0 else b.type.tid], 0);
-		ctx.print();
-	}
-
-	function subs( tstr : String, down = 0 ) {
-		var lt = null;
-		for( t in types )
-			if( t.t.toString() == tstr ) {
-				lt = t;
-				break;
-			}
-		if( lt == null ) {
-			log("Type not found");
-			return;
-		}
-
-		var ctx = new Stats(this);
-		var mark = new Map();
-		for( b in filteredBlocks )
-			if( b.type == lt ) {
-				function addRec(tl:Array<Int>,b:Block, k:Int) {
-					if( k < 0 ) return;
-					if( mark.exists(b) )
-						return;
-					mark.set(b, true);
-					tl.push(b.type == null ? 0 : b.type.tid);
-					ctx.addPath(tl, b.size);
-					if( b.subs != null ) {
-							k--;
-						for( s in b.subs )
-							addRec(tl.copy(),s.b, k);
-					}
-				}
-				addRec([], b, down);
-			}
-		ctx.print();
-	}
-
-	public function setFilterMode(m: FilterMode) {
-		filterMode = m;
-		switch( m ) {
-		case None:
-			filteredBlocks = blocks.copy();
-		default:
-			filteredBlocks = [];
-			var progress = 0;
-			for( b in blocks ) {
-				progress++;
-				if( displayProgress && progress % 1000 == 0 )
-					Sys.print((Std.int((progress / blocks.length) * 1000.0) / 10) + "%  \r");
-				if( !isBlockIgnored(b, m) )
-					filteredBlocks.push(b);
-			}
-			if( displayProgress )
-				Sys.print("       \r");
-		}
-	}
-	public function isBlockIgnored(b: Block, m: FilterMode) {
-		switch( m ) {
-		case None:
-			return false;
-		case Intersect:
-			for( m in otherMems ) {
-				if( m.pointerBlock.get(b.addr ) == null )
-					return true;
-			}
-		case Unique:
-			for( m in otherMems ) {
-				if( m.pointerBlock.get(b.addr ) != null )
-					return true;
-			}
-		}
-		return false;
-	}
-
-	public function log(msg:String) {
-		Sys.println(msg);
-	}
-
-	static function parseArgs(str: String) {
-		str = StringTools.trim(str);
-		var i = 0;
-		var tok = "";
-		var args = [];
-		var escape = false;
-		while(i != str.length) {
-			var c = str.charAt(i++);
-			if(c == '"') {
-				escape = !escape;
-			}
-			else {
-				if(c == " " && !escape) {
-					if(tok.length > 0) args.push(tok);
-					tok = "";
-				}
-				else
-					tok += c;
-			}
-		}
-		if(tok.length > 0) args.push(tok);
-		return args;
-	}
-
-	static var useColor = false;
-	static function main() {
-		var m = new Memory();
-		var others: Array<Memory> = [];
-		var filterMode: FilterMode = None;
-
-		//hl.Gc.dumpMemory(); Sys.command("cp memory.hl test.hl");
-
-		var code = null, memory = null;
-		var args = Sys.args();
-		while( args.length > 0 ) {
-			var arg = args.shift();
-			if( StringTools.endsWith(arg, ".hl") ) {
-				code = arg;
-				m.loadBytecode(arg);
-				continue;
-			}
-			if( arg == "-c" || arg == "--color" ) {
-				useColor = true;
-				continue;
-			}
-			if( arg == "--args" ) {
-				m.displayProgress = false;
-				break;
-			}
-			if (memory == null) {
-				memory = arg;
-				m.loadMemory(arg);
-			} else {
-				var m2 = new Memory();
-				m2.loadMemory(arg);
-				others.push(m2);
-			}
-		}
-		if( code != null && memory == null ) {
-			var dir = new haxe.io.Path(code).dir;
-			if( dir == null ) dir = ".";
-			memory = dir+"/hlmemory.dump";
-			if( sys.FileSystem.exists(memory) ) m.loadMemory(memory);
-		}
-
-		m.check();
-		for (m2 in others) {
-			m2.code = m.code;
-			m2.check();
-		}
-		m.otherMems = [for (i in others) i];
-		m.setFilterMode(filterMode);
-
-		var stdin = Sys.stdin();
-		while( true ) {
-			Sys.print(withColor("> ", 31));
-			var args = parseArgs(args.length > 0 ? args.shift() : stdin.readLine());
-			var cmd = args.shift();
-			switch( cmd ) {
-			case "exit", "quit", "q":
-				break;
-			case "types":
-				m.printByType();
-			case "stats":
-				m.printStats();
-			case "false":
-				m.printFalsePositives(args.shift());
-			case "unknown":
-				m.printUnknown();
-			case "locate":
-				m.locate(args.shift(), Std.parseInt(args.shift()));
-			case "count":
-				m.count(args.shift(), args);
-			case "parents":
-				m.parents(args.shift());
-			case "subs":
-				m.subs(args.shift(), Std.parseInt(args.shift()));
-			case "sort":
-				switch( args.shift() ) {
-				case "mem":
-					m.sortByCount = false;
-				case "count":
-					m.sortByCount = true;
-				case mode:
-					Sys.println("Unknown sort mode " + mode);
-				}
-			case "fields":
-				switch( args.shift() ) {
-				case "full":
-					m.displayFields = Full;
-				case "none":
-					m.displayFields = None;
-				case "parents":
-					m.displayFields = Parents;
-				case mode:
-					Sys.println("Unknown fields mode " + mode);
-				}
-			case "filter":
-				switch( args.shift() ) {
-				case "none":
-					filterMode = None;
-				case "intersect":
-					filterMode = Intersect;
-				case "unique":
-					filterMode = Unique;
-				case mode:
-					Sys.println("Unknown filter mode " + mode);
-				}
-				m.setFilterMode(filterMode);
-			case "nextDump":
-				others.push(m);
-				m = others.shift();
-				m.otherMems = [for (i in others) i];
-				m.setFilterMode(filterMode);
-				var ostr = others.length > 0 ? (" (others are " + others.map(m -> m.memFile) + ")") : "";
-				Sys.println("Using dump " + m.memFile + ostr);
-			case "lines":
-				var v = args.shift();
-				if( v != null )
-					m.maxLines = Std.parseInt(v);
-				Sys.println(m.maxLines == 0 ? "Lines limit disabled" : m.maxLines + " maximum lines displayed");
-			case null:
-				Sys.println("");
-			default:
-				Sys.println("Unknown command " + cmd);
-			}
-		}
-	}
-
-	// A list of ansi colors is available at
-	// https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797#8-16-colors
-	public static function withColor(str: String, ansiCol: Int) {
-		if (!useColor)
-			return str;
-		return "\x1B[" + ansiCol + "m" + str + "\x1B[0m";
-	}
+package hlmem;
+
+import hlmem.Block;
+import format.hl.Data;
+using format.hl.Tools;
+
+class Stats {
+	var mem : Memory;
+	var byT = new Map();
+	var allT = [];
+
+	public function new(mem) {
+		this.mem = mem;
+	}
+
+	public function add( t : TType, mem : Int ) {
+		return addPath([t == null ? 0 : t.tid], mem);
+	}
+
+	public inline function makeID( t : TType, field : Int ) {
+		return t.tid | (field << 24);
+	}
+
+	public function addPath( tl : Array<Int>, mem : Int ) {
+		var key = tl.join(" ");
+		var inf = byT.get(key);
+		if( inf == null ) {
+			inf = { tl : tl, count : 0, mem : 0 };
+			byT.set(key, inf);
+			allT.push(inf);
+		}
+		inf.count++;
+		inf.mem += mem;
+	}
+
+	public function print( withSum = false ) {
+		sort( @:privateAccess mem.sortByCount );
+		var totCount = 0;
+		var totMem = 0;
+		var max = @:privateAccess mem.maxLines;
+		if( max > 0 && allT.length > max ) {
+			mem.log("<ignore "+(allT.length - max)+" lines>");
+			allT = allT.slice(allT.length - max);
+		}
+		for( i in allT ) {
+			totCount += i.count;
+			totMem += i.mem;
+			var tpath = getPathStrings(mem, i.tl);
+			mem.log(Memory.withColor(i.count + " count, " + Memory.MB(i.mem) + " ", 33) + tpath.join(${Memory.withColor(' > ', 36)}));
+		}
+		if( withSum )
+			mem.log("Total: "+totCount+" count, "+Memory.MB(totMem));
+	}
+
+	public static function getTypeString(mem : Memory, id : Int){
+		var t = mem.types[id & 0xFFFFFF];
+		var tstr = t.toString();
+		var fid = id >>> 24;
+		if( fid > 0 ) {
+			var f = t.memFieldsNames[fid-1];
+			if( f != null ) tstr += "." + f;
+		}
+		return tstr;
+	}
+
+	public static function getPathStrings(mem : Memory, i : Array<Int>){
+		var tpath = [];
+		for( tid in i )
+			tpath.push(getTypeString(mem, tid));
+
+		return tpath;
+	}
+
+	public function sort( byCount = true , asc = true) {
+		if( byCount )
+			allT.sort(function(i1, i2) return asc ? i1.count - i2.count : i2.count - i1.count);
+		else
+			allT.sort(function(i1, i2) return asc ? i1.mem - i2.mem : i2.mem - i1.mem);
+	}
+}
+
+enum FieldsMode {
+	Full;
+	Parents;
+	None;
+}
+
+enum FilterMode {
+	None;
+	Intersect;	// Will display only blocks present in all memories
+	Unique;		// Will display only blocks not present in other memories
+}
+
+class Memory {
+	public var memoryDump : sys.io.FileInput;
+
+	#if js
+	var memBytes : haxe.io.Bytes;
+	var memPos : Int;
+	#end
+
+	public var is64 : Bool;
+	public var bool32 : Bool;
+	public var ptrBits : Int;
+
+	public var types : Array<TType>;
+
+	var otherMems : Array<Memory>;
+	var filterMode: FilterMode = None;
+
+	var memFile : String;
+
+	var privateData : Int;
+	var markData : Int;
+
+	var sortByCount : Bool;
+	var displayFields : FieldsMode = Full;
+	var displayProgress = true;
+
+	var code : format.hl.Data;
+	var pages : Array<Page>;
+	var roots : Array<Pointer>;
+	var stacks : Array<Stack>;
+	var typesPointers : Array<Pointer>;
+	var closuresPointers : Array<Pointer>;
+	var blocks : Array<Block>;
+	var filteredBlocks : Array<Block> = [];
+	var baseTypes : Array<{ t : HLType, p : Pointer }>;
+	var all : Block;
+
+	var toProcess : Array<Block>;
+	var tdynObj : TType;
+	var tdynObjData : TType;
+	var pointerBlock : PointerMap<Block>;
+	var pointerType : PointerMap<TType>;
+	var falseCandidates : Array<{ b : Block, f : Block, idx : Int }>;
+
+	var currentTypeIndex = 0;
+	var resolveCache : Map<String,TType> = new Map();
+	var maxLines : Int = 100;
+
+	function new() {
+	}
+
+	public function typeSize( t : HLType ) {
+		return switch( t ) {
+		case HVoid: 0;
+		case HUi8: 1;
+		case HUi16: 2;
+		case HI32, HF32: 4;
+		case HI64, HF64: 8;
+		case HBool:
+			return bool32 ? 4 : 1;
+		case HPacked(_), HAt(_):
+			throw "assert";
+		default:
+			return is64 ? 8 : 4;
+		}
+	}
+
+	public function getType( t : HLType, isNull = false ) : TType {
+		// this is quite slow, but we can't use a Map, maybe try a more per-type specific approach ?
+		for( t2 in types )
+			if( t == t2.t )
+				return t2;
+		if( isNull )
+			return null;
+		throw "Type not found " + t.toString();
+		return null;
+	}
+
+	function loadBytecode( arg : String ) {
+		if( code != null ) throw "Duplicate code";
+		code = new format.hl.Reader(false).read(new haxe.io.BytesInput(sys.io.File.getBytes(arg)));
+		log(arg + " code loaded");
+	}
+
+
+	inline function readInt() {
+		#if js
+		var ch1 = memBytes.get(memPos);
+		var ch2 = memBytes.get(memPos + 1);
+		var ch3 = memBytes.get(memPos + 2);
+		var ch4 = memBytes.get(memPos + 3);
+		memPos += 4;
+		return memoryDump.bigEndian ? ch4 | (ch3 << 8) | (ch2 << 16) | (ch1 << 24) : ch1 | (ch2 << 8) | (ch3 << 16) | (ch4 << 24);
+		#else
+		return memoryDump.readInt32();
+		#end
+	}
+
+	inline function readPointer() : Pointer {
+		#if js
+		var low = readInt();
+		var high = is64 ? readInt() : 0;
+		#else
+		var low = memoryDump.readInt32();
+		var high = is64 ? memoryDump.readInt32() : 0;
+		#end
+
+		return cast haxe.Int64.make(high,low);
+	}
+
+	public static function MB( v : Float ) {
+		if( v < 1000 )
+			return Std.int(v) + "B";
+		if( v < 1024 * 1000 )
+			return (Math.round(v * 10 / 1024) / 10)+"KB";
+		return (Math.round(v * 10 / (1024 * 1024)) / 10)+"MB";
+	}
+
+	function loadMemory( arg : String ) {
+		memFile = arg;
+		memoryDump = sys.io.File.read(arg);
+
+		var version = 0;
+
+		#if js
+		memBytes = sys.io.File.getBytes(arg);
+		memPos = 0;
+
+		if( memBytes.getString(memPos, 3) != "HMD" )
+			throw "Invalid memory dump file";
+
+		memPos += 3;
+
+		version = memBytes.get(memPos) - "0".code;
+		memPos += 1;
+
+		#else
+		if( memoryDump.read(3).toString() != "HMD" )
+			throw "Invalid memory dump file";
+
+		version = memoryDump.readByte() - "0".code;
+		#end
+
+		if( version != 1 )
+			throw "Unsupported format version "+version;
+
+		var flags = readInt();
+		is64 = (flags & 1) != 0;
+		bool32 = (flags & 2) != 0;
+		ptrBits = is64 ? 3 : 2;
+		var ptrSize = 1 << ptrBits;
+
+		privateData = readInt();
+		markData = readInt();
+
+		// load pages
+		var count = readInt();
+		pages = [];
+		blocks = [];
+		for( i in 0...count ) {
+			var addr = readPointer();
+			var p = new Page();
+			p.addr = addr;
+			p.kind = cast readInt();
+			p.size = readInt();
+			p.reserved = readInt();
+
+			var readPtr = !p.memHasPtr();
+			while( true ) {
+				var ptr = readPointer();
+				if( ptr.isNull() ) break;
+				var size = readInt();
+				var b = new Block();
+				b.page = p;
+				b.addr = ptr;
+				b.size = size;
+				b.typePtr = @:privateAccess new Pointer(0);
+				b.owner = null;
+				b.typeKind = null;
+				b.subs = null;
+				b.parents = null;
+
+				if( readPtr && size >= ptrSize ) b.typePtr = readPointer();
+				blocks.push(b);
+			}
+
+			if( p.memHasPtr() ) {
+				#if js
+				p.dataPosition = memPos;
+				memPos += p.size;
+				#else
+				p.dataPosition = memoryDump.tell();
+				memoryDump.seek(p.size, SeekCur);
+				#end
+			}
+			pages.push(p);
+		}
+
+		// load roots
+		roots = [for( i in 0...readInt() ) readPointer()];
+
+		// load stacks
+		stacks = [];
+		for( i in 0...readInt() ) {
+			var s = new Stack();
+			s.base = readPointer();
+			s.contents = [for( i in 0...readInt() ) readPointer()];
+			stacks.push(s);
+		}
+
+		// load types
+
+		baseTypes = [];
+		while( true ) {
+			var tid = readInt();
+			if( tid < 0 ) break;
+			var ptr = readPointer();
+			baseTypes.push({ t : Type.createEnumIndex(HLType, tid), p : ptr });
+		}
+
+		typesPointers = [for( i in 0...readInt() ) readPointer()];
+		closuresPointers = [for( i in 0...readInt() ) readPointer()];
+	}
+
+	public function getStats() {
+		var pagesSize = 0, reserved = 0;
+		var used = 0, gc = 0;
+		var fUsed = 0;
+		for( p in pages ) {
+			pagesSize += p.size;
+			reserved += p.reserved;
+		}
+		for( b in blocks )
+			used += b.size;
+		for (b in filteredBlocks)
+			fUsed += b.size;
+
+		var objs = [];
+		var obj = {
+			memFile : memFile,
+			free: pagesSize - used - reserved,
+			used: used,
+			filterUsed: fUsed,
+			totalAllocated: pagesSize - reserved,
+			gc : privateData + markData,
+			pagesCount : pages.length,
+			pagesSize : pagesSize,
+			rootsCount : roots.length,
+			stackCount : stacks.length,
+			typesCount : code.types.length,
+			closuresCount : closuresPointers.length,
+			blockCount : blocks.length
+		};
+
+		objs.push(obj);
+		for (m in otherMems??[]) objs = objs.concat(m.getStats());
+
+		return objs;
+	}
+
+	function printStats() {
+		var objs = getStats();
+
+		for (obj in objs) {
+			log(withColor("--- " + obj.memFile + " ---", 36));
+			log(obj.pagesCount + " pages, " + MB(obj.pagesSize) + " memory");
+			log(obj.rootsCount + " roots, "+ obj.stackCount + " stacks");
+			log(obj.typesCount + " types, " + obj.closuresCount + " closures");
+			log(obj.blockCount + " live blocks " + MB(obj.used) + " used, " + MB(obj.free) + " free, "+MB(obj.gc)+" gc");
+			if (filterMode != None)
+				log(filteredBlocks.length + " blocks in filter " + MB(obj.filterUsed) + " used");
+		}
+	}
+
+	function getTypeNull( t : TType ) {
+		if( t.nullWrap != null )
+			return t.nullWrap;
+		for( t2 in types )
+			switch( t2.t ) {
+			case HNull(base) if( base == t.t ):
+				t.nullWrap = t2;
+				return t2;
+			default:
+			}
+		var r = new TType(types.length, HNull(t.t));
+		t.nullWrap = r;
+		types.push(r);
+		return r;
+	}
+
+	function goto( b : Block ) {
+		var p = b.page.dataPosition;
+		if( p < 0 ) throw "assert";
+
+		#if js
+		memPos = p + b.addr.sub(b.page.addr);
+		#else
+		memoryDump.seek(p + b.addr.sub(b.page.addr), SeekBegin);
+		#end
+	}
+
+	function check() {
+		if( code == null ) throw "Missing .hl file";
+		if( memoryDump == null ) throw "Missing .dump file";
+		if( code.types.length != this.typesPointers.length ) throw "Types count mismatch";
+
+		pointerType = new PointerMap();
+		var cid = 0;
+		types = [for( i in 0...code.types.length ) new TType(i, code.types[i])];
+		for( i in 0...typesPointers.length ) {
+			pointerType.set(typesPointers[i], types[i]);
+			switch( code.types[i] ) {
+			case HFun(f):
+				var tid = types.length;
+				var args = f.args.copy();
+				var clparam = args.shift();
+				if( clparam == null ) {
+					cid++;
+					continue;
+				}
+				switch( clparam ) {
+				case HEnum(p) if( p.name == "" ):
+					p.name = '<closure$i context>';
+				default:
+				}
+				var ct = new TType(tid, HFun({ args : args, ret : f.ret }), clparam);
+				types.push(ct);
+				var pt = closuresPointers[cid++];
+				if( !pt.isNull() )
+					pointerType.set(pt, ct);
+			case HObj(o), HStruct(o):
+				if( o.tsuper != null ) {
+					var found = false;
+					for( j in 0...types.length )
+						if( types[j].t == o.tsuper ) {
+							types[i].parentClass = types[j];
+							found = true;
+							break;
+						}
+					if( !found ) throw "Missing parent class";
+				}
+			default:
+			}
+		}
+
+		for( b in baseTypes ) {
+			var t = getType(b.t, true);
+			if( t == null ) {
+				t = new TType(types.length, b.t);
+				types.push(t);
+			}
+			pointerType.set(b.p, t);
+		}
+
+		var progress = 0;
+		pointerBlock = new PointerMap();
+
+		var missingTypes = 0;
+		for( b in blocks ) {
+			progress++;
+			if( displayProgress && progress % 1000 == 0 )
+				Sys.print((Std.int((progress / blocks.length) * 1000.0) / 10) + "%  \r");
+			if( b.page.kind == PDynamic ) {
+				goto(b);
+				b.typePtr = readPointer();
+			}
+			b.type = pointerType.get(b.typePtr);
+			if( b.page.kind == PDynamic && b.type == null && b.typePtr != Pointer.NULL )
+				missingTypes++; // types that we don't have in our dump
+			b.typePtr = Pointer.NULL;
+			if( b.type != null ) {
+				switch( b.page.kind ) {
+				case PDynamic:
+				case PNoPtr:
+					if( b.type.hasPtr ) {
+						if( b.type.t.match(HEnum(_)) ) {
+							// most likely one of the constructor without pointer parameter
+						} else
+							b.type = null; // false positive
+					}
+				case PRaw, PFinalizer:
+					if( b.type.isDyn )
+						b.type = null; // false positive
+				}
+			}
+			if( b.type != null && !b.type.isDyn )
+				b.type = getTypeNull(b.type);
+			if( b.type != null )
+				b.typeKind = KHeader;
+			pointerBlock.set(b.addr, b);
+		}
+
+		//Sys.println(missingTypes+" blocks with unresolved type");
+
+		printStats();
+
+		// look in roots (higher ownership priority)
+		all = new Block();
+
+		var broot = new Block();
+		broot.type = new TType(types.length, HAbstract("roots"));
+		types.push(broot.type);
+		broot.depth = 0;
+		broot.addParent(all);
+
+		for( r in roots ) {
+			var b = pointerBlock.get(r);
+			if( b == null ) continue;
+			b.addParent(broot);
+			if( b.type == null )
+				b.typeKind = KRoot;
+		}
+
+		var tinvalid = new TType(types.length, HAbstract("invalid"));
+		types.push(tinvalid);
+		for( t in types )
+			t.buildTypes(this, tinvalid);
+
+		var tunknown = new TType(types.length, HAbstract("unknown"));
+		types.push(tunknown);
+
+		tdynObj = getType(HDynObj);
+		tdynObjData = new TType(types.length, HAbstract("dynobjdata"));
+		types.push(tdynObjData);
+
+		toProcess = blocks.copy();
+		falseCandidates = [];
+		while( toProcess.length > 0 )
+			buildHierarchy();
+
+		// look in stacks (low priority of ownership)
+		var tstacks = new TType(types.length, HAbstract("stack"));
+		var bstacks = [];
+		types.push(tstacks);
+		for( s in stacks ) {
+			var bstack = new Block();
+			bstack.depth = 10000;
+			bstack.type = tstacks;
+			bstack.addParent(all);
+			bstacks.push(bstack);
+			for( r in s.contents ) {
+				var b = pointerBlock.get(r);
+				if( b != null )
+					b.addParent(bstack);
+			}
+		}
+
+		for( f in falseCandidates )
+			if( f.f.owner == null ) {
+				f.f.addParent(f.b);
+				f.b.type.falsePositive++;
+				f.b.type.falsePositiveIndexes[f.idx]++;
+			}
+
+		// precompute Arrays (no NativeArray intermediate)
+		function shortCircuit( native, haxe ) {
+			var tnat = resolveType(native, false);
+			var tarr = resolveType(haxe, false);
+			if( tnat == null || tarr == null ) return;
+			for( b in blocks ) {
+				if( b.type == tnat && b.owner != null && b.owner.type == tarr && b.subs != null ) {
+					for( s in b.subs )
+						s.b.addParent(b.owner);
+				}
+			}
+		}
+		shortCircuit("hl.NativeArray","Array<T>");
+		// disable for now, this generates unknowns and "Void" links
+		//shortCircuit("hl_bytes_map","Map<String,Dynamic>");
+		//shortCircuit("hl_int_map","Map<Int,Dynamic>");
+		//shortCircuit("hl_obj_map","Map<{},Dynamic>");
+
+		// assign depths
+
+		Sys.println("Computing depths...");
+		broot.markDepth();
+		for( b in bstacks ) b.markDepth();
+
+		var changed = -1;
+		while( changed != 0 ) {
+			changed = 0;
+			for( b in blocks ) {
+				var minD = -1;
+				if( b.parents == null ) {
+					if( b.owner != null && b.owner.depth >= 0 )
+						minD = b.owner.depth;
+				} else {
+					for( p in b.parents )
+						if( p.depth >= 0 && (minD < 0 || p.depth < minD) )
+							minD = p.depth;
+				}
+				if( minD >= 0 ) {
+					minD++;
+					if( b.depth < 0 || b.depth > minD ) {
+						b.depth = minD;
+						changed++;
+					}
+				}
+			}
+		}
+
+		for( b in blocks )
+			b.finalize();
+
+		var unk = 0, unkMem = 0, unRef = 0;
+		for( b in blocks ) {
+			if( b.owner == null ) {
+				unRef++;
+				if( unRef < 100 )
+					log("  "+b.addr.toString()+"["+b.size+"] is not referenced");
+				continue;
+			}
+
+			if( b.type != null )
+				continue;
+
+			var o = b.owner;
+			while( o != null && o.type == null )
+				o = o.owner;
+			if( o != null )
+				switch( o.type.t ) {
+				case HAbstract(_):
+					b.type = o.type; // data inside this
+					b.typeKind = KAbstractData;
+					continue;
+				default:
+				}
+
+			b.type = tunknown;
+		}
+
+		var falseCount = 0;
+		for( t in types )
+			falseCount += t.falsePositive;
+
+		log("Hierarchy built, "+falseCount+" false positives, "+unRef+" unreferenced");
+	}
+
+	function printFalsePositives( ?typeStr : String ) {
+		var falses = [for( t in types ) if( t.falsePositive > 0 && (typeStr == null || t.toString().indexOf(typeStr) >= 0) ) t];
+		falses.sort(function(t1, t2) return t1.falsePositive - t2.falsePositive);
+		for( f in falses )
+			log(f.falsePositive+" count " + f + " "+f.falsePositiveIndexes+"\n    "+[for( f in f.memFields ) f.t.toString()]);
+	}
+
+	function printUnknown() {
+		var byT = new Map();
+		for( b in blocks ) {
+			if( b.type != null ) continue;
+
+			var o = b;
+			while( o != null && o.type == null )
+				o = o.owner;
+
+			var t = o == null ? null : o.type;
+			var tid = t == null ? -1 : t.tid;
+			var inf = byT.get(tid);
+			if( inf == null ) {
+				inf = { t : t, count : 0, mem : 0 };
+				byT.set(tid, inf);
+			}
+			inf.count++;
+			inf.mem += b.size;
+		}
+		var all = [for( k in byT ) k];
+		all.sort(function(i1, i2) return i1.count - i2.count);
+		for( a in all )
+			log("Unknown "+a.count + " count, " + MB(a.mem)+" "+(a.t == null ? "" : a.t.toString()));
+	}
+
+	function buildHierarchy() {
+		var progress = 0;
+		var blocks = toProcess;
+		toProcess = [];
+
+		for( b in blocks )
+			b.removeChildren();
+
+		for( b in blocks ) {
+			progress++;
+			if( displayProgress && progress % 10000 == 0 )
+				Sys.print((Std.int(progress * 1000.0 / blocks.length) / 10) + "%  \r");
+
+			if( !b.page.memHasPtr() )
+				continue;
+
+			if( b.type != null && !b.type.hasPtr )
+				log("  Scanning "+b.type+" "+b.addr.toString());
+
+			goto(b);
+			var fields = null;
+			var start = 0;
+			var ptrTags = null;
+			var hasFieldNames = false;
+			if( b.type != null ) {
+				hasFieldNames = b.type.memFieldsNames != null;
+				fields = b.type.memFields;
+				ptrTags = b.type.ptrTags;
+				// enum
+				if( b.type.constructs != null ) {
+					readPointer(); // type
+					var index = readInt();
+					fields = b.type.constructs[index];
+					if( is64 ) readInt(); // skip, not a pointer anyway
+					start += 2;
+				}
+			}
+
+			for( i in start...(b.size >> ptrBits) ) {
+				var r = readPointer();
+
+				var bs = pointerBlock.get(r);
+				if( bs == null ) continue;
+				var ft = fields != null ? fields[i] : null;
+
+				if( ptrTags != null && ((ptrTags.get(i >> 3) >>> (i & 7)) & 1) == 0 && !b.type.t.match(HVirtual(_)) )
+					continue;
+
+				if( b.type == tdynObj && (i == 1 || i == 2 || i == 3) ) {
+					if( bs.typeKind != KHeader && (bs.typeKind != null || bs.type != null) )
+						trace(bs.typeKind, bs.type);
+					else {
+						bs.type = tdynObjData;
+						bs.typeKind = KDynObjData;
+					}
+				}
+
+				if( ft != null && !ft.t.isPtr() ) {
+					falseCandidates.push({ b : b, f:bs, idx : i });
+					continue;
+				}
+				bs.addParent(b,hasFieldNames ? (i+1) : 0);
+
+				if( bs.type == null && ft != null ) {
+					if( ft.t.match(HDyn | HObj(_)) ) {
+						// we can't infer with a polymorph type
+						continue;
+					}
+					bs.type = ft;
+					bs.typeKind = KInferred(b.type, b.typeKind);
+					if( bs.subs != null )
+						toProcess.push(bs);
+				}
+			}
+		}
+	}
+
+	function printByType() {
+		var ctx = new Stats(this);
+		for( b in filteredBlocks )
+			ctx.add(b.type, b.size);
+		ctx.print();
+	}
+
+	function resolveType( str, showError = true ) {
+		var t = resolveCache.get(str);
+		if( t != null )
+			return t;
+		for( i in currentTypeIndex...types.length ) {
+			var t = types[i];
+			var tstr = t.toString();
+			if (tstr != null) {
+				resolveCache.set(tstr, t);
+				currentTypeIndex = i + 1;
+				if( tstr == str )
+					return t;
+			}
+		}
+		if( showError )
+			log("Type not found '"+str+"'");
+		return null;
+	}
+
+	function locate( tstr : String, up = 0 ) {
+		var ctx = new Stats(this);
+
+		var lt = resolveType(tstr);
+		if( lt == null ) return ctx;
+
+		inline function isVirtualField(t) { t >>>= 24; return t == 1 || t == 2; }
+
+
+		for( b in filteredBlocks )
+			if( b.type != null && b.type.match(lt) ) {
+				var tl = [];
+				var owner = b.owner;
+				// skip first virtual field
+				if( lt.t != HDynObj && owner != null && owner.type != null && owner.type.t.match(HVirtual(_)) && isVirtualField(owner.makeTID(b,true)) )
+					owner = owner.owner;
+
+				if( owner != null ) {
+					tl.push(owner.makeTID(b,displayFields == Full));
+					var k : Int = up;
+					while( owner.owner != null && k-- > 0 && owner.owner != all ) {
+						var tag = owner.owner.makeTID(owner,displayFields != None);
+						owner = owner.owner;
+						// remove recursive sequence
+						for( i => tag2 in tl )
+							if( tag2 == tag ) {
+								var seq = true;
+								for( n in 0...i ) {
+									if( tl[n] != tl[i+1+n] )
+										seq = false;
+								}
+								if( seq ) {
+									for( k in 0...i ) tl.shift();
+									tag = -1;
+									k += i + 1;
+								}
+								break;
+							}
+						// don't display virtual wrappers
+						if( displayFields != None && owner.type != null && isVirtualField(tag) && owner.type.t.match(HVirtual(_)) ) {
+							tag = -1;
+							k++;
+						}
+						if( tag != -1 )
+							tl.unshift(tag);
+					}
+				}
+				ctx.addPath(tl, b.size);
+			}
+
+		ctx.print();
+		return ctx;
+	}
+
+	function count( tstr : String, excludes : Array<String> ) {
+		var t = resolveType(tstr);
+		if( t == null ) return;
+		var texclude = [];
+		for( e in excludes ) {
+			var t = resolveType(e);
+			if( t == null ) return;
+			texclude.push(t);
+		}
+		var ctx = new Stats(this);
+		Block.MARK_UID++;
+		var mark = [];
+		for( b in filteredBlocks )
+			if( b.type == t )
+				visitRec(b,ctx,[],mark);
+		while( mark.length > 0 ) {
+			var b = mark.pop();
+			for( s in b.subs )
+				visitRec(s.b,ctx,texclude,mark);
+		}
+		ctx.print(true);
+	}
+
+	function visitRec( b : Block, ctx : Stats, exclude : Array<TType>, mark : Array<Block> ) {
+		if( b.mark == Block.MARK_UID ) return;
+		b.mark = Block.MARK_UID;
+		if( b.type != null ) for( t in exclude ) if( b.type.match(t) ) return;
+		ctx.addPath(b.type == null ? [] : [b.type.tid],b.size);
+		if( b.subs != null )
+			mark.push(b);
+	}
+
+	function parents( tstr : String, up = 0 ) {
+		var lt = null;
+		for( t in types )
+			if( t.t.toString() == tstr ) {
+				lt = t;
+				break;
+			}
+		if( lt == null ) {
+			log("Type not found");
+			return;
+		}
+
+		var ctx = new Stats(this);
+		for( b in filteredBlocks )
+			if( b.type == lt )
+				for( b in b.getParents() )
+					ctx.addPath([if( b.type == null ) 0 else b.type.tid], 0);
+		ctx.print();
+	}
+
+	function subs( tstr : String, down = 0 ) {
+		var lt = null;
+		for( t in types )
+			if( t.t.toString() == tstr ) {
+				lt = t;
+				break;
+			}
+		if( lt == null ) {
+			log("Type not found");
+			return;
+		}
+
+		var ctx = new Stats(this);
+		var mark = new Map();
+		for( b in filteredBlocks )
+			if( b.type == lt ) {
+				function addRec(tl:Array<Int>,b:Block, k:Int) {
+					if( k < 0 ) return;
+					if( mark.exists(b) )
+						return;
+					mark.set(b, true);
+					tl.push(b.type == null ? 0 : b.type.tid);
+					ctx.addPath(tl, b.size);
+					if( b.subs != null ) {
+							k--;
+						for( s in b.subs )
+							addRec(tl.copy(),s.b, k);
+					}
+				}
+				addRec([], b, down);
+			}
+		ctx.print();
+	}
+
+	public function setFilterMode(m: FilterMode) {
+		filterMode = m;
+		switch( m ) {
+		case None:
+			filteredBlocks = blocks.copy();
+		default:
+			filteredBlocks = [];
+			var progress = 0;
+			for( b in blocks ) {
+				progress++;
+				if( displayProgress && progress % 1000 == 0 )
+					Sys.print((Std.int((progress / blocks.length) * 1000.0) / 10) + "%  \r");
+				if( !isBlockIgnored(b, m) )
+					filteredBlocks.push(b);
+			}
+			if( displayProgress )
+				Sys.print("       \r");
+		}
+	}
+	public function isBlockIgnored(b: Block, m: FilterMode) {
+		switch( m ) {
+		case None:
+			return false;
+		case Intersect:
+			for( m in otherMems ) {
+				if( m.pointerBlock.get(b.addr ) == null )
+					return true;
+			}
+		case Unique:
+			for( m in otherMems ) {
+				if( m.pointerBlock.get(b.addr ) != null )
+					return true;
+			}
+		}
+		return false;
+	}
+
+	public function log(msg:String) {
+		Sys.println(msg);
+	}
+
+	static function parseArgs(str: String) {
+		str = StringTools.trim(str);
+		var i = 0;
+		var tok = "";
+		var args = [];
+		var escape = false;
+		while(i != str.length) {
+			var c = str.charAt(i++);
+			if(c == '"') {
+				escape = !escape;
+			}
+			else {
+				if(c == " " && !escape) {
+					if(tok.length > 0) args.push(tok);
+					tok = "";
+				}
+				else
+					tok += c;
+			}
+		}
+		if(tok.length > 0) args.push(tok);
+		return args;
+	}
+
+	static var useColor = false;
+	static function main() {
+		var m = new Memory();
+		var others: Array<Memory> = [];
+		var filterMode: FilterMode = None;
+
+		//hl.Gc.dumpMemory(); Sys.command("cp memory.hl test.hl");
+
+		var code = null, memory = null;
+		var args = Sys.args();
+		while( args.length > 0 ) {
+			var arg = args.shift();
+			if( StringTools.endsWith(arg, ".hl") ) {
+				code = arg;
+				m.loadBytecode(arg);
+				continue;
+			}
+			if( arg == "-c" || arg == "--color" ) {
+				useColor = true;
+				continue;
+			}
+			if( arg == "--args" ) {
+				m.displayProgress = false;
+				break;
+			}
+			if (memory == null) {
+				memory = arg;
+				m.loadMemory(arg);
+			} else {
+				var m2 = new Memory();
+				m2.loadMemory(arg);
+				others.push(m2);
+			}
+		}
+		if( code != null && memory == null ) {
+			var dir = new haxe.io.Path(code).dir;
+			if( dir == null ) dir = ".";
+			memory = dir+"/hlmemory.dump";
+			if( sys.FileSystem.exists(memory) ) m.loadMemory(memory);
+		}
+
+		m.check();
+		for (m2 in others) {
+			m2.code = m.code;
+			m2.check();
+		}
+		m.otherMems = [for (i in others) i];
+		m.setFilterMode(filterMode);
+
+		var stdin = Sys.stdin();
+		while( true ) {
+			Sys.print(withColor("> ", 31));
+			var args = parseArgs(args.length > 0 ? args.shift() : stdin.readLine());
+			var cmd = args.shift();
+			switch( cmd ) {
+			case "exit", "quit", "q":
+				break;
+			case "types":
+				m.printByType();
+			case "stats":
+				m.printStats();
+			case "false":
+				m.printFalsePositives(args.shift());
+			case "unknown":
+				m.printUnknown();
+			case "locate":
+				m.locate(args.shift(), Std.parseInt(args.shift()));
+			case "count":
+				m.count(args.shift(), args);
+			case "parents":
+				m.parents(args.shift());
+			case "subs":
+				m.subs(args.shift(), Std.parseInt(args.shift()));
+			case "sort":
+				switch( args.shift() ) {
+				case "mem":
+					m.sortByCount = false;
+				case "count":
+					m.sortByCount = true;
+				case mode:
+					Sys.println("Unknown sort mode " + mode);
+				}
+			case "fields":
+				switch( args.shift() ) {
+				case "full":
+					m.displayFields = Full;
+				case "none":
+					m.displayFields = None;
+				case "parents":
+					m.displayFields = Parents;
+				case mode:
+					Sys.println("Unknown fields mode " + mode);
+				}
+			case "filter":
+				switch( args.shift() ) {
+				case "none":
+					filterMode = None;
+				case "intersect":
+					filterMode = Intersect;
+				case "unique":
+					filterMode = Unique;
+				case mode:
+					Sys.println("Unknown filter mode " + mode);
+				}
+				m.setFilterMode(filterMode);
+			case "nextDump":
+				others.push(m);
+				m = others.shift();
+				m.otherMems = [for (i in others) i];
+				m.setFilterMode(filterMode);
+				var ostr = others.length > 0 ? (" (others are " + others.map(m -> m.memFile) + ")") : "";
+				Sys.println("Using dump " + m.memFile + ostr);
+			case "lines":
+				var v = args.shift();
+				if( v != null )
+					m.maxLines = Std.parseInt(v);
+				Sys.println(m.maxLines == 0 ? "Lines limit disabled" : m.maxLines + " maximum lines displayed");
+			case null:
+				Sys.println("");
+			default:
+				Sys.println("Unknown command " + cmd);
+			}
+		}
+	}
+
+	// A list of ansi colors is available at
+	// https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797#8-16-colors
+	public static function withColor(str: String, ansiCol: Int) {
+		if (!useColor)
+			return str;
+		return "\x1B[" + ansiCol + "m" + str + "\x1B[0m";
+	}
 }

+ 187 - 185
other/memory/TType.hx → other/haxelib/hlmem/TType.hx

@@ -1,186 +1,188 @@
-import format.hl.Data;
-using format.hl.Tools;
-
-class TType {
-	public var tid : Int;
-	public var t : HLType;
-	public var closure : HLType;
-	public var bmp : hl.Bytes;
-	public var hasPtr : Bool;
-	public var isDyn : Bool;
-
-	public var memFields : Array<TType>;
-	public var memFieldsNames : Array<String>;
-
-	public var constructs : Array<Array<TType>>;
-	public var nullWrap : TType;
-	public var ptrTags : haxe.io.Bytes;
-	public var parentClass : TType;
-
-	public var falsePositive = 0;
-	public var falsePositiveIndexes = [];
-
-	public function new(tid, t, ?cl) {
-		this.tid = tid;
-		this.t = t;
-		this.closure = cl;
-		isDyn = t.isDynamic();
-		switch( t ) {
-		case HFun(_):
-			hasPtr = cl != null && cl.isPtr();
-		default:
-			hasPtr = t.containsPointer();
-		}
-	}
-
-	public function match( t : TType ) {
-		if( t == this ) return true;
-		if( parentClass != null ) return parentClass.match(t);
-		return false;
-	}
-
-	function tagPtr( pos : Int ) {
-		var p = pos >> 3;
-		if( ptrTags == null || ptrTags.length <= p ) {
-			var nc = haxe.io.Bytes.alloc(p + 1);
-			if( ptrTags != null ) nc.blit(0, ptrTags, 0, ptrTags.length);
-			ptrTags = nc;
-		}
-		ptrTags.set(p, ptrTags.get(p) | (1 << (pos & 7)));
-	}
-
-	function appendField( m : Memory, pos : Int, name : String, t : HLType ) {
-		switch( t ) {
-		case HPacked({ v : HStruct(p) }):
-			var all = [p];
-			while( p.tsuper != null ) {
-				switch( p.tsuper ) {
-				case HStruct(p2):
-					p = p2;
-					all.unshift(p2);
-				default:
-					throw "assert";
-				}
-			}
-			for( p in all )
-				for( f in p.fields )
-					pos = appendField(m, pos, name+"."+f.name, f.t);
-		case HPacked(_):
-			throw "assert";
-		default:
-			var size = m.typeSize(t);
-			pos = align(pos, size);
-			memFields[pos >> m.ptrBits] = m.getType(t);
-			memFieldsNames[pos >> m.ptrBits] = name;
-			if( t.isPtr() ) tagPtr(pos >> m.ptrBits);
-			pos += size;
-		}
-		return pos;
-	}
-
-	public function buildTypes( m : Memory, tvoid : TType ) {
-		if( !hasPtr ) return;
-
-		// layout of data inside memory
-		inline function fill(fields:Array<TType>, pos:Int) {
-			if( m.is64 ) {
-				for( i in 0...pos >> m.ptrBits )
-					if( fields[i] == null )
-						fields[i] = tvoid;
-			} else {
-				// fill two slots for 64bit data
-				var i = pos >> m.ptrBits;
-				while( --i >= 0 )
-					if( fields[i] == null )
-						fields[i] = (fields[i-1] == null || fields[i-1].t != HF64) ? tvoid : fields[i-1];
-			}
-		}
-
-		switch( t ) {
-		case HObj(p), HStruct(p):
-			var protos = [p];
-			while( p.tsuper != null )
-				switch( p.tsuper ) {
-				case HObj(p2), HStruct(p2):
-					protos.unshift(p2);
-					p = p2;
-				default:
-				}
-
-			memFields = [];
-			memFieldsNames = [];
-
-			var pos = t.match(HStruct(_)) ? 0 : 1 << m.ptrBits; // type
-			for( p in protos )
-				for( f in p.fields )
-					pos = appendField(m, pos, f.name, f.t);
-			fill(memFields, pos);
-		case HEnum(e):
-			constructs = [];
-			for( c in e.constructs ) {
-				var pos = (1<<m.ptrBits) + 4; // type + index
-				var fields = [];
-				for( t in c.params ) {
-					var size = m.typeSize(t);
-					pos = align(pos, size);
-					fields[pos>>m.ptrBits] = m.getType(t);
-					if( t.isPtr() ) tagPtr(pos >> m.ptrBits);
-					pos += size;
-				}
-				fill(fields, pos);
-				constructs.push(fields);
-			}
-		case HVirtual(fl):
-			memFields = [
-				tvoid, // type
-				m.getType(HDyn), // obj
-				m.getType(HDyn), // next
-			];
-			memFieldsNames = [];
-			var pos = (fl.length + 3) << m.ptrBits;
-			tagPtr(1);
-			tagPtr(2);
-			for( f in fl ) {
-				var size = m.typeSize(f.t);
-				pos = align(pos, size);
-				memFields[pos >> m.ptrBits] = m.getType(f.t);
-				memFieldsNames[pos >> m.ptrBits] = f.name;
-				if( f.t.isPtr() ) tagPtr(pos >> m.ptrBits);
-				pos += size;
-			}
-
-			fill(memFields, pos);
-			// keep null our fields pointers since they might point to a DynObj data head
-			for( i in 0...fl.length )
-				memFields[i+3] = null;
-		case HNull(t):
-			if( m.is64 )
-				memFields = [tvoid, m.getType(t)];
-			else
-				memFields = [tvoid, tvoid, m.getType(t), tvoid];
-		case HFun(_):
-			memFields = [tvoid, tvoid, tvoid, m.getType(closure)];
-		default:
-		}
-	}
-
-	inline function align(pos, size) {
-		var d = pos & (size - 1);
-		if( d != 0 ) pos += size - d;
-		return pos;
-	}
-
-	public function toString() {
-		switch( t ) {
-		case HAbstract("roots"):
-			return Memory.withColor("roots", 32);
-		case HAbstract(p):
-			return p;
-		case HFun(_), HMethod(_):
-			return 'Function(${t.toString()})';
-		default:
-			return t.toString();
-		}
-	}
-
+package hlmem;
+
+import format.hl.Data;
+using format.hl.Tools;
+
+class TType {
+	public var tid : Int;
+	public var t : HLType;
+	public var closure : HLType;
+	public var bmp : #if js haxe.io.Bytes #else hl.Bytes #end;
+	public var hasPtr : Bool;
+	public var isDyn : Bool;
+
+	public var memFields : Array<TType>;
+	public var memFieldsNames : Array<String>;
+
+	public var constructs : Array<Array<TType>>;
+	public var nullWrap : TType;
+	public var ptrTags : haxe.io.Bytes;
+	public var parentClass : TType;
+
+	public var falsePositive = 0;
+	public var falsePositiveIndexes = [];
+
+	public function new(tid, t, ?cl) {
+		this.tid = tid;
+		this.t = t;
+		this.closure = cl;
+		isDyn = t.isDynamic();
+		switch( t ) {
+		case HFun(_):
+			hasPtr = cl != null && cl.isPtr();
+		default:
+			hasPtr = t.containsPointer();
+		}
+	}
+
+	public function match( t : TType ) {
+		if( t == this ) return true;
+		if( parentClass != null ) return parentClass.match(t);
+		return false;
+	}
+
+	function tagPtr( pos : Int ) {
+		var p = pos >> 3;
+		if( ptrTags == null || ptrTags.length <= p ) {
+			var nc = haxe.io.Bytes.alloc(p + 1);
+			if( ptrTags != null ) nc.blit(0, ptrTags, 0, ptrTags.length);
+			ptrTags = nc;
+		}
+		ptrTags.set(p, ptrTags.get(p) | (1 << (pos & 7)));
+	}
+
+	function appendField( m : Memory, pos : Int, name : String, t : HLType ) {
+		switch( t ) {
+		case HPacked({ v : HStruct(p) }):
+			var all = [p];
+			while( p.tsuper != null ) {
+				switch( p.tsuper ) {
+				case HStruct(p2):
+					p = p2;
+					all.unshift(p2);
+				default:
+					throw "assert";
+				}
+			}
+			for( p in all )
+				for( f in p.fields )
+					pos = appendField(m, pos, name+"."+f.name, f.t);
+		case HPacked(_):
+			throw "assert";
+		default:
+			var size = m.typeSize(t);
+			pos = align(pos, size);
+			memFields[pos >> m.ptrBits] = m.getType(t);
+			memFieldsNames[pos >> m.ptrBits] = name;
+			if( t.isPtr() ) tagPtr(pos >> m.ptrBits);
+			pos += size;
+		}
+		return pos;
+	}
+
+	public function buildTypes( m : Memory, tvoid : TType ) {
+		if( !hasPtr ) return;
+
+		// layout of data inside memory
+		inline function fill(fields:Array<TType>, pos:Int) {
+			if( m.is64 ) {
+				for( i in 0...pos >> m.ptrBits )
+					if( fields[i] == null )
+						fields[i] = tvoid;
+			} else {
+				// fill two slots for 64bit data
+				var i = pos >> m.ptrBits;
+				while( --i >= 0 )
+					if( fields[i] == null )
+						fields[i] = (fields[i-1] == null || fields[i-1].t != HF64) ? tvoid : fields[i-1];
+			}
+		}
+
+		switch( t ) {
+		case HObj(p), HStruct(p):
+			var protos = [p];
+			while( p.tsuper != null )
+				switch( p.tsuper ) {
+				case HObj(p2), HStruct(p2):
+					protos.unshift(p2);
+					p = p2;
+				default:
+				}
+
+			memFields = [];
+			memFieldsNames = [];
+
+			var pos = t.match(HStruct(_)) ? 0 : 1 << m.ptrBits; // type
+			for( p in protos )
+				for( f in p.fields )
+					pos = appendField(m, pos, f.name, f.t);
+			fill(memFields, pos);
+		case HEnum(e):
+			constructs = [];
+			for( c in e.constructs ) {
+				var pos = (1<<m.ptrBits) + 4; // type + index
+				var fields = [];
+				for( t in c.params ) {
+					var size = m.typeSize(t);
+					pos = align(pos, size);
+					fields[pos>>m.ptrBits] = m.getType(t);
+					if( t.isPtr() ) tagPtr(pos >> m.ptrBits);
+					pos += size;
+				}
+				fill(fields, pos);
+				constructs.push(fields);
+			}
+		case HVirtual(fl):
+			memFields = [
+				tvoid, // type
+				m.getType(HDyn), // obj
+				m.getType(HDyn), // next
+			];
+			memFieldsNames = [];
+			var pos = (fl.length + 3) << m.ptrBits;
+			tagPtr(1);
+			tagPtr(2);
+			for( f in fl ) {
+				var size = m.typeSize(f.t);
+				pos = align(pos, size);
+				memFields[pos >> m.ptrBits] = m.getType(f.t);
+				memFieldsNames[pos >> m.ptrBits] = f.name;
+				if( f.t.isPtr() ) tagPtr(pos >> m.ptrBits);
+				pos += size;
+			}
+
+			fill(memFields, pos);
+			// keep null our fields pointers since they might point to a DynObj data head
+			for( i in 0...fl.length )
+				memFields[i+3] = null;
+		case HNull(t):
+			if( m.is64 )
+				memFields = [tvoid, m.getType(t)];
+			else
+				memFields = [tvoid, tvoid, m.getType(t), tvoid];
+		case HFun(_):
+			memFields = [tvoid, tvoid, tvoid, m.getType(closure)];
+		default:
+		}
+	}
+
+	inline function align(pos, size) {
+		var d = pos & (size - 1);
+		if( d != 0 ) pos += size - d;
+		return pos;
+	}
+
+	public function toString() {
+		switch( t ) {
+		case HAbstract("roots"):
+			return Memory.withColor("roots", 32);
+		case HAbstract(p):
+			return p;
+		case HFun(_), HMethod(_):
+			return 'Function(${t.toString()})';
+		default:
+			return t.toString();
+		}
+	}
+
 }

+ 3 - 3
other/memory/memory.hxml → other/haxelib/memory.hxml

@@ -1,3 +1,3 @@
--lib format
--main Memory
--hl memory.hl
+-lib format
+-main hlmem.Memory
+-hl memory.hl

+ 0 - 17
other/memory/.vscode/launch.json

@@ -1,17 +0,0 @@
-{
-    // Utilisez IntelliSense pour en savoir plus sur les attributs possibles.
-    // Pointez pour afficher la description des attributs existants.
-    // Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387
-    "version": "0.2.0",
-    "configurations": [
-        {
-            "name": "HashLink",
-            "request": "launch",
-            "type": "hl",
-            "hxml": "memory.hxml",
-            "argsFile": "args.txt",
-            "cwd": "${workspaceRoot}",
-            "preLaunchTask": "Build"
-        }
-    ]
-}

+ 0 - 16
other/memory/.vscode/tasks.json

@@ -1,16 +0,0 @@
-{
-	// See https://go.microsoft.com/fwlink/?LinkId=733558
-	// for the documentation about the tasks.json format
-	"version": "2.0.0",
-	"tasks": [
-		{
-			"type": "haxe",
-			"label": "Build",
-			"args": "active configuration",
-			"group": {
-				"kind": "build",
-				"isDefault": true
-			}
-		}
-	]
-}