【问题标题】:ARM assembly: can’t find a register in class ‘GENERAL_REGS’ while reloading ‘asm’ARM 程序集:重新加载“asm”时在“GENERAL_REGS”类中找不到寄存器
【发布时间】:2016-03-19 10:53:38
【问题描述】:

我正在尝试在 ARM Cortex-a8 上的 ARM 程序集中实现一个将 32 位操作数与 256 位操作数相乘的函数。问题是我的寄存器用完了,我不知道如何减少这里使用的寄存器的数量。这是我的功能:

typedef struct UN_256fe{

uint32_t uint32[8];

}UN_256fe;

typedef struct UN_288bite{

uint32_t uint32[9];

}UN_288bite;
void multiply32x256(uint32_t A, UN_256fe* B, UN_288bite* res){

asm (

        "umull          r3, r4, %9, %10;\n\t"
        "mov            %0, r3;         \n\t"/*res->uint32[0] = r3*/
        "umull          r3, r5, %9, %11;\n\t"
        "adds           r6, r3, r4;     \n\t"/*res->uint32[1] = r3 + r4*/
        "mov            %1, r6;         \n\t"
        "umull          r3, r4, %9, %12;\n\t"
        "adcs           r6, r5, r3;     \n\t"
        "mov            %2, r6;         \n\t"/*res->uint32[2] = r6*/
        "umull          r3, r5, %9, %13;\n\t"
        "adcs           r6, r3, r4;     \n\t"
        "mov            %3, r6;         \n\t"/*res->uint32[3] = r6*/
        "umull          r3, r4, %9, %14;\n\t"
        "adcs           r6, r3, r5;     \n\t"
        "mov            %4, r6;         \n\t"/*res->uint32[4] = r6*/
        "umull          r3, r5, %9, %15;\n\t"
        "adcs           r6, r3, r4;     \n\t"
        "mov            %5, r6;         \n\t"/*res->uint32[5] = r6*/
        "umull          r3, r4, %9, %16;\n\t"
        "adcs           r6, r3, r5;     \n\t"
        "mov            %6, r6;         \n\t"/*res->uint32[6] = r6*/
        "umull          r3, r5, %9, %17;\n\t"
        "adcs           r6, r3, r4;     \n\t"
        "mov            %7, r6;         \n\t"/*res->uint32[7] = r6*/
        "adc            r6, r5, #0 ;    \n\t"
        "mov            %8, r6;         \n\t"/*res->uint32[8] = r6*/

        : "=r"(res->uint32[8]), "=r"(res->uint32[7]), "=r"(res->uint32[6]), "=r"(res->uint32[5]), "=r"(res->uint32[4]),
           "=r"(res->uint32[3]), "=r"(res->uint32[2]), "=r"(res->uint32[1]), "=r"(res->uint32[0])
         : "r"(A), "r"(B->uint32[7]), "r"(B->uint32[6]), "r"(B->uint32[5]),
           "r"(B->uint32[4]), "r"(B->uint32[3]), "r"(B->uint32[2]), "r"(B->uint32[1]), "r"(B->uint32[0]), "r"(temp)
         : "r3", "r4", "r5", "r6", "cc", "memory");

}

EDIT-1:我根据第一条评论更新了我的clobber列表,但我仍然收到同样的错误

【问题讨论】:

  • 你的 asm 语句有更大的问题。您需要将您在 asm 语句中明确指定的所有寄存器添加到 clobber 列表(还需要包括“cc”)。这些 clobber 加上保存输入和输出操作数所需的所有寄存器(也需要标记为 early clobber)意味着您使用的寄存器比 ARM 更多。你上次的尝试只是让问题变得更糟。
  • @RossRidge 有什么方法可以在我的输入之前使用另一个符号而不是 "r" 并获得正确的结果?我的意思是"g""m"
  • 您确实需要一个循环[迭代计数为 8],而不是您正在做的事情。重新考虑:如果你的输入向量中有 20,000 个元素,你会怎么做?您需要 reg 表示标量 A 值,reg 表示 B ptr,reg 表示 res ptr,reg 表示迭代计数,以及您需要执行 umull 等的任何其他 reg。 al [可能另外 4-6] 在每次循环迭代中,所以总数约为 10。事实上,向量大小为 2-3 的 regs 用完了,更不用说 8 了。为了让你的向量算法直截了当,如何编写一个 C fnc 来执行此操作[也可以作为你 asm fnc 的参考]。

标签: c gcc arm inline-assembly


【解决方案1】:

一个简单的解决方案是将其分解,不要使用“clobber”。将变量声明为 'tmp1' 等。尽量不要使用任何mov 语句;如果必须,让编译器执行此操作。编译器将使用一种算法来找出最佳的信息“流”。如果你使用'clobber',它不能重用寄存器。他们现在的样子,你让它在汇编程序执行之前先加载所有内存。这很糟糕,因为您希望内存/CPU ALU 流水线化。

void multiply32x256(uint32_t A, UN_256fe* B, UN_288bite* res) 
{

  uint32_t mulhi1, mullo1;
  uint32_t mulhi2, mullo2;
  uint32_t tmp;

  asm("umull          %0, %1, %2, %3;\n\t"
       : "=r" (mullo1), "=r" (mulhi1)
       : "r"(A), "r"(B->uint32[7])
  );
  res->uint32[8] = mullo1; /* was 'mov %0, r3; */
  volatile asm("umull          %0, %1, %3, %4;\n\t"
      "adds           %2, %5, %6;     \n\t"/*res->uint32[1] = r3 + r4*/
     : "=r" (mullo2), "=r" (mulhi2), "=r" (tmp)
     : "r"(A), "r"(B->uint32[6]), "r" (mullo1), "r"(mulhi1)
     : "cc"
    );
  res->uint32[7] = tmp; /* was 'mov %1, r6; */
  /* ... etc */
}

“gcc inline assembler”的全部目的不是直接在“C”文件中编写汇编程序。就是利用编译器的寄存器分配逻辑AND做一些在'C'中不容易做到的事情。在您的情况下使用进位逻辑。

通过不让它成为一个巨大的“asm”子句,编译器可以在需要新寄存器时从内存中调度加载。它还将使用加载/存储单元处理您的“UMULL”ALU 活动。

只有在指令隐式破坏特定寄存器时才应使用 clobber。你也可以使用类似的东西,

register int *p1 asm ("r0");

并将其用作输出。但是,我不知道任何这样的 ARM 指令,除了那些可能会改变堆栈的指令,而且您的代码不使用这些指令,当然还有进位。

GCC 知道,如果将内存列为输入/输出,则内存会发生变化,因此您不需要 memory clobber。事实上,这是有害的,因为 memory clobber 是 compiler memory barrier,当编译器可能会为后者安排内存时,这将导致内存被写入。


道德是使用 gcc 内联汇编器与编译器一起工作。如果您在汇编程序中编码并且您有大量例程,那么寄存器的使用可能会变得复杂和混乱。典型的汇编程序编码器在每个例程中只会在一个寄存器中保存一件事,但这并不总是对寄存器的最佳使用。当代码变大时,编译器将以一种很难被击败的相当智能的方式对数据进行混洗(并且对于手工代码 IMO 不太满意)。

您可能想查看the GMP library,它有很多方法可以有效地解决一些与您的代码相似的问题。

【讨论】:

  • 首先感谢您的简短回答。我有一个问题,如果我想从这个函数中获得最好的性能,是不是最好在内联汇编中实现所有代码?我知道我可能会用完像这里这样的寄存器,但我正在考虑将我的指针(*B*res)放在两个寄存器中,并使用ldr 指令访问每个uint32_t 数组。我不确定这是否可能,但性能对我来说真的很重要
  • 是的,您可以在代码中使用ldr;我正在考虑将其作为基准。编译器具有交错 ALU 和加载/存储 (ldr/str) 指令的算法和智能。它甚至可能会执行ldm/stm 和/或ldrd/strd,具体取决于内存顺序以及可用的寄存器和 CPU 的类别(您将为 A8 硬编码,其中不执行加载/存储将允许编译器选择) .无论如何,你可以做一个大例程并使用ldr,因为你已经用完了ARM寄存器。但是,我认为如果您拆分并查看输出,您会发现很多。
  • 您可能会惊讶于您的算法受内存限制/支配,而不是 CPU 限制/支配。可以肯定的是,您当前的大型例程不会让这些操作 pipeline 或并行发生(人们也说内存停滞)。我相信你可以在看到它的输出后击败编译器。如果我事先没有看到它可能会做什么样的事情,我很少能击败它(在更大的例程中)。目前,您有 9/25 ~= 36% 的指令为 mov 语句,并且没有考虑编译器加载/存储内容的操作。
  • 再次感谢您,我会按照您说的实现它,并将性能与我的 C 实现进行比较,并将结果放在这里
  • 这在您使用随身携带时有一些限制。在asm 语句之间不能有分支(可能会设置进位)。因此,最纯粹的“C”可能会被排斥(但它已经是 asm)。无论如何,它提供了一个更好的起始基础(使用编译器寄存器分配和内存调度),您可以使用它。或者,也许您对几个“asm”块变体感到满意。只需将它们标记为 'volatile' 以让编译器知道它们的顺序很重要,并在下面的 'asm' 部分添加注释以不使用 'if/while/etc',因为我们希望保留它们之间的进位。跨度>
猜你喜欢
  • 1970-01-01
  • 2012-05-13
  • 1970-01-01
  • 1970-01-01
  • 2011-04-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多