пример cmpxchg для 64-битного целого числа

Я использую cmpxchg (сравнение и обмен) в архитектуре i686 для 32-битного сравнения и обмена следующим образом.

(Примечание редактора: в исходном 32-битном примере были ошибки, но вопрос не в этом. Я считаю, что эта версия безопасна, и в качестве бонуса корректно компилируется и для x86-64. Также обратите внимание, что встроенный ассемблер не требуется и не рекомендуется для этого; __atomic_compare_exchange_n или более старый __sync_bool_compare_and_swap работать для int32_t или int64_t на i486 и x86-64. Но этот вопрос касается того, чтобы сделать это с помощью встроенного ассемблера, если вы все еще хотите.)

// note that this function doesn't return the updated oldVal
static int CAS(int *ptr, int oldVal, int newVal)
{
    unsigned char ret;
    __asm__ __volatile__ (
            "  lock\n"
            "  cmpxchgl %[newval], %[mem]\n"
            "  sete %0\n"
            : "=q" (ret), [mem] "+m" (*ptr), "+a" (oldVal)
            : [newval]"r" (newVal)
            : "memory");    // barrier for compiler reordering around this

    return ret;   // ZF result, 1 on success else 0
}

Что эквивалентно 64-разрядной архитектуре x86_64 для сравнения и замены

static int CAS(long *ptr, long oldVal, long newVal)
{
    unsigned char ret;
    // ?
    return ret;
}

