/* Copyright 2005 Chris Thomasson */


#include "../../../include/sys/ac_sys.h"
#include "../../../include/ac_rwspinlock_algo1.h"
#include "../../../include/ac_dlist.h"


#if defined ( AC_BUILD_OS_WINDOWS ) || \
    defined ( AC_BUILD_OS_UNDER_WINDOWS )
#include <malloc.h>
#define AC_OS_ALLOCA _alloca
#else
#include <alloca.h>
#define AC_OS_ALLOCA alloca
#endif




#ifdef _MSC_VER 
/* 4324: structure was padded due to _declspec(align()) */
#pragma warning ( disable : 4324 )
#pragma pack(1) 
#endif



/* MUST be two adjacent words! */
typedef struct 
AC_DECLSPEC_PACKED
ac_prv_smr_dlist_
{ 
  ac_i686_lfgc_smr_t *front;
  ac_i686_lfgc_smr_t *back;
        
} ac_prv_smr_dlist_t;


typedef struct 
AC_DECLSPEC_PACKED_ALIGN_CACHE_LINE 
ac_prv_smr_shared_
{ 
  ac_rwspinlock_algo1_shared_padded_t mutex_impl;
  ac_prv_smr_dlist_t active;
  ac_prv_smr_dlist_t cache;
  ac_i686_intword_t active_count;
        
} ac_prv_smr_shared_t;

#ifdef _MSC_VER 
#pragma pack() 
/* 4324: structure was padded due to _declspec(align()) */
#pragma warning ( default : 4324 )
#endif




typedef struct ac_prv_smr_
{
  ac_prv_smr_shared_t shared;
  ac_rwspinlock_algo1_t mutex_impl;
  void *this_mem;
        
} ac_prv_smr_t;




static void AC_APIDECL
ac_prv_smr_hazard_scan
( ac_i686_lfgc_smr_t*,
  ac_i686_node_t**,
  ac_i686_intword_t );




static ac_prv_smr_t *g_smr;




int AC_APIDECL
ac_i686_lfgc_smr_startup
( void )
{
  int ret;
  void *this_mem;

  g_smr = ac_calloc_aligned
            ( &this_mem,
              1,
              sizeof( *g_smr ),
              AC_CPU_CACHE_LINE );
  if ( ! g_smr ) { return ac_sys_error( ENOMEM ); }

  g_smr->this_mem = this_mem;
  
  ret = ac_rwspinlock_algo1_alloc
          ( &g_smr->mutex_impl,
            &g_smr->shared.mutex_impl._this );
  if ( ret ) { ac_free( this_mem ); return ac_sys_error( ret ); }

  return 0;
}


int AC_APIDECL
ac_i686_lfgc_smr_shutdown
( void )
{
  ac_i686_lfgc_smr_t *next;
  ac_i686_node_t *node_next;

  while ( g_smr->shared.cache.front )
  {
    next = g_smr->shared.cache.front->next;

    while ( g_smr->shared.cache.front->cache_front )
    {
      node_next = g_smr->shared.cache.front->cache_front->lfgc_next;

      if ( ac_thread_cpu_node_cache_push_no_free
            ( 0,
              g_smr->shared.cache.front->cache_front ) )
      {
        ac_free( g_smr->shared.cache.front->cache_front );
      }

      --g_smr->shared.cache.front->cache_count;

      g_smr->shared.cache.front->cache_front = node_next;
    }

    assert( ! g_smr->shared.cache.front->cache_count );

    ac_free( g_smr->shared.cache.front->this_mem );

    g_smr->shared.cache.front = next;
  }
  
  ac_rwspinlock_algo1_free( &g_smr->mutex_impl );

  ac_free( g_smr->this_mem );

  return 0;
}


int AC_APIDECL
ac_i686_lfgc_smr_alloc
( ac_i686_tls_t *_this )
{
  int ret;
  ac_i686_lfgc_smr_t *smr;

  ret = ac_rwspinlock_algo1_write_lock
        ( &g_smr->mutex_impl,
          _this->thread );
  if ( ret ) { ac_sys_error( ret ); abort(); }

  smr = ac_dlist_pop_front_cast
          ( &g_smr->shared.cache );

  assert( ! _this->lfgc_smr );

  if ( ! smr )
  {
    void *this_mem;

    smr = ac_calloc_aligned
            ( &this_mem,
              1,
              sizeof( *smr ),
              AC_CPU_CACHE_LINE );
    if ( ! smr ) 
    { ac_rwspinlock_algo1_write_unlock
        ( &g_smr->mutex_impl,
          _this->thread );
      return ac_sys_error( ENOMEM ); 
    }

    smr->this_mem = this_mem;
  }
  
  _this->lfgc_smr = smr;
  smr->_tls = _this;

  ac_dlist_push_back_cast
    ( &g_smr->shared.active,
      smr );

  ++g_smr->shared.active_count;

  ret = ac_rwspinlock_algo1_write_unlock
        ( &g_smr->mutex_impl,
          _this->thread );
  if ( ret ) { ac_sys_error( ret ); abort(); }

  return 0;
}


