x86 汇编 如何正确地将 xmm0 放入 st0?

问题描述 投票:0回答:1

大家度过了美好的周日。

我目前正在32位环境(当前是Windows)中学习很多汇编。 我为此使用 FASM。

我成功制作了以下代码,但我对将 xmm0 加载到 st0 中的方式非常不满意:

GetDistance: ;(__cdecl*)(float x1, float y1, float x2, float y2)
        push    ebp
        mov     ebp, esp        
        sub     esp, 0x4
        
        movss   xmm0, DWORD [ebp + 0x0014] ; Load x2
        subss   xmm0, DWORD [ebp + 0x000C] ; Subtract x1

        movss   xmm1, DWORD [ebp + 0x0010] ; Load y2
        subss   xmm1, DWORD [ebp + 0x0008]  ; Subtract y1

        mulss   xmm0, xmm0             ; Square of the x difference
        mulss   xmm1, xmm1             ; Square of the y difference

        addss   xmm0, xmm1             ; Sum of squared differences

        sqrtss  xmm0, xmm0             ; Square root
                                 
        movss   dword [ebp - 0x0004], xmm0
        fld     dword [ebp - 0x0004]    
        
        add     esp, 0x4
        
        pop     ebp
        ret     0

它确实有效,但我现在已经在谷歌上搜索了2小时(甚至问chatgpt)如何将我的xmm0值放入st0,但我无法搜索正确的问题,我猜,chatGPT的答案总是会产生编译错误或使我的函数返回'南'。 ChatGPT 将我的简单函数始终转换为可执行主块,该块使用 .data 部分作为全局变量,我认为这导致我走向完全错误的方向。

我不喜欢我必须使用 sub 并添加 esp 才能将 xmm0 放入 st0。

我也很感谢任何改进我的代码的技巧,甚至是从中学习的好资源。 我现在只想关注 32 位。 :)

assembly floating-point sse fasm x87
1个回答
1
投票

从 XMM 传输到

st0
需要存储/重新加载。即使 MMX 寄存器是 x87 寄存器的别名,也无法使用
MOVDQ2Q mm0, xmm0
将 80 位 FP 位模式转换为
st0
,甚至除了在不清除寄存器的情况下从 MMX 切换回 x87 状态的问题之外。

相关:Intel x86_64 组件,如何在 x87 和 SSE2 之间移动? (计算双精度数的反正切)

不过,您不需要浪费指令将 EBP 设置为帧指针,尤其是在像这样的简单函数中,可以很容易地跟踪相对于 ESP 的偏移量。

在具有堆栈参数的函数中,被调用者(您的函数)“拥有”它们,因此您可以使用

[esp+4]
作为暂存空间,而不是保留新空间。这就是为什么使用相同的参数调用同一函数两次,调用者必须再次存储参数。例如

square:                   ; float square(float a); legacy cdecl convention
 movss  xmm0, [esp+4]
 mulss  xmm0, xmm0
 movss  [esp+4], xmm0      ; reuse the incoming arg as scratch space
 fld    dword [esp+4]
 ret

在这种情况下,使用

fld dword [esp+4]
/
fmul st0
/
ret
会更有效,因为我们使用的是在
st0
中返回的调用约定。


如果您坚持使用 32 位代码,那么默认的调用约定又旧又坏,在堆栈上传递参数并在

float
中返回
double
/
st0
,而不是
xmm0

对于 Windows,有不太糟糕的 32 位调用约定。 32 位

vectorcall
传递 xmm 寄存器中的前 6 个 FP(或 SIMD 向量)参数,并在
xmm0
中返回。 regs 中的前 2 个整数参数如
fastcall
。 (64 位向量调用仅在 XMM 寄存器中传递 4 个参数,与标准 Windows x64 约定的不同之处仅在于处理
__m128i
__m256
等类型。)请参阅 https://learn.microsoft.com/en-us/ cpp/cpp/vectorcall?view=msvc-170 了解更多。

float _vectorcall 
 foo(float a, float b, float c, float d, float e, float f, float g, int i){
    return a+b+c+d+e+f+g + i;
}

使用 x86 MSVC 19.10 (Godbolt) 进行编译。这是一个像

fastcall
这样的被调用者流行约定;请注意
ret 4
,因为我们有一个堆栈参数。但是,如果您没有任何堆栈参数,则仅使用普通的
ret
仍然是正确的。

_g$ = 8                                       ; size = 4
float foo(float,float,float,float,float,float,float,int) PROC                                ; foo, COMDAT
        addss   xmm0, xmm1
        movd    xmm1, ecx
        cvtdq2ps xmm1, xmm1          ; avoids a false dependency vs. cvtsi2ss xmm1, ecx which is also 2 uops
        addss   xmm0, xmm2
        addss   xmm0, xmm3
        addss   xmm0, xmm4
        addss   xmm0, xmm5
        addss   xmm0, DWORD PTR _g$[esp-4]   ; 7th FP arg comes from the stack.
                                             ; with _g$ = 8, this is actually [esp+4]
        addss   xmm0, xmm1                   ; +i  converted earlier
        ret     4
float foo(float,float,float,float,float,float,float,int) ENDP                                ; foo

如果你的调用者也是手写的asm,那么你不必遵循标准的调用约定;您可以在方便的寄存器中传递/返回参数,并在每个函数的基础上用注释记录它。


chatGPT 的答案总是会产生编译错误或使我的函数返回

NAN
。 ChatGPT 将我的简单函数始终转换为可执行主块,该块使用 .data 部分作为全局变量,我认为这导致我走向完全错误的方向。

不足为奇; ChatGPT 非常不擅长汇编语言,有错误的代码很正常。 它不“理解”它在任何语言中所做的事情,但 x86 asm 在其训练数据中可能更罕见,并且/或者对于大型语言模型来说更困难,因为所有程序中都使用相同的寄存器名称和助记符。而且汇编语言有很多不同的风格(包括 x86 的多种语言),这可能没有帮助。

© www.soinside.com 2019 - 2024. All rights reserved.