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

In assembly, how to add integers without destroying either operand?

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

add %[a], %[b], %[c]

不幸的是,GNU 的 assembler 不会这样做。为什么不呢?

详情

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

VEX-encoded general-purpose-register instructions have ... instruction syntax support for three encodable operands.

VEX 前缀是一个 AVX 特性,因此来自 Sandy Bridge/Bulldozer 的 x86-64 CPUs 向前实现它。那是十年前的事了,所以 GNU 的 assembler 应该 assemble 我的三操作数指令,不是吗?

澄清一下,我知道可以用旧式写成

mov %[a], %[c]
add %[b], %[c]

但是,我希望用新的 VEX 风格来写它。顺便说一下,我已经通过向 GCC 发出 -march=skylake 命令行选项来通知 assembler 我有一个现代的 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";
}

只有少数特定的 GPR 指令有 VEX 编码,主要是 BMI1/BMI2 instructions that were added after AVX already existed. See the list in Table 2-28, which has ANDN, BEXTR, BLSI, BLSMSK, BLSR, BZHI, MULX, PDEP, PEXT, RORX, SARX, SHLX, SHRX, as well as the same list in 5.1.16.1. For example, andn's manual entry lists only a VEX encoding, and's manual entry 没有列出任何指令。

所以英特尔(很遗憾)没有为整个指令集引入全新的三操作数交替编码。他们只是介绍了一些特定的指令,这些指令采用三个操作数并使用 VEX。在某些情况下,它们具有与现有指令相似或等效的功能,例如SHLX for SHL with a variable count,因此有效地提供了前一个双操作数指令的三操作数版本,但仅限于那些特殊情况。没有全面的等效说明。

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

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

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


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

使用单个 lea 作为 asm 模板意味着您不需要 =&r early-clobber 输出,因为您的 asm 将在写入该输出之前读取其所有输入。在您的情况下,将其作为 input/output 可能会阻止编译器选择相同的寄存器作为输入之一,这可能会破坏 mov; add.