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

ens*_*nsc 5 c assembly gcc 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));
Run Code Online (Sandbox Code Playgroud)

不工作,因为它可能把a到r1和b成r0:

ldm ip!, {r1, r0}
Run Code Online (Sandbox Code Playgroud)

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));
Run Code Online (Sandbox Code Playgroud)

但这会消除很多灵活性,并可能使生成的代码不是最佳的.

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] }" ...)
Run Code Online (Sandbox Code Playgroud)

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

art*_*ise 1

我在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;
}
Run Code Online (Sandbox Code Playgroud)

这不会编译,因为在我的 gcc 中存在非法 ARM 指令ldm r3!,{r1,r0}。解决方案是使用-Sldm标志仅进行汇编,然后运行对/操作数进行排序的脚本stm。Perl 可以轻松地做到这一点,

$reglist = join(',', sort(split(',', $reglist)));
Run Code Online (Sandbox Code Playgroud)

或者任何其他方式。不幸的是,似乎没有办法使用汇编器约束来做到这一点。如果我们可以访问指定的寄存器号,则可以使用内联替代或条件编译。

也许最简单的解决方案是使用显式寄存器分配。除非您正在编写需要加载/存储多个值的向量库,并且您希望给编译器一些自由来生成更好的代码。在这种情况下,最好使用结构,因为更高级别的 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;
     }
  }
Run Code Online (Sandbox Code Playgroud)

原因是ARM流水线通常是几个阶段。具有单独的加载/存储单元。ALU(算术)可以与加载和存储并行进行。因此,您可以在加载后面的单词时处理第一个单词。在这种情况下,您还可以就地替换该值,这将带来缓存优势,除非您需要重新使用 20 位值。一旦代码进入缓存,ldm/stm如果您在数据上停滞,则没有什么好处。那将是你的情况。

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