How to convert old x87 assembly code to extended asm (with "=u" and "=t" constraints) to convert spherical coordinates

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

我有这个旧代码可以将球面坐标转换为笛卡尔 3D 坐标:

TDVector3D Cartesian3D_asm(const double &Theta, const double &Phi)
{
  TDVector3D V;
  __asm__
  {
    mov    eax,[ebp+0x0C]
    mov    edx,[ebp+0x10]
    fld     qword ptr [eax]  // ST0=T     Theta
    fsincos                  // ST1=sin(T)  ST0=cos(T)
    fxch    ST(1)            // ST1=cos(T)  ST0=sin(T)
    fld     qword ptr [edx]  // ST2=cos(T)  ST1=sin(T)  ST0=P   Phi
    fsincos                  // ST3=cos(T)  ST2=sin(T)  ST1=sin(P) ST0=cos(P)
    fmul    ST,ST(2)         // ST3=cos(T)  ST2=sin(T)  ST1=sin(P) ST0=cos(P)*sin(T)
    fstp    qword ptr V.X    // ST2=cos(T)  ST1=sin(T)  ST0=sin(P)

    fmulp   ST(1),ST         // ST1=cos(T)  ST0=sin(P)*sin(T)
    fstp    qword ptr V.Y    // ST0=cos(T)

    fstp    qword ptr V.Z    // Coprocesseur vide
    fwait
  }
  return V;
}

这个 TDVector3D 结构:

typedef struct TDVector3D {
        double X, Y, Z;
        TDVector3D(double x, double y, double z): X(x), Y(y), Z(z) { }
} TDVector3D;

无汇编代码是:

TDVector3D Cartesian3D(const double &Theta, const double &Phi)
{
  double X, Y, Z;
  X = Y = sin(Theta);
  X *= cos(Phi);
  Y *= sin(Phi);
  Z = cos(Theta);
  return TDVector3D(X, Y, Z);
}

我为正余弦找到了这个样本:

void SinCos(double Theta, double *sinT, double *cosT)
{
    __asm__ ("fsincos" : "=t" (*cosT), "=u" (*sinT) : "0" (Theta));
}

我尝试转换我的旧代码,但我完全迷失了“u”、“0”、“t”(谁是关于 ST0、ST1 ......)。

c++ assembly inline-assembly cartesian x87
1个回答
0
投票

https://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html - “=t”表示输出选择栈顶寄存器 st(0)。 “0”选择与操作数 0 相同的位置,一个“匹配约束”,st(0) 也是如此,这是有道理的,因为这是 fsincos 的输入。 “=u”不出所料是 st(1),另一个输出。

IDK 为什么你想要使用内联汇编,而不是让编译器从

sincos()
/
<math.h>
优化
<cmath>
(GNU 扩展);数学库函数调用很好,而且可能比 x87 指令更快。如果在循环中调用
Cartesian3D
,甚至可以自动向量化,产生 2 或 4 个结果,而工作量与一个结果大致相同。

另外,为什么要通过 const 引用取一个 double?它足够小,可以按值传递。顺便说一句,在现代代码中使用 x87 的主要原因是为了 80 位扩展精度。如果你需要,仍然只使用 sincosl。 GCC 可能会将其内联为

fsincos
指令,或者可能会调用库函数,这可能仍然更快; https://agner.org/optimize 以 60-120 uops 乘以 fsincos,具有 60-140 周期延迟(奇怪的是没有报告吞吐量。)

此外,如果您确实坚持使用 asm 强制它运行 x87 fsincos,则无需将其转换为一位 asm() 语句,您只需调用该工作 SinCos() 两次,用于两个单独的输入,编译器将负责加载/存储和 fxchg。好吧,我认为您不需要,但是 GCC 和 clang 在 64 位模式下做得相当糟糕,想要将数据弹回 XMM 寄存器以进行乘法运算。 https://godbolt.org/z/1j9df4cro 是按值传递(如果它不内联,则强制它首先将 XMM 寄存器溢出到内存中)还是按引用传递。

即使在 32 位模式下,它也会自动向量化我认为的乘法,想要使用

mulpd
https://godbolt.org/z/sTd43zono 显示 GCC
-m32 -O3 -mfpmath=387 -fno-tree-vectorize
使用包装函数制作高效的 asm,指令数量与内联
asm{}
块大致相同。

static inline void SinCos_x87(double Theta, double *sinT, double *cosT)
{
    __asm__ ("fsincos" : "=t" (*cosT), "=u" (*sinT) : "0" (Theta));
}

#if 1
 #define SINCOS SinCos_x87
#else
 #include <math.h>
 #define SINCOS sincos
#endif

TDVector3D Cartesian3D_sincos(const double Theta, const double Phi)
{
    double sinTheta, cosTheta, sinPhi, cosPhi;
    SINCOS(Theta, &sinTheta, &cosTheta);
    SINCOS(Phi, &sinPhi, &cosPhi);
    double X = sinTheta * cosPhi;
    double Y = sinTheta * sinPhi;
    double Z = cosTheta;
    return TDVector3D(X, Y, Z);
}

不同于 MSVC 低效的

asm{}
块样式,GNU C 内联 asm 可以有效地包装单个指令,并告诉编译器放置输入和查找输出的寄存器。没有开销,所以只要编译器可以在内联
asm("":::)
语句之间生成高效的代码,拥有一个更复杂的
asm
语句就没有任何好处。

没有自动矢量化的 32 位模式就是这种情况,但 64 位模式则不然(除非您还使用

-mfpmath=387
作为该编译单元!Godbolt

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