【问题标题】:How to prevent LDM/STM instructions expansion in ARM Compiler 5 armcc inline assembler?如何防止 ARM Compiler 5 armcc 内联汇编程序中的 LDM/STM 指令扩展?
【发布时间】:2016-01-15 23:16:13
【问题描述】:

我正在尝试使用 STM/LDM 指令在使用 ARM Compiler 5 armcc 编译的 .c 文件中的内联汇编中生成 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,并且这些指令的顺序是任意的。 这会影响性能,因为我们使用针对突发处理进行优化的硬件。这也破坏了功能的正确性,因为我们使用的硬件考虑了单词的顺序并忽略了偏移量(但编译器认为更改指令的顺序是安全的)。

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

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

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

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

【问题讨论】:

  • 如果您非常关心性能,那么在单独的汇编文件中编写更多您需要的内容。考虑到编译器对其余代码的处理有多糟糕,内联 C 函数中的单个指令不会让您得到太多。我的操作原则始终是 - 如果您关心时间关键例程的性能,请自己编写(在汇编程序中)。
  • @imiron13:我怀疑你被搞砸了。 Kiel 内联汇编让优化器开箱即用,并且对其“优化”的内容缺乏细粒度的控制。如果您使用普通的 volatile 指针来确保写入器顺序与 64 位类型一起尝试组合写入,那么代码生成有多糟糕?
  • @BitBank:我的假设是,性能损失并不局限于单个关键的内循环,可以很容易地手动调整,但是写入是内联生成到代码库的重要部分中的。
  • @BitBank:情况就像描述的doynax一样,大部分硬件都针对突发事件进行了优化,在汇编中实现整个HAL层是不合理的。
  • @doynax:我还没有尝试去引用 (volatile uint64_t*),也许它会产生 LDM/STM,但在其他情况下也需要 16-64 字节突发,所以它似乎不是可以用 C 代码来表达。

标签: c assembly arm embedded armcc


【解决方案1】:

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

关于编译器优化的一点点。 Register allocation 是最艰难的工作之一。任何编译器代码生成的核心都可能是它分配物理 CPU 寄存器的时候。大多数编译器都使用Single static assignment or 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 and 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 的要求。

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

如果硬件是总线上特定的非内存从属外围设备,那么您可以将写入该从属设备的功能包装在外部包装器中并强制分配寄存器(请参阅AAPCS),以便ldm/stm 将工作。这将导致性能下降,可以通过设备驱动程序中的某些自定义汇编程序来缓解。

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

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

【讨论】:

  • (这与您在帖子中链接的问题更相关,但我无法在那里发表评论)。 “gcc 的问题”是否意味着可以在那里可靠地生成突发,但由于明确的寄存器分配,它可能不是最佳的?
  • 虽然,您的里程可能会因 GCC 版本和选项标志而异。我发现唯一可靠的方法是使用函数调用并依赖寄存器参数或指定 gcc 寄存器 asm 变量。如果您不这样做,您可能会收到关于寄存器在 stm/ldm 中出现故障的神秘汇编程序消息。即,有人更改了代码或编译器选项以及 GCC 选择不起作用的寄存器。
  • 如果这一切都是“片上”并且您的公司没有设计它,但是一些大公司并且芯片组被广泛使用,我怀疑需要某种总线配置。很难相信商业 SOC 会遇到您在编辑中描述的问题。任何使用该芯片的人都会遇到同样的问题。如果芯片是未来产品的样品,那么这可能是一个勘误表,我希望在芯片投入大规模生产之前得到修复。
猜你喜欢
  • 1970-01-01
  • 2023-03-10
  • 1970-01-01
  • 2021-10-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-25
  • 2022-08-11
相关资源
最近更新 更多