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

时间:2011-07-13 17:04:41

标签: c gcc assembly gas register-allocation

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

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

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

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

现在,我正在模拟环境中工作,我想快速完成我的实验。将来我会添加GAS并将内在函数添加到GCC中,但是现在我正在寻找快速修复。

谢谢!

4 个答案:

答案 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];中完成),下面的硬编码暂存器代码是

bad.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);
}
如果您改用以下非硬编码版本,则

可以将其编译为更有效的版本:

good.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

请注意,在此示例中,我们必须使用&将暂存器标记为早期的缓存器寄存器,以防止将其作为输入放入同一个寄存器中,我在以下详细说明了:{{ 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"
     );
}