/*
 * $Id: halloc.c 13984 2007-06-29 13:31:06Z cbiere $
 *
 * Copyright (c) 2006, Christian Biere
 *
 *----------------------------------------------------------------------
 * This file is part of gtk-gnutella.
 *
 *  gtk-gnutella is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  gtk-gnutella 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with gtk-gnutella; if not, write to the Free Software
 *  Foundation, Inc.:
 *      59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *----------------------------------------------------------------------
 */

/**
 * @ingroup lib
 * @file
 *
 * Hashtable-tracked allocator. Small chunks are allocated via walloc(),
 * whereas large chunks are served via alloc_pages(). The interface
 * is the same as that of malloc()/free(). The hashtable keeps track of
 * the sizes which should gain a more compact memory layout and is
 * considered more cache-friendly. alloc_pages() may cause an overhead
 * up to (pagesize - 1) per allocation. The advantage is that the pages
 * will be unmapped on hfree().
 *
 * Define USE_HALLOC to use this instead of the default memory allocation
 * functions used.
 *
 * @author Christian Biere
 * @date 2006
 */

#include "common.h"

RCSID("$Id: halloc.c 13984 2007-06-29 13:31:06Z cbiere $")

#include "halloc.h"
#include "hashtable.h"
#include "pagetable.h"
#include "misc.h"
#include "malloc.h"
#include "walloc.h"

#include "glib-missing.h"
#include "override.h"		/* Must be the last header included */

/*
 * Under REMAP_ZALLOC, do not define walloc(), wfree() and wrealloc().
 */

#if defined(USE_HALLOC)
#if defined(REMAP_ZALLOC) || defined(TRACK_MALLOC) || defined(MALLOC_STATS)
#undef USE_HALLOC
#endif	/* REMAP_ZALLOC || TRACK_MALLOC */
#endif	/* USE_HALLOC */

static size_t bytes_allocated;	/* Amount of bytes allocated */
static size_t chunks_allocated;	/* Amount of chunks allocated */

#if defined(USE_HALLOC)

static int use_page_table;
static page_table_t *pt_pages;
static hash_table_t *ht_pages;
size_t page_threshold;

union align {
  size_t	size;
  char		bytes[MEM_ALIGNBYTES];
};

static inline size_t
page_lookup(void *p)
{
	if (use_page_table) {
		return page_table_lookup(pt_pages, p);
	} else {
		return (size_t) hash_table_lookup(ht_pages, p);
	}
}

static inline int
page_insert(void *p, size_t size)
{
	if (use_page_table) {
		return page_table_insert(pt_pages, p, size);
	} else {
		return hash_table_insert(ht_pages, p, (void *) size);
	}
}

static inline int
page_remove(void *p)
{
	if (use_page_table) {
		return page_table_remove(pt_pages, p);
	} else {
		return hash_table_remove(ht_pages, p);
	}
}

static void
hdestroy_page_table_item(void *key, size_t size, void *unused_udata)
{
	(void) unused_udata;
	RUNTIME_ASSERT(size > 0);
	hfree(key);
}

static void
hdestroy_hash_table_item(void *key, void *value, void *unused_udata)
{
	(void) unused_udata;
	RUNTIME_ASSERT((size_t) value > 0);
	hfree(key);
}

static inline void
page_destroy(void)
{
	if (use_page_table) {
		page_table_foreach(pt_pages, hdestroy_page_table_item, NULL);
		page_table_destroy(pt_pages);
		pt_pages = NULL;
	} else {
		hash_table_foreach(ht_pages, hdestroy_hash_table_item, NULL);
		hash_table_destroy(ht_pages);
		ht_pages = NULL;
	}
}

static inline size_t
halloc_get_size(void *p)
{
	size_t size;

	size = page_lookup(p);
	if (size) {
		RUNTIME_ASSERT(size >= page_threshold);
	} else {
		union align *head = p;

		head--;
		RUNTIME_ASSERT(head->size > 0);
		RUNTIME_ASSERT(head->size < page_threshold);
		size = head->size;
	}
	return size;
}

/**
 * Allocate memory from a zone suitable for the given size.
 *
 * @return a pointer to the start of the allocated block.
 */
void *
halloc(size_t size)
{
	void *p;

	if (0 == size)
		return NULL;

	if (size < page_threshold) {
		union align *head;

		head = walloc(size + sizeof head[0]);
		head->size = size;
		p = &head[1];
	} else {
		int inserted;

		p = alloc_pages(size);
		inserted = page_insert(p, size);
		RUNTIME_ASSERT(inserted);
	}
	bytes_allocated += size;
	chunks_allocated++;
	return p;
}

