使用 Clang 进行交叉编译时,如何实现原生级别的优化?

How can I achieve native-level optimizations when cross-compiling with Clang?

当使用 clang-target 选项进行交叉编译时,针对与本机系统相同的体系结构和硬件,我注意到 clang 似乎产生了更差的优化对于三元组中 <sys>none.

的情况,比本地构建的对应部分

考虑这个简单的代码示例:

int square(int num) {
    return num * num;
}

当使用 -target x86_64-linux-elf-O3 优化时,本机 x86_64 目标代码生成产生:

square(int):
        mov     eax, edi
        imul    eax, edi
        ret

-target x86_64-none-elf 生成的代码产生:

square(int):
        push    rbp
        mov     rbp, rsp
        mov     eax, edi
        imul    eax, edi
        pop     rbp
        ret

Live Example

尽管具有相同的硬件和优化标志,但显然缺少优化。如果在目标三元组中将 none 替换为 linux,问题就会消失,尽管没有使用系统特定的功能。

乍一看它似乎根本没有优化,但不同的代码段显示它正在执行一些 ] 优化,但不是全部。例如,loop-unrolling is still occurring.

虽然上面的例子只是 x86_64,但在实践中,这个问题正在为基于 armv7 的受限嵌入式系统生成代码膨胀,我注意到在某些情况,例如:

我想知道我能做些什么,如果有的话,在交叉编译时实现与本机编译相同的优化?是否有任何相关标志可以帮助调整三元组中 <sys> 以某种方式暗示的调整?


如果可能的话,我很想知道 为什么 交叉编译目标似乎无法优化某些事情,仅仅是因为系统不同,尽管具有相同的系统架构和能力。我的理解是 LLVM 的优化器在 IR 上运行,只要不依赖于目标系统本身,它应该有效地生成相同的优化。

TL;DR: 对于 x86 目标,当 OS 未知时,默认启用 frame-pointers。您可以使用 -fomit-frame-pointer 手动禁用它们。对于ARM平台,你当然需要提供更多的信息,以便后端推导出目标ABI。使用 -emit-llvm so 检查 Clang/LLVM 的哪一部分生成了低效代码。


应用程序二进制接口 (ABI) 可以从一个目标更改为另一个目标。 C 中没有标准 ABI。选择一个取决于几个参数,包括体系结构、版本、供应商、OS、环境等

-target 参数的使用帮助编译器select ABI。问题是 x86_64-none-elf 不够完整,因此后端实际上可以生成快速代码。事实上,我认为这实际上不是一个有效的目标,因为在这种情况下有来自 Clang 的警告,并且相同的警告出现在错误的随机目标上。令人惊讶的是,编译器仍然可以使用提供的信息成功生成 通用二进制文件 x86_64-windowsx86_64-linux 等目标与 x86_64-unknown-windows-cygnus 一样有效(对于 Windows 中的 Cygwin)。您可以获得 Clang 支持的平台、OS、环境等的列表。in the code.

ABI 的一个特殊方面是调用约定。它们在操作系统之间是不同的。例如,x86-64 Linux 平台使用来自 System V AMD64 ABI 的调用约定,而最近的 x86-64 Windows 平台使用 vectorcall 调用约定基于旧的 Microsoft x64 之一。对于旧的 x86 架构,情况更为复杂。有关此的更多信息,请阅读 this and this

在您的情况下,如果没有关于 OS 的信息,编译器可能 select 它自己的通用 ABI 导致使用旧的 push/pop 指令。也就是说,编译器假定 edi 包含传递给函数的参数,这意味着所选 ABI 是 System V AMD64(或派生版本)。环境可以在堆栈优化中发挥重要作用,例如从函数外部(例如被调用函数)访问堆栈。

我最初的猜测是,由于缺少有关指定目标的信息,程序集后端禁用了一些优化,但 x86-64 ELF 的情况并非如此,因为相同的后端是 selected。请注意,目标由 architecture-specific 后端解析(例如,请参见 here)。

事实上,问题来自 Clang 发出的 IR 代码,其中 frame-pointer 标志设置为 all。您可以使用标志 -emit-llvm 检查 IR 代码。您可以使用 -fomit-frame-pointer.

解决此问题

在 ARM 上,问题可能不同并且来自汇编后端。您当然应该指定目标 OS 或至少更多信息,例如 sub-architecture 类型(主要针对 ARM)、供应商和环境。

总体而言,请注意,由于缺乏信息,更通用的目标会产生效率较低的代码,这是合理的。如果您需要有关此的更多信息,请在 Clang 或 LLVM 错误跟踪器上填写问题,以便跟踪发生这种情况的原因 or/and 让开发人员修复此问题。

相关posts/links: