view src/lib/data-stack.c @ 4891:6ab2712f1a93 HEAD

Only imap binary was actually working.
author Timo Sirainen <tss@iki.fi>
date Sun, 10 Dec 2006 14:35:02 +0200
parents 65b56dd915cb
children 9e86dbe68663
line wrap: on
line source

/* Copyright (c) 2002-2003 Timo Sirainen */

/* @UNSAFE: whole file */

#include "lib.h"
#include "data-stack.h"

#include <stdlib.h>

#ifdef HAVE_GC_GC_H
#  include <gc/gc.h>
#elif defined (HAVE_GC_H)
#  include <gc.h>
#endif

/* Use malloc() and free() for all memory allocations. Useful for debugging
   memory corruption. */
/* #define DISABLE_DATA_STACK */

#ifndef DISABLE_DATA_STACK

/* Initial stack size - this should be kept in a size that doesn't exceed
   in a normal use to avoid extra malloc()ing. */
#ifdef DEBUG
#  define INITIAL_STACK_SIZE (1024*10)
#else
#  define INITIAL_STACK_SIZE (1024*32)
#endif

#ifdef DEBUG
#  define CLEAR_CHR 0xde
#elif defined(USE_GC)
#  define CLEAR_CHR 0
#endif

struct stack_block {
	struct stack_block *next;

	size_t size, left;
	/* unsigned char data[]; */
};

#define SIZEOF_MEMBLOCK MEM_ALIGN(sizeof(struct stack_block))

#define STACK_BLOCK_DATA(block) \
	((char *) (block) + SIZEOF_MEMBLOCK)

/* current_frame_block contains last t_push()ed frames. After that new
   stack_frame_block is created and it's ->prev is set to
   current_frame_block. */
#define BLOCK_FRAME_COUNT 32

struct stack_frame_block {
	struct stack_frame_block *prev;

	struct stack_block *block[BLOCK_FRAME_COUNT];
        size_t block_space_used[BLOCK_FRAME_COUNT];
	size_t last_alloc_size[BLOCK_FRAME_COUNT];
};

unsigned int data_stack_frame = 0;

static int frame_pos = BLOCK_FRAME_COUNT-1; /* in current_frame_block */
static struct stack_frame_block *current_frame_block;
static struct stack_frame_block *unused_frame_blocks;

static struct stack_block *current_block; /* block now used for allocation */
static struct stack_block *unused_block; /* largest unused block is kept here */

static struct stack_block *last_buffer_block;
static size_t last_buffer_size;

unsigned int t_push(void)
{
        struct stack_frame_block *frame_block;

	frame_pos++;
	if (frame_pos == BLOCK_FRAME_COUNT) {
		/* frame block full */
		if (data_stack_frame == 0) {
			/* kludgy, but allow this before initialization */
			frame_pos = 0;
			data_stack_init();
			return t_push();
		}

		frame_pos = 0;
		if (unused_frame_blocks == NULL) {
			/* allocate new block */
#ifndef USE_GC
			frame_block = calloc(sizeof(*frame_block), 1);
#else
			frame_block = GC_malloc(sizeof(*frame_block));
#endif
			if (frame_block == NULL) {
				i_fatal_status(FATAL_OUTOFMEM,
					       "t_push(): Out of memory");
			}
		} else {
			/* use existing unused frame_block */
			frame_block = unused_frame_blocks;
			unused_frame_blocks = unused_frame_blocks->prev;
		}

		frame_block->prev = current_frame_block;
		current_frame_block = frame_block;
	}

	/* mark our current position */
	current_frame_block->block[frame_pos] = current_block;
	current_frame_block->block_space_used[frame_pos] = current_block->left;
        current_frame_block->last_alloc_size[frame_pos] = 0;

        return data_stack_frame++;
}

#ifndef USE_GC
static void free_blocks(struct stack_block *block)
{
	struct stack_block *next;

	/* free all the blocks, except if any of them is bigger than
	   unused_block, replace it */
	while (block != NULL) {
		next = block->next;

		if (unused_block == NULL || block->size > unused_block->size) {
			free(unused_block);
			unused_block = block;
		} else {
			free(block);
		}

		block = next;
	}
}
#endif

unsigned int t_pop(void)
{
	struct stack_frame_block *frame_block;
	int popped_frame_pos;

	if (frame_pos < 0)
		i_panic("t_pop() called with empty stack");

	/* update the current block */
	current_block = current_frame_block->block[frame_pos];
	current_block->left = current_frame_block->block_space_used[frame_pos];
#ifdef CLEAR_CHR
	memset(STACK_BLOCK_DATA(current_block) +
	       (current_block->size - current_block->left), CLEAR_CHR,
	       current_block->left);
#endif
	if (current_block->next != NULL) {
		/* free unused blocks */
#ifndef USE_GC
		free_blocks(current_block->next);
#endif
		current_block->next = NULL;
	}

	popped_frame_pos = frame_pos;
	if (frame_pos > 0)
		frame_pos--;
	else {
		/* frame block is now unused, add it to unused list */
		frame_pos = BLOCK_FRAME_COUNT-1;

		frame_block = current_frame_block;
		current_frame_block = frame_block->prev;

		frame_block->prev = unused_frame_blocks;
		unused_frame_blocks = frame_block;
	}

        return --data_stack_frame;
}

static struct stack_block *mem_block_alloc(size_t min_size)
{
	struct stack_block *block;
	size_t prev_size, alloc_size;

	prev_size = current_block == NULL ? 0 : current_block->size;
	alloc_size = nearest_power(prev_size + min_size);

#ifndef USE_GC
	block = malloc(SIZEOF_MEMBLOCK + alloc_size);
#else
	block = GC_malloc(SIZEOF_MEMBLOCK + alloc_size);
#endif
	if (block == NULL) {
		i_fatal_status(FATAL_OUTOFMEM, "mem_block_alloc(): "
			       "Out of memory when allocating %"PRIuSIZE_T
			       " bytes", alloc_size + SIZEOF_MEMBLOCK);
	}
	block->size = alloc_size;
	block->next = NULL;

	return block;
}

static void *t_malloc_real(size_t size, bool permanent)
{
	struct stack_block *block;
        void *ret;
#ifdef DEBUG
	bool warn = FALSE;
#endif

	if (size == 0 || size > SSIZE_T_MAX)
		i_panic("Trying to allocate %"PRIuSIZE_T" bytes", size);

	if (data_stack_frame == 0) {
		/* kludgy, but allow this before initialization */
		data_stack_init();
	}

	/* reset t_buffer_get() mark - not really needed but makes it easier
	   to notice if t_malloc() is called between t_buffer_get() and
	   t_buffer_alloc() */
        last_buffer_block = NULL;

	/* allocate only aligned amount of memory so alignment comes
	   always properly */
	size = MEM_ALIGN(size);

	/* used for t_try_realloc() */
	current_frame_block->last_alloc_size[frame_pos] = size;

	if (current_block->left >= size) {
		/* enough space in current block, use it */
		ret = STACK_BLOCK_DATA(current_block) +
			(current_block->size - current_block->left);
                if (permanent)
			current_block->left -= size;
		return ret;
	}

	/* current block is full, see if we can use the unused_block */
	if (unused_block != NULL && unused_block->size >= size) {
		block = unused_block;
		unused_block = NULL;
	} else {
		block = mem_block_alloc(size);
#ifdef DEBUG
		warn = TRUE;
#endif
	}

	block->left = block->size;
	if (permanent)
		block->left -= size;
	block->next = NULL;

	current_block->next = block;
	current_block = block;

	ret = STACK_BLOCK_DATA(current_block);
#ifdef DEBUG
	if (warn) {
		/* warn later, so that if i_warning() wants to allocate more
		   memory we don't go to infinite loop */
		i_warning("Growing data stack with: %"PRIuSIZE_T, block->size);
	}
#endif

        return ret;
}

void *t_malloc(size_t size)
{
        return t_malloc_real(size, TRUE);
}

void *t_malloc0(size_t size)
{
	void *mem;

	mem = t_malloc_real(size, TRUE);
	memset(mem, 0, size);
        return mem;
}

bool t_try_realloc(void *mem, size_t size)
{
	size_t last_alloc_size;

	if (size == 0 || size > SSIZE_T_MAX)
		i_panic("Trying to allocate %"PRIuSIZE_T" bytes", size);

	last_alloc_size = current_frame_block->last_alloc_size[frame_pos];

	/* see if we're trying to grow the memory we allocated last */
	if (STACK_BLOCK_DATA(current_block) +
	    (current_block->size - current_block->left -
	     last_alloc_size) == mem) {
		/* yeah, see if we have space to grow */
		size = MEM_ALIGN(size);
		if (current_block->left >= size - last_alloc_size) {
			/* just shrink the available size */
			current_block->left -= size - last_alloc_size;
			current_frame_block->last_alloc_size[frame_pos] = size;
			return TRUE;
		}
	}

	return FALSE;
}

size_t t_get_bytes_available(void)
{
	return current_block->left;
}

void *t_buffer_get(size_t size)
{
	void *ret;

	ret = t_malloc_real(size, FALSE);

	last_buffer_size = size;
	last_buffer_block = current_block;
	return ret;
}

