我正试图创建一个ldm
(resp.stm
)具有内联汇编的指令,但在表示操作数(尤其是其顺序)时有问题。
琐碎的
void *ptr;
unsigned int a;
unsigned int b;
__asm__("ldm %0!,{%1,%2}" : "+&r"(ptr), "=r"(a), "=r"(b));
不起作用,因为它可能将
a
放入r1
并将b
放入r0
:ldm ip!, {r1, r0}
ldm
需要按升序排列的寄存器(因为它们是在位字段中编码的),所以我需要一种方式来说明a
使用的寄存器低于b
的寄存器。一个简单的方法是固定的寄存器分配:
register unsigned int a asm("r0");
register unsigned int b asm("r1");
__asm__("ldm %0!,{%1,%2}" : "+&r"(ptr), "=r"(a), "=r"(b));
但这会消除很多灵活性,并可能导致生成的代码不理想。
GCC(4.8)是否支持对
ldm/stm
的特殊约束?或者,有没有更好的方法来解决这个问题(例如,一些__builtin
函数)?编辑:
因为有人建议使用“更高层次”的结构…我要解决的问题是将一个32位字的20位打包(例如,输入是8个字,输出是5个字)。伪代码是
asm("ldm %[in]!,{ %[a],%[b],%[c],%[d] }" ...)
asm("ldm %[in]!,{ %[e],%[f],%[g],%[h] }" ...) /* splitting of ldm generates better code;
gcc gets out of registers else */
/* do some arithmetic on a - h */
asm volatile("stm %[out]!,{ %[a],%[b],%[c],%[d],%[e] }" ...)
这里的速度很重要,
ldm
比ldr
快50%。这个算法很复杂,因为gcc
生成的代码比我好得多;)我想在内联程序集中解决这个问题,并给出一些关于优化内存访问的提示。 最佳答案
我在ARM memtest中推荐了相同的解决方案。即,显式地分配寄存器。analysis on gcc-help是错误的。不需要重新编写gcc的寄存器分配。唯一需要做的是允许在汇编程序规范中对寄存器进行排序。
也就是说下面的人会聚集在一起,
int main(void)
{
void *ptr;
register unsigned int a __asm__("r1");
register unsigned int b __asm__("r0");
__asm__("ldm %0!,{%1,%2}" : "+&r"(ptr), "=r"(a), "=r"(b));
return 0;
}
这不会编译,因为在我的gcc中有一个非法的arm指令
ldm r3!,{r1,r0}
。一种解决方案是使用-s标志只进行汇编,然后运行一个脚本,该脚本将对ldm
/stm
操作数进行排序。Perl很容易做到这一点,$reglist = join(',', sort(split(',', $reglist)));
或者其他方式。不幸的是,似乎根本没有使用汇编程序约束来实现这一点。如果我们有权访问指定的寄存器号,则可以使用内联替代编译或条件编译。
可能最简单的解决方案是使用显式寄存器分配。除非您编写的向量库需要加载/存储多个值,并且您希望给编译器一些自由来生成更好的代码。在这种情况下,最好使用结构,因为更高级别的gcc优化将能够检测不需要的操作(例如乘以一或加零等)。
编辑:
因为有人建议使用“更高层次”的结构…我要解决的问题是将一个32位字的20位打包(例如,输入是8个字,输出是5个字)。
这可能会有更好的结果,
u32 *ip, *op;
u32 in, out, mask;
int shift = 0;
const u32 *op_end = op + 5;
while(op != op_end) {
in = *ip++;
/* mask and accumulate... */
if(shift >= 32) {
*op++ = out;
shift -=32;
}
}
原因是arm管道通常分为几个阶段。带有单独的装载/储存装置。算术运算可以与加载和存储并行进行。所以你可以在加载后面的单词时处理第一个单词。在这种情况下,您还可以替换适当的值,这将给缓存带来好处,除非您需要重新使用20位值。一旦代码在缓存中,
ldm/stm
就没有什么好处了。那将是你的情况。第二次编辑:编译器的主要工作是不从内存中加载值。也就是说,注册分配是至关重要的。通常,
ldm
/stm
在内存传输函数中最有用。例如,一个内存测试,一个memcpy()
实现,等等。如果您使用数据进行计算,那么编译器可能对管道调度有更好的了解。您可能需要接受普通的“C”代码,或者移动以完成汇编程序。记住,ldm
有第一个可立即使用的操作数。在后续寄存器中使用ALU可能会导致数据加载暂停。类似地,stm
在执行时需要完成第一个寄存器的计算;但这并不那么重要。