为什么 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 或其他人练习。