c - 汇编代码 fsqrt 和 fmul 指令

标签 c gcc assembly x86 inline-assembly

我尝试使用内联汇编在此函数中计算 1.34 *sqrt(lght) 但遇到如下错误:

'_asm' undeclared (first use in this function) each undeclared identifier is reported only once for each func tion it appears in expected ';' before '{' token

<小时/>

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

我的代码是:

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

我使用 Ubuntu,所以 GCC,32 位

最佳答案

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

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

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

使用内联汇编器学习汇编语言是一种非常困难的学习方法。特定于x86的机器约束可以在here中找到。 x86 系列。有关约束修饰符的信息可以找到 here ,有关 GCC 扩展汇编器模板的信息可以在 here 中找到。 .

我只是给您一个起点,因为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)");

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

<小时/>

如果您正在开发 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;
}

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

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

Rounding Mode RC Field

00B Rounded result is the closest to the infinitely precise result. If two values are equally close, the result is the even value (that is, the one with the least-significant bit of zero). Default Round down (toward −∞)

01B Rounded result is closest to but no greater than the infinitely precise result. Round up (toward +∞)

10B Rounded result is closest to but no less than the infinitely precise result.Round toward zero (Truncate)

11B Rounded result is closest to but no greater in absolute value than the infinitely precise result.

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

关于c - 汇编代码 fsqrt 和 fmul 指令,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35515378/

相关文章:

c - 我在 ppp 项目的模块 chat.c 中找到了一个代码块,但我无法理解它

c - 如何在没有Windows头文件的情况下使用NtCurrentTeb()?

c - 移位后 float 的位布局如何给我某些值?

我可以将函数作为参数传递并且该函数可以保存不同的参数吗?

c++ - 为什么 MSVC 和 GCC 不能使用具有默认值的字段初始化结构

c++ - 在 g++ 4.4.3 上得到负 NaN,这是标准吗?

assembly - 为什么MIPS在计算分支目标地址时使用 'PC+4'作为基地址?

c++ - 汇编操作的时间

c - 如何在 C 中设置 uint8_t 的低 3 位

c - 在 C 中验证文件