Porting Guide (liblfds)

From liblfds.org
Revision as of 15:17, 26 December 2015 by Admin (talk | contribs) (→‎lfds700_porting_abstraction_layer_atomic_compiler.h)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Introduction

To minimize the work involved is making liblfds work on any given platform a porting abstraction layer has been written. Porting simply involves implementing the porting abstraction layer; the library will then compile and work. Implementation involves providing values to a small set defines, macros and typedefs. The porting abstraction layer is implemented as a small set of header files in the public header file directory, thus;

└───liblfds700
    └───inc
        └───liblfds700
                lfds700_porting_abstraction_layer_atomic_compiler.h
                lfds700_porting_abstraction_layer_atomic_operating_system.h
                lfds700_porting_abstraction_layer_atomic_processor.h

Each header file uses #ifdefs and compiler defined macros to select the appropriate porting abstraction layer implementation for the local platform.

So, for example, in lfds700_porting_abstraction_layer_operating_system.h, for MSVC, x86, Windows user-mode, we see the following, which is for MSVC on Windows kernel-mode.

#if( defined _MSC_VER && _MSC_VER >= 1400 && defined _WIN32 && defined _KERNEL_MODE )
  [snip porting abstraction layer implementation]
#endif

So, to add a new platform, introduce a new #ifdef, which matches the appropriate compiler defined macros for your platform.

The Header Files

A porting abstraction layer is a small set of defines, macros and typedefs. Each of the compiler, the operating system and the processor (for the processor, in the form of information about the processor provided by the user in #defines) turns out to be a source of information for a porting abstraction layer.

In general then the porting abstraction layer is split up into these three files, so that everything in the porting layer which comes from any one source is in one place - i.e. everything which comes from the GCC compiler is in one section, for the GCC compiler, in the compiler header file.

In almost all cases, across all the platforms supported so far - i.e. all the compilers, all the operating systems, all the processors - the same sets of information come from the same sources, i.e. the compiler provides the atomic instrincs, the processor provides cache-line lengths, etc. However, this not quite a hard and fast rule. There has so far been one exception - on Windows, with SSE2 or greater, processor barriers are a compiler instrinc (and so are in the compiler file), but prior to this, they were a function call provided by the operating system (and so are in the operating system file).

Basically, put things where they come from - and the current divison seems to work well for this end.

lfds700_porting_abstraction_layer_atomic_compiler.h

A compiler port should consist of everything which is a compiler intrinsic and this always or almost always consists of the following;

#define LFDS700_PAL_COMPILER_STRING
#define LFDS700_PAL_ALIGN( alignment )
#define LFDS700_PAL_INLINE
#define LFDS700_PAL_BARRIER_COMPILER_LOAD
#define LFDS700_PAL_BARRIER_COMPILER_STORE
#define LFDS700_PAL_BARRIER_COMPILER_FULL
#define LFDS700_PAL_BARRIER_PROCESSOR_LOAD
#define LFDS700_PAL_BARRIER_PROCESSOR_STORE
#define LFDS700_PAL_BARRIER_PROCESSOR_FULL
#define LFDS700_PAL_ATOMIC_CAS( pointer_to_destination, pointer_to_compare, new_destination, cas_strength, result )
#define LFDS700_PAL_ATOMIC_DWCAS( pointer_to_destination, pointer_to_compare, pointer_to_new_destination, cas_strength, result )
#define LFDS700_PAL_ATOMIC_EXCHANGE( pointer_to_destination, pointer_to_exchange )

lfds700_porting_abstraction_layer_atomic_operating_system.h

An operating system port contains and #includes and implements the following;

#define LFDS700_PAL_OS_STRING
#define LFDS700_PAL_ASSERT( expression )

lfds700_porting_abstraction_layer_atomic_processor.h

A processor port contains user-provided information about the processor;

typedef [type] lfds700_pal_atom_t;
typedef [type] lfds700_pal_uint_t;
#define LFDS700_PAL_PROCESSOR_STRING
#define LFDS700_PAL_ALIGN_SINGLE_POINTER
#define LFDS700_PAL_ALIGN_DOUBLE_POINTER
#define LFDS700_PAL_CACHE_LINE_LENGTH_IN_BYTES
#define LFDS700_PAL_ATOMIC_ISOLATION_IN_BYTES

Partial Implementatons

It is not necessary to fully implement a porting abstraction layer; not everything in the porting abstration layer is mandatory, and each of the atomic operations has a dummy version provided, for the purpose of allowing the library to compile even if that atomic operation has not been implemented or is not available on the platform (as not all atomic operations are available on all processors). Naturally, if a data structure depends on a given atomic operation and that atomic operation has not in fact been implemented, then you cannot use that data structure.

Atomic Operation Support by Processor
  Memory
Barriers
CAS DWCAS Exchange
ARM32/64, x86, x64
Alpha, Itanium, MIPS32/64, PowerPC32/64, SPARC32/64

The data structures have the following requirements;

Data Structure by Atomic Operations
  Memory
Barriers
CAS DWCAS Exchange
Binary Tree (add-only, unbalanced)
Freelist
Hash (add-only)
List (add-only, ordered, singly-linked)
List (add-only, singly-linked, unordered)
Queue
Queue (BSCSP)1
Ringbuffer
Stack

1. Bounded (fixed maximum number of elements, specified at instantiation time), single consumer, single producer

Which means you end up with the following;

Data Structures by Processors
  ARM32/64, x86, x64 Alpha, Itanium, MIPS32/64, PowerPC32/64, SPARC32/64
Binary Tree (add-only, unbalanced)
Freelist
Hash (add-only)
List (add-only, ordered, singly-linked)
List (add-only, singly-linked, unordered)
Queue
Queue (BSCSP)1
Ringbuffer
Stack

1. Bounded (fixed maximum number of elements, specified at instantiation time), single consumer, single producer

So, for example, if you port to some new x86 based platform and implement only memory barriers, CAS and exchange, then you can use the hash, the list and the bounded, single consumer, single producer queue. The library will compile fully, but if you try to use the other data structures, you will call a dummy version of one of the other atomic operations, where those dummy functions call assert (if it's been provided) and then try to write 0 to memory location 0. You were warned :-)

See Also