Premise: I'm working with an ARM embedded (almost bare-metal) environment where I don't even have C++11 (with std::atomic<int>
) available, so please avoid answers like "just use standard C++ std::atomic<int>
": I can't.
Is this ARM implementation of AtomicInt correct? (assume ARM architecture is ARMv7-A)
Do you see some synchronization issue? Is it volatile
required/useful?
// File: atomic_int.h
#ifndef ATOMIC_INT_H_
#define ATOMIC_INT_H_
#include <stdint.h>
class AtomicInt
{
public:
AtomicInt(int32_t init = 0) : atom(init) { }
~AtomicInt() {}
int32_t add(int32_t value); // Implement 'add' method in platform-specific file
int32_t sub(int32_t value) { return add(-value); }
int32_t inc(void) { return add(1); }
int32_t dec(void) { return add(-1); }
private:
volatile int32_t atom;
};
#endif
// File: arm/atomic_int.cpp
#include "atomic_int.h"
int32_t AtomicInt::add(int32_t value)
{
int32_t res, prev, tmp;
asm volatile(
"try: ldrex %1, [%3]\n" // prev = atom;
" add %0, %1, %4\n" // res = prev + value;
" strex %2, %0, [%3]\n" // tmp = outcome(atom = res); // may fail
" teq %2, #0\n" // if (tmp)
" bne try" // goto try; /* add failed: someone else modified atom -> retry */
: "=&r" (res), "=&r" (prev), "=&r" (tmp), "+mo" (atom) // output (atom is both in-out)
: "r" (value) // input
: "cc"); // clobbers (condition code register [CPSR] changed)
return prev; // safe return (local variable cannot be changed by other execution contexts)
}
Also, I'm trying to achieve some code reuse, that is why I isolated just one basic function to implement in platform-specific code (add()
method inside arm/atomic_int.cpp
).
Is atomic_int.h
really portable as it is across different platforms/architectures/compilers? Is this approach feasible? (With feasible I mean feasible for every platform to guarantee atomicity by implementing just the add()
method).
here is the corresponding ARM GCC 8.3.1 implementation of the same function. Apparently, the only real difference is the presence of dmb
before and after. Are they really needed in my case? Why? Do you have an example where my AtomicInt
(without dmb
) fails?
UPDATE: fixed implementation, removed get()
method to solve atomicity and alignment issues. Now the add()
behaves like a standard fetchAndAdd()
.
volatile
keyword in C++ means don't optimize via variable. Soget()
method benefits from it. Though, in general, volatile is about to deprycates in C++. If your system cannot built-in syncronize 32-bit data, then you have little choice but to use mutexes - spinlock in the very least. – Flosi__ATOMIC_INT_H_
) and names that begin with an underscore followed by a capital letter are reserved for use by the implementation. Don't use them in your code. – Carchemishatomic
is probably best not used to avoid confusion withstd::atomic
, although it begs the question why you wouldn't just use that in any case. – Footstool__ATOMIC_INT_H_
identifier. – Troveratomic
withatom
. @clifford if you're asking me why I'm not using std::atomic, it is because the cross-compiler I'm using does not support C++11. – Trover__attribute__
or#pragma
directives to force a specific alignment, or the compiler documentation will tell you what it guarantees or not otherwise. But yes, the implementation will be platform, architecture and/or compiler specific. – FootstoolAtomicInt
). At the same time, I want the framework itself to be "easily" ported, i.e. minimizing and isolating the framework code that is platform/architecture/compiler specific, so that I will have to rewrite only specific portions of the framework when porting. So my question here is dual: did I isolate the necessary/sufficient portion ofAtomicInt
? Did I implemented that portion correctly for ARMv7-A? – Troverldrex
, but the ARM documentation I found isn't too clear. – Presentativedmb
, and I'm also not sure ifclrex
is needed to reset exclusive monitors during context switches (or is it done automatically) on Cortex-A. – Troverget()
method. Please focus on implementation v2 withoutget()
method. – Trovervolatile
doesn't guarantee that. And (as far as I know)asm volatile("...")
is a compiler specific extension so it can be compiler specific. – Missendasm volatile("...")
it is ok for me to be compiler specific, I only wantatomic_int.h
to be portable (question edited: I further clarified my portability issue). For memory barrier, I would like to understand why and in which cases it is needed. – Troverasm
has all the needed specification, and everything is access with inlineasm
, then inherently no variable needs to be volatile: volatile applies to access in C/C++, not in code linked (external assembly code) or fused (inline assembly) into C/C++. – Kernelvolatile
(if it's not harmful) in order to be more generic. – Trover