assembly 如何将旧的x87汇编代码转换为扩展asm(具有“=u”和“=t”约束)以转换球坐标

ojsjcaue  于 2023-03-18  发布在  其他
关注(0)|答案(1)|浏览(119)

我有这个旧代码转换球面到笛卡尔三维坐标:

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);
}

我找到了SinCos的样本:

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

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

nzk0hqpo

nzk0hqpo1#

https://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html-“=t”表示输出选取栈顶寄存器st(0)。“0”选取与操作数0相同的位置,这是“匹配约束”,因此st(0)也是如此,这是有意义的,因为这是fsincos的输入。“=u”是另一个输出st(1),这并不奇怪。
IDK为什么要使用内联asm,而不是让编译器从<math.h>/<cmath>优化sincos()(GNU扩展);数学库函数调用是好的,并且可能比x87指令快。如果在循环中调用Cartesian3D,甚至可能自动矢量化,产生2或4个结果,工作量与一个结果大致相同。
还有,为什么要用double by const引用呢?它足够小,可以通过值传递。顺便说一句,在现代代码中使用x87的主要原因是为了80位扩展精度。如果你需要的话,仍然只需要使用sincosl。GCC可能会将它内联到fsincos指令中,或者可能会调用一个库函数,这可能会更快;https://agner.org/optimize乘以fsincos,微指令数为60-120,周期延迟为60-140(奇怪的是,没有报告吞吐量)。
此外,如果您坚持使用asm来强制它运行x87 fsincos,则不需要将其转换为一位asm()语句,您可以称之为工作SinCos()两次,对于两个独立的输入,编译器将处理加载/存储和fxchg。嗯,我认为您不需要,但GCC和clang在64位模式下做得相当差,希望将数据弹回XMM寄存器以进行乘法运算。https://godbolt.org/z/1j9df4cro,无论是按值传递(如果不是内联,则强制它首先将XMM寄存器溢出到内存)还是按引用传递。
即使在32位模式下,我想它也会自动向量化乘法,希望使用mulpdhttps://godbolt.org/z/sTd43zono显示GCC -m32 -O3 -mfpmath=387 -fno-tree-vectorize使用 Package 函数生成高效的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 inline asm可以高效地 Package 一条指令,并告诉编译器在哪些寄存器中放置输入和查找输出。没有开销,因此只要编译器可以在内联的asm("":::)语句之间生成高效的代码,使用一个更复杂的asm语句就没有好处。
对于没有自动矢量化的32位模式是这样的,但对于64位模式则不是这样(除非您也使用-mfpmath=387作为编译单元!Godbolt)

相关问题