有没有办法在linux平台上编译microsoft风格的内联汇编代码?

有没有办法在linux平台上编译microsoft风格的内联汇编代码?,linux,gcc,visual-c++,inline-assembly,Linux,Gcc,Visual C++,Inline Assembly,正如标题中提到的,我想知道有没有办法在linux操作系统(如ubuntu)中编译微软风格的内联汇编代码(如下所示) 示例代码是内联程序集代码的一部分,可以使用cl.exe编译器在win10上成功编译该内联程序集代码。有没有办法在linux上编译它?我是否必须用GNU c/c++风格(即uu asm uu{;;})重写它?首先,通常应该替换内联asm(使用内部函数或纯c),而不是移植它 clang-fasm块主要与MSVC低效的内联asm语法兼容。但它不支持通过将值留在EAX中,然后从非void

正如标题中提到的,我想知道有没有办法在linux操作系统(如ubuntu)中编译微软风格的内联汇编代码(如下所示)


示例代码是内联程序集代码的一部分,可以使用cl.exe编译器在win10上成功编译该内联程序集代码。有没有办法在linux上编译它?我是否必须用GNU c/c++风格(即uu asm uu{;;})重写它?

首先,通常应该替换内联asm(使用内部函数或纯c),而不是移植它


clang-fasm块
主要与MSVC低效的内联asm语法兼容
。但它不支持通过将值留在EAX中,然后从非void函数的末尾掉下来来返回值

因此,您必须编写内联asm,将该值放入命名的C变量中,然后
返回
,这通常会导致额外的存储/重新加载,从而使MSVC语法更加糟糕。(非常糟糕,除非您在asm中编写一个完整的循环,以分摊将数据输入/输出asm块的存储/重新加载开销)。有关包装单个指令时MSVC内联asm效率的比较,请参阅。当那些函数不内联时,使用堆栈参数的函数内部就不那么愚蠢了,但只有当您已经使事情变得低效时才会发生这种情况(例如,使用传统的32位调用约定,不使用链接时间优化来内联小函数)

MSVC在内联到调用方时可以用立即的
1
替换
A
,但clang不能。两者都可以阻止常量传播,但MSVC至少可以避免通过存储/重新加载反弹常量输入。(只要只与支持直接源操作数的指令一起使用。)

Clang接受
\u asm
asm
\u asm\u
来引入asm块。MSVC接受
\uu asm
(2个下划线,如clang)或
\u asm
(更常用,但clang不接受)

因此,对于现有的MSVC代码,您可能需要
#定义_asm\u asm
,这样您的代码就可以同时使用MSVC和clang进行编译,除非您需要创建单独的版本。或者使用
clang-D_asm=asm
在命令行上设置CPP宏

