diff --git a/ci/build-stanza-version.txt b/ci/build-stanza-version.txt index 71434844..bf293833 100644 --- a/ci/build-stanza-version.txt +++ b/ci/build-stanza-version.txt @@ -7,4 +7,4 @@ # like 1.23.45 # # Use version 0.17.56 to compile 0.18.0 -0.18.96 +0.18.97 diff --git a/compiler/codegen.stanza b/compiler/codegen.stanza index 96c140bc..da9a5375 100644 --- a/compiler/codegen.stanza +++ b/compiler/codegen.stanza @@ -72,6 +72,7 @@ public defstruct AsmStubs : collect-garbage:Int saved-regs:Tuple saved-fregs:Tuple + heap-statistics:Int class-table:Int global-root-table:Int stackmap-table:Int @@ -128,6 +129,7 @@ public defn AsmStubs (backend:Backend) : next(id-counter) ;collect-garbage:Int saved-regs ;saved-regs:Tuple saved-fregs ;saved-fregs:Tuple + next(id-counter) ;heap-statistics:Int next(id-counter) ;class-table:Int next(id-counter) ;global-root-table:Int next(id-counter) ;stackmap-table:Int @@ -334,6 +336,7 @@ public defn compile-entry-function (emitter:CodeEmitter, stubs:AsmStubs) : #label(safepoint-table) ;safepoint-table:ptr #label(debug-table) ;debug-table:ptr #label(local-var-table) ;local-var-table:ptr + #label(heap-statistics) ;heap-statistics:ptr #label(class-table) ;class-table:ptr #label(global-root-table) ;global-root-table:ptr #label(stackmap-table) ;stackmap-table:ptr diff --git a/compiler/compiler.stanza b/compiler/compiler.stanza index 825d7765..26e27dfc 100644 --- a/compiler/compiler.stanza +++ b/compiler/compiler.stanza @@ -243,6 +243,7 @@ public defn compile (settings:BuildSettings, system:System) : println("Build target %~ is already up-to-date." % [build-target?(settings*)]) else : setup-system-flags(settings*) + add-flag(`HEAP-ANALYZER) val proj-params = ProjParams(compiler-flags(), optimize?(settings*), debug?(settings*), false, build-from-source?(settings), pkg-cache-dir(settings*)) val proj-manager = ProjManager(proj, proj-params, auxfile) val comp-result = compile(proj-manager, auxfile, build-inputs!(settings*), vm-packages(settings*), asm?(settings*), pkg-dir(settings*), diff --git a/compiler/stitcher.stanza b/compiler/stitcher.stanza index 0c8bf64a..7b1a922f 100644 --- a/compiler/stitcher.stanza +++ b/compiler/stitcher.stanza @@ -856,6 +856,18 @@ public defn Stitcher (packages:Collection, bindings:Bindings|False, s E $ DefText() E $ Comment("End of Data Tables") + defn emit-heap-statistics-table (code-emitter:CodeEmitter) : + defn E (i:Ins) : emit(code-emitter, i) + E $ Comment("Heap Statistics Table") + E $ DefData() + E $ Label(/heap-statistics(stubs)) + E $ DefLong(to-long(num-concrete-classes)) + for i in 0 to num-concrete-classes do : + E $ DefLong(0L) ; num-uses + E $ DefLong(0L) ; num-bytes + E $ DefText() + E $ Comment("End of Heap Statistics Table") + ;Emit class table defn emit-class-table (code-emitter:CodeEmitter) : defn E (i:Ins) : emit(code-emitter, i) @@ -995,6 +1007,7 @@ public defn Stitcher (packages:Collection, bindings:Bindings|False, s ; so they can be dynamically imported by other executables if generate-export-directives-table?(backend(stubs)) : emit-export-table(code-emitter) + emit-heap-statistics-table(code-emitter) defmethod stubs (this) : stubs defmethod core-fn (this, id:FnId) : diff --git a/compiler/vm-structures.stanza b/compiler/vm-structures.stanza index 3bad507f..746da736 100644 --- a/compiler/vm-structures.stanza +++ b/compiler/vm-structures.stanza @@ -31,6 +31,7 @@ public lostanza deftype VMState : var safepoint-table: ptr ;(Permanent State) var debug-table: ptr ;(Permanent State) var local-var-table: ptr ;(Permanent State) + var heap-statistics: ptr ;(Permanent State) var class-table: ptr ;(Permanent State) ;Interpreted Mode Tables var instructions: ptr ;(Permanent State) diff --git a/core/core.stanza b/core/core.stanza index a2a602ac..6fb7c819 100644 --- a/core/core.stanza +++ b/core/core.stanza @@ -209,36 +209,75 @@ protected lostanza deftype ArrayRecord : num-item-roots:int roots:int ... +lostanza deftype HeapStatistics : + length: long + entries: HeapStatistic ... + +protected lostanza deftype HeapStatistic : + var num-uses:long + var num-bytes:long + ;The first fields in VMState are used by the core library ;in both compiled and interpreted mode. The last fields ;are used only in compiled mode. ;Permanent state changes in-between each code load. ;Variable state changes in-between each boundary change. -protected lostanza deftype VMState : - ;Compiled and Interpreted Mode - global-offsets: ptr ;(Permanent State) - global-mem: ptr ;(Permanent State) - var sig-handler: long ;(Permanent State) - var current-coroutine-ptr: ptr ;[TODO] Change to long to represent reference. - var stepping-coroutine-ptr: ptr ;[TODO] Change to long to represent reference. - const-table: ptr ;(Permanent State) - const-mem: ptr ;(Permanent State) - data-offsets: ptr ;(Permanent State) - data-mem: ptr ;(Permanent State) - code-offsets: ptr ;(Permanent State) - registers: ptr ;(Permanent State) - system-registers: ptr ;(Permanent State) - var heap: Heap ;(Variable State) - safepoint-table: ptr ;(Variable State) - debug-table: ptr ;(Variable State) - local-var-table: ptr ;(Variable State) - ;Compiled Mode Tables - class-table: ptr - global-root-table: ptr - stackmap-table: ptr> - stack-trace-table: ptr - extern-table: ptr - extern-defn-table: ptr +#if-defined(BOOTSTRAP) : + + protected lostanza deftype VMState : + ;Compiled and Interpreted Mode + global-offsets: ptr ;(Permanent State) + global-mem: ptr ;(Permanent State) + var sig-handler: long ;(Permanent State) + var current-coroutine-ptr: ptr ;[TODO] Change to long to represent reference. + var stepping-coroutine-ptr: ptr ;[TODO] Change to long to represent reference. + const-table: ptr ;(Permanent State) + const-mem: ptr ;(Permanent State) + data-offsets: ptr ;(Permanent State) + data-mem: ptr ;(Permanent State) + code-offsets: ptr ;(Permanent State) + registers: ptr ;(Permanent State) + system-registers: ptr ;(Permanent State) + var heap: Heap ;(Variable State) + safepoint-table: ptr ;(Variable State) + debug-table: ptr ;(Variable State) + local-var-table: ptr ;(Variable State) + ;Compiled Mode Tables + class-table: ptr + global-root-table: ptr + stackmap-table: ptr> + stack-trace-table: ptr + extern-table: ptr + extern-defn-table: ptr + +#else: + + protected lostanza deftype VMState : + ;Compiled and Interpreted Mode + global-offsets: ptr ;(Permanent State) + global-mem: ptr ;(Permanent State) + var sig-handler: long ;(Permanent State) + var current-coroutine-ptr: ptr ;[TODO] Change to long to represent reference. + var stepping-coroutine-ptr: ptr ;[TODO] Change to long to represent reference. + const-table: ptr ;(Permanent State) + const-mem: ptr ;(Permanent State) + data-offsets: ptr ;(Permanent State) + data-mem: ptr ;(Permanent State) + code-offsets: ptr ;(Permanent State) + registers: ptr ;(Permanent State) + system-registers: ptr ;(Permanent State) + var heap: Heap ;(Variable State) + safepoint-table: ptr ;(Variable State) + debug-table: ptr ;(Variable State) + local-var-table: ptr ;(Variable State) + heap-statistics: ptr ;(Variable State) + ;Compiled Mode Tables + class-table: ptr + global-root-table: ptr + stackmap-table: ptr> + stack-trace-table: ptr + extern-table: ptr + extern-defn-table: ptr lostanza deftype ExternTable : length: long @@ -2776,6 +2815,89 @@ public lostanza defn min (x:long, y:long) -> long : if x < y : return x else : return y +;============================================================ +;===================== Heap Analyzer ======================== +;============================================================ + +#if-defined(BOOTSTRAP) : + + public defn analyze-heap () -> Long : 0L + +#else : + + public defstruct HeapStat : + tag : Int ; type + name : String ; name of type + num-uses : Long ; num references in heap + num-bytes : Long ; num bytes occupied in heap + with: + printer => true + + ; run gc, collect heap stats while walking each object in nursery and heap + lostanza defn do-analyze-heap (stats:ref>) -> ref : + val vms:ptr = call-prim flush-vm() + run-garbage-collector() + val num-classes = vms.heap-statistics.length + clear(addr(vms.heap-statistics.entries), num-classes * sizeof(HeapStatistic)) + val hsize = do-analyze-heap(vms.heap.start, vms.heap.old-objects-end, vms) + val nursery = nursery-start(addr(vms.heap)) + val nsize = do-analyze-heap(nursery, vms.heap.top, vms) + for (var i:int = 0, i < num-classes, i = i + 1) : + val heap-stat = addr(vms.heap-statistics.entries[i]) + val num-uses = heap-stat.num-uses + if num-uses > 0L : + add(stats, HeapStat(new Int{i as int}, String(class-name(i)), new Long{num-uses}, new Long{heap-stat.num-bytes})) + return new Long{ hsize + nsize } + + ; collect heap stats while walking each object in consecutive range of memory + lostanza defn do-analyze-heap (pstart:ptr, pend:ptr, vms:ptr) -> long : + var p:ptr = pstart + while p < pend : + val tag = [p] as int + val class = vms.class-table[tag].record + var size:long = 0L + if class.item-size == 0 : + size = object-size-on-heap(class.size) + else : + val class = class as ptr + val array = p as ptr + val len = array.slots[0] + val base-size = class.base-size + val item-size = class.item-size + val my-size = base-size + item-size * len + size = object-size-on-heap(my-size) + p = p + size + val stat = addr(vms.heap-statistics.entries[tag]) + stat.num-uses = stat.num-uses + 1L + stat.num-bytes = stat.num-bytes + size + return (pend as long) - (pstart as long) + + public defstruct HeapStats : + total-size : Long + entries : Tuple + + ; public interface to heap analyzer collecting and printing out stats + public defn analyze-heap () -> HeapStats : + val stats = Vector() + val size = do-analyze-heap(stats) + val res = reverse(to-list(lazy-qsort(num-bytes, stats))) + HeapStats(size, to-tuple(res)) + + public defn dump (stats:HeapStats) : + println("Heap size %_" % [total-size(stats)]) + var max-bytes-size = reduce(max, length("Size"), seq({ length(to-string(num-bytes(_))) }, entries(stats))) + var max-perc-size = 6 + var max-uses-size = reduce(max, length("Uses"), seq({ length(to-string(num-uses(_))) }, entries(stats))) + defn pad (s:String, n:Int) -> String : append-all(cat(repeatedly({ " " }, (n - length(s))), [s])) + defn pad0 (s:String, n:Int) -> String : append-all(cat([s], repeatedly({ "0" }, (n - length(s))))) + println(" %_ %_ %_: %_" % [pad("Size", max-bytes-size), pad("Perc", max-perc-size), pad("Use", max-uses-size), "Type"]) + for hc in entries(stats) do : + val pt = to-int(to-double(num-bytes(hc)) * 10000.0 / to-double(total-size(stats))) + val p = string-join $ [pt / 100, ".", pad0(to-string(pt - ((pt / 100) * 100)), 2)] + println(" %_ %_%% %_: %_" % [ + pad(to-string(num-bytes(hc)), max-bytes-size), pad(p, max-perc-size), pad(to-string(num-uses(hc)), max-uses-size), name(hc)]) + + ;============================================================ ;===================== Debugging ============================ ;============================================================