/*
 * (c) Copyright 1992 by Panagiotis Tsirigotis
 * All rights reserved.  The file named COPYRIGHT specifies the terms 
 * and conditions for redistribution.
 */

static char RCSid[] = "$Id: fsma.c,v 5.2 1993/09/16 22:13:12 panos Exp $" ;
static char *version = VERSION ;

#include "fsma.h"
#include "impl.h"

#ifdef DEBUG
#include <stdio.h>
#endif

/*
 * We assume that memory handed by malloc is suitably aligned for any
 * machine data type (for example, if the CPU wants double's aligned
 * at an 8-byte boundary, the memory provided by malloc will be so aligned).
 */
char *malloc() ;
int free() ;


PRIVATE void init_free_list __ARGS( ( unsigned, unsigned, char * ) ) ;
PRIVATE void terminate __ARGS( ( char * ) ) ;


/*
 * An allocator manages a linked list of chunks:
 *
 *		______________			______________			______________
 *		|   HEADER	 |------>|				 |------>|				 |
 *		|____________|			|____________|			|____________|
 *		|		 		 |			|		 		 |			|		 		 |
 *		|		 		 |			|		 		 |			|		 		 |
 *		|		 		 |			|		 		 |			|		 		 |
 *		|		 		 |			|		 		 |			|		 		 |
 *		|	 SLOTS	 |			|		 		 |			|		 		 |
 *		|		 		 |			|		 		 |			|		 		 |
 *		|		 		 |			|		 		 |			|		 		 |
 *		|		 		 |			|		 		 |			|		 		 |
 *		|		 		 |			|		 		 |			|		 		 |
 *		|____________|			|____________|			|____________|
 *
 *
 * The SLOTS sections are organized as a linked list of slots whose
 * size is determined by the object size specified in fsm_create
 */

fsma_h fsm_create( object_size, slots_per_chunk, flags )
	unsigned		object_size ;
	unsigned		slots_per_chunk ;
	int			flags ;
{
	register fsma_h				fp ;
	union __fsma_chunk_header	*chp ;
	char								*slots ;
	unsigned							nslots ;
	unsigned							chunk_size ;
	unsigned							slot_size ;
	int								header_inlined ;

	nslots = ( slots_per_chunk == 0 ) ? SLOTS_PER_CHUNK : slots_per_chunk ;
	slot_size = ( object_size < MINSIZE ) ? MINSIZE : object_size ;

	/*
	 * Make sure that the slot_size is a multiple of the pointer size
	 */
	if ( slot_size % sizeof( __fsma_pointer ) != 0 )
		slot_size += sizeof( __fsma_pointer ) -
								slot_size % sizeof( __fsma_pointer ) ;

	chunk_size = sizeof( union __fsma_chunk_header ) + nslots * slot_size ;

	chp = CHUNK_HEADER( malloc( chunk_size ) ) ;
	if ( chp == NULL )
		if ( flags & FSM_RETURN_ERROR )
			return( NULL ) ;
		else
			terminate( "FSMA fsm_create: malloc failed\n" ) ;

	slots = (char *) &chp[ 1 ] ;
	init_free_list( nslots, slot_size, slots ) ;

#ifdef DEBUG
	{
		int i ;

		fprintf( stderr, "Size = %d, nslots = %d\n", slot_size, nslots ) ;
		for ( i = 0 ; i < nslots+1 ; i++ )
			fprintf( stderr, "slot[ %d ] = %p\n",
							i, (*(char **) (slots + i * slot_size ))) ;
	}
#endif

	/*
	 * Check if we can fit the header in an object slot
	 */
	if ( slot_size >= sizeof( struct __fsma_header ) )
	{
		/*
		 * We can do it.
		 * Allocate the first slot
		 */
		fp = (fsma_h) slots ;
		slots = *(POINTER *) slots ;
		header_inlined = TRUE ;
	}
	else
	{
		fp = (fsma_h) malloc( sizeof( struct __fsma_header ) ) ;
		if ( fp == NULL )
			if ( flags & FSM_RETURN_ERROR )
			{
				free( (char *) chp ) ;
				return( NULL ) ;
			}
			else
				terminate( "FSMA fsm_create: malloc of header failed\n" ) ;
		header_inlined = FALSE ;
	}

	chp->next_chunk = NULL ;

	fp->next_free = (POINTER) slots ;
	fp->chunk_chain = chp ;
	fp->slots_in_chunk = nslots ;
	fp->slot_size = slot_size ;
	fp->chunk_size = chunk_size ;
	fp->flags = flags ;
	fp->is_inlined = header_inlined ;

#ifdef DEBUG
	fprintf( stderr, "Slots/chunk = %d\n", nslots ) ;
	fprintf( stderr, "Allocating chunk %p\n", chunk ) ;
#endif
	
	return( (fsma_h) fp ) ;
}



