//
// Handle based swapping memory manager
//
include "inc/cmdsys.plh"
include "inc/fileio.plh"

struc t_initdata
    word    volptr
    word    freeblks
    word    ramvol
    word    bestvol
    word    ramfree
    word    bestfree
    word    filecnt
    word    catentry
    byte    catref
    byte    firstblk
    byte    entrylen
    byte    entriesblk
    byte    swapstrlen
    byte[4] volparms
    byte[]  volinfo
    byte[]  catalog
end
word initdata
//
// Access bytes within a word
//
struc t_word
    byte lsb
    byte msb
end
//
// Alloced memory block structure
//
struc t_memblk
    word blksiz
    byte blkref
    byte blklok
end
//
// Free memory block structure
//
struc t_freblk
    word fresiz
    word frenxt
end
//
// Block size
//
const MAX_BLK_SIZE = $2010
const MAX_BLK_MASK = $1FFF
const MIN_BLK_SIZE = $04
const MIN_BLK_MASK = $03
//
// Block states
//
const HMEM_ADDR     = $FFFC
const HMEM_SIZE     = $FFFC
const HMEM_STATE    = $03
const HMEM_MOVEABLE = $00 // Many dependencies on this being $00
const HMEM_AVAIL    = $01
const HMEM_LOCKED   = $02
const HMEM_SWAPPED  = $03
//
// Block flags
//
const HMEM_ACCESSED = $80
//
// Handle modifier
//
const HMOD = $4321
//
// Handle tables/lists
//
const PG_SIZE     = 512
const PG_ENTRIES  = 256
const PG_TBL_SIZE = 8
word sysbuf
word[PG_TBL_SIZE] hpgtbl // Handle page table
word pooladdr
word poolsize
word frelst              // Free list
byte sweeppg, sweepen
byte[64] swapvol = "/"   // Swap volume
byte swapdir = "/SWAP/"
byte hexchar = '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
//
// DEBUG
//
//byte swapinstr = "Swap in:"
//byte swapoutstr = "Swap out:"
//byte getblkstr = "Get block = "
//byte allocpgstr = "Alloc page:"
//
//def putln
//    return putc($0D)
//end
//def putb(hexb)
//    return call($FDDA, hexb, 0, 0, 0)
//end
//def puth(hex)
//    return call($F941, hex >> 8, hex, 0, 0)
//end
//
// Fill block filename
//
def strcharadd(str, chr)#0
    ^str = ^str + 1
    str->.[^str] = chr
end
def swapfile(filestr, hmem)#0
    memcpy(filestr, @swapvol, swapvol + 1)
    strcharadd(filestr, 'H')
    strcharadd(filestr, hexchar[(hmem >> 12) & $0F])
    strcharadd(filestr, hexchar[(hmem >> 8)  & $0F])
    strcharadd(filestr, hexchar[(hmem >> 4)  & $0F])
    strcharadd(filestr, hexchar[ hmem        & $0F])
end
//
// Find exact/best free memory match
//
def unlink(freblk, freprv)
    //
    // Unlink free block
    //
    if freprv
        freprv=>frenxt = freblk=>frenxt
    else
        frelst = freblk=>frenxt
    fin
    return freblk
end
def unfre(freblk, freprv, size)
    word shrink

    if freblk=>fresiz == size
        //
    // Unlink free block
    //
        unlink(freblk, freprv)
    elsif freblk=>fresiz > size
        //
    // Shrink free block
    //
    shrink = freblk + size
    if freprv
        freprv=>frenxt = shrink
    else
        frelst = shrink
    fin
    shrink=>fresiz = freblk=>fresiz - size
    shrink=>frenxt = freblk=>frenxt
    else
        freblk = 0
    fin
    return freblk
end
def addfre(freblk)#0
    word srch

    //freblk=>fresiz = freblk=>blksiz
    if frelst and frelst < freblk
        srch = frelst
        while srch=>frenxt
            if srch=>frenxt > freblk
                //
                // Insert into list
                //
                freblk=>frenxt = srch=>frenxt
                srch=>frenxt   = freblk
                return
            fin
            srch = srch=>frenxt
        loop
        //
        // Add to end of list
        //
        freblk=>frenxt = 0
        srch=>frenxt   = freblk
    else
        //
        // Add to beginning of list
        //
        freblk=>frenxt = frelst
        frelst         = freblk
    fin
