AshBlade
@AshBlade
Просто хочу быть счастливым

Почему GCC не видит встроенную атомарную операцию?

Пытаюсь сделать конкурентную очередь на CAS операциях. Использую GCC, у него есть расширения для атомарных операций (в частности __atomic_compare_exchange).

Сделал такую портянку для начала (протестировать CAS):

struct entry
{
    int value;
    struct entry *next;
};

struct list
{
    struct entry *head;
};

int main(int argc, char const *argv[])
{
    struct list l;
    l.head = (struct entry *)malloc(sizeof(struct entry));
    l.head->next = NULL;
    l.head->value = 123;
    struct entry *next_head = (struct entry*) malloc(sizeof(struct entry));
    next_head->value = 444;
    next_head->next = NULL;
    if (__atomic_compare_exchange(l.head, (struct entry *)NULL, next_head, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))
    {
        printf("success: next = %d\n", l.head->next->value);
    }
    else
    {
        printf("failed\n");
    }

    return 0;
}


Компилирую и получаю на выходе ошибку:
$> gcc main.c -o main
/usr/bin/ld: /tmp/ccCrSn4B.o: в функции «main»:
main.c:(.text+0x82): неопределённая ссылка на «__atomic_compare_exchange_16»
collect2: error: ld returned 1 exit status


Пытался настраивать архитектуру (-mcpu), изменял стандарт c (-std), поддержку инструкций (-mcx16).
Возможно проблема в том, что GCC хочет использовать размер в 16 байт, хотя у меня 64 бит поэтому необходимо только 8.

/proc/cpuinfo

processor       : 0
vendor_id       : AuthenticAMD
cpu family      : 25
model           : 68
model name      : AMD Ryzen 5 6600H with Radeon Graphics
stepping        : 1
microcode       : 0xa404101
cpu MHz         : 1636.215
cache size      : 512 KB
physical id     : 0
siblings        : 12
core id         : 0
cpu cores       : 6
apicid          : 0
initial apicid  : 0
fpu             : yes
fpu_exception   : yes
cpuid level     : 16
wp              : yes
flags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl nonstop_tsc cpuid extd_apicid aperfmperf rapl pni pclmulqdq monitor ssse3 fma cx16 sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw ibs skinit wdt tce topoext perfctr_core perfctr_nb bpext perfctr_llc mwaitx cpb cat_l3 cdp_l3 hw_pstate ssbd mba ibrs ibpb stibp vmmcall fsgsbase bmi1 avx2 smep bmi2 invpcid cqm rdt_a rdseed adx smap clflushopt clwb sha_ni xsaveopt xsavec xgetbv1 xsaves cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local clzero irperf xsaveerptr rdpru wbnoinvd cppc arat npt lbrv svm_lock nrip_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilter pfthreshold avic v_vmsave_vmload vgif v_spec_ctrl umip pku ospke vaes vpclmulqdq rdpid overflow_recov succor smca
bugs            : sysret_ss_attrs spectre_v1 spectre_v2 spec_store_bypass srso
bogomips        : 6587.56
TLB size        : 2560 4K pages
clflush size    : 64
cache_alignment : 64
address sizes   : 48 bits physical, 48 bits virtual
power management: ts ttp tm hwpstate cpb eff_freq_ro [13] [14]



Версия GCC: 11.4.0
  • Вопрос задан
  • 468 просмотров
Решения вопроса 1
AshBlade
@AshBlade Автор вопроса
Просто хочу быть счастливым
Разобрался.
Есть 2 замечания:
1. Поближе посмотрел на сигнатуру

bool __atomic_compare_exchange (type *ptr, type *expected, type *desired, bool weak, int success_memorder, int failure_memorder)


Короче говоря, мне нужно было передавать не указатели, а указатели на указатели. Исправил:

if (__atomic_compare_exchange(&l.head->next,  (struct entry**)NULL, &new_next, 1, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))


Но потом начал ловить SEGFAULT. И тут пришел к 2 замечанию

2. Не заметил следующего в документации:

This built-in function implements an atomic compare and exchange operation. This compares the contents of *ptr with the contents of *expected. If equal, the operation is a read-modify-write operation that writes desired into *ptr. If they are not equal, the operation is a read and the current contents of *ptr are written into *expected. weak is true for weak compare_exchange, which may fail spuriously, and false for the strong variation, which never fails spuriously. Many targets only offer the strong variation and ignore the parameter. When in doubt, use the strong variation.


Т.е. если значение во 2 аргументе не равно значению из 1, то (!!!) по месту указателя 2 записывается полученное из указателя 1 значение. А у меня там был NULL (конец списка обозначается NULL). Заменил на корректно выделенный элемент списка и все заработало

int main(int argc, char const *argv[])
{
    struct list l;

    l.head = (struct entry *)malloc(sizeof(struct entry));
    l.head->next = NULL;
    l.head->value = 123;
    struct entry *old_next = (struct entry *)malloc(sizeof(struct entry));
    old_next->next = NULL;
    old_next->value = 555;
    l.head->next = old_next;

    struct entry *new_next = (struct entry *)malloc(sizeof(struct entry));
    new_next->value = 444;
    new_next->next = NULL;
    if (__atomic_compare_exchange(&l.head->next, &old_next, &new_next, 1, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))
    {
        printf("ok\n");
        printf("success: next = %d\n", l.head->next->value);
    }
    else
    {
        printf("failed\n");
    }

    return 0;
}


Не хочу возиться со всякими dummy узлами, поэтому буду использовать __sync_bool_compare_and_swap. Вот такой код сработает:
int main(int argc, char const *argv[])
{
    struct list l;

    l.head = (struct entry *)malloc(sizeof(struct entry));
    l.head->next = NULL;
    l.head->value = 123;

    struct entry *new_next = (struct entry *)malloc(sizeof(struct entry));
    new_next->value = 444;
    new_next->next = NULL;

    if (__sync_bool_compare_and_swap(&l.head->next, (struct entry *)NULL, new_next))
    {
        printf("ok\n");
        printf("success: next = %d\n", l.head->next->value);
    }
    else
    {
        printf("failed\n");
    }

    return 0;
}


UPD: спасибо res2001, разобрался лучше. итоговый вариант

#include <stdatomic.h>

int main(int argc, char const *argv[])
{
    struct list l;

    l.head = (struct entry *)malloc(sizeof(struct entry));
    l.head->next = NULL;
    l.head->value = 123;

    struct entry *new_next = (struct entry *)malloc(sizeof(struct entry));
    new_next->value = 444;
    new_next->next = NULL;
    struct entry *tmp = NULL;
    if (atomic_compare_exchange_strong(&l.head->next, &tmp, new_next))
    {
        printf("ok\n");
        printf("success: next = %d\n", l.head->next->value);
    }
    else
    {
        printf("failed\n");
    }

    return 0;
}


Просто надо создать переменную указатель, которой присвоить NULL. Также использовал atomic_compare_exchange_strong из stdatomic вместо расширения GCC - он под капотом использует __atomic_* вместо устаревшего __sync_*
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
@res2001
Developer, ex-admin
Почему не использовать функции из стандартной библиотеки?
https://en.cppreference.com/w/c/atomic/atomic_comp...
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы