臂组件:can’;在类‘中找不到寄存器;一般规则’;重新加载时‘;asm&x2019;
我试图在ARM Cortex-a8上的ARM assembly中实现一个将32位操作数与256位操作数相乘的函数。问题是我的寄存器用完了,我不知道如何减少这里使用的寄存器的数量。以下是我的功能:臂组件:can’;在类‘中找不到寄存器;一般规则’;重新加载时‘;asm&x2019;,c,gcc,arm,inline-assembly,C,Gcc,Arm,Inline Assembly,我试图在ARM Cortex-a8上的ARM assembly中实现一个将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,
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列表,但我仍然得到了相同的错误一个简单的解决方案是打破这一点,不使用“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内联汇编程序”的全部目的不是直接在“C”文件中编写汇编程序。它是使用编译器的寄存器分配逻辑和做一些在“C”中不容易做的事情。在您的案例中使用进位逻辑
通过不使其成为一个巨大的“asm”子句,编译器可以在需要新寄存器时从内存调度加载。它还将通过加载/存储单元为您的“UMULL”ALU活动提供管道
只有当指令隐式地对特定寄存器执行了clobber操作时,才应该使用clobber。你也可以使用类似
register int *p1 asm ("r0");
并将其用作输出。然而,除了那些可能改变堆栈的ARM指令之外,我不知道还有任何类似的ARM指令,当然,您的代码不使用这些指令和进位指令
GCC知道,如果内存被列为输入/输出,那么它会发生变化,所以您不需要内存阻塞器。事实上,这是有害的,因为内存阻塞器是一个错误,这将导致在编译器可以为后者调度内存时写入内存
寓意是使用gcc内联汇编程序与编译器一起工作。如果您在汇编程序中编写代码,并且有大量例程,那么寄存器的使用可能会变得复杂和混乱。典型的汇编代码编写者在每个例程的寄存器中只保留一个东西,但这并不总是寄存器的最佳用途。当代码大小变大时,编译器将以一种相当智能的方式对数据进行洗牌,这是很难克服的(而且对于手工编写代码IMO来说也不是很令人满意)
您可能想看看哪种方法可以有效地解决代码中的一些问题。asm语句有一个更大的问题。您需要将在asm语句中显式指定的所有寄存器添加到clobber列表中(该列表还需要包含“cc”)。这些clobber加上保存输入和输出操作数所需的所有寄存器(也需要标记为早期clobber)意味着您使用的寄存器比ARM多得多。您上次的尝试只会让问题变得更糟。@RossRidge有没有办法在输入之前使用另一种符号而不是
“r”
,从而得到正确的结果?我的意思是像“g”
或“m”
?你真的需要一个循环[迭代次数为8]而不是你正在做的。重新考虑:如果你的输入向量有20000个元素,你会怎么做?您需要为标量A
值设置reg,为B
ptr设置reg,为res
ptr设置reg,为迭代计数设置reg,以及为每个循环迭代设置umull等人(可能需要另外设置4-6个)所需的任何其他reg,因此总数为~10。实际上,向量大小为2-3的regs已经用完,更不用说8了。为了让你的向量算法更清晰,我们来编写一个C fnc,它可以做到这一点[也可以作为你asm fnc的参考]。首先感谢你的简短回答。我有一个问题,如果我想从这个函数中获得最佳性能,在内联汇编中实现所有代码不是更好吗?我知道这里的寄存器可能用完了,但我正在考虑将指针(*B
和*res
)放在两个寄存器中,并使用ldr
指令访问每个uint32\t
数组。我不确定这是否可能,但性能对我来说非常重要是的,您可以在代码中使用ldr
;我想这是一个基准点。编译器具有交错ALU和加载/存储(ldr/str
)指令的特性和智能。它甚至可以执行ldm/stm
和/或ldrd/strd
,这取决于内存顺序、可用寄存器以及CPU类别(您将为A8硬编码,不执行加载/存储将允许编译器选择)。无论如何,您可以执行一个大型例程并使用ldr
,因为ARM寄存器已经用完了。但是,我认为如果您拆分并查看输出,您会发现很多。您可能会惊讶于您的算法是内存受限/占主导地位的,而不是CPU受限/占主导地位的。可以肯定的是,您当前的大型例程不允许这些操作并行进行(人们也说内存暂停)。我相信,在看到编译器的输出后,您可以击败它。如果我看不到它能做些什么的话,我很难(在更大的程序中)打败它。目前,您有9/25~=36%的指令作为mov
语句,并且还没有考虑编译器加载/存储东西所做的工作。再次感谢您,我将按照您所说的那样实现它,并将性能与我的C实现进行比较,并将结果放在这里。使用进位时,这有一些限制。在asm
语句之间不能有分支(这可能会设置进位)。因此,最纯的“C”可能会被拒绝(但它已经是asm)。在任何情况下,它都提供了一个更好的起点(编译器寄存器分配和内存调度)