23

这是一个奇怪的要求,但我觉得这是可能的。我想要的是在我的代码区域(用 C 语言编写)中插入一些编译指示或指令,以便 GCC 的寄存器分配器不会使用它们。

我知道我可以做这样的事情,这可能会为这个变量留出这个寄存器

register int var1 asm ("EBX") = 1984;
register int var2 asm ("r9") = 101;

问题是我直接插入新指令(用于硬件模拟器),而 GCC 和 GAS 还不能识别这些指令。我的新指令可以使用现有的通用寄存器,我想确保保留其中的一些(即 r12->r15)。

现在,我在一个模型环境中工作,我想快速做我的实验。将来我将附加 GAS 并将内在函数添加到 GCC 中,但现在我正在寻找一个快速修复。

谢谢!

4

4 回答 4

18

在编写 GCC 内联汇编程序时,您可以指定一个“clobber 列表”——一个可能被您的内联汇编程序代码覆盖的寄存器列表。然后,GCC 将在 inline 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

于 2011-07-13T17:41:42.070 回答
5

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

于 2011-07-13T17:12:27.123 回答
5

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

于 2011-07-13T17:13:22.153 回答
4

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

这不是对原始问题的直接答案,但是自从我在这种情况下一直在谷歌搜索并且自从https://stackoverflow.com/a/6683183/895245被接受以来,我将尝试提供一个可能的改进该答案。

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

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

坏的.c

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

如果您改为使用此非硬编码版本,则可以编译为更有效的东西:

好.c

#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,

请注意,在此示例中,我们必须将临时标记为早期 clobber 寄存器,&以防止将其作为输入放入相同的寄存器中,我已在以下位置更详细地解释了这一点:何时在扩展的 GCC 内联汇编中使用 earlyclobber 约束? 这个例子在我没有测试的实现中也碰巧失败了&

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

gcc -O3 -std=c99 -ggdb3 -Wall -Werror -pedantic -o good.out good.c
./good.out

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

与通过clobbers 分配固定寄存器来为asm 语句提供临时寄存器不同,另一种方法是定义一个变量并使其成为早期的clobber 输出,如下例中的a2 和a3 所示。这给了编译器寄存器分配器更多的自由。您还可以定义一个变量并将其作为与输入相关联的输出,如 a0 和 a1,分别与 ap 和 lda 相关联。当然,对于绑定输出,您的 asm 在修改输出寄存器后不能使用输入值,因为它们是同一个寄存器。更重要的是,如果您在输出中省略 early-clobber,如果 GCC 可以证明它们在进入 asm 时具有相同的值,那么 GCC 可能会将相同的寄存器分配给另一个输入。这就是为什么 a1 有一个 early-clobber。它的绑定输入,可以想象,lda 的值是 16,并且没有早期的 clobber,它与 %11 共享相同的寄存器。另一方面,ap 不能与任何其他输入相同,因此不需要 a0 上的 early-clobber。在这种情况下也是不可取的。a0 上的早期破坏会导致 GCC 为“m”分配一个单独的寄存器((const double ( )[]) ap) 输入。请注意,将输入绑定到输出是设置由 asm 语句修改的初始化临时寄存器的方法。GCC 假定未绑定到输出的输入不变,例如下面的“b”(16) 将 %11 设置为 16,如果碰巧需要值 16,GCC 可能会在以下代码中使用该寄存器。如果在使用暂存器之前消耗了可能共享同一寄存器的所有输入,您甚至可以使用普通 asm 输出作为暂存器。被 asm 语句破坏的 VSX 寄存器可以使用这种技术,除了 GCC 对 asm 参数数量的限制。

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"
     );
}
于 2019-03-02T22:51:12.557 回答