//============================================================================
// Copyright (c) 1996, Microsoft Corporation
//
// File:    mm.c
//
// History:
//      Abolade Gbadegesin      Jan-226-1996    Created.
//
// Contains code for memory management in IPRIP
//============================================================================



#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <rtutils.h>
#include "mm.h"


// Function:    MmHeapCreate
//
// This function creates a heap and initializes lists which will contain
// the addresses of allocated and freed blocks of memory.

HANDLE
MmHeapCreate(
    DWORD dwInitialSize,
    DWORD dwMaximumSize
    ) {

    DWORD dwErr;
    HANDLE hHeap;
    MMHEAP *pheap;


    //
    // create a Win32 heap; we specify no serialization
    // since our critical section will enforce serialization
    //

    hHeap = HeapCreate(HEAP_NO_SERIALIZE, dwInitialSize, dwMaximumSize);
    if (hHeap == NULL) { return NULL; }


    //
    // within the heap created, allocate space for the managed heap
    //

    pheap = HeapAlloc(hHeap, 0, sizeof(MMHEAP));
    if (pheap == NULL) {

        dwErr = GetLastError();
        HeapDestroy(hHeap);
        SetLastError(dwErr);

        return NULL;
    }


    //
    // initialize the managed heap
    //

    pheap->hHeap = hHeap;
    InitializeListHead(&pheap->lhFreeList);
    InitializeListHead(&pheap->lhBusyList);
    InitializeCriticalSection(&pheap->csListLock);


    //
    // return a pointer to the managed heap structure
    //

    return (HANDLE)pheap;
}




// Function:    MmHeapDestroy
//
// This function destroys a heap.

BOOL
MmHeapDestroy(
    HANDLE hHeap
    ) {

    MMHEAP *pheap;

    pheap = (MMHEAP *)hHeap;


    //
    // delete the lists' synchronization object
    //

    DeleteCriticalSection(&pheap->csListLock);


    //
    // a managed heap can be destroyed by merely destroying the heap
    // which was initially created for the managed heap, since all
    // allocations came out of that heap
    //

    return HeapDestroy(pheap->hHeap);
}



// Function:    MmHeapAlloc
//
// This function makes an allocation from a managed heap

PVOID
MmHeapAlloc(
    HANDLE hHeap,
    DWORD dwBytes
    ) {

    INT cmp;
    DWORD dwErr;
    MMHEAP *pheap;
    MMHEADER *phdr;
    LIST_ENTRY *ple, *phead;

    if (!hHeap || !dwBytes) { return NULL; }

    pheap = (MMHEAP *)hHeap;


    EnterCriticalSection(&pheap->csListLock);


    //
    // search the free-list for the allocation which
    // is closest in size to the number of bytes requested
    //

    phdr = NULL;
    phead = &pheap->lhFreeList;

    for (ple = phead->Flink; ple != phead; ple = ple->Flink) {

        phdr = CONTAINING_RECORD(ple, MMHEADER, leLink);

        cmp = (dwBytes - phdr->dwBlockSize);

        if (cmp < 0) { continue; }
        else
        if (cmp > 0) { break; }

        //
        // the entry found is precisely the required size;
        //

        break;
    }


    //
    // if a re-usable entry was found, reset its timestamp,
    // move it to the busy-list, and return a pointer past the header.
    // otherwise, allocate a new entry for the caller,
    // initialize it, place it on the busy-list, and return a pointer.
    //

    if (ple != phead) {

        //
        // a re-usable entry was found
        //

        RemoveEntryList(&phdr->leLink);
    }
    else {

        //
        // no re-usable entry was found, allocate a new one
        //

        phdr = HeapAlloc(
                    pheap->hHeap, HEAP_NO_SERIALIZE, dwBytes + sizeof(MMHEADER)
                    );
        if (!phdr) {

            dwErr = GetLastError();
            LeaveCriticalSection(&pheap->csListLock);
            SetLastError(dwErr);

            return NULL;
        }
    }


    //
    // set the entry's timestamp and put it on the busy list
    //

    NtQuerySystemTime(&phdr->liTimeStamp);

    InsertHeadList(&pheap->lhBusyList, &phdr->leLink);


    LeaveCriticalSection(&pheap->csListLock);

    return (PVOID)(phdr + 1);
}