/**
 * Same as halloc(), but fills the allocated memory with zeros before returning.
 */
void *
halloc0(size_t size)
{
	void *p = halloc(size);
	if (p) {
		memset(p, 0, size);
	}
	return p;
}

/**
 * Free a block allocated via halloc().
 */
void
hfree(void *p)
{
	size_t size;

	if (NULL == p)
		return;

	size = halloc_get_size(p);
	RUNTIME_ASSERT(size > 0);

	if (size < page_threshold) {
		union align *head = p;

		head--;
		wfree(head, size + sizeof(union align));
	} else {
		page_remove(p);
		free_pages(p, size);
	}
	bytes_allocated -= size;
	chunks_allocated--;
}

/**
 * Reallocate a block allocated via halloc().
 *
 * @return new block address.
 */
void *
hrealloc(void *old, size_t new_size)
{
	size_t old_size;
	void *p;

	if (NULL == old)
		return halloc(new_size);

	if (0 == new_size) {
		hfree(old);
		return NULL;
	}

	old_size = halloc_get_size(old);
	RUNTIME_ASSERT(old_size > 0);

	if (old_size >= new_size && old_size / 2 < new_size)
		return old;

	p = halloc(new_size);
	RUNTIME_ASSERT(NULL != p);

	memcpy(p, old, MIN(new_size, old_size));
	hfree(old);

	return p;
}

/**
 * Destroy all the zones we allocated so far.
 */
void
hdestroy(void)
{
	page_destroy();
}

gpointer
gm_malloc(gsize size)
{
	return halloc(size);
}

gpointer
gm_realloc(gpointer p, gsize size)
{
	return hrealloc(p, size);
}

void
gm_free(gpointer p)
{
	hfree(p);
}

static void
halloc_glib12_check(void)
{
#if !GLIB_CHECK_VERSION(2,0,0)
	gpointer p;

	/*
	 * Check whether the remapping is effective. This may not be
	 * the case for our GLib 1.2 hack. This is required for Darwin,
	 * for example.
	 */
	p = g_strdup("");
	if (0 == halloc_get_size(p)) {
		static GMemVTable zero_vtable;
		fprintf(stderr, "WARNING: Resetting g_mem_set_vtable\n");
		g_mem_set_vtable(&zero_vtable);
	} else {
		G_FREE_NULL(p);
	}
#endif	/* GLib < 2.0.0 */
}

static void
halloc_init_vtable(void)
{
	static GMemVTable vtable;

#if GLIB_CHECK_VERSION(2,0,0)
	vtable.malloc = gm_malloc;
	vtable.realloc = gm_realloc;
	vtable.free = gm_free;
#else	/* GLib < 2.0.0 */
	vtable.gmvt_malloc = gm_malloc;
	vtable.gmvt_realloc = gm_realloc;
	vtable.gmvt_free = gm_free;
#endif	/* GLib >= 2.0.0 */

	g_mem_set_vtable(&vtable);

	halloc_glib12_check();
}

void
halloc_init(void)
{
	static int initialized;

	RUNTIME_ASSERT(!initialized);
	initialized = TRUE;

#if GLIB_CHECK_VERSION(2,0,0)
	{
		/* NOTE: This string is not read-only because it will be overwritten
		 *		 by gm_setproctitle() as it becomes part of the environment.
		 *	     This happens at least on some Linux systems.
		 */
		static char variable[] = "G_SLICE=always-malloc";
		putenv(variable);
	}
#endif	/* GLib >= 2.0 */
	
	use_page_table = (size_t)-1 == (guint32)-1 && compat_pagesize() == 4096;
	page_threshold = compat_pagesize() - sizeof(union align);

	if (use_page_table) {
		pt_pages = page_table_new();
	} else {
		ht_pages = hash_table_new();
	}
	halloc_init_vtable();
}

#else	/* !USE_HALLOC */

void
halloc_init(void)
{
	/* NOTHING */
}

void
hdestroy(void)
{
	/* NOTHING */
}

#endif	/* USE_HALLOC */

size_t
halloc_bytes_allocated(void)
{
	return bytes_allocated;
}

size_t
halloc_chunks_allocated(void)
{
	return chunks_allocated;
}

/* vi: set ts=4 sw=4 cindent: */
