/*
 * %CopyrightBegin%
 * 
 * Copyright Ericsson AB 1996-2009. All Rights Reserved.
 * 
 * The contents of this file are subject to the Erlang Public License,
 * Version 1.1, (the "License"); you may not use this file except in
 * compliance with the License. You should have received a copy of the
 * Erlang Public License along with this software. If not, it can be
 * retrieved online at http://www.erlang.org/.
 * 
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * %CopyrightEnd%
 */
/* General purpose Memory allocator for fixed block size objects         */
/* This allocater is at least an order of magnitude faster than malloc() */


#define NOPERBLOCK 20
#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include "sys.h"
#include "erl_vm.h"
#include "global.h"
#include "erl_db.h"

#ifdef ERTS_ALC_N_MIN_A_FIXED_SIZE

#if ERTS_ALC_MTA_FIXED_SIZE
#include "erl_threads.h"
#include "erl_smp.h"
#  ifdef ERTS_SMP
#    define FA_LOCK(FA) erts_smp_spin_lock(&(FA)->slck)
#    define FA_UNLOCK(FA) erts_smp_spin_unlock(&(FA)->slck)
#  else
#    define FA_LOCK(FA) erts_mtx_lock(&(FA)->mtx)
#    define FA_UNLOCK(FA) erts_mtx_unlock(&(FA)->mtx)
#  endif
#else
#  define FA_LOCK(FA)
#  define FA_UNLOCK(FA)
#endif

typedef union {double d; long l;} align_t;

typedef struct fix_alloc_block {
    struct fix_alloc_block *next;
    align_t mem[1];
} FixAllocBlock;

typedef struct fix_alloc {
    Uint item_size;
    void *freelist;
    Uint no_free;
    Uint no_blocks;
    FixAllocBlock *blocks;
#if ERTS_ALC_MTA_FIXED_SIZE
#  ifdef ERTS_SMP
    erts_smp_spinlock_t slck;
#  else
    erts_mtx_t mtx;
#  endif
#endif
} FixAlloc;

static void *(*core_alloc)(Uint); 
static Uint xblk_sz;

static FixAlloc **fa;
#define FA_SZ (1 + ERTS_ALC_N_MAX_A_FIXED_SIZE - ERTS_ALC_N_MIN_A_FIXED_SIZE)

#define FIX_IX(N) ((N) - ERTS_ALC_N_MIN_A_FIXED_SIZE)

#define FIX_POOL_SZ(I_SZ) \
  ((I_SZ)*NOPERBLOCK + sizeof(FixAllocBlock) - sizeof(align_t))

#if defined(DEBUG) && !ERTS_ALC_MTA_FIXED_SIZE
static int first_time;
#endif

void erts_init_fix_alloc(Uint extra_block_size,
			 void *(*alloc)(Uint))
{
    int i;

    xblk_sz = extra_block_size;
    core_alloc = alloc;

    fa = (FixAlloc **) (*core_alloc)(FA_SZ * sizeof(FixAlloc *));
    if (!fa)
	erts_alloc_enomem(ERTS_ALC_T_UNDEF, FA_SZ * sizeof(FixAlloc *));

    for (i = 0; i < FA_SZ; i++)
	fa[i] = NULL;
#if defined(DEBUG) && !ERTS_ALC_MTA_FIXED_SIZE
    first_time = 1;
#endif
}

Uint
erts_get_fix_size(ErtsAlcType_t type)
{
    Uint i = FIX_IX(ERTS_ALC_T2N(type));
    return i < FA_SZ && fa[i] ? fa[i]->item_size : 0;
}

void
erts_set_fix_size(ErtsAlcType_t type, Uint size)
{
    Uint sz;
    Uint i;
    FixAlloc *fs;
    ErtsAlcType_t t_no = ERTS_ALC_T2N(type);
    sz = xblk_sz + size;

#ifdef DEBUG
    ASSERT(ERTS_ALC_N_MIN_A_FIXED_SIZE <= t_no);
    ASSERT(t_no <= ERTS_ALC_N_MAX_A_FIXED_SIZE);
#endif

    while (sz % sizeof(align_t) != 0)     /* Alignment */
	sz++;

    i = FIX_IX(t_no);
    fs = (FixAlloc *) (*core_alloc)(sizeof(FixAlloc));
    if (!fs)
	erts_alloc_n_enomem(t_no, sizeof(FixAlloc));
    
    fs->item_size = sz;
    fs->no_blocks = 0;
    fs->no_free = 0;
    fs->blocks = NULL;
    fs->freelist = NULL;
    if (fa[i])
	erl_exit(-1, "Attempt to overwrite existing fix size (%d)", i);
    fa[i] = fs;

#if ERTS_ALC_MTA_FIXED_SIZE
#ifdef ERTS_SMP
    erts_smp_spinlock_init_x(&fs->slck, "fix_alloc", make_small(i));
#else
    erts_mtx_init_x(&fs->mtx, "fix_alloc", make_small(i));
#endif
#endif

}

