/* --- Copyright University of Sussex 1998. All rights reserved. ----------
 * File:			C.win32/extern/src/memory.c
 * Purpose:			Memory management -- heap and callstack areas
 * Author:			Robert John Duncan, Jun 14 1994 (see revisions)
 * Documentation:
 * Related Files:
 */

#include "popcore.h"

DWORD __pop_nr_seg[20000];
DWORD __pop_nr_seg_nbytes = sizeof(__pop_nr_seg);
	/*	Static space for the pop no-restore segment.
		Must be large enough to contain process args/env vars, etc.
	*/


/***************************************************************************
*                                                                          *
*	The Private Heap                                                       *
*                                                                          *
***************************************************************************/

static HANDLE private_heap;
	/*	For dynamic memory allocation outside of the Poplog heap
	*/

void *pop_alloc(DWORD nbytes)
{
	return HeapAlloc(private_heap, 0, nbytes);
}

void *pop_realloc(void *ptr, DWORD nbytes)
{
	return HeapReAlloc(private_heap, 0, ptr, nbytes);
}

BOOL pop_free(void *ptr)
{
	return HeapFree(private_heap, 0, ptr);
}


/***************************************************************************
*                                                                          *
*	The Poplog Heap                                                        *
*                                                                          *
***************************************************************************/

static SYSTEM_INFO system_info;

#define PAGESIZE system_info.dwPageSize

#define ROUND_TO_PAGE(n) \
	(((n) + (PAGESIZE-1)) & ~(PAGESIZE-1))

#define RESERVE_HEAP(base, size) \
	VirtualAlloc(base, size, MEM_RESERVE, PAGE_NOACCESS)

#define RELEASE_HEAP(base, size) \
	VirtualFree(base, size, MEM_RELEASE)

#define COMMIT_HEAP(base, size) \
	VirtualAlloc(base, size, MEM_COMMIT, PAGE_READWRITE)

#define DECOMMIT_HEAP(base, size) \
	VirtualFree(base, size, MEM_DECOMMIT)

#define HEAP_MAX (256*1024*1024)
#define HEAP_MIN (512*1024)
#define HEAP_STEP (64*1024)

#define HEAP_PREFERRED_FIRST	0x20000000
#define HEAP_PREFERRED_LAST		0x60000000
#define HEAP_PREFERRED_STEP		0x10000000

static BYTE *heap_start, *heap_end, *heap_ptr;

void pop_init_heap(void)
	/*  Initialise the heap area: that means reserving a large block of
		memory -- bounded by HEAP_START and HEAP_END -- from which
		heap space can be committed on demand by Poplog. Reserving the
		space means that it can't be used to satisfy any other dynamic
		storage requests, so avoiding any possibility of fragmentation.
			It pays to claim a lot of space, because once it's used up,
		we don't have the mechanisms in place to get any more. On the
		other hand we have to leave room for memory claimed by external
		code, including DLLs loaded at run-time.
	*/
{
	char* addr;
	void *base;
	MEMORY_BASIC_INFORMATION mbi;

	if (heap_start != 0) return;	/* second call */

	GetSystemInfo(&system_info);

	/* reserve the Pop heap space */
	for (addr = (char*)HEAP_PREFERRED_FIRST;
			addr <= (char*)HEAP_PREFERRED_LAST;
			addr += HEAP_PREFERRED_STEP) {
		if ((base = RESERVE_HEAP((void*)addr, HEAP_MAX)) != NULL) break;
	}
	if (base == NULL) {
		/* couldn't get it where we wanted; try elsewhere */
		base = RESERVE_HEAP(NULL, HEAP_MAX);
	}
	if (base == NULL) {
		/* still no luck -- try for whatever we can */
		DWORD hi, lo, mid;

		hi = HEAP_MAX/HEAP_STEP;
		lo = HEAP_MIN/HEAP_STEP;
		while (hi - lo > 1) {
			mid = lo + (hi - lo) / 2;
			base = RESERVE_HEAP(NULL, mid*HEAP_STEP);
			if (base == NULL)
				hi = mid;
			else if (RELEASE_HEAP(base, 0))
				lo = mid;
			else
				/* reserved it, but can't release it ? */
				RaiseException(POP_EXCEPTION_INIT_FAILED, 0, 0, NULL);
		}

		/* last successful value -- if any -- must have been LO */
		base = RESERVE_HEAP(NULL, lo*HEAP_STEP);
		if (base == NULL) {
			/* can't get any memory at all */
			RaiseException(POP_EXCEPTION_INIT_FAILED, 0, 0, NULL);
		}
	}

	/* create the private heap */
	private_heap = HeapCreate(0, 0, 0);
	if (private_heap == NULL) {
		RaiseException(POP_EXCEPTION_INIT_FAILED, 0, 0, NULL);
	}

	/* set global heap parameters */
	VirtualQuery(base, &mbi, sizeof mbi);
	heap_start = base;
	heap_end = heap_start + mbi.RegionSize;
	heap_ptr = heap_start;

}

