//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1992 - 1994.
//
//  File:	stalloc.cxx
//
//  Contents:	CStackAllocator
//
//  History:	29-Sep-94	DrewB	Created
//
//  Notes:      Loosely based on BobDay's original PSTACK implementation
//
//----------------------------------------------------------------------------

#include "headers.cxx"
#pragma hdrstop

// Pad a count to the given alignment
// Alignment must be 2^n-1
#define ALIGN_CB(cb, align) \
    (((cb)+(align)) & ~(align))

//+---------------------------------------------------------------------------
//
//  Structure:	SStackBlock (sb)
//
//  Purpose:	Header information for stack blocks
//
//  History:	29-Sep-94	DrewB	Created
//
//----------------------------------------------------------------------------

struct SStackBlock
{
    DWORD dwNextBlock;
    DWORD dwStackTop;
};

#define BLOCK_OVERHEAD (sizeof(SStackBlock))
#define BLOCK_START(mem) ((mem)+BLOCK_OVERHEAD)
#define BLOCK_AVAILABLE(cb) ((cb)-BLOCK_OVERHEAD)

//+---------------------------------------------------------------------------
//
//  Function:	CStackAllocator::CStackAllocator, public
//
//  Arguments:	[pmm] - Memory model to use
//              [cbBlock] - Size of chunk to allocate when necessary
//              [cbAlignment] - Alignment size, must be 2^N
//
//  History:	29-Sep-94	DrewB	Created
//
//----------------------------------------------------------------------------

CStackAllocator::CStackAllocator(CMemoryModel *pmm,
                                 DWORD cbBlock,
                                 DWORD cbAlignment)
{
    thkAssert(BLOCK_AVAILABLE(cbBlock) > 0);

    // Ensure that the alignment is a power of two
    thkAssert((cbAlignment & (cbAlignment-1)) == 0);
    // Store alignment - 1 since that's the actual value we need for
    // alignment computations
    _cbAlignment = cbAlignment-1;

    // Ensure that overhead and tracking will not affect alignment
    thkAssert(ALIGN_CB(BLOCK_OVERHEAD, _cbAlignment) == BLOCK_OVERHEAD &&
              ALIGN_CB(sizeof(SStackMemTrace), _cbAlignment) ==
              sizeof(SStackMemTrace));

    _pmm = pmm;
    _cbBlock = cbBlock;
    _dwBlocks = 0;
    _dwCurrent = 0;
    _cbAvailable = 0;

    _psaNext = NULL;
    _fActive = TRUE;
}

//+---------------------------------------------------------------------------
//
//  Member:	CStackAllocator::~CStackAllocator, public virtual
//
//  History:	29-Sep-94	DrewB	Created
//
//----------------------------------------------------------------------------

CStackAllocator::~CStackAllocator(void)
{
    Reset();
}

//+---------------------------------------------------------------------------
//
//  Function:   CStackAllocator::Alloc, public
//
//  Synopsis:   Allocates a chunk of memory from the stack
//
//  Arguments:  [cb] - Amount of memory to allocate
//
//  Returns:    Pointer to memory or NULL
//
//  History:    29-Sep-94       DrewB   Created
//
//----------------------------------------------------------------------------

