У меня х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
Только окончательное значение представляется правильным. Что не так с этим кодом? Почему он не всегда возвращает значение с плавающей запятой, которое я ожидаю от своей функции? Как я могу исправить этот код?
Основная проблема не в том, что произошел сбой при возврате значения в регистр с плавающей запятой 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
Других решений пока нет …