海湾合作委员会:禁止使用某些登记册

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.


Has*_*kun 5

如果使用全局显式寄存器变量,它们将在整个编译单元中保留,并且编译器不会将其用于任何其他内容(它可能仍由系统库使用,因此选择将由这些库恢复的内容).本地寄存器变量不保证您的值始终在寄存器中,但仅在代码引用或作为asm操作数引用时.


Zan*_*ynx 5

如果为新指令编写内联asm块,则会有一些命令通知GCC该块使用哪些寄存器以及它们的使用方式.然后GCC将避免使用这些寄存器或至少保存和重新加载其内容.


Cir*_*四事件 5

内联汇编中的非硬编码暂存寄存器

\n\n

这不是对原始问题的直接答案,但自从我在这种情况下不断谷歌搜索以来,并且由于/sf/answers/467822841/被接受,我将尝试提供一个对该答案的可能改进。

\n\n

改进如下:您应该尽可能避免对暂存寄存器进行硬编码,以便为寄存器分配器提供更多自由。

\n\n

因此,作为一个在实践中无用的教育示例(可以在单个 中完成lea (%[in1], %[in2]), %[out];),以下硬编码的暂存寄存器代码:

\n\n

坏.c

\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    __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}\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果您使用这个非硬编码版本,可以编译成更有效的东西:

\n\n

好.c

\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}\n
Run Code Online (Sandbox Code Playgroud)\n\n

因为编译器可以自由选择它想要的任何寄存器,而不仅仅是rax

\n\n

请注意,在本示例中,我们必须将暂存标记为早期破坏寄存器,&以防止将其作为输入放入同一寄存器中,我已在以下位置对此进行了更详细的解释:何时在扩展 GCC 内联汇编中使用早期破坏约束?这个例子在我测试的没有&.

\n\n

在Ubuntu 18.10 amd64、GCC 8.2.0中测试,编译并运行:

\n\n
gcc -O3 -std=c99 -ggdb3 -Wall -Werror -pedantic -o good.out good.c\n./good.out\n
Run Code Online (Sandbox Code Playgroud)\n\n

GCC 手册6.45.2.6“Clobbers and Scratch Registers”中也提到了非硬编码暂存寄存器,尽管它们的示例对于普通人来说太多了,无法立即理解:

\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\n
static 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
Run Code Online (Sandbox Code Playgroud)\n
\n