Пытаюсь понять, что будет делать эта инструкция testb (x86-64).
testb $1, %al
Какая здесь стоимость 1 доллара. это все единицы (0xFF
) или одна единица (0x1
)?
Сборка производится clang для следующей программы:
#include <atomic>
std::atomic<bool> flag_atomic{false};
extern void f1();
extern void f2();
void foo() {
bool b = flag_atomic.load(std::memory_order_relaxed);
if (b == false) {
f1();
} else {
f2();
}
}
Соответствующая сборка с (clang ++ -s test.cpp -O3) следующая:
Lcfi2:
.cfi_def_cfa_register %rbp
movb _flag_atomic(%rip), %al
testb $1, %al ; <<<<------------
jne LBB0_2
Обратите внимание, что movb
уже представляет собой микросхему нагрузки + слияние с младшим байтом RAX, на недавнем Intel. movzbl
позволит избежать ложной зависимости. Таким образом, clang уже имеет 3 мупа слитого домена (2 из них из movb
), но всего 2 мупа слитого домена таким образом.
Да, по какой-то причине это происходит только с atomic_load, поскольку скалярная загрузка clang генерирует cmpb
без использования %al
.
Ах да, atomic
обрабатывается в компиляторе особым образом, и (как volatile
) иногда заканчивает работать с оптимизатором. Текущие компиляторы даже не оптимизируют повторные атомные нагрузки, хотя текущий стандарт позволяет это: Может и оптимизирует ли компилятор две атомные нагрузки? и особенно мой ответ на Почему компиляторы не объединяют избыточные записи std :: atomic?
FYI, godbolt.org действительно хорош для игры с выводом asm компилятора (например, ваш код: godbolt.org/g/oNarvk) с разными параметрами / компиляторами / версиями.
Спасибо, что поделились ресурсами. Очень полезно! Кстати, я регулярно использую godbolt. PS: gcc.gnu.org/bugzilla/show_bug.cgi?id=85610
В синтаксисе AT&T $
- это префикс для немедленных значений (смотрите также); $1
- это простая 1, поэтому ваша инструкция устанавливает флаги в соответствии с младшим битом al
.
is it all ones (0xFF) or a single 1 (0x1)?
Все будут
testb $-1, %al
или (точно такой же машинный код, просто предпочтение разборки)
testb $0xff, %al
который, кстати, будет иметь ту же семантику, что и
testb %al, %al
(поскольку маска 0xff
над 8-битным регистром ничего не маскирует), и в этом случае также действительна для вашего кода, поскольку для логического значения не должно быть необходимости что-либо маскировать, чтобы проверить, истинно ли оно (и действительно gcc предпочитает эту последнюю версию для вашего кода).
movb _flag_atomic(%rip), %al
testb $1, %al
jne LBB0_2
в синтаксисе Intel (без префиксов, без суффиксов, порядок операндов dest, source
, явный синтаксис адресации памяти) это
mov al, [rip+_flag_atomic]
test al, 1
jne LBB0_2
И в псевдо-C:
%al = _flag_atomic;
if (%al & 1 != 0) goto LBB0_2;
(jne
- это псевдоним jnz
, который, вероятно, в данном случае более понятен).
0x
имеет префикс шестнадцатеричного числа. 0
имеет префикс восьмеричного числа, и если вы не упомянули какой-либо префикс, это будет десятичная система счисления. В вашем случае это 1 в десятичной системе счисления.
Размер операнда
b
равен байт, а не немного, поэтому$1
- это байт, у которого установлен только младший бит. Странно, что clang использует отдельную загрузку вместоtest
с операндом памяти; функция больше не использует значение, поэтому нет необходимости хранить его в регистре. Или на самом деле немедленный + RIP-родственник не перегорает на Intel. Он также может не сработать с макросом, поэтому вы можете получить 3 uop с объединенными доменами на Intel отtest $1, _flag_atomic(%rip) / jnz
.