Ada Programming/Pragmas/Atomic

From Wikibooks, open books for an open world
Jump to navigation Jump to search

Ada. Time-tested, safe and secure.
Ada. Time-tested, safe and secure.
pragma Atomic (local_name);

Description[edit | edit source]

Atomic is a representation pragma that can be used with types and variables to specify that the code generated must read and write the type or variable from memory atomically, i.e. as a single/non-interruptible operation. It implies pragma Volatile, the difference is that pragma Atomic is stronger: the compilation must fail if the variable cannot be updated atomically.

This is typically used in the context of interfacing with hardware registers, which must be written atomically and where the compiler must not reorder or suppress any redundant read or write. In some cases it can also be used for tasking, but for communication of Ada tasks it is safer to use protected types or other synchronization primitives.

Example[edit | edit source]

Device_Status : Status_Register;
pragma Atomic (Device_Status);
for Device_Status'Address use [[Ada Programming/Libraries/|]].System.Storage_Elements.To_Address (16#8010_FF74#);

Incorrect usage[edit | edit source]

It is almost always incorrect to use atomic or volatile variables for tasking.[1] When an object is atomic it just means that it will be read from or written to memory atomically. The compiler will not generate atomic instructions or memory barriers when accessing to that object, it will just:

  • check that the architecture guarantees atomic memory loads and stores,
  • disallow some compiler optimizations, like reordering or suppressing redundant accesses to the object.

For example, the following code, where A is an atomic object can be misunderstood:

A := A + 1;  -- Not an atomic increment!

The compiler will not (and is not allowed by the Standard to) generate an atomic increment instruction to directly increment and update from memory the variable A.[2] This is the code generated by the compiler:

  A := A + 1;
804969f:	a1 04 95 05 08       	mov    0x8059504,%eax
80496a4:	40                   	inc    %eax
80496a5:	a3 04 95 05 08       	mov    %eax,0x8059504

As can be seen, no atomic increment instruction or test-and-set opcode will be generated. Like in other programming languages, if these specific instructions are required in the program they must be written explicitly using machine code insertions.[3]

The above code snippet is equivalent to the following code (both code sequences generates exactly the same object code), where T is a (non-atomic) temporary variable:

T := A;      -- A is copied atomically to local variable T
T := T + 1;  -- local variable T is incremented
A := T;      -- A is stored atomically

Thus it is incorrect to modify an atomic variable at the same time from multiple tasks. For example, two tasks incrementing a counter in parallel. Even in an uniprocessor, other Ada tasking features like a protected object should be used instead. In multiprocessors, depending on the memory consistency model, using various atomic or volatile variables for task communication can have surprising consequences.[2][4] Therefore, extreme care should be taken when using atomic objects for task data sharing or synchronization, specially in a multiprocessor.

References[edit | edit source]

  1. Arch Robison (2007-11-30). "Volatile: Almost Useless for Multi-Threaded Programming". Intel Software Network. Retrieved 2008-05-30. There is a widespread notion that the keyword volatile is good for multi-threaded programming (...) volatile is almost useless for multi-threaded programming.
  2. a b Ian Lance Taylor (2008-03-05). "Volatile". Retrieved 2008-05-28. Using [the C/C++ qualifier] volatile does not mean that the variable is accessed atomically; no locks are used. Using volatile does not mean that other cores in a multi-core system will see the memory accesses; no cache flushes are used. (...) Using volatile does not imply any sort of memory barrier; the processor can and will rearrange volatile memory accesses. (...) You should not use more than one such variable to communicate between any pair of threads, as there is no guarantee that the different threads will see the accesses in the same order.
  3. Laurent Guerby (1995). "C.5 Shared Variable Control". Ada 95 Rationale. Intermetrics. A need to access specific machine instructions arises sometimes (...). Examples include instructions that perform compound operations atomically on shared memory, such as test-and-set and compare-and-swap (...) {{cite book}}: |access-date= requires |url= (help); External link in |chapter= (help); Unknown parameter |month= ignored (help)
  4. Sarita V. Adve, Kourosh Gharachorloo (1996). "Shared Memory Consistency Models: A Tutorial" (PDF). IEEE Computer. 29 (12): 66–76. Retrieved 2008-05-28. {{cite journal}}: Unknown parameter |month= ignored (help)

Portability[edit | edit source]

Compilers[edit | edit source]

Atomic is a standard pragma since Ada 95, defined in the Systems Programming Annex (Annex C). This is a specialized needs annex, therefore this pragma is not available in those compilers not implementing that annex.

Ada versions[edit | edit source]

  • Available in Ada 95 and Ada 2005.
  • In Ada 83 pragma Shared produces similar effects than pragma Atomic (but it is obsolescent since Ada 95).[1]

Platforms[edit | edit source]

Atomic is a portability aid between different platforms. For example, when porting between a 64-bit to a 32-bit architecture, the compiler will detect portability problems thanks to this pragma.

Interfacing[edit | edit source]

The effects of pragma Atomic can be achieved in C using the type volatile sig_atomic_t, defined in signal.h (called volatile std::sig_atomic_t in C++). It is worth noting that the only values that can be portably stored in this type are 0–127,[2] whereas the Ada pragma doesn't impose any specific limit (other than the type of the atomic object).

However, this C type is rarely known by programmers. Therefore, the C volatile qualifier is the approach widely used both for volatile and atomic data, even if atomic access in required. Thus, when interfacing with existing C code, the programmer must first read the code to understand whether the data needs atomic load and stores or not. If needed, the data should be marked in the Ada side with pragma Atomic or Atomic_Components. Otherwise, pragma Volatile or Volatile_Components should be added instead.

C++11 and C11 incorporated atomic operations and types,[3] but differ from the semantics in Ada and are not compatible.

See also[edit | edit source]

Wikibook[edit | edit source]

Ada Reference Manual[edit | edit source]

Ada 95 Rationale[edit | edit source]

Ada 95 Quality and Style Guide[edit | edit source]

References[edit | edit source]

  1. Robert Dewar (1996-02-17). "pragma Shared (was Ada is almost ....)". comp.lang.ada. (Web link). Retrieved on 2008-05-28. "pragma Atomic is QUITE different from pragma Volatile. pragma Atomic in Ada is essentially identical to pragma Shared in Ada 83. pragma Volatile in Ada is essentially identical to volatile in C, and is a feature that is not available in Ada 83."
  2. Justin Pincar (2008-11-19). "DCL34-C. Use volatile for data that cannot be cached". CERT C Secure Coding Standard. Computer Emergency Response Team. Retrieved 2009-01-10. Integer values from 0 through 127 can be safely stored to a variable of type sig_atomic_t.
  3. Herb Sutter (2009-01-08). "volatile vs. volatile". Dr. Dobb's. Retrieved 2009-01-10. ISO C++ added them to the C++0x draft Standard in 2007, under the templated name atomic<T> (e.g., atomic). They started to become available beginning in 2008 in the Boost project and other implementations. The ISO C++ atomics library also provides a C-compatible way to spell those types and their operations (e.g., atomic_int), and these appear to be likely to be adopted by ISO C in the future. {{cite journal}}: External link in |quote= (help)