end
//
// Memory coallesce/compaction/swap out routines
//
def coallesce
    word srch, combined

    combined = 0
    srch = frelst
    while srch and srch=>frenxt
        if srch + srch=>fresiz == srch=>frenxt
            //
            // Combine adjacent free space
            //
            //putc('C');putc(' ');puth(srch);putc('+');puth(srch=>frenxt);putc(' ')
            srch=>fresiz = srch=>fresiz + srch=>frenxt=>fresiz
            srch=>frenxt = srch=>frenxt=>frenxt
            combined = 1
        else
            srch = srch=>frenxt
        fin
    loop
    return combined
end
def compact
    word page, entry, memblk, moveblk, size, srch, prev
    byte moved

    moved = 0
    for page = PG_TBL_SIZE - 1 downto 0
        if hpgtbl[page]
            for entry = 255 downto 0
                if hpgtbl:[page, entry].lsb & HMEM_STATE == HMEM_MOVEABLE
                    memblk = hpgtbl:[page, entry]
                    size    = memblk=>blksiz
                    moveblk = 0
                    prev    = 0
                    srch    = frelst
                    while srch and srch < memblk
                        if srch=>fresiz >= size
                            moveblk = unfre(srch, prev, size)
                            break
                        fin
                        prev = srch
                        srch = srch=>frenxt
                    loop
                    if moveblk
                        //putc('M');putc(' ');puth(moveblk);putc('=');puth(memblk);putc(' ')
                        memcpy(moveblk, memblk, size)
                        hpgtbl:[page, entry] = moveblk
                        addfre(memblk)
                        moved = 1
                    fin
                fin
            next
        else
            break
        fin
    next
    if moved
        coallesce
    fin
    return moved
end
//
// Swap out memory block
//
def swapout(accessed)
    byte[64] filename
    byte ref
    word page, entry, memblk, hmem, size
    byte swapped

    swapped = 0
    for page = PG_TBL_SIZE - 1 downto 0
        if hpgtbl[page]
            for entry = 255 downto 0
                if hpgtbl:[page, entry].lsb & HMEM_STATE == HMEM_MOVEABLE
                    memblk = hpgtbl:[page, entry]
                    if not (memblk->blklok & accessed)
                        //
                        // Swap this block out
                        //
                        size     = memblk=>blksiz
                        hmem.lsb = page
                        hmem.msb = entry
                        swapfile(@filename, hmem)
                        fileio:create(@filename, $00, size) // embed size in aux type
                        //puts(@swapoutstr);puts(@filename);putc('@');puth(memblk);putc(':');puth(size);putln
                        ref = fileio:open(@filename)
                        if ref
                            //
                            // Write it out
                            //
                            if fileio:write(ref, memblk, size) == size
                                //
                                // Zero size in page table flags swapin to read from disk
                                //
                                hpgtbl:[page, entry] = HMEM_SWAPPED
                                addfre(memblk)
                                swapped = 1
                            fin
                            fileio:close(ref)
                        fin
                    fin
                fin
            next
        else
            break
        fin
    next
    if swapped
        coallesce
        compact
    fin
    return swapped
end
//
// Find a memory block
//
def findexact(size)
    word srch, prev

    srch = frelst
    prev = 0
    while srch
        if srch=>fresiz == size
            //putc('E')
            return unlink(srch, prev)
        fin
        prev = srch
        srch = srch=>frenxt
    loop
    return 0
end
def findbest(size)
    word srch, prev

    prev = 0
    srch = frelst
    while srch
        if srch=>fresiz >= size
            //putc('B')
            return unfre(srch, prev, size)
        fin
        prev = srch
        srch = srch=>frenxt
    loop
    return 0