BYTE *pop_get_heap_ptr(void)
	/*	Return the limit address of the committed portion of the heap
	*/
{
	return heap_ptr;
}

BYTE *pop_set_heap_ptr(BYTE *hi, BYTE *lo)
	/*	Set the limit of the committed heap: HI is the desired value, LO is
		something we'd settle for (LO <= HI). HI may be less than the
		current limit, in which case we want to give some memory back.
	*/
{
	hi = (BYTE*)ROUND_TO_PAGE((DWORD)hi);
	if (hi > heap_ptr) {
		/* claiming space */
		if (COMMIT_HEAP(heap_ptr, hi - heap_ptr) != NULL) {
			heap_ptr = hi;
		}
		else {
			/* best allocation failed -- try for less */
			lo = (BYTE*)ROUND_TO_PAGE((DWORD)lo);
			if (COMMIT_HEAP(heap_ptr, lo - heap_ptr) != NULL) {
				heap_ptr = lo;
			}
			else {
				/* still no good -- return failure */
				return NULL;
			}
		}
	}
	else if (hi < heap_ptr) {
		/* freeing space */
		if (DECOMMIT_HEAP(hi, heap_ptr - hi)) {
			heap_ptr = hi;
		}
	}

	/* return the actual value achieved */
	return heap_ptr;
}


/***************************************************************************
*                                                                          *
*	Memory Protection                                                      *
*                                                                          *
***************************************************************************/

static DWORD prot_map[] = {
	PAGE_NOACCESS,				/* --- */
	PAGE_EXECUTE,				/* --X */
	PAGE_READWRITE,				/* -W- */
	PAGE_EXECUTE_READWRITE,		/* -WX */
	PAGE_READONLY,				/* R-- */
	PAGE_EXECUTE_READ,			/* R-X */
	PAGE_READWRITE,				/* RW- */
	PAGE_EXECUTE_READWRITE,		/* RWX */
};

BOOL pop_virtual_protect(BYTE *base, BYTE *limit, DWORD prot)
	/*	Set protection on the region of the heap between BASE and LIMIT.
		Protection is expressed using the Unix convention of a three-
		bit field indicating desired RWX access (see "memseg.ph"); this
		has to be converted to the Win32 equivalent and applied using
		VirtualProtect.
	*/
{
	return VirtualProtect(
		base,							/* region start */
		limit-base,						/* region size */
		prot_map[prot&POP_M_PROT_ALL],	/* converted protection */
		&prot);							/* old protection (not wanted) */
}

BOOL pop_has_read_access(VOID *address)
	/*	Test whether the byte at ADDRESS is readable.
	*/
{
	return !IsBadReadPtr(address, 1);
}


/***************************************************************************
*                                                                          *
*	Callstack Limits                                                       *
*                                                                          *
***************************************************************************/

PVOID pop_get_callstack_base(void)
	/*	Returns the lowest callstack address. This works on the assumption
		that:
			(1) the stack grows down
			(2) the whole of the stack segment was reserved with a single
				call to VirtualAlloc
		in which case the base address of the allocated region should be
		the address we want. We add two pages for a guard page and an
		overflow page.
	*/
{
	MEMORY_BASIC_INFORMATION mbi;
	VirtualQuery(&mbi, &mbi, sizeof mbi);
	return (void*)((BYTE*)mbi.AllocationBase + 2*PAGESIZE);
}

/* --- Revision History ---------------------------------------------------
--- Robert Duncan, Aug 11 1998
		Modified the heap allocation algorithm further to try a range of
		fixed addresses, in between the default DLL address starting at
		0x10000000 and growing up the system DLL address at 0x80000000
		growing down.
--- Robert Duncan, Jun 20 1996
		Changed pop_init_heap() to try allocating at a fixed address so
		that saved images made by different executables are more likely to
		be compatible
 */
