您的问题有点模棱两可,您的代码也是如此。我将介绍一些使用 x87 FPU 和 SSE 指令的想法。不鼓励在 64 位代码中使用 x87 FPU 指令,首选 SSE/SSE2。 SSE/SSE2 适用于所有 64 位 AMD 和 64 位 Intel x86 处理器。
使用 x87 FPU 的 64 位代码中的 32 位浮点数
如果您的问题是“如何使用 x87 FPU 编写使用 32 位浮点数的 64 位汇编代码?” 那么您的 C++ 代码看起来不错,但是您的汇编代码需要一些工作。您的 C++ 代码表明函数的输出类型是 32 位浮点数:
extern "C" float funct(long long n);
我们需要创建一个返回 32 位浮点数的函数。您的汇编代码可以按以下方式修改。我在您的代码中保留堆栈帧代码和 RBX 的推送/弹出,因为我假设您只是给我们一个最小的示例,并且您的真实代码正在使用 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(AKA 寄存器参数区域)打电话。该区域可用作划痕区域。由于我们建立了一个栈帧,RBP在RBP+0,返回地址在RBP+8,暂存区从RBP+16开始。如果您没有使用堆栈帧,则返回地址为RSP+0,影子空间将从RSP+8 开始我们可以将浮点运算的结果存储在那里而不是 QWORD em> 你标记为 bla。
展开浮点堆栈是一个合理的想法,因此在我们退出函数之前它上面没有任何东西。我使用 FPU 浮点函数在我们使用完寄存器后弹出寄存器。
64-bit Microsoft calling convention 要求浮点值为returned in XMM0。我们使用 SSE 指令 MOVSS 将标量单个(32 位浮点数)移动到 XMM0 寄存器。这就是 C++ 代码期望返回该值的地方。
使用 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 将标量整数转换为单标量(浮点数)。在这种情况下,RCX 中的 64 位整数值被转换为 32 位浮点数并存储在 XMM0 中。 XMM0 是将返回值放入的寄存器。 XMM0 到 XMM5 被视为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++ 中的 double 类型,汇编程序中的 REAL8(或 QWORD) ,以及 SSE2 中的 scalar double。由于我们现在使用 double 作为返回类型,我们必须将 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 中返回适当大小的浮点数很重要。如果您使用 MOVSS,则返回给 C++ 函数的值可能不正确。
使用 SSE2 的 64 位代码中的 64 位双浮点
这是第二个示例的变体,但现在我们使用 64 位浮点数,也称为 C++ 中的 double 类型,汇编程序中的 REAL8(或 QWORD) ,以及 SSE2 中的 scalar double。 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 而不是CVTSI2SS。指令中的 SD 表示我们正在转换为双精度标量(64 位双精度浮点数)。类似地,我们使用 MULSD 指令使用标量双精度进行乘法运算。 XMM0 将保存将返回给调用函数的 64 位双精度标量(双浮点)。