【问题标题】:Why is the compiler using the frame pointer and link register?为什么编译器使用帧指针和链接寄存器?
【发布时间】:2021-01-14 23:49:50
【问题描述】:

我试图了解 GNU 如何解释几件事,所以我的第一个示例非常简单:声明一个整数并打印它。如果没有调用优化,汇编代码如下:

    .arch armv8-a
    .file   "ex1.c"
    .text
    .section        .rodata
    .align  3 .LC0:
    .string "%i\n"
    .text
    .align  2
    .global main
    .type   main, %function main: .LFB0:
    .cfi_startproc
    stp     x29, x30, [sp, -32]!
    .cfi_def_cfa_offset 32
    .cfi_offset 29, -32
    .cfi_offset 30, -24
    mov     x29, sp
    str     w0, [sp, 28]
    mov     w0, 328
    str     w0, [sp, 28]
    ldr     w1, [sp, 28]
    adrp    x0, .LC0
    add     x0, x0, :lo12:.LC0
    bl      printf
    nop
    ldp     x29, x30, [sp], 32
    .cfi_restore 30
    .cfi_restore 29
    .cfi_def_cfa_offset 0
    ret
    .cfi_endproc .LFE0:
    .size   main, .-main
    .ident  "GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0"
    .section        .note.GNU-stack,"",@progbits

如果编译优化(-O3),汇编代码更简洁:

    .arch armv8-a
    .file   "ex1.c"
    .text
    .section        .rodata.str1.8,"aMS",@progbits,1
    .align  3 .LC0:
    .string "%i\n"
    .section        .text.startup,"ax",@progbits
    .align  2
    .p2align 3,,7
    .global main
    .type   main, %function main: .LFB23:
    .cfi_startproc
    adrp    x1, .LC0
    mov     w2, 328
    add     x1, x1, :lo12:.LC0
    mov     w0, 1
    b       __printf_chk
    .cfi_endproc .LFE23:
    .size   main, .-main
    .ident  "GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0"
    .section        .note.GNU-stack,"",@progbits

除了 p2align 3,,7 之外,大多数东西都相对简单,我在阅读了它在 sourceware 上的描述后仍在弄清楚。但是,我的主要问题是别的。为什么未优化版本使用帧指针和链接寄存器以及 CFA?它试图完成什么?有人可能想知道为什么我在乎,选择优化版本。原因是 Fortran 代码的优化版本通过使用帧指针和链接寄存器恢复到类似于未优化的 C 版本。
Fortran 代码很简单:

  program integer_printing

  integer (kind=4) a
  a=328
  write (*,*) a
  end

优化后的汇编代码如下

        .arch armv8-a
        .file   "exa1F.f90"
        .text
        .section        .rodata.str1.8,"aMS",@progbits,1
        .align  3
.LC0:
        .string "exa1F.f90"
        .text
        .align  2
        .p2align 3,,7
        .type   MAIN__, %function
