Zero  0.1.0
Public Member Functions | Static Public Member Functions | Static Public Attributes | Protected Attributes | List of all members
MarkablePointer< T > Class Template Reference

Bit-stealing Atomic Markable Pointer. More...

#include <w_markable_pointer.h>

Public Member Functions

 MarkablePointer ()
 
 MarkablePointer (T *pointer, bool mark, aba_stamp stamp=0)
 
 MarkablePointer (const MarkablePointer< T > &other)
 
MarkablePointeroperator= (const MarkablePointer< T > &other)
 
bool operator== (const MarkablePointer &other) const
 
bool operator!= (const MarkablePointer &other) const
 
void set_mark (bool on)
 
bool is_marked () const
 
aba_stamp get_aba_stamp () const
 
void set_aba_stamp (aba_stamp stamp)
 
void increase_aba_stamp ()
 
Tget_pointer () const
 
Toperator-> () const
 
bool is_null () const
 
uint64_t as_int () const
 
bool atomic_cas (T *expected_pointer, T *new_pointer, bool expected_mark, bool new_mark, aba_stamp expected_stamp, aba_stamp new_stamp)
 [Atomic] Compare and set for both pointer and mark (stashed value). See Figure 9.23 of [HERLIHY] More...
 
bool atomic_cas (const MarkablePointer &expected, const MarkablePointer &desired)
 
MarkablePointer atomic_swap (const MarkablePointer &new_ptr)
 [Atomic] Swap pointer, mark, and stamp altogether. More...
 

Static Public Member Functions

static uintptr_t combine (T *ptr, bool mark, aba_stamp stamp)
 
static uintptr_t cast_to_int (T *ptr)
 
static Tcast_to_ptr (uintptr_t ptr)
 

Static Public Attributes

static const uint64_t MARK_BIT = 0x0000000000000001LL
 
static const uint64_t POINTER_MASK = 0x0000FFFFFFFFFFFELL
 
static const uint64_t STAMP_SHIFT = 48
 
static const uint64_t STAMP_MASK = 0xFFFF000000000000LL
 

Protected Attributes

uintptr_t _pointer
 

Detailed Description

template<class T>
class MarkablePointer< T >

Bit-stealing Atomic Markable Pointer.

Template Parameters
TType of the pointed class/struct. Must not be unaligned types like char (unless you make sure the pointed address is aligned).

Marked For Death

This is a common technique to implement lock-free data structures in C/C++ [HERLIHY]. Because pointers to struct/class in C++ is without doubt an even integer, the last bit of the pointer is always 0. We use the last bit to store additional flag in the 8 byte pointer. We can then use the value for 8-byte atomic operations efficiently.

Note
To address a common misunderstanding of lock-free list/queue: the "mark for death" bit in the pointer is NOT marking the pointed object (e.g., "next") for death. It's often marking the object that holds the pointer for death because it is what we want to atomically guarantee when we link/delink on the next pointer. So, x->next.is_marked() means x is marked for death, not x->next.

ABA Counter

