BDE 4.14.0 Production release
Loading...
Searching...
No Matches
bsls_atomicoperations_default

Detailed Description

Outline

Purpose

Provide default implementation for atomic operations.

Classes

Description

[PRIVATE] This component provides classes having default implementations of atomic operations independent of a any specific platform. The purpose of these classes is for them to be combined with platform-specific implementations of atomic operations to form a full set of atomic operations.

[WARNING] This component should not be used directly by client code. It is subject to change without warning.

The implementation of atomic operations splits the set of operations into two groups: essential (core) operations and non-essential operations. The essential core operations, such as getInt, setInt, swapInt, testAndSwapInt, must be implemented on every target platform. The non-essential operations, such as incrementInt, decrementInt, have default implementations written in terms of the core operations (though the default implementation may be overridden for a target platform).

This component provides the default implementations for non-essential atomic operations. For the essential core operations, it provides only function prototypes without any concrete implementation. The concrete implementation for core operations on each platform are provided by a platform-specific implementation component.

Design of Components Providing Atomic Operations

There are about 70 different atomic operations we provide in this atomics library. Only a fraction of these operations (about 10-15) have implementations specific to a target platform. These are the core atomic operations. Other operations have implementations that are independent of the platform, and are defined in terms of those core operations.

A primary design objective for this implementation of atomic operations is to provide a single common implementation for the platform-independent, non-essential, atomic operations. This way, each platform-specific implementation of atomics needs to provide only the 10-15 core operations, and may obtain the remaining 60 atomic operations automatically from this default implementation component.

The implementation technique used to achieve this goal is called the Curiously Recurring Template Pattern, or CRTP.

In the CRTP a derived class inherits from the base class, but the base class is parameterized with the derived class template parameter. In the context of atomic operations, the CRTP allows the non-essential base class methods to be accessed through the platform specific derived class, while the template parameterization of the base class allows the implementations for platform-independent (non-essential) operations in the base class to be implemented in terms of the core operations implemented in the platform-specific derived class.

Let's illustrate this idea with an example. The base class, AtomicsBase, will provide an implementation for a single non-essential atomic operation, getIntAcquire, whose implementation delegates to the platform-specific implementation of another, (core) atomic operation, getInt:

template <class IMP>
struct AtomicsBase<IMP>
{
// static
// int getInt(int volatile *);
// Implemented in the derived class, 'IMP'.
static
int getIntAcquire(int volatile *obj)
// Platform-independent; implemented in terms of the derived
// 'getInt'.
{
return IMP::getInt(obj);
}
};

Now we define a platform-specific implementation of the atomics class for the x86 platform providing the core operation getInt:

struct AtomicsX86 : AtomicsBase<AtomicsX86>
{
static
int getInt(int volatile *obj)
// Platform-specific implementation.
{
// ...whatever is necessary for this atomic operation on x86
}
};

To get an idea what the effective interface and the implementation of the AtomicsX86 class looks like, we can flatten the hierarchy of AtomicsX86 and remove the AtomicsBase class from the picture, as if we implemented AtomicsX86 without the help of AtomicsBase. The effective interface and the implementation of AtomicsX86 is as follows:

struct AtomicsX86Flattened
{
static
int getInt(int volatile *obj)
// Platform-specific implementation.
{
// ...whatever is necessary for this atomic operation on x86
}
static
int getIntAcquire(int volatile *obj)
// Platform-independent; implemented in terms of 'getInt'.
{
return getInt(obj);
}
};

The above code shows that the design goal is achieved: Platform-independent atomic operations are factored out into easily reusable AtomicsBase which, when combined with platform-specific atomic operations, produces the complete set of atomic operations for a given target-platform.

Component and Class Hierarchy for Atomic Operations

This section describes the hierarchy of components and types that implement atomic operations. Below is the list of base classes providing default implementations of platform-independent, non-essential atomic operations. Atomic operations for different data types are factored into their own base classes:

The platform-specific core atomic operations are left unimplemented in these default implementation classes. The implementations for those operations have to be provided by a platform-specific derived classes.

The above default implementation classes are combined (using inheritance) into more specialized base classes that provide nearly complete set of atomic operations for a generic platform:

This is how the generic platform base classes are composed:

