天天看点

使用Clang作为编译器 —— Assembling a Complete Toolchain

装配一个完整的工具链

      • 1. 介绍
      • 2. 工具
          • 2.1 Clang 前端
          • 2.2 其他语言的语言前端
          • 2.3 汇编器
          • 2.4 链接器
      • 3. 运行时库
          • 3.1 编译器运行时(Compiler runtime)
          • 3.2 原子库(Atomics library)
          • 3.3 Unwind 库
          • 3.4 Sanitizer 运行时
          • 3.5 C 标准库
          • 3.6 C++ ABI 库
          • 3.7 C++ 标准库

本文为译文,点击 此处查看原文。

1. 介绍

Clang 只是 C 家族编程语言完整工具链(tool chain)中的一个组件。为了装配一个完整的工具链,需要额外的工具和运行时库。Clang 被设计为与用于其目标平台的现有工具和库进行交互操作,并且 LLVM 项目为许多这些组件提供了替代方案。

本文档描述了完整工具链中所需的和可选的组件,在哪里可以找到它们,以及每个选项支持的版本和限制。

警告

本文档目前描述了使用 GCC-compatible 的

clang

驱动程序在类 POSIX 操作系统上的 Clang 配置。当使用 MSVC-compatible 的

clang-cl

驱动程序在目标系统Windows上时,有些细节是不同的。

2. 工具

C 家族编程语言的完整编译通常涉及以下工具管道,其中一些在某些编译中被省略:

  • 预处理器(Preprocessor)

    :执行 C 预处理器的操作:展开

    #include

    #defines

    -E

    标志指示 Clang 在此步骤之后停止。
  • 解析(Parsing)

    :根据输入,解析和语义分析源语言,并构建一个源代码级中间表示(“AST”),生成预编译头文件(precompiled header,PCH)、序言(preamble)或预编译模块文件(precompiled module file,PCM)。

    -precompile

    标志指示 Clang 在此步骤之后停止。当输入是一个头文件时,这是默认值。
  • IR生成(IR generation)

    :将源代码级中间表示转换为一个特定于优化器(optimizer)的中间表示(IR);对于 Clang,这是LLVM IR。

    -emit-llvm

    标志指示 Clang 在此步骤之后停止。如果与

    -S

    结合,Clang 将产生文本形式的 LLVM IR;否则,将产生 LLVM IR 字节码。
  • 编译器后端(Compiler backend)

    :这将中间表示转换为特定于目标的汇编代码。

    -S

    标志指示 Clang 在此步骤之后停止。
  • 汇编器(Assembler)

    :将特定于目标的汇编代码转换为特定于目标的机器码目标文件。

    -c

    标志指示 Clang 在此步骤之后停止。
  • 链接器(Linker)

    :它将多个目标文件组合成一个映像(一个共享对象或一个可执行文件)。

Clang提供了除链接器之外的所有这些部分。当同一工具执行多个步骤时,通常将这些步骤合并在一起,以避免创建中间文件。

当给定上述步骤之一的输出作为输入时,将跳过前面的步骤(例如,输入一个

.s

文件将会进行汇编和链接)。

可以使用

-###

标记(在大多数 shell 中需要转义这个参数)调用 Clang 驱动程序,以查看将为上述步骤运行哪些命令,在不运行这些命令的情况下。除了运行命令外,

-v

(verbose)标志还将打印命令。

2.1 Clang 前端

Clang 前端(

clang -cc1

)用于编译 C 家族语言。前端的命令行接口被认为是一个实现细节,故意没有外部文档,并且可以在不注意的情况下进行更改。

2.2 其他语言的语言前端

Clang可以提供用非 C 家族语言编写的输入。在这种情况下,将使用一个外部工具编译输入。目前支持的语言有:

  • Ada (

    -x ada

    .ad[bs]

    )
  • Fortran (

    -x f95

    .f

    .f9[05]

    .for

    .fpp

    、不区分大小写)
  • Java (

    -x java

    )