As described in Chap 10.6 of [HERLIHY], there is the ABA problem. When we new/delete classes/structs, they reuse the same memory area. Hence, we need to make sure the same pointer is differentiable. The common solution is to implant some counter in the 64bit pointer like the mark for death. Intel64/AMD64 so far uses only 48 bits (http://en.wikipedia.org/wiki/X86-64), so we steal the most significant 16 bits to store a stamp value. 16 bits is not perfect for avoiding ABA problems, but double-word CAS is quite environment dependant. So, we pick this approach. If 16 bits are not enough, garbage collector should a coordinated object reclamation like what Chap 10.6 of [HERLIHY] recommends.

Atomic Functions and Non-Atomic Functions

Methods whose name start with "atomic_" provide atomic semantics. Other method are NOT atomic. So, appropriately use memory barriers to protect your access. However, other methods are regular, meaning you will not see an utter garbage but only see either the old value or new value (some valid value other thread set). This is possible because this class contains only 8 bytes information. In x86_64, an aligned 8 byte access is always regular though not atomic.

When you want to call more than one non-atomic methods of MarkablePointer, it's a good idea to copy a shared MarkablePointer variable to a local MarkablePointer variable then access the local one. For example,

// This might be bad! (unless only used as inputs for atomic_cas)
bool marked = some_shared_markable_pointer.is_marked();
... many lines in-between
aba_stamp stamp = some_shared_markable_pointer.get_aba_stamp();
// This is at least regular.
MarkablePointer<Hoge> copied(some_shared_markable_pointer);
bool marked = copied.is_marked();
... many lines in-between
aba_stamp stamp = copied.get_aba_stamp();

The copy/assignment operators of this class use the ACCESS_ONCE semantics to prohibit compiler from doing something dangerous for this purpose.

Still, CPU might do something and this is not atomic either. In case it matters, use the atomic_cas().

Dependency

This tiny class is completely header-only. To use, just include w_markable_pointer.h.

See also
http://stackoverflow.com/questions/19389243/stealing-bits-from-a-pointer
http://www.1024cores.net/home/lock-free-algorithms/tricks/pointer-packing

Constructor & Destructor Documentation

§ MarkablePointer() [1/3]

template<class T>
MarkablePointer< T >::MarkablePointer ( )
inline

Empty NULL constructor.

§ MarkablePointer() [2/3]

template<class T>
MarkablePointer< T >::MarkablePointer ( T pointer,
bool  mark,
aba_stamp  stamp = 0 
)
inline

Constructs with initial pointer, mark and ABA stamp.

§ MarkablePointer() [3/3]

template<class T>
MarkablePointer< T >::MarkablePointer ( const MarkablePointer< T > &  other)
inline

Copy constructor. This is regular though might not be atomic.

Member Function Documentation

§ as_int()

template<class T>
uint64_t MarkablePointer< T >::as_int ( ) const
inline

[Non-atomic] Returns integer representation of the pointer and stashed values.

§ atomic_cas() [1/2]

template<class T>
bool MarkablePointer< T >::atomic_cas ( T expected_pointer,
T new_pointer,
bool  expected_mark,
bool  new_mark,
aba_stamp  expected_stamp,
aba_stamp  new_stamp 
)
inline

[Atomic] Compare and set for both pointer and mark (stashed value). See Figure 9.23 of [HERLIHY]

Parameters
[in]expected_pointertest value for pointer
[in]new_pointerif succeeds this value is set to pointer
[in]expected_marktest value for mark
[in]new_markif succeeds this value is set to mark
[in]expected_stamptest value for ABA stamp
[in]new_stampif succeeds this value is set to ABA stamp
Returns
whether the CAS succeeds.

§ atomic_cas() [2/2]

template<class T>
bool MarkablePointer< T >::atomic_cas ( const MarkablePointer< T > &  expected,
const MarkablePointer< T > &  desired 
)
inline

[Atomic] Overload to receive MarkablePointer.

Parameters
[in]expectedtest value
[in]desiredif succeeds this value is set
Returns
whether the CAS succeeds.

§ atomic_swap()

template<class T >
MarkablePointer< T > MarkablePointer< T >::atomic_swap ( const MarkablePointer< T > &  new_ptr)
inline

[Atomic] Swap pointer, mark, and stamp altogether.

Parameters
[in]new_ptrthe value to set
Returns
old value before swap

§ cast_to_int()

template<class T>
static uintptr_t MarkablePointer< T >::cast_to_int ( T ptr)
inlinestatic

ISO/IEC 9899:2011 compliant way of casting a pointer to an int.

§ cast_to_ptr()

template<class T>
static T* MarkablePointer< T >::cast_to_ptr ( uintptr_t  ptr)
inlinestatic

ISO/IEC 9899:2011 compliant way of casting an int to a pointer.

§ combine()

template<class T>
static uintptr_t MarkablePointer< T >::combine ( T ptr,
bool  mark,
aba_stamp  stamp 
)
inlinestatic

Returns an integer value that combined the pointer and the mark to stash.

§ get_aba_stamp()

template<class T>
aba_stamp MarkablePointer< T >::get_aba_stamp ( ) const
inline

[Non-atomic] Returns the ABA counter.

§ get_pointer()

template<class T>
T* MarkablePointer< T >::get_pointer ( ) const
inline

[Non-atomic] Returns the original pointer without stashed value.

§ increase_aba_stamp()

template<class T>
void MarkablePointer< T >::increase_aba_stamp ( )
inline

[Non-atomic] Increase the ABA counter by one.

§ is_marked()

template<class T>
bool MarkablePointer< T >::is_marked ( ) const
inline

[Non-atomic] Returns if the pointer is marked in the stashed boolen flag.

§ is_null()

template<class T>
bool MarkablePointer< T >::is_null ( ) const
inline

[Non-atomic] Tells if the pointer is null.

§ operator!=()

template<class T>
bool MarkablePointer< T >::operator!= ( const MarkablePointer< T > &  other) const
inline

[Non-atomic] Inequality operator on the contained pointer value.

§ operator->()

template<class T>
T* MarkablePointer< T >::operator-> ( ) const
inline

[Non-atomic] Shorthand for get_pointer()->bluh.

§ operator=()

template<class T>
MarkablePointer& MarkablePointer< T >::operator= ( const MarkablePointer< T > &  other)
inline

Copy assignment. This is regular though might not be atomic.

§ operator==()

template<class T>
bool MarkablePointer< T >::operator== ( const MarkablePointer< T > &  other) const
inline

[Non-atomic] Equality operator on the contained pointer value.

§ set_aba_stamp()

template<class T>
void MarkablePointer< T >::set_aba_stamp ( aba_stamp  stamp)
inline

[Non-atomic] Sets the ABA counter.

§ set_mark()

template<class T>
void MarkablePointer< T >::set_mark ( bool  on)
inline

[Non-atomic] Marks the pointer for death, stashing TRUE into the pointer.

See also
atomic_cas()

Member Data Documentation

§ _pointer

template<class T>
uintptr_t MarkablePointer< T >::_pointer
protected

The pointer and stashed flags.

§ MARK_BIT

template<class T>
const uint64_t MarkablePointer< T >::MARK_BIT = 0x0000000000000001LL
static

The lowest bit is stolen for mark for death.

§ POINTER_MASK

template<class T>
const uint64_t MarkablePointer< T >::POINTER_MASK = 0x0000FFFFFFFFFFFELL
static

The original pointer uses only 17-63th bits.

§ STAMP_MASK

template<class T>
const uint64_t MarkablePointer< T >::STAMP_MASK = 0xFFFF000000000000LL
static

We store a stamp value in the high 16 bits.

§ STAMP_SHIFT

template<class T>
const uint64_t MarkablePointer< T >::STAMP_SHIFT = 48
static

Bit-shift count for ABA counter.


The documentation for this class was generated from the following file: