Clang: соглашение о вызовах x86 FPU - PullRequest
1 голос
/ 26 октября 2019

Мне нужно поддерживать динамические библиотеки и статическое связывание объектных файлов для 32-битных платформ (x86): Win32, Linux32 и MacOS32. Проблема возникает при передаче аргументов FPU (float и double). По умолчанию они передаются в регистрах SSE, а не в стеке. Я не против SSE, но мне нужно, чтобы аргументы и результат передавались стандартно - через стек и FPU.

Я попытался (Godbolt) , установив -mno-sse , и это дает желаемый результат. Но я бы не хотел полностью отказываться от SSE, иногда мне хотелось бы использовать встроенные функции и / или использовать оптимизации MMX / SSE.

__attribute__((stdcall))
long double test(int* num, float f, double d) 
{
    *num = sizeof(long double);
    return f * d;
}
/*-target i386-windows-gnu -c -O3*/
        push    ebp
        mov     ebp, esp
        and     esp, -8
        sub     esp, 8
        movss   xmm0, dword ptr [ebp + 12] # xmm0 = mem[0],zero,zero,zero
        mov     eax, dword ptr [ebp + 8]
        cvtss2sd        xmm0, xmm0
        mov     dword ptr [eax], 12
        mulsd   xmm0, qword ptr [ebp + 16]
        movsd   qword ptr [esp], xmm0
        fld     qword ptr [esp]
        mov     esp, ebp
        pop     ebp
        ret     16
/*-target i386-windows-gnu -mno-sse -c -O3*/
        mov     eax, dword ptr [esp + 4]
        mov     dword ptr [eax], 12
        fld     dword ptr [esp + 8]
        fmul    qword ptr [esp + 12]
        ret     16

1 Ответ

4 голосов
/ 26 октября 2019

Обе версии вашей функции , использующие одно и то же соглашение о вызовах

По умолчанию они передаются в регистры SSE, а не в стек.

Это не то, что показывает ваш вывод asm, а не то, что происходит. Обратите внимание, что ваша первая функция загружает свой dword float arg из стека в xmm0, затем используя mulsd с аргументом qword double также из стека. movss xmm0, dword ptr [ebp + 12] - это нагрузка, которая уничтожаетстарое содержимое XMM0;XMM0 не является входом для этой функции.

Затем, чтобы вернуть ответ в x87 st0 в соответствии с используемым вами твердым старым 32-разрядным соглашением о вызовах, он использует хранилище movsd дляstack и fld x87 load.

Оператор * повышает float до double, чтобы соответствовать другому операнду, что приводит к умножению double, а не long double. Повышение с double до long double не произойдет до тех пор, пока не будет возвращен этот временный double результат.

Похоже, что значения по умолчанию для clang будут называться -mfpmath=sse, если доступно ,Обычно это хорошо, за исключением небольших функций, где мешает соглашение о вызове возвращаемого значения x87. (Также обратите внимание, что x87 имеет «бесплатное» продвижение от float и double до long double, как часть того, как работают fld dword и qword.) Clang не проверяет, сколько стоит затрат на использование математики SSEв небольшой функции;здесь, очевидно, было бы более эффективно использовать x87 для одного умножения.

Но в любом случае -mno-sse не меняет ABI;читайте ваш ассм более внимательно. Если это так, сгенерированный ассм будет меньше отстой!


В Windows, если вы застряли при создании 32-битного кода вообще, vectorcall должно бытьлучший способ передавать / возвращать переменные FP, когда это возможно: он может использовать регистры XMM для передачи / возврата. Очевидно, что любые ABI, которые установлены в камне (как для существующих библиотек), должны быть правильно объявлены, чтобы компилятор вызывал их / правильно возвращал значения из них.

То, что у вас в данный момент есть , является stdcall с аргументами FP в стеке и возвращенными в st0.


Кстати, большая часть кода в вашей первой функции от clang выравнивает стек до разлива /перезагрузите временную double;Windows ABI гарантирует только 4-байтовое выравнивание стека. Это объем работы, чтобы избежать риска разделения строки кэша, почти наверняка того не стоит. Особенно, когда он мог просто уничтожить свой аргумент стека double d как пустое место, и надеялся, что вызывающий объект выровнял это. Оптимизация включена, она просто устанавливает указатель кадра, чтобы он мог and esp без потери старого ESP.


Вы можете использовать return f * (long double)d;

, который компилируется в идентичный ассемблер дляверсия -mno-sse. https://godbolt.org/z/LK0s_5

SSE2 не поддерживает 80-битные типы x87, поэтому clang вынужден использовать fmul. В итоге он вообще не возится с SSE, и в результате получается то, что ему нужно для возвращаемого значения.

...