end
def findblk(size)
    word addr

    if size > MAX_BLK_SIZE; return 0; fin
    addr = findexact(size)
    if !addr
        coallesce
        addr = findexact(size)
        if !addr
            compact
            addr = findbest(size)
            if !addr
                swapout(HMEM_ACCESSED)
                addr = findbest(size)
                if !addr
                    swapout(0)
                    addr = findbest(size)
                fin
            fin
        fin
    fin
    //
    // Fill in the block
    //
    //puts(@getblkstr);puth(addr);putc(':');puth(size);putln
    if addr
        addr=>blksiz = size
        addr->blkref = 1
        addr->blklok = 0
    fin
    return addr
end
//
// Swap in a memory block (or allocate an uninitialized block)
//
def swapin(hmem)
    byte[64] filename
    byte[15] info
    byte ref
    word memblk, size

    size = hpgtbl:[hmem.lsb, hmem.msb] & HMEM_SIZE
    if size
        //
        // This was just uninitialized memory, don't bother reading from file
        //
        memblk = findblk(size)
    else
        //
        // Swap this block back in
        //
        swapfile(@filename, hmem)
        fileio:getfileinfo(@filename, @info)
        size   = info:2 // Size encoded in aux type
        memblk = findblk(size)
        //puts(@swapinstr);puts(@filename);putc('@');puth(memblk);putc(':');puth(size);putln
        if memblk
            //
            // Read it in
            //
            ref = fileio:open(@filename)
            if ref
                fileio:read(ref, memblk, size)
                fileio:close(ref)
                fileio:destroy(@filename)
            fin
        fin
    fin
    if memblk
        hpgtbl:[hmem.lsb, hmem.msb] = memblk
    fin
    return memblk
end
//
// Incrementally clear the ACCESSED BIT
//
export def sweep#0
    word memblk

    if hpgtbl[sweeppg]
        memblk = hpgtbl:[sweeppg, sweepen]
        when memblk.lsb & HMEM_STATE
            is HMEM_MOVEABLE
            is HMEM_LOCKED
                memblk = memblk & HMEM_ADDR
                if memblk->blklok & HMEM_ACCESSED
                    memblk->blklok = memblk->blklok & $7F
                fin
        wend
        if sweepen == 255
            sweepen = 0
            if sweeppg == 0
                sweeppg = PG_TBL_SIZE - 1
            else
                sweeppg = sweeppg - 1
            fin
        else
            sweepen = sweepen + 1
        fin
    else
        sweeppg = PG_TBL_SIZE - 1
    fin
end
//
// Set end of memory pool
//
export def brk(addr)
    word heapalign, brkblk, brksiz, srch

    //
    // Check if addr is too high or low
    //
    if isuge(addr, @heapalign); return 0; fin
    if isule(addr, heapmark); return 0; fin
    if not pooladdr
        //
        // Allocate the memory pool
        //
        heapalign = (heapmark | MIN_BLK_MASK) + 1
        brksiz    = addr - heapalign
        if isult(brksiz, MAX_BLK_SIZE); return 0; fin // Not enough heap
            poolsize = addr - heapmark
            pooladdr = heapalloc(poolsize)
            if pooladdr
                frelst         = heapalign
                frelst=>fresiz = brksiz
                frelst=>frenxt = 0
            else
                poolsize = 0
            fin
        else
        //
        // Can we extend the memory pool?
        //
        if pooladdr + poolsize == heapmark
            brksiz = addr - heapmark
            brkblk = heapalloc(brksiz)
            if brkblk
                //
                // Add block to end of free list
                //
                poolsize       = poolsize + brksiz
                brkblk=>fresiz = brksiz
                brkblk=>frenxt = 0
                if frelst
                    srch = frelst
                    while srch=>frenxt; srch = srch=>frenxt; loop
                    srch=>frenxt = brkblk
                else
                    frelst = brkblk
                fin
                coallesce // combine adjacent free space
            fin
        fin
    fin
    return poolsize
end
export def sbrk(size)
    return brk((heapmark | MIN_BLK_MASK) + 1 + size + MIN_BLK_SIZE)
