/*
 * Copyright (C) 2005 Red Hat, Inc
 * Author Matthias Clasen
 *
 * xmemchunk.c: implementation of lockfree allocation of fixed
 *              size memory chunks.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
#include <glib.h>

#include <string.h>             /* memset */

#include "xmemchunk.h"


typedef struct _XMemArea XMemArea;

struct _XMemArea
{
  XMemArea *next;
  
  gpointer area;
};

struct _XMemChunk
{
  GPrivate *private;

  gchar *name;
  gulong area_size;
  gulong chunk_size;
  XMemArea *mem_areas;
  gboolean list;
};

typedef struct _XFreeListElement XFreeListElement;

struct _XFreeListElement 
{
  XFreeListElement *next;
  gpointer *list_next;
};

static inline void
x_free_list_push (XMemChunk *mem_chunk, 
		  gpointer   mem)
{
  XFreeListElement *elem = (XFreeListElement *) mem;

  elem->next = g_private_get (mem_chunk->private);
  g_private_set (mem_chunk->private, elem);
}

static inline gpointer
x_free_list_pop (XMemChunk *mem_chunk, 
		 gboolean   list)
{
  XFreeListElement *head, *elem;

  head = g_private_get (mem_chunk->private);

  if (!head)
    return NULL;

  if (list && head->list_next)
    {
      elem = (XFreeListElement *)head->list_next;
      elem->next = head->next;
    }
  else 
    elem = head->next;

  g_private_set (mem_chunk->private, elem);

  return head;
}
		 
static void
populate (XMemChunk * mem_chunk)
{
  XMemArea *new_area;
  gint i;

  new_area = g_new0 (XMemArea, 1);
  new_area->area = g_malloc0 (mem_chunk->area_size);
  new_area->next = mem_chunk->mem_areas;

  mem_chunk->mem_areas = new_area;

  for (i = 0; i < mem_chunk->area_size; i += mem_chunk->chunk_size) {
    x_free_list_push (mem_chunk, new_area->area + i);
  }
}

/**
 * x_mem_chunk_new:
 * @name: the name of the chunk
 * @atom_size: the size of the allocated atoms
 * @area_size: the initial size of the memory area
 * @type: the allocation strategy to use
 *
 * Creates a new memchunk that will allocate atom_sized memchunks.
 * The initial area is set to area_size and will grow automatically 
 * when it is too small (with a small overhead when that happens)
 *
 * Returns: a new #XMemChunk
 *
 * MT safe.
 */
XMemChunk *
x_mem_chunk_new (gchar *name, gint atom_size, gulong area_size, gint type)
{
  XMemChunk *mem_chunk;

  g_return_val_if_fail (atom_size > 0, NULL);
  g_return_val_if_fail (area_size >= atom_size, NULL);

  mem_chunk = g_malloc (sizeof (XMemChunk));

  mem_chunk->name = g_strdup (name);
  mem_chunk->chunk_size = atom_size;
  mem_chunk->area_size = (area_size / atom_size) * atom_size;
  mem_chunk->mem_areas = NULL;
  mem_chunk->list = FALSE;

  mem_chunk->private = g_private_new (NULL);

  populate (mem_chunk);

  return mem_chunk;
}

XMemChunk *
x_mem_chunk_new_list (gchar * name, gint atom_size, gulong area_size, gint type)
{
  XMemChunk *mem_chunk;

  mem_chunk = x_mem_chunk_new (name, atom_size, area_size, type);

  mem_chunk->list = TRUE;
}

/**
 * x_mem_chunk_destroy:
 * @mem_chunk: the XMemChunk to destroy
 *
 * Free the memory allocated by the memchunk. This function
 * is not Threadsafe as it does not wait for all outstanding
 * allocations to be freed.
 */
void
x_mem_chunk_destroy (XMemChunk * mem_chunk)
{
  XMemArea *mem_area, *next;
  gpointer data;

  mem_area = mem_chunk->mem_areas; 
  while (mem_area)
    {
      next = mem_area->next;

      g_free (mem_area->area);
      g_free (mem_area);

      mem_area = next;
    }

  g_free (mem_chunk->name);
  g_free (mem_chunk);
}

/**
 * x_mem_chunk_alloc:
 * @mem_chunk: the mem chunk to use
 *
 * Allocate a new memory region from the chunk. The size
 * of the allocated memory was specified when the memchunk
 * was created.
 *
 * Returns: a pointer to the allocated memory region.
 *
 * MT safe.
 */
gpointer
x_mem_chunk_alloc (XMemChunk * mem_chunk)
{
  gpointer chunk;

  g_return_val_if_fail (mem_chunk != NULL, NULL);

again:
  chunk = x_free_list_pop (mem_chunk, mem_chunk->list);

  /* chunk is empty, try to refill */
  if (G_UNLIKELY (!chunk)) 
    {
      populate (mem_chunk);
      goto again;
    }
  
  return chunk;
}

/**
 * x_mem_chunk_alloc0:
 * @mem_chunk: the mem chunk to use
 *
 * Allocate a new memory region from the chunk. The size
 * of the allocated memory was specified when the memchunk
 * was created. The memory will be set to all zeroes.
 *
 * Returns: a pointer to the allocated memory region.
 *
 * MT safe.
 */
gpointer
x_mem_chunk_alloc0 (XMemChunk * mem_chunk)
{
  gpointer mem;

  mem = x_mem_chunk_alloc (mem_chunk);
  memset (mem, 0, mem_chunk->chunk_size);

  return mem;
}

/**
 * x_mem_chunk_free:
 * @mem_chunk: the mem chunk to use
 * @mem: the memory region to hand back to the chunk
 *
 * Free the memeory region allocated from the chunk.
 *
 * MT safe.
 */
void
x_mem_chunk_free (XMemChunk * mem_chunk, gpointer mem)
{
  g_return_if_fail (mem_chunk != NULL);
  g_return_if_fail (mem != NULL);

  x_free_list_push (mem_chunk, mem);
}