DWORD CStackAllocator::Alloc(DWORD cb)
{
    DWORD dwMem;

    thkAssert(cb > 0);

    // Round size up to maintain alignment of stack
    cb = ALIGN_CB(cb, _cbAlignment);

#if DBG == 1
    // Reserve space to record caller
    cb += sizeof(SStackMemTrace);
#endif

    thkAssert(cb <= BLOCK_AVAILABLE(_cbBlock));

    // Check to see if the current block can hold the new allocation
    if (cb > _cbAvailable)
    {
        DWORD dwBlock;
        SStackBlock UNALIGNED *psb;

        // It's too big, so allocate a new block
        dwBlock = _pmm->AllocMemory(_cbBlock);
        if (dwBlock == 0)
        {
            return 0;
        }

        if (_dwBlocks != 0)
        {
            // Update current top block
            psb = (SStackBlock UNALIGNED *)
                _pmm->ResolvePtr(_dwBlocks, sizeof(SStackBlock));
            psb->dwStackTop = _dwCurrent;
            _pmm->ReleasePtr(_dwBlocks);
        }

        // Make the new block the top block
        psb = (SStackBlock UNALIGNED *)
            _pmm->ResolvePtr(dwBlock, sizeof(SStackBlock));
        psb->dwNextBlock = _dwBlocks;
        _dwBlocks = dwBlock;
        _pmm->ReleasePtr(dwBlock);

        _dwCurrent = BLOCK_START(dwBlock);
        _cbAvailable = BLOCK_AVAILABLE(_cbBlock);
    }

    thkAssert(_cbAvailable >= cb);

    dwMem = _dwCurrent;
    _dwCurrent += cb;
    _cbAvailable -= cb;

#if DBG == 1
    void *pvMem;

    // Fill memory to show reuse problems
    pvMem = _pmm->ResolvePtr(dwMem, cb);
    memset(pvMem, 0xED, cb);
    _pmm->ReleasePtr(dwMem);
#endif

#if DBG == 1
    SStackMemTrace UNALIGNED *psmt;

    psmt = (SStackMemTrace UNALIGNED *)
        _pmm->ResolvePtr(_dwCurrent-sizeof(SStackMemTrace),
                         sizeof(SStackMemTrace));
    psmt->cbSize = cb-sizeof(SStackMemTrace);

#if !defined(_CHICAGO_)
    //
    // On RISC platforms, psmt points to an unaligned structure.
    // Use a temp variable so we don't get an alignment fault
    // when RtlGetCallersAddress returns the value.
    //
    void *pv;
    void *pvCaller;
    RtlGetCallersAddress(&pvCaller, &pv);
    psmt->pvCaller = pvCaller;
#else
    // Depends on return address being directly below first argument
    psmt->pvCaller = *((void **)&cb-1);
#endif

    thkDebugOut((DEB_MEMORY, "Stack: %p alloc 0x%08lX:%3d, avail %d\n",
                 psmt->pvCaller, dwMem, cb, _cbAvailable));

    _pmm->ReleasePtr(_dwCurrent-sizeof(SStackMemTrace));
#endif

    return dwMem;
}

//+---------------------------------------------------------------------------
//
//  Function:   CStackAllocator::Free, public
//
//  Synopsis:   Frees allocated memory
//
//  Arguments:  [dwMem] - Memory
//              [cb] - Amount of memory allocated
//
//  History:    29-Sep-94       DrewB   Created
//
//----------------------------------------------------------------------------

void CStackAllocator::Free(DWORD dwMem, DWORD cb)
{
    thkAssert(dwMem != 0);
    thkAssert(cb > 0);

    // Round size up to maintain alignment of stack
    cb = ALIGN_CB(cb, _cbAlignment);

#if DBG == 1
    cb += sizeof(SStackMemTrace);
#endif

    thkAssert(cb <= BLOCK_AVAILABLE(_cbBlock));

#if DBG == 1
    void *pvCaller;

#if !defined(_CHICAGO_)
    void *pv;
    RtlGetCallersAddress(&pvCaller, &pv);
#else
    // Depends on return address being directly below first argument
    pvCaller = *((void **)&dwMem-1);
#endif

    thkDebugOut((DEB_MEMORY, "Stack: %p frees 0x%08lX:%3d, avail %d\n",
                 pvCaller, dwMem, cb, _cbAvailable));
#endif

#if DBG == 1
    if (_dwCurrent-cb != dwMem)
    {
        thkDebugOut((DEB_ERROR, "Free of %d:%d is not TOS (0x%08lX)\n",
                     dwMem, cb, _dwCurrent));

        thkAssert(_dwCurrent-cb == dwMem);
    }
#endif

    _dwCurrent -= cb;
    _cbAvailable += cb;

#if DBG == 1
    void *pvMem;

    // Fill memory to show reuse problems
    pvMem = _pmm->ResolvePtr(dwMem, cb);
    memset(pvMem, 0xDD, cb);
    _pmm->ReleasePtr(dwMem);
#endif

    if (_dwCurrent == BLOCK_START(_dwBlocks))
    {
        SStackBlock UNALIGNED *psb;
        DWORD dwBlock;

        // If we've just freed up an entire block and it's not the
        // only block for the stack, free the block itself and
        // restore stack state from the next block
        // We keep the first block around forever to avoid memory
        // thrashing

        psb = (SStackBlock UNALIGNED *)
            _pmm->ResolvePtr(_dwBlocks, sizeof(SStackBlock));
        dwBlock = psb->dwNextBlock;
        _pmm->ReleasePtr(_dwBlocks);

        if (dwBlock != 0)
        {
            _pmm->FreeMemory(_dwBlocks);

            _dwBlocks = dwBlock;
            psb = (SStackBlock UNALIGNED *)
                _pmm->ResolvePtr(_dwBlocks, sizeof(SStackBlock));
            _dwCurrent = psb->dwStackTop;
            _cbAvailable = _cbBlock-(_dwCurrent-_dwBlocks);
            _pmm->ReleasePtr(_dwBlocks);
        }
    }
}

