我正在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
的声明?
首先:如果可以使用EAX,ECX或EDX,请不要使用EBX寄存器。根据cdecl calling convention,有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
),但这非常脏,因为您不完全了解序言所做的事情。要绕开序言和结尾,都将函数声明为naked
,并使用naked
作为基本指针。仍然需要内存才能将XMM的结果传输到FPU。我为此使用了第一个传递的变量-不再需要它。
ESP
使用
__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 } }
,您消除了最后剩余的堆栈开销。现在,参数已在ECX和EDX寄存器中传递。但是这样您就没有更多的存储空间了。我的建议:为此使用全局变量。
__fastcall calling convention有些人不喜欢全局变量,我不确定,如果在使用线程时遇到麻烦。使用堆栈有点麻烦,因为Windows不知道像Linux这样的红色区域。
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 } }
为什么不使用宏?为什么不对齐向量以使用更快的指令(例如
__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