From 7fcec88336d0ed9da554becf63a2ab03c76a76f5 Mon Sep 17 00:00:00 2001 From: Yuxiao Mao Date: Tue, 7 Jan 2025 12:18:25 +0100 Subject: [PATCH] [hlmem] refacto for hide integration (#739) --- other/haxelib/hlmem/Analyzer.hx | 108 ++++ other/haxelib/hlmem/Block.hx | 16 +- other/haxelib/hlmem/FileReader.hx | 111 ++++ other/haxelib/hlmem/Main.hx | 172 ++++++ other/haxelib/hlmem/Memory.hx | 928 +++++++++++------------------- other/haxelib/hlmem/Result.hx | 157 +++++ other/haxelib/hlmem/TType.hx | 4 +- other/haxelib/memory.hxml | 2 +- 8 files changed, 884 insertions(+), 614 deletions(-) create mode 100644 other/haxelib/hlmem/Analyzer.hx create mode 100644 other/haxelib/hlmem/FileReader.hx create mode 100644 other/haxelib/hlmem/Main.hx create mode 100644 other/haxelib/hlmem/Result.hx diff --git a/other/haxelib/hlmem/Analyzer.hx b/other/haxelib/hlmem/Analyzer.hx new file mode 100644 index 000000000..3d1afb1b5 --- /dev/null +++ b/other/haxelib/hlmem/Analyzer.hx @@ -0,0 +1,108 @@ +package hlmem; + +import hlmem.Memory; +using format.hl.Tools; + +// A list of ansi colors is available at +// https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797#8-16-colors +enum abstract TextColor(Int) { + var Black = 30; + var Red = 31; + var Green = 32; + var Yellow = 33; + var Blue = 34; + var Magenta = 35; + var Cyan = 36; + var White = 37; +} + +class Analyzer { + + public static var displayProgress = true; + public static var displayFields : FieldsMode = Full; + public static var maxLines : Int = 100; + public static var useColor : Bool = true; + + public var code : format.hl.Data; + var mem : Memory; + var otherMems : Array = []; + + public function new() { + } + + public function loadBytecode( file : String ) { + if( code != null ) throw "Duplicate code"; + code = new format.hl.Reader(false).read(new haxe.io.BytesInput(sys.io.File.getBytes(file))); + Analyzer.log(file + " code loaded"); + } + + public function loadMemoryDump( file : String ) : Memory { + var m = new Memory(this); + m.load(file); + if( mem == null ) { + mem = m; + } else { + otherMems.push(m); + } + return m; + } + + public function build( filter : FilterMode = None ) { + mem.build(); + for( m2 in otherMems ) { + m2.buildBlockTypes(); + } + mem.otherMems = [for (i in otherMems) i]; + mem.filterMode = filter; + mem.buildFilteredBlocks(); + } + + public function nextDump() : Memory { + otherMems.push(mem); + mem = otherMems.shift(); + mem.otherMems = [for (i in otherMems) i]; + mem.build(); + mem.buildFilteredBlocks(); + var ostr = otherMems.length > 0 ? (" (others are " + otherMems.map(m -> m.memFile) + ")") : ""; + Analyzer.log("Using dump " + mem.memFile + ostr); + return mem; + } + + public inline function getMainMemory() { + return mem; + } + + public function getMemStats() : Array { + var objs = [mem.getMemStats()]; + for( m2 in otherMems ) { + objs.push(m2.getMemStats()); + } + return objs; + } + + public static function mb( v : Float ) : String { + 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"; + } + + public static inline function logProgress( current : Int, max : Int ) { + if( displayProgress && current % 1000 == 0 ) + Sys.print(Std.int((current * 1000.0 / max) / 10) + "% \r"); + if( displayProgress && current == max ) + Sys.print(" \r"); + } + + public static inline function log( msg : String ) { + Sys.println(msg); + } + + public static inline function withColor( str : String, textColor : TextColor ) { + if( !useColor ) + return str; + return '\x1B[${textColor}m${str}\x1B[0m'; + } + +} diff --git a/other/haxelib/hlmem/Block.hx b/other/haxelib/hlmem/Block.hx index 9baa10c6e..f2de63da7 100644 --- a/other/haxelib/hlmem/Block.hx +++ b/other/haxelib/hlmem/Block.hx @@ -24,6 +24,16 @@ abstract Pointer(haxe.Int64) { return haxe.Int64.shr(this,k); } + @:op(A == B) + public static function eq( a : Pointer, b : Pointer ) : Bool { + return a.value == b.value; + } + + @:op(A != B) + public static function neq( a : Pointer, b : Pointer ) : Bool { + return a.value != b.value; + } + public static var NULL(get,never) : Pointer; inline static function get_NULL() return new Pointer(0); @@ -41,7 +51,7 @@ class Page { public var kind : PageKind; public var size : Int; public var reserved : Int; - public var dataPosition : Int = -1; + public var dataPosition : Float; public function new() { } @@ -108,7 +118,7 @@ class Block { parents.push(b); } if( b.subs == null ) b.subs = []; - b.subs.push(new BlockSub(this,fid)); + b.subs.push(new BlockSub(this, fid)); } public function makeTID( prev : Block, withField : Bool ) { @@ -161,7 +171,7 @@ class Block { owner = parents[0]; } - function removeParent( p : Block ) { + public function removeParent( p : Block ) { if( parents != null ) { parents.remove(p); if( parents.length == 0 ) parents = null; diff --git a/other/haxelib/hlmem/FileReader.hx b/other/haxelib/hlmem/FileReader.hx new file mode 100644 index 000000000..4d15886f6 --- /dev/null +++ b/other/haxelib/hlmem/FileReader.hx @@ -0,0 +1,111 @@ +package hlmem; + +#if js +enum FileSeekMode { + SeekBegin; + SeekCur; + SeekEnd; +} +#else +typedef FileSeekMode = sys.io.FileSeek; +#end + +class FileReader { + + #if js + // js read file in once for better performance + var memBytes : haxe.io.Bytes; + var memPos : Int; + #else + var memInput : sys.io.FileInput; + #end + + public inline function new(file : String) { + #if js + memBytes = sys.io.File.getBytes(file); + memPos = 0; + #else + memInput = sys.io.File.read(file); + #end + } + + public inline function close() { + #if js + memBytes = null; + memPos = 0; + #else + if( memInput != null ) + memInput.close(); + memInput = null; + #end + } + + public inline function readString(length : Int) : String { + #if js + var str = memBytes.getString(memPos, 3); + memPos += 3; + #else + var str = memInput.read(3).toString(); + #end + return str; + } + + public inline function readByte() : Int { + #if js + var b = memBytes.get(memPos); + memPos += 1; + #else + var b = memInput.readByte(); + #end + return b; + } + + public inline function readInt32() : Int { + #if js + var i = memBytes.getInt32(memPos); + memPos += 4; + #else + var i = memInput.readInt32(); + #end + return i; + } + + public inline function readPointer( is64 : Bool ) : Block.Pointer { + var low = readInt32(); + var high = is64 ? readInt32() : 0; + return cast haxe.Int64.make(high,low); + } + + public inline function tell() : Float { + #if js + return memPos; + #else + return tell2(@:privateAccess memInput.__f); + #end + } + + #if (hl && hl_ver >= version("1.12.0")) + @:hlNative("std","file_seek2") static function seek2( f : sys.io.File.FileHandle, pos : Float, mode : Int ) : Bool { return false; } + @:hlNative("std","file_tell2") static function tell2( f : sys.io.File.FileHandle ) : Float { return 0; } + #end + + // pos will be cast to Int64 + public inline function seek( pos : Float, mode : FileSeekMode ) { + #if js + if( pos > 0x7FFFFFFF ) throw haxe.io.Error.Custom("seek out of bounds"); + var dpos = Std.int(pos); + switch( mode ) { + case SeekBegin: + memPos = dpos; + case SeekCur: + memPos += dpos; + case SeekEnd: + memPos = memBytes.length + dpos; + } + #else + if( !seek2(@:privateAccess memInput.__f, pos, mode.getIndex()) ) + throw haxe.io.Error.Custom("seek2 failure()"); + #end + } + +} diff --git a/other/haxelib/hlmem/Main.hx b/other/haxelib/hlmem/Main.hx new file mode 100644 index 000000000..76d60fc1b --- /dev/null +++ b/other/haxelib/hlmem/Main.hx @@ -0,0 +1,172 @@ +package hlmem; + +class Main { + static var args : Array; + static var analyzer : Analyzer; + + static var sortByCount : Bool = false; + + 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 function command() : Bool { + Sys.print(Analyzer.withColor("> ", Red)); + var args = parseArgs(Main.args.length > 0 ? Main.args.shift() : Sys.stdin().readLine()); + var cmd = args.shift(); + var mem = analyzer.getMainMemory(); + switch( cmd ) { + case "exit", "quit", "q": + return false; + case "stats": + var res = mem.getMemStats(); + res.print(); + case "types": + var res = mem.getBlockStatsByType(); + res.sort(sortByCount); + res.print(); + case "false": + var res = mem.getFalsePositives(args.shift()); + res.print(); + case "unknown": + var res = mem.getUnknown(); + res.sort(sortByCount); + res.print(); + case "locate": + var lt = mem.resolveType(args.shift()); + if( lt != null ) { + var res = mem.locate(lt, Std.parseInt(args.shift())); + res.sort(sortByCount); + res.print(); + } + case "count": + var lt = mem.resolveType(args.shift()); + if( lt != null ) { + var res = mem.count(lt, args); + res.sort(sortByCount); + res.printWithSum = true; + res.print(); + } + case "parents": + var lt = mem.resolveType(args.shift()); + if( lt != null ) { + var res = mem.parents(lt); + res.sort(sortByCount); + res.print(); + } + case "subs": + var lt = mem.resolveType(args.shift()); + if( lt != null ) { + var res = mem.subs(lt, Std.parseInt(args.shift())); + res.sort(sortByCount); + res.print(); + } + case "sort": + switch( args.shift() ) { + case "mem": + sortByCount = false; + case "count": + sortByCount = true; + case mode: + Sys.println("Unknown sort mode " + mode); + } + case "fields": + switch( args.shift() ) { + case "full": + Analyzer.displayFields = Full; + case "none": + Analyzer.displayFields = None; + case "parents": + Analyzer.displayFields = Parents; + case mode: + Sys.println("Unknown fields mode " + mode); + } + case "filter": + switch( args.shift() ) { + case "n" | "none": + mem.filterMode = None; + case "i" | "intersect": + mem.filterMode = Intersect; + case "u" | "unique": + mem.filterMode = Unique; + case mode: + Sys.println("Unknown filter mode " + mode); + } + mem.buildFilteredBlocks(); + case "nextDump": + analyzer.nextDump(); + case "lines": + var v = args.shift(); + if( v != null ) + Analyzer.maxLines = Std.parseInt(v); + Sys.println(Analyzer.maxLines == 0 ? "Lines limit disabled" : Analyzer.maxLines + " maximum lines displayed"); + case null: + // Ignore + default: + Sys.println("Unknown command " + cmd); + } + return true; + } + + static function main() { + analyzer = new Analyzer(); + args = Sys.args(); + + //hl.Gc.dumpMemory(); Sys.command("cp memory.hl test.hl"); + + var code = null, memory = null; + while( args.length > 0 ) { + var arg = args.shift(); + if( StringTools.endsWith(arg, ".hl") ) { + code = arg; + analyzer.loadBytecode(arg); + continue; + } + if( arg == "-c" || arg == "--color" ) + continue; + if( arg == "--no-color" ) { + Analyzer.useColor = false; + continue; + } + if( arg == "--args" ) { + Analyzer.displayProgress = false; + break; + } + if (memory == null) { + memory = arg; + } + analyzer.loadMemoryDump(arg); + } + 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) ) + analyzer.loadMemoryDump(memory); + } + analyzer.build(); + + while( command() ) { + } + } +} diff --git a/other/haxelib/hlmem/Memory.hx b/other/haxelib/hlmem/Memory.hx index 886ff8577..49647d744 100644 --- a/other/haxelib/hlmem/Memory.hx +++ b/other/haxelib/hlmem/Memory.hx @@ -1,86 +1,10 @@ package hlmem; import hlmem.Block; +import hlmem.Result; 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, 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(""); - 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 tid = id & 0xFFFFFF; - var t = mem.types[tid]; - var tstr = t.toString(); - tstr += Memory.withColor("#" + tid, 35); - 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){ - 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; @@ -94,57 +18,54 @@ enum FilterMode { } class Memory { - public var memoryDump : sys.io.FileInput; - - #if js - var memBytes : haxe.io.Bytes; - var memPos : Int; - #end + var analyzer : Analyzer; + var code : format.hl.Data; + var buildDone : Bool = false; + // Load from dump + public var memFile : String; + var memFileReader : FileReader; public var is64 : Bool; public var bool32 : Bool; public var ptrBits : Int; - - public var types : Array; - - var otherMems : Array; - 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; + var blocks : Array; var roots : Array; var stacks : Array; + var baseTypes : Array<{ t : HLType, p : Pointer }>; var typesPointers : Array; var closuresPointers : Array; - var blocks : Array; - var filteredBlocks : Array = []; - var baseTypes : Array<{ t : HLType, p : Pointer }>; - var all : Block; - var toProcess : Array; + // Build using dump + code + var types : Array; + var pointerType : PointerMap; + var pointerBlock : PointerMap; + var all : Block; var tdynObj : TType; var tdynObjData : TType; - var pointerBlock : PointerMap; - var pointerType : PointerMap; var falseCandidates : Array<{ b : Block, f : Block, idx : Int }>; - var currentTypeIndex = 0; + // Cache + var memStats : MemStats = null; + var resolveLastIndex : Int = 0; var resolveCache : Map = new Map(); - var maxLines : Int = 100; - function new() { + // Filter + public var filterMode : FilterMode = None; + public var otherMems : Array = []; + var filteredBlocks : Array = []; + + // ----- Init ----- + + public function new( analyzer : Analyzer ) { + this.analyzer = analyzer; } - public function typeSize( t : HLType ) { + // ----- Type ----- + + public function typeSize( t : HLType ) : Int { return switch( t ) { case HVoid: 0; case HUi8: 1; @@ -171,70 +92,50 @@ class Memory { 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"); + public function getTypeById( tid : Int ) : TType { + var tid = tid & 0xFFFFFF; + return types[tid]; + } + + public function getTypeString( id : Int, withTstr : Bool, withId : Bool, withField : Bool ) : String { + var tid = id & 0xFFFFFF; + var t = types[tid]; + var tstr = withTstr ? t.toString() : ""; + if( withId ) + tstr += Analyzer.withColor("#" + tid, Magenta); + var fid = id >>> 24; + if( withField && fid > 0 ) { + var f = t.memFieldsNames[fid-1]; + if( f != null ) tstr += "." + f; + } + return tstr; } + // ----- Load & Build ----- - 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 readInt() : Int { + return memFileReader.readInt32(); } 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); + return memFileReader.readPointer(is64); } - 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 goto( b : Block ) { + var p = b.page.dataPosition; + if( p < 0 ) throw "assert"; + memFileReader.seek(p + b.addr.sub(b.page.addr), SeekBegin); } - function loadMemory( arg : String ) { - memFile = arg; - memoryDump = sys.io.File.read(arg); + public function load( file : String ) { + memFile = file; + memFileReader = new FileReader(file); 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" ) + if( memFileReader.readString(3) != "HMD" ) throw "Invalid memory dump file"; - version = memoryDump.readByte() - "0".code; - #end + version = memFileReader.readByte() - "0".code; if( version != 1 ) throw "Unsupported format version "+version; @@ -269,7 +170,7 @@ class Memory { b.page = p; b.addr = ptr; b.size = size; - b.typePtr = @:privateAccess new Pointer(0); + b.typePtr = Pointer.NULL; b.owner = null; b.typeKind = null; b.subs = null; @@ -280,13 +181,8 @@ class Memory { } if( p.memHasPtr() ) { - #if js - p.dataPosition = memPos; - memPos += p.size; - #else - p.dataPosition = memoryDump.tell(); - memoryDump.seek(p.size, SeekCur); - #end + p.dataPosition = memFileReader.tell(); + memFileReader.seek(p.size, SeekCur); } pages.push(p); } @@ -315,56 +211,8 @@ class Memory { 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"); - } + Analyzer.log(file + " dump loaded"); } function getTypeNull( t : TType ) { @@ -383,111 +231,12 @@ class Memory { return r; } - function goto( b : Block ) { - var p = b.page.dataPosition; - if( p < 0 ) throw "assert"; + public function build() { + if( buildDone ) return; + if( memFile == null ) throw "Missing .dump file"; + if( memFileReader == null ) memFileReader = new FileReader(memFile); - #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 = ''; - 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(); + buildBlockTypes(false); // look in roots (higher ownership priority) all = new Block(); @@ -518,10 +267,10 @@ class Memory { tdynObjData = new TType(types.length, HAbstract("dynobjdata")); types.push(tdynObjData); - toProcess = blocks.copy(); + var toProcess = blocks.copy(); falseCandidates = []; while( toProcess.length > 0 ) - buildHierarchy(); + toProcess = buildHierarchy(toProcess); // look in stacks (low priority of ownership) var tstacks = new TType(types.length, HAbstract("stack")); @@ -567,7 +316,7 @@ class Memory { // assign depths - Sys.println("Computing depths..."); + Analyzer.log("Computing depths..."); broot.markDepth(); for( b in bstacks ) b.markDepth(); @@ -602,7 +351,7 @@ class Memory { if( b.owner == null ) { unRef++; if( unRef < 100 ) - log(" "+b.addr.toString()+"["+b.size+"] is not referenced"); + Analyzer.log(" "+b.addr.toString()+"["+b.size+"] is not referenced"); continue; } @@ -628,42 +377,116 @@ class Memory { for( t in types ) falseCount += t.falsePositive; - log("Hierarchy built, "+falseCount+" false positives, "+unRef+" unreferenced"); + buildDone = true; + memFileReader.close(); + memFileReader = null; + Analyzer.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()]); - } + /** + * Can be used instead of build() to only initialize blocks for filter. Must be called after load(). + */ + public function buildBlockTypes( closeFile : Bool = true ) { + if( types != null ) return; // Already built types + if( code == null ) code = analyzer.code; + if( code == null ) throw "Missing .hl file"; + if( code.types.length != typesPointers.length ) throw "Types count mismatch"; - function printUnknown() { - var byT = new Map(); - for( b in blocks ) { - if( b.type != null ) continue; + var cid = 0; + types = [for( i in 0...code.types.length ) new TType(i, code.types[i])]; + pointerType = new PointerMap(); + 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 = ''; + 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: + } + } - var o = b; - while( o != null && o.type == null ) - o = o.owner; + 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 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); + var progress = 0; + pointerBlock = new PointerMap(); + + var missingTypes = 0; + for( b in blocks ) { + progress++; + Analyzer.logProgress(progress, blocks.length); + 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 + } } - inf.count++; - inf.mem += b.size; + if( b.type != null && !b.type.isDyn ) + b.type = getTypeNull(b.type); + if( b.type != null ) + b.typeKind = KHeader; + pointerBlock.set(b.addr, b); + } + + if( closeFile ) { + memFileReader.close(); + memFileReader = null; } - 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())); + + getMemStats().print(); + Analyzer.log("Block types built, " + missingTypes + " blocks with unresolved type"); } - function buildHierarchy() { + function buildHierarchy( toProcess : Array ) : Array { var progress = 0; var blocks = toProcess; toProcess = []; @@ -673,14 +496,13 @@ class Memory { for( b in blocks ) { progress++; - if( displayProgress && progress % 10000 == 0 ) - Sys.print((Std.int(progress * 1000.0 / blocks.length) / 10) + "% \r"); + Analyzer.logProgress(progress, blocks.length); if( !b.page.memHasPtr() ) continue; if( b.type != null && !b.type.hasPtr ) - log(" Scanning "+b.type+" "+b.addr.toString()); + Analyzer.log(" Scanning "+b.type+" "+b.addr.toString()); goto(b); var fields = null; @@ -738,52 +560,108 @@ class Memory { } } } + return toProcess; } - function printByType() { - var ctx = new Stats(this); - for( b in filteredBlocks ) - ctx.add(b.type, b.size); - ctx.print(); + // ----- Stats ----- + + public function getMemStats() : MemStats { + if( memStats != null ) + return memStats; + var pagesSize = 0, reserved = 0; + var used = 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; + + memStats = { + 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, + filterMode: filterMode, + filteredBlockCount : filteredBlocks.length, + }; + return memStats; } - 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; + public function getFalsePositives( ?typeStr : String ) : FalsePositiveStats { + var ctx = new FalsePositiveStats(); + for( t in types ) + if( t.falsePositive > 0 && (typeStr == null || t.toString().indexOf(typeStr) >= 0) ) { + ctx.add(t); } + ctx.sort(); + return ctx; + } + + public function getUnknown() : BlockStats { + var ctx = new BlockStats(this); + 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; + ctx.add(t, b.size); } - if( showError ) - log("Type not found '"+str+"'"); - return null; + return ctx; } - function locate( tstr : String, up = 0 ) { - var ctx = new Stats(this); + public function getBlockStatsByType() : BlockStats { + var ctx = new BlockStats(this); + for( b in filteredBlocks ) + ctx.add(b.type, b.size); + return ctx; + } - var lt = null; - if (StringTools.startsWith(tstr, "#")) { - var id = Std.parseInt(tstr.substr(1, tstr.length)); - if (id != null && id >= 0) { - lt = types[id]; + public function resolveType( str : String, showError = true ) : TType { + if( StringTools.startsWith(str, "#") ) { + var id = Std.parseInt(str.substr(1, str.length)); + if (id != null) { + return getTypeById(id); } } else { - lt = resolveType(tstr); + var t = resolveCache.get(str); + if( t != null ) + return t; + for( i in resolveLastIndex...types.length ) { + var t = types[i]; + var tstr = t.toString(); + if (tstr != null) { + resolveCache.set(tstr, t); + resolveLastIndex = i + 1; + if( tstr == str ) + return t; + } + } } - if( lt == null ) return ctx; + if( showError ) + Analyzer.log("Type not found '" + str + "'"); + return null; + } + + public function locate( lt : TType, up = 0 ) : BlockStats { + var ctx = new BlockStats(this); 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 = []; @@ -793,10 +671,14 @@ class Memory { owner = owner.owner; if( owner != null ) { - tl.push(owner.makeTID(b,displayFields == Full)); + tl.push(owner.makeTID(b, Analyzer.displayFields == Full)); var k : Int = up; - while( owner.owner != null && k-- > 0 && owner.owner != all ) { - var tag = owner.owner.makeTID(owner,displayFields != None); + while( k-- > 0 && owner.owner != all ) { + if( owner.owner == null ) { + tl.unshift(0); + break; + } + var tag = owner.owner.makeTID(owner, Analyzer.displayFields != None); owner = owner.owner; // remove recursive sequence for( i => tag2 in tl ) @@ -809,12 +691,13 @@ class Memory { if( seq ) { for( k in 0...i ) tl.shift(); tag = -1; - k += i + 1; + // do not add i+1 here to prevent infinit loop + k += i; } break; } // don't display virtual wrappers - if( displayFields != None && owner.type != null && isVirtualField(tag) && owner.type.t.match(HVirtual(_)) ) { + if( Analyzer.displayFields != None && owner.type != null && isVirtualField(tag) && owner.type.t.match(HVirtual(_)) ) { tag = -1; k++; } @@ -825,34 +708,31 @@ class Memory { ctx.addPath(tl, b.size); } - ctx.print(); return ctx; } - function count( tstr : String, excludes : Array ) { - var t = resolveType(tstr); - if( t == null ) return; + public function count( lt : TType, excludes : Array ) : BlockStats { + var ctx = new BlockStats(this); var texclude = []; for( e in excludes ) { var t = resolveType(e); - if( t == null ) return; + if( t == null ) return ctx; texclude.push(t); } - var ctx = new Stats(this); Block.MARK_UID++; var mark = []; for( b in filteredBlocks ) - if( b.type == t ) + if( b.type == lt ) 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); + return ctx; } - function visitRec( b : Block, ctx : Stats, exclude : Array, mark : Array ) { + function visitRec( b : Block, ctx : BlockStats, exclude : Array, mark : Array ) { 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; @@ -861,43 +741,22 @@ class Memory { 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); + public function parents( lt : TType, up = 0 ) : BlockStats { + var ctx = new BlockStats(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(); + for( p in b.getParents() ) { + ctx.addPath([p.makeTID(b, Analyzer.displayFields != None)], p.size); + } + return ctx; } - 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); + public function subs( lt : TType, down = 0 ) { + var ctx = new BlockStats(this); var mark = new Map(); for( b in filteredBlocks ) if( b.type == lt ) { - function addRec(tl:Array,b:Block, k:Int) { + function addRec(tl:Array, b:Block, k:Int) { if( k < 0 ) return; if( mark.exists(b) ) return; @@ -907,46 +766,61 @@ class Memory { if( b.subs != null ) { k--; for( s in b.subs ) - addRec(tl.copy(),s.b, k); + addRec(tl.copy(), s.b, k); } } - addRec([], b, down); + if( b.subs != null ) { + for( s in b.subs ) + addRec([], s.b, down); + } } - ctx.print(); + return ctx; + } + + // ----- Tools with side-effect ----- + + public function removeParent( lt : TType, pt : TType ) { + for( b in filteredBlocks ) + if( b.type == lt ) + for( p in b.getParents() ) + if( p.type == pt ) + b.removeParent(p); } - public function setFilterMode(m: FilterMode) { - filterMode = m; - switch( m ) { + // ----- Filter ----- + + public function buildFilteredBlocks() { + memStats = null; + switch( filterMode ) { case None: filteredBlocks = blocks.copy(); + case _ if (otherMems.length == 0): + 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) ) + Analyzer.logProgress(progress, blocks.length); + if( !isBlockIgnored(b) ) filteredBlocks.push(b); } - if( displayProgress ) - Sys.print(" \r"); } } - public function isBlockIgnored(b: Block, m: FilterMode) { - switch( m ) { + + function isBlockIgnored( b : Block ) { + switch( filterMode ) { case None: return false; case Intersect: - for( m in otherMems ) { - var b2 = m.pointerBlock.get(b.addr); - if( b2 == null || b2.typePtr != b.typePtr || b2.size != b.size) + for( m2 in otherMems ) { + var b2 = m2.pointerBlock.get(b.addr); + if( b2 == null || b2.typePtr != b.typePtr || b2.size != b.size ) return true; } case Unique: - for( m in otherMems ) { - var b2 = m.pointerBlock.get(b.addr); + for( m2 in otherMems ) { + var b2 = m2.pointerBlock.get(b.addr); if( b2 != null && b2.typePtr == b.typePtr && b2.size == b.size ) return true; } @@ -954,166 +828,4 @@ class Memory { 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 = true; - static function main() { - var m = new Memory(); - var others: Array = []; - 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" ) - continue; - if( arg == "--no-color" ) { - useColor = false; - 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"; - } -} \ No newline at end of file +} diff --git a/other/haxelib/hlmem/Result.hx b/other/haxelib/hlmem/Result.hx new file mode 100644 index 000000000..8ee2b410a --- /dev/null +++ b/other/haxelib/hlmem/Result.hx @@ -0,0 +1,157 @@ +package hlmem; + +abstract class Result { + abstract public function print() : Void; +} + +@:structInit +class MemStats extends Result { + public var memFile : String; + public var free : Int; + public var used : Int; + public var filterUsed : Int; + public var totalAllocated : Int; + public var gc : Int; + public var pagesCount : Int; + public var pagesSize : Int; + public var rootsCount : Int; + public var stackCount : Int; + public var typesCount : Int; + public var closuresCount : Int; + public var blockCount : Int; + public var filterMode : Memory.FilterMode; + public var filteredBlockCount : Int; + + public function toString() { + var str = Analyzer.withColor("--- " + memFile + " ---", Cyan); + str += "\n" + pagesCount + " pages, " + Analyzer.mb(pagesSize) + " memory"; + str += "\n" + rootsCount + " roots, "+ stackCount + " stacks"; + str += "\n" + typesCount + " types, " + closuresCount + " closures"; + str += "\n" + blockCount + " live blocks, " + Analyzer.mb(used) + " used, " + Analyzer.mb(free) + " free, "+ Analyzer.mb(gc) + " gc"; + if( filterMode != None ) + str += "\n" + filteredBlockCount + " blocks in filter, " + Analyzer.mb(filterUsed) + " used"; + return str; + } + + public function print() { + Analyzer.log(this.toString()); + } +} + +class FalsePositiveStats extends Result { + public var falses : Array; + public function new() { + } + public function add( t : TType ) { + falses.push(t); + } + public function sort() { + falses.sort((t1, t2) -> t1.falsePositive - t2.falsePositive); + } + public function print() { + for( f in falses ) + Analyzer.log(f.falsePositive + " count " + f + " " + f.falsePositiveIndexes + "\n "+ [for( f in f.memFields ) format.hl.Tools.toString(f.t)]); + } +} + +class BlockStatsElement { + var mem : Memory; + public var tl : Array; + public var count : Int = 0; + public var size : Int = 0; + public function new( mem : Memory, tl : Array ) { + this.mem = mem; + this.tl = tl; + } + public function add( size : Int ) { + this.count ++; + this.size += size; + } + public function getTypes() { + var ttypes = []; + for( tid in tl ) + ttypes.push(mem.getTypeById(tid)); + return ttypes; + } + public function getNames( withTstr : Bool = true, withId : Bool = true, withField : Bool = true ) { + var tpath = []; + for( tid in tl ) + tpath.push(mem.getTypeString(tid, withTstr, withId, withField)); + return tpath; + } +} + +class BlockStats extends Result { + var mem : Memory; + var byT : Map; + public var allT : Array; + public var printWithSum : Bool; + + public function new( mem : Memory ) { + this.mem = mem; + this.byT = []; + this.allT = []; + this.printWithSum = false; + } + + public function add( t : TType, size : Int ) { + return addPath([t == null ? 0 : t.tid], size); + } + + public function addPath( tl : Array, size : Int ) { + var key = tl.join(" "); + var inf = byT.get(key); + if( inf == null ) { + inf = new BlockStatsElement(mem, tl); + byT.set(key, inf); + allT.push(inf); + } + inf.add(size); + } + + public function get( t : TType ) : BlockStatsElement { + return getPath([t == null ? 0 : t.tid]); + } + + public function getPath( tl : Array ) : BlockStatsElement { + var key = tl.join(" "); + return byT.get(key); + } + + 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.size - i2.size : i2.size - i1.size); + } + + /** + * Create a copy of the current BlockStats from `pos` to `end`. + * (If call add on the copy, a separate entry will be created.) + */ + public function slice( pos : Int, ?end : Int ) : BlockStats { + var newS = new BlockStats(mem); + newS.allT = allT.slice(pos, end); + // Do not set byT to reduce memory usage and prevent overwrite parent's elements + newS.printWithSum = printWithSum; + return newS; + } + + public function print() { + var totCount = 0; + var totSize = 0; + var max = Analyzer.maxLines; + if( max > 0 && allT.length > max ) { + Analyzer.log(""); + allT = allT.slice(allT.length - max); + } + for( i in allT ) { + totCount += i.count; + totSize += i.size; + Analyzer.log(Analyzer.withColor(i.count + " count, " + Analyzer.mb(i.size) + " ", Yellow) + i.getNames().join(Analyzer.withColor(' > ', Cyan))); + } + if( printWithSum ) + Analyzer.log("Total: " + totCount + " count, " + Analyzer.mb(totSize)); + } + +} diff --git a/other/haxelib/hlmem/TType.hx b/other/haxelib/hlmem/TType.hx index bc96f84d3..db7fc7fe2 100644 --- a/other/haxelib/hlmem/TType.hx +++ b/other/haxelib/hlmem/TType.hx @@ -175,7 +175,7 @@ class TType { public function toString() { switch( t ) { case HAbstract("roots"): - return Memory.withColor("roots", 32); + return Analyzer.withColor("roots", Green); case HAbstract(p): return p; case HFun(_), HMethod(_): @@ -185,4 +185,4 @@ class TType { } } -} \ No newline at end of file +} diff --git a/other/haxelib/memory.hxml b/other/haxelib/memory.hxml index a2326b648..acb79fd87 100644 --- a/other/haxelib/memory.hxml +++ b/other/haxelib/memory.hxml @@ -1,3 +1,3 @@ -lib format --main hlmem.Memory +-main hlmem.Main -hl memory.hl