/***
*heapgrow.c - Grow the heap
*
*       Copyright (c) 1989-1997, Microsoft Corporation. All rights reserved.
*
*Purpose:
*       Get memory from OS and add to the heap.
*
*******************************************************************************/

#ifndef WINHEAP

#ifdef _WIN32


#include <cruntime.h>
#include <oscalls.h>
#include <heap.h>
#include <malloc.h>
#include <stdlib.h>

static int __cdecl _heap_new_region(unsigned, size_t);


/***
*_heap_grow() - Grow the heap
*
*Purpose:
*       Get memory from the OS and add it to the heap.
*
*Entry:
*       size_t _size = user's block request
*
*Exit:
*        0 = success, new mem is in the heap
*       -1 = failure
*
*Exceptions:
*
*******************************************************************************/

int __cdecl _heap_grow (
        REG1 size_t size
        )
{
        REG2 int index;
        int free_entry = -1;

        /*
         * Bump size to include header and round to nearest page boundary.
         */

        size += _HDRSIZE;
        size = _ROUND2(size,_PAGESIZE_);

        /*
         * Loop through the region table looking for an existing region
         * we can grow.  Remember the index of the first null region entry.
         *
         * size = size of grow request
         */

        for (index = 0; index < _HEAP_REGIONMAX; index++) {

                if ( (_heap_regions[index]._totalsize -
                    _heap_regions[index]._currsize) >= size )

                        /*
                         * Grow this region to satisfy the request.
                         */

                        return( _heap_grow_region(index, size) );


                if ( (free_entry == -1) &&
                    (_heap_regions[index]._regbase == NULL) )

                        /*
                         * Remember 1st free table entry for later
                         */

                        free_entry = index;

        }

        /*
         * Could not find any existing regions to grow.  Try to
         * get a new region.
         *
         * size = size of grow request
         * free_entry = index of first free entry in table
         */

        if ( free_entry >= 0 )

                /*
                 * Get a new region to satisfy the request.
                 */

                return( _heap_new_region(free_entry, size) );

        else
                /*
                 * No free table entries: return an error.
                 */

                return(-1);

}


/***
*_heap_new_region() - Get a new heap region
*
*Purpose:
*       Get a new heap region and put it in the region table.
*       Also, grow it large enough to support the caller's
*       request.
*
*       NOTES:
*       (1) Caller has verified that there is room in the _heap_region
*       table for another region.
*       (2) The caller must have rounded the size to a page boundary.
*
*Entry:
*       int index = index in table where new region data should go
*       size_t size = size of request (this has been rounded to a
*                       page-sized boundary)
*
*Exit:
*        0 = success
*       -1 = failure
*
*Exceptions:
*
*******************************************************************************/

static int __cdecl _heap_new_region (
        REG1 unsigned index,
        size_t size
        )
{
        void * region;
        REG2 unsigned int regsize;


        /*
         * Round the heap region size to a page boundary (in case
         * the user played with it).
         */

        regsize = _ROUND2(_heap_regionsize, _PAGESIZE_);

        /*
         * To acommodate large users, request twice
         * as big a region next time around.
         */

        if ( _heap_regionsize < _heap_maxregsize )
                _heap_regionsize *= 2 ;

        /*
         * See if region is big enough for request
         */

        if (regsize < size)
                regsize = size;

        /*
         * Go get the new region
         */

        if (!(region = VirtualAlloc(NULL, regsize, MEM_RESERVE,
        PAGE_READWRITE)))
                goto error;

        /*
         * Put the new region in the table.
         */

         _heap_regions[index]._regbase = region;
         _heap_regions[index]._totalsize = regsize;
         _heap_regions[index]._currsize = 0;


        /*
         * Grow the region to satisfy the size request.
         */

        if (_heap_grow_region(index, size) != 0) {

                /*
                 * Ouch.  Allocated a region but couldn't commit
                 * any pages in it.  Free region and return error.
                 */

                _heap_free_region(index);
                goto error;
        }


        /*
         * Good return
         */

        /* done:   unreferenced label to be removed */
                return(0);

        /*
         * Error return
         */

        error:
                return(-1);

}


