Esta implementação AtomicInt do C ++ está correta?

9

Premissa: estou trabalhando com um ambiente ARM incorporado (quase bare-metal) em que nem tenho C ++ 11 (com std::atomic<int>) disponível; portanto, evite respostas como " basta usar C ++ padrãostd::atomic<int> ": não posso .

Esta implementação ARM do AtomicInt está correta? (suponha que a arquitetura do ARM seja ARMv7-A )

Você vê algum problema de sincronização? É volatilenecessário / ú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)
}

Além disso, estou tentando obter uma reutilização de código, por isso isolei apenas uma função básica para implementar no código específico da plataforma ( add()método interno arm/atomic_int.cpp).

É atomic_int.hrealmente portátil como em diferentes plataformas / arquiteturas / compiladores? Essa abordagem é viável ? (Com viável, quero dizer viável para todas as plataformas garantir a atomicidade implementando apenas o add()método ).

aqui está a implementação correspondente do ARM GCC 8.3.1 da mesma função. Aparentemente, a única diferença real é a presença de dmbantes e depois. Eles são realmente necessários no meu caso? Por quê? Você tem um exemplo em que meu AtomicInt(sem dmb) falha?

UPDATE: implementação fixa, get()método removido para resolver problemas de atomicidade e alinhamento. Agora o add()comportamento se comporta como um padrão fetchAndAdd().

gentooise
fonte
volatileA palavra-chave em C ++ significa não otimizar via variável. Portanto, o get()método se beneficia dele. Embora, em geral, o volátil esteja prestes a desaprovar em C ++. Se o seu sistema não puder sincronizar dados de 32 bits embutidos, você terá pouca opção a não ser usar mutexes - no mínimo, spinlock.
ALX23z 11/11/19
Qual versão da arquitetura do braço você está usando? armv-7?
Mike van Dyke
11
Isso não aborda a questão, mas nomes que contêm dois sublinhados consecutivos ( __ATOMIC_INT_H_) e nomes que começam com um sublinhado seguido por uma letra maiúscula são reservados para uso pela implementação. Não os use no seu código.
Pete Becker
O nome do membro atomicprovavelmente não deve ser usado para evitar confusões std::atomic, embora implique a pergunta por que você não usaria isso em nenhum caso.
Clifford
Arquitetura ARM adicionada, __ATOMIC_INT_H_identificador renomeado .
Gentooise # 11/19

Respostas:

2

Se você usar, gccpode usar as Funções incorporadas herdadas __syncpara acesso à memória atômica :

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

Gera :

add(int volatile&, int):
.L2:
        ldxr    w2, [x0]
        add     w2, w2, w1
        stlxr   w3, w2, [x0]
        cbnz    w3, .L2
        dmb     ish
        ret
Maxim Egorushkin
fonte
Infelizmente não estou usando gcce, de qualquer forma, não quero vincular a implementação a nenhum compilador específico. De qualquer forma, obrigado pela sua dica, pelo menos me diz que minha add()parte do BRAÇO deve estar correta. Qual é a diferença entre ldxre ldrex?
Gentooise # 11/19
Este é o ARM8 (por exemplo, 64 bits), em vez de uma das versões de 32 bits.
Marko
Consegui obter o código correspondente especificando a arquitetura de destino: link . Parece que o GCC coloca dmbantes e depois do loop ldrex/ strex.
Gentooise # 14/19
2
Eu acho que essa é uma boa abordagem, mas para torná-lo independente do compilador, basta acessar o tipo godbolt.org/z/WB8rxw na função que você deseja usar os builtins do gcc e copiar a saída de montagem correspondente. Certifique-se de combinar o parâmetro -march com a versão específica do ARM.