为什么 GCC (ARM Cortex-M0) 在应该知道数据已经是 uint8 时生成 UXTB 指令
Why GCC (ARM Cortex-M0) generates UXTB instruction when it should know that data is already uint8
我正在使用 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 <-- 这是从 GPIO 寄存器加载 BYTE
- 加载readb1变量的R2地址
- UXTB 扩展了 uint8 值 ???但是 rotate 参数是 0,所以基本上对 uint8 没有任何作用!
- 将来自 R3 的数据作为 BYTE 存储到 R2 的地址(我的变量)
为什么会这样?
首先要知道R3中的数据只是一个BYTE的意思(已经正确生成了LDRB)。其次,STRB 已经 trim 7..0 LSB 那么为什么要使用 UXTB?
感谢您的澄清,
已编辑:
编译器版本:
gcc 版本 9.2.1 20191025(发布)[ARM/arm-9-branch 修订版 277599](GNU Tools for Arm Embedded Processors 9-2019-q4-major)
我用-O3
First of all, it should know that data in R3 has just a BYTE meaning
寄存器只有 32 位。他们没有任何其他“意义”。该寄存器必须包含与加载字节相同的值 - 即 UXTB。之后的任何其他操作(例如添加一些东西需要整个寄存器包含正确的值。
一般来说,使用比 32 位短的类型通常会增加一些开销,因为 Cortex-Mx 处理器不会对寄存器的“部分”进行操作。
看起来像是编译器留下的额外指令 and/or cortex-m 或更新的内核有一些细微差别(很想知道细微差别是什么)。
#define GpioPadB(x) (*((volatile unsigned char *)(x)))
volatile unsigned char readb1;
void fun ( void )
{
readb1 = 0x1A;
readb1 = GpioPadB(0x1234000);
}
获得了 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 大小的变量时,您可以期望 and/or 容忍编译器转换为寄存器大小。因编译器和目标而异,关于它们是在进场还是出场时执行(当读取变量时或在写入或使用变量之前)。
对于 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的优化问题。
“volatile”修饰符是罪魁祸首。它在编写时不会调用类型扩展,因为它没有意义。但是读的时候总是调用分机。因为现在数据存储在寄存器中,并且必须准备好在可见性限制的整个范围内进行任何操作。
放弃“volatile”会删除对数据的任何额外操作,但它也可以删除使用变量的事实。
要解决此问题,您需要在 https://gcc.gnu.org/bugzilla/ 提交错误。但是有两个困难的情况。
- 与“volatile”相关的bug很多,而且都没有关闭,大部分甚至都没有确认。据我了解,开发人员已经厌倦了与风车的战斗,甚至没有反应。
- 要成功解决问题 - 您需要找到极端,即写下邪恶根源的那个。著作权和所有。你不会被允许进入别人的分支,只有最先进的才能进入大师。
但即使在这一刻之前,你也需要找到这种行为的原因,这里又出现了问题。
GCC代码海量,可以无限搜索
我个人的看法:GCC 将 ARM 内核寄存器视为快速内存的一部分。可以通过物理地址访问此内存,这只会增加问题。那么,如果这是内存,并且维度不匹配,那么,根据 GCC,您需要添加扩展命令。
为什么 GCC 在简单访问时使用正确的命令? - 好吧,他从记忆中读到记忆中。强调 - “从记忆中”。不管接下来会发生什么,你都需要马上阅读。
我正在使用 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 <-- 这是从 GPIO 寄存器加载 BYTE
- 加载readb1变量的R2地址
- UXTB 扩展了 uint8 值 ???但是 rotate 参数是 0,所以基本上对 uint8 没有任何作用!
- 将来自 R3 的数据作为 BYTE 存储到 R2 的地址(我的变量)
为什么会这样?
首先要知道R3中的数据只是一个BYTE的意思(已经正确生成了LDRB)。其次,STRB 已经 trim 7..0 LSB 那么为什么要使用 UXTB?
感谢您的澄清,
已编辑: 编译器版本:
gcc 版本 9.2.1 20191025(发布)[ARM/arm-9-branch 修订版 277599](GNU Tools for Arm Embedded Processors 9-2019-q4-major)
我用-O3
First of all, it should know that data in R3 has just a BYTE meaning
寄存器只有 32 位。他们没有任何其他“意义”。该寄存器必须包含与加载字节相同的值 - 即 UXTB。之后的任何其他操作(例如添加一些东西需要整个寄存器包含正确的值。
一般来说,使用比 32 位短的类型通常会增加一些开销,因为 Cortex-Mx 处理器不会对寄存器的“部分”进行操作。
看起来像是编译器留下的额外指令 and/or cortex-m 或更新的内核有一些细微差别(很想知道细微差别是什么)。
#define GpioPadB(x) (*((volatile unsigned char *)(x)))
volatile unsigned char readb1;
void fun ( void )
{
readb1 = 0x1A;
readb1 = GpioPadB(0x1234000);
}
获得了 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 大小的变量时,您可以期望 and/or 容忍编译器转换为寄存器大小。因编译器和目标而异,关于它们是在进场还是出场时执行(当读取变量时或在写入或使用变量之前)。
对于 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的优化问题。
“volatile”修饰符是罪魁祸首。它在编写时不会调用类型扩展,因为它没有意义。但是读的时候总是调用分机。因为现在数据存储在寄存器中,并且必须准备好在可见性限制的整个范围内进行任何操作。 放弃“volatile”会删除对数据的任何额外操作,但它也可以删除使用变量的事实。
要解决此问题,您需要在 https://gcc.gnu.org/bugzilla/ 提交错误。但是有两个困难的情况。
- 与“volatile”相关的bug很多,而且都没有关闭,大部分甚至都没有确认。据我了解,开发人员已经厌倦了与风车的战斗,甚至没有反应。
- 要成功解决问题 - 您需要找到极端,即写下邪恶根源的那个。著作权和所有。你不会被允许进入别人的分支,只有最先进的才能进入大师。 但即使在这一刻之前,你也需要找到这种行为的原因,这里又出现了问题。 GCC代码海量,可以无限搜索
我个人的看法:GCC 将 ARM 内核寄存器视为快速内存的一部分。可以通过物理地址访问此内存,这只会增加问题。那么,如果这是内存,并且维度不匹配,那么,根据 GCC,您需要添加扩展命令。 为什么 GCC 在简单访问时使用正确的命令? - 好吧,他从记忆中读到记忆中。强调 - “从记忆中”。不管接下来会发生什么,你都需要马上阅读。