MAIN__:
.LFB0:
        .cfi_startproc
        sub     sp, sp, #576
        .cfi_def_cfa_offset 576
        adrp    x0, .LC1
        adrp    x1, .LC0
        add     x1, x1, :lo12:.LC0
        mov     w3, 328
        mov     w2, 5
        stp     x29, x30, [sp]
        .cfi_offset 29, -576
        .cfi_offset 30, -568
        mov     x29, sp
        ldr     d0, [x0, #:lo12:.LC1]
        str     x19, [sp, 16]
        .cfi_offset 19, -560
        add     x19, sp, 48
        mov     x0, x19
        str     w3, [sp, 44]
        str     d0, [sp, 48]
        str     x1, [sp, 56]
        str     w2, [sp, 64]
        bl      _gfortran_st_write
        add     x1, sp, 44
        mov     w2, 4
        mov     x0, x19
        bl      _gfortran_transfer_integer_write
        mov     x0, x19
        bl      _gfortran_st_write_done
        ldp     x29, x30, [sp]
        ldr     x19, [sp, 16]
        add     sp, sp, 576
        .cfi_restore 29
        .cfi_restore 30
        .cfi_restore 19
        .cfi_def_cfa_offset 0
        ret
        .cfi_endproc
.LFE0:
        .size   MAIN__, .-MAIN__
        .section        .text.startup,"ax",@progbits
        .align  2
        .p2align 3,,7
        .global main
        .type   main, %function
main:
.LFB1:
        .cfi_startproc
        stp     x29, x30, [sp, -16]!
        .cfi_def_cfa_offset 16
        .cfi_offset 29, -16
        .cfi_offset 30, -8
        mov     x29, sp
        bl      _gfortran_set_args
        adrp    x1, .LANCHOR0
        add     x1, x1, :lo12:.LANCHOR0
        mov     w0, 7
        bl      _gfortran_set_options
        bl      MAIN__
        mov     w0, 0
        ldp     x29, x30, [sp], 16
        .cfi_restore 30
        .cfi_restore 29
        .cfi_def_cfa_offset 0
        ret
        .cfi_endproc
.LFE1:
        .size   main, .-main
        .section        .rodata.cst8,"aM",@progbits,8
        .align  3
.LC1:
        .word   128
        .word   6
        .section        .rodata
        .align  3
        .set    .LANCHOR0,. + 0
        .type   options.1.2778, %object
        .size   options.1.2778, 28
options.1.2778:
        .word   2116
        .word   4095
        .word   0
        .word   1
        .word   1
        .word   0
        .word   31
        .ident  "GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0"
        .section        .note.GNU-stack,"",@progbits

【问题讨论】:

  • 帧指针用于未优化的代码以帮助调试。优化后的版本还使用了跳转到printf(尾调用消除)。如果您有动态堆栈分配,也可以使用帧指针。
  • 好的,这回答了我的大部分问题。我仍然不完全理解为什么优化的 Fortran 版本使用帧指针(可能是因为“经典”Fortran 不使用指针),因为代码绝对没有使用可分配变量。谢谢。
  • 你问错问题了。不要询问无关的 C 代码,而是发布您的 Fortran 代码和它生成的汇编代码,并询问为什么它使用启用了优化的帧指针。
  • 我编辑了帖子以包含 Fortran 和汇编代码。来自未优化 C 编译的汇编代码仍然令人感兴趣,因为 GNU 编译器使用帧指针作为优化的 Fortran 编译(这是我试图弄清楚的要点之一)。

标签: assembly fortran gfortran gnu-assembler


【解决方案1】:

无论出于何种原因,-O3 在使用 GCC 编译 ARM64 目标(包括 GNU Fortran)时都没有打开 -fomit-frame-pointer 选项。您需要 enable this option explicitly 让编译器优化非叶函数中帧指针的使用:

MAIN__:
        adrp    x0, .LC1
        sub     sp, sp, #560
        adrp    x1, .LC0
        add     x1, x1, :lo12:.LC0
        ldr     d0, [x0, #:lo12:.LC1]
        mov     w3, 328
        mov     w2, 5
        add     x0, sp, 32
        str     x30, [sp]
        str     w3, [sp, 28]
        str     d0, [sp, 32]
        str     x1, [sp, 40]
        str     w2, [sp, 48]
        bl      _gfortran_st_write
        add     x1, sp, 28
        mov     w2, 4
        add     x0, sp, 32
        bl      _gfortran_transfer_integer_write
        add     x0, sp, 32
        bl      _gfortran_st_write_done
        ldr     x30, [sp]
        add     sp, sp, 560
        ret

当编译器将对 printf (bl printf) 的尾调用更改为跳转 (b __printf_chk) 时,您的 C 示例代码将优化为叶函数,该函数不调用其他函数。这就是为什么不使用-fomit-frame-pointer而消除帧指针的原因。

请注意,链接寄存器永远不能被优化掉,至少在任何可以返回给其调用者的函数中都不能,因为它需要保留这个寄存器中的值,因为它包含要返回的地址。在您优化的 C 示例中,编译器不需要保存或恢复链接寄存器(X30)。它只是让它保持不变,因此__printf_chk 直接返回到您的示例 C 函数的调用者。在您的其他示例中,存储在链接寄存器中的值被这些函数调用的函数破坏(特别是通过 BL 指令),因此需要保存和恢复。

最后,帧指针与 C 指针无关。编译器使用它来访问函数的局部变量,并且还形成堆栈帧的链表,调试器可以使用它来创建回溯并检查调用函数的局部变量。然而,在大多数架构上,如果函数进行可变大小的堆栈分配(例如variable length arrays),则只需要访问局部变量。在这些架构上,当局部变量的大小固定时,可以使用堆栈指针来访问局部变量,但这是以使调试更加困难为代价的,因此这被视为一种优化。

【讨论】:

  • 谢谢。这是很好的反馈。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-04-30
  • 2019-08-11
  • 2018-09-18
  • 1970-01-01
  • 2011-04-19
  • 1970-01-01
  • 2013-11-11
相关资源
最近更新 更多