我在Visual Studio环境中学习内联汇编程序。 所以,我正在实现一个简单的点积函数,我似乎无法找到一个返回浮点结果的正确方法。
float dot(vec3 &a,vec3 &b)
{
float result;
float *p_result=&result;
_asm
{
mov eax,dword ptr a
mov ebx,dword ptr b
movups xmm0,[eax]
movups xmm1,[ebx]
mulps xmm0,xmm1
movaps xmm1,xmm0
shufps xmm1,xmm1,0b1h
addps xmm1,xmm0
movaps xmm2,xmm1
shufps xmm2,xmm2,02h
addps xmm2,xmm1
mov eax,dword ptr p_result
movss [eax],xmm2
}
return result;
}
有没有办法可以在我的函数中传递float
结果和float *p_result
的声明?
答案 0 :(得分:1)
首先:如果可以使用EAX,ECX或EDX,请不要使用EBX寄存器。根据{{3}},是保存了EBX,ESI和EDI的被调用者,即该函数必须将它们保持不变。 Visual Studio会在必要时管理这些寄存器的存储和恢复,但这是不必要的。
您不需要指向结果的指针。内联汇编器可以直接访问局部变量。此外,如果汇编程序可以识别适当的大小,则不需要大小指令(DWORD PTR
)。
float dot(vec3 &a,vec3 &b) // no ebx, no pointer, no size directive
{
float result;
_asm
{
mov eax,a
mov edx,b
movups xmm0,[eax]
movups xmm1,[edx]
mulps xmm0,xmm1
movaps xmm1,xmm0
shufps xmm1,xmm1,0b1h
addps xmm1,xmm0
movaps xmm2,xmm1
shufps xmm2,xmm2,02h
addps xmm2,xmm1
movss result,xmm2
}
return result;
}
如果您自己指定返回值,则可以省略返回行。最终您会收到警告,但是该函数将正确返回。如果返回值为浮点型,则必须位于FPU的ST(0)中。
float dot(vec3 &a,vec3 &b) // omit return, set ST(0) manually
{
float result;
_asm
{
mov eax,a
mov edx,b
movups xmm0,[eax]
movups xmm1,[edx]
mulps xmm0,xmm1
movaps xmm1,xmm0
shufps xmm1,xmm1,0b1h
addps xmm1,xmm0
movaps xmm2,xmm1
shufps xmm2,xmm2,02h
addps xmm2,xmm1
movss result,xmm2
fld result
}
}
编译器会在函数的开头和结尾生成其他代码,称为“序言”和“结尾”。您可以绕开尾声(leave
-ret
),但这非常脏,因为您不知道序言到底做了什么。要绕过序言和结尾,都将函数声明为cdecl calling convention,并使用ESP
作为基本指针。仍然需要内存才能将XMM的结果传输到FPU。我使用传递的变量中的第一个-不再需要它。
__declspec(naked) float dot(vec3 &a,vec3 &b) // naked
{
_asm
{
mov eax,[esp+4] ; a
mov edx,[esp+8] ; b
movups xmm0,[eax]
movups xmm1,[edx]
mulps xmm0,xmm1
movaps xmm1,xmm0
shufps xmm1,xmm1,0b1h
addps xmm1,xmm0
movaps xmm2,xmm1
shufps xmm2,xmm2,02h
addps xmm2,xmm1
movss [esp+4],xmm2 ; a
fld [esp+4] ; Result in ST(0)
ret
}
}
使用naked
消除了最后剩下的堆栈开销。现在,参数已在ECX和EDX寄存器中传递。但是这样您就没有更多的存储空间可以存储结果了。我的建议:为此使用全局变量。
float dummy; // global
__declspec(naked) float __fastcall dot(vec3 &a,vec3 &b) // fastcall, global variable
{
_asm
{
movups xmm0,[ecx] ; a
movups xmm1,[edx] ; b
mulps xmm0,xmm1
movaps xmm1,xmm0
shufps xmm1,xmm1,0b1h
addps xmm1,xmm0
movaps xmm2,xmm1
shufps xmm2,xmm2,02h
addps xmm2,xmm1
sub esp, 4
movss [dummy],xmm2
fld [dummy]
add esp, 4
ret
}
}
有些人不喜欢全局变量,我不确定,如果在使用线程时遇到麻烦。使用堆栈有点麻烦,因为Windows不知道像Linux这样的红色区域。
__declspec(naked) float __fastcall dot(vec3 &a,vec3 &b) // fastcall, stack access
{
_asm
{
movups xmm0,[ecx] ; a
movups xmm1,[edx] ; b
mulps xmm0,xmm1
movaps xmm1,xmm0
shufps xmm1,xmm1,0b1h
addps xmm1,xmm0
movaps xmm2,xmm1
shufps xmm2,xmm2,02h
addps xmm2,xmm1
sub esp, 4
movss [esp],xmm2
fld [esp]
add esp, 4
ret
}
}
为什么不使用宏?为什么不对齐向量以使用更快的指令(例如MOVAPS
)?这是一个显示将汇编块用作宏的程序:
#include <windows.h>
#include <stdio.h>
typedef __declspec(align(16)) float vec3[4]; // https://docs.microsoft.com/cpp/cpp/align-cpp#vclrf_declspecaligntypedef
#define dotproduct(dest,a,b) __asm \
{ \
__asm movaps xmm0,[a] \
__asm movaps xmm1,[b] \
__asm mulps xmm0,xmm1 \
__asm movaps xmm1,xmm0 \
__asm shufps xmm1,xmm1,0b1h \
__asm addps xmm1,xmm0 \
__asm movaps xmm2,xmm1 \
__asm shufps xmm2,xmm2,02h \
__asm addps xmm2,xmm1 \
__asm movss [dest],xmm2 \
}
int main ( void )
{
vec3 f1 = {1.0,2.0,3.0};
vec3 f2 = {5.0,6.0,8.0};
float R;
dotproduct (R,f1,f2)
printf ("%f\n",R);
return 0;
}