void
erts_fix_info(ErtsAlcType_t type, ErtsFixInfo *efip)
{
    Uint i;
    FixAlloc *f;
#ifdef DEBUG
    FixAllocBlock *b;
    void *fp;
#endif
    Uint real_item_size;
    ErtsAlcType_t t_no = ERTS_ALC_T2N(type);

    ASSERT(ERTS_ALC_N_MIN_A_FIXED_SIZE <= t_no);
    ASSERT(t_no <= ERTS_ALC_N_MAX_A_FIXED_SIZE);

    i = FIX_IX(t_no);
    f = fa[i];

    efip->total	= sizeof(FixAlloc *);
    efip->used	= 0;
    if (!f)
	return;

    real_item_size = f->item_size - xblk_sz;

    FA_LOCK(f);

    efip->total += sizeof(FixAlloc);
    efip->total += f->no_blocks*FIX_POOL_SZ(real_item_size);
    efip->used = efip->total - f->no_free*real_item_size;

#ifdef DEBUG
    ASSERT(efip->total >= efip->used);
    for(i = 0, b = f->blocks; b; i++, b = b->next);
    ASSERT(f->no_blocks == i);
    for (i = 0, fp = f->freelist; fp; i++, fp = *((void **) fp));
    ASSERT(f->no_free == i);
#endif

    FA_UNLOCK(f);

}

void
erts_fix_free(ErtsAlcType_t t_no, void *extra, void* ptr)
{
    Uint i;
    FixAlloc *f;

    ASSERT(ERTS_ALC_N_MIN_A_FIXED_SIZE <= t_no);
    ASSERT(t_no <= ERTS_ALC_N_MAX_A_FIXED_SIZE);

    i = FIX_IX(t_no);
    f = fa[i];

    FA_LOCK(f);
    *((void **) ptr) = f->freelist;
    f->freelist = ptr;
    f->no_free++;
    FA_UNLOCK(f);
}


void *erts_fix_realloc(ErtsAlcType_t t_no, void *extra, void* ptr, Uint size)
{
    erts_alc_fatal_error(ERTS_ALC_E_NOTSUP, ERTS_ALC_O_REALLOC, t_no);
    return NULL;
}

void *erts_fix_alloc(ErtsAlcType_t t_no, void *extra, Uint size)
{
    void *ret;
    int i;
    FixAlloc *f;

#if defined(DEBUG) && !ERTS_ALC_MTA_FIXED_SIZE
    ASSERT(ERTS_ALC_N_MIN_A_FIXED_SIZE <= t_no);
    ASSERT(t_no <= ERTS_ALC_N_MAX_A_FIXED_SIZE);
    if (first_time) { /* Check that all sizes have been initialized */
	int i;
	for (i = 0; i < FA_SZ; i++)
	    ASSERT(fa[i]);
	first_time = 0;
    }
#endif


    i = FIX_IX(t_no);
    f = fa[i];

    ASSERT(f);
    ASSERT(f->item_size >= size);

    FA_LOCK(f);
    if (f->freelist == NULL) {  /* Gotta alloc some more mem */
	char *ptr;
	FixAllocBlock *bl;
	Uint n;


	FA_UNLOCK(f);
	bl = (*core_alloc)(FIX_POOL_SZ(f->item_size));
	if (!bl)
	    return NULL;

	FA_LOCK(f);
	bl->next = f->blocks;  /* link in first */
	f->blocks = bl;

	n = NOPERBLOCK;
	ptr = (char *) &f->blocks->mem[0];
	while(n--) {
	    *((void **) ptr) = f->freelist;
	    f->freelist = (void *) ptr;
	    ptr += f->item_size;
	}
#if !ERTS_ALC_MTA_FIXED_SIZE 
	ASSERT(f->no_free == 0);
#endif
	f->no_free += NOPERBLOCK;
	f->no_blocks++;
    }

    ret = f->freelist;
    f->freelist = *((void **) f->freelist);
    ASSERT(f->no_free > 0);
    f->no_free--;

    FA_UNLOCK(f);

    return ret;
}

#endif /* #ifdef ERTS_ALC_N_MIN_A_FIXED_SIZE */