person Community    schedule 07.05.2009    source источник
comment
У этого есть ошибка: должно быть "+a"(oldval), потому что cmpxchg обновляет EAX, если сравнение не удается и сохранение не выполнено. (Я думаю, что мы можем пропустить раннее затирание "+&a", потому что единственное, что нужно написать позже, это ret. Нам не нужно читать обновленное oldVal из EAX внутри asm, поэтому, если компилятор не не нужен обновленный oldVal, это нормально, если он выделяет ret в al (и на самом деле ваша функция не принимает oldval по ссылке. И, кстати, да, это может сломаться после встраивания, хотя автономная версия безопасна, потому что соглашения о вызовах.)   -  person Peter Cordes    schedule 15.04.2018
comment
Кроме того, он выпадает из конца непустой функции, если ret==1 (CAS успешно). Просто return ret; как нормальный человек.   -  person Peter Cordes    schedule 15.04.2018
comment
Кто-то должен сделать обязательную ссылку на встроенные атомарные функции gcc. Нет необходимости писать это самостоятельно, и есть много причин, по которым вы не должны.   -  person David Wohlferd    schedule 15.04.2018
comment
@DavidWohlferd: да, собирался отредактировать это в вопросе, но решил, что это будет слишком навязчиво. __sync_bool_compare_and_swap или более новый __atomic_compare_exchange_n решит проблему целая проблема для 32- или 64-битных целых чисел на i386 или x86-64 или любой другой архитектуре, которая вам нравится! С дополнительным преимуществом, позволяющим избежать sete / test в цикле CAS и избежать дополнительной нагрузки, потому что эта дрянная версия не обновляет oldVal по ссылке. gcc.gnu.org/wiki/DontUseInlineAsm.   -  person Peter Cordes    schedule 15.04.2018
comment
@DavidWohlferd: передумал, подумал о лучшей формулировке для редактирования и добавил это к вопросу.   -  person Peter Cordes    schedule 15.04.2018
comment
@PeterCordes. Если мы обновляем это для будущих поколений, не следует ли использовать пометить выходные данные вместо sete? Интересно, что случилось с идентификатором пользователя оператора? Я не могу нажать на нее? Может быть, это вот этот парень?   -  person David Wohlferd    schedule 15.04.2018
comment
@DavidWohlferd: я просто искал неужасный cmpxchg, на который можно было бы связать, просто хотел связать с встроенной сборкой c, получающей несоответствие размера операнда при использовании cmpxchg вместо того, чтобы переписывать свои собственные. Поскольку встроенный ассемблер обычно является неправильным подходом для этого, я не прилагал здесь усилий. Это совершенно тривиальный вопрос; удаление суффикса l заставит код работать с 64-битным размером операнда, если аргументы изменены на 64-битные (как обычно для x86-64), и мы не можем переписать его в разумный вопрос, не аннулируя ответы. Я попробую. опубликуйте там хорошую версию.   -  person Peter Cordes    schedule 15.04.2018


Ответы (4)


Набор инструкций x86_64 содержит инструкцию cmpxchgq (q для четверного слова) для 8-байтовых ( 64 бит) сравнить и поменять местами.

Также есть инструкция cmpxchg8b, которая будет работать с 8-байтовыми величинами, но ее сложнее настроить, так как вам нужно использовать edx:eax и ecx:ebx, а не более естественную 64-битную rax. Причина, по которой это существует, почти наверняка связана с тем фактом, что Intel понадобились 64-битные операции сравнения и замены задолго до появления x86_64. Он по-прежнему существует в 64-битном режиме, но уже не является единственным вариантом.

Но, как уже говорилось, cmpxchgq, вероятно, лучший вариант для 64-битного кода.


Если вам нужно cmpxchg для 16-байтового объекта, используйте 64-разрядную версию cmpxchg8b cmpxchg16b< /а>. Он отсутствовал в самых ранних процессорах AMD64, поэтому компиляторы не будут генерировать его для std::atomic::compare_exchange для объектов размером 16 Б, если вы не включите -mcx16 (для gcc). Однако ассемблеры соберут его, но имейте в виду, что ваш двоичный файл не будет работать на самых ранних процессорах K8. (Это относится только к cmpxchg16b, а не к cmpxchg8b в 64-битном режиме или к cmpxchgq).

person paxdiablo    schedule 07.05.2009

смpxchg8b

__forceinline int64_t interlockedCompareExchange(volatile int64_t & v,int64_t exValue,int64_t cmpValue)
{
  __asm {
    mov         esi,v
    mov         ebx,dword ptr exValue
    mov         ecx,dword ptr exValue + 4
    mov         eax,dword ptr cmpValue
    mov         edx,dword ptr cmpValue + 4
    lock cmpxchg8b qword ptr [esi]
  }
}
person Shay Erlichmen    schedule 07.05.2009
comment
IIRC, cmpxchg8b восходит к первым процессорам i486, поэтому совместимость будет меньшей проблемой, чем с cpxchgq. - person Brian Knoblauch; 01.12.2010
comment
@ Брайан, я не слишком убежден, что совместимость здесь является проблемой, поскольку OP прямо заявил, что это для x86_64. Что я считаю более потенциальной проблемой, так это гимнастику asm, необходимую для использования edx:eax/ecx:ebx, а не более естественного (для меня) rax. Вы также должны убедиться, что ваши соглашения о вызовах, разрешающие удаление регистров, разрешают запись в эти регистры. В противном случае вам понадобятся толчки и щелчки, чтобы защитить их. - person paxdiablo; 21.10.2016
comment
@Shay: в x86-64 int64_t соответствует rax, поэтому вы возвращаете младшую половину значения из памяти перед cmpxchg8b. Если вы предназначали это для 32-битного MSVC, а не x86-64 clang для x32 ABI (32-битные указатели в длинном режиме), который также может компилировать этот синтаксис, вы должны так и сказать. В любом случае, предполагая, что вы вернете полное значение, вызывающая сторона должна будет сравнить возвращаемое значение с тем, что он передал для cmpValue, чтобы увидеть, удалось ли сравнение, я полагаю? Поскольку вы теряете результат флага из cmpxchg8b. - person Peter Cordes; 15.04.2018
comment
Кроме того, имена переменных кажутся фиктивными. Ожидаемое значение и значение сравнения описывают ввод edx:eax, то есть то, что вы ожидаете найти в памяти. ecx:ebx - это новое значение (хранится, если сравнение завершается успешно), поэтому IDK, что должно означать exValue. - person Peter Cordes; 15.04.2018
comment
@PeterCordes Я не писал код, я использовал его из FFMPEG (думаю, это было ПОЧТИ 10 лет назад) - person Shay Erlichmen; 16.04.2018

Архитектура x64 поддерживает 64-битное сравнение-обмен с использованием старой доброй инструкции cmpexch. Или вы также можете использовать несколько более сложную инструкцию cmpexch8b (из "Руководство программиста по архитектуре AMD64, том 1: Программирование приложений"):

Инструкция CMPXCHG сравнивает значение в регистре AL или rAX с первым (целевым) операндом и устанавливает арифметические флаги (ZF, OF, SF, AF, CF, PF) в соответствии с результатом. Если сравниваемые значения равны, исходный операнд загружается в операнд назначения. Если они не равны, первый операнд загружается в аккумулятор. CMPXCHG можно использовать, чтобы попытаться перехватить семафор, т. е. проверить, свободен ли его статус, и если да, загрузить в семафор новое значение, сделав его состояние занятым. Проверка и загрузка выполняются атомарно, поэтому параллельные процессы или потоки, использующие семафор для доступа к общему объекту, не будут конфликтовать.

Инструкция CMPXCHG8B сравнивает 64-битные значения в регистрах EDX:EAX с 64-битной ячейкой памяти. Если значения равны, устанавливается нулевой флаг (ZF), и значение ECX:EBX копируется в ячейку памяти. В противном случае флаг ZF сбрасывается, а значение памяти копируется в EDX:EAX.

Инструкция CMPXCHG16B сравнивает 128-битное значение в регистрах RDX:RAX и RCX:RBX со 128-битной ячейкой памяти. Если значения равны, устанавливается нулевой флаг (ZF), и значение RCX:RBX копируется в ячейку памяти. В противном случае флаг ZF сбрасывается, и значение памяти копируется в rDX:rAX.

Для различных синтаксисов ассемблера может потребоваться длина операций, указанная в мнемонике инструкции, если размер операндов не может быть выведен. Это может иметь место для встроенного ассемблера GCC - я не знаю.

person Michael Burr    schedule 07.05.2009

использование cmpxchg8B из Руководства программиста по архитектуре AMD64 V3:

Сравните регистр EDX:EAX с 64-битной ячейкой памяти. Если они равны, установите нулевой флаг (ZF) в 1 и скопируйте регистр ECX:EBX в ячейку памяти. В противном случае скопируйте ячейку памяти в EDX:EAX и очистите нулевой флаг.

Я использую cmpxchg8B для реализации простой функции блокировки мьютекса на машине x86-64. вот код

.text
.align 8
.global mutex_lock
mutex_lock:
    pushq   %rbp
    movq    %rsp,   %rbp

    jmp .L1

.L1:
    movl    $0, %edx
    movl    $0, %eax
    movl    $0, %ecx
    movl    $1, %ebx
    lock    cmpxchg8B   (%rdi)
    jne .L1
    popq    %rbp
    ret
person xinghua    schedule 01.12.2013
comment
Проголосовали за то, что они слишком сложны, а также за рекомендацию менее эффективной инструкции. jmp .L1 не работает; выполнение всегда продолжается до следующей инструкции самостоятельно. И cmpxchg8b использовать гораздо сложнее, чем qword cmpxchg. например mutex_lock: xor %eax,%eax; mov $1,%edx; lock cmpxchg (%rdi); jne mutex_lock; ret выполняет всю вашу функцию. (Не стесняйтесь заменить свой код моим, я был бы рад удалить свой отрицательный голос, если вы улучшите ответ.) - person Peter Cordes; 15.04.2018
comment
Это нехороший комментарий, учитывая, что mutex_lock: mov $1, %edx; .mutex: xor %eax, %eax; блокировка cmpxchg (%rdi); jne .мьютекс; рет; поставил бы 1+ этому даже. Независимо от того, является ли пример, демонстрирующий использование, примером передовой практики или просто примером, демонстрирующим возможность, он все равно остается примером. Он прямо не заявил, что приводит именно примеры передовой практики. : o cmpxchg16b, вероятно, потребует какой-то процедуры перемещения атомарной строки, чтобы даже найти цель, которую в любом случае было бы трудно дать наилучшей практике, поскольку, вероятно, всегда будут лучшие способы, особенно по сравнению с cmpxchg8b. Я не знаю - person GodDamn; 16.04.2021
comment
Основное отличие заключается в том, как это называется. cmpxchg{8,16}b имеет два операнда, тогда как cmpxchg имеет только один. Операнд памяти задействован в обеих инструкциях. В то время как cmpxchg{8,16}b неявно использует как {e,r}cx:{e,r}bx в качестве нового значения замены, так и {e,r}dx:{e,r}ax в качестве старого значения сравнения, cmpxchg с другой стороны, требуется, чтобы второй операнд регистра был явно указан как значение замены. Есть множество инструкций, которые частично перекрываются, хотя, честно говоря, и все они просто предпочтения. AT&T сложнее, чем Intel. Итак, зачем разбивать сложный ответ еще большим количеством сложностей? - person GodDamn; 16.04.2021
comment
cmpxchg8|16}b может атомарно загружать/сохранять 16-байтовую строку за одну операцию, потенциально выполняя ее многократно, и это все, что нужно, честно говоря... - person GodDamn; 16.04.2021