Если ваша сопрограмма выглядит как непрозрачный (не встроенный) вызов функции, компилятор уже будет предполагать, что состояние FP забито (за исключением регистров управления, таких как MXCSR и управляющего слова x87 (режим округления)), потому что все регистры FP закрыты при вызове в обычном соглашении о вызовах функций.
За исключением Windows, где xmm6..15 сохраняются при вызове.
Также имейте в виду, что если вы помещаете call
во встроенный ассемблер, вы не сможете сказать компилятору, что ваш ассемблер перекрывает красную зону (на 128 байт ниже RSP в x86-64 System V ABI).Вы можете скомпилировать этот файл с помощью -mno-redzone
или использовать add rsp, -128
перед call
, чтобы пропустить красную зону, принадлежащую сгенерированному компилятором коду.
Чтобы объявить клобберы насостояние FP, вы должны называть все регистры по отдельности.
"xmm0", "xmm1", ..., "xmm15"
(сгусток xmm0 считается как сглаживающий ymm0 / zmm0).
Для правильной меры вы должны также назвать "mm0", ..., "mm7"
(MMX), на случай, если ваш код встроен в какой-то унаследованный код с использованием встроенных функций MMX.
Чтобы также сжать стек x87, "st"
- это то, как вы ссылаетесь на st(0)
в списке клоббера.Остальные регистры имеют свои нормальные имена для синтаксиса GAS: "st (1)", ..., "st (7)" .
https://stackoverflow.com/questions/39728398/how-to-specify-clobbered-bottom-of-the-x87-fpu-stack-with-extended-gcc-assembly
You never know, it is possible to compile with
clang -mfpmath = 387 , or to use 387 via
long double`.
(Надеемся, что ни один код не использует -mfpmath=387
в 64-битном режиме и встроенных MMX одновременно; следующий тестовый пример выглядит немного неработающим с gcc в этом случае.)
#include <immintrin.h>
float gvar;
int testclobber(float f, char *p)
{
int arg1 = 1, arg2 = 2;
f += gvar; // with -mno-sse, this will be in an x87 register
__m64 mmx_var = *(const __m64*)p; // MMX
mmx_var = _mm_unpacklo_pi8(mmx_var, mmx_var);
// x86-64 System V calling convention
unsigned long long retval;
asm volatile ("add $-128, %%rsp \n\t" // skip red zone. -128 fits in an imm8
"call whatever \n\t"
"sub $-128, %%rsp \n\t"
// FIXME should probably align the stack in here somewhere
: "=a"(retval) // returns in RAX
: "D" (arg1), "S" (arg2) // input args in registers
: "rcx", "rdx", "r8", "r9", "r10", "r11" // call-clobbered integer regs
// call clobbered FP regs, *NOT* including MXCSR
, "mm0", "mm1", "mm2", "mm3", "mm4", "mm5", "mm6", "mm7" // MMX
, "st", "st(1)", "st(2)", "st(3)", "st(4)", "st(5)", "st(6)", "st(7)" // x87
// SSE/AVX: clobbering any results in a redundant vzeroupper with gcc?
, "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7"
, "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15"
#ifdef __AVX512F__
, "zmm16", "zmm17", "zmm18", "zmm19", "zmm20", "zmm21", "zmm22", "zmm23"
, "zmm24", "zmm25", "zmm26", "zmm27", "zmm28", "zmm29", "zmm30", "zmm31"
, "k0", "k1", "k2", "k3", "k4", "k5", "k6", "k7"
#endif
#ifdef __MPX__
, "bnd0", "bnd1", "bnd2", "bnd3"
#endif
, "memory" // reads/writes of globals and pointed-to data can't reorder across the asm (at compile time; runtime StoreLoad reordering is still a thing)
);
// Use the MMX var after the asm: compiler has to spill/reload the reg it was in
*(__m64*)p = mmx_var;
_mm_empty(); // emms
gvar = f; // memory clobber prevents hoisting this ahead of the asm.
return retval;
}
source + asm в проводнике компилятора Godbolt
Комментируя одну из строк clobbers, мы можем видеть, что разлив-улет исчезает в ассемблере,например, комментирование x87 st .. st(7)
clobbers приводит к коду, который оставляет f + gvar
в st0, всего на fst dword [gvar]
после вызова.
Аналогично, комментирование строки mm0
позволяет gcc и clang сохранять mmx_var
в mm0
через call
. ABI требует, чтобы FPU находился в режиме x87, а не MMX, на call
/ ret
, этого на самом деле недостаточно. Компилятор разлит / перезагрузит вокруг asm, но он выиграл 'Вставьте emms
для нас. Но, к тому же, для функции, использующей MMX, было бы ошибкой вызывать вашу подпрограмму без предварительного выполнения _mm_empty()
, поэтому, возможно, это не является реальной проблемой.
Я не экспериментировал с __m256
переменными, чтобы увидеть, вставляет ли он vzeroupper
перед asm, чтобы избежать возможных замедлений SSE / AVX.
Если мы прокомментируем строку xmm8..15
, мы увидимверсия, которая не использует x87 для float
, сохраняет ее в xmm8
, потому что теперь она думает, что у нее есть некоторые неубитые регистры xmm. Если мы прокомментируем оба набора строк, предполагается, что xmm0
живет через asm, так что это работает как тест clobbers.
вывод asm со всемиClobbers на месте
Сохраняет / восстанавливает RBX (для удержания аргумента указателя в операторе asm), что приводит к повторному выравниванию стека на 16. Это еще одна проблема с использованием call
из встроенногоasm: я не думаю, что выравнивание RSP гарантировано.
# from clang7.0 -march=skylake-avx512 -mmpx
testclobber: # @testclobber
push rbx
vaddss xmm0, xmm0, dword ptr [rip + gvar]
vmovss dword ptr [rsp - 12], xmm0 # 4-byte Spill (because of xmm0..15 clobber)
mov rbx, rdi # save pointer for after asm
movq mm0, qword ptr [rdi]
punpcklbw mm0, mm0 # mm0 = mm0[0,0,1,1,2,2,3,3]
movq qword ptr [rsp - 8], mm0 # 8-byte Spill (because of mm0..7 clobber)
mov edi, 1
mov esi, 2
add rsp, -128
call whatever
sub rsp, -128
movq mm0, qword ptr [rsp - 8] # 8-byte Reload
movq qword ptr [rbx], mm0
emms # note this didn't happen before call
vmovss xmm0, dword ptr [rsp - 12] # 4-byte Reload
vmovss dword ptr [rip + gvar], xmm0
pop rbx
ret
Обратите внимание, что из-за "memory"
clobber в операторе asm
, *p
и gvar
читаются перед asm, но написано после.Без этого оптимизатор мог бы уменьшить нагрузку или поднять хранилище, чтобы ни одна локальная переменная не находилась в выражении asm
.Но теперь оптимизатору необходимо предположить, что сам оператор asm
может прочитать старое значение gvar
и / или изменить его.(И предположим, что p
указывает на память, которая так или иначе доступна глобально, потому что мы не использовали __restrict
.)