探索 Rust 编译器选项:GCC 与 LLVM

2,728 阅读8分钟

Rust 在不牺牲性能的情况下保证内存安全,因此在软件开发人员中获得了大量追随者。Rust 曾经是小众语言,现在广泛应用于 Windows 和 Android 等各个系统。

rust-compiler-options-gcc-vs-llvm.avif

rustc 使用 LLVM 作为后端来优化并将其转换为低级机器代码。然而,最近出现了一种新的编译器,替代 GCC 前端,称为 gccrs .

在本文中,我们将探讨 Rust 编译的发展,重点关注两个原生编译器项目:LLVM 和 GCC。

什么是LLVM?

LLVM 是由可重用的编译器和工具链组件组成的编译器基础设施的集合。从技术上讲,LLVM 是 Low-Level Virtual Machine 的首字母缩写词,但随着时间的推移,首字母缩写词本身已成为该项目的品牌。LLVM 因其跨各种编程语言优化代码和生成高性能机器代码的能力而闻名。

标准的编译器基础结构可以分为前端、中端和后端。前端充当中间代码(IR)与高级编程语言的转换层,这在不同的编译器(包括 LLVM 和 GCC)中是相似的。

中端对代码应用各种优化,例如循环展开和函数内联。LLVM IR 是 LLVM 的中间表示,可以根据目标直接编译多个不同的后端。

自 Rust 编译器成立以来, rustc LLVM 一直是 Rust 编译器的默认后端;rustc 本质上是一个 LLVM 前端。Rust 和 LLVM 之间的这种合作关系已被证明是成功的,因为 LLVM 的高级优化技术增强了 Rust 程序的性能,并允许它们在多个平台上运行。

GCC?

GCC代表GNU编译器集合,是一个开源编译器集合,支持各种编程语言,如C,C++,Fortran等。它以其稳定性、可靠性和对不同架构和操作系统的广泛支持而广为人知。

出于对自由软件生态系统的需求,编程世界在很大程度上归功于 GCC 在为各种平台实现代码编译方面的贡献。除了上面列出的语言之外,GCC 已经发展到支持许多其他语言,包括 Ada、Java、Go 以及最近(仍在开发中)的 Rust。

有多个前端支持各种语言,每个前端都会将编程语言转换为抽象语法树 (AST)。AST是前端和中端之间的中介。

虽然 LLVM 有 IR 作为其中介表示,但 GCC 有 GIMPLE 和 RTL。GIMPLE 是由 GCC 中间端处理的高级中介表示。GIMPLE 提供了程序的简化表示形式,保留了高级语义并简化了优化任务。

然后,在 GIMPLE 表示之后,代码进一步转换为 RTL。这种低级表示与汇编语言指令非常相似,在用于生成机器代码之前进行了进一步的优化。

目前正在为 Rust 开发一个名为 gccrs 的 GCC 前端。该项目尚不稳定,尚未正式纳入GCC。

GCC 与 LLVM:架构差异

作为一个编译器集合,GCC 与 LLVM 的编译方法不同。GCC 采用更传统的方法,使用前端来解析源代码并生成 AST。

然后,此 AST 被转换为称为 GIMPLE 的高级中介表示,它保留了程序的高级语义。与 LLVM 不同,GCC 添加了一个中间表示 RTL:

gcc-architecture-diagram.avif

这两种优化的目标不同。GIMPLE 侧重于高级优化,而 RTL 侧重于低级优化和转换为类似汇编的指令。

LLVM 直接从前端获取其中间表示 LLVM IR。LLVM IR 优化与语言与架构无关。这允许 LLVM 执行各种优化,这些优化可以使不同的编程语言和目标架构受益:

llvm-architecture-diagram (1).avif

然而,GCC 和 LLVM 之间最惊人的区别是它们如何构建源代码。LLVM 是模块化的;从一开始,它就被构建为可扩展的,并被多种语言使用,针对各种后端机器。

另一方面,GCC 被设计为具有紧密耦合组件的单体编译器。可以为 GCC 创建扩展,但其大部分代码都是紧密结合的,需要下载整个 GCC 代码库才能进行更改或添加。

使用 GCC 和 LLVM 设置 Rust

Rust 编程语言主要使用 LLVM 作为其默认的编译器基础设施。这意味着 Rust 代码默认使用 LLVM 的优化和转换来编译以生成机器代码。

如果你想为 Rust 设置 LLVM,你可以按照 Rust 安装说明进行操作。但是,要将 GCC 用于 Rust,需要使用 gccrs . gccrs 是 Rust 编译器的前端,它使用原生 GCC 作为后端。

要安装软件包 gccrs ,请打开 gccrs 存储库并按照其中提供的安装说明进行操作。这些指令基于 Debian Linux,因此你需要根据使用的发行版调整指令:

sudo apt install build-essential libgmp3-dev libmpfr-dev libmpc-dev flex bison autogen gcc-multilib dejagnu

然后,可以克隆 gccrs 存储库:

git clone https://github.com/Rust-GCC/gccrs

最后,构建工具链以创建一个与 gccrs 存储库相邻的新目录,并运行 make 以在那里编译构建:

mkdir gccrs-build
cd gccrs-build
../gccrs/configure — prefix=$HOME/gccrs-install — disable-bootstrap — enable-multilib — enable-languages=rust
make

安装过程完成后,将准备好二进制 gccrs 文件作为使用 GCC 后端编译 Rust 代码的前端。