/***
*_heap_grow_region() - Grow a heap region
*
*Purpose:
*       Grow a region and add the new memory to the heap.
*
*       NOTES:
*       (1) The caller must have rounded the size to a page boundary.
*
*Entry:
*       unsigned index = index of region in the _heap_regions[] table
*       size_t size = size of request (this has been rounded to a
*                       page-sized boundary)
*
*Exit:
*        0 = success
*       -1 = failure
*
*Exceptions:
*
*******************************************************************************/

int __cdecl _heap_grow_region (
        REG1 unsigned index,
        size_t size
        )
{
        size_t left;
        REG2 size_t growsize;
        void * base;
        unsigned dosretval;


        /*
         * Init some variables
         * left = space left in region
         * base = base of next section of region to validate
         */

        left = _heap_regions[index]._totalsize -
                _heap_regions[index]._currsize;

        base = (char *) _heap_regions[index]._regbase +
                _heap_regions[index]._currsize;

        /*
         * Make sure we can satisfy request
         */

        if (left < size)
                goto error;

        /*
         * Round size up to next _heap_growsize boundary.
         * (Must round _heap_growsize itself to page boundary, in
         * case user set it himself).
         */

        growsize = _ROUND2(_heap_growsize, _PAGESIZE_);
        growsize = _ROUND(size, growsize);

        if (left < growsize)
                growsize = left;

        /*
         * Validate the new portion of the region
         */

        if (!VirtualAlloc(base, growsize, MEM_COMMIT, PAGE_READWRITE))
                dosretval = GetLastError();
        else
                dosretval = 0;

        if (dosretval)
                /*
                 * Error committing pages.  If out of memory, return
                 * error, else abort.
                 */

                if (dosretval == ERROR_NOT_ENOUGH_MEMORY)
                        goto error;
                else
                        _heap_abort();


        /*
         * Update the region data base
         */

        _heap_regions[index]._currsize += growsize;




        /*
         * Add the memory to the heap
         */

        if (_heap_addblock(base, growsize) != 0)
                _heap_abort();


        /*
         * Good return
         */

        /* done:   unreferenced label to be removed */
                return(0);

        /*
         * Error return
         */

        error:
                return(-1);

}


/***
*_heap_free_region() - Free up a region
*
*Purpose:
*       Return a heap region to the OS and zero out
*       corresponding region data entry.
*
*Entry:
*       int index = index of region to be freed
*
*Exit:
*       void
*
*Exceptions:
*
*******************************************************************************/

void __cdecl _heap_free_region (
        REG1 int index
        )
{

        /*
         * Give the memory back to the OS
         */

        if (!VirtualFree(_heap_regions[index]._regbase, 0, MEM_RELEASE))
                _heap_abort();

        /*
         * Zero out the heap region entry
         */

        _heap_regions[index]._regbase = NULL;
        _heap_regions[index]._currsize =
        _heap_regions[index]._totalsize = 0;

}


#else  /* _WIN32 */

#if defined (_M_MPPC) || defined (_M_M68K)


#include <cruntime.h>
#include <heap.h>
#include <malloc.h>
#include <stdlib.h>
#include <string.h>
#include <macos\memory.h>               // Mac OS interface header
#include <macos\errors.h>
#include <macos\types.h>
#include <macos\traps.h>

static int __cdecl _heap_new_region(unsigned, size_t);
extern Handle hHeapRegions;
extern int _heap_region_table_cur;

static unsigned short *pMemErr = (unsigned short *)0x220;
static int index_start = 0;

/***
*_heap_grow() - Grow the heap
*
*Purpose:
*       Get memory from the OS and add it to the heap.
*
*Entry:
*       size_t _size = user's block request
*
*Exit:
*        0 = success, new mem is in the heap
*       -1 = failure
*
*Exceptions:
*
*******************************************************************************/

int __cdecl _heap_grow (
        REG1 size_t size
        )
{
        REG2 int index;
        struct _heap_region_ *pHeapRegions;
        int free_entry = -1;
        size_t sizeTmp;
        Handle hTemp;

        /*
         * Bump size to include header and round to nearest page boundary.
         */

        size += _HDRSIZE;
        size = _ROUND2(size, _GRANULARITY);

        /*
         * Loop through the region table looking for an existing region
         * we can grow.  Remember the index of the first null region entry.
         *
         * size = size of grow request
         */

        for ( index=index_start ; index < _heap_region_table_cur; index++ ) {

                pHeapRegions = (struct _heap_region_ *)(*hHeapRegions);
                        /*
                         * Grow this region to satisfy the request.
                         */
                if ( (pHeapRegions+index)->_regbase != NULL)
                        {
                        if (_heap_grow_region(index, size) != -1)
                                {
                                index_start = index;
                                return 0;
                                }
                        }

                pHeapRegions = (struct _heap_region_ *)(*hHeapRegions);
                if ( (free_entry == -1) &&
                    ((pHeapRegions+index)->_regbase == NULL) )

                        /*
                         * Remember 1st free table entry for later
                         */
                        {
                        free_entry = index;
                        break;
                        }

        }

        /*
         * Could not find any existing regions to grow.  Try to
         * get a new region.
         *
         * size = size of grow request
         * free_entry = index of first free entry in table
         */

        if ( free_entry == -1)
                /*
                 * No free table entries: grow heap region table.
                 */
                {
                sizeTmp = sizeof(struct _heap_region_)*(_heap_region_table_cur+_HEAP_REGIONMAX);
                if (hHeapRegions)
                        {
                        SetHandleSize(hHeapRegions, sizeTmp);
                        }
                if (hHeapRegions== NULL || *pMemErr != 0)
                        {
                        /*
                         * grow failed
                         */
                        hTemp = NewHandle(sizeTmp);
                        if (hTemp == NULL)
                                {
                                return (-1);
                                }
                        HLock(hTemp);
                        if (hHeapRegions != NULL)
                                {
                                BlockMove(*hHeapRegions, *hTemp, sizeof(struct _heap_region_)*_heap_region_table_cur);
                                DisposeHandle(hHeapRegions);
                                }
                        hHeapRegions = hTemp;
                        }
                /*
                 * set rest of the table to zero
                 */
                memset(*hHeapRegions + sizeof(struct _heap_region_)*_heap_region_table_cur, 0, sizeof(struct _heap_region_)*_HEAP_REGIONMAX);
                free_entry = _heap_region_table_cur;
                _heap_region_table_cur += _HEAP_REGIONMAX;
                }
        /*
         * Get a new region to satisfy the request.
         */

        return( _heap_new_region(free_entry, size) );
}


/***
*_heap_new_region() - Get a new heap region
*
*Purpose:
*       Get a new heap region and put it in the region table.
*       Also, grow it large enough to support the caller's
*       request.
*
*       NOTES:
*       (1) Caller has verified that there is room in the _heap_region
*       table for another region.
*       (2) The caller must have rounded the size to a page boundary.
*
*Entry:
*       int index = index in table where new region data should go
*       size_t size = size of request (this has been rounded to a
*                       page-sized boundary)
*
*Exit:
*        0 = success
*       -1 = failure
*
*Exceptions:
*
*******************************************************************************/

static int __cdecl _heap_new_region (
        REG1 unsigned index,
        size_t size
        )
{
        void * region;
        REG2 unsigned int regsize;
        struct _heap_region_ *pHeapRegions;


        /*
         * Round the heap region size to a page boundary (in case
         * the user played with it).
         */

        regsize = _ROUND2(_heap_regionsize, _PAGESIZE_);

        /*
         * See if region is big enough for request
         */

        if (regsize < size)
                regsize = size;

        /*
         * Go get the new region
         */

#ifdef _M_MPPC
        if ((region = NewPtr(regsize + 8)) == NULL)
#else  /* _M_MPPC */
        if ((region = NewPtr(regsize + 4)) == NULL)
#endif  /* _M_MPPC */
                {
                goto error;
                }

        /*
         * Put the new region in the table.
         */

        pHeapRegions = (struct _heap_region_ *)*hHeapRegions;
        if (((unsigned long)region & 0x3) != 0)
                {
                /* we are not allocating at 4 bytes bound */
                (pHeapRegions + index)->_regbase = (void *)_ROUND2((size_t)region, _GRANULARITY);
                }
        else
                {
                (pHeapRegions + index)->_regbase = region;
                }

        (pHeapRegions + index)->_regbaseCopy = region;
        (pHeapRegions + index)->_totalsize = regsize;
        (pHeapRegions + index)->_currsize = 0;


        /*
         * Grow the region to satisfy the size request.
         */

        if (_heap_grow_region(index, size) != 0) {

                /*
                 * Ouch.  Allocated a region but couldn't commit
                 * any pages in it.  Free region and return error.
                 */

                _heap_free_region(index);
                goto error;
        }


        /*
         * Good return
         */

        /* done:   unreferenced label to be removed */
                return(0);

        /*
         * Error return
         */

        error:
                return(-1);

}


