我正在尝试使用内联汇编创建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位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
生成的代码比我好得多;)我想在内联汇编中解决它,并给出一些关于优化内存访问的提示。
答案 0 :(得分:1)
我在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位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
需要在执行时完成第一个寄存器计算;但这不太重要。