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

У меня х86 NASM Программа, которая, кажется, работает отлично. У меня проблемы с использованием значений, возвращаемых из него. Это 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

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

0

Решение

Основная проблема не в том, что произошел сбой при возврате значения в регистр с плавающей запятой 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 внутренне. С разными С Вы можете обнаружить, что код не работает вообще.

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

  • Аргументы функции передаются в стеке в порядке справа налево.
  • Результат функции сохраняется в 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
2

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]