【问题标题】:Why GCC (ARM Cortex-M0) generates UXTB instruction when it should know that data is already uint8为什么GCC(ARM Cortex-M0)应该知道数据已经是uint8时会生成UXTB指令
【发布时间】:2021-03-21 11:36:07
【问题描述】:

我正在使用 NXP (LPC845) 的 Cortex-M0 MCU,我正在尝试弄清楚 GCC 想要做什么 :)

基本上C代码(伪)如下:

volatile uint8_t readb1 = 0x1a; // dummy
readb1 = GpioPadB(GPIO_PIN);

而我写的宏是

(*((volatile uint8_t*)(SOME_GPIO_ADDRESS)))

现在代码可以运行了,但它产生了一些我不理解的额外 UXTB 指令

00000378:   ldrb    r3, [r3, #0]
0000037a:   ldr     r2, [pc, #200]  ; (0x444 <AppInit+272>)
0000037c:   uxtb    r3, r3
0000037e:   strb    r3, [r2, #0]
105         asm("nop");

我的解释如下:

  • 从 R3 中指定的地址加载 BYTE,将结果放入 R3
  • 加载readb1变量的R2地址
  • UXTB 扩展了 uint8 值 ???但是 rotate 参数是 0,所以 uint8 基本上什么都不做!
  • 将来自 R3 的数据作为 BYTE 存储到 R2 的地址(我的变量)

为什么会这样?

首先,它应该知道R3中的数据只有一个BYTE的含义(它已经正确生成了LDRB)。其次,STRB 已经可以修整 7..0 LSB,为什么还要使用 UXTB 呢?

感谢您的澄清,

编辑: 编译器版本:

gcc 版本 9.2.1 20191025(发布)[ARM/arm-9-branch 修订版 277599](用于 Arm 嵌入式处理器的 GNU 工具 9-2019-q4-major)

我使用-O3

【问题讨论】:

  • 什么版本的 gcc 以及你使用了哪些命令行选项?
  • 请提供一个完整的可重复示例
  • 这是一个很好的问题,但是你把它弄得一团糟!请提供我们可以查看的真实代码。 “我写的宏是”:什么宏??它叫什么名字?你在哪里调用它?
  • 这绝对是编译器错误(功能??)。

标签: gcc arm cortex-m


【解决方案1】:

看起来像是编译器留下的额外指令和/或 cortex-m 或更新的内核有一些细微差别(很想知道细微差别是什么)。

#define GpioPadB(x) (*((volatile unsigned char *)(x)))
volatile unsigned char readb1;
void fun ( void )
{
    readb1 = 0x1A;
    readb1 = GpioPadB(0x1234000);
}

一个apt得到gcc

arm-none-eabi-gcc --version
arm-none-eabi-gcc (15:4.9.3+svn231177-1) 4.9.3 20150529 (prerelease)
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

arm-none-eabi-gcc -O2 -c -mthumb so.c -o so.o
arm-none-eabi-objdump -d so.o


00000000 <fun>:
   0:   231a        movs    r3, #26
   2:   4a03        ldr     r2, [pc, #12]   ; (10 <fun+0x10>)
   4:   7013        strb    r3, [r2, #0]
   6:   4b03        ldr     r3, [pc, #12]   ; (14 <fun+0x14>)
   8:   781b        ldrb    r3, [r3, #0]
   a:   7013        strb    r3, [r2, #0]
   c:   4770        bx      lr
   e:   46c0        nop         ; (mov r8, r8)
  10:   00000000    .word   0x00000000
  14:   01234000    .word   0x01234000

正如人们所期望的那样。

arm-none-eabi-gcc -O2 -c -mthumb -march=armv7-m so.c -o so.o
arm-none-eabi-objdump -d so.o
so.o:     file format elf32-littlearm


Disassembly of section .text:

00000000 <fun>:
   0:   4a03        ldr     r2, [pc, #12]   ; (10 <fun+0x10>)
   2:   211a        movs    r1, #26
   4:   4b03        ldr     r3, [pc, #12]   ; (14 <fun+0x14>)
   6:   7011        strb    r1, [r2, #0]
   8:   781b        ldrb    r3, [r3, #0]
   a:   b2db        uxtb    r3, r3
   c:   7013        strb    r3, [r2, #0]
   e:   4770        bx  lr
  10:   00000000    .word   0x00000000
  14:   01234000    .word   0x01234000

里面有额外的 utxb 指令

有点新的东西

arm-none-eabi-gcc --version
arm-none-eabi-gcc (GCC) 10.2.0
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

对于 armv6m 和 armv7m

00000000 <fun>:
   0:   231a        movs    r3, #26
   2:   4a03        ldr     r2, [pc, #12]   ; (10 <fun+0x10>)
   4:   7013        strb    r3, [r2, #0]
   6:   4b03        ldr     r3, [pc, #12]   ; (14 <fun+0x14>)
   8:   781b        ldrb    r3, [r3, #0]
   a:   7013        strb    r3, [r2, #0]
   c:   4770        bx      lr
   e:   46c0        nop         ; (mov r8, r8)
  10:   00000000    .word   0x00000000
  14:   01234000    .word   0x01234000

对于 armv4t

00000000 <fun>:
   0:   231a        movs    r3, #26
   2:   4a03        ldr     r2, [pc, #12]   ; (10 <fun+0x10>)
   4:   7013        strb    r3, [r2, #0]
   6:   4b03        ldr     r3, [pc, #12]   ; (14 <fun+0x14>)
   8:   781b        ldrb    r3, [r3, #0]
   a:   7013        strb    r3, [r2, #0]
   c:   4770        bx      lr
   e:   46c0        nop         ; (mov r8, r8)
  10:   00000000    .word   0x00000000
  14:   01234000    .word   0x01234000

utxb 不见了。

我认为这只是一个错过的优化,窥视孔或其他。

正如已经回答的那样,当您使用非 gpr 大小的变量时,您可以期望和/或容忍编译器转换为寄存器大小。根据编译器和目标的不同,它们是在输入还是输出时执行(读取变量时,还是在写入或使用之前)。

对于 x86,您可以单独访问寄存器的各个部分(或使用基于内存的操作数),您会发现它们不会这样做(在 gcc 中),即使在明显需要符号扩展或填充的情况下也是如此。并在使用该值时将其整理出来。

您可以在 gcc 源中搜索 utxb,或许可以看到问题或评论。

编辑

请注意,clang 采用不同的路径,它会烧掉生成地址但不进行扩展的时钟

00000000 <fun>:
   0:   f240 0000   movw    r0, #0
   4:   f2c0 0000   movt    r0, #0
   8:   211a        movs    r1, #26
   a:   7001        strb    r1, [r0, #0]
   c:   f244 0100   movw    r1, #16384  ; 0x4000
  10:   f2c0 1123   movt    r1, #291    ; 0x123
  14:   7809        ldrb    r1, [r1, #0]
  16:   7001        strb    r1, [r0, #0]
  18:   4770        bx  lr

clang --version
clang version 11.1.0 (https://github.com/llvm/llvm-project.git 1fdec59bffc11ae37eb51a1b9869f0696bfd5312)
Target: armv7m-none-unknown-eabi
Thread model: posix
InstalledDir: /opt/llvm11armv7m/bin

我认为这只是 gcc/gnu 的优化问题。

【讨论】:

  • OP 问题是关于 M0 的。没有办法避免godbolt.org/z/6xc1a4
  • armv6-m 支持 utxb 并已添加。但这并不能解释为什么添加它,似乎是编译器问题。 (而 gcc 在过去十年左右因存在这些问题而闻名)。
  • 你是对的,答案就在免责声明中:“不提供任何保证;甚至不保证适销性或特定用途的适用性”
  • 嗯.. 这证实了我的想法,由于我不是 ASM 专家,感谢大家的 cmets。我会说特定的芯片组制造商(即德克萨斯州、Nxp 等)应该检查 GCC 输出的内容要好得多,因为这条指令可以在以几兆赫兹切换 GPIO 时产生影响......
【解决方案2】:

“volatile”修饰符是罪魁祸首。它在编写时不会调用类型扩展,因为它没有意义。但是在阅读时,它总是调用扩展。因为现在数据存储在寄存器中,并且必须准备好在可见性限制的整个范围内进行任何操作。 放弃“volatile”会删除对数据的任何额外操作,但也可以删除使用变量这一事实。

https://godbolt.org/z/cGvc8r6se

【讨论】:

    【解决方案3】:

    首先要知道R3中的数据只有一个BYTE的意思

    寄存器只有 32 位。它们没有任何其他“意义”。寄存器必须包含与加载字节相同的值 - 因此是 UXTB。以后的任何其他操作(例如添加一些东西需要整个寄存器包含正确的值。

    一般来说,使用比 32 位更短的类型通常会增加一些开销,因为 Cortex-Mx 处理器不对寄存器的“部分”进行操作。

    【讨论】:

    • 有点不同意。几乎所有像 MCU(不仅仅是 Cortex)这样的“小内核”都具有所谓的“优化”指令,用于操作 8 位和 16 位正是为了这个目的:尽管存储是 32 位,但数据可以具有不同的含义。我可以读取 32 位,但我有意义的数据只是 8 位 LSB(一个 I2C 寄存器),所以其余的只是垃圾。这就是他们为 8,16 和 32 提供版本或 LOAD/STORE 的原因。令人惊奇的是,GCC 尤其适用于 Cortex-M0 等成熟内核的 GCC 可以正确使用它。
    • 我完全无法理解这一点。虽然OP的问题在细节上不清楚,但看起来uxtb指令在这里是不必要的。
    • 你所说的在这种情况下是无效的。 ldrb 已经将高 24 位设置为 0,strb 只是忽略它们。无论上下文如何,都绝对没有理由使用uxtb
    【解决方案4】:

    要解决此问题,您需要在https://gcc.gnu.org/bugzilla/ 提交错误。但是有两种困难的情况。

    1. 有很多与“volatile”相关的bug,而且都没有关闭,甚至大部分都没有确认。据我了解,开发者已经厌倦了与风车战斗,甚至没有任何反应。
    2. 要成功解决问题 - 您需要找到极端,即写下万恶之源的那个极端。作者身份等等。你不会被允许进入别人的分支,只有最高级的才能进入 master。 但即使在这一刻之前,您也需要找到这种行为的原因,而这里又出现了问题。 GCC 代码很大,你可以无休止地搜索。

    我的个人意见:GCC 将 ARM 内核寄存器视为快速内存的一部分。可以通过物理地址访问该内存,这只会增加问题。好吧,如果这是内存,并且维度不匹配,那么根据GCC,您需要添加扩展命令。 为什么 GCC 在简单访问时使用正确的命令? - 好吧,他从一个记忆读到另一个记忆。强调 - “从记忆中”。无论接下来发生什么,您都需要立即阅读。

    【讨论】:

      猜你喜欢
      • 2015-09-15
      • 2015-07-05
      • 2016-06-10
      • 1970-01-01
      • 2022-12-06
      • 1970-01-01
      • 2021-10-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多