#include <stdlib.h>
#include <stdio.h>
#include <setjmp.h>
#include <time.h>    /* for clock() */


/* static size of cleanup stack */
#define  MAX_SLOTS   3000

#define  INLINE_CLEANUP

#ifndef NO_EXCEPTIONS
  /********************************************************************/
  /********************************************************************/
  /*****                                                          *****/
  /*****            THE EXCEPTION SUB-SYSTEM IMPLEMENTATION       *****/
  /*****                                                          *****/
  /*****  this is a drastically simple implementation but shows   *****/
  /*****  that the implementation of cleanup-stack based SEH is   *****/
  /*****  not trivial.                                            *****/
  /*****                                                          *****/
  /*****  Any real-world implementation should be thread-safe     *****/
  /*****  and use dynamic allocation to manage the cleanup        *****/
  /*****  stack..                                                 *****/
  /*****                                                          *****/
  /********************************************************************/
  /********************************************************************/

  /* cleanup stack types */

  typedef  void*   cleanup_item_t;
  typedef  void  (*cleanup_func_t)( cleanup_item_t, const void* );
  
  typedef  int     cleanup_mark_t;
  
  typedef struct
  {
    cleanup_item_t  item;
    cleanup_func_t  cleanup_func;
    const void*     cleanup_data;
    void*           pad;
    
  } cleanup_slot_t;


  typedef struct
  {
    cleanup_slot_t   items[ MAX_SLOTS ];
    cleanup_slot_t*  top;
    cleanup_slot_t*  limit;
  
  } cleanup_stack_t;
  
  
  
  /* exception types */
  
  typedef  int    exception_t;
  
#define  ERR_OK              0
#define  ERR_ALLOC           1
#define  ERR_DIVIDE_BY_ZERO  2
  
  typedef struct exception_handler_t  exception_handler_t;

  struct exception_handler_t
  {
    exception_handler_t*  previous;
    jmp_buf               jump_buffer;
    cleanup_mark_t        mark;
    exception_t           exception;
  };


/* the TRY macro */
#define  TRY                                                       \
       {                                                           \
         exception_handler_t   __x_handler;                        \
                                                                   \
         exception_handler_set(&__x_handler);                      \
         if ( setjmp( __x_handler.jump_buffer ) == 0 )             \
         {                                                         \

/* the CATCH macro */
#define  CATCH(_e_)                                                    \
         }                                                             \
         else                                                          \
         {                                                             \
           exception_t  _e_ = exception_handler_unset( &__x_handler );


/* the END_CATCH macro */
#define  END_CATCH                                    \
         }                                            \
         if ( __x_handler.exception == ERR_OK )       \
           exception_handler_unset( &__x_handler );   \
       }