void *t_buffer_reget(void *buffer, size_t size)
{
	size_t old_size;
        void *new_buffer;

	old_size = last_buffer_size;
	if (size <= old_size)
                return buffer;

	new_buffer = t_buffer_get(size);
	if (new_buffer != buffer)
                memcpy(new_buffer, buffer, old_size);

        return new_buffer;
}

void t_buffer_alloc(size_t size)
{
	i_assert(last_buffer_block != NULL);
	i_assert(last_buffer_size >= size);
	i_assert(current_block->left >= size);

	/* we've already reserved the space, now we just mark it used */
	t_malloc_real(size, TRUE);
}

void data_stack_init(void)
{
	if (data_stack_frame == 0) {
		data_stack_frame = 1;

		current_block = mem_block_alloc(INITIAL_STACK_SIZE);
		current_block->left = current_block->size;
		current_block->next = NULL;

		current_frame_block = NULL;
		unused_frame_blocks = NULL;
		frame_pos = BLOCK_FRAME_COUNT-1;

		last_buffer_block = NULL;
		last_buffer_size = 0;
	}

	t_push();
}

void data_stack_deinit(void)
{
	t_pop();

	if (frame_pos != BLOCK_FRAME_COUNT-1)
		i_panic("Missing t_pop() call");

#ifndef USE_GC
	while (unused_frame_blocks != NULL) {
                struct stack_frame_block *frame_block = unused_frame_blocks;
		unused_frame_blocks = unused_frame_blocks->prev;

		free(frame_block);
	}

	free(current_block);
	free(unused_block);
#endif
	unused_frame_blocks = NULL;
	current_block = NULL;
	unused_block = NULL;
}

#else

#ifdef USE_GC
#  error No GC with disabled data stack
#endif

struct stack_frame {
	struct stack_frame *next;
	struct frame_alloc *allocs;
};

struct frame_alloc {
	struct frame_alloc *next;
	void *mem;
};

unsigned int data_stack_frame;

static struct stack_frame *current_frame;
static void *buffer_mem;

unsigned int t_push(void)
{
	struct stack_frame *frame;

	frame = malloc(sizeof(struct stack_frame));
	if (frame == NULL)
		i_panic("t_push(): Out of memory");
	frame->allocs = NULL;

	frame->next = current_frame;
	current_frame = frame;
	return data_stack_frame++;
}

unsigned int t_pop(void)
{
	struct stack_frame *frame;
	struct frame_alloc *alloc;

	frame = current_frame;
	current_frame = frame->next;

	while (frame->allocs != NULL) {
		alloc = frame->allocs;
		frame->allocs = alloc->next;

		free(alloc->mem);
		free(alloc);
	}

	free(frame);
	return --data_stack_frame;
}

static void add_alloc(void *mem)
{
	struct frame_alloc *alloc;

	alloc = malloc(sizeof(struct frame_alloc));
	if (alloc == NULL)
		i_panic("add_alloc(): Out of memory");
	alloc->mem = mem;
	alloc->next = current_frame->allocs;
	current_frame->allocs = alloc;

	if (buffer_mem != NULL) {
		free(buffer_mem);
		buffer_mem = NULL;
	}
}

void *t_malloc(size_t size)
{
	void *mem;

	mem = malloc(size);
	if (mem == NULL)
		i_panic("t_malloc(): Out of memory");
	add_alloc(mem);
	return mem;
}

void *t_malloc0(size_t size)
{
	void *mem;

	mem = calloc(size, 1);
	if (mem == NULL)
		i_panic("t_malloc0(): Out of memory");
	add_alloc(mem);
	return mem;
}

bool t_try_realloc(void *mem __attr_unused__, size_t size __attr_unused__)
{
	return FALSE;
}

void *t_buffer_get(size_t size)
{
	buffer_mem = realloc(buffer_mem, size);
	if (buffer_mem == NULL)
		i_panic("t_buffer_get(): Out of memory");
	return buffer_mem;
}

void *t_buffer_reget(void *buffer, size_t size)
{
	i_assert(buffer == buffer_mem);

	buffer_mem = realloc(buffer_mem, size);
	if (buffer_mem == NULL)
		i_panic("t_buffer_reget(): Out of memory");
	return buffer_mem;
}

void t_buffer_alloc(size_t size)
{
	void *mem;

	i_assert(buffer_mem != NULL);

	mem = realloc(buffer_mem, size);
	if (mem == NULL)
		i_panic("t_buffer_alloc(): Out of memory");
	buffer_mem = NULL;

	add_alloc(mem);
}

void data_stack_init(void)
{
	data_stack_frame = 0;
	current_frame = NULL;
	buffer_mem = NULL;

	t_push();
}

void data_stack_deinit(void)
{
	t_pop();

	if (data_stack_frame != 0)
		i_panic("Missing t_pop() call");
}

#endif