3

我正在尝试为 GCC 编译的应用程序编写一个组装仪器模块作为安全框架的一部分。为了提高模块的性能,我需要尽可能减少动态跳转/动态函数调用。这些,基本上,使用一些动态指针(例如寄存器)来执行跳转或调用函数。

当前的 GCC 编译器在多次调用同一个函数(代码中的某个标签)时,将标签加载到寄存器中,然后在需要调用该函数时跳转到该寄存器。这当然比每次都跳转到同一个标签(更小的代码和更少的时钟周期)要快得多,但是正如我所提到的,这对我的框架来说效率很低。为了给你一个我想避免的例子,这里有一个代码片段:

MOV #function_label, R10.  #Copy the label to the R10 register
CALL R10
...
...
CALL R10
...
...
CALL R10

虽然我希望 GCC 执行以下操作:

CALL #label_function
...
...
CALL #label_function
...
...
CALL #label_function

请注意,我实际上使用的是 mspgcc,它是 MSP430 系列微控制器的 GCC 编译器,但基于 GCC 应该不会有太大的不同。

你认为有什么可以做的吗(除了重写 GCC 编译器)?非常感谢您的帮助

4

1 回答 1

2

用于-fno-function-cse不对函数地址执行公共子表达式消除。 GCC 手册

-fno-function-cse

不要将函数地址放在寄存器中;使每条调用常量函数的指令都明确包含函数的地址。

此选项会导致代码效率降低,但是在不使用此选项时执行的优化可能会混淆一些改变汇编器输出的奇怪技巧。

默认为 -ffunction-cse


如何找到特定的 GCC 选项

我查看了gcc -O1 -fverbose-asmasm 输出以查看所有-O1暗示的优化选项(GCC 在 asm 注释中列出)。 -O1 -fno-...将所有内容的版本编译为仅 3条指令,每个指令都带有符号名称,确认其中一条是我想要的,所以我只需要通过平分该选项call列表来缩小范围-fno-

我使用了具有 MSP430 GCC6.2.1、测试代码 + asm的 Godbolt 编译器资源管理器。我禁用了“comments”过滤器选项,所以我可以在 asm 输出中看到纯注释行。

由于有很多选项,我过去常常tr ' ' '\n' | sed -e 's/-f/-fno-/' -e '/;/d'-f选项变成它们的否定形式。我将整个 asm 注释块复制/粘贴到终端中的该命令中,然后将结果复制/粘贴到 Godbolt 上的 GCC 选项框中。(与-O1.一起-O0是一种用于一致调试的特殊反优化模式,因此即使使用正确的选项,跨语句优化也可能永远不会处于活动状态-O0。这就是为什么我需要否定选项而不是尝试没有 的积极形式-O1

然后我选择并删除了一堆选项,看看是否改变了 asm。如果没有,请继续。当我找到一个块时,我知道我想要的选项在那里,所以我可以撤消 (control-z) 并删除所有其他-f选项,然后将其缩小到一个。(当我-fno-function-cse在该组中看到名称时,我认为这听起来像是正确的事情。幸运的是,如果您知道编译器/优化术语,GCC 选项确实具有有意义的名称。)

这比一次只看一个选项或翻阅手册要快,因为我什至不确定这些特定选项中的任何一个都能控制这一点。


顺便说一句,GCC 不会对大多数其他 ISA 进行代码大小优化,因为这对它们来说不是性能上的胜利。代码大小不是 x86-64 甚至 ARM thumb 上性能的最重要因素;间接跳转的可能分支错误预测的额外成本(以及分支预测器的额外污染)超过了代码大小的成本。

x86 上的代码大小胜利,其中可以为多个 2 字节指令设置5 字节mov立即或 7 字节 RIP 相对(x86-64)。leacall

对于许多固定指令宽度的 ISA,如 AArch64 或 ARM(Thumb 模式除外),它通常甚至不是代码大小的胜利,其中标准代码模型假设函数在相对分支和链接的范围内(呼叫)指示。因此,调用任何函数都需要一条指令,与任何其他指令的大小相同。

即使-ffunction-cse显式启用,GCC 也不会对 x86-64 或 ARM thumb 进行这种优化,即使在它已经使用 GOT 中的函数指针的情况下也是如此。(Godbolt 上的 x86-64 gcc-Os -fPIE -fno-plt -ffunction-cse。我什至告诉 GCC 优化代码大小;保存/恢复像 RBX 这样的调用保留寄存器以用于 2 字节call rbx而不是 6 字节call [RIP+rel32],即使在需要额外的指令之后也会节省大小推送/弹出 RBX(每个 1 字节)并加载到 RBX(一个具有 RIP 相对寻址模式的 mov)。)

这可能被认为是错过的优化-Os,尤其是对于“简单”内核的 ARM Thumb,-mcpu=cortex-m3甚至可能没有分支预测器。

(AArch64会将函数指针加载到带有 , 的寄存器中-fPIE -fno-plt,用于没有“隐藏”可见性的函数,即该函数可能仅位于共享库中。即使使用-fno-function-cse. https://godbolt.org/z/f3MP56 也会发生这种情况。 )

于 2020-10-02T02:30:21.363 回答