以下函数不能使用64位Delphi XE2编译器进行编译。 (错误都与fld
指令有关。)
[dcc64 Error] Project1.dpr(12): E2116 Invalid combination of opcode and operands
[dcc64 Error] Project1.dpr(13): E2116 Invalid combination of opcode and operands
[dcc64 Error] Project1.dpr(20): E2116 Invalid combination of opcode and operands
第12行& 13:
fld Y
fld X
第20行:
fld X
不幸的是我没有装配技能,而且我正在使用第三方代码,我需要将其移植到64位。你能帮助我使它在32位和64位上工作吗?
function PartArcTan(Y, X: Extended): Extended;
asm
fld Y // st(0) = Y
fld X // st(0) = X
fpatan // st(0) = ArcTan(Y, X)
fwait
end;
function ArcSin(X: Extended): Extended; // -1 <= X <= 1
asm
fld X // st(0) = X
fld st(0) // st(1) = X
fmul st(0), st(0) // st(0) = Sqr(X)
fld1 // st(0) = 1
fsubrp st(1), st(0) // st(0) = 1 - Sqr(X)
fsqrt // st(0) = Sqrt(1 - Sqr(X))
fpatan // st(0) = ArcTan(X, Sqrt(1 - X*X))
fwait
end;
答案 0 :(得分:7)
此代码的主要问题是移植到x64,它使用了错误的浮点单元。在x64上,浮点数在SSE单元上完成。
是的,x87装置仍然存在,但相比之下它很慢。另一个问题是x64 ABI假定您将使用SSE单元。参数到达SSE寄存器。浮点值在SSE寄存器中返回。在SSE和x87单位之间传输值是毫无意义的(更不用说相当辛苦和耗时)。更重要的是,浮点控制,异常掩码是为SSE单元初始化的,但是你确定它们将被正确设置为SSE单元。
因此,鉴于所有这些,我强烈建议您确保所有浮点代码都在x64下的SSE单元上执行。我认为唯一可以使用x87寄存器的情况是一种算法,它需要x87支持的10字节扩展类型而不支持SSE。情况并非如此。
现在,移植到SSE单元并不像将操作码转换为SSE等价物那么简单。那是因为SSE浮动单元的内置功能要少得多。例如,SSE操作码中不包含三角函数。
因此,处理此问题的正确方法是切换到使用Pascal代码。这些功能可分别由Math.ArcTan2
和Math.ArcSin
替换。
详细说明一下,让我们看一下在x64下x87单元上进行计算所涉及的内容。 ArcSin
的代码如下:
function ArcSin(X: Double): Double;
// to be 100% clear, do **not** use this code
asm
movq [rsp-8], xmm0 // X arrives in xmm0, move it to stack memory
fld qword ptr [rsp-8] // now load X into the x87 unit
fld st(0) // calculation code exactly as before
fmul st(0), st(0)
fld1
fsubrp st(1), st(0)
fsqrt
fpatan
fwait
fstp qword ptr [rsp-8] // but now we need to move the return value
movq xmm0, [rsp-8] // back into xmm0, again via the stack
end;
注意事项:
所以,也许这可以作为对希望使用x87在x64下执行浮点运算的未来访问者的警告。
答案 1 :(得分:3)
x64仍支持经典浮点单元,但您需要调整代码以遵循不同的ABI。
x32 / x64例子:
function PartArcTan(X: double): double;
asm
{$IFDEF CPUX64}
movq [rsp-8], xmm0
fld qword ptr [rsp-8]
{$ELSE}
fld qword ptr X
{$ENDIF}
fld1
fpatan
fwait
{$IFDEF CPUX64}
fstp qword ptr [rsp-8]
movq xmm0, [rsp-8]
{$ENDIF}
end;