/* the THROW and RETHROW macros */
#define  THROW(x)    exception_throw(x)
#define  RETHROW(x)  exception_throw(x)


  /* forward declaration */
  extern void  exception_throw( exception_t  e );

  /***************************************************************/
  /*                                                             */
  /* Implementing the cleanup stack                              */
  /*                                                             */
  
  /* the cleanup stack itself */
  static  cleanup_stack_t  cleanup_stack;


  /* initialise cleanup stack */
  void  cleanup_init( void )
  {
    cleanup_stack.top   = cleanup_stack.items;
    cleanup_stack.limit = cleanup_stack.items + MAX_SLOTS;
  }


  /* push new element on top of the stack */
  void  cleanup_push( const void*     item,
                      cleanup_func_t  cleanup_func,
                      const void*     cleanup_data )
  {
    cleanup_slot_t*  top = cleanup_stack.top;
    
    if ( top < cleanup_stack.limit )
    {
      top->item         = (cleanup_item_t)item;
      top->cleanup_func = cleanup_func;
      top->cleanup_data = cleanup_data;
      
      cleanup_stack.top = top+1;
    }
    else
    {
      /* no more items can be pushed (equivalent to memory exhaustion) */
      /* first, cleanup the item itself..                              */
      if (item)
        cleanup_func( (cleanup_item_t)item, cleanup_data );
        
      /* then, throw exception */
      THROW( ERR_ALLOC );
    }
  }

  
  /* pop top-most element */
  void  cleanup_pop( const void*  item,
                     int          keep_it )
  {
    cleanup_slot_t*  top = --cleanup_stack.top;
    
    if ( top < cleanup_stack.items || top->item != item )
    {
      /* the stack is empty, something weird happened */
      fprintf( stderr, "invalid cleanup stack state detected !!"
                       "\nAborting immediately\n" );
      exit(1);
    }
    
    if ( !keep_it )
      top->cleanup_func( top->item, top->cleanup_data );
  }


  /* return current stack height */
  cleanup_mark_t  cleanup_mark( void )
  {
    return (cleanup_mark_t)(cleanup_stack.top - cleanup_stack.items);
  }
  
  
  /* unwind stack */
  void  cleanup_unwind( cleanup_mark_t  mark )
  {
    cleanup_slot_t*  new_top = cleanup_stack.items + mark;
    cleanup_slot_t*  top     = cleanup_stack.top - 1;
    
    if ( new_top > top || mark < 0 )
    {
      /* an invalid mark value was passed */
      fprintf( stderr, "invalid cleanup stack state detected. Your program"
                       "is severely buggy !!\nAborting immediately\n" );
      exit(1);
    }
    
    cleanup_stack.top = new_top;

    for ( ; top > new_top; top-- )    
    { 
      if ( top->item )
        top->cleanup_func( top->item, top->cleanup_data );
    }
  }

  /***************************************************************/
  /*                                                             */
  /* Implementing exception handling                             */
  /*                                                             */

  /* the current exception handler - this is normally thread-specific */
  static exception_handler_t*  current_handler = NULL;  
  
  void exception_handler_set( exception_handler_t*  handler )
  {
    handler->exception = 0;
    handler->previous  = current_handler;
    current_handler    = handler;
    handler->mark      = cleanup_mark();
  }

  /* unlink exception handler, return exception if any */
  exception_t  exception_handler_unset( exception_handler_t*  handler )
  {
    exception_t  result;
    
    if ( handler != current_handler )
    {
      /* something strange happened */
      fprintf( stderr, "invalid exception sub-system internal state. Aborting\n" );
      exit(2);
    }
    
    current_handler    = handler->previous;
    handler->previous  = NULL;

    return handler->exception;
  }      


  /* throw an exception */
  void  exception_throw( exception_t  exc )
  {
    exception_handler_t*  handler = current_handler;
    
    if (!handler)
    {
      /* no handler was set when this exception occured */
      fprintf( stderr, "uncaught fatal exception %d. Aborting\n", exc );
    }

    /* store exception code, cleanup the stack, then transfer control */    
    handler->exception = exc;
    cleanup_unwind( handler->mark );
    longjmp( handler->jump_buffer, 1 );
  }



  /***************************************************************/
  /*                                                             */
  /* Support routines                                            */
  /*                                                             */
  
  /* allocate a new block of memory and push it on top of the */
  /* cleanup stack..                                          */
  /*                                                          */
  void*  malloc_push( size_t  size )
  {
    void*  p = NULL;
    
    if ( size > 0 )
    {
      p = malloc(size);
      if ( !p )
      {
        THROW(ERR_ALLOC);
        return NULL;
      }
      
      /* now push on top of the cleanup stack */
#ifndef INLINE_CLEANUP
       cleanup_push( p, (cleanup_func_t)free, NULL );
#else
       { 
         /* inlined version, for speed */
         cleanup_slot_t*  top = cleanup_stack.top;
              
         if ( top < cleanup_stack.limit )
         {
           top->item         = (cleanup_item_t)p;
           top->cleanup_func = (cleanup_func_t)free;
                
           cleanup_stack.top = top+1;
         }
         else
         {
           /* no more items can be pushed (equivalent to memory exhaustion) */
           /* first, cleanup the item itself..                              */
           if (p)
             free(p);
                  
           /* then, throw exception */
           THROW( ERR_ALLOC );
         }
       }
#endif    
    }  
    return p;
  }
      
  
  /* pop and free */
  void  pop_free( void*  block )
  {
#ifndef INLINE_CLEANUP  
    cleanup_pop( block, 1 );
    free( block );
#else
    cleanup_slot_t*  top = --cleanup_stack.top;
    
    if ( top < cleanup_stack.items || top->item != block ||
         top->cleanup_func != (cleanup_func_t)free )
    {
      /* the stack is empty, something weird happened */
      fprintf( stderr, "invalid cleanup stack state detected !!"
                       "\nAborting immediately\n" );
      exit(1);
      return;
    }
    
    free( block );
#endif
  }

  
#else  /* NO_EXCEPTIONS */

#  define  malloc_push(x)  malloc(x)
#  define  pop_free(x)     free(x)

#endif /* NO_EXCEPTIONS */

  /********************************************************************/
  /********************************************************************/
  /*****                                                          *****/
  /*****                      THE TESTING CODE                    *****/
  /*****                                                          *****/
  /********************************************************************/
  /********************************************************************/

  /***************************************************************/
  /*                                                             */
  /* Timing code..                                               */
  /*                                                             */

