gcc内联ARM程序集中的`ldm / stm`

我正在尝试使用内联汇编创建一个ldmstm )指令,但是在表达操作数​​方面存在问题(特别是:它们的顺序)。

一件微不足道的事

 void *ptr; unsigned int a; unsigned int b; __asm__("ldm %0!,{%1,%2}" : "+&r"(ptr), "=r"(a), "=r"(b)); 

不起作用,因为它可能会将r1b放入r0

 ldm ip!, {r1, r0} 

ldm期望寄存器按升序排列(因为它们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位32位字的打包(例如输入是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] }" ...) 

速度在这里很重要, ldmldr快50%。 这个算法很棘手,因为gcc生成的代码比我好得多;)我想在内联汇编中解决它,给出一些关于优化内存访问的提示。

我在ARM memtest中推荐了相同的解决方案。 即,明确分配寄存器。 对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优化将能够检测不需要的操作(例如乘以1或加等)。

编辑:

因为有建议使用“更高级别”的结构……我想解决的问题是32位32位字的打包(例如输入是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管道通常是几个阶段。 带有单独的装载/存储单元。 ALU(算术)可以与加载和存储并行进行。 所以你可以在加载后来的单词时处理第一个单词。 在这种情况下,除非需要重新使用20位值,否则也可以替换将提供缓存优势的就地值。 一旦代码在缓存中,如果停止数据, ldm/stm几乎没有什么好处。 这将是你的情况。

第二次编辑:编译器的主要工作是不从内存加载值。 即,注册分配至关重要。 通常, ldm / stm在存储器传输函数中最有用。 即,内存测试, memcpy()实现等。如果您正在使用数据进行计算,那么编译器可能对管道调度有更好的了解。 您可能需要接受简单的“C”代码或移动到完成汇编程序。 请记住, ldm具有可立即使用的第一个操作数。 将ALU与后续寄存器一起使用可能会导致数据加载停顿。 类似地, stm需要在执行时完成第一个寄存器计算; 但这不那么重要。