/*
 * $Id: fragcheck.c 13436 2007-04-26 20:22:55Z 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
 *
 * Memory fragmentation.
 *
 * @author Christian Biere
 * @date 2006
 */

#include "common.h"
#include "lib/fragcheck.h"

#ifdef FRAGCHECK

#include "lib/glib-missing.h"
#include "lib/bit_array.h"
#include "lib/misc.h"

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

RCSID("$Id: fragcheck.c 13436 2007-04-26 20:22:55Z cbiere $")

#if HAVE_GCC(3, 0)
#define FRAGCHECK_TRACK_CALLERS
#endif	/* GCC >= 3.0 */

#if 0
#define FRAGCHECK_VERBOSE
#endif

union alloc {
	void *p;
	long l;
	int i;
	short s;
	char c;
	double d;
	float f;
};

#ifdef FRAGCHECK_TRACK_CALLERS
enum fragcheck_ret {
	FC_RET_0,
	FC_RET_1,
	FC_RET_2,
	FC_RET_3,

	NUM_FC_RET
};
#endif

struct fragcheck_meta {
	const void *base;
	size_t size;
#ifdef FRAGCHECK_TRACK_CALLERS
	size_t ret[NUM_FC_RET];
#endif	/* FRAGCHECK_TRACK_CALLERS */
};

/* Configured for a maximum address space of 512 MiB */
#define BIT_COUNT (512 * 1024 * 1024 / sizeof (union alloc))
#define MAX_ALLOC_NUM (1024 * 1024)

static struct {
	/* The index uses two bits per slot. If the first is set,
	 * the item is or was in use, if the second bit is set the item
	 * has been deleted, otherwise it is in use.
	 *
	 * 00: free
	 * 01: deleted
	 * 10: in-use
	 * 11: re-used
	 */
	struct fragcheck_meta meta_tab[MAX_ALLOC_NUM];
	bit_array_t meta_index[BIT_ARRAY_SIZE(MAX_ALLOC_NUM * 2)];

	size_t alloc_base;
	bit_array_t allocated[BIT_ARRAY_SIZE(BIT_COUNT)];
	bit_array_t touched[BIT_ARRAY_SIZE(BIT_COUNT)];
} vars;

static inline guint32
fragcheck_meta_hash(const void *p)
{
	size_t x = (size_t) p;

	x ^= x >> 31;
	return x % MAX_ALLOC_NUM;
}

static struct fragcheck_meta *
fragcheck_meta_new(const void *p)
{
	size_t i, slot = fragcheck_meta_hash(p);

	for (i = 0; i < MAX_ALLOC_NUM; i++) {
		size_t x = slot * 2;

		if (!bit_array_get(vars.meta_index, x)) {
			bit_array_set(vars.meta_index, x);
			return &vars.meta_tab[slot];
		}
		slot += 37;
		slot %= MAX_ALLOC_NUM;
	}
	return NULL;
}

static struct fragcheck_meta *
fragcheck_meta_lookup(const void *p)
{
	guint32 i, slot = fragcheck_meta_hash(p);

	for (i = 0; i < MAX_ALLOC_NUM; i++) {
		size_t x = slot * 2;

		if (bit_array_get(vars.meta_index, x)) {
			if (p == vars.meta_tab[slot].base) {
				return &vars.meta_tab[slot];
			}
		} else if (!bit_array_get(vars.meta_index, x + 1)) {
			break;
		}
		slot += 37;
		slot %= MAX_ALLOC_NUM;
	}
	return NULL;
}

static void
fragcheck_meta_delete(struct fragcheck_meta *meta)
{
	RUNTIME_ASSERT(meta);
	{
		size_t x, slot;
		
		slot = meta - &vars.meta_tab[0];
		x = slot * 2;
		bit_array_clear(vars.meta_index, x);		/* free slot */
		bit_array_set(vars.meta_index, x + 1);	/* deleted */
		meta->base = NULL;
		meta->size = 0;
	}
}