/* SunOS 4.1.* does not define CLOCKS_PER_SEC, so include <sys/param.h> */
/* to get the HZ macro which is the equivalent.                         */
#if defined(__sun__) && !defined(SVR4) && !defined(__SVR4)
#include <sys/param.h>
#define CLOCKS_PER_SEC HZ
#endif
 
 /* return time in milliseconds */
  double  get_time( void )
  {
    return clock() * 1000.0 / CLOCKS_PER_SEC;
  }



  /***************************************************************/
  /*                                                             */
  /* The tests..                                                 */
  /*                                                             */
  /*   This is really simple, we first allocate 1 temporary      */
  /*   memory block and free it, we then allocate 2 blocks,      */
  /*   free them, allocate 3 blocks, etc..                       */
  /*                                                             */

#define  MAX_TEMPS   5000  /* must be > MAX_SLOTS */
#define  BLOCK_SIZE  32

  static  void*   temps[ MAX_TEMPS ];
  static  int     count = 0;
  

  /* the test, with no checks at all */
  static void  do_test_1( void )
  {
    int   n, i;

    count = 0;
        
    for ( n = 1; n < MAX_TEMPS; n++ )
    {
      /* allocate 'i' temporary memory blocks */
      for ( i = 0; i < n; i++ )
      {
        /* when no exceptions are compiled in, we must abort when */
        /* MAX_SLOTS is attained to perform the exact same number */
        /* of allocations and frees. These result in accurate     */
        /* timings for comparisons..                              */
        if ( i == MAX_SLOTS )
        {
          /* we need to release the current blocks to mimic exactly */
          /* the behaviour of test 2                                */
          for ( --i; i >= 0; i-- )
            free( temps[i] );
            
          return;
        }

        temps[i] = malloc( BLOCK_SIZE );
        count++;
      }
      
      /* now, release them (in order) */
      for ( i = n-1; i >= 0; i-- )
        free( temps[i] );
    }
  }


#ifndef NO_EXCEPTIONS
  /* perform test with exceptions */
  static void  do_test_2( void )
  {
    int   n, i;

    count = 0;
        
    for ( n = 1; n < MAX_TEMPS; n++ )
    {
      /* allocate 'i' temporary memory blocks */
      for ( i = 0; i < n; i++ )
      {
        temps[i] = malloc_push( BLOCK_SIZE );
        count++;
      }
      
      /* now, release them (in order) */
      for ( i = n-1; i >= 0; i-- )
        pop_free( temps[i] );
    }
  }
#endif /* !NO_EXCEPTIONS */



 /* the same test than do_test_1, except that everything is checked */
  static void  do_test_3( void )
  {
    int   n, i;

    count = 0;
        
    for ( n = 1; n < MAX_TEMPS; n++ )
    {
      for ( i = 0; i < n; i++ )
      {
        if ( i == MAX_SLOTS )
        {
        Error:
          for ( --i; i >= 0; i-- )
            free( temps[i] );
            
          return;
        }

        temps[i] = malloc( BLOCK_SIZE );
        if ( temps[i] == NULL )
          goto Error;
          
        count++;
      }
      
      /* now, release them (in order) */
      for ( i = n-1; i >= 0; i-- )
        free( temps[i] );
    }
  }


  
  int  main( void )
  { 
    double  t0, t1;
    
    /* initialise sub-system */
    cleanup_init();
    
    /* first, do test without exceptions */
    t0 = get_time();
    do_test_1();
    t0 = get_time() - t0;
    
    fprintf( stderr, "%10d alloc/free without checks      = %6.3f s\n", count, t0/1000.0 );

    t0 = get_time();
    do_test_3();
    t0 = get_time() - t0;
    
    fprintf( stderr, "%10d alloc/free with checks         = %6.3f s\n", count, t0/1000.0 );


#ifndef NO_EXCEPTIONS
    
    /* then, the test with exceptions */
    t1 = get_time();
    
    TRY
    { 
      do_test_2();
    }
    CATCH(e)
    {
      switch (e)
      {
        case ERR_ALLOC:
          /* printf( "memory exhaustion for count = %d\n", count ); */
          break;
          
        default:
          printf( "exception %d occured ??\n", e );
      }
    }
    END_CATCH

    t1 = get_time() - t1;
    
    fprintf( stderr, "%10d alloc/free with exceptions     = %6.3f s\n", count, t1/1000.0 );
    fprintf( stderr, "                      difference          = %6.3f s (%.0f%%)\n", 
                     (t1-t0)/1000.0, t0 > 0 ? ((t1-t0)/t0)*100.0 : 0.0 );

#endif /* !NO_EXCEPTIONS */
    
    /* return now */
    return 0;
  }
