Преглед на файлове

new memory dump format (using allocator api)
+ added field hierarchy

Nicolas Cannasse преди 5 години
родител
ревизия
aaba93705b
променени са 5 файла, в които са добавени 286 реда и са изтрити 256 реда
  1. 30 42
      other/memory/Block.hx
  2. 138 179
      other/memory/Memory.hx
  3. 24 12
      other/memory/TType.hx
  4. 33 0
      src/allocator.c
  5. 61 23
      src/gc.c

+ 30 - 42
other/memory/Block.hx

@@ -15,9 +15,6 @@ abstract Pointer(haxe.Int64) {
 	public inline function sub( p : Pointer ) : Int {
 		return haxe.Int64.toInt(this - p.value);
 	}
-	public inline function pageAddress() : Pointer {
-		return new Pointer(haxe.Int64.and(this,haxe.Int64.make(-1,~0xFFFF)));
-	}
 	public inline function toString() {
 		return "0x"+(this.high == 0 ? StringTools.hex(this.high, 8) : "")+StringTools.hex(this.low,8);
 	}
@@ -34,43 +31,19 @@ abstract Pointer(haxe.Int64) {
 }
 
 class Page {
-	var memory : Memory;
 	public var addr : Pointer;
 	public var kind : PageKind;
 	public var size : Int;
-	public var blockSize : Int;
-	public var firstBlock : Int;
-	public var maxBlocks : Int;
-	public var nextBlock : Int;
-	public var dataPosition : Int;
-	public var bmp : haxe.io.Bytes;
-	public var sizes : haxe.io.Bytes;
+	public var reserved : Int;
+	public var dataPosition : Int = -1;
 
-	public function new(m) {
-		memory = m;
+	public function new() {
 	}
 
 	public inline function memHasPtr() {
 		return kind == PDynamic || kind == PRaw;
 	}
 
-	public function isLiveBlock( bid : Int ) {
-		if( sizes != null && sizes.get(bid) == 0 ) return false;
-		return (bmp.get(bid >> 3) & (1 << (bid & 7))) != 0;
-	}
-
-	public function getBlockSize( bid : Int ) {
-		return sizes == null ? 1 : sizes.get(bid);
-	}
-
-	public function goto( bid : Int ) {
-		memory.memoryDump.seek(dataPosition + bid * blockSize, SeekBegin);
-	}
-
-	public function getPointer( bid : Int ) {
-		return addr.offset(bid * blockSize);
-	}
-
 }
 
 class Stack {
@@ -88,11 +61,22 @@ enum BlockTypeKind {
 	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 bid : Int;
+	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;
@@ -100,26 +84,19 @@ class Block {
 	public var depth : Int = -1;
 	public var mark : Int = -1;
 
-	public var subs : Array<Block>; // can be null
+	public var subs : Array<BlockSub>; // can be null
 	public var parents : Array<Block>; // if multiple owners
 
 	public function new() {
 	}
 
-	public inline function getPointer() {
-		return page.getPointer(bid);
-	}
-	public inline function getMemSize() {
-		return page.getBlockSize(bid) * page.blockSize;
-	}
-
 	function set_type( t : TType ) {
 		if( t != null && t.t == HF64 && page.kind != PNoPtr )
 			throw "!";
 		return type = t;
 	}
 
-	public function addParent(b:Block) {
+	public function addParent(b:Block,fid:Int=0) {
 		if( owner == null ) {
 			owner = b;
 		} else {
@@ -127,7 +104,17 @@ class Block {
 			parents.push(b);
 		}
 		if( b.subs == null ) b.subs = [];
-		b.subs.push(this);
+		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() {
@@ -140,6 +127,7 @@ class Block {
 		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);
@@ -180,7 +168,7 @@ class Block {
 	public function removeChildren() {
 		if( subs != null ) {
 			for( s in subs )
-				s.removeParent(this);
+				s.b.removeParent(this);
 			subs = null;
 		}
 	}

+ 138 - 179
other/memory/Memory.hx

@@ -15,6 +15,10 @@ class Stats {
 		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);
@@ -37,7 +41,18 @@ class Stats {
 		for( i in allT ) {
 			totCount += i.count;
 			totMem += i.mem;
-			mem.log(i.count + " count, " + Memory.MB(i.mem) + " " + [for( tid in i.tl ) mem.types[tid].toString()].join(" > "));
+			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(i.count + " count, " + Memory.MB(i.mem) + " " + tpath.join(" > "));
 		}
 		if( withSum )
 			mem.log("Total: "+totCount+" count, "+Memory.MB(totMem));
@@ -47,8 +62,6 @@ class Stats {
 
 class Memory {
 
-	static inline var PAGE_BITS = 16;
-
 	public var memoryDump : sys.io.FileInput;
 
 	public var is64 : Bool;
@@ -57,7 +70,12 @@ class Memory {
 
 	public var types : Array<TType>;
 
+	var privateData : Int;
+	var markData : Int;
+
 	var sortByCount : Bool;
+	var displayFields : Bool = true;
+
 	var code : format.hl.Data;
 	var pages : Array<Page>;
 	var roots : Array<Pointer>;
@@ -75,6 +93,9 @@ class Memory {
 	var pointerType : PointerMap<TType>;
 	var falseCandidates : Array<{ b : Block, f : Block, idx : Int }>;
 
+	var currentTypeIndex = 0;
+	var resolveCache : Map<String,TType> = new Map();
+
 	function new() {
 	}
 
@@ -133,38 +154,49 @@ class Memory {
 			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 addr = readPointer();
-				if( addr.isNull() ) break;
-				var p = new Page(this);
-				p.addr = addr;
-				p.kind = cast readInt();
-				p.size = readInt();
-				p.blockSize = readInt();
-				p.firstBlock = readInt();
-				p.maxBlocks = readInt();
-				p.nextBlock = readInt();
+				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);
-
-				var flags = readInt();
-				if( flags & 1 != 0 )
-					p.bmp = memoryDump.read((p.maxBlocks + 7) >> 3); // 1 bit per block
-				if( flags & 2 != 0 )
-					p.sizes = memoryDump.read(p.maxBlocks); // 1 byte per block
-				pages.push(p);
 			}
+			pages.push(p);
 		}
 
 		// load roots
@@ -194,20 +226,18 @@ class Memory {
 	}
 
 	function printStats() {
-		var totalSize = 0, maxSize = 0;
+		var pagesSize = 0, reserved = 0;
 		var used = 0, gc = 0;
 		for( p in pages ) {
-			maxSize += p.size;
-			totalSize += p.size + p.bmp.length;
-			gc += p.size - (p.maxBlocks - p.firstBlock) * p.blockSize;
-			gc += p.bmp.length;
+			pagesSize += p.size;
+			reserved += p.reserved;
 		}
 		for( b in blocks )
-			used += b.getMemSize();
-		log(pages.length + " pages, " + MB(totalSize) + " memory");
+			used += b.size;
+		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(maxSize - used) + " free, "+MB(gc)+" gc");
+		log(blocks.length + " live blocks " + MB(used) + " used, " + MB(pagesSize - used - reserved) + " free, "+MB(privateData + markData)+" gc");
 	}
 
 	function getTypeNull( t : TType ) {
@@ -227,7 +257,9 @@ class Memory {
 	}
 
 	function goto( b : Block ) {
-		b.page.goto(b.bid);
+		var p = b.page.dataPosition;
+		if( p < 0 ) throw "assert";
+		memoryDump.seek(p + b.addr.sub(b.page.addr), SeekBegin);
 	}
 
 	function check() {
@@ -279,47 +311,30 @@ class Memory {
 			pointerType.set(b.p, t);
 		}
 
-		blocks = [];
-		var pageMem = new PointerMap<Page>();
 		var progress = 0;
 		pointerBlock = new PointerMap();
 
-		for( p in pages ) {
+		for( b in blocks ) {
 			progress++;
-			if( progress % 100 == 0 )
-				Sys.print((Std.int(progress * 1000 / pages.length) / 10) + "%  \r");
-
-			var bid = p.firstBlock;
-			while( bid < p.maxBlocks ) {
-				if( !p.isLiveBlock(bid) ) {
-					bid++;
-					continue;
-				}
-				var sz = p.getBlockSize(bid);
-
-				var b = new Block();
-				b.page = p;
-				b.bid = bid;
-
-				// NoPtr page can also have a type ptr
+			if( progress % 1000 == 0 )
+				Sys.print((Std.int(progress * 1000 / blocks.length) / 10) + "%  \r");
+			if( b.page.memHasPtr() ) {
 				goto(b);
-				b.type = pointerType.get(readPointer());
-				if( b.type != null && b.type.hasPtr && p.kind == PNoPtr )
+				b.typePtr = readPointer();
+			}
+			b.type = pointerType.get(b.typePtr);
+			b.typePtr = null;
+			if( b.type != null && b.type.hasPtr && b.page.kind == PNoPtr ) {
+				if( b.type.t.match(HEnum(_)) ) {
+					// most likely one of the constructor without pointer parameter
+				} else
 					b.type = null; // false positive
-				if( b.type != null && !b.type.isDyn )
-					b.type = getTypeNull(b.type);
-				if( b.type != null )
-					b.typeKind = KHeader;
-
-				blocks.push(b);
-				pointerBlock.set(b.getPointer(), b);
-
-				bid += sz;
 			}
-
-			for( i in 0...(p.size >> PAGE_BITS) )
-				pageMem.set(p.addr.offset(i << PAGE_BITS), p);
-
+			if( b.type != null && !b.type.isDyn )
+				b.type = getTypeNull(b.type);
+			if( b.type != null )
+				b.typeKind = KHeader;
+			pointerBlock.set(b.addr, b);
 		}
 
 		printStats();
@@ -382,6 +397,24 @@ class Memory {
 				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...");
@@ -419,7 +452,7 @@ class Memory {
 			if( b.owner == null ) {
 				unRef++;
 				if( unRef < 100 )
-					log("  "+b.getPointer().toString() + " is not referenced");
+					log("  "+b.addr.toString() + " is not referenced");
 				continue;
 			}
 
@@ -438,7 +471,7 @@ class Memory {
 				}
 
 			unk++;
-			unkMem += b.getMemSize();
+			unkMem += b.size;
 		}
 
 		var falseCount = 0;
@@ -452,7 +485,7 @@ class Memory {
 		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    "+f.fields);
+			log(f.falsePositive+" count " + f + " "+f.falsePositiveIndexes+"\n    "+[for( f in f.memFields ) f.t.toString()]);
 	}
 
 	function printUnknown() {
@@ -472,7 +505,7 @@ class Memory {
 				byT.set(tid, inf);
 			}
 			inf.count++;
-			inf.mem += b.getMemSize();
+			inf.mem += b.size;
 		}
 		var all = [for( k in byT ) k];
 		all.sort(function(i1, i2) return i1.count - i2.count);
@@ -489,27 +522,28 @@ class Memory {
 			b.removeChildren();
 
 		for( b in blocks ) {
-
 			progress++;
 			if( progress % 10000 == 0 )
 				Sys.print((Std.int(progress * 1000.0 / blocks.length) / 10) + "%  \r");
 
-			if( b.page.kind == PNoPtr )
+			if( !b.page.memHasPtr() )
 				continue;
 
 			if( b.type != null && !b.type.hasPtr )
 				switch(b.type.t) {
 				case HFun(_):
 				default:
-					log("  Scanning "+b.type+" "+b.getPointer().toString());
+					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 ) {
-				fields = b.type.fields;
+				hasFieldNames = b.type.memFieldsNames != null;
+				fields = b.type.memFields;
 				ptrTags = b.type.ptrTags;
 				// enum
 				if( b.type.constructs != null ) {
@@ -519,7 +553,7 @@ class Memory {
 				}
 			}
 
-			for( i in start...(b.getMemSize() >> ptrBits) ) {
+			for( i in start...(b.size >> ptrBits) ) {
 				var r = readPointer();
 
 				//if( ptrTags != null && ((ptrTags.get(i >> 5) >>> (i & 31)) & 1) == 0 ) continue;
@@ -541,11 +575,11 @@ class Memory {
 					falseCandidates.push({ b : b, f:bs, idx : i });
 					continue;
 				}
-				bs.addParent(b);
+				bs.addParent(b,hasFieldNames ? (i+1) : 0);
 
 				if( bs.type == null && ft != null ) {
 					if( ft.t.isDynamic() ) {
-						trace(b.typeKind, b.getPointer().toString(), b.type.toString(), ft.toString());
+						trace(b.typeKind, b.addr.toString(), b.type.toString(), ft.toString());
 						continue;
 					}
 					bs.type = ft;
@@ -560,15 +594,24 @@ class Memory {
 	function printByType() {
 		var ctx = new Stats(this);
 		for( b in blocks )
-			ctx.add(b.type, b.getMemSize());
+			ctx.add(b.type, b.size);
 		ctx.print();
 	}
 
-	function resolveType( str ) {
-		for( t in types )
-			if( t.t.toString() == str )
+	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();
+			resolveCache.set(tstr, t);
+			currentTypeIndex = i + 1;
+			if( tstr == str )
 				return t;
-		log("Type not found '"+str+"'");
+		}
+		if( showError )
+			log("Type not found '"+str+"'");
 		return null;
 	}
 
@@ -589,10 +632,10 @@ class Memory {
 						var prev = owner;
 						owner = owner.owner;
 						ol.push(owner);
-						tl.unshift(owner.type == null ? 0 : owner.type.tid);
+						tl.unshift(owner.makeTID(prev,displayFields));
 					}
 				}
-				ctx.addPath(tl, b.getMemSize());
+				ctx.addPath(tl, b.size);
 			}
 		ctx.print();
 	}
@@ -615,7 +658,7 @@ class Memory {
 		while( mark.length > 0 ) {
 			var b = mark.pop();
 			for( s in b.subs )
-				visitRec(s,ctx,texclude,mark);
+				visitRec(s.b,ctx,texclude,mark);
 		}
 		ctx.print(true);
 	}
@@ -624,7 +667,7 @@ class Memory {
 		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.getMemSize());
+		ctx.addPath(b.type == null ? [] : [b.type.tid],b.size);
 		if( b.subs != null )
 			mark.push(b);
 	}
@@ -671,11 +714,11 @@ class Memory {
 						return;
 					mark.set(b, true);
 					tl.push(b.type == null ? 0 : b.type.tid);
-					ctx.addPath(tl, b.getMemSize());
+					ctx.addPath(tl, b.size);
 					if( b.subs != null ) {
 						k--;
 						for( s in b.subs )
-							addRec(tl.copy(),s, k);
+							addRec(tl.copy(),s.b, k);
 					}
 				}
 				addRec([], b, down);
@@ -683,97 +726,10 @@ class Memory {
 		ctx.print();
 	}
 
-	function printPartition() {
-		var part = sys.io.File.write("part.txt",false);
-		for( p in pages ) {
-			var bid = p.firstBlock;
-			part.writeString(p.addr.toString()+ " [" + p.blockSize+"]");
-			while( bid < p.maxBlocks ) {
-				if( !p.isLiveBlock(bid) ) {
-					part.writeByte(".".code);
-					bid++;
-					continue;
-				}
-				var sz = p.getBlockSize(bid);
-				for( i in 0...sz ) part.writeByte("#".code);
-				bid += sz;
-			}
-			part.writeByte("\n".code);
-		}
-		part.close();
-		log("Partition saved in part.txt");
-	}
-
 	public function log(msg:String) {
 		Sys.println(msg);
 	}
 
-	static inline var BMP_BITS = 5;
-
-	function makeBitmap() {
-
-		// VMMap.exe tool can also be used to show address space fragmentation in realtime
-		// although it will not be able to show GC fragmentation
-
-		var totalMem = 2 * 1024. * 1024. * 1024.; // GB of memory
-		var sizeReal = Math.ceil(Math.sqrt(totalMem / (1 << BMP_BITS)));
-		var size = 1, bits = 1;
-		while( size < sizeReal ) {
-			size <<= 1;
-			bits++;
-		}
-		var bytes = haxe.io.Bytes.alloc(size * size * 4);
-		var bmp : hl.BytesAccess<Int> = (bytes : hl.Bytes);
-		for( i in 0...size * size )
-			bmp[i] = 0xFF000000;
-		for( p in pages ) {
-			var index = p.addr.shift(BMP_BITS).low;
-			// reserved
-			for( i in 0...(p.size >> BMP_BITS) )
-				bmp[index+i] = 0xFF808080;
-			for( i in 0...((p.firstBlock * p.blockSize) >> BMP_BITS) )
-				bmp[index + i] = 0xFFFFFF00; // YELLOW = GC MEMORY
-		}
-		for( b in blocks ) {
-			var index = b.getPointer().shift(BMP_BITS).low;
-			var color = b.page.memHasPtr() ? 0xFFFF0000 : 0xFF00FF00; // GREEN = data / RED = objects
-			for( i in 0...b.getMemSize() >> BMP_BITS )
-				bmp[index + i] = color;
-		}
-		var f = sys.io.File.write("bitmap.png");
-		new format.png.Writer(f).write(format.png.Tools.build32BGRA(size, size, bytes));
-		f.close();
-	}
-
-	function printPages() {
-		var perBlockSize = new Map();
-		for( p in pages ) {
-			var inf = perBlockSize.get(p.blockSize);
-			if( inf == null ) {
-				inf = {
-					blockSize : p.blockSize,
-					count : 0,
-					mem : 0.,
-					kinds : [],
-					blocks : [],
-				};
-				perBlockSize.set(p.blockSize, inf);
-			}
-			inf.count++;
-			inf.mem += p.size;
-			inf.kinds[cast p.kind]++;
-		}
-		for( b in blocks ) {
-			var inf = perBlockSize.get(b.page.blockSize);
-			inf.blocks[cast b.page.kind]++;
-		}
-		var all = Lambda.array(perBlockSize);
-		all.sort(function(i1, i2) return i1.blockSize - i2.blockSize);
-		log("Kinds = [Dyn,Raw,NoPtr,Finalizer]");
-		for( i in all )
-			log('BlockSize ${i.blockSize} : ${i.count} pages, ${Std.int(i.mem/1024)} KB, kinds = ${i.kinds}, blocks = ${i.blocks}');
-	}
-
 	static function main() {
 		var m = new Memory();
 
@@ -822,12 +778,6 @@ class Memory {
 				m.parents(args.shift());
 			case "subs":
 				m.subs(args.shift(), Std.parseInt(args.shift()));
-			case "part":
-				m.printPartition();
-			case "bitmap":
-				m.makeBitmap();
-			case "pages":
-				m.printPages();
 			case "sort":
 				switch( args.shift() ) {
 				case "mem":
@@ -837,6 +787,15 @@ class Memory {
 				case mode:
 					Sys.println("Unknown sort mode " + mode);
 				}
+			case "fields":
+				switch( args.shift() ) {
+				case "true":
+					m.displayFields = true;
+				case "false":
+					m.displayFields = false;
+				case mode:
+					Sys.println("Unknown fields mode " + mode);
+				}
 			default:
 				Sys.println("Unknown command " + cmd);
 			}

+ 24 - 12
other/memory/TType.hx

@@ -8,7 +8,10 @@ class TType {
 	public var bmp : hl.Bytes;
 	public var hasPtr : Bool;
 	public var isDyn : Bool;
-	public var fields : Array<TType>;
+
+	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;
@@ -75,7 +78,8 @@ class TType {
 				default:
 				}
 
-			fields = [];
+			memFields = [];
+			memFieldsNames = [];
 
 			var fcount = 0;
 			for( p in protos )
@@ -86,12 +90,13 @@ class TType {
 				for( f in p.fields ) {
 					var size = m.typeSize(f.t);
 					pos = align(pos, size);
-					fields[pos >> m.ptrBits] = m.getType(f.t);
+					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(fields, pos);
+			fill(memFields, pos);
 		case HEnum(e):
 			constructs = [];
 			for( c in e.constructs ) {
@@ -108,33 +113,35 @@ class TType {
 				constructs.push(fields);
 			}
 		case HVirtual(fl):
-			fields = [
+			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);
-				fields[pos >> m.ptrBits] = m.getType(f.t);
+				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(fields, pos);
+			fill(memFields, pos);
 			// keep null our fields pointers since they might point to a DynObj data head
 			for( i in 0...fl.length )
-				fields[i+3] = null;
+				memFields[i+3] = null;
 		case HNull(t):
 			if( m.is64 )
-				fields = [tvoid, m.getType(t)];
+				memFields = [tvoid, m.getType(t)];
 			else
-				fields = [tvoid, tvoid, m.getType(t), tvoid];
+				memFields = [tvoid, tvoid, m.getType(t), tvoid];
 		case HFun(_):
-			fields = [tvoid, tvoid, tvoid, m.getType(closure)];
+			memFields = [tvoid, tvoid, tvoid, m.getType(closure)];
 		default:
 		}
 	}
@@ -146,7 +153,12 @@ class TType {
 	}
 
 	public function toString() {
-		return t.toString();
+		switch( t ) {
+		case HAbstract(p):
+			return p;
+		default:
+			return t.toString();
+		}
 	}
 
 }

+ 33 - 0
src/allocator.c

@@ -449,5 +449,38 @@ static void gc_allocator_after_mark() {
 	gc_flush_empty_pages();
 }
 
+static void gc_get_stats( int *page_count, int *private_data ) {
+	int count = 0;
+	int i;
+	for(i=0;i<GC_ALL_PAGES;i++) {
+		gc_pheader *p = gc_pages[i];
+		while( p ) {
+			count++;
+			p = p->next_page;
+		}
+	}
+	*page_count = count;
+	*private_data = 0; // no malloc
+} 
 
+static void gc_iter_pages( gc_page_iterator iter ) {
+	int i;
+	for(i=0;i<GC_ALL_PAGES;i++) {
+		gc_pheader *p = gc_pages[i];
+		while( p ) {
+			int size = 0;
+			if( p->alloc.sizes && p->alloc.max_blocks > 8 ) size = p->alloc.max_blocks;
+			iter(p,size);
+			p = p->next_page;
+		}
+	}
+}
 
+static void gc_iter_live_blocks( gc_pheader *ph, gc_block_iterator iter ) {
+	int i;
+	gc_allocator_page_data *p = &ph->alloc;
+	for(i=0;i<p->max_blocks;i++) {
+		if( ph->bmp[(i>>3)] & (1<<(i&7)) )
+			iter(ph->base + i*p->block_size,p->sizes?p->sizes[i]*p->block_size:p->block_size);
+	}
+}

+ 61 - 23
src/gc.c

@@ -81,7 +81,14 @@ static int_val gc_hash( void *ptr ) {
 
 typedef struct _gc_pheader gc_pheader;
 
-#if 0
+// page + private total reserved data per page
+typedef void (*gc_page_iterator)( gc_pheader *, int );
+// block-ptr + size
+typedef void (*gc_block_iterator)( void *, int );
+
+//#define GC_EXTERN_API
+
+#ifdef GC_EXTERN_API
 typedef void* gc_allocator_page_data;
 
 // Initialize the allocator
@@ -108,6 +115,11 @@ void gc_allocator_after_mark();
 // Sets size to -1 if allocation refused (required size is invalid)
 void *gc_allocator_alloc( int *size, int page_kind );
 
+// returns the number of pages allocated and private data size (global)
+void gc_get_stats( int *page_count, int *private_data);
+void gc_iter_pages( gc_page_iterator i );
+void gc_iter_live_blocks( gc_pheader *p, gc_block_iterator i );
+
 #else
 #	include "allocator.h"
 #endif
@@ -143,7 +155,10 @@ static gc_pheader *gc_free_pheaders = NULL;
 
 static gc_pheader *gc_alloc_page( int size, int kind, int block_count );
 static void gc_free_page( gc_pheader *page, int block_count );
+
+#ifndef GC_EXTERN_API
 #include "allocator.c"
+#endif
 
 static struct {
 	int count;
@@ -1022,37 +1037,60 @@ HL_API void hl_gc_set_dump_types( hl_types_dump tdump ) {
 	gc_types_dump = tdump;
 }
 
+static void gc_dump_block( void *block, int size ) {
+	fdump_p(block);
+	fdump_i(size);
+}
+
+static void gc_dump_block_ptr( void *block, int size ) {
+	fdump_p(block);
+	fdump_i(size);
+	if( size >= sizeof(void*) ) fdump_p(*(void**)block);
+}
+
+static void gc_dump_page( gc_pheader *p, int private_data ) {
+	fdump_p(p->base);
+	fdump_i(p->page_kind);
+	fdump_i(p->page_size);
+	fdump_i(private_data);
+	if( p->page_kind & MEM_KIND_NOPTR ) {
+		gc_iter_live_blocks(p, gc_dump_block_ptr); // only dump type
+		fdump_p(NULL);
+	} else {
+		gc_iter_live_blocks(p,gc_dump_block);
+		fdump_p(NULL);
+		fdump_d(p->base, p->page_size);
+	}
+}
+
 HL_API void hl_gc_dump_memory( const char *filename ) {
 	int i;
 	gc_global_lock(true);
 	gc_stop_world(true);
 	gc_mark();
 	fdump = fopen(filename,"wb");
+
 	// header
-	fdump_d("HMD0",4);
+	fdump_d("HMD1",4);
 	fdump_i(((sizeof(void*) == 8)?1:0) | ((sizeof(bool) == 4)?2:0));
+
 	// pages
-/*
-	fdump_i(GC_ALL_PAGES);
-	for(i=0;i<GC_ALL_PAGES;i++) {
-		gc_pheader *p = gc_pages[i];
-		while( p != NULL ) {
-			fdump_p(p->base);
-			fdump_i(p->page_kind);
-			fdump_i(p->page_size);
-			fdump_i(p->block_size);
-			fdump_i(p->first_block);
-			fdump_i(p->max_blocks);
-			fdump_i(p->next_block);
-			fdump_d(p->base,p->page_size);
-			fdump_i((p->bmp ? 1 :0) | (p->sizes?2:0));
-			if( p->bmp ) fdump_d(p->bmp,(p->max_blocks + 7) >> 3);
-			if( p->sizes ) fdump_d(p->sizes,p->max_blocks);
-			p = p->next_page;
-		}
-		fdump_p(NULL);
-	}
-*/
+	int page_count, private_data;
+	gc_get_stats(&page_count, &private_data);
+
+	// all mallocs
+	private_data += sizeof(gc_pheader) * page_count;
+	private_data += sizeof(void*) * gc_roots_max;
+	private_data += gc_threads.count * (sizeof(void*) + sizeof(hl_thread_info));
+	for(i=0;i<1<<GC_LEVEL0_BITS;i++)
+		if( hl_gc_page_map[i] != gc_level1_null )
+			private_data += sizeof(void*) * (1<<GC_LEVEL1_BITS);
+
+	fdump_i(private_data);
+	fdump_i(mark_stack_size); // keep separate
+	fdump_i(page_count);
+	gc_iter_pages(gc_dump_page);
+
 	// roots
 	fdump_i(gc_roots_count);
 	for(i=0;i<gc_roots_count;i++)