static gpointer
my_malloc(gsize n)
{
	struct fragcheck_meta *meta;
	void *p;

	n = round_size(sizeof (union alloc), n);
	p = malloc(n);

#ifdef FRAGCHECK_VERBOSE 
	printf("%s(%lu)=0x%08lx\n", __func__, (unsigned long) n, (unsigned long) p);
#endif	/* FRAGCHECK_VERBOSE */

	RUNTIME_ASSERT(p);
	RUNTIME_ASSERT(0 == (size_t) p % sizeof (union alloc));
	RUNTIME_ASSERT((size_t) p >= (size_t) vars.alloc_base);
	RUNTIME_ASSERT(!fragcheck_meta_lookup(p));

	if (!vars.alloc_base) {
		/* The divisor is a good guess and depends on the malloc
		 * implementation. The first call doesn't necessarily
		 * return the lowest available address. */
		vars.alloc_base = (size_t) p / 2;
	} else {
		RUNTIME_ASSERT(vars.alloc_base <= (size_t) p);
	}

	meta = fragcheck_meta_new(p);
	RUNTIME_ASSERT(meta);
	meta->base = p;
	meta->size = n;

#ifdef FRAGCHECK_TRACK_CALLERS
	{
		enum fragcheck_ret i;
		for (i = FC_RET_0; i < NUM_FC_RET; i++) {
			switch (i) {
#define CASE(x) \
	case x: meta->ret[x] = (size_t) __builtin_return_address(x + 1); break;
			CASE(FC_RET_0)
			CASE(FC_RET_1)
			CASE(FC_RET_2)
			CASE(FC_RET_3)
			case NUM_FC_RET: RUNTIME_UNREACHABLE();
#undef CASE
			}
		}
	}
#endif	/* FRAGCHECK_TRACK_CALLERS */
	
	{
		size_t from, to;

		from = ((size_t) p - vars.alloc_base) / sizeof (union alloc);
		to = from + (meta->size / sizeof (union alloc)) - 1;

		RUNTIME_ASSERT(from < BIT_COUNT);
		RUNTIME_ASSERT(to <= BIT_COUNT);
		RUNTIME_ASSERT(to >= from);
	
		RUNTIME_ASSERT(!bit_array_get(vars.allocated, from));
		RUNTIME_ASSERT(!bit_array_get(vars.allocated, to));
		bit_array_set_range(vars.allocated, from, to);
		bit_array_set_range(vars.touched, from, to);
	}
	RUNTIME_ASSERT(fragcheck_meta_lookup(p));
	return p;
}

static void
my_free(gpointer p)
{
#ifdef FRAGCHECK_VERBOSE 
	printf("%s(%p)\n", __func__, p);
#endif	/* FRAGCHECK_VERBOSE */

	if (p) {
		struct fragcheck_meta *meta;

		meta = fragcheck_meta_lookup(p);
		RUNTIME_ASSERT(meta);
		RUNTIME_ASSERT(meta->size >= sizeof (union alloc));
		RUNTIME_ASSERT(0 == meta->size % sizeof (union alloc));
		RUNTIME_ASSERT(0 == (size_t) p % sizeof (union alloc));
		RUNTIME_ASSERT((size_t) p >= (size_t) vars.alloc_base);
		{
			size_t from, to;

			from = ((size_t) p - vars.alloc_base) / sizeof (union alloc);
			to = from + (meta->size / sizeof (union alloc)) - 1;

			RUNTIME_ASSERT(from < BIT_COUNT);
			RUNTIME_ASSERT(to <= BIT_COUNT);
			RUNTIME_ASSERT(to >= from);

			RUNTIME_ASSERT(bit_array_get(vars.allocated, from));
			RUNTIME_ASSERT(bit_array_get(vars.allocated, to));
			RUNTIME_ASSERT(bit_array_get(vars.touched, from));
			RUNTIME_ASSERT(bit_array_get(vars.touched, to));
			bit_array_clear_range(vars.allocated, from, to);
		}
		memset(p, 0, meta->size);
		fragcheck_meta_delete(meta);
		RUNTIME_ASSERT(!fragcheck_meta_lookup(p));
		free(p);
	}
}

