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

6

最近的一个问题的启发。

使用gcc风格的内嵌汇编的一个用例是编码编译器和汇编器都不知道的指令。例如,我提供了这个示例,演示如何在支持不了rdrand指令的旧工具链上使用该指令。

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

不幸的是,硬编码指令意味着您还需要硬编码与之一起使用的寄存器,这大大降低了编译器执行寄存器分配的自由度。

在某些体系结构上(例如具有.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");

类似地,如果有一种方法可以让gcc在ARM64上将SIMD寄存器12打印为12而不是v12,那么就可以执行以下操作:
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;
}

有没有一种方法可以获得寄存器编号?如果没有,是否存在其他选项来编码指令,既不需要编译器也不需要汇编器知道,而又不必硬编码寄存器编号?

1个回答

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

这个怎么运作?
记住,像%0%1这样的占位符将通过简单的字符串替换由编译器在将结果传递给汇编器之前填充为一个寄存器名字。
在汇编文件中,我们可以使用.equ指令来定义表示整数的符号。(以.L开头的符号不会在生成的目标文件中可见,因此我们不会不必要地混乱符号表)
每个对REG_CONST宏的调用都将定义一个(局部)符号:.L__reg_const__v0等于0,.L__reg_const__v1等于1,.L__reg_const__v2等于2,依此类推。
这些宏故意放置在文件顶部,不在任何函数外面,因为生成的asm(".equ .L__reg_const__v0 0")表达式应该放在汇编文件的顶部。
add3函数内的asm(".inst ...")模板中,%0%1%2将被编译器选择的寄存器替换为abc
由于我们狡猾地在.L__reg_const__表达式之后直接写入了占位符,替换将把它变成.L__reg_const__v7这样的表达式。
但是这正好对应于我们在顶部定义的整数符号的名称!因此汇编器实际上会将其作为一个符号并用我们定义的整数值替换它。
在计算符号之后,结果是一个纯数字表达式,汇编器将愉快地将整数值“或”在一起,得到所需的操作码。

1
这是一个不错的技巧,但它只适用于寄存器名称是有效标识符的架构/汇编方言,对吧?我想 %rax 会破坏一些东西... - Siguza
4
除了他没有使用“寄存器名称”,是吗?通过将%X0.L__reg_const__直接拼接,他正在创建一个以寄存器名称作为符号名称后缀的标识符。在标识符中使用百分号可能会对x86产生问题,但我相信有一个修改器 (V)可以关闭它。 - David Wohlferd
那是个很棒的想法! - fuz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接