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

时间:2013-12-17 10:39:09

标签: c gcc assembly arm inline-assembly

我正在尝试使用内联汇编创建ldm(resp。stm)指令,但在表达操作数​​方面存在问题(尤其是:它们的顺序)。

琐碎的

void *ptr;
unsigned int a;
unsigned int b;

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

不起作用,因为它可能会a r1b加入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] }" ...)

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

1 个答案:

答案 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需要在执行时完成第一个寄存器计算;但这不太重要。