我正试图创建一个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] }" ...)

这里的速度很重要,ldmldr快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在执行时需要完成第一个寄存器的计算;但这并不那么重要。

08-16 11:23