Apakah implementasi C ++ AtomicInt ini benar?

9

Premis: Saya bekerja dengan lingkungan embedded ARM (hampir bare-metal) di mana saya bahkan tidak memiliki C ++ 11 (with std::atomic<int>) tersedia, jadi harap hindari jawaban seperti " cukup gunakan standar C ++std::atomic<int> ": Saya tidak bisa .

Apakah implementasi ARM dari AtomicInt ini benar? (anggap arsitektur ARM adalah ARMv7-A )

Apakah Anda melihat beberapa masalah sinkronisasi? Apakah itu volatilewajib / bermanfaat?

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

Juga, saya mencoba untuk mencapai beberapa penggunaan kembali kode, itulah sebabnya saya mengisolasi hanya satu fungsi dasar untuk diimplementasikan dalam kode platform-spesifik ( add()metode di dalam arm/atomic_int.cpp).

Apakah atomic_int.hbenar-benar portabel karena melintasi berbagai platform / arsitektur / kompiler? Apakah pendekatan ini layak ? (Dengan layak saya maksud layak untuk setiap platform untuk menjamin atomicity dengan menerapkan add()metode saja ).

di sini adalah implementasi GCC 8.3.1 ARM yang sesuai dari fungsi yang sama. Rupanya, satu-satunya perbedaan nyata adalah kehadiran dmbsebelum dan sesudah. Apakah mereka benar-benar dibutuhkan dalam kasus saya? Mengapa? Apakah Anda memiliki contoh di mana AtomicInt(tanpa dmb) saya gagal?

UPDATE: implementasi tetap, get()metode yang dihapus untuk memecahkan masalah atomicity dan penyelarasan. Sekarang add()berperilaku seperti standar fetchAndAdd().

gentooise
sumber
volatilekata kunci dalam C ++ berarti tidak mengoptimalkan melalui variabel. Jadi get()manfaat metode itu. Padahal, secara umum, volatile akan mendeprycates dalam C ++. Jika sistem Anda tidak dapat melakukan sinkronisasi data 32-bit bawaan, maka Anda tidak punya banyak pilihan selain menggunakan mutex - spinlock.
ALX23z
Apa versi arsitektur lengan yang Anda gunakan? armv-7?
Mike van Dyke
1
Ini tidak menjawab pertanyaan, tetapi nama yang berisi dua garis bawah berturut-turut ( __ATOMIC_INT_H_) dan nama yang dimulai dengan garis bawah diikuti dengan huruf kapital dicadangkan untuk digunakan oleh implementasi. Jangan menggunakannya dalam kode Anda.
Pete Becker
Nama anggota atomicmungkin paling baik tidak digunakan untuk menghindari kebingungan std::atomic, meskipun menimbulkan pertanyaan mengapa Anda tidak hanya menggunakannya dalam hal apa pun.
Clifford
Menambahkan arsitektur ARM, __ATOMIC_INT_H_pengidentifikasi berganti nama .
gentooise

Jawaban:

2

Jika Anda menggunakan gccmungkin Anda dapat menggunakan Legacy __syncBuilt-in Functions for Atomic Memory Access :

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

Menghasilkan :

add(int volatile&, int):
.L2:
        ldxr    w2, [x0]
        add     w2, w2, w1
        stlxr   w3, w2, [x0]
        cbnz    w3, .L2
        dmb     ish
        ret
Maxim Egorushkin
sumber
Sayangnya saya tidak menggunakan gcc, dan dalam hal apa pun saya tidak ingin mengikat implementasi ke kompiler tertentu. Terima kasih atas petunjuk Anda, setidaknya itu memberi tahu saya bahwa add()bagian ARM saya harus benar. Apa perbedaan antara ldxrdan ldrex?
gentooise
Ini adalah ARM8 (eg64bit) daripada salah satu dari versi 32-bit.
marko
Saya berhasil mendapatkan kode yang sesuai dengan menentukan arsitektur target: tautan . Tampaknya GCC benar-benar meletakkan dmbsebelum dan sesudah ldrex/ strexloop.
gentooise
2
Saya pikir ini adalah pendekatan yang baik tetapi untuk membuatnya kompiler independen, buka godbolt.org/z/WB8rxw ketik fungsi yang ingin Anda gunakan gcc builtins dan salin output perakitan yang sesuai. Pastikan untuk mencocokkan parameter -march dengan versi spesifik ARM.