int AC_APIDECL
ac_i686_lfgc_smr_free
( ac_i686_tls_t *_this )
{
  if ( _this->lfgc_smr )
  {
    int ret;

    ac_i686_lfgc_smr_t *smr = _this->lfgc_smr;

    ac_i686_lfgc_smr_scan( smr );

    smr->_tls = 0;
    _this->lfgc_smr = 0;

    ret = ac_rwspinlock_algo1_write_lock
          ( &g_smr->mutex_impl,
            _this->thread );
    if ( ret ) { ac_sys_error( ret ); abort(); }

    ac_dlist_pop_cast
      ( &g_smr->shared.active,
        smr );

    ac_dlist_push_back_cast
      ( &g_smr->shared.cache,
        smr );

    --g_smr->shared.active_count;

    ret = ac_rwspinlock_algo1_write_unlock
          ( &g_smr->mutex_impl,
            _this->thread );
    if ( ret ) { ac_sys_error( ret ); abort(); }
  }

  return 0;
}


void AC_APIDECL
ac_prv_smr_hazard_scan
( ac_i686_lfgc_smr_t *_this,
  ac_i686_node_t **hazards,
  ac_i686_intword_t haz_depth )
{
  int i;
  ac_i686_node_t *cur, *next, *prev;

  cur = _this->cache_front;
  prev = cur;

  while ( cur )
  {
    next = cur->lfgc_next;

    for ( i = 0; i < haz_depth; ++i )
    {
      if ( cur == hazards[i] ) { break; }
    }

    if ( i == haz_depth )
    {
      --_this->cache_count;

      if ( cur->fp_dtor &&
           cur->fp_dtor != (ac_fp_dtor_t)0x00000001 )
      {
        cur->fp_dtor( cur );
      }

      else
      {
        ac_thread_cpu_node_cache_push
          ( _this->_tls->thread,
            cur );
      }

      if ( prev == cur )
      {
        _this->cache_front = next;
        cur = next;
        prev = cur;
      }

      else
      {
        prev->lfgc_next = next;
        cur = next;
      }
    }

    else
    {
      prev = cur;
      cur = next; 
    }
  }

  if ( ! haz_depth ) { _this->cache_front = 0; }

  if ( ! _this->cache_front && _this->cache_count )
  {
    ac_sys_error( AC_ECORRUPTED );
    abort();
  }
}


void AC_APIDECL
ac_i686_lfgc_smr_scan
( ac_i686_lfgc_smr_t *_this )
{
  int ret, i = 0, x;
  ac_i686_intword_t smr_depth;
  ac_i686_node_t **hazards = 0, *temp;
  ac_i686_lfgc_smr_t *smr_front;

  ret = ac_rwspinlock_algo1_read_lock
        ( &g_smr->mutex_impl,
          _this->_tls->thread );
  if ( ret ) { ac_sys_error( ret ); abort(); }

  smr_depth = g_smr->shared.active_count * AC_I686_LFGC_SMR_HAZARD_DEPTH;
  smr_front = g_smr->shared.active.front;

  hazards = AC_OS_ALLOCA( sizeof( smr_front ) * smr_depth );

  while ( smr_front )
  {
    if ( smr_front != _this )
    {
      for ( x = 0; x < AC_I686_LFGC_SMR_HAZARD_DEPTH; ++x )
      {
        temp = ac_mb_loadptr_depends( &smr_front->hazards[x] );
        if ( temp ) 
        { 
          hazards[i++] = temp; 
        }
      }
    }

    smr_front = smr_front->next;
  }

  ret = ac_rwspinlock_algo1_read_unlock
          ( &g_smr->mutex_impl,
            _this->_tls->thread );
  if ( ret ) { ac_sys_error( ret ); abort(); }

  ac_prv_smr_hazard_scan
    ( _this,
      hazards,
      i );
}