hay*_*sti 22 c assembly gcc gnu-assembler register-allocation
这是一个奇怪的要求,但我觉得它有可能.我想要的是将一些编译指示或指令插入到我的代码区域(用C编写),以便GCC的寄存器分配器不会使用它们.
我知道我可以做这样的事情,这可能会为这个变量留下这个寄存器
register int var1 asm ("EBX") = 1984;
register int var2 asm ("r9") = 101;
Run Code Online (Sandbox Code Playgroud)
问题是我直接插入新指令(用于硬件模拟器),而GCC和GAS还没有识别出这些指令.我的新指令可以使用现有的通用寄存器,我想确保我保留了一些(即r12-> r15).
现在,我在一个模拟环境中工作,我想快速做我的实验.将来我会添加GAS并将内在函数添加到GCC中,但是现在我正在寻找快速修复.
谢谢!
bdo*_*lan 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"
);
}
Run Code Online (Sandbox Code Playgroud)
有关更多信息,请参阅GCC-Inline-Asm-HOWTO.
内联汇编中的非硬编码暂存寄存器
\n\n这不是对原始问题的直接答案,但自从我在这种情况下不断谷歌搜索以来,并且由于/sf/answers/467822841/被接受,我将尝试提供一个对该答案的可能改进。
\n\n改进如下:您应该尽可能避免对暂存寄存器进行硬编码,以便为寄存器分配器提供更多自由。
\n\n因此,作为一个在实践中无用的教育示例(可以在单个 中完成lea (%[in1], %[in2]), %[out];),以下硬编码的暂存寄存器代码:
#include <assert.h>\n#include <inttypes.h>\n\nint main(void) {\n uint64_t in1 = 0xFFFFFFFF;\n uint64_t in2 = 1;\n uint64_t out;\n __asm__ (\n "mov %[in2], %%rax;" /* scratch = in2 */\n "add %[in1], %%rax;" /* scratch += in1 */\n "mov %%rax, %[out];" /* out = scratch */\n : [out] "=r" (out)\n : [in1] "r" (in1),\n [in2] "r" (in2)\n : "rax"\n );\n assert(out == 0x100000000);\n}\nRun Code Online (Sandbox Code Playgroud)\n\n如果您使用这个非硬编码版本,可以编译成更有效的东西:
\n\n\n\n#include <assert.h>\n#include <inttypes.h>\n\nint main(void) {\n uint64_t in1 = 0xFFFFFFFF;\n uint64_t in2 = 1;\n uint64_t out;\n uint64_t scratch;\n __asm__ (\n "mov %[in2], %[scratch];" /* scratch = in2 */\n "add %[in1], %[scratch];" /* scratch += in1 */\n "mov %[scratch], %[out];" /* out = scratch */\n : [scratch] "=&r" (scratch),\n [out] "=r" (out)\n : [in1] "r" (in1),\n [in2] "r" (in2)\n :\n );\n assert(out == 0x100000000);\n}\nRun Code Online (Sandbox Code Playgroud)\n\n因为编译器可以自由选择它想要的任何寄存器,而不仅仅是rax,
请注意,在本示例中,我们必须将暂存标记为早期破坏寄存器,&以防止将其作为输入放入同一寄存器中,我已在以下位置对此进行了更详细的解释:何时在扩展 GCC 内联汇编中使用早期破坏约束?这个例子在我测试的没有&.
在Ubuntu 18.10 amd64、GCC 8.2.0中测试,编译并运行:
\n\ngcc -O3 -std=c99 -ggdb3 -Wall -Werror -pedantic -o good.out good.c\n./good.out\nRun Code Online (Sandbox Code Playgroud)\n\nGCC 手册6.45.2.6“Clobbers and Scratch Registers”中也提到了非硬编码暂存寄存器,尽管它们的示例对于普通人来说太多了,无法立即理解:
\n\n\n\n与其通过 clobber 分配固定寄存器来为 asm 语句提供暂存寄存器,另一种方法是定义一个变量并使其成为早期 clobber 输出,如下面示例中的 a2 和 a3 所示。这给了编译器寄存器分配器更多的自由。您还可以定义一个变量,并使其成为与输入绑定的输出,就像 a0 和 a1 一样,分别与 ap 和 lda 绑定。当然,对于绑定输出,您的 asm 不能在修改输出寄存器后使用输入值,因为它们是同一个寄存器。\xe2\x80\x99s 更重要的是,如果省略输出中的早期破坏,如果 GCC 可以证明它们在进入 asm 时具有相同的值,则 GCC 可能会将相同的寄存器分配给另一个输入。这就是为什么a1有一个早期的失败者。可以想象,其绑定输入 lda 的值为 16,并且没有早期破坏者与 %11 共享相同的寄存器。另一方面, ap 可以\xe2\x80\x99t 与任何其他输入相同,因此不需要对 a0 进行早期破坏。在这种情况下也是不可取的。a0 上的早期破坏将导致 GCC 为“m”((const double ( )[]) ap)输入分配一个单独的寄存器。请注意,将输入绑定到输出是设置由 asm 语句修改的初始化临时寄存器的方法。GCC 假定未与输出绑定的输入保持不变,例如下面的“b”(16) 将 %11 设置为 16,如果碰巧需要值 16,GCC 可能会在以下代码中使用该寄存器。如果在使用暂存之前消耗了可能共享同一寄存器的所有输入,您甚至可以使用普通的 asm 输出进行暂存。除了 GCC\xe2\x80\x99s 对 asm 参数数量的限制之外,被 asm 语句破坏的 VSX 寄存器也可以使用此技术。
\n\nRun Code Online (Sandbox Code Playgroud)\nstatic void\ndgemv_kernel_4x4 (long n, const double *ap, long lda,\n const double *x, double *y, double alpha)\n{\n double *a0;\n double *a1;\n double *a2;\n double *a3;\n\n __asm__\n (\n /* lots of asm here */\n "#n=%1 ap=%8=%12 lda=%13 x=%7=%10 y=%0=%2 alpha=%9 o16=%11\\n"\n "#a0=%3 a1=%4 a2=%5 a3=%6"\n :\n "+m" (*(double (*)[n]) y),\n "+&r" (n), // 1\n "+b" (y), // 2\n "=b" (a0), // 3\n "=&b" (a1), // 4\n "=&b" (a2), // 5\n "=&b" (a3) // 6\n :\n "m" (*(const double (*)[n]) x),\n "m" (*(const double (*)[]) ap),\n "d" (alpha), // 9\n "r" (x), // 10\n "b" (16), // 11\n "3" (ap), // 12\n "4" (lda) // 13\n :\n "cr0",\n "vs32","vs33","vs34","vs35","vs36","vs37",\n "vs40","vs41","vs42","vs43","vs44","vs45","vs46","vs47"\n );\n}\n