有没有办法在Linux平台上遵守微软风格的内联汇编代码



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

_asm{
mov edi, A;
....
EMMS;
}

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

首先,你通常应该替换内联asm(用内联或纯C)而不是移植它。 https://gcc.gnu.org/wiki/DontUseInlineAsm


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

因此,您必须编写内联 asm,将值放在命名的 C 变量中并return,这通常会导致额外的存储/重新加载,使 MSVC 语法更加糟糕。 (非常糟糕,除非您在asm中编写一个完整的循环,以摊销将数据传入/传出asm块的存储/重新加载开销)。 请参阅"asm"、"__asm"和"__asm__"之间有什么区别?比较 MSVC 内联 ASM 包装单个指令时的效率低下。 当这些函数不内联时,带有堆栈参数的函数内部就不那么愚蠢了,但只有当你已经使事情变得低效时(例如,使用传统的 32 位调用约定并且不使用链接时间优化来内联小函数)。

MSVC 可以在内联到调用方时将A替换为即时1,但 clang 不能。 两者都会破坏常量传播,但 MSVC 至少避免了通过存储/重新加载来反弹常量输入。 (只要您仅将其与可以支持即时源操作数的指令一起使用。

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

因此,对于现有的 MSVC 代码,您可能希望#define _asm __asm以便您的代码可以使用 MSVC 和 clang 进行编译,除非您无论如何都需要制作单独的版本。 或者使用clang -D_asm=asm在命令行上设置 CPP 宏。

示例:使用 MSVC 或clang -fasm-blocks编译

(不要忘记启用优化:clang -fasm-blocks -O3 -march=native -flto -Wall。 省略或修改-march=native,如果你想要一个可以在编译主机更早/其他 CPU 上运行的二进制文件。

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

在 Godbolt 上使用 x86-64 Linuxclang 8.0进行编译表明,clang 可以内联包含内联 asm 的包装器函数,以及需要多少存储/重新加载 MSVC 语法(相对于 GNU C 内联 asm,它可以在寄存器中接受输入和输出)。

我在英特尔语法 asm 输出模式下使用 clang,但它在以 AT&T 语法模式输出时也会编译英特尔语法 asm 块。 (通常 clang 无论如何都会直接编译为机器代码,这也是正确的。

## 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

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

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

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

因此,我们可以看到,仅从内联 asm 读取A会产生错过的优化:编译器再次存储1,即使 asm 只读取该输入而不对其进行修改。

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

我也没有测试过将指针传递到 asm 块中并循环指向数组,看看它是否像 GNU C 内联 asm 中的"memory"clobber。 我想是的。

我的 Godbolt 链接还包括一个在 EAX 中具有值的非空函数末尾掉落的示例。 MSVC 支持此功能,但与往常一样,在内联到调用方时,它会像往常一样进行 UB,并中断。 (奇怪的是,即使在-Wall也没有警告)。 你可以在上面的Godbolt链接上看到x86 MSVC是如何编译它的。


https://gcc.gnu.org/wiki/DontUseInlineAsm

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

如果你打算对现有的手写asm做任何事情,通常用纯C替换它们将是最有效的,当然也是最面向未来的前进道路。 将来可以自动矢量化为更宽矢量的代码总是好的。 但是,如果您确实需要手动矢量化以进行一些棘手的洗牌,那么内部函数是要走的路,除非编译器以某种方式将其弄得一团糟。

查看您从内部函数中获得的编译器生成的asm,以确保它与原始版本一样好或更好。

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

有没有办法在Linux平台上遵守微软风格的内联汇编代码?

是的,这是可能的。有点。

对于GCC,您必须同时使用Intel和AT&T语法。由于问题 24232、内联程序集操作数不适用于 .intel_syntax 和问题 39895,错误:使用内联 asm 的表达式中的未知标记,它不适用于 Clang。

这是模式。汇编程序模板使用.intel_syntax.然后,在 asm 模板结束时,切换回.att_syntax模式,以便编译器生成的 asm 的其余部分处于正确的模式。

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

如果您使用任何内存操作数,这将不起作用,因为编译器会将 AT&T 语法4(%rsp)替换到模板中,而不是[rsp + 4],例如。

这也仅适用于不使用gcc -masm=intel进行编译的情况。 否则,当 GCC 发出英特尔语法时,您将把汇编程序置于 AT&T 模式。 因此,使用.intel_syntax noprefix会破坏您在 GCC 中使用任一语法的能力。


mov edi, A;

我帮助的代码不像你展示的那样使用汇编程序中的变量。我不知道它与英特尔风格的ASM配合得如何(很差?我知道不支持 MASM 样式语法。

您可以使用asmSymbolicNames.有关详细信息,请参阅 GCC 扩展 ASM 操作方法。

但是,要转换为 GCC 可以使用的东西,您只需要使用位置参数:

__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"
);

或者更好的是,首先使用"D"约束来请求 EDI/RDI 中的变量。 如果GNU C内联asm语句以mov开头或结尾,这通常表明你做错了。


关于asmSymbolicNames,以下是GCC扩展ASM HowTo对它们的看法:

此代码不使用可选的 asmSymbolicName。因此它 将第一个输出操作数引用为 %0(如果有第二个,则 将是 %1,依此类推)。第一个输入操作数的编号为 1 大于最后一个输出操作数。在此 i386 示例中, 这使得掩码引用为 %1:

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

该代码将覆盖变量 Index ('='),将值放在 寄存器 ('R')。使用通用的"r"约束而不是 特定寄存器的约束允许编译器选择 注册以使用,这可以产生更高效的代码。这可能不是 如果汇编程序指令需要特定的寄存器,则可以使用。

以下 i386 示例使用 asmSymbolicName 语法。它 产生与上述代码相同的结果,但有些人可能会认为它 更具可读性或更易于维护,因为重新排序索引号是 添加或删除操作数时不需要。名称 a索引和 aMask 在此示例中仅用于强调使用哪些名称 哪里。可以重复使用名称"索引"和"掩码"。

uint32_t Mask = 1234;
uint32_t Index;
asm ("bsfl %[aMask], %[aIndex]"
: [aIndex] "=r" (Index)
: [aMask] "r" (Mask)
: "cc");

示例代码是内联汇编代码的一部分,可以在 win10 上使用 cl.exe 编译器成功编译...

回到10,000英尺,如果你正在寻找一些易于使用的东西来集成内联ASM,比如在Microsoft环境中,那么你在Linux上没有它。GCC 内联 ASM 绝对很糟糕。GCC 内联汇编器是一个过时的、难以使用的工具,我鄙视与之交互。

(而且您还没有遇到带有虚假行信息的难以理解的错误消息)。

彼得的想法解决了我的问题。我刚刚在我的源文件中添加了一个宏,其中所有函数都由英特尔语法的单个大的内联 asm 块组成。宏如下所示:

#define _asm
asm(".intel_syntax noprefixn");
asm

之后,我用命令编译了它:

clang++ -c -fasm-blocks source.cpp

那么一切都没事了。

最新更新