Cygwin:使用asm标签编译cpp文件

Cygwin: Compile cpp file with asm tag

我是汇编新手,目前正在尝试创建带有 asm 标记的 C++ 代码。我正在使用 cygwin 进行编译。这是我的代码:

#include <iostream>
using namespace std;

int main()  
{  
    float flp1_num, flp2_num, flp_rslt1;

    cin >>flp1_num >>flp2_num;

    __asm
    {
        FLD flp1_num
        FLDPI
        FADD flp2_num
        FST flp_rslt1
    }

    cout << flp_rslt1;
}  

使用的语法来自here

我正在使用 g++ arq.cpp -o arq.exe 进行编译,这让我报错:

arq.cpp: In function ‘int main()’:
arq.cpp:13:5: error: expected ‘(’ before ‘{’ token
     {
     ^
arq.cpp:14:9: error: ‘FLD’ was not declared in this scope
         FLD flp1_num
         ^

然后我尝试将 __asm {} 更改为 __asm(),它给了我不同的错误:

arq.cpp: In function ‘int main()’:
arq.cpp:14:9: error: expected string-literal before ‘FLD’
         FLD flp1_num

我四处搜索并找到了几个可能有用的替代方案,但它们对我不起作用。例如 __asm__("fld flp1_num");asm("fld flp1_num"); 都给我错误提示 /tmp/cccDDfUP.o:arq.cpp:(.text+0x32): undefined reference to flp1_num.

如何修复此错误?

查看此文档:How to embed assembler code in gcc

使用 gcc/g++ 时需要以不同的方式嵌入汇编代码:

int src = 1;
int dst;    
asm ("mov %1, %0\n\t"
     "add , %0"
      : "=r" (dst)
      : "r" (src));

而 cygwin 是 gcc 的 windows 端口。

正如其他人所说,您正在查看 Microsoft 的编译器文档,该编译器的内联汇编形式与 GCC 使用的形式截然不同。事实上,it is a substantially less powerful form,在很多方面,虽然它确实有更容易学习使用的优点。

您需要查阅可用的 Gnu 内联汇编语法文档 here. For a gentler introduction, there is a good tutorial here, and I particularly like David Wohlferd's answer here。虽然这是一个不相关的问题,但如果您只是为了理解他的解释,他对内联汇编的基础知识进行了很好的介绍。


无论如何,关于你的具体问题。几个紧迫的问题:

  1. 代码很可能没有按照您的想法执行。您的代码 实际上 所做的是将 pi 添加到 flp2_num,然后将结果放入 flp_rslt1。它对 flp1_num.

    没有任何作用

    如果非要我猜的话,我会想象你想把 flp1_num、pi 和 flp2_num 加在一起,然后 return 结果在 flp_rslt1. (但也许不是;这不是很清楚,因为您没有任何说明您的意图的评论,也没有描述性的函数名称。)

  2. 您的代码也损坏,因为它没有正确清理浮点堆栈。您有两个 "load" 指令,但是 没有弹出指令 !你 push/load 到浮点堆栈上的所有东西都必须是 popped/unloaded,否则你会使浮点堆栈不平衡,这会导致重大问题。

因此,在 MSVC 语法中,您的代码应该类似于以下内容(为了方便和清晰起见,包装成一个函数):

float SumPlusPi(float flp1_num, float flp2_num)
{
    float flp_rslt1;
    __asm
    {
       fldpi                       ; load the constant PI onto the top of the FP stack
       fadd  DWORD PTR [flp2_num]  ; add flp2_num to PI, and leave the result on the top of the stack
       fadd  DWORD PTR [flp1_num]  ; add flp1_num to the top of the stack, again leaving the result there
       fstp  DWORD PTR [flp_rslt1] ; pop the top of the stack into flp_rslt1
    }
    return flp_rslt1;
}

我只推了一次(fldpi),所以我只弹出了一次(fstp)。对于添加,我使用了 fadd 的形式,它适用于内存操作数;这导致值被隐式加载到堆栈上,但在其他方面似乎作为单个指令执行。但是,您可以通过许多不同的方式来编写它。重要的是平衡推送次数和弹出次数。有显式弹出的指令(fstp),还有其他执行操作然后弹出的指令(例如faddp)。指令的不同组合,按照某些顺序,很可能比其他指令更优化,但我上面的代码确实有效。


这是翻译成 GAS 语法的等效代码:

float SumPlusPi(float flp1_num, float flp2_num)
{
    float flp_rslt1;
    __asm__("fldpi        \n\t"
            "faddl %[two] \n\t"
            "faddl %[one]"
           : [result] "=t" (flp_rslt1)   // tell compiler result is left at the top of the floating-point stack,
                                         //  making an explicit pop unnecessary
           : [one]    "m" (flp1_num),    // input operand from memory (inefficient)
             [two]    "m" (flp2_num));   // input operand from memory (inefficient)
    return flp_rslt1;
}

虽然这有效,但它也不是最佳选择,因为它没有利用 GAS 内联汇编语法的高级功能,特别是使用已经加载到浮点堆栈上的值作为输入的能力。


不过,最重要的是,不要错过 the reasons why you should not use inline assembly(也是 David Wohlferd 的作品)!这是内联汇编的真正毫无意义的用法。 编译器将生成更好的代码,并且它需要您显着减少的工作量。因此,更喜欢这样写上面的函数:

#include <cmath>    // for M_PI constant
float SumPlusPi(float flp1_num, float flp2_num)
{
    return (flp1_num + flp2_num + static_cast<float>(M_PI));
}

请注意,如果您真的想实现与我假设的不同的逻辑,更改此代码以执行您想要的操作是微不足道的。

如果您不相信我生成的代码与内联汇编一样好——如果不是更好——这里是 GCC 6.2 生成的确切目标代码对于上述功能(Clang 发出相同的代码):

fld     DWORD PTR [flp2_num]  ; load flp2_num onto top of FPU stack
fadd    DWORD PTR [flp1_num]  ; add flp1_num to value at top of FPU stack
fadd    DWORD PTR [M_PI]      ; add constant M_PI to value at top of FPU stack
ret                           ; return, with result at top of FPU stack

使用 fldpi 与像 GCC 那样从常量加载值相比,没有速度上的优势。如果有的话,强制使用这条指令实际上是一种悲观,因为这意味着您的代码永远无法利用允许操纵浮点值 far 的 SSE/SSE2 指令 more比旧的 x87 FPU 更高效。为上述 C 代码启用 SSE/SSE2 就像扔一个编译器开关一样简单(或指定支持它的目标体系结构,这将隐式启用它)。这将为您提供以下内容:

sub       esp, 4                      ; reserve space on the stack    
movss     xmm0, DWORD PTR [M_PI]      ; load M_PI constant
addss     xmm0, DWORD PTR [flp2_num]  ; add flp2_num
addss     xmm0, DWORD PTR [flp1_num]  ; add flp1_num
movss     DWORD PTR [esp], xmm0       ; store result in temporary space on stack
fld       DWORD PTR [esp]             ; load result from stack to top of FPU stack
add       esp, 4                      ; clean up stack space
ret                                   ; return, with result at top of FPU stack