这是一个奇怪的请求,但我觉得它有可能。我想要的是在我的代码区域中插入一些pragma或指令(用C语言编写),这样GCC的寄存器分配器将不使用它们。
我知道我可以做这样的事情,可能为这个变量留出这个寄存器
register int var1 asm ("EBX") = 1984;
register int var2 asm ("r9") = 101;
问题是我正在直接插入新指令(用于硬件模拟器),而GCC和GAS尚未识别这些指令。我的新指令可以使用现有的通用寄存器,我想确保我保留了一些(即r12-> r15)。
现在,我正在模拟环境中工作,我想快速完成我的实验。将来我会添加GAS并将内在函数添加到GCC中,但是现在我正在寻找快速修复。
谢谢!
答案 0 :(得分:15)
编写GCC内联汇编程序时,可以指定“clobber list” - 可能被内联汇编程序代码覆盖的寄存器列表。然后,GCC将在内联asm段的过程中执行保存和恢复这些寄存器中的数据(或首先避免使用它们)所需的任何操作。您还可以将输入或输出寄存器绑定到C变量。
例如:
inline unsigned long addone(unsigned long v)
{
unsigned long rv;
asm("mov $1, %%eax;"
"mov %0, %%ebx;"
"add %%eax, %%ebx"
: /* outputs */ "b" (rv)
: /* inputs */ "g" (v) /* select unused general purpose reg into %0 */
: /* clobbers */ "eax"
);
}
有关详细信息,请参阅GCC-Inline-Asm-HOWTO。
答案 1 :(得分:5)
如果使用global explicit register variables,这些将在整个编译单元中保留,并且编译器不会将其用于任何其他内容(它可能仍被系统的库使用,因此选择将要恢复的内容由那些)。本地寄存器变量不保证您的值始终在寄存器中,但仅在代码引用或作为asm
操作数引用时。
答案 2 :(得分:5)
如果为新指令编写内联asm块,则会有一些命令通知GCC该块使用哪些寄存器以及它们的使用方式。然后GCC将避免使用这些寄存器或至少保存并重新加载其内容。
答案 3 :(得分:2)
内联汇编中的非硬编码暂存器
这不是原始问题的直接答案,但是由于并且由于我一直在这种情况下使用Google搜索,并且自https://stackoverflow.com/a/6683183/895245被接受以来,我将尝试为该答案提供可能的改进。
改进如下:应尽可能避免对暂存器进行硬编码,以使寄存器分配器具有更大的自由度。
因此,作为一个实际上没有用的教育示例(可以在单个lea (%[in1], %[in2]), %[out];
中完成),下面的硬编码暂存器代码是
#include <assert.h>
#include <inttypes.h>
int main(void) {
uint64_t in1 = 0xFFFFFFFF;
uint64_t in2 = 1;
uint64_t out;
__asm__ (
"mov %[in2], %%rax;" /* scratch = in2 */
"add %[in1], %%rax;" /* scratch += in1 */
"mov %%rax, %[out];" /* out = scratch */
: [out] "=r" (out)
: [in1] "r" (in1),
[in2] "r" (in2)
: "rax"
);
assert(out == 0x100000000);
}
如果您改用以下非硬编码版本,则可以将其编译为更有效的版本:
#include <assert.h>
#include <inttypes.h>
int main(void) {
uint64_t in1 = 0xFFFFFFFF;
uint64_t in2 = 1;
uint64_t out;
uint64_t scratch;
__asm__ (
"mov %[in2], %[scratch];" /* scratch = in2 */
"add %[in1], %[scratch];" /* scratch += in1 */
"mov %[scratch], %[out];" /* out = scratch */
: [scratch] "=&r" (scratch),
[out] "=r" (out)
: [in1] "r" (in1),
[in2] "r" (in2)
:
);
assert(out == 0x100000000);
}
由于编译器可以自由选择所需的任何寄存器,而不仅仅是rax
请注意,在此示例中,我们必须使用&
将暂存器标记为早期的缓存器寄存器,以防止将其作为输入放入同一个寄存器中,我在以下详细说明了:{{ 3}}在没有&
的情况下,此示例在我测试的实现中也碰巧失败。
在Ubuntu 18.10 amd64,GCC 8.2.0中进行了测试,编译并运行:
gcc -O3 -std=c99 -ggdb3 -Wall -Werror -pedantic -o good.out good.c
./good.out
在When to use earlyclobber constraint in extended GCC inline assembly? 6.45.2.6“ Clobbers和Scratch寄存器”中也提到了非硬编码的暂存器,尽管它们的例子对于凡人都无法立即使用:
与其通过Clobbers分配固定寄存器来为asm语句提供暂存器,还可以定义变量并将其设置为早期生成器输出,如下面的示例中的a2和a3所示。这赋予了编译器寄存器分配器更多的自由。您还可以定义一个变量并使它的输出绑定到输入,就像a0和a1分别绑定到ap和lda。当然,对于绑定输出,由于它们是同一寄存器,因此在修改输出寄存器后,您的asm无法使用输入值。而且,如果您在输出中省略了早期代码,那么如果GCC可以证明它们在输入asm时具有相同的值,则GCC可能会将相同的寄存器分配给另一个输入。这就是为什么a1具有早期优势的原因。可以想象,它的并列输入lda具有值16,并且没有早期指令共享与%11相同的寄存器。另一方面,ap不能与其他任何输入相同,因此不需要a0上的早期提示。在这种情况下也是不希望的。 a0上的早期中断会导致GCC为“ m”((const double()[])ap)输入分配一个单独的寄存器。注意,将输入绑定到输出是设置由asm语句修改的初始化临时寄存器的方法。 GCC认为与输出无关的输入是不变的,例如,下面的“ b”(16)将%11设置为16,如果碰巧需要值16,则GCC可以在以下代码中使用该寄存器。如果在使用暂存器之前已消耗了可能共享同一寄存器的所有输入,则甚至可以将普通的asm输出用于暂存器。除了GCC对asm参数数量的限制外,被asm语句破坏的VSX寄存器可能已经使用了此技术。
static void dgemv_kernel_4x4 (long n, const double *ap, long lda, const double *x, double *y, double alpha) { double *a0; double *a1; double *a2; double *a3; __asm__ ( /* lots of asm here */ "#n=%1 ap=%8=%12 lda=%13 x=%7=%10 y=%0=%2 alpha=%9 o16=%11\n" "#a0=%3 a1=%4 a2=%5 a3=%6" : "+m" (*(double (*)[n]) y), "+&r" (n), // 1 "+b" (y), // 2 "=b" (a0), // 3 "=&b" (a1), // 4 "=&b" (a2), // 5 "=&b" (a3) // 6 : "m" (*(const double (*)[n]) x), "m" (*(const double (*)[]) ap), "d" (alpha), // 9 "r" (x), // 10 "b" (16), // 11 "3" (ap), // 12 "4" (lda) // 13 : "cr0", "vs32","vs33","vs34","vs35","vs36","vs37", "vs40","vs41","vs42","vs43","vs44","vs45","vs46","vs47" ); }