Cette implémentation C ++ AtomicInt est-elle correcte?

9

Prémisse: je travaille avec un environnement ARM embarqué (presque bare-metal) où je n'ai même pas C ++ 11 (avec std::atomic<int>) disponible, donc veuillez éviter les réponses comme " utilisez simplement le C ++ standardstd::atomic<int> ": je ne peux pas .

Cette implémentation ARM d'AtomicInt est-elle correcte? (supposons que l'architecture ARM est ARMv7-A )

Voyez-vous un problème de synchronisation? Est-ce volatilenécessaire / utile?

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

En outre, j'essaie de réutiliser du code, c'est pourquoi j'ai isolé une seule fonction de base à implémenter dans du code spécifique à la plate-forme ( add()méthode à l'intérieur arm/atomic_int.cpp).

Est-il atomic_int.hvraiment portable comme sur différentes plates-formes / architectures / compilateurs? Cette approche est-elle réalisable ? (Avec faisable, je veux dire faisable pour chaque plate-forme pour garantir l'atomicité en mettant en œuvre uniquement la add()méthode ).

voici l'implémentation ARM GCC 8.3.1 correspondante de la même fonction. Apparemment, la seule vraie différence est la présence d' dmbavant et d'après. Sont-ils vraiment nécessaires dans mon cas? Pourquoi? Avez-vous un exemple où mon AtomicInt(sans dmb) échoue?

MISE À JOUR: implémentation fixe, get()méthode supprimée pour résoudre les problèmes d'atomicité et d'alignement. Maintenant, il add()se comporte comme un standard fetchAndAdd().

gentooise
la source
volatilemot-clé en C ++ signifie ne pas optimiser via une variable. La get()méthode en profite donc. Bien que, en général, volatile soit sur le point de se déprécier en C ++. Si votre système ne peut pas synchroniser les données 32 bits intégrées, vous n'avez pas d'autre choix que d'utiliser des mutex - spinlock à tout le moins.
ALX23z
Quelle version de l'architecture de bras utilisez-vous? armv-7?
Mike van Dyke
1
Cela ne résout pas la question, mais les noms qui contiennent deux traits de soulignement consécutifs ( __ATOMIC_INT_H_) et les noms qui commencent par un trait de soulignement suivi d'une lettre majuscule sont réservés à l'utilisation par l'implémentation. Ne les utilisez pas dans votre code.
Pete Becker
Il atomicest préférable de ne pas utiliser le nom de membre pour éviter toute confusion std::atomic, même si cela soulève la question de savoir pourquoi vous ne l'utiliseriez pas de toute façon.
Clifford
Ajout de l'architecture ARM, __ATOMIC_INT_H_identifiant renommé .
gentooise

Réponses:

2

Si vous utilisez gccpeut - être vous pouvez utiliser héritage __syncFonctions intégrées pour l' accès à la mémoire atomique :

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

Génère :

add(int volatile&, int):
.L2:
        ldxr    w2, [x0]
        add     w2, w2, w1
        stlxr   w3, w2, [x0]
        cbnz    w3, .L2
        dmb     ish
        ret
Maxim Egorushkin
la source
Malheureusement, je n'utilise pas gcc, et en tout cas je ne veux pas lier l'implémentation à un compilateur spécifique. Merci quand même pour votre conseil, au moins ça me dit que ma add()partie ARM devrait être correcte. Quelle est la différence entre ldxret ldrex?
gentooise
Il s'agit d'ARM8 (par exemple 64 bits) plutôt que d'une des versions 32 bits.
Marko
J'ai réussi à obtenir le code correspondant en spécifiant l'architecture cible: lien . Il semble que GCC place réellement dmbavant et après la boucle ldrex/ strex.
gentooise
2
Je pense que c'est la bonne approche, mais pour le rendre indépendant du compilateur, allez simplement sur godbolt.org/z/WB8rxw dans la fonction que vous souhaitez utiliser avec les commandes internes gcc et copiez la sortie d'assembly correspondante. Assurez-vous de faire correspondre le paramètre -march avec la version spécifique d'ARM.