export async function dumpheapAsync()

in cli/gdb.ts [436:951]


export async function dumpheapAsync(filename?: string) {
    let memStart = findAddr("_sdata", true) || findAddr("__data_start__")
    let memEnd = findAddr("_estack", true) || findAddr("__StackTop")
    console.log(`memory: ${hex(memStart)} - ${hex(memEnd)}`)
    let mem: Buffer
    if (filename) {
        const buf = fs.readFileSync(filename)
        mem = buf.slice(memStart & 0xffff)
    } else {
        await initGdbServerAsync()
        mem = await getMemoryAsync(memStart, memEnd - memStart)
    }
    let heapDesc = findAddr("heap")
    let m = /\.bss\.heap\s+0x000000[a-f0-9]+\s+0x([a-f0-9]+)/.exec(getMap())
    let heapSz = 8
    let heapNum = parseInt(m[1], 16) / heapSz
    let vtablePtrs: pxt.Map<string> = {}

    getMap().replace(/0x0000000([0-9a-f]+)\s+vtable for (.*)/g, (f, a, cl) => {
        let n = parseInt(a, 16)
        vtablePtrs[hex(n)] = cl
        vtablePtrs[hex(n + 4)] = cl
        vtablePtrs[hex(n + 8)] = cl
        vtablePtrs[hex(n + 16)] = cl
        vtablePtrs[hex(n + 20)] = cl
        return ""
    })

    let pointerClassification: pxt.Map<string> = {}
    let numFibers = 0
    let numListeners = 0
    for (let q of ["runQueue", "sleepQueue", "waitQueue", "fiberPool", "idleFiber"]) {
        let addr = findAddr("codal::" + q, true) || findAddr(q)
        for (let ptr = read32(addr); ptr; ptr = read32(ptr + 6 * 4)) {
            pointerClassification[hex(ptr)] = "Fiber/" + q
            pointerClassification[hex(read32(ptr))] = "Fiber/TCB/" + q
            pointerClassification[hex(read32(ptr + 4))] = "Fiber/Stack/" + q
            pointerClassification[hex(read32(ptr + 8 * 4))] = "Fiber/PXT/" + q
            if (q == "idleFiber")
                break
            numFibers++
        }
    }

    let messageBus = read32(findAddr("codal::EventModel::defaultEventBus", true))
    if (messageBus) {
        for (let ptr = read32(messageBus + 20); ptr; ptr = read32(ptr + 36)) {
            numListeners++
            pointerClassification[hex(ptr)] = "codal::Listener"
        }
        for (let ptr = read32(messageBus + 24); ptr; ptr = read32(ptr + 16)) {
            pointerClassification[hex(ptr)] = "codal::EventQueueItem"
        }

        const handler = findAddr("pxt::handlerBindings", true)
        if (handler)
            for (let ptr = read32(handler); ptr; ptr = read32(ptr)) {
                pointerClassification[hex(ptr)] = "pxt::HandlerBinding"
            }
    }

    console.log(`heaps at ${hex(heapDesc)}, num=${heapNum}`)
    let cnts: pxt.Map<number> = {}
    let examples: pxt.Map<number[]> = {}
    let fiberSize = 0
    for (let i = 0; i < heapNum; ++i) {
        let heapStart = read32(heapDesc + i * heapSz)
        let heapEnd = read32(heapDesc + i * heapSz + 4)
        console.log(`*** heap ${hex(heapStart)} ${heapEnd - heapStart} bytes`)

        let block = heapStart

        let totalFreeBlock = 0
        let totalUsedBlock = 0
        while (block < heapEnd) {
            let bp = read32(block)
            let blockSize = bp & 0x7fffffff;
            let isFree = (bp & 0x80000000) != 0

            // console.log(`${hex(block)} -> ${hex(bp)} ${blockSize * 4}`)
            let classification = classifyCPP(block, blockSize)
            if (U.startsWith(classification, "Fiber/"))
                fiberSize += blockSize * 4
            let mark = `[${isFree ? "F" : "U"}:${blockSize * 4} / ${classification}]`
            if (!cnts[mark]) {
                cnts[mark] = 0
                examples[mark] = []
            }
            cnts[mark] += blockSize * 4
            examples[mark].push(block)

            if (isFree)
                totalFreeBlock += blockSize;
            else
                totalUsedBlock += blockSize;

            block += blockSize * 4;
        }
        console.log(`free: ${totalFreeBlock * 4}`)
    }

    {
        let keys = Object.keys(cnts)
        keys.sort((a, b) => cnts[b] - cnts[a])
        for (let k of keys) {
            U.randomPermute(examples[k])
            console.log(`${cnts[k]}\t${k}\t${examples[k].slice(0, 6).map(p => p.toString(16)).join(", ")}`)
        }
    }

    let uf2 = pxtc.UF2.parseFile(new Uint8Array(fs.readFileSync("built/binary.uf2")))

    let currClass = ""
    let classMap: pxt.Map<ClassInfo> = {}
    let inIface = false
    let classInfo: ClassInfo
    for (let line of fs.readFileSync("built/binary.asm", "utf8").split(/\n/)) {
        let m = /(\w+)__C\d+_VT:/.exec(line)
        if (m) currClass = m[1]
        m = /\w+__C\d+_IfaceVT:/.exec(line)
        if (m) inIface = true
        m = /(\d+)\s+;\s+class-id/.exec(line)
        if (currClass && m) {
            classInfo = {
                name: currClass,
                fields: []
            }
            classMap[m[1]] = classInfo
            currClass = ""
        }

        if (inIface) {
            m = /\.short \d+, (\d+) ; (.*)/.exec(line)
            if (m) {
                if (m[2] == "the end") {
                    inIface = false
                } else if (m[1] != "0") {
                    classInfo.fields.push(m[2])
                }
            }
        }

    }

    let objects: pxt.Map<HeapObj> = {}

    let byCategory: pxt.Map<number> = {}
    let numByCategory: pxt.Map<number> = {}
    let maxFree = 0

    const string_inline_ascii_vt = findAddr("pxt::string_inline_ascii_vt")
    const string_inline_utf8_vt = findAddr("pxt::string_inline_utf8_vt")
    const string_cons_vt = findAddr("pxt::string_cons_vt")
    const string_skiplist16_vt = findAddr("pxt::string_skiplist16_vt")
    const string_skiplist16_packed_vt = findAddr("pxt::string_skiplist16_packed_vt", true)

    const PNG: any = require("pngjs").PNG;
    const visWidth = 256
    const visHeight = 256
    const heapVis = new PNG({ width: visWidth, height: visHeight })
    const visData: Uint8Array = heapVis.data

    for (let i = 0; i < visWidth * visHeight * 4; ++i)
        visData[i] = 0xff

    /*
    struct VTable {
        uint16_t numbytes;
        ValType objectType;
        uint8_t magic;
        PVoid *ifaceTable;
        BuiltInType classNo;
    };
    */
    for (let gcHeap = read32(findAddr("pxt::firstBlock")); gcHeap; gcHeap = read32(gcHeap)) {
        let heapSize = read32(gcHeap + 4)
        console.log(`*** GC heap ${hex(gcHeap)} size=${heapSize}`)
        let objPtr = gcHeap + 8
        let heapEnd = objPtr + heapSize
        let fields: pxt.Map<HeapRef>

        while (objPtr < heapEnd) {
            let vtable = read32(objPtr)
            let numbytes = 0
            let category = ""
            let addWords = 0
            fields = {}
            let color = 0x000000
            if (vtable & FREE_MASK) {
                color = 0x00ff00
                category = "free"
                numbytes = VAR_BLOCK_WORDS(vtable) << 2
                maxFree = Math.max(numbytes, maxFree)
            } else if (vtable & ARRAY_MASK) {
                numbytes = VAR_BLOCK_WORDS(vtable) << 2
                category = "arraybuffer sz=" + (numbytes >> 2)
                if (vtable & PERMA_MASK) {
                    category = "app_alloc sz=" + numbytes
                    let classification = classifyCPP(objPtr, numbytes >> 2)
                    if (classification != "?")
                        category = classification
                    if (U.startsWith(classification, "Fiber/"))
                        fiberSize += numbytes
                } else
                    category = "arraybuffer sz=" + (numbytes >> 2)
            } else {
                vtable &= ~ANY_MARKED_MASK
                let vt0 = read32(vtable)
                if ((vt0 >>> 24) != pxt.VTABLE_MAGIC) {
                    console.log(`Invalid vtable: at ${hex(objPtr)} *${hex(vtable)} = ${hex(vt0)}`)
                    break
                }
                numbytes = vt0 & 0xffff
                let objectType = (vt0 >> 16) & 0xff
                let classNoEtc = read32(vtable + 8)
                let classNo = classNoEtc & 0xffff
                let word0 = read32(objPtr + 4)
                let tmp = 0
                let len = 0
                switch (classNo) {
                    case pxt.BuiltInType.BoxedString:
                        if (vtable == string_inline_ascii_vt) {
                            category = "ascii_string"
                            numbytes = 4 + 2 + (word0 & 0xffff) + 1
                        } else if (vtable == string_inline_utf8_vt) {
                            category = "utf8_string"
                            numbytes = 4 + 2 + (word0 & 0xffff) + 1
                        } else if (vtable == string_skiplist16_vt) {
                            category = "skip_string"
                            numbytes = 4 + 2 + 2 + 4
                            fields[".data"] = hex(read32(objPtr + 8) - 4)
                        } else if (vtable == string_skiplist16_packed_vt) {
                            category = "skip_string_packed"
                            const numskip = (word0 >> 16) >> 4
                            numbytes = 2 + 2 + numskip * 2 + (word0 & 0xffff) + 1
                        } else if (vtable == string_cons_vt) {
                            category = "cons_string"
                            numbytes = 4 + 4 + 4
                            fields["left"] = hex(read32(objPtr + 4))
                            fields["right"] = hex(read32(objPtr + 8))
                        } else {
                            console.log(`Invalid string vtable: ${hex(vtable)}`)
                            break
                        }
                        break
                    case pxt.BuiltInType.BoxedBuffer:
                        category = "buffer"
                        numbytes += word0
                        break
                    case pxt.BuiltInType.RefAction:
                        category = "action"
                        len = word0 & 0xffff
                        for (let i = 0; i < len; ++i) {
                            fields["" + i] = readRef(objPtr + (i + 3) * 4)
                        }
                        numbytes += len * 4
                        break
                    case pxt.BuiltInType.RefImage:
                        category = "image"
                        if (word0 & 1) {
                            numbytes += word0 >> 2
                        }
                        break
                    case pxt.BuiltInType.BoxedNumber:
                        category = "number"
                        break
                    case pxt.BuiltInType.RefCollection:
                        len = read32(objPtr + 8)
                        category = "array sz=" + (len >>> 16)
                        len &= 0xffff
                        fields["length"] = len
                        fields[".data"] = hex(word0 - 4)
                        for (let i = 0; i < len; ++i) {
                            fields["" + i] = readRef(word0 + i * 4)
                        }
                        break
                    case pxt.BuiltInType.RefRefLocal:
                        category = "reflocal"
                        fields["value"] = readRef(objPtr + 4)
                        break
                    case pxt.BuiltInType.RefMap:
                        len = read32(objPtr + 8)
                        category = "refmap sz=" + (len >>> 16)
                        len &= 0xffff
                        tmp = read32(objPtr + 12)
                        fields["length"] = len
                        fields[".keys"] = hex(word0 - 4)
                        fields[".values"] = hex(tmp - 4)
                        for (let i = 0; i < len; ++i) {
                            fields["k" + i] = readRef(word0 + i * 4)
                            fields["v" + i] = readRef(tmp + i * 4)
                        }
                        break
                    default:
                        if (classMap[classNo + ""]) {
                            let cinfo = classMap[classNo + ""]
                            category = cinfo.name
                            len = (numbytes - 4) >> 2
                            if (len != cinfo.fields.length)
                                fields["$error"] = "fieldMismatch"
                            for (let i = 0; i < len; ++i)
                                fields[cinfo.fields[i] || ".f" + i] = readRef(objPtr + (i + 1) * 4)
                        } else {
                            category = ("C_" + classNo)
                            len = (numbytes - 4) >> 2
                            for (let i = 0; i < len; ++i)
                                fields[".f" + i] = readRef(objPtr + (i + 1) * 4)

                        }
                        break
                }
            }
            // console.log(`${hex(objPtr)} vt=${hex(vtable)} ${category} bytes=${numbytes}`)
            if (!byCategory[category]) {
                byCategory[category] = 0
                numByCategory[category] = 0
            }
            let numwords = (numbytes + 3) >> 2
            let obj: HeapObj = {
                addr: hex(objPtr),
                tag: category,
                size: (addWords + numwords) * 4,
                fields: fields
            }
            objects[obj.addr] = obj
            byCategory[category] += (addWords + numwords) * 4
            numByCategory[category]++

            for (let i = 0; i < numwords; ++i) {
                const j = (objPtr & 0xffffff) + i * 4
                if (category == "free") {
                    const mask = Math.min(i / 1, 255) | 0
                    color = (mask << 16) | 0x00ff00
                }
                visData[j] = (color >> 16) & 0xff
                visData[j + 1] = (color >> 8) & 0xff
                visData[j + 2] = (color >> 0) & 0xff
                visData[j + 3] = 0xff // alpha
            }

            objPtr += numwords * 4
        }
    }

    heapVis.pack()
        .pipe(fs.createWriteStream('heap.png'))
        .on('finish', function () {
            console.log('Written heap.png!');
        });

    let cats = Object.keys(byCategory)
    cats.sort((a, b) => byCategory[b] - byCategory[a])
    let fidx = cats.indexOf("free")
    cats.splice(fidx, 1)
    cats.push("free")
    for (let c of cats) {
        console.log(`${byCategory[c]}\t${numByCategory[c]}\t${c}`)
    }

    console.log(`max. free block: ${maxFree} bytes`)
    console.log(`fibers: ${fiberSize} bytes, ${numFibers} fibers; ${numListeners} listeners`)

    let dmesg = getDmesg()
    let roots: pxt.Map<HeapRef[]> = {}

    dmesg
        .replace(/.*--MARK/, "")
        .replace(/^R(.*):0x([\da-f]+)\/(\d+)/img, (f, id, ptr, len) => {
            roots[id] = getRoots(parseInt(ptr, 16), parseInt(len), id == "P")
            return ""
        })

    for (let rootId of Object.keys(roots)) {
        for (let f of roots[rootId])
            mark(rootId, f)
    }

    let unreachable: string[] = []

    for (let o of U.values(objects)) {
        if (!o.incoming) {
            if (o.tag != "free")
                unreachable.push(o.addr)
        }
    }

    fs.writeFileSync("dump.json", JSON.stringify({
        unreachable,
        roots,
        dmesg,
        objects
    }, null, 1))

    // dgml output
    let dgml = `<DirectedGraph xmlns="http://schemas.microsoft.com/vs/2009/dgml">\n`;
    dgml += `<Nodes>\n`
    for (const addr of Object.keys(objects)) {
        const obj = objects[addr];
        dgml += `<Node Id="${addr}" Label="${obj.tag}" Size="${obj.size}" />\n`
    }
    dgml += `</Nodes>\n`
    dgml += `<Links>\n`
    for (const addr of Object.keys(objects)) {
        const obj = objects[addr];
        for (const fieldaddr of Object.keys(obj.fields)) {
            const field = obj.fields[fieldaddr];
            dgml += `<Link Source="${addr}" Target="${field}" Label="${fieldaddr}" />\n`
        }
    }
    dgml += `</Links>\n`
    dgml += `<Properties>
    <Property Id="Size" Label="Size" DataType="System.Int32" />
</Properties>\n`
    dgml += `</DirectedGraph>`;
    fs.writeFileSync("dump.dgml", dgml, { encoding: "utf8" });
    console.log(`written dump.dgml`);

    function mark(src: HeapRef, r: HeapRef) {
        if (typeof r == "string" && U.startsWith(r, "0x2")) {
            let o = objects[r]
            if (o) {
                if (!o.incoming) {
                    o.incoming = [src]
                    for (let f of U.values(o.fields))
                        mark(r, f)
                } else {
                    o.incoming.push(src)
                }
            } else {
                objects[r] = {
                    addr: r,
                    size: -1,
                    tag: "missing",
                    incoming: [src],
                    fields: {}
                }
            }
        }
    }

    function getRoots(start: number, len: number, encoded = false) {
        let refs: HeapRef[] = []
        for (let i = 0; i < len; ++i) {
            let addr = start + i * 4
            if (encoded) {
                let v = read32(addr)
                if (v & 1)
                    addr = v & ~1
            }
            refs.push(readRef(addr))
        }
        return refs
    }

    function readRef(addr: number): HeapRef {
        let v = read32(addr)
        if (!v) return "undefined"
        if (v & 1) {
            if (0x8000000 <= v && v <= 0x80f0000)
                return hex(v)
            return v >> 1
        }
        if (v & 2) {
            if (v == pxtc.taggedFalse)
                return "false"
            if (v == pxtc.taggedTrue)
                return "true"
            if (v == pxtc.taggedNaN)
                return "NaN"
            if (v == pxtc.taggedNull)
                return "null"
            return "tagged_" + v
        }
        return hex(v)
    }

    function read32(addr: number) {
        if (addr >= memStart)
            return pxt.HF2.read32(mem, addr - memStart)
        let r = pxtc.UF2.readBytes(uf2, addr, 4)
        if (r && r.length == 4)
            return pxt.HF2.read32(r, 0)
        U.userError(`can't read memory at ${addr}`)
        return 0
    }

    function getDmesg() {
        let addr = findAddr("codalLogStore")
        let start = addr + 4 - memStart
        for (let i = 0; i < maxDMesgSize; ++i) {
            if (i == maxDMesgSize - 1 || mem[start + i] == 0)
                return mem.slice(start, start + i).toString("utf8")
        }
        return ""
    }

    function classifyCPP(block: number, blockSize: number) {
        let w0 = read32(block + 4)
        let w1 = read32(block + 8)
        let hx = hex(w0)
        let classification = pointerClassification[hex(block + 4)]
        if (!classification)
            classification = vtablePtrs[hx]
        if (!classification) {
            if (blockSize == 1312 / 4)
                classification = "ST7735WorkBuffer"
            else if (blockSize == 1184 / 4)
                classification = "ZPWM_buffer"
            else if (w0 & 1 && (w0 >> 16) == 2)
                classification = "codal::BufferData"
            else
                classification = "?" // hx
        }
        return classification
    }
}