//+---------------------------------------------------------------------------
//
//  Member:	CStackAllocator::Reset, public
//
//  Synopsis:	Releases all memory in the stack
//
//  History:	29-Sep-94	DrewB	Created
//
//----------------------------------------------------------------------------

void CStackAllocator::Reset(void)
{
    DWORD dwBlock;
    SStackBlock UNALIGNED *psb;

    while (_dwBlocks != 0)
    {
        psb = (SStackBlock UNALIGNED *)
            _pmm->ResolvePtr(_dwBlocks, sizeof(SStackBlock));
        dwBlock = psb->dwNextBlock;
        _pmm->ReleasePtr(_dwBlocks);

        _pmm->FreeMemory(_dwBlocks);

        _dwBlocks = dwBlock;
    }

    _dwCurrent = 0;
    _cbAvailable = 0;
}

//+---------------------------------------------------------------------------
//
//  Function:   CStackAllocator::RecordState, public debug
//
//  Synopsis:   Records the current state of the stack
//
//  Arguments:  [psr] - Storage space for information
//
//  Modifies:   [psr]
//
//  History:    28-Apr-94       DrewB   Created
//
//----------------------------------------------------------------------------

#if DBG == 1
void CStackAllocator::RecordState(SStackRecord *psr)
{
    psr->dwStackPointer = _dwCurrent;
    psr->dwThreadId = GetCurrentThreadId();
}
#endif

//+---------------------------------------------------------------------------
//
//  Function:   CStackAllocator::CheckState, public debug
//
//  Synopsis:   Checks recorded information about the stack against its
//              current state
//
//  Arguments:  [psr] - Recorded information
//
//  History:    28-Apr-94       DrewB   Created
//
//----------------------------------------------------------------------------

#if DBG == 1
void CStackAllocator::CheckState(SStackRecord *psr)
{
    thkAssert(psr->dwThreadId == GetCurrentThreadId());

    if ((psr->dwStackPointer != 0 && psr->dwStackPointer != _dwCurrent) ||
        (psr->dwStackPointer == 0 &&
         _dwCurrent != 0 && _dwCurrent != BLOCK_START(_dwBlocks)))
    {
        thkDebugOut((DEB_ERROR, "Stack alloc change: 0x%08lX to 0x%08lX\n",
                     psr->dwStackPointer, _dwCurrent));

        if (_dwCurrent > BLOCK_START(_dwBlocks))
        {
            SStackMemTrace UNALIGNED *psmt;

            psmt = (SStackMemTrace UNALIGNED *)
                _pmm->ResolvePtr(_dwCurrent-sizeof(SStackMemTrace),
                                 sizeof(SStackMemTrace));
            thkDebugOut((DEB_ERROR, "Top alloc: %d bytes by %p\n",
                         psmt->cbSize, psmt->pvCaller));
            _pmm->ReleasePtr(_dwCurrent-sizeof(SStackMemTrace));
        }

        thkAssert(!((psr->dwStackPointer != 0 &&
                     psr->dwStackPointer != _dwCurrent) ||
                    (psr->dwStackPointer == 0 &&
                     _dwCurrent != 0 ||
                      _dwCurrent != BLOCK_START(_dwBlocks))));
    }
}
#endif