Существует ли ограничение для x86-64, похожее на ограничение «i», но оно соответствует только тогда, когда значение операнда помещается в 32-битный немедленный знак со знаком?
Для функции, показанной ниже, я хотел бы, чтобы gcc использовал lock add mem, imm
, когда операнд вписывается в 32-битный непосредственный объект со знаком, и я хотел бы, чтобы он использовал ограничение «r» и генерировал mov r, imm; lock add mem, r
, когда непосредственный результат не подходит.
Показанный код работает правильно, когда v
является непостоянным значением или константой, которая помещается в знаковое 32-битное число, но gcc генерирует недопустимую инструкцию при использовании с постоянным значением, которое не помещается в знаковое 32-битное число. непосредственный операнд.*
static inline void atomic_add(volatile unsigned long *m, unsigned long v)
{
asm volatile ("lock addq %1, %0" : "+m"(*m) : "ri"(v));
}
Я попробовал использовать в ограничении «n» вместо «i», но, похоже, оно работает так же, как «i». Удаление ограничения «i» работает во всех случаях, но оно перемещает непосредственное значение в регистр, даже если в этом нет необходимости. Поскольку в подавляющем большинстве случаев константа умещается в 8 или 32 бита, я бы предпочел не использовать это решение.
Вот пример, демонстрирующий проблему: https://godbolt.org/z/nPY46Kfdh
extern unsigned long x;
unsigned long m(volatile unsigned long *v)
{
atomic_add(v, 12ul);
atomic_add(v, 12345ul);
atomic_add(v, 123456789000ul);
atomic_add(v, x);
return *v;
}
* Здесь есть множество ответов, объясняющих, почему 64-битная непосредственная реализация не разрешена в инструкции добавления, поэтому нет необходимости в еще одном объяснении, почему она не поддерживается.
@Дэвид, спасибо, кажется, «е» — это именно то, что я искал.
@Дэвид, да, мне следует использовать стандартные атомы, но этот код намного старше их и никогда не модифицировался для их использования.
Если эта кодовая база выполняет свою атомарность через такие функции-обертки, вам нужно будет просто изменить тело функции с asm volatile...
на __atomic_fetch_add(m, v, __ATOMIC_SEQ_CST)
. Даже если вы не измените другие функции-оболочки; для x86 встроенные функции __atomic
без блокировки ABI-совместимы с этим встроенным asm.
Превращаем комментарий в ответ...
Глядя на машинные ограничения для семейства x86 (прокрутите вниз), мы видим:
e 32-bit signed integer constant, or a symbolic reference known to fit that range (for immediate operands in sign-extending x86-64 instructions).
Кажется, это делает то, что вы ищете.
Кроме того, похоже, что и Питер, и я принадлежим к той школе мысли, которая гласит: не используйте встроенный asm. Поэтому, когда это возможно, я рекомендую использовать встроенные функции, а не ассемблерные блоки. В данном случае это наверное __atomic_fetch_add(m, v, __ATOMIC_SEQ_CST)
.
Я понимаю, что вы, возможно, не захотите прямо сейчас тратить время на переработку всего проекта для использования новых атомарных функций, но, возможно, имеет смысл начать миграцию с этой. Особенно если есть обертка, куда можно просто закинуть новый код.
И последняя мысль: я заметил, что вы не используете ассемблер для очистки памяти. В зависимости от того, как вы используете эту процедуру, возможно, вы вносите здесь ошибку синхронизации. Возможно, вы захотите быстро проверить его, чтобы убедиться, что он делает то, что вы намереваетесь.
Если быть более конкретным, отсутствие "memory"
затирания позволяет переупорядочивать во время компиляции с простым доступом, как это сделал бы _ATOMIC_RELAXED
. Это заказано относительно. другие доступы volatile
, поскольку это asm volatile
, включая чистую загрузку/чистое сохранение других атомарных переменных, созданных вручную. Хорошо замечено, это определенная причина переключиться на встроенную функцию __atomic
, если только такое сочетание требований к порядку не является именно тем, что желательно.
Пробовали
e
? ЭТА: Пробовали__atomic_fetch_add
?