XOR немного переворачивает вместо того, чтобы всегда очищать его. И — это один вариант, BTR (bit-test-reset) — другой. BTR с назначением памяти очень медленный с регистровым источником, но совсем неплохо с немедленным (всего 2 мкп на Haswell, 3 на Skylake. Правда, до 4 на AMD, где стоит 2 мкп даже для btr $9, %eax
. )
popf
довольно медленный (9 мкп, 1 за 20 циклов на Skylake). Или на Ryzen 35 мкп и один на 13 тактов. (http://agner.org/optimize). Таким образом, оптимизация окружающего кода не будет иметь большого значения, но интересно найти способ сохранить компактный размер кода.
Вам не нужно сохранять/восстанавливать EAX самостоятельно, просто скажите компилятору, что вы хотите стереть его с помощью : "eax"
в качестве списка стирания, или использовать фиктивный операнд вывода (обратите внимание, что я использую расширенный ассемблер GNU C, а не базовый).
static inline
void clear_tf(void) {
long dummy; // there's no type that's always 32-bit on 32-bit, and always 64 on 64-bit. x32 uses 32-bit pointers in long mode so uintptr_t or size_t doesn't work.
// if porting to x86-64 System V user-space: beware that push clobbers the red-zone
__asm__ volatile("pushf \n\t"
"pop %[tmp] \n\t"
"btr $9, %[tmp]\n\t" // reset bit 9
"push %[tmp] \n\t"
"popf"
: [tmp] "=r"(dummy)
: // no inputs
: // no clobbers. // "memory" // would block reordering with loads/stores.
);
}
Или просто не трогайте никакие регистры: это тоже очень эффективно, особенно на AMD Ryzen, где нет uop-синхронизации стека и памяти-назначения И это одиночный uop.
static inline
void clear_tf(void) {
// if porting to x86-64 System V user-space: beware that push clobbers the red-zone
__asm__ volatile("pushf \n\t"
"andl $0xFFFFFEFF, (%esp) \n\t" // 1 byte larger than the pop/btr/push version
"popf"
);
// Basic asm syntax: no clobbers.
}
Для меньшего размера кода, вероятно, подойдет btrl $9, (%esp)
. По-прежнему всего 2 мопов на Haswell (3 на Skylake), но на 2 байта меньше, чем andl
. andb $0xfe, 1(%esp)
также имеет тот же размер, но вызывает остановку переадресации хранилища и составляет 2 uop + uop-синхронизация стека на Intel при использовании после push
. pop %%eax; and $0xfe, %ah; push %eax
тоже такого же размера, а также 3 моп (плюс слияющий моп с частичным регистром, который выдает в цикле сам по себе на Haswell/SKL). Но на АМД красиво.
Переносимость
Кстати, в коде пользовательского пространства x86-64 System V вы не можете безопасно вставлять/выталкивать, не затирая красную зону компилятора, поэтому вы, вероятно, захотите add $-128, %rsp
до push
и восстановить его после.
В коде ядра нет красной зоны, поэтому push/pop внутри встроенного asm в порядке.
Windows использует другой ABI без красной зоны.
person
Peter Cordes
schedule
20.07.2018