首页 > 解决方案 > GCC 扩展程序集 pin 局部变量到除 r12 之外的任何寄存器

问题描述

基本上,我正在寻找一种将临时固定到任何寄存器的方法,除了r12.

我知道我可以“提示”编译器将其固定到单个寄存器:

// Toy example. Obviously an unbalanced `pop` in 
// extended assembly will cause serious problems.

register long tmp asm("rdi"); // or just clober rdi and use it directly.
asm volatile("pop %[tmp]\n"   // using pop hence don't want r12
             : [tmp] "=&r" (tmp)
             :
             :);

这通常可以避免r12但可能会弄乱编译器在其他地方的寄存器分配。

是否可以在不强制编译器使用单个寄存器的情况下做到这一点?

标签: cgccx86inline-assembly

解决方案


请注意,这register asm并没有真正将变量“固定”到寄存器,它只确保使用该变量作为内联 asm 中的操作数将使用该寄存器。原则上,变量可以存储在两者之间的其他地方。请参阅https://gcc.gnu.org/onlinedocs/gcc-11.1.0/gcc/Local-Register-Variables.html#Local-Register-Variables。但听起来您真正需要的只是确保您的pop指令不用r12作其操作数,可能是因为使用寄存器 R12 时为什么 POP 很慢?. 我不知道有什么方法可以做到这一点,但这里有一些可能会有所帮助的选项。

每个寄存器rax, rbx, rcx, rdx, rsi, rdi都有自己的约束字母,a,b,c,d,S,D分别(其他寄存器没有)。所以你可以通过做

long tmp;
asm volatile("pop %[tmp]\n"
             : [tmp] "=&abcdSD" (tmp)
             :
             :);

这样,编译器可以选择这六个寄存器中的任何一个,这应该给寄存器分配器更多的灵活性。

另一种选择是声明您的 asm clobbers r12,这将阻止编译器在那里分配操作数:

long tmp;
asm volatile("pop %[tmp]\n"
             : [tmp] "=&r" (tmp)
             :
             : "r12");

权衡是它也不会用于r12缓存 中的局部变量asm,因为它假定它可以被修改。希望它足够聪明,可以完全避免r12在代码的那部分使用,但如果不能,它可能会发出额外的寄存器移动或溢出到 asm 周围的堆栈中。尽管如此,它仍然没有-ffixed-r12阻止编译器使用r12整个源文件中的任何地方那么残酷。


未来的读者应该注意,通常在 x86-64 上修改内联 asm 中的堆栈指针是不安全的。编译器假定内联 asm 不会更改它,并且它可以随时rsp通过具有相对于 的恒定偏移量的有效地址访问堆栈变量。rsp而且x86-64使用了红色区域,所以即使是push/pop一对也不安全,因为下面可能存储了重要的数据rsp。(而意外pop可能意味着其他重要数据不再在红色区域,因此会被信号处理程序覆盖。)因此,除非您愿意在每次重新编译后仔细阅读生成的程序集以确保编译器没有决定执行任何这些操作,否则您不应该这样做事物。(在你问之前,你不能通过声明一个rsp; 的破坏来解决这个问题,这是不受支持的。)


推荐阅读