在每种情况下,都会调用 GCC 来编译输入。

2.3 汇编器

Clang 既可以使用 LLVM 的集成汇编器,也可以使用外部特定于系统的工具(例如,GNU 操作系统上的 GNU 汇编器),以从汇编代码生成机器码。默认情况下,Clang 在支持 LLVM 的所有目标上使用 LLVM 的集成汇编器。如果想使用系统汇编器,请使用

-fno-integrated-as

选项。

2.4 链接器

Clang可以配置为使用几个不同的链接器其中一个:

  • GNU ld

  • GNU gold

  • LLVM lld

  • MSVC link.exe

lld 原生支持链接时优化,使用 gold 时通过一个链接器插件以支持链接时优化。

默认链接器在不同的目标之间是不同的,可以通过

-fuse-ld=<linker name>

标志覆盖它。

3. 运行时库

需要许多不同的运行时库来为 C 家族程序提供不同的支持层。Clang 将隐式地链接每个运行时库的一个适当实现,这些实现是根据目标默认值选择的,或者由

--rtlib=

--stdlib=

标志显式地选择。

隐式链接库的集合依赖于语言模式。因此,在链接 C++ 程序时应该使用

clang++

,以确保提供了 C++ 运行时。

注意

对于这些组件,可能存在下面没有描述的其他实现。请让我们知道这些其他实现与 Clang 的工作效果如何,以便将它们添加到这个列表中!

3.1 编译器运行时(Compiler runtime)

编译器运行时库提供编译器隐式调用的函数的定义,以支持底层硬件不支持的操作(例如,128位整数乘法),以及认为操作的内联展开不合适的地方。

默认的运行时库是特定于目标的。对于 GCC 是主要编译器的目标,Clang 目前默认使用

libgcc_s

。在大多数其他目标上,默认情况下使用

compiler-rt

  • compiler-rt (LLVM)

    LLVM的编译器运行时库提供了一组完整的运行时库函数,其中包含 Clang 将隐式调用的所有函数,在

    libclang_rt.builtins.<arch>.a

    中。

    您可以指示 Clang 使用带有

    --rtlib=compiler-rt

    标志的

    compiler-rt

    ,并非所有平台都支持此功能。

    如果使用

    libc++

    和/或

    libc++abi

    ,您可能需要将它们配置为使用

    compiler-rt

    而不是

    libgcc_s

    ,方法是将

    -DLIBCXX_USE_COMPILER_RT=YES

    和/或

    -DLIBCXXABI_USE_COMPILER_RT=YES

    传递给

    cmake

    。否则,您可能会将两个运行时库链接到您的程序中(这通常是无害的,但很浪费)。
  • libgcc_s (GNU)

    GCC的运行时库可以用来代替

    compiler-rt

    。但是,它缺少几个LLVM可能发出引用的函数,特别是在使用Clang的内置函数家族的

    __builtin_*_overflow

    时。

    您可以指示 Clang 使用带有

    --rtlib=libgcc

    标志的

    libgcc_s

    ,并非所有平台都支持此功能。
3.2 原子库(Atomics library)

如果您的程序使用了原子操作,编译器无法直接降低他们到机器指令(因为没有合适的机器指令或操作数不知道适当对齐),将会生成对一个运行时库

__atomic_*

函数的一次调用。这些程序需要一个包含这些原子函数的运行时库。

  • compiler-rt (LLVM)

    compiler-rt 包含一个原子库的实现。
  • libatomic (GNU)

    libgcc_s 不提供原子库的实现。相反,GCC 的 libatomic library 可以在使用 libgcc_s 时提供这些原子库。

注意

当使用

libgcc_s

时,Clang当前不会自动链接到

libatomic

。在使用非本机原子操作时(如果您看到引用了

__atomic_*

函数的链接错误),可能需要手动添加

-latomic

