如何防止ARM编译器5 armcc内联汇编程序中的LDM / STM指令扩展?

我正在尝试使用ARM编译器5 armcc编译的.c文件中的内联汇编中的STM / LDM指令生成AXI总线突发访问。

inline void STMIA2(uint32_t addr, uint32_t w0, uint32_t w1) { __asm { STMIA addr!, { w0, w1 } } } 

但ARM编译器armcc用户指南第7.18段说:“所有LDM和STM指令都扩展为LDR和STR指令序列,效果相同。但是,编译器可能会在优化期间将单独的指令重新组合成LDM或STM。 “

这就是实践中真正发生的事情,LDM / STM在某些情况下会扩展为一组LDR / STR,这些指令的顺序是任意的。 这会影响性能,因为我们使用的HW针对突发处理进行了优化。 这也打破了function正确性,因为我们使用的HW考虑了单词序列并忽略了偏移(但编译器认为改变指令的顺序是安全的)。

要解决这个问题,可以使用嵌入式汇编程序而不是内联汇编程序,但这会导致额外的函数调用 – 返回影响性能的因素。

所以我想知道是否有办法在不损失性能的情况下正确生成LDM / STM? 我们能够在GCC中做到这一点,但没有找到任何armcc的解决方案。

目标CPU:Cortex M0 +(ARMv6-M)。

编辑:从设备都是片上设备,大多数都是非内存设备。 对于支持突发访问区域的非内存从站的每个寄存器都保留了地址空间(例如[0x10000..0x10100]),我不完全确定为什么,也许CPU或总线不支持固定(非增量) )地址。 HW忽略该区域内的偏移。 例如,完整请求可以是16字节,并且完整请求的第一个字是第一个字写入的(即使偏移是非零)。

所以我想知道是否有办法在不损失性能的情况下正确生成LDM / STM? 我们能够在GCC中做到这一点,但没有找到任何armcc的解决方案。

关于编译器优化的一点点。 注册分配是其中最艰巨的工作之一。 任何编译器代码生成的核心可能是在分配物理CPU寄存器时。 大多数编译器使用单一静态赋值或SSA将“C”变量重命名为一堆伪变量(或时间顺序变量)。

为了使您的STMIA和LDMIA工作,您需要加载和存储保持一致。 即,如果它是stmia [rx], {r3,r7}并恢复像ldmia [rx], {r4,r8} ,其中’r3’映射到新的’r4’并且存储的’r7’映射到恢复’r8’。 对于任何编译器来说,这一点并不简单,因为“C”变量将根据需要进行分配。 同一变量的不同版本可能在不同的寄存器中。 要使stm/ldm工作,必须分配这些变量,以便寄存器以正确的顺序递增。 即,对于上面的ldmia ,如果编译器想要在r0存储r7 (可能是返回值?),则无法在不生成其他代码的情况下创建良好的ldm指令。

你可能已经得到了gcc来生成这个,但它可能是运气。 如果你只进行gcc,你可能会发现它不起作用。

有关GCC stm / ldm的问题,请参阅: ldm / stm和gcc 。

举个例子,

 inline void STMIA2(uint32_t addr, uint32_t w0, uint32_t w1) { __asm { STMIA addr!, { w0, w1 } } } 

inline的值是整个函数体可以放在代码中。 调用者可能在寄存器R8和R4中具有w0w1 。 如果函数不是inline函数,那么编译必须将它们放在R1和R2中,但可能产生了额外的动作。 任何编译器都难以一般地满足ldm/stm的要求。

这会影响性能,因为我们使用的HW针对突发处理进行了优化。 这也打破了function正确性,因为我们使用的HW考虑了单词序列并忽略了偏移(但编译器认为改变指令的顺序是安全的)。

如果硬件是总线上特定的非内存从属外设,那么您可以在外部包装器中包装写入此从属的function并强制进行寄存器分配(请参阅AAPCS )以使ldm/stm起作用。 这将导致性能损失,可以通过设备驱动程序中的某个自定义汇编程序来缓解。

但是,听起来设备可能是内存? 在这种情况下,你有一个问题。 通常,像这样的内存设备只会使用缓存吗? 如果您的CPU具有MPU(内存保护单元)并且可以同时启用数据和代码缓存,那么您可以解决此问题。 高速缓存行将始终是突发访问。 只需要在代码中注意设置MPU和数据缓存。 OP Cortex-M0 +没有缓存,设备是非内存,所以这是不可能的(也不需要)。

如果您的设备是内存并且您没有数据缓存,那么您的问题可能无法解决(无需大量工作)并且您需要不同的硬件。 或者你可以像外围设备一样包装它并获得性能提升; 失去了存储设备随机存取的好处。