gcc内联ARM程序集中的`ldm / stm`
我正在尝试使用内联汇编创建一个ldm
( stm
)指令,但是在表达操作数方面存在问题(特别是:它们的顺序)。
一件微不足道的事
void *ptr; unsigned int a; unsigned int b; __asm__("ldm %0!,{%1,%2}" : "+&r"(ptr), "=r"(a), "=r"(b));
不起作用,因为它可能会将r1
和b
放入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] }" ...)
速度在这里很重要, ldm
比ldr
快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
需要在执行时完成第一个寄存器计算; 但这不那么重要。