示例:使用MSVC或
clang-fasm块编译
(不要忘了启用优化:
clang-fasm blocks-O3-march=native-flto-Wall
。如果您希望二进制文件可以在编译主机以外的早期/其他CPU上运行,请忽略或修改
-march=native

使用编译表明,clang可以内联包含内联asm的包装器函数,以及需要多少存储/重新加载MSVC语法(与GNU C内联asm相比,GNU C内联asm可以在寄存器中获取输入和输出)

我在Intel syntax asm输出模式下使用了clang,但在AT&T语法模式下输出时,它也编译Intel syntax asm块。(通常,clang直接编译为机器代码,这也是正确的。)

注意编译器是如何在asm源代码块中用
[rsp-4]
[rsp-8]
替换C局部变量
A
out
的。静态存储器中的一个变量获得RIP相对寻址。GNU C内联asm不这样做,您需要声明
%[name]
操作数并告诉编译器将它们放在哪里

我们甚至可以看到将该函数两次内联到一个调用者中,并将符号扩展优化为64位,因为该函数只返回
int

int caller() {
    return foo(1, 2, nullptr) + foo(1, 2, nullptr);
}
因此,我们可以看到,仅从内联asm中读取
A
会造成错过的优化:编译器再次存储
1
,即使asm只读取该输入而不修改它

在asm语句之前/之间/之后,我没有做过分配或读取
a_global
之类的测试,以确保编译器“知道”asm语句修改了该变量

我还没有测试过将指针传递到asm块并在指向数组上循环,看它是否像GNU C内联asm中的
“内存”
clobber。我想是的

我的Godbolt链接还包括一个从非void函数末尾脱落的示例,该函数的值为EAX。MSVC支持这一点,但当内联到调用方时,它像往常一样发出叮当声和中断声。(奇怪的是,即使在
-Wall
,也没有任何警告)。您可以在上面我的Godbolt链接上看到x86msvc是如何编译它的


将MSVC asm移植到GNU C内联asm几乎肯定是错误的选择编译器对优化内部函数的支持非常好,因此您通常可以让编译器为您生成高质量高效的asm

如果您打算对现有的手写asm做些什么,通常用纯C替换它们将是最有效的,当然也是最经得起未来考验的前进之路。将来可以自动矢量化到更宽矢量的代码总是好的。但是,如果您确实需要手动矢量化以进行一些复杂的洗牌,那么intrinics是一种方法,除非编译器以某种方式将其弄得一团糟

查看从intrinsics获得的编译器生成的asm,以确保它与原始版本一样好或更好

如果您正在使用MMX
EMMS
,现在可能是用SSE2内部函数替换MMX代码的好时机。SSE2是x86-64的基准,很少有Linux系统运行过时的32位内核

有没有办法在linux平台上编译microsoft风格的内联汇编代码

是的,这是可能的。有点

对于GCC,您必须同时使用Intel和AT&T语法。由于问题24232和问题39895,它不适用于Clang

这是模式。汇编程序模板使用
.intel\u语法
。然后,在asm模板的末尾,切换回int a_global; inline long foo(int A, int B, int *arr) { int out; // You can't assume A will be in RDI: after inlining it prob. won't be __asm { mov ecx, A // comment syntax add dword ptr [a_global], 1 mov out, ecx } return out; }
## The x86-64 System V ABI passes args in rdi, rsi, rdx, ...
# clang -O3 -fasm-blocks -Wall
foo(int, int, int*):
        mov     dword ptr [rsp - 4], edi        # compiler-generated store of register arg to the stack

        mov     ecx, dword ptr [rsp - 4]        # start of inline asm
        add     dword ptr [rip + a_global], 1
        mov     dword ptr [rsp - 8], ecx        # end of inline asm

        movsxd  rax, dword ptr [rsp - 8]        # reload `out` with sign-extension to long (64-bit) : compiler-generated
        ret
int caller() {
    return foo(1, 2, nullptr) + foo(1, 2, nullptr);
}
caller():                             # @caller()
        mov     dword ptr [rsp - 4], 1

        mov     ecx, dword ptr [rsp - 4]      # first inline asm
        add     dword ptr [rip + a_global], 1
        mov     dword ptr [rsp - 8], ecx

        mov     eax, dword ptr [rsp - 8]     # compiler-generated reload
        mov     dword ptr [rsp - 4], 1       # and store of A=1 again

        mov     ecx, dword ptr [rsp - 4]      # second inline asm
        add     dword ptr [rip + a_global], 1
        mov     dword ptr [rsp - 8], ecx

        add     eax, dword ptr [rsp - 8]     # compiler-generated reload
        ret
#include <cstddef>
int main(int argc, char* argv[])
{
    size_t ret = 1, N = 0;
    asm __volatile__
    (
        ".intel_syntax   noprefix ;\n"
        "xor esi, esi    ;\n"           // zero RSI
        "neg %1          ;\n"           // %1 is replaced with the operand location chosen by the compiler, in this case RCX
        "inc %1          ;\n"
        "push %1         ;\n"           // UNSAFE: steps on the red-zone
        "pop rax         ;\n"
        ".att_syntax     prefix ;\n"
        : "=a" (ret)      // output-only operand in RAX
          "+c" (N)        // read-write operand in RCX
        :                 // no read-only inputs
        : "%rsi"          // RSI is clobbered: input and output register constraints can't pick it
    );
    return (int)ret;
}
mov edi, A;
__asm__ __volatile__
(
    ".intel_syntax   noprefix ;\n"
    "mov edi, %0     \n";            // inefficient: use a "D" constraint instead of a mov
    ...
    ".att_syntax     prefix ;\n"
    : : "r" (A) : "%edi"
);
uint32_t Mask = 1234;
uint32_t Index;

  asm ("bsfl %1, %0"
     : "=r" (Index)
     : "r" (Mask)
     : "cc");
uint32_t Mask = 1234;
uint32_t Index;

  asm ("bsfl %[aMask], %[aIndex]"
     : [aIndex] "=r" (Index)
     : [aMask] "r" (Mask)
     : "cc");
#define _asm\
        asm(".intel_syntax noprefix\n");\
        asm\
clang++ -c -fasm-blocks source.cpp