来支持此配置。
3.3 Unwind 库

unwind

库提供了一系列

_Unwind_*

函数,实现了

Itanium C++ ABI

(第I级)的语言无关的堆栈unwind部分,它是 C++ ABI 库的依赖项,有时是其他运行时的依赖项。

  • libunwind (LLVM)

    LLVM 的 unwinder 库是 llvm-project git 存储库的一部分。要构建它,请将

    -DLLVM_ENABLE_PROJECTS=libunwind

    传递给 cmake 调用。

    如果使用

    libc++abi

    ,您可能需要将其配置为使用

    libunwind

    而不是

    libgcc_s

    ,方法是将

    -DLIBCXXABI_USE_LLVM_UNWINDER=YES

    传递给 cmake。如果将

    libc++abi

    配置为使用某个版本的

    libunwind

    ,则该库将会被隐式链接到二进制文件,这些二进制文件链接到libc++abi。
  • libgcc_s (GNU)

    libgcc_s 有一个集成的 unwinder,不需要提供外部的 unwind 库。
  • libunwind (nongnu.org)

    这是 libunwind 规范的另一个实现。请参阅 libunwind (nongnu.org)。
  • libunwind (PathScale)

    这是 libunwind 规范的另一个实现。请参阅 libunwind (pathscale)。
3.4 Sanitizer 运行时

Clang 的

sanitizers

(-fsanitize=…)添加的工具隐式地调用一个运行时库,以便维护程序执行的侧状态,并在检测到问题时发出诊断消息。

这些运行时的唯一支持实现由 LLVM 的

compiler-rt

提供,以及这个库(

libclang_rt.<sanitizer>.<arch>.a

) 的相关部分将会被链接当使用一个

-fsanitize=...

标志链接时。

3.5 C 标准库

Clang 支持多种 C 标准库实现。

3.6 C++ ABI 库

C++ ABI 库

提供了

Itanium C++ ABI

库部分的实现,包括 main Itanium c++ ABI文档中的支持功能和异常处理支持的 Level II。在编译 C++ 代码时,Clang 隐式地生成对这个库中函数和对象的引用。

虽然有可能使用

libstdc++

链接 C++ 代码和使用

libc++

链接代码一起到相同的程序(只要你不要试图通过 C++ 标准库对象的边界),它通常在一个程序不大可能有超过一个 C++ ABI 库。

Clang 使用的 C++ ABI 库的版本将是所选 C++ 标准库所链接的版本。有几种实现可用:

  • libc + + abi (LLVM)

    libc++abi 是 LLVM 对该规范的实现。
  • libsupc++ (GNU)

    libsupc++

    是 GCC 对该规范的实现。但是,只有在静态链接

    libstdc++

    时才使用这个库。

    libstdc++

    的动态库版本包含

    libsupc++

    的一个副本。

    注意

    当静态链接

    libstdc++

    时,Clang当前不会自动链接到

    libatomic

    。在使用

    -static

    -static-libstdc++

    时,可能需要手动添加

    -lsupc++

    来支持此配置。
  • libcxxrt (PathScale)

    这是 Itanium C++ ABI 规范的另一个实现。请参阅 libcxxrt。
3.7 C++ 标准库

Clang 支持使用 LLVM 的

libc++

或 GCC 的

libstdc++

C++标准库实现。

  • libc++ (LLVM)

    libc++ 是 LLVM 的 C++ 标准库实现,旨在从 C++ 11 开始成为全面的 C++ 标准实现。

    您可以指示 Clang 用

    -stdlib=libc++

    标志来使用 libc++。
  • libstdc++ (GNU)

    libstdc++ 是 GCC 的 C++ 标准库实现。Clang 支持各种版本的 libstdc++,从4.2版本开始,并将隐式地处理旧版本libstdc++中的一些bug。

    您可以指示 Clang 用

    -stdlib=libstdc++

    标志来使用 libstdc++。

继续阅读