¿Es correcta esta implementación de C ++ AtomicInt?

9

Premisa: estoy trabajando con un entorno ARM integrado (casi desnudo) donde ni siquiera tengo C ++ 11 (con std::atomic<int>) disponible, así que evite respuestas como " solo use C ++ estándarstd::atomic<int> ": no puedo .

¿Es correcta esta implementación ARM de AtomicInt? (suponga que la arquitectura ARM es ARMv7-A )

¿Ves algún problema de sincronización? ¿Es volatilerequerido / útil?

// 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)
}

Además, estoy tratando de lograr una cierta reutilización del código, es por eso que aislé solo una función básica para implementar en el código específico de la plataforma ( add()método dentro arm/atomic_int.cpp).

¿Es atomic_int.hrealmente portátil como lo es a través de diferentes plataformas / arquitecturas / compiladores? ¿Es factible este enfoque ? (Con factible quiero decir factible para cada plataforma para garantizar la atomicidad implementando solo el add()método ).

Aquí está la implementación correspondiente de ARM GCC 8.3.1 de la misma función. Aparentemente, la única diferencia real es la presencia de dmbantes y después. ¿Son realmente necesarios en mi caso? ¿Por qué? ¿Tienes un ejemplo donde mi AtomicInt(sin dmb) falla?

ACTUALIZACIÓN: implementación fija, get()método eliminado para resolver problemas de atomicidad y alineación. Ahora el se add()comporta como un estándar fetchAndAdd().

gentooise
fuente
volatileLa palabra clave en C ++ significa no optimizar a través de la variable. Entonces el get()método se beneficia de ello. Aunque, en general, la volatilidad está a punto de desencriptarse en C ++. Si su sistema no puede sincronizar datos de 32 bits incorporados, entonces no tiene más remedio que usar mutexes, spinlock como mínimo.
ALX23z
¿Qué versión de la arquitectura de brazo estás usando? armv-7?
Mike van Dyke
1
Esto no responde a la pregunta, pero los nombres que contienen dos guiones bajos consecutivos ( __ATOMIC_INT_H_) y nombres que comienzan con un guión bajo seguido de una letra mayúscula están reservados para su uso por la implementación. No los uses en tu código.
Pete Becker
El nombre del miembro atomicprobablemente no se utilice para evitar confusiones std::atomic, aunque plantea la pregunta de por qué no lo usarías en cualquier caso.
Clifford
Arquitectura ARM agregada, __ATOMIC_INT_H_identificador renombrado .
gentooise

Respuestas:

2

Si usa gccmay be puede usar las funciones integradas heredadas __syncpara el acceso a la memoria atómica :

void add(int volatile& a, int value) {
    __sync_fetch_and_add(&a, value);
}

Genera :

add(int volatile&, int):
.L2:
        ldxr    w2, [x0]
        add     w2, w2, w1
        stlxr   w3, w2, [x0]
        cbnz    w3, .L2
        dmb     ish
        ret
Maxim Egorushkin
fuente
Lamentablemente no estoy usando gcc, y en cualquier caso no quiero vincular la implementación a ningún compilador específico. Gracias de todos modos por su sugerencia, al menos me dice que mi add()parte ARM debe ser correcta. ¿Cuál es la diferencia entre ldxry ldrex?
gentooise
Esta es ARM8 (por ejemplo, 64 bits) en lugar de una de las versiones de 32 bits.
marko
Logré obtener el código correspondiente especificando la arquitectura de destino: enlace . Parece que GCC realmente pone dmbantes y después del bucle ldrex/ strex.
gentooise
2
Creo que este es el buen enfoque, pero para que sea independiente del compilador, simplemente vaya a godbolt.org/z/WB8rxw escriba la función que desea usando gcc builtins y copie la salida de ensamblaje correspondiente. Asegúrese de hacer coincidir el parámetro -march con la versión específica de ARM.