end
//
// Return the amount of free memory available, after garbage
// collection, swaps
//
export def hmemFre(lptr)
    word srch, free, largest

    coallesce
    while compact; loop
    free    = 0
    largest = 0
    srch    = frelst
    while srch
        //putc('F'); putc(' '); puth(srch); putc(':'); puth(srch=>fresiz); putln
        free = free + srch=>fresiz
        if srch=>fresiz > largest
            largest = srch=>fresiz
        fin
        srch = srch=>frenxt
    loop
    if lptr
        *lptr = largest - MIN_BLK_SIZE
    fin
    return free - MIN_BLK_SIZE
end
//
// Allocate memory block
//
export def hmemNew(size)
    word page, entry, hnew, memblk

    //
    // First, find a free handle
    //
    for page = PG_TBL_SIZE - 1 downto 0
        if !hpgtbl[page]
            //
            // Allocate a new page table
            //
            hpgtbl[page] = heapalloc(PG_SIZE)
            memset(hpgtbl[page], HMEM_AVAIL, PG_SIZE)
            //puts(@allocpgstr);puth(hpgtbl[page]);putln
            //
            // Check if we need to allocate the memory pool
            //
            if not pooladdr
                //
                // Allocate 3/4 of available heap on 128K machine, 1/2 on 64K machine
                //
                poolsize = (heapavail >> 1) & $7FFF
                if MACHID & $30 == $30
                        poolsize = poolsize + (poolsize >> 1)
                fin
                if isult(poolsize, MAX_BLK_SIZE)
                    poolsize = MAX_BLK_SIZE
                fin
                sbrk(poolsize)
            fin
        fin
        for entry = 255 downto 0
            if hpgtbl:[page, entry].lsb == HMEM_AVAIL
                //
                // Reserve handle as swapped out block
                // Nonzero size will flag swapin to not read from disk
                //
                //putc('N');putc(' ');putb(entry);putb(page);putc('@')
                size = ((size + t_memblk) | MIN_BLK_MASK) + 1
                hpgtbl:[page, entry] = size | HMEM_SWAPPED
                hnew.lsb = page
                hnew.msb = entry
                return hnew
            fin
        next
    next
    return 0
end
//
// Lock memory block in place
//
export def hmemLock(hmem)
    word memblk

    memblk = hpgtbl:[hmem.lsb, hmem.msb]
    when memblk.lsb & HMEM_STATE
        is HMEM_SWAPPED
            memblk = swapin(hmem)
            if not memblk; return 0; fin
        is HMEM_MOVEABLE
            hpgtbl:[hmem.lsb, hmem.msb] = memblk | HMEM_LOCKED
        is HMEM_LOCKED
            memblk = memblk & HMEM_ADDR
            memblk->blklok = (memblk->blklok + 1) | HMEM_ACCESSED
            //putc('L');putc(' ');puth(hmem);putc('@');puth(memblk);putln
            return memblk + t_memblk
    wend
    return 0
end
//
// Unlock memory block
//
export def hmemUnlock(hmem)#0
    byte lock
    word memblk

    memblk = hpgtbl:[hmem.lsb, hmem.msb]
    if memblk.lsb & HMEM_STATE == HMEM_LOCKED
        memblk = memblk & HMEM_ADDR
        lock = memblk->blklok - 1
        if lock == HMEM_ACCESSED
            //
            // Block is now moveable
            //
            hpgtbl:[hmem.lsb, hmem.msb] = memblk
        fin
        memblk->blklok = lock
        //putc('U');putc(' ');puth(hmem);putln
    fin
end
//
// Increment reference count
//
export def hmemRef(hmem)#0
    word memblk

    memblk = hpgtbl:[hmem.lsb, hmem.msb]
    when memblk.lsb & HMEM_STATE
        is HMEM_SWAPPED
            memblk = swapin(hmem)
        is HMEM_LOCKED
        is HMEM_MOVEABLE
            memblk = memblk & HMEM_ADDR
            if not memblk; return; fin
            memblk->blkref = memblk->blkref + 1
    wend
