天天看點

使用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++。

繼續閱讀