在汇编中,如何在不破坏任何一个操作数的情况下添加整数?
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
/sub
,gcc -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
.
在 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
来保存指令。 (或者使用一些调整选项,如 -mtune=atom
用于旧的有序 Atom,将使用 lea
即使他们可以使用 add
。)
如果存在除加法以外的更灵活的通用整数指令编码,例如 and
/xor
/sub
,gcc -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
.