为什么 GCC 在 LDR 之后产生额外的 ADDS 指令以在 ARM thumb 指令集上加载 .rodata 指针?
Why does GCC produce extra ADDS instruction after LDR for loading an .rodata pointer on ARM thumb instruction set?
此代码:
...
const char myTable[] = { 1, 2, 3, 4 };
int foo() {
return (int)(&myTable);
}
...
为 thumb 指令集编译为以下程序集:
...
foo:
@ args = 0, pretend = 0, frame = 0
@ frame_needed = 0, uses_anonymous_args = 0
@ link register save eliminated.
ldr r0, .L5
@ sp needed
adds r0, r0, #10
bx lr
.L6:
.align 2
.L5:
.word .LANCHOR0
.size foo, .-foo
.align 1
.global bar
.syntax unified
.code 16
.thumb_func
.type bar, %function
...
myTable:
.ascii "[=11=]1[=11=]2[=11=]3[=11=]4"
它看起来像是在将指针 (ldr
) 加载到 .rodata 的顶部,然后以编程方式偏移到 myTable
(adds
) 的位置。但是为什么不直接加载 table 本身的地址呢?
注意:当我删除 const
时,它似乎没有 ADDS
指令(myTable
在 .data
中)
问题的背景是我正在尝试手动优化一些 C 固件并注意到这个 adds
指令似乎是多余的,所以我想知道是否有办法重组我的摆脱它的代码。
注意:这都是针对ARM thumb指令集编译如下(使用arm-none-eabi-gcc version 11.2.1):
arm-none-eabi-gcc -Os -c -mcpu=cortex-m0 -mthumb temp.c -S
另请注意:此处的示例代码旨在表示较大代码库的片段。如果 myTable
是唯一被编译的东西,那么它位于地址 0 并且 adds
指令消失,但这不是现实世界场景的典型情况。为了表示生成此程序集的典型真实场景,我在 table 之前添加了填充。见 full minimal example code here.
这个
const char myTable[] = { 1, 2, 3, 4 };
int foo() {
return (int)(&myTable);
}
arm-none-eabi-gcc -Os -c -mthumb so.c -o so.o
arm-none-eabi-objdump -D so.o
生产
Disassembly of section .text:
00000000 <foo>:
0: 4800 ldr r0, [pc, #0] ; (4 <foo+0x4>)
2: 4770 bx lr
4: 00000000 andeq r0, r0, r0
Disassembly of section .rodata:
00000000 <myTable>:
0: 04030201 streq r0, [r3], #-513 ; 0xfffffdff
这不是您的问题所包含的内容。总的来说,即使有你的外部 link,我也会在这里停下来放弃。请返回并在问题中添加正确的示例,在评论中输入文本而不是 link。
所以根据你的问题和这个:
const char padding[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
const char myTable[] = { 1, 2, 3, 4 };
int foo() {
return (int)(&myTable);
}
很明显为什么 myTable 的偏移量为 10。
但是填充被优化掉了,所以你仍然会得到相同的结果。
所以
const char padding[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
const char myTable[] = { 1, 2, 3, 4 };
int keepPadding() {
return (int)(&padding);
}
int foo() {
return (int)(&myTable);
}
该函数的名称暗示您已经知道所有这些,并且知道如何制作一个最小示例等等。想知道为什么这是一个 SO 问题,如果您已经完成了工作....
arm-none-eabi-gcc -Os -c -mthumb so.c -S
foo:
@ Function supports interworking.
@ args = 0, pretend = 0, frame = 0
@ frame_needed = 0, uses_anonymous_args = 0
@ link register save eliminated.
ldr r0, .L5
@ sp needed
adds r0, r0, #10
bx lr
.L6:
.align 2
.L5:
.word .LANCHOR0
.size foo, .-foo
.global myTable
.global padding
.section .rodata
.set .LANCHOR0,. + 0
.type padding, %object
.size padding, 10
padding:
.space 10
.type myTable, %object
.size myTable, 4
myTable:
.ascii "[=14=]1[=14=]2[=14=]3[=14=]4"
.ident "GCC: (GNU) 11.2.0"
它正在生成一个锚点,然后从锚点引用而不是直接引用标签。
我怀疑这是为了优化 ldr...让我们试试。
arm-none-eabi-gcc -Os -c -mthumb -mcpu=cortex-m4 so.c -S
foo:
@ args = 0, pretend = 0, frame = 0
@ frame_needed = 0, uses_anonymous_args = 0
@ link register save eliminated.
ldr r0, .L5
bx lr
.L6:
.align 2
.L5:
.word .LANCHOR0+10
.size foo, .-foo
00000008 <foo>:
8: 4800 ldr r0, [pc, #0] ; (c <foo+0x4>)
a: 4770 bx lr
c: 0000000a .word 0x0000000a
是的,所以修复了它,但是 linking 它呢
Disassembly of section .rodata:
00000000 <padding>:
...
0000000a <myTable>:
a: 04030201 streq r0, [r3], #-513 ; 0xfffffdff
Disassembly of section .text:
00000010 <keepPadding>:
10: 4800 ldr r0, [pc, #0] ; (14 <keepPadding+0x4>)
12: 4770 bx lr
14: 00000000 andeq r0, r0, r0
00000018 <foo>:
18: 4801 ldr r0, [pc, #4] ; (20 <foo+0x8>)
1a: 300a adds r0, #10
1c: 4770 bx lr
1e: 46c0 nop ; (mov r8, r8)
20: 00000000 andeq r0, r0, r0
不,希望 linker 会替换 pc-relative 负载并将其转换为 mov r0,#0...节省负载(可能是)优化对于不是 cortex-m(甚至 cortex-m)的系统。
注意:这也有效
arm-none-eabi-gcc -Os -c -mthumb -fno-section-anchors so.c -o so.o
00000008 <foo>:
8: 4800 ldr r0, [pc, #0] ; (c <foo+0x4>)
a: 4770 bx lr
c: 00000000 andeq r0, r0, r0
foo:
@ Function supports interworking.
@ args = 0, pretend = 0, frame = 0
@ frame_needed = 0, uses_anonymous_args = 0
@ link register save eliminated.
ldr r0, .L5
@ sp needed
bx lr
.L6:
.align 2
.L5:
.word myTable
.size foo, .-foo
.global myTable
.section .rodata
.type myTable, %object
.size myTable, 4
myTable:
.ascii "[=17=]1[=17=]2[=17=]3[=17=]4"
.global padding
.type padding, %object
.size padding, 10
没有使用锚点,直接使用myTable的地址
从我的角度来看,“为什么”是因为使用了锚点,前面的填充导致 myTable 与锚点发生偏移。所以负载加载锚地址然后添加让你从锚点到 table.
为什么主播?为 reader 或其他人练习。
此代码:
...
const char myTable[] = { 1, 2, 3, 4 };
int foo() {
return (int)(&myTable);
}
...
为 thumb 指令集编译为以下程序集:
...
foo:
@ args = 0, pretend = 0, frame = 0
@ frame_needed = 0, uses_anonymous_args = 0
@ link register save eliminated.
ldr r0, .L5
@ sp needed
adds r0, r0, #10
bx lr
.L6:
.align 2
.L5:
.word .LANCHOR0
.size foo, .-foo
.align 1
.global bar
.syntax unified
.code 16
.thumb_func
.type bar, %function
...
myTable:
.ascii "[=11=]1[=11=]2[=11=]3[=11=]4"
它看起来像是在将指针 (ldr
) 加载到 .rodata 的顶部,然后以编程方式偏移到 myTable
(adds
) 的位置。但是为什么不直接加载 table 本身的地址呢?
注意:当我删除 const
时,它似乎没有 ADDS
指令(myTable
在 .data
中)
问题的背景是我正在尝试手动优化一些 C 固件并注意到这个 adds
指令似乎是多余的,所以我想知道是否有办法重组我的摆脱它的代码。
注意:这都是针对ARM thumb指令集编译如下(使用arm-none-eabi-gcc version 11.2.1):
arm-none-eabi-gcc -Os -c -mcpu=cortex-m0 -mthumb temp.c -S
另请注意:此处的示例代码旨在表示较大代码库的片段。如果 myTable
是唯一被编译的东西,那么它位于地址 0 并且 adds
指令消失,但这不是现实世界场景的典型情况。为了表示生成此程序集的典型真实场景,我在 table 之前添加了填充。见 full minimal example code here.
这个
const char myTable[] = { 1, 2, 3, 4 };
int foo() {
return (int)(&myTable);
}
arm-none-eabi-gcc -Os -c -mthumb so.c -o so.o
arm-none-eabi-objdump -D so.o
生产
Disassembly of section .text:
00000000 <foo>:
0: 4800 ldr r0, [pc, #0] ; (4 <foo+0x4>)
2: 4770 bx lr
4: 00000000 andeq r0, r0, r0
Disassembly of section .rodata:
00000000 <myTable>:
0: 04030201 streq r0, [r3], #-513 ; 0xfffffdff
这不是您的问题所包含的内容。总的来说,即使有你的外部 link,我也会在这里停下来放弃。请返回并在问题中添加正确的示例,在评论中输入文本而不是 link。
所以根据你的问题和这个:
const char padding[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
const char myTable[] = { 1, 2, 3, 4 };
int foo() {
return (int)(&myTable);
}
很明显为什么 myTable 的偏移量为 10。
但是填充被优化掉了,所以你仍然会得到相同的结果。
所以
const char padding[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
const char myTable[] = { 1, 2, 3, 4 };
int keepPadding() {
return (int)(&padding);
}
int foo() {
return (int)(&myTable);
}
该函数的名称暗示您已经知道所有这些,并且知道如何制作一个最小示例等等。想知道为什么这是一个 SO 问题,如果您已经完成了工作....
arm-none-eabi-gcc -Os -c -mthumb so.c -S
foo:
@ Function supports interworking.
@ args = 0, pretend = 0, frame = 0
@ frame_needed = 0, uses_anonymous_args = 0
@ link register save eliminated.
ldr r0, .L5
@ sp needed
adds r0, r0, #10
bx lr
.L6:
.align 2
.L5:
.word .LANCHOR0
.size foo, .-foo
.global myTable
.global padding
.section .rodata
.set .LANCHOR0,. + 0
.type padding, %object
.size padding, 10
padding:
.space 10
.type myTable, %object
.size myTable, 4
myTable:
.ascii "[=14=]1[=14=]2[=14=]3[=14=]4"
.ident "GCC: (GNU) 11.2.0"
它正在生成一个锚点,然后从锚点引用而不是直接引用标签。
我怀疑这是为了优化 ldr...让我们试试。
arm-none-eabi-gcc -Os -c -mthumb -mcpu=cortex-m4 so.c -S
foo:
@ args = 0, pretend = 0, frame = 0
@ frame_needed = 0, uses_anonymous_args = 0
@ link register save eliminated.
ldr r0, .L5
bx lr
.L6:
.align 2
.L5:
.word .LANCHOR0+10
.size foo, .-foo
00000008 <foo>:
8: 4800 ldr r0, [pc, #0] ; (c <foo+0x4>)
a: 4770 bx lr
c: 0000000a .word 0x0000000a
是的,所以修复了它,但是 linking 它呢
Disassembly of section .rodata:
00000000 <padding>:
...
0000000a <myTable>:
a: 04030201 streq r0, [r3], #-513 ; 0xfffffdff
Disassembly of section .text:
00000010 <keepPadding>:
10: 4800 ldr r0, [pc, #0] ; (14 <keepPadding+0x4>)
12: 4770 bx lr
14: 00000000 andeq r0, r0, r0
00000018 <foo>:
18: 4801 ldr r0, [pc, #4] ; (20 <foo+0x8>)
1a: 300a adds r0, #10
1c: 4770 bx lr
1e: 46c0 nop ; (mov r8, r8)
20: 00000000 andeq r0, r0, r0
不,希望 linker 会替换 pc-relative 负载并将其转换为 mov r0,#0...节省负载(可能是)优化对于不是 cortex-m(甚至 cortex-m)的系统。
注意:这也有效
arm-none-eabi-gcc -Os -c -mthumb -fno-section-anchors so.c -o so.o
00000008 <foo>:
8: 4800 ldr r0, [pc, #0] ; (c <foo+0x4>)
a: 4770 bx lr
c: 00000000 andeq r0, r0, r0
foo:
@ Function supports interworking.
@ args = 0, pretend = 0, frame = 0
@ frame_needed = 0, uses_anonymous_args = 0
@ link register save eliminated.
ldr r0, .L5
@ sp needed
bx lr
.L6:
.align 2
.L5:
.word myTable
.size foo, .-foo
.global myTable
.section .rodata
.type myTable, %object
.size myTable, 4
myTable:
.ascii "[=17=]1[=17=]2[=17=]3[=17=]4"
.global padding
.type padding, %object
.size padding, 10
没有使用锚点,直接使用myTable的地址
从我的角度来看,“为什么”是因为使用了锚点,前面的填充导致 myTable 与锚点发生偏移。所以负载加载锚地址然后添加让你从锚点到 table.
为什么主播?为 reader 或其他人练习。