// Function:    MmHeapFree
//
// This function frees an allocation made by MmHeapAlloc

BOOL
MmHeapFree(
    HANDLE hHeap,
    PVOID pMem
    ) {

    INT cmp;
    MMHEAP *pheap;
    MMHEADER *phdr, *phdrFree;
    LIST_ENTRY *ple, *phead;

    if (!hHeap || !pMem) { return FALSE; }

    pheap = (MMHEAP *)hHeap;


    EnterCriticalSection(&pheap->csListLock);


    phdr = (MMHEADER *)((PBYTE)pMem - sizeof(MMHEADER));


    //
    // remove the entry from the busy-list
    //

    RemoveEntryList(&phdr->leLink);


    //
    // place the entry on the free-list,
    // which is in order of ascending size (smallest-first)
    //

    phead = &pheap->lhFreeList;

    for (ple = phead->Flink; ple != phead; ple = ple->Flink) {

        phdrFree = CONTAINING_RECORD(ple, MMHEADER, leLink);

        cmp = (phdr->dwBlockSize - phdrFree->dwBlockSize);

        if (cmp < 0) { break; }
        else
        if (cmp > 0) { continue; }

        //
        // the allocations are the same size, but the newly-free one
        // is most likely the most-recently active
        //

        break;
    }


    //
    // insert the newly-free entry before the one found
    // (since this is a circular list, we accomplish this
    // using InsertTailList; think about it a while.)
    //

    NtQuerySystemTime(&phdr->liTimeStamp);

    InsertTailList(ple, &phdr->leLink);


    LeaveCriticalSection(&pheap->csListLock);

    return TRUE;
}



// Function:    MmHeapCollectGarbage
//
// This function is called by the owner of the heap.
// It takes as its argument an interval I in systime-units
// (system-time is measured in 100-nanosecond units), and any allocations
// on the free list which have not been re-used in the past I systime-units
// are returned to the Win32 heap.
//
// To return all free entries to the Win32 heap, specify an interval of 0.

BOOL
MmHeapCollectGarbage(
    HANDLE hHeap,
    LARGE_INTEGER liInterval
    ) {

    INT cmp;
    MMHEAP *pheap;
    MMHEADER *phdr;
    LIST_ENTRY *ple, *phead;
    LARGE_INTEGER liCutoff;

    if (!hHeap) { return FALSE; }

    pheap = (MMHEAP *)hHeap;

    EnterCriticalSection(&pheap->csListLock);


    //
    // get the current system-time, and from that compute the cutoff-time
    // for allocation timestamps, by subtracting the interval passed in
    // to get the time of the earliest allocation which can be exempt
    // from garbage-collection
    //

    NtQuerySystemTime(&liCutoff);
    liCutoff = RtlLargeIntegerSubtract(liCutoff, liInterval);


    //
    // go through the free-list
    //

    phead = &pheap->lhFreeList;

    for (ple = phead->Flink; ple != phead; ple = ple->Flink) {

        phdr = CONTAINING_RECORD(ple, MMHEADER, leLink);

        if (RtlLargeIntegerLessThan(liCutoff, phdr->liTimeStamp)) { continue; }


        //
        // this allocation is stamped from before the cutoff interval,
        // so we'll have to free it (with care, since it is a link
        // in the list we are walking through).
        //

        ple = ple->Blink;

        RemoveEntryList(&phdr->leLink);

        HeapFree(pheap->hHeap, HEAP_NO_SERIALIZE, phdr);
    }

    LeaveCriticalSection(&pheap->csListLock);

    return TRUE;
}