/*
 * $Id: walloc.c,v 1.3 2004/12/09 04:28:08 cbiere Exp $
 *
 * Copyright (c) 2002-2003, Raphael Manfredi
 *
 * Explicit-width block allocator, based on zalloc().
 *
 *----------------------------------------------------------------------
 * 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
 *----------------------------------------------------------------------
 */

#include "common.h"

RCSID("$Id: walloc.c,v 1.3 2004/12/09 04:28:08 cbiere Exp $");

#include "walloc.h"
#include "zalloc.h"
#include "override.h"		/* Must be the last header included */

#if ZALLOC_ALIGNBYTES == 2
#define ZALLOC_ALIGNBITS 1
#elif ZALLOC_ALIGNBYTES == 4
#define ZALLOC_ALIGNBITS 2
#elif ZALLOC_ALIGNBYTES == 8
#define ZALLOC_ALIGNBITS 3
#else
#error "Unexpected ZALLOC_ALIGNBYTES value"
#endif

#ifdef TRACK_ZALLOC
#undef walloc					/* We want to define the real routines */
#undef walloc0
#undef wrealloc
#endif

#define WALLOC_MAX		4096	/* Passed this size, use malloc() */
#define WALLOC_CHUNK	4096	/* Target chunk size for small structs */
#define WALLOC_MINCOUNT	8		/* Minimum amount of structs in a chunk */

#define WZONE_SIZE	(WALLOC_MAX / ZALLOC_ALIGNBYTES)

static struct zone *wzone[WZONE_SIZE];

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

#ifndef REMAP_ZALLOC
/*
 * walloc
 *
 * Allocate memory from a zone suitable for the given size.
 *
 * The basics for this algorithm is to allocate from fixed-sized zones, which
 * are multiples of ZALLOC_ALIGNBYTES until WALLOC_MAX (e.g. 8, 16, 24, 40, ...)
 * and to malloc() if size is greater or equal to WALLOC_MAX.
 * Naturally, zones are allocated on demand only.
 *
 * Returns a pointer to the start of the allocated block.
 */
gpointer walloc(int size)
{
	zone_t *zone;
	gint rounded = zalloc_round(size);
	gint idx;

	g_assert(size > 0);

	if (rounded >= WALLOC_MAX)
		return g_malloc(size);		/* Too big for efficient zalloc() */

	idx = rounded >> ZALLOC_ALIGNBITS;

	STATIC_ASSERT(WALLOC_MAX >> ZALLOC_ALIGNBITS == WZONE_SIZE);
	g_assert(idx >= 0 && idx < WZONE_SIZE);

	if (!(zone = wzone[idx])) {
		gint count;

		/*
		 * We're paying this computation/allocation cost once per size!
		 *
		 * Try to create approximately WALLOC_CHUNK byte chunks, but
		 * capable of holding at least WALLOC_MINCOUNT structures.
		 */

		count = WALLOC_CHUNK / rounded;
		count = MAX(count, WALLOC_MINCOUNT);

		if (!(zone = wzone[idx] = zget(rounded, count)))
			g_error("zget() failed?");
	}

	return zalloc(zone);
}

/*
 * walloc0
 *
 * Same as walloc(), but zeroes the allocated memory before returning.
 */
gpointer walloc0(int size)
{
	gpointer p = walloc(size);

	if (p != NULL)
		memset(p, 0, size);

	return p;
}

/*
 * wfree
 *
 * Free a block allocated via walloc().
 *
 * The size is used to find the zone from which the block was allocated, or
 * to determine that we actually malloc()'ed it so it gets free()'ed.
 */
void wfree(gpointer ptr, gint size)
{
	zone_t *zone;
	gint rounded = zalloc_round(size);
	gint idx;

	g_assert(ptr);
	g_assert(size > 0);

	if (rounded >= WALLOC_MAX) {
		g_free(ptr);
		return;
	}

	idx = rounded >> ZALLOC_ALIGNBITS;

	g_assert(idx >= 0 && idx < WZONE_SIZE);

	zone = wzone[idx];
	g_assert(zone);

	zfree(zone, ptr);
}

/*
 * wrealloc
 *
 * Reallocate a block allocated via walloc().
 * Returns new block address.
 */
gpointer wrealloc(gpointer old, gint old_size, gint new_size)
{
	gpointer new;
	gulong rounded = zalloc_round(new_size);

	if (zalloc_round(old_size) == rounded)
		return old;

	new = walloc(new_size);
	memcpy(new, old, MIN(old_size, new_size));
	wfree(old, old_size);

	return new;
}
#endif	/* !REMAP_ZALLOC */

/***
 *** Tracking versions of the walloc routines.
 ***/

#ifdef TRACK_ZALLOC
/*
 * walloc_track
 *
 * Allocate memory from a zone suitable for the given size.
 * Returns a pointer to the start of the allocated block.
 */
gpointer walloc_track(int size, gchar *file, gint line)
{
	zone_t *zone;
	gint rounded = zalloc_round(size);
	gint idx;

	g_assert(size > 0);

	if (rounded >= WALLOC_MAX)
#ifdef TRACK_MALLOC
		return malloc_track(size, file, line);
#else
		return g_malloc(size);		/* Too big for efficient zalloc() */
#endif

	idx = rounded >> ZALLOC_ALIGNBITS;

	g_assert(WALLOC_MAX >> ZALLOC_ALIGNBITS == WZONE_SIZE);
	g_assert(idx >= 0 && idx < WZONE_SIZE);

	if (!(zone = wzone[idx])) {
		gint count;

		/*
		 * We're paying this computation/allocation cost once per size!
		 *
		 * Try to create approximately WALLOC_CHUNK byte chunks, but
		 * capable of holding at least WALLOC_MINCOUNT structures.
		 */

		count = WALLOC_CHUNK / rounded;
		count = MAX(count, WALLOC_MINCOUNT);

		if (!(zone = wzone[idx] = zget(rounded, count)))
			g_error("zget() failed?");
	}

	return zalloc_track(zone, file, line);
}

/*
 * walloc0_track
 *
 * Same as walloc_track(), but zeroes the allocated memory before returning.
 */
gpointer walloc0_track(int size, gchar *file, gint line)
{
	gpointer p = walloc_track(size, file, line);

	if (p != NULL)
		memset(p, 0, size);

	return p;
}

/*
 * wrealloc_track
 *
 * Reallocate a block allocated via walloc().
 * Returns new block address.
 */
gpointer wrealloc_track(gpointer old, gint old_size, gint new_size,
	gchar *file, gint line)
{
	gpointer new;
	gint rounded = zalloc_round(new_size);

	if (zalloc_round(old_size) == (gulong) rounded)
		return old;

	new = walloc_track(new_size, file, line);
	memcpy(new, old, MIN(old_size, new_size));
	wfree(old, old_size);

	return new;
}
#endif	/* TRACK_ZALLOC */

/*
 * wdestroy
 *
 * Destroy all the zones we allocated so far.
 */
void wdestroy(void)
{
	gint i;

	for (i = 0; i < WZONE_SIZE; i++) {
		if (wzone[i] != NULL) {
			zdestroy(wzone[i]);
			wzone[i] = NULL;
		}
	}
}

