/* slab.c - Slab allocator. */
/* Copyright (C) 2019 Robin Miyagi https://www.linuxprogramming.ca/
 *
 * This program 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 3 of the
 * License, or (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/* Project includes. */
#include <fast-malloc.h>

/* System includes. */
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <error.h>
#include <assert.h>

/*
 * Types required for forward declarations that will be exported as
 * opaque data types.
 */
typedef struct
{
  int     x;	/* exists flag */
  void*   p; 	/* memory location */
  size_t  s;	/* size of each element */
  size_t  n;	/* number of elements */
  void**  f;	/* free list */
  size_t  fc;	/* free list count */
} t_slab;

/*
 * Forward declarations...............................................
 */
/*
 * Allocate a new slab object.
 */
t_slab* slab_new (size_t s);

/*
 * Destroy the given slab object.
 */
void slab_delete (t_slab* s);

/*
 * Allocate one element in the given slab.
 */
void* slab_alloc (t_slab* s);

/*
 * Free the given memory in the given slab.
 */
void slab_free (t_slab* s, void* ptr);

/*
 * Implementation.....................................................
 */
#define MAX_SLABS (1 << 10)
static _Thread_local t_slab slbarray[MAX_SLABS];

t_slab*
slab_new
(size_t s)
{
  t_slab* ret;
  size_t i;

  for (i = 0; (i < MAX_SLABS) && (slbarray[i].x != 0); ++i)
    ;
  assert (i < MAX_SLABS);
  ret = slbarray + i;
  ret->x = 1;
  ret->s = s;
  return ret;
}
void
slab_delete
(t_slab* s)
{
  if (s->p != NULL)
    free_blk (s->p);
  if (s->f != NULL)
    free_blk (s->f);
  s->x = 0;
}
void*
slab_alloc
(t_slab* s)
{
  void* ret;
  size_t i;
  
  if (s->f != NULL)
    {
      for (i = 0; i < s->fc; ++i)
	if (s->f[i] != NULL)
	  break;
      if (i < s->fc)
	{
	  ret = s->f[i];
	  s->f[i] = NULL;
	}
      else
	goto nfl;
    }
  else
    goto nfl;
 quit:
  return ret;
 nfl:
  ++(s->n);
  if (s->p == NULL)
    s->p = malloc_blk (s->s * s->n);
  else
    s->p = realloc_blk (s->p, s->s * s->n);
  ret = s->p + s->s * (s->n - 1);
  goto quit;
}
void
slab_free
(t_slab* s, void* ptr)
{
  size_t i;

  if (s->f != NULL)
    {
      for (i = 0; i < s->fc; ++i)
	if (s->f[i] == NULL)
	  break;
      if (i < s->fc)
	s->f[i] = ptr;
      else
	{
	  ++(s->fc);
	  s->f = realloc_blk (s->f, s->fc * sizeof (void*));
	  s->f[s->fc - 1] = ptr;
	}
    }
  else
    {
      ++(s->fc);
      s->f = malloc_blk (s->fc * sizeof (void*));
      s->f[s->fc - 1] = ptr;
    }
}