void fsm_destroy( fp )
	register fsma_h fp ;
{
	int header_inlined = fp->is_inlined ;
	register union __fsma_chunk_header *chp, *next_chunk ;
	register int zero_memory = fp->flags & FSM_ZERO_DESTROY ;
	register chunk_size = fp->chunk_size ;

	/*
	 * Free all chunks in the chunk chain
	 */
	for ( chp = fp->chunk_chain ; chp != NULL ; chp = next_chunk )
	{
		next_chunk = chp->next_chunk ;
		if ( zero_memory )
			(void) memset( (char *)chp, 0, chunk_size ) ;

#ifdef DEBUG
		fprintf( stderr, "Freeing chunk %p\n", chp ) ;
#endif
		free( (char *)chp ) ;
	}

	/*
	 * If fp->inlined is NO, we have to free the handle.
	 * Note that we copied fp->inlined in case it is YES.
	 */
	if ( ! header_inlined )
		free( (char *)fp ) ;
}


char *_fsm_alloc( fp )
	register fsma_h fp ;
{
	register POINTER object ;

	/*
	 * Check if there are any slots on the free list
	 */
	if ( fp->next_free == NULL )
	{
		/*
		 * Free list exhausted; allocate a new chunk
		 */
		char *slots ;
		union __fsma_chunk_header *chp ;

		chp = CHUNK_HEADER( malloc( fp->chunk_size ) ) ;
		if ( chp == NULL )
			if ( fp->flags & FSM_RETURN_ERROR )
				return( NULL ) ;
			else
				terminate( "FSMA fsm_alloc: malloc failed\n" ) ;

#ifdef DEBUG
		fprintf( stderr, "Allocating chunk %p\n", chunk ) ;
#endif
		/*
		 * Put the slots in this chunk in a linked list
		 * and add this list to the free list
		 */
		slots = (char *) &chp[ 1 ] ;
		init_free_list( fp->slots_in_chunk, fp->slot_size, slots ) ;
		fp->next_free = (POINTER) slots ;

		/*
		 * Put this chunk at the head of the chunk chain
		 */
		chp->next_chunk = fp->chunk_chain ;
		fp->chunk_chain = chp ;
	}

	object = fp->next_free ;
	fp->next_free = *(POINTER *)object ;

	if ( fp->flags & FSM_ZERO_ALLOC )
		(void) memset( object, 0, fp->slot_size ) ;

	return( object ) ;
}


void _fsm_free( fp, object )
	fsma_h fp ;
	char *object ;
{
	if ( fp->flags & FSM_ZERO_FREE )
		(void) memset( object, 0, fp->slot_size ) ;

	*(POINTER *)object = fp->next_free ;
	fp->next_free = object ;
}


PRIVATE void terminate( s )
	char *s ;
{
	write( 2, s, strlen( s ) ) ;
	abort() ;
	exit( 1 ) ;
}


PRIVATE void init_free_list( nslots, size, slots )
	unsigned nslots ;
	register unsigned size ;
	char *slots ;
{
	register int i ;
	register char *next ;
	register POINTER current ;

	for ( i = 0, current = slots, next = slots + size ; i < nslots - 1 ;
														i++, current = next, next += size )
		*(POINTER *)current = next ;
	*(POINTER *)current = NULL ;
}

