Использование возвращаемых значений FPU в коде C++

У меня есть программа NASM x86, которая, кажется, работает отлично. У меня проблемы с использованием значений, возвращаемых из него. Это 32-битная Windows, использующая MSVC++. Я ожидаю возвращаемое значение в ST0.

Минимальный пример, демонстрирующий проблему с возвращаемыми значениями, можно увидеть в этом коде сборки C++ и NASM:

#include <iostream>
extern "C" float arsinh(float);

int main()
{
    float test = arsinh(5.0);
    printf("%f\n", test);                    
    printf("%f\n", arsinh(5.0));             
    std::cout << test << std::endl;          
    std::cout << arsinh(5.0) << std::endl;   
}

Код сборки:

section .data
value: dq 1.0
section .text
global _arsinh
_arsinh:
    fld dword[esi-8]      ;loads the given value into st0
    ret

Я не могу понять, как использовать возвращаемое значение, так как я всегда получаю неправильное значение независимо от того, какой тип данных я использую. В этом примере должно быть возвращено значение 5, и я ожидаю, что результат будет выглядеть так:

5.000000

5.000000

5

5

Вместо этого я получаю вывод, похожий на:

-9671494178951383518019584,000000

-9671494178951383518019584,000000

-9.67149e + 24

5

Только окончательное значение представляется правильным. Что не так с этим кодом? Почему он не всегда возвращает значение с плавающей запятой, которое я ожидаю от своей функции? Как я могу исправить этот код?

1 ответ

Основная проблема заключается не в том, что произошел сбой при возврате значения в регистр с плавающей запятой ST0, а в том, как вы пытаетесь загрузить 32-битный параметр (одинарной точности) из стека. Вопрос здесь:

fld dword[esi-8]      ;loads the given value into st0

Это должно читать:

fld dword[esp+4]      ;loads the DWORD parameter from stack into st0

fld dword[esi-8] иногда работает только из-за того, что вызывающая функция использует ESI для внутреннего использования. С различными компиляторами C и включенными оптимизациями вы можете обнаружить, что код не работает вообще.

При использовании 32-битного кода C/C++ параметры передаются в стек справа налево. Когда вы выполняете инструкцию CALL в 32-битном коде, 4-байтовый адрес возврата помещается в стек. Адрес памяти esp+0 будет содержать адрес возврата, и первый параметр будет в esp+4, Если бы у вас был второй параметр, он был бы esp+8, Хорошее описание соглашения о вызовах 32-битного CDECL от Microsoft можно найти в этой записи WikiBook. По важности:

  • Аргументы функции передаются в стеке в порядке справа налево.
  • Результат функции сохраняется в EAX/AX/AL
  • Возвращаемые значения с плавающей точкой будут возвращены в ST0
  • 8-битные и 16-битные целочисленные аргументы преобразуются в 32-битные аргументы.

При работе с инструкциями x87 FPU очень важно, чтобы при возврате FLOAT единственным значением в стеке было значение в ST0. Неспособность освободить (вытолкнуть / освободить) все, что вы положили в стек FPU, может привести к сбою вашей функции при многократном вызове. У стека x87 FPU всего 8 слотов (не очень много). Если вы не очистите стек FPU до возврата из функции, это может привести к переполнению стека FPU, когда будущие инструкции должны загрузить новое значение в стек FPU.

Пример реализации вашей функции мог бы выглядеть так:

use32
section .text
; _arsinh takes a single float (angle) as a parameter
;     angle is at memory location esp+4 on the stack
;     arcsinh(x) = ln(x + sqrt(x^2+1)) 
global _arsinh
_arsinh:
    fldln2           ; st(0) = ln2
    fld dword[esp+4] ; st(0) = angle, st(1)=ln2
    fld st0          ; st(0) = angle, st(1) = angle, st(2)=ln2
    fmul st0         ; st(0) = angle^2, st(1) = angle, st(2)=ln2
    fld1             ; st(0) = 1, st(1) = angle^2, st(2) = angle, st(3)=ln2
    faddp            ; st(0) = 1 + angle^2, st(1) = angle, st(2)=ln2
    fsqrt            ; st(0) = sqrt(1 + angle^2), st(1) = angle, st(2)=ln2
    faddp            ; st(0) = sqrt(1 + angle^2) + angle, st(1)=ln2
    fyl2x            ; st(0) = log2(sqrt(1 + angle^2) + angle)*ln2
                     ; st(0) = asinh(angle)
    ret
Другие вопросы по тегам