/* Copyright 2005 Chris Thomasson */


#ifndef AC_I686_LFGC_REFCOUNT_H
#define AC_I686_LFGC_REFCOUNT_H


#ifdef __cplusplus
extern "C"
{
#endif




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


/* MUST be four adjacent words */
typedef struct 
AC_DECLSPEC_PACKED
ac_i686_lfgc_refcount_
{
  ac_intword_t refs;
  ac_i686_node_t *lfgc_next;
  ac_fp_dtor_t fp_dtor;
  const void *state;

} ac_i686_lfgc_refcount_t;


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




/* we need to be able to cast an ac_i686_lfgc_refcount_t 
   to/from an ac_i686_node_t. */
AC_BUILD_DBG_ASSERT
( lfgc_refcount,
  sizeof( ac_i686_lfgc_refcount_t ) == sizeof( ac_i686_node_t ) );
  



#define ac_i686_lfgc_refcount_get( ac_macro_this ) \
  ( (void*)(ac_macro_this)->state )


#define ac_i686_mb_lfgc_refcount_get( ac_macro_this ) \
  ( ac_mb_loadptr_depends( &(ac_macro_this)->state ) )


AC_DECLSPEC_INLINE int AC_APIDECL
ac_i686_lfgc_refcount_alloc
( ac_thread_t *_tls,
  ac_i686_lfgc_refcount_t **_pthis,
  ac_fp_dtor_t fp_dtor,
  const void *state )
{
  ac_i686_lfgc_refcount_t *_this;

  if ( ! _tls ) { _tls = ac_thread_self(); }

  _this = (ac_i686_lfgc_refcount_t*)
            ac_thread_cpu_node_cache_pop( _tls, state );
  if ( ! _this ) { return ac_sys_error( ENOMEM ); }
  
  _this->refs = 1;
  
  if ( _this->fp_dtor == (ac_fp_dtor_t)0x00000001 )
  { _this->refs |= 0x80000000;
  }

  _this->fp_dtor = fp_dtor;
  
  *_pthis = _this;
  return 0;
}
  

AC_DECLSPEC_INLINE void AC_APIDECL
ac_i686_lfgc_refcount_release
( ac_i686_lfgc_refcount_t *_this )
{
  if (   _this && 
       ! ( ac_atomic_dec_release
            ( &_this->refs ) & 0x7FFFFFFF ) )
  { ac_thread_t *_tls = ac_thread_self();
  
    _this->fp_dtor( (void*)_this->state );
    
    _this->fp_dtor = 
      ( _this->refs & 0x80000000 ) ? (ac_fp_dtor_t)0x00000001 : 0;

    ac_lfgc_smr_collect( _tls, 0, (ac_i686_node_t*)_this );
  }
}


AC_DECLSPEC_INLINE ac_i686_lfgc_refcount_t* AC_APIDECL
ac_i686_lfgc_refcount_addref
( ac_thread_t *_tls,
  ac_i686_lfgc_refcount_t **pdest )
{
  ac_intword_t refs, refs_cmp;
  ac_lfgc_smr_hazard_t hazard;
  ac_i686_lfgc_refcount_t *dest;

  /* grab tls and hazard pointer */
  if ( ! _tls ) { _tls = ac_thread_self(); };
  hazard = ac_lfgc_smr_get( _tls, 0 );
  
  for( ;; )
  { /* expensive smr load */
    dest = (ac_i686_lfgc_refcount_t*)
            ac_i686_lfgc_smr_activate
              ( hazard, 
                (ac_i686_node_t**)pdest );
    if ( ! dest ) { break; }
    
    /* addref if greater than zero */
    refs = dest->refs;
    while( ( refs & 0x7FFFFFFF ) > 0 )
    { refs_cmp = refs;
      refs = ac_atomic_cas_acquire
              ( &dest->refs,
                refs_cmp,
                refs_cmp + 1 );
      if ( refs == refs_cmp )
      { /* we now own a reference to dest */
        ac_i686_lfgc_smr_deactivate( hazard );
        return dest;
      }
    }
  }

  return 0;
}


AC_DECLSPEC_INLINE void AC_APIDECL
ac_i686_lfgc_refcount_null
( ac_i686_lfgc_refcount_t **pdest )
{
  ac_i686_lfgc_refcount_release
    ( (ac_i686_lfgc_refcount_t*)
        ac_atomic_xchgptr_release
          ( pdest,
            0 ) );
}


AC_DECLSPEC_INLINE void AC_APIDECL
ac_i686_lfgc_refcount_copy_local
( ac_i686_lfgc_refcount_t **pdest,
  ac_i686_lfgc_refcount_t *src )
{
  ac_i686_lfgc_refcount_t *old = 
    (ac_i686_lfgc_refcount_t*)ac_mb_loadptr_depends( pdest );
  if ( src ) { ac_atomic_inc_acquire( &src->refs ); }
  ac_mb_storeptr_release( pdest, src );
  ac_i686_lfgc_refcount_release( old );
}


AC_DECLSPEC_INLINE void AC_APIDECL
ac_i686_lfgc_refcount_copy_shared
( ac_thread_t *_tls,
  ac_i686_lfgc_refcount_t **pdest,
  ac_i686_lfgc_refcount_t **psrc )
{
  ac_i686_lfgc_refcount_release
    ( (ac_i686_lfgc_refcount_t*)
        ac_atomic_xchgptr_release
          ( pdest,
            ac_i686_lfgc_refcount_addref
              ( _tls,
                psrc ) ) );
}


AC_DECLSPEC_INLINE ac_i686_lfgc_refcount_t* AC_APIDECL
ac_i686_lfgc_refcount_xchg
( ac_i686_lfgc_refcount_t **pdest,
  ac_i686_lfgc_refcount_t *src )
{
  if ( src ) { ac_atomic_inc_acquire( &src->refs ); }
  return (ac_i686_lfgc_refcount_t*)
            ac_atomic_xchgptr_release
              ( pdest,
                src );
}


AC_DECLSPEC_INLINE int AC_APIDECL
ac_i686_lfgc_refcount_cas
( ac_i686_lfgc_refcount_t **pdest,
  ac_i686_lfgc_refcount_t *cmp,
  ac_i686_lfgc_refcount_t *xchg )
{
  if ( xchg ) { ac_atomic_inc_acquire( &xchg->refs ); }
  
  if ( ac_atomic_casptr_release
        ( pdest,
          cmp,
          xchg ) != cmp )
  { ac_i686_lfgc_refcount_release( xchg );
    return 1;
  }
  
  ac_i686_lfgc_refcount_release( cmp );
  return 0;
}




typedef ac_i686_lfgc_refcount_t ac_lfgc_refcount_t;
#define ac_lfgc_refcount_alloc ac_i686_lfgc_refcount_alloc
#define ac_lfgc_refcount_get ac_i686_lfgc_refcount_get
#define ac_mb_lfgc_refcount_get ac_i686_mb_lfgc_refcount_get
#define ac_lfgc_refcount_null ac_i686_lfgc_refcount_null
#define ac_lfgc_refcount_cas ac_i686_lfgc_refcount_cas
#define ac_lfgc_refcount_xchg ac_i686_lfgc_refcount_xchg
#define ac_lfgc_refcount_release ac_i686_lfgc_refcount_release
#define ac_lfgc_refcount_copy_shared ac_i686_lfgc_refcount_copy_shared
#define ac_lfgc_refcount_copy_local ac_i686_lfgc_refcount_copy_local




#ifdef __cplusplus
}
#endif


#endif