在汇编中,如何在不破坏任何操作数的情况下添加整数?

thb*_*thb 6 assembly x86-64 gnu-assembler avx

在 x86-64 上使用 AT&T 语法,我希望汇编c = a + b;为

add %[a], %[b], %[c]
Run Code Online (Sandbox Code Playgroud)

不幸的是,GNU 的汇编器不会这样做。为什么不?

细节

根据英特尔软件开发人员手册,修订版。75(2021 年 6 月),卷。2、第 2.5 节,

VEX 编码的通用寄存器指令具有对三个可编码操作数的指令语法支持。

VEX 前缀是 AVX 功能,因此从 Sandy Bridge/Bulldozer 开始的 x86-64 CPU 都实现了它。那是十年前的事了,所以 GNU 的汇编器应该汇编我的三操作数指令,不是吗?

为了澄清,我知道可以用旧的风格将其写为

mov %[a], %[c]
add %[b], %[c]
Run Code Online (Sandbox Code Playgroud)

然而,我希望用新的 VEX 风格来写它。-march=skylake顺便说一句,我已经通过向 GCC 发出命令行选项来通知汇编器我有一个现代 CPU 。

请问我的错误是什么?

示例代码

在 C++ 包装器中,

#include <cstddef>
#include <iostream>

int main()
{
    volatile int a{8};
    volatile int b{5};
    volatile int c{0};
    //c = a + b;
    asm volatile (
        //"mov %[a], %[c]\n\t"
        //"add %[b], %[c]\n\t"
        "add %[a], %[b], %[c]\n\t"
        : [c] "=&r" (c)
        : [a] "r" (a), [b] "r" (b)
        : "cc"
    );
    std::cout << c << "\n";
}
Run Code Online (Sandbox Code Playgroud)

Nat*_*dge 8

只有少数特定的 GPR 指令具有 VEX 编码,主要是AVX 存在后添加的BMI1/BMI2指令。参见表 2-28 中的列表,其中有ANDN, BEXTR, BLSI, BLSMSK, BLSR, BZHI, MULX, PDEP, PEXT, RORX, SARX, SHLX, SHRX,以及 5.1.16.1 中的相同列表。例如,andn的手动条目仅列出了VEX编码,and的手动条目没有列出任何编码。

因此,英特尔(不幸的是)没有为整个指令集引入全新的三操作数替代编码。他们只是介绍了一些采用三个操作数并使用 VEX 的具体指令。在一些情况下,这些指令具有与现有指令类似或等效的功能,例如SHLX具有SHL可变计数,因此有效地提供了先前两操作数指令的三操作数版本,但仅在那些特殊情况下。没有全面等效的说明。

“旧式”双操作数形式仍然是指令的唯一版本add。然而,正如 fuz 在评论中指出的那样,lea添加两个寄存器并将结果写入第三个寄存器可能是一个好方法,但受到操作数大小的一些限制。

请参阅在不是地址/指针的值上使用 LEA?对于 LEA 可以做的更一般的事情,例如将常量复制并添加到寄存器,或移位并添加。编译器已经知道这一点,并且会lea在适当的时候在保存指令时使用。(或者使用一些调整选项,例如-mtune=atom旧的有序 Atom,lea即使他们可以使用,也会使用add。)

如果存在除 add 之外的更灵活的常见整数指令编码,例如and/ xor/ sub, gcc -O3 -march=skylake则已经在其自己的 asm 输出中使用它们,而不需要内联 asm。或者,如果替代指令可以完成工作,例如leafor add,就会这样做,因此查看编译器输出以了解它知道哪些技巧是有意义的。自己尝试一下会更有意义,因为可以在一个独立.s文件中进行操作,该文件仅进行退出系统调用,或者只是单步执行,从而消除使用内联汇编的复杂性。(GAS 默认情况下不限制指令集。 gcc -march=skylake不会将其传递给汇编器。as)


在您的内联汇编中,您的c操作数应该是仅输出:=r而不是+r. 旧值被覆盖,因此无需告诉编译器将其生成作为输入。(就像你说的,你不想c = a+b。c += a+b)

使用 singlelea作为 asm 模板意味着您不需要=&r早期的输出,因为您的 asm 将在写入输出之前读取其所有输入。在您的情况下,将其作为输入/输出可能会阻止编译器选择相同的寄存器作为输入之一,这可能会与mov; add.