Встроенная сборка GCC: ограничения
У меня возникают трудности с пониманием роли, которую играют ограничения в встроенной сборке GCC (x86). Я прочитал руководство, которое объясняет, что конкретно делает каждое ограничение. Проблема в том, что, хотя я понимаю, что делает каждое ограничение, я очень мало понимаю, почему вы будете использовать одно ограничение над другим, или каковы могут быть последствия.
Я понимаю, что это очень широкая тема, поэтому небольшой пример должен помочь сузить фокус. Ниже приведена простая процедура asm, которая просто добавляет два числа. Если происходит целочисленное переполнение, записывается значение 1
к выходной переменной C.
int32_t a = 10, b = 5;
int32_t c = 0; // overflow flag
__asm__
(
"addl %2,%3;" // Do a + b (the result goes into b)
"jno 0f;" // Jump ahead if an overflow occurred
"movl $1, %1;" // Copy 1 into c
"0:" // We're done.
:"=r"(b), "=m"(c) // Output list
:"r"(a), "0"(b) // Input list
);
Теперь это работает нормально, за исключением того, что мне пришлось произвольно возиться с ограничениями, пока я не заставил его работать правильно. Первоначально я использовал следующие ограничения:
:"=r"(b), "=m"(c) // Output list
:"r"(a), "m"(b) // Input list
Обратите внимание, что вместо "0" я использую ограничение "m" для b
, Это имело странный побочный эффект: если я скомпилировал флаги оптимизации и дважды вызвал функцию, то по какой-то причине результат операции сложения также будет сохранен в c
, В конце концов я прочитал об " ограничениях соответствия", которые позволяют вам указать, что переменная должна использоваться как входной и выходной операнд. Когда я изменился "m"(b)
в "0"(b)
это сработало.
Но я не очень понимаю, почему вы используете одно ограничение над другим. Я имею в виду, да, я понимаю, что "r" означает, что переменная должна быть в регистре, а "m" означает, что она должна быть в памяти - но я не очень понимаю, каковы последствия выбора одного над другим, или почему добавление операция не будет работать правильно, если я выберу определенную комбинацию ограничений.
Вопросы: 1) В приведенном выше примере кода, почему ограничение "m" на b
причина c
чтобы написать? 2) Есть ли учебник или онлайн-ресурс, который более подробно описывает ограничения?
1 ответ
Вот пример, чтобы лучше проиллюстрировать, почему вы должны тщательно выбирать ограничения (та же функция, что и у вас, но, возможно, написана более кратко):
bool add_and_check_overflow(int32_t& a, int32_t b)
{
bool result;
__asm__("addl %2, %1; seto %b0"
: "=q" (result), "+g" (a)
: "r" (b));
return result;
}
Итак, использовались следующие ограничения: q
, r
, а также g
,
q
значит толькоeax
,ecx
,edx
, или жеebx
может быть выбран. Это потому чтоset*
инструкции должны записываться в 8-битный адресуемый регистр (al
,ah
...) Использованиеb
в%b0
значит, использовать младшую 8-битную часть (al
,cl
...)- Для большинства инструкций с двумя операндами, по крайней мере, один из операндов должен быть регистром. Так что не используйте
m
или жеg
для обоих; использованиеr
по крайней мере, один из операндов. - Для последнего операнда не имеет значения, регистр это или память, поэтому используйте
g
(генеральный).
В приведенном выше примере я решил использовать g
(скорее, чем r
) за a
потому что ссылки обычно реализуются как указатели памяти, поэтому с помощью r
ограничение потребовало бы сначала скопировать референт в регистр, а затем скопировать обратно. С помощью g
, референт может быть обновлен напрямую.
Относительно того, почему ваша оригинальная версия изменила вашу c
со значением сложения, это потому, что вы указали =m
в выходной слот, а не (скажем) +m
; это означает, что компилятору разрешено повторно использовать одну и ту же ячейку памяти для ввода и вывода.
В вашем случае это означает два результата (так как одна и та же ячейка памяти использовалась для b
а также c
):
- Дополнение не переполнилось: тогда,
c
был перезаписан со значениемb
(результат сложения). - Дополнение переполнилось: тогда
c
стало 1 (иb
может также стать 1, в зависимости от того, как был сгенерирован код).