汇编代码 fsqrt 和 fmul 指令

-6 c x86 assembly gcc inline-assembly

我正在尝试1.34 *sqrt(lght)使用内联汇编在此函数中进行计算,但出现以下错误:

'_asm' 未声明(首次在此函数中使用)每个未声明的标识符仅针对它出现在预期中的每个函数报告一次 ';' 在“{”标记之前


我一直在研究如何解决这个问题,但找不到太多信息。有人可以建议一种方法来让它发挥作用吗?

我的代码是:

   double hullSpeed(double lgth) {
       _asm {
           global _start
           fld lght; //load lght
           fld st(0); //duplicate lght on Top of stack
           fsqrt;
           square root of lght
           fld st(0); //load square result on top of stack
           fld 1.34; //load 1.34 on top of stack
           fld st(i);
           duplicate 1.34 on TOS
           fmulp st(0), st(i); //multiply them 
           fst z;
           save result in z
       }
       return z; // return result of [ 1.34 *sqrt(lght) ]
   }
Run Code Online (Sandbox Code Playgroud)

我使用 Ubuntu 所以 GCC,32 位

Mic*_*tch 5

看起来你正在尝试做类似的事情:

#include <stdio.h>

double hullSpeed(double lgth)
{
    double result;

    __asm__(
            "fldl %1\n\t" //st(0)=>st(1), st(0)=lgth . FLDL means load double float
            "fsqrt\n\t"   //st(0) = square root st(0)
            "fmulp\n\t"   //Multiplies st(0) and st(1) (1.34). Result in st(0)
            : "=&t" (result) : "m" (lgth), "0" (1.34));

    return result;
}

int main()
{
    printf ("%f\n", hullSpeed(64.0));
}
Run Code Online (Sandbox Code Playgroud)

我使用的模板可以简化,但用于演示目的就足够了。我们使用"=&t"约束,因为我们在 中返回浮点堆栈顶部的结果st(0),并且我们使用&符号来表示早期的破坏(我们将使用浮点堆栈的顶部来传递 1.34)。我们通过地址lgth通过约束内存引用"m" (lgth)"0"(1.34)约束说,我们将在1.34越过同一寄存器作为参数0,在这种情况下是浮点堆栈的顶部。这些是我们的汇编程序将覆盖但不会作为输入或输出约束出现的寄存器(或内存)。

用内联汇编器学习汇编语言是一种非常困难的学习方式。本机的限制特定于86可以发现这里下的x86系列。在约束修饰符的信息可以发现,在这里,并在信息GCC扩展汇编程序模板,可以发现在这里

我只是给你一个起点,因为GCC的内联汇编器使用可能相当复杂,任何答案对于 Stackoverflow 答案来说都可能太宽泛了。您使用带有 x87 浮点的内联汇编器这一事实使它变得更加复杂。


一旦您掌握了约束和修饰符,另一种可以由编译器生成更好汇编代码的机制是:

__asm__(
        "fsqrt\n\t"   // st(0) = square root st(0)
        "fmulp\n\t"   // Multiplies st(0) and st(1) (1.34). Result in st(0)
        : "=t"(result) : "0"(lgth), "u" (1.34) : "st(1)");
Run Code Online (Sandbox Code Playgroud)

提示:约束"u"在 x87 浮点寄存器中放置一个值st(1)。汇编器模板约束有效地放置lgthst(0)和 1.34 中st(1)st(1)内联汇编完成后无效,因此我们将其列为clobber。我们使用约束将我们的值放在浮点堆栈上。这具有减少我们必须在汇编代码本身内部做的工作的效果。


如果您正在开发 64 位应用程序,我强烈建议您至少使用 SSE/SSE2 进行基本浮点计算。上面的代码应该适用于 32 位和 64 位。在 64 位代码中,x87 浮点指令通常不如 SSE/SSE2 有效,但它们可以工作。


使用内联汇编和 x87 进行舍入

如果您尝试基于 x87 上的 4 种舍入模式之一进行舍入,您可以使用如下代码:

#include <stdint.h>
#include <stdio.h>

#define RND_CTL_BIT_SHIFT   10

typedef enum {
    ROUND_NEAREST_EVEN =    0 << RND_CTL_BIT_SHIFT,
    ROUND_MINUS_INF =       1 << RND_CTL_BIT_SHIFT,
    ROUND_PLUS_INF =        2 << RND_CTL_BIT_SHIFT,
    ROUND_TOWARD_ZERO =     3 << RND_CTL_BIT_SHIFT
} RoundingMode;

double roundd (double n, RoundingMode mode)
{
    uint16_t cw;        /* Storage for the current x87 control register */
    uint16_t newcw;     /* Storage for the new value of the control register */
    uint16_t dummyreg;  /* Temporary dummy register used in the template */

    __asm__ __volatile__ (
            "fstcw %w[cw]          \n\t" /* Read current x87 control register into cw*/
            "fwait                 \n\t" /* Do an fwait after an fstcw instruction */
            "mov %w[cw],%w[treg]   \n\t" /* ax = value in cw variable*/
            "and $0xf3ff,%w[treg]  \n\t" /* Set rounding mode bits 10 and 11 of control
                                            register to zero*/
            "or %w[rmode],%w[treg] \n\t" /* Set the rounding mode bits */
            "mov %w[treg],%w[newcw]\n\t" /* newcw = value for new control reg value*/
            "fldcw %w[newcw]       \n\t" /* Set control register to newcw */
            "frndint               \n\t" /* st(0) = round(st(0)) */
            "fldcw %w[cw]          \n\t" /* restore control reg to orig value in cw*/
            : [cw]"=m"(cw),
              [newcw]"=m"(newcw),
              [treg]"=&r"(dummyreg),  /* Register constraint with dummy variable
                                         allows compiler to choose available register */
              [n]"+t"(n)              /* +t constraint passes `n` through 
                                         top of FPU stack (st0) for both input&output*/
            : [rmode]"rmi"((uint16_t)mode)); /* "g" constraint same as "rmi" */

    return n;
}

double hullSpeed(double lgth)
{
    double result;

    __asm__(
            "fsqrt\n\t"   // st(0) = square root st(0)
            "fmulp\n\t"   // Multiplies st(0) and st(1) (1.34). Result in st(0)
            : "=t"(result) : "0"(lgth), "u" (1.34) : "st(1)");
    
    return result;
}

int main()
{
    double dbHullSpeed = hullSpeed(64.0);
    printf ("%f, %f\n", dbHullSpeed, roundd(dbHullSpeed, ROUND_NEAREST_EVEN));
    printf ("%f, %f\n", dbHullSpeed, roundd(dbHullSpeed, ROUND_MINUS_INF));
    printf ("%f, %f\n", dbHullSpeed, roundd(dbHullSpeed, ROUND_PLUS_INF));
    printf ("%f, %f\n", dbHullSpeed, roundd(dbHullSpeed, ROUND_TOWARD_ZERO));
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

正如您在评论中指出的那样,此Stackoverflow 答案中有等效的代码,但它使用了多个__asm__语句,您很好奇如何对单个__asm__语句进行编码。

舍入模式 (0,1,2,3) 可以在Intel Architecture Document 中找到

舍入模式 RC 字段

00B 四舍五入的结果最接近无限精确的结果。如果两个值同样接近,则结果是偶数值(即最低有效位为零的值)。默认向下舍入(朝 ??)

01B 舍入结果最接近但不大于无限精确结果。向上舍入(朝向 +?)

10B 舍入结果最接近但不小于无限精确结果。向零舍入(截断)

11B 舍入结果最接近但绝对值不大于无限精确结果。

在第 8.1.5 节(在第 8.1.5.3 节中具体描述的舍入模式)中有对字段的描述。4 种舍入模式在图 4-8 的 4.8.4 节中定义。