Return 来自使用 x87 FPU 的 64 位汇编函数的浮点数
Return a float from a 64-bit assembly function that uses x87 FPU
我正在尝试编写一个程序来计算使用 64 位寄存器、浮点数和协处理器指令的方程式(目前什么方程式并不重要)。不幸的是,我不知道如何以浮点数的形式访问方程式的最终结果。我能做到:
fist qword ptr [bla]
mov rax,bla
并将函数类型更改为 INT 并获取我的值,但我无法将其作为 FLOAT 访问。即使当我将结果留在 ST(0)(协处理器堆栈的顶部)时,它也不会按预期工作并且我的 C++ 程序得到错误的结果。我的汇编代码是:
public funct
.data
bla qword ?
bla2 qword 10.0
.code
funct PROC
push rbp
mov rbp, rsp
push rbx
mov bla,rcx
fild qword ptr[bla]
fld qword ptr [bla2]
fmul st(0), st(1)
fist dword ptr [bla]
pop rbx
pop rbp
ret
funct ENDP
END
我的C++代码是:
#include <stdlib.h>
#include <cstdlib>
#include <stdio.h>
extern "C" float funct(long long n);
int main(){
float value1= funct(3);
return 0;
}
这是什么问题,我该如何解决?
您可以将结果的地址作为参数传递:
main.c:
#include<stdio.h>
extern "C" void funct(long long, float*);
int main ( void )
{
float value1 = 0; // float = DWORD ("double" would be QWORD)!
funct(3, &value1);
printf ("%f\n",value1);
return 0;
}
callee.asm:
.data
bla qword ?
bla2 qword 10.0
.code
funct PROC
push rbp
mov rbp, rsp
push rbx
mov bla,rcx
fild qword ptr[bla] ; -> st(1)
fld qword ptr [bla2] ; -> st(0)
fmul st(0), st(1)
fstp dword ptr [rdx] ; pop the first value
ffree st(0) ; pop the second value
pop rbx
pop rbp
ret
funct ENDP
END
你的问题有点模棱两可,你的代码也是。我将介绍一些使用 x87 FPU 和 SSE 指令的想法。不鼓励在 64 位代码中使用 x87 FPU 指令,首选 SSE/SSE2。 SSE/SSE2 适用于所有 64 位 AMD 和 64 位 Intel x86 处理器。
使用 x87 FPU 的 64 位代码中的 32 位浮点数
如果您的问题是 "How do I write 64-bit assembler code that uses 32-bit floats using the x87 FPU?" 那么您的 C++ 代码看起来不错,但您的汇编代码需要一些工作。您的 C++ 代码表明该函数的输出类型是 32 位浮点数:
extern "C" float funct(long long n);
我们需要创建一个 return 是 32 位浮点数的函数。您的汇编代码可以按以下方式修改。我在你的代码中保留了堆栈帧代码和 RBX 的 push/pop,因为我假设你只是给我们一个最小的例子,而你的真实代码正在使用 RBX。考虑到这一点,以下代码应该可以工作:
public funct
.data
ten REAL4 10.0 ; Define variable ten as 32-bit (4-byte float)
; REAL4 and DWORD are both same size.
; REAL4 makes for more readable code when using floats
.code
funct PROC
push rbp
mov rbp, rsp ; Setup stack frame
; RSP aligned to 16 bytes at this point
push rbx
mov [rbp+16],rcx ; 32 byte shadow space is just above the return address
; at RBP+16 (this address is 16 byte aligned). Rather
; than use a temporary variable in the data section to
; store the value of RCX, we just store it to the
; shadow space on the stack.
fild QWORD ptr[rbp+16] ; Load and convert 64-bit integer into st(0)
fld [ten] ; st(0) => st(1), st(0) = 10.0
fmulp ; st(1)=st(1)*st(0), st(1) => st(0)
fstp REAL4 ptr [rbp+16] ; Store result to shadow space as 32-bit float
movss xmm0, REAL4 ptr [rbp+16] ; Store single scalar (32-bit float) to xmm0
; XMM0 = return value for 32(and 64-bit) floats
; in 64-bit code.
pop rbx
mov rsp, rbp ; Remove stack frame
pop rbp
ret
funct ENDP
END
我已经对代码进行了注释,但可能感兴趣的是我没有在 DATA 部分中使用第二个变量。 64 位 Windows 调用约定要求函数的调用者确保堆栈在 16 字节边界上对齐,并且有一个 32 字节 shadow space(又名 register 参数area) 在调用之前分配。该区域可用作草稿区。由于我们设置了栈帧,RBP在RBP+0
,return地址在RBP+8
,暂存区从[=20开始=].如果你没有使用堆栈帧,那么 return 地址在 RSP+0
,影子 space 将从 RSP+8
开始 我们可以存储浮点数的结果操作在那里而不是在 QWORD 你标记为 bla.
展开浮点堆栈是一个合理的想法,这样在我们退出我们的函数之前就不会在上面留下任何东西。我使用 FPU 浮点函数,在我们使用完它们后弹出寄存器。
64-bit Microsoft calling convention requires floating point values to be returned in XMM0。我们使用 SSE 指令 MOVSS 将标量单个(32 位浮点数)移动到 XMM0登记。这就是 C++ 代码期望该值被 returned.
的地方
使用 SSE 的 64 位代码中的 32 位浮点数
基于上一节的想法,我们可以修改代码以使用具有 32 位浮点数的 SSE 指令。此类代码的示例如下:
public funct
.data
ten REAL4 10.0 ; Define variable ten as 32-bit (4-byte float)
; REAL4 and DWORD are both same size.
; REAL4 makes for more readable code when using floats
.code
funct PROC
push rbp
mov rbp, rsp ; Setup stack frame
; RSP aligned to 16 bytes at this point
push rbx
cvtsi2ss xmm0, rcx ; Convert scalar integer in RCX to
; scalar single(float) and store in XMM0
mulss xmm0, [ten] ; 32-bit float multiply by 10.0 store in XMM0
; XMM0 = return value for 32(and 64-bit) floats
; in 64-bit code.
pop rbx
mov rsp, rbp ; Remove stack frame
pop rbp
ret
funct ENDP
END
此代码通过使用 SSE 指令删除了 x87 FPU 的使用。我们特别使用:
cvtsi2ss xmm0, rcx ; Convert scalar integer in RCX to
; scalar single(float) and store in XMM0
CVTSI2SS converts a scalar integer to a scalar single (float). In this case the 64-bit integer value in RCX is converted to a 32-bit float and stored in XMM0. XMM0 is the register we'll be placing our returned value into. XMM0 to XMM5 are considered volatile 所以我们不需要保存它们的值。
mulss xmm0, [ten] ; 32-bit float multiply by 10.0 store in XMM0
; XMM0 = return value for 32(and 64-bit) floats
; in 64-bit code.
MULSS 是一个 SSE 指令,用于使用标量单个(浮点数)的 SSE 乘法。在这种情况下 MULSS 会执行 XMM0=XMM0*(32 位浮点内存操作数)。这将产生将 XMM0 乘以 10.0 的 32 位浮点数的 32 位浮点数的效果。由于 XMM0 也包含我们的最终结果,我们除了正确退出函数外别无他法。
使用 x87 FPU 的 64 位代码中的 64 位双浮点数
这是第一个示例的变体,但现在我们使用的是 64 位浮点数,在 C++、[=24= 中也称为 double
类型](或 QWORD
)在汇编程序中,scalar double
在 SSE2 中。由于我们现在使用 double
作为 return 类型,我们必须将 C++ 代码修改为:
#include <stdlib.h>
#include <cstdlib>
#include <stdio.h>
extern "C" double funct(long long n);
int main() {
double value1 = funct(3);
return 0;
}
汇编代码如下所示:
public funct
.data
ten REAL8 10.0 ; Define variable ten as 64-bit (8-byte float)
; REAL8 and QWORD are both same size.
; REAL8 makes for more readable code when using floats
.code
funct PROC
push rbp
mov rbp, rsp ; Setup stack frame
; RSP aligned to 16 bytes at this point
push rbx
mov [rbp+16],rcx ; 32 byte shadow space is just above the return address
; at RBP+8 (this address is 16 byte aligned). Rather
; than use a temporary variable in the data section to
; store the value of RCX, we just store it to the
; shadow space on the stack.
fild QWORD ptr[rbp+16] ; Load and convert 64-bit integer into st(0)
fld [ten] ; st(0) => st(1), st(0) = 10.0
fmulp ; st(1)=st(1)*st(0), st(1) => st(0)
fstp REAL8 ptr [rbp+16] ; Store result to shadow space as 64-bit float
movsd xmm0, REAL8 ptr [rbp+16] ; Store double scalar (64-bit float) to xmm0
; XMM0 = return value for 32(and 64-bit) floats
; in 64-bit code.
pop rbx
mov rsp, rbp ; Remove stack frame
pop rbp
ret
funct ENDP
END
此代码与使用 32 位浮点数的 x87 代码几乎相同。我们正在使用 REAL8(与 QWORD 相同)存储 64 位浮点数并使用 MOVSD 移动 64 位双精度数浮动(双精度标量)到 XMM0。 MOVSD 是一条 SSE2 指令。在 XMM0 中 return 适当的大小浮动很重要。如果您使用 MOVSS 值 returned 到 C++ 函数可能不正确。
使用 SSE2 的 64 位代码中的 64 位双浮点数
这是第二个示例的变体,但现在我们使用的是 64 位浮点数,在 C++、[=24= 中也称为 double
类型](或 QWORD
)在汇编程序中,scalar double
在 SSE2 中。 C++ 代码应使用上一节中的代码,以便使用 double 而不是 float。汇编代码类似于:
public funct
.data
ten REAL8 10.0 ; Define variable ten as 64-bit (8-byte float)
; REAL8 and QWORD are both same size.
; REAL8 makes for more readable code when using floats
.code
funct PROC
push rbp
mov rbp, rsp ; Setup stack frame
; RSP aligned to 16 bytes at this point
push rbx
cvtsi2sd xmm0, rcx ; Convert scalar integer in RCX to
; scalar double(double float) and store in XMM0
mulsd xmm0, [ten] ; 64-bit float multiply by 10.0 store in XMM0
; XMM0 = return value for 32(and 64-bit) floats
; in 64-bit code.
pop rbx
mov rsp, rbp ; Remove stack frame
pop rbp
ret
funct ENDP
END
与第二个示例的主要区别在于我们使用 CVTSI2SD instead of CVTSI2SS. SD in the instruction means we are converting to a scalar double (64-bit double float). Similarly we use the MULSD 指令使用标量双精度进行乘法运算。 XMM0 将保存将 returned 到调用函数的 64 位双精度标量(双浮点数)。
我正在尝试编写一个程序来计算使用 64 位寄存器、浮点数和协处理器指令的方程式(目前什么方程式并不重要)。不幸的是,我不知道如何以浮点数的形式访问方程式的最终结果。我能做到:
fist qword ptr [bla]
mov rax,bla
并将函数类型更改为 INT 并获取我的值,但我无法将其作为 FLOAT 访问。即使当我将结果留在 ST(0)(协处理器堆栈的顶部)时,它也不会按预期工作并且我的 C++ 程序得到错误的结果。我的汇编代码是:
public funct
.data
bla qword ?
bla2 qword 10.0
.code
funct PROC
push rbp
mov rbp, rsp
push rbx
mov bla,rcx
fild qword ptr[bla]
fld qword ptr [bla2]
fmul st(0), st(1)
fist dword ptr [bla]
pop rbx
pop rbp
ret
funct ENDP
END
我的C++代码是:
#include <stdlib.h>
#include <cstdlib>
#include <stdio.h>
extern "C" float funct(long long n);
int main(){
float value1= funct(3);
return 0;
}
这是什么问题,我该如何解决?
您可以将结果的地址作为参数传递:
main.c:
#include<stdio.h>
extern "C" void funct(long long, float*);
int main ( void )
{
float value1 = 0; // float = DWORD ("double" would be QWORD)!
funct(3, &value1);
printf ("%f\n",value1);
return 0;
}
callee.asm:
.data
bla qword ?
bla2 qword 10.0
.code
funct PROC
push rbp
mov rbp, rsp
push rbx
mov bla,rcx
fild qword ptr[bla] ; -> st(1)
fld qword ptr [bla2] ; -> st(0)
fmul st(0), st(1)
fstp dword ptr [rdx] ; pop the first value
ffree st(0) ; pop the second value
pop rbx
pop rbp
ret
funct ENDP
END
你的问题有点模棱两可,你的代码也是。我将介绍一些使用 x87 FPU 和 SSE 指令的想法。不鼓励在 64 位代码中使用 x87 FPU 指令,首选 SSE/SSE2。 SSE/SSE2 适用于所有 64 位 AMD 和 64 位 Intel x86 处理器。
使用 x87 FPU 的 64 位代码中的 32 位浮点数
如果您的问题是 "How do I write 64-bit assembler code that uses 32-bit floats using the x87 FPU?" 那么您的 C++ 代码看起来不错,但您的汇编代码需要一些工作。您的 C++ 代码表明该函数的输出类型是 32 位浮点数:
extern "C" float funct(long long n);
我们需要创建一个 return 是 32 位浮点数的函数。您的汇编代码可以按以下方式修改。我在你的代码中保留了堆栈帧代码和 RBX 的 push/pop,因为我假设你只是给我们一个最小的例子,而你的真实代码正在使用 RBX。考虑到这一点,以下代码应该可以工作:
public funct
.data
ten REAL4 10.0 ; Define variable ten as 32-bit (4-byte float)
; REAL4 and DWORD are both same size.
; REAL4 makes for more readable code when using floats
.code
funct PROC
push rbp
mov rbp, rsp ; Setup stack frame
; RSP aligned to 16 bytes at this point
push rbx
mov [rbp+16],rcx ; 32 byte shadow space is just above the return address
; at RBP+16 (this address is 16 byte aligned). Rather
; than use a temporary variable in the data section to
; store the value of RCX, we just store it to the
; shadow space on the stack.
fild QWORD ptr[rbp+16] ; Load and convert 64-bit integer into st(0)
fld [ten] ; st(0) => st(1), st(0) = 10.0
fmulp ; st(1)=st(1)*st(0), st(1) => st(0)
fstp REAL4 ptr [rbp+16] ; Store result to shadow space as 32-bit float
movss xmm0, REAL4 ptr [rbp+16] ; Store single scalar (32-bit float) to xmm0
; XMM0 = return value for 32(and 64-bit) floats
; in 64-bit code.
pop rbx
mov rsp, rbp ; Remove stack frame
pop rbp
ret
funct ENDP
END
我已经对代码进行了注释,但可能感兴趣的是我没有在 DATA 部分中使用第二个变量。 64 位 Windows 调用约定要求函数的调用者确保堆栈在 16 字节边界上对齐,并且有一个 32 字节 shadow space(又名 register 参数area) 在调用之前分配。该区域可用作草稿区。由于我们设置了栈帧,RBP在RBP+0
,return地址在RBP+8
,暂存区从[=20开始=].如果你没有使用堆栈帧,那么 return 地址在 RSP+0
,影子 space 将从 RSP+8
开始 我们可以存储浮点数的结果操作在那里而不是在 QWORD 你标记为 bla.
展开浮点堆栈是一个合理的想法,这样在我们退出我们的函数之前就不会在上面留下任何东西。我使用 FPU 浮点函数,在我们使用完它们后弹出寄存器。
64-bit Microsoft calling convention requires floating point values to be returned in XMM0。我们使用 SSE 指令 MOVSS 将标量单个(32 位浮点数)移动到 XMM0登记。这就是 C++ 代码期望该值被 returned.
的地方使用 SSE 的 64 位代码中的 32 位浮点数
基于上一节的想法,我们可以修改代码以使用具有 32 位浮点数的 SSE 指令。此类代码的示例如下:
public funct
.data
ten REAL4 10.0 ; Define variable ten as 32-bit (4-byte float)
; REAL4 and DWORD are both same size.
; REAL4 makes for more readable code when using floats
.code
funct PROC
push rbp
mov rbp, rsp ; Setup stack frame
; RSP aligned to 16 bytes at this point
push rbx
cvtsi2ss xmm0, rcx ; Convert scalar integer in RCX to
; scalar single(float) and store in XMM0
mulss xmm0, [ten] ; 32-bit float multiply by 10.0 store in XMM0
; XMM0 = return value for 32(and 64-bit) floats
; in 64-bit code.
pop rbx
mov rsp, rbp ; Remove stack frame
pop rbp
ret
funct ENDP
END
此代码通过使用 SSE 指令删除了 x87 FPU 的使用。我们特别使用:
cvtsi2ss xmm0, rcx ; Convert scalar integer in RCX to
; scalar single(float) and store in XMM0
CVTSI2SS converts a scalar integer to a scalar single (float). In this case the 64-bit integer value in RCX is converted to a 32-bit float and stored in XMM0. XMM0 is the register we'll be placing our returned value into. XMM0 to XMM5 are considered volatile 所以我们不需要保存它们的值。
mulss xmm0, [ten] ; 32-bit float multiply by 10.0 store in XMM0
; XMM0 = return value for 32(and 64-bit) floats
; in 64-bit code.
MULSS 是一个 SSE 指令,用于使用标量单个(浮点数)的 SSE 乘法。在这种情况下 MULSS 会执行 XMM0=XMM0*(32 位浮点内存操作数)。这将产生将 XMM0 乘以 10.0 的 32 位浮点数的 32 位浮点数的效果。由于 XMM0 也包含我们的最终结果,我们除了正确退出函数外别无他法。
使用 x87 FPU 的 64 位代码中的 64 位双浮点数
这是第一个示例的变体,但现在我们使用的是 64 位浮点数,在 C++、[=24= 中也称为 double
类型](或 QWORD
)在汇编程序中,scalar double
在 SSE2 中。由于我们现在使用 double
作为 return 类型,我们必须将 C++ 代码修改为:
#include <stdlib.h>
#include <cstdlib>
#include <stdio.h>
extern "C" double funct(long long n);
int main() {
double value1 = funct(3);
return 0;
}
汇编代码如下所示:
public funct
.data
ten REAL8 10.0 ; Define variable ten as 64-bit (8-byte float)
; REAL8 and QWORD are both same size.
; REAL8 makes for more readable code when using floats
.code
funct PROC
push rbp
mov rbp, rsp ; Setup stack frame
; RSP aligned to 16 bytes at this point
push rbx
mov [rbp+16],rcx ; 32 byte shadow space is just above the return address
; at RBP+8 (this address is 16 byte aligned). Rather
; than use a temporary variable in the data section to
; store the value of RCX, we just store it to the
; shadow space on the stack.
fild QWORD ptr[rbp+16] ; Load and convert 64-bit integer into st(0)
fld [ten] ; st(0) => st(1), st(0) = 10.0
fmulp ; st(1)=st(1)*st(0), st(1) => st(0)
fstp REAL8 ptr [rbp+16] ; Store result to shadow space as 64-bit float
movsd xmm0, REAL8 ptr [rbp+16] ; Store double scalar (64-bit float) to xmm0
; XMM0 = return value for 32(and 64-bit) floats
; in 64-bit code.
pop rbx
mov rsp, rbp ; Remove stack frame
pop rbp
ret
funct ENDP
END
此代码与使用 32 位浮点数的 x87 代码几乎相同。我们正在使用 REAL8(与 QWORD 相同)存储 64 位浮点数并使用 MOVSD 移动 64 位双精度数浮动(双精度标量)到 XMM0。 MOVSD 是一条 SSE2 指令。在 XMM0 中 return 适当的大小浮动很重要。如果您使用 MOVSS 值 returned 到 C++ 函数可能不正确。
使用 SSE2 的 64 位代码中的 64 位双浮点数
这是第二个示例的变体,但现在我们使用的是 64 位浮点数,在 C++、[=24= 中也称为 double
类型](或 QWORD
)在汇编程序中,scalar double
在 SSE2 中。 C++ 代码应使用上一节中的代码,以便使用 double 而不是 float。汇编代码类似于:
public funct
.data
ten REAL8 10.0 ; Define variable ten as 64-bit (8-byte float)
; REAL8 and QWORD are both same size.
; REAL8 makes for more readable code when using floats
.code
funct PROC
push rbp
mov rbp, rsp ; Setup stack frame
; RSP aligned to 16 bytes at this point
push rbx
cvtsi2sd xmm0, rcx ; Convert scalar integer in RCX to
; scalar double(double float) and store in XMM0
mulsd xmm0, [ten] ; 64-bit float multiply by 10.0 store in XMM0
; XMM0 = return value for 32(and 64-bit) floats
; in 64-bit code.
pop rbx
mov rsp, rbp ; Remove stack frame
pop rbp
ret
funct ENDP
END
与第二个示例的主要区别在于我们使用 CVTSI2SD instead of CVTSI2SS. SD in the instruction means we are converting to a scalar double (64-bit double float). Similarly we use the MULSD 指令使用标量双精度进行乘法运算。 XMM0 将保存将 returned 到调用函数的 64 位双精度标量(双浮点数)。