end
//
// Decrement reference count
//
export def hmemDel(hmem)#0
    byte ref
    word memblk

    memblk = hpgtbl:[hmem.lsb, hmem.msb]
    when memblk & HMEM_STATE
        is HMEM_SWAPPED
        memblk = swapin(hmem)
        is HMEM_LOCKED
        is HMEM_MOVEABLE
            memblk = memblk & HMEM_ADDR
            if not memblk; return; fin
            ref = memblk->blkref - 1
            if ref == 0
                //
                // No more references, free block
                //
                hpgtbl:[hmem.lsb, hmem.msb] = HMEM_AVAIL
                addfre(memblk)
            else
                memblk->blkref = ref
            fin
            break
    wend
end
//
// Initialization:
//
// Search for best swap volume
//
// !!! Does this work on Apple ///???
//
sysbuf = $0800 // heapallocalign(1024, 8, 0)
initdata = heapalloc(t_initdata) // Use data on heap for initialization
initdata=>volparms.0 = 2
initdata=>volparms.1 = 0
initdata=>volparms:2 = sysbuf
syscall($C5, @initdata=>volparms)
initdata=>volptr   = sysbuf
initdata=>ramfree  = 0
initdata=>bestfree = 0
for sweepen = 0 to 15
    ^initdata=>volptr = ^initdata=>volptr & $0F
    if ^initdata=>volptr
        memcpy(@swapvol + 2, initdata=>volptr + 1, ^initdata=>volptr)
        swapvol = ^initdata=>volptr + 1
        fileio:getfileinfo(@swapvol, @initdata->volinfo)
        initdata=>freeblks = initdata=>volinfo:2 - initdata=>volinfo:5
        if initdata=>volptr:1 == $522F and initdata=>volptr:3 == $4D41 // '/RAM'
            if isugt(initdata=>freeblks, initdata=>ramfree)
                initdata=>ramvol  = initdata=>volptr
                initdata=>ramfree = initdata=>freeblks
            fin
        elsif isugt(initdata=>freeblks, initdata=>bestfree)
            initdata=>bestvol  = initdata=>volptr
            initdata=>bestfree = initdata=>freeblks
        fin
    fin
    initdata=>volptr = initdata=>volptr + 16
next
if isugt(initdata=>ramfree, $40) or isugt(initdata=>ramfree, initdata=>bestfree) // RAMdrive greater than 64K? Use it
    initdata=>bestvol  = initdata=>ramvol
    initdata=>bestfree = initdata=>ramfree
fin
memcpy(@swapvol + 2, initdata=>bestvol + 1, ^initdata=>bestvol)
swapvol = ^initdata=>bestvol + 1
memcpy(@swapvol + swapvol + 1, @swapdir + 1, swapdir)
swapvol = swapvol + swapdir
fileio:create(@swapvol, $0F, $0000) // Create the swap directory
//
// Clear out left over swap files
//
initdata->catref     = fileio:open(@swapvol)
initdata->firstblk   = 1
initdata->swapstrlen = swapvol
repeat
    if fileio:read(initdata->catref, @initdata->catalog, 512) == 512
        initdata=>catentry = @initdata->catalog.4
        if initdata->firstblk
            initdata->entrylen   = initdata->catalog.$23
            initdata->entriesblk = initdata->catalog.$24
            initdata=>filecnt    = initdata=>catalog:$25
            initdata=>catentry   = initdata=>catentry + initdata->entrylen
        fin
        for sweepen = initdata->firstblk to initdata->entriesblk
            ^initdata=>catentry = ^initdata=>catentry & $0F
            if ^initdata=>catentry
                memcpy(@swapvol + swapvol + 1, initdata=>catentry + 1, ^initdata=>catentry)
                swapvol = swapvol + ^initdata=>catentry
                fileio:destroy(@swapvol)
                swapvol = initdata->swapstrlen
            fin
            initdata=>catentry = initdata=>catentry + initdata->entrylen
        next
        initdata->firstblk = 0
    else
        initdata->filecnt = 0
    fin
until !initdata->filecnt
fileio:close(initdata->catref)
heaprelease(initdata)
//puts(@swapvol); putln
done