gccrs 目前仍处于早期开发阶段,因此它无法支持大多数 Rust 语法,尤其是与 rustc LLVM 相比。例如,在撰写本文时, gccrs 不支持 Rust 宏。因此,很难将 Rust 与 GCC 和 LLVM 进行细致比较。

要开始使用 GCC 编译 Rust 代码,可以在 gccrs 项目测试套件提供的 gccrs 存储库中找到现成的测试代码。让我们举一个例子:

type_infer1.rs:

struct Foo {
    one: i32,
// { dg-warning "field is never read" "" { target *-*-* } .-1 }
    two: i32,
// { dg-warning "field is never read" "" { target *-*-* } .-1 }
}

fn test(x: i32) -> i32 {
    return x + 1;
}

fn main() {
    let logical: bool = true;
    // { dg-warning "unused name" "" { target *-*-* } .-1 }
    let an_integer = 5;
    let mut default_integer = 7;

    default_integer = 1 + an_integer;

    let call_test = test(1);
    // { dg-warning "unused name" "" { target *-*-* } .-1 }
    let struct_test = Foo { one: 1, two: 2 };
    // { dg-warning "unused name" "" { target *-*-* } .-1 }
}

该文件包含声明结构、定义函数并演示 Rust 中变量用法的代码。这是一个简单的类型推断示例,其中包含整数的变量没有被显式类型化,因为 Rust 可以根据其用法推断类型。

要使用 LLVM 编译此 Rust 代码,您可以使用 rustc 编译器:

rustc ./gccrs/gcc/testsuite/rust/compile/torture/type_infer1.rs type_infer1.rs

使用 rustc 运行它非常简单。但是,如果你想使用 GCC 作为前端编译相同的 Rust 代码,你必须传递更多的参数:

例如,使用二进制文件通过 GCC 编译此 Rust 代码。转到在上一节中创建的 gccrs-build 文件夹,然后运行以下命令:

./gcc/crab1 ../gccrs/gcc/testsuite/rust/compile/torture/type_infer1.rs -frust-debug -Warray-bounds -dumpbase ../gccrs/gcc/testsuite/rust/compile/torture/type_infer1.rs -mtune=generic -march=x86–64 -O0 -version -fdump-tree-gimple -o test -L/lib/x86_64-linux-gnu -L/lib/../lib64 -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib64 -frust-incomplete-and-experimental-compiler-do-not-use

此命令将构建一个可用于执行 Rust 代码的二进制文件。它应该输出如下内容:

Analyzing compilation unit
Performing interprocedural optimizations
<*free_lang_data> {heap 1432k} <visibility> {heap 1432k} <build_ssa_passes> {heap 1432k} <opt_local_passes> {heap 1704k} <remove_symbols> {heap 1704k} <targetclone> {heap 1704k} <free-fnsummary> {heap 1704k}Streaming LTO
<whole-program> {heap 1704k} <fnsummary> {heap 1704k} <inline> {heap 1704k} <modref> {heap 1704k} <free-fnsummary> {heap 1704k} <single-use> {heap 1704k} <comdats> {heap 1704k}Assembling functions:
<simdclone> {heap 1704k} type_infer1::test type_infer1::main
Time variable usr sys wall GGC
phase parsing : 0.00 ( 0%) 0.00 ( 0%) 0.01 ( 50%) 82k ( 24%)
phase opt and generate : 0.00 ( 0%) 0.01 (100%) 0.01 ( 50%) 121k ( 35%)
trivially dead code : 0.00 ( 0%) 0.00 ( 0%) 0.01 ( 50%) 0 ( 0%)
parser (global) : 0.00 ( 0%) 0.00 ( 0%) 0.01 ( 50%) 82k ( 24%)
initialize rtl : 0.00 ( 0%) 0.01 (100%) 0.00 ( 0%) 12k ( 4%)
TOTAL : 0.00 0.01 0.02 343k
Extra diagnostic checks enabled; compiler may run slowly.
Configure with - enable-checking=release to disable checks.

请注意,它 gccrs 仍处于非常早期的开发阶段;你还不能导入外部包或库,它只部分支持 Rust 语言的功能。

未来展望:正在进行的项目和发展

使用 GCC 和 LLVM 编译 Rust 代码可能会在性能和优化方面产生不同的结果。这两种方法都有其独特的优势。就 GCC 而言,它可以针对多种架构,并且已经存在了更长的时间,使其在某些领域更加成熟和稳定。它有一个成熟的代码库,已经优化了几十年。

两个项目正在努力使 Rust GCC 兼容。第一个当然是gccrs ,第二个是  rustc_codegen_gcc 

两者之间的区别在于, rustc_codegen_gcc 使用 rustc 做为 GCC 后端生成中间表示。它更稳定,并且可以提供更好的编译体验。

为什么 gccrs 对 Rust 社区很重要

rustc 与当前状态进行广泛比较是不公平 gccrs 的。

首先,它正处于社区主导的早期阶段,并且 rustc 背后有 Rust 基金会—— rustc 毕竟它是主要的 Rust 编译器。但是,拥有一个以社区为主导的编译器为 Rust 生态系统增加了更多的多样性,有助于使 Rust 在多个生态系统中更加通用。

GCC 是老的,相对稳定,它针对的是更多与 LLVM 不兼容的遗留系统——例如,摩托罗拉 68000 (m68k),这是 1980 年代和 1990 年代常用的传统微处理器。许多这样的应用场景都可以通过 GCC 轻松访问,而 LLVM 支持它们是不合理的,因为该技术已经过时了。

摘要、延伸阅读和资源

如果你想了解有关该主题的更多信息,请查看以下资源:

原文:blog.logrocket.com/exploring-r…