static gpointer
my_realloc(gpointer p, gsize n)
{
	static volatile gboolean lock;
	gpointer x;

	RUNTIME_ASSERT(!lock);
	lock = TRUE;

#ifdef FRAGCHECK_VERBOSE 
	printf("%s(%p, %lu)\n", __func__, p, (unsigned long) n);
#endif	/* FRAGCHECK_VERBOSE */

	RUNTIME_ASSERT(n > 0);
	x = my_malloc(n);
	if (p) {
		const struct fragcheck_meta *meta;

		meta = fragcheck_meta_lookup(p);
		RUNTIME_ASSERT(meta);
		RUNTIME_ASSERT(meta->size >= sizeof (union alloc));
		RUNTIME_ASSERT(0 == meta->size % sizeof (union alloc));
		RUNTIME_ASSERT(0 == (size_t) p % sizeof (union alloc));
		RUNTIME_ASSERT((size_t) p >= (size_t) vars.alloc_base);

		memcpy(x, p, MIN(meta->size, n));
		my_free(p);
	}
	lock = FALSE;
	return x;
}

void
alloc_dump(FILE *f, gboolean unused_flag)
{
	size_t i, base_i = 0;
	int cur = -1;

	(void) unused_flag;

	for (i = 0; /* NOTHING */; i++) {
		int v;

		if (i < BIT_COUNT) {
			if (bit_array_get(vars.touched, i)) {
				v = bit_array_get(vars.allocated, i) ? 'a' : 'f';
			} else {
				v = 'u';
			}
		} else {
			v = -2;
		}

		if (v != cur) {
			size_t n = i - base_i;
			if (n > 0) {
				size_t base = vars.alloc_base + base_i * sizeof (union alloc);
				size_t len = n * sizeof (union alloc);

				fprintf(f, "%c base: 0x%08lx length: %8.1lu",
					cur, (unsigned long) base, (unsigned long) len);
				fputs("\n", f);
			}
			if (i == BIT_COUNT) {
				fflush(f);
				break;
			}
			base_i = i;
			cur = v;
		}
	}
}

void
alloc_dump2(FILE *f, gboolean unused_flag)
{
	size_t i;

	(void) unused_flag;

	for (i = 0; i < MAX_ALLOC_NUM; i++) {
		const struct fragcheck_meta *meta;

		meta = &vars.meta_tab[i];
		if (meta->base) {
			fprintf(f, "base: 0x%08lx length: %8.1lu",
				(unsigned long) meta->base,
				(unsigned long) meta->size);

#ifdef FRAGCHECK_TRACK_CALLERS
			{
				unsigned j;
				
				fputs(" callers:", f);
				for (j = FC_RET_0; j < NUM_FC_RET; j++) {
					fprintf(f, " 0x%08lx", (unsigned long) meta->ret[j]);
				}
			}
#endif	/* FRAGCHECK_TRACK_CALLERS */

			fputs("\n", f);
		}
	}
}

void
fragcheck_init(void)
{
	static GMemVTable vtable;
	/* 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);

	vtable.malloc = my_malloc;
	vtable.realloc = my_realloc;
	vtable.free = my_free;

	g_mem_set_vtable(&vtable);

	{
		extern const int end;

		/*
		 * The address of "end" points to end of the BSS and should
		 * be equivalent to the lowest heap address
		 */

		vars.alloc_base = round_size(sizeof (union alloc),
						(size_t) &end - sizeof (union alloc));
	}
}

#endif	/* FRAGCHECK */

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