如何使用 gcc 风格的内联汇编打印寄存器号?

fuz*_*fuz 7 assembly gcc clang inline-assembly arm64

受到最近一个问题的启发。

gcc 式内联汇编的一种用例是对编译器和汇编器都不知道的指令进行编码。例如,我给出了如何在太旧而无法支持的工具链上使用指令的示例:rdrand

/* "rdrand %%rax ; setc %b1" */
asm volatile (".byte 0x48, 0x0f, 0xc7, 0xf0; setc %b1"
    : "=a"(result), "=qm"(success) :: "cc");
Run Code Online (Sandbox Code Playgroud)

不幸的是,对指令进行硬编码意味着您还需要对其使用的寄存器进行硬编码,从而大大减少了编译器执行寄存器分配的自由度。

在某些架构上(例如带有.insn指令的 RISC-V),汇编器提供了一种系统地构建原始指令的方法,但这似乎是例外。

一个简单的解决方案是有一种方法来获取寄存器的未修饰编号,以将其手动编码到指令中。例如,假设X存在模板修饰符来打印所选寄存器的编号。那么,上面的例子可以变得更加灵活:

/* "rdrand %0 ; setc %b1" */
asm volatile (".byte 0x48 | (%X0 >> 3), 0x0f, 0xc7, 0xf0 | (%X0 & 7); setc %b1"
    : "=r"(result), "=qm"(success) :: "cc");
Run Code Online (Sandbox Code Playgroud)

同样,如果有一种方法可以让 gcc 打印12而不是v12ARM64 上的 SIMD 寄存器 12,则可以执行如下操作:

float32x4_t add3(float32x4_t a, float32x4_t b)
{
    float32x4_t c;

    /* fadd %0, %1, %2 */
    asm (".inst 0x4e20d40 + %X0 + (%X1<<5) + (%X2<<16)" : "=w"(c) : "w"(a), "w"(b));

    return c;
}
Run Code Online (Sandbox Code Playgroud)

有没有办法获取注册号?如果不是,还有哪些其他选项可以对编译器和汇编器都不知道的指令进行编码,而无需对寄存器号进行硬编码?

Mar*_*ler 7

我实际上遇到了同样的问题并提出了以下解决方案。

#define REG_CONST(n) asm(".equ .L__reg_const__v" #n ", " #n);

REG_CONST(0)
REG_CONST(1)
REG_CONST(2)
REG_CONST(3)
// ... repeat this for all register numbers ...
REG_CONST(27)
REG_CONST(28)
REG_CONST(29)
REG_CONST(30)

float32x4_t add3(float32x4_t a, float32x4_t b) {
    float32x4_t c;
    // fadd %0, %1, %2
    asm(".inst 0x4e20d40 | .L__reg_const__%0 | (.L__reg_const__%1 << 5) + (.L__reg_const__%2 << 16)" : "=w"(c) : "w"(a), "w"(b));

    return c;
}
Run Code Online (Sandbox Code Playgroud)

这是如何运作的?

  1. 请记住,在将结果传递给汇编器之前,编译器将通过简单的字符串替换来填充诸如%0, , ... 之类的占位符。%1
  2. 在汇编文件中,我们可以使用.equ指令来定义表示整数的符号。(以 开头的符号.L在生成的目标文件中将不可见,因此我们不会不必要地弄乱符号表)
  3. 宏的每次调用REG_CONST都会定义一个(本地)符号:.L__reg_const__v0该符号等于 0、.L__reg_const__v1等于 1、.L__reg_const__v2等于 2,依此类推。
  4. 这些宏被有意放置在文件的顶部,任何函数之外,因为结果asm(".equ .L__reg_const__v0 0")表达式应该位于汇编文件的顶部。
  5. 在函数asm(".inst ...")内的模板中,, ,将被替换为编译器为,和所选的任何寄存器。add3%0%1%2abc
  6. 由于我们偷偷地在表达式后面直接写了占位符,没有任何空格.L__reg_const__,因此替换会将其变成类似 的表达式.L__reg_const__v7
  7. 但这与我们在顶部定义的整数符号的名称完全对应!因此汇编器实际上会将其作为符号并将其替换为我们定义的整数值。
  8. 在评估符号之后,结果是一个纯数字表达式,汇编器会很乐意将整数值“或”在一起,产生所需的操作码。

  • @Siguza - 除了他没有使用“注册名称”,是吗?通过针对“.L__reg_const__”运行“%X0”,他创建了一个标识符,其中寄存器名称作为符号名称的后缀。将百分号作为标识符的一部分对于 x86 可能会出现问题,但我相信有一个[修饰符](https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#x86Operandmodifiers) (`V` )将其关闭。 (4认同)