A typical derived class implementing platform-specific atomic operations needs to derive from either bsls::AtomicOperations_Default32 (if the platform is 32-bit) or bsls::AtomicOperations_Default64 (if the platform is 64-bit).

For example, let's take the X86_64 platform with GCC compiler. The derived class for this platform, bsls::AtomicOperations_X64_ALL_GCC, inherits from bsls::AtomicOperations_Default64 and implements all platform-specific atomic operations:

struct bsls::AtomicOperations_X64_ALL_GCC
: bsls::AtomicOperations_Default64<bsls::AtomicOperations_X64_ALL_GCC>
{
AtomicTypes; // for explanation of atomic type traits see below
// *** atomic functions for int ***
static int getInt(const AtomicTypes::Int *atomicInt);
static void setInt(AtomicTypes::Int *atomicInt, int value);
// ...
// *** atomic functions for Int64 ***
static bsls::Types::Int64 getInt64(
AtomicTypes::Int64 const *atomicInt);
static void setInt64(
AtomicTypes::Int64 *atomicInt, bsls::Types::Int64 value);
// ...
};
Definition bsls_atomicoperations_default.h:1388
Atomic_TypeTraits< IMP > AtomicTypes
Definition bsls_atomicoperations_default.h:347
Definition bsls_atomicoperations_default.h:333
long long Int64
Definition bsls_types.h:132

A derived class can also override some default atomic implementations from a base class. For example, bsls::AtomicOperations_X64_ALL_GCC can provide a better implementation for getIntAcquire than the one provided by the bsls::AtomicOperations_DefaultInt base class. So bsls::AtomicOperations_X64_ALL_GCC implements its own getIntAcquire with the same signature as in the base class:

struct bsls::AtomicOperations_X64_ALL_GCC
: bsls::AtomicOperations_Default64<bsls::AtomicOperations_X64_ALL_GCC>
{
// *** atomic functions for int ***
static int getIntAcquire(const AtomicTypes::Int *atomicInt);
// ...
};
static int getIntAcquire(typename AtomicTypes::Int const *atomicInt)
Definition bsls_atomicoperations_default.h:1403

Technically speaking, this is not overriding, but hiding the respective methods of the base class, but for our purpose, it works as if the methods were overridden.

Platform-specific atomic operations for other platforms are implemented in a similar manner. Here is a conceptual diagram of relationships between classes that provide the default atomic operations and platform-specific atomic operations (the bsls::AtomicOperations_ prefix is omitted for brevity):

DefaultPointer32 DefaultInt DefaultInt64 DefaultPointer64
^ ^ ^ ^
| | | |
+---------------+ +-------------------+ +--------------+
| | | |
Default32 Default64
^ ^
| |
X86_ALL_GCC --+ +-- X64_ALL_GCC
| |
X86_WIN_MSVC --+ +-- X64_WIN_MSVC
| |
POWERPC32_AIX_XLC --+ +-- POWERPC64_AIX_XLC
| |
SPARC32_SUN_CC --+ +-- SPARC64_SUN_CC
|
+-- IA64_HP_ACC

The last part of the implementation is the atomic type traits class. We need a special representation for atomic primitive types to ensure conformance to size and alignment requirements for each platform. For example, the atomic primitive type for int is usually a 4-byte aligned volatile int. Since the alignment is specified differently for different platforms and compilers, the atomic type traits class allows us to abstract the rest of the implementation of atomics from those differences.

This default implementation component provides only a declaration of a generic atomic type traits class:

template <class IMP>

Each platform-specific implementation has to provide its own specialization of the type traits class. For example, the specialization for the x86_64 platform defined by the bsls::AtomicOperations_X64_ALL_GCC class might look like:

template <>
struct bsls::Atomic_TypeTraits<bsls::AtomicOperations_X64_ALL_GCC>
{
struct Int
{
volatile int d_value __attribute__((__aligned__(sizeof(int))));
};
struct Int64
{
volatile bsls::Types::Int64 d_value
__attribute__((__aligned__(sizeof(bsls::Types::Int64))));
};
struct Pointer
{
void * volatile d_value
__attribute__((__aligned__(sizeof(void *))));
};
};
Definition bdlt_iso8601util.h:691

Usage

This component is a private implementation type of bsls_atomicoperations ; see bsls_atomicoperations for a usage example.