/***
*_heap_grow_region() - Grow a heap region
*
*Purpose:
*       Grow a region and add the new memory to the heap.
*
*       NOTES:
*       (1) The caller must have rounded the size to a page boundary.
*
*Entry:
*       unsigned index = index of region in the _heap_regions[] table
*       size_t size = size of request (this has been rounded to a
*                       page-sized boundary)
*
*Exit:
*        0 = success
*       -1 = failure
*
*Exceptions:
*
*******************************************************************************/

int __cdecl _heap_grow_region (
        REG1 unsigned index,
        size_t size
        )
{
        size_t left;
        REG2 size_t growsize;
        void * base;
        struct _heap_region_ *pHeapRegions;
        struct _heap_region_ *pHRTmp;


        /*
         * Init some variables
         * left = space left in region
         * base = base of next section of region to validate
         */

        pHeapRegions = (struct _heap_region_ *)(*hHeapRegions);
        pHRTmp = pHeapRegions + index;
        left = pHRTmp->_totalsize - pHRTmp->_currsize;

        base = (char *) (pHRTmp->_regbase) + pHRTmp->_currsize;

        /*
         * Make sure we can satisfy request
         */
        growsize = _ROUND2(size, _GRANULARITY);

        if (left < growsize)
                {
                size_t sizeTmp;

                sizeTmp = growsize-left+1+ pHRTmp->_totalsize;
                sizeTmp = _ROUND2(sizeTmp, _GRANULARITY);
                SetPtrSize(pHRTmp->_regbase, sizeTmp);
                pHeapRegions = (struct _heap_region_ *)(*hHeapRegions);
                pHRTmp = pHeapRegions + index;
                if (*pMemErr != 0)
                        {
                        goto error;
                        }
                pHRTmp->_totalsize = sizeTmp;
                left = sizeTmp - pHRTmp->_currsize;
                base = (char *) (pHRTmp->_regbase) + pHRTmp->_currsize;
                }

        /*
         * Update the region data base
         */

        pHRTmp->_currsize += growsize;




        /*
         * Add the memory to the heap
         */

        if (_heap_addblock(base, growsize) != 0)
                _heap_abort();


        /*
         * Good return
         */

        /* done:   unreferenced label to be removed */
                return(0);

        /*
         * Error return
         */

        error:
                return(-1);

}


/***
*_heap_free_region() - Free up a region
*
*Purpose:
*       Return a heap region to the OS and zero out
*       corresponding region data entry.
*
*Entry:
*       int index = index of region to be freed
*
*Exit:
*       void
*
*Exceptions:
*
*******************************************************************************/

void __cdecl _heap_free_region (
        REG1 int index
        )
{
        struct _heap_region_ *pHeapRegions;

        /*
         * Give the memory back to the OS
         */

        pHeapRegions = (struct _heap_region_ *)(*hHeapRegions);
        if ((pHeapRegions + index)->_regbase)
                {
                DisposePtr((pHeapRegions + index)->_regbaseCopy);
                }

        /*
         * Zero out the heap region entry
         */

        pHeapRegions = (struct _heap_region_ *)(*hHeapRegions);
        (pHeapRegions + index)->_regbase = NULL;
        (pHeapRegions + index)->_currsize = 0;
        (pHeapRegions + index)->_totalsize = 0;

}


#endif  /* defined (_M_MPPC) || defined (_M_M68K) */

#endif  /* _WIN32 */

#endif  /* WINHEAP */
