/*
 * jit-vmem.c - Virtual memory routines.
 *
 * Copyright (C) 2011  Aleksey Demakov
 *
 * The libjit library is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation, either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * The libjit library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with the libjit library.  If not, see
 * <http://www.gnu.org/licenses/>.
 */

#include <jit/jit-vmem.h>
#include "jit-config.h"

#if defined(JIT_VMEM_WIN32)
# include <windows.h>
#elif defined(JIT_VMEM_MMAP)
# include <sys/mman.h>
#endif

#if !defined(JIT_WIN32_PLATFORM) && defined(HAVE_UNISTD_H)
# include <unistd.h>
#endif

/*
 * Define getpagesize() if not provided
 */
#if !defined(JIT_WIN32_PLATFORM) && !defined(HAVE_GETPAGESIZE)
# if defined(NBPG)
#  define getpagesize() (NBPG)
# elif defined(PAGE_SIZE)
#  define getpagesize() (PAGE_SIZE)
# else
#  define getpagesize() (4096)
# endif
#endif

/*
 * Make sure that "MAP_ANONYMOUS" is correctly defined, because it
 * may not exist on some variants of Unix.
 */
#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
# define MAP_ANONYMOUS        MAP_ANON
#endif

static jit_uint page_size;

#if defined(JIT_VMEM_WIN32)
static DWORD
convert_prot(jit_prot_t prot)
{
	switch(prot)
	{
	case JIT_PROT_NONE:
		return PAGE_NOACCESS;
	case JIT_PROT_READ:
		return PAGE_READONLY;
	case JIT_PROT_READ_WRITE:
		return PAGE_READWRITE;
	case JIT_PROT_EXEC_READ:
		return PAGE_EXECUTE_READ;
	case JIT_PROT_EXEC_READ_WRITE:
		return PAGE_EXECUTE_READWRITE;
	}
	return PAGE_NOACCESS;
}
#elif defined(JIT_VMEM_MMAP)
static int
convert_prot(jit_prot_t prot)
{
	switch(prot)
	{
	case JIT_PROT_NONE:
		return PROT_NONE;
	case JIT_PROT_READ:
		return PROT_READ;
	case JIT_PROT_READ_WRITE:
		return PROT_READ | PROT_WRITE;
	case JIT_PROT_EXEC_READ:
		return PROT_EXEC | PROT_READ;
	case JIT_PROT_EXEC_READ_WRITE:
		return PROT_EXEC | PROT_READ | PROT_WRITE;
	}
	return PROT_NONE;
}
#endif

void
jit_vmem_init(void)
{
#if defined(JIT_VMEM_WIN32)
	/* Get the page size from a Windows-specific API */
	SYSTEM_INFO sysInfo;
	GetSystemInfo(&sysInfo);
	page_size = (jit_uint) (sysInfo.dwPageSize);
#else
	/* Get the page size using a Unix-like sequence */
	page_size = (jit_uint) getpagesize();
#endif
}

/*@
 * @deftypefun {unsigned int} jit_vmem_page_size (void)
 * Get the page allocation size for the system.  This is the preferred
 * unit when making calls to @code{_jit_malloc_exec}.  It is not
 * required that you supply a multiple of this size when allocating,
 * but it can lead to better performance on some systems.
 * @end deftypefun
@*/
jit_uint
jit_vmem_page_size(void)
{
	return page_size;
}

jit_nuint
jit_vmem_round_up(jit_nuint value)
{
	return (value + page_size - 1) & ~(page_size - 1);
}

jit_nuint
jit_vmem_round_down(jit_nuint value)
{
	return ((jit_nuint) value) & ~(page_size - 1);
}

void *
jit_vmem_reserve(jit_uint size)
{
#if defined(JIT_VMEM_WIN32)

	return VirtualAlloc(NULL, size, MEM_RESERVE, PAGE_NOACCESS);

#elif defined(JIT_VMEM_MMAP)

	void *addr;

	addr = mmap(0, size, PROT_NONE,
		    MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
	if(addr == MAP_FAILED)
	{
		return (void *) 0;
	}
	return addr;

#else
	return (void *) 0;
#endif
}

void *
jit_vmem_reserve_committed(jit_uint size, jit_prot_t prot)
{
#if defined(JIT_VMEM_WIN32)

	DWORD nprot;

	nprot = convert_prot(prot);
	return VirtualAlloc(NULL, size, MEM_RESERVE | MEM_COMMIT, nprot);

#elif defined(JIT_VMEM_MMAP)

	void *addr;
	int nprot;

	nprot = convert_prot(prot);
	addr = mmap(0, size, nprot, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	if(addr == MAP_FAILED)
	{
		return (void *) 0;
	}
	return addr;

#else
	return (void *) 0;
#endif
}

int
jit_vmem_release(void *addr, jit_uint size)
{
#if defined(JIT_VMEM_WIN32)

	return VirtualFree(addr, 0, MEM_RELEASE) != 0;

#elif defined(JIT_VMEM_MMAP)

	return munmap(addr, size) == 0;

#else
	return 0;
#endif
}

int
jit_vmem_commit(void *addr, jit_uint size, jit_prot_t prot)
{
#if defined(JIT_VMEM_WIN32)

	DWORD nprot;

	nprot = convert_prot(prot);
	return VirtualAlloc(addr, size, MEM_COMMIT, nprot);

#elif defined(JIT_VMEM_MMAP)

	int nprot;

	nprot = convert_prot(prot);
	addr = mmap(0, size, nprot, MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
	if(addr == MAP_FAILED)
	{
		return (void *) 0;
	}
	return addr;

#else
	return 0;
#endif
}

int
jit_vmem_decommit(void *addr, jit_uint size)
{
#if defined(JIT_VMEM_WIN32)

	return VirtualFree(addr, size, MEM_DECOMMIT) != 0;

#elif defined(JIT_VMEM_MMAP)

#if defined(MADV_FREE)
	int result = madvise(addr, size, MADV_FREE);
	if(result < 0)
	{
		return 0;
	}
#elif defined(MADV_DONTNEED) && defined(JIT_LINUX_PLATFORM)
	int result = madvise(addr, size, MADV_DONTNEED);
	if(result < 0)
	{
		return 0;
	}
#endif

	addr = mmap(addr, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0);
	if(addr == MAP_FAILED)
	{
		return 0;
	}
	return 1;

#else
	return 0;
#endif
}

int
jit_vmem_protect(void *addr, jit_uint size, jit_prot_t prot)
{
#if defined(JIT_VMEM_WIN32)

	DWORD nprot, oprot;

	nprot = convert_prot(prot);
	if(VirtualProtect(addr, size, nprot, &oprot) == 0)
	{
		return 0;
	}
	return 1;

#elif defined(JIT_VMEM_MMAP)

	int nprot;

	nprot = convert_prot(prot);
	if(mprotect(addr, size, nprot) < 0)
	{
		return 0;
	}
	return 1;

#else
	return 0;
#endif
}
