/***
*heapgrow.c - Grow the heap
*
*	Copyright (c) 1989-1991, Microsoft Corporation. All rights reserved.
*
*Purpose:
*	Get memory from OS and add to the heap.
*
*Revision History:
*	06-06-89   JCR	Module created.
*	07-19-89   JCR	Added region support
*	11-07-89   JCR	Region table is no longer "packed"
*	11-08-89   JCR	Use new _ROUND/_ROUND2 macros
*	11-10-89   JCR	Don't abort on ERROR_NOT_ENOUGH_MEMORY
*	11-13-89   GJF	Fixed copyright
*	12-18-89   GJF	Removed DEBUG286 stuff, a little tuning, cleaned up
*			the formatting a bit, changed header file name to
*			heap.h, also added _cdecl to functions (that didn't
*			already have explicit calling type)
*	03-11-90   GJF	Replaced _cdecl with _CALLTYPE1, added #include
*			<cruntime.h> and removed #include <register.h>.
*	03-29-90   GJF	Made _heap_new_region() _CALLTYPE4.
*	07-24-90   SBM	Compiles cleanly with -W3 (tentatively removed
*			unreferenced labels), removed '32' from API names
*	09-28-90   GJF	New-style function declarators.
*       12-04-90  SRW   Changed to include <oscalls.h> instead of <doscalls.h>
*       12-06-90  SRW   Added _CRUISER_ and _WIN32 conditionals.
*       02-01-91  SRW   Changed for new VirtualAlloc interface (_WIN32_)
*	04-09-91  PNT   Added _MAC_ conditional
*       04-26-91  SRW   Removed level 3 warnings
*
*******************************************************************************/

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

static int _CALLTYPE4 _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 _CALLTYPE1 _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 _CALLTYPE4 _heap_new_region (
	REG1 unsigned index,
	size_t size
	)
{
	void * region;
	REG2 unsigned int regsize;

#ifdef DEBUG

	int i;

	/*
	 * Make sure the size has been rounded to a page boundary
	 */

	if (size & (_PAGESIZE_-1))
		_heap_abort();

	/*
	 * Make sure there's a free slot in the table
	 */

	for (i=0; i < _HEAP_REGIONMAX; i++) {
		if (_heap_regions[i]._regbase == NULL)
			break;
	}

	if (i >= _HEAP_REGIONMAX)
		_heap_abort();

#endif

	/*
	 * 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	_CRUISER_

	if ( DOSALLOCMEM(&region, regsize, _NEWREGION, 0) != 0)
		goto error;

#else	/* ndef _CRUISER_ */

#ifdef	_WIN32_

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

#else	/* ndef _WIN32_ */

#ifdef	_MAC_

	TBD();

#else	/* ndef _MAC_ */

#error ERROR - ONLY CRUISER, WIN32, OR MAC TARGET SUPPORTED!

#endif	/* _MAC_ */

#endif	/* _WIN32_ */

#endif	/* _CRUISER_ */
	/*
	 * 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 _CALLTYPE1 _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
	 */

#ifdef	_CRUISER_

        dosretval = DOSSETMEM(base, growsize, _COMMIT);

#else	/* ndef _CRUISER_ */

#ifdef	_WIN32_

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

#else	/* ndef _WIN32_ */

#ifdef	_MAC_

	TBD();

#else	/* ndef _MAC_ */

#error ERROR - ONLY CRUISER, WIN32, OR MAC TARGET SUPPORTED!

#endif	/* _MAC_ */

#endif	/* _WIN32_ */

#endif	/* _CRUISER_ */

        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;


#ifdef DEBUG
	/*
	 * The current size should never be greater than the total size
	 */

	if (_heap_regions[index]._currsize > _heap_regions[index]._totalsize)
		_heap_abort();
#endif


	/*
	 * 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 _CALLTYPE1 _heap_free_region (
	REG1 int index
	)
{

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

#ifdef	_CRUISER_

	if (DOSFREEMEM(_heap_regions[index]._regbase))
		_heap_abort();

#else	/* ndef _CRUISER_ */

#ifdef	_WIN32_

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

#else	/* ndef _WIN32_ */

#ifdef	_MAC_

	TBD();

#else	/* ndef _MAC_ */

#error ERROR - ONLY CRUISER, WIN32, OR MAC TARGET SUPPORTED!

#endif	/* _MAC_ */

#endif	/* _WIN32_ */

#endif	/* _CRUISER_ */

	/*
	 * Zero out the heap region entry
	 */

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

}