跳轉到內容

GNU C 編譯器內部/GNU C 編譯器架構

來自 Wikibooks,開放世界中的開放書籍

GCC 架構概述

[編輯 | 編輯原始碼]

本節基於 Red Hat 雜誌文章 [1]

GNU 編譯器集合 (GCC) 包含多個用於不同程式語言的編譯器。主要的 GCC 可執行檔案 gcc 處理用 C、C++、Objective-C、Objective-C++、Java、Fortran 或 Ada 編寫的原始檔,併為每個原始檔生成彙編檔案。它是一個驅動程式,根據原始檔的語言呼叫相應的編譯程式。對於 C 原始檔,它們是預處理器和編譯器 cc1、彙編器 as 和連結器 collect2。第一個和第三個程式附帶 GCC 發行版,彙編器是 GNU binutils 包的一部分。本書描述了預處理器和編譯器 cc1 的內部工作原理。

每個編譯器都包含以下三個元件:前端、中間端和後端。GCC 一次編譯一個檔案。原始檔依次透過所有三個元件。圖 1 說明了與每個元件相關的元件和原始檔表示。

GCC 前端、中間端和後端,以及原始檔表示。

前端的目的是讀取原始檔,解析它,並將其轉換為標準的抽象語法樹 (AST) 表示。每種程式語言都有一個前端。由於語言的差異,生成的 AST 的格式對於每種語言略有不同。AST 生成後的下一步是統一步驟,其中 AST 樹被轉換為稱為泛型的統一形式。之後,編譯器的中間端部分開始接管。首先,樹被轉換為另一種稱為GIMPLE 的表示。在此形式中,每個表示式最多包含三個運算元,所有控制流結構都表示為條件語句和 goto 運算子的組合,函式呼叫的引數只能是變數,等等。圖 2 說明了泛型形式的樹和 GIMPLE 形式的樹之間的差異。GIMPLE 是最佳化原始碼的方便表示。


在 GIMPLE 之後,原始碼被轉換為靜態單賦值 (SSA) 表示。這種形式的核心思想是,每個變數只被賦值一次,但可以在表示式的右側被多次使用。每次在 GIMPLE 形式的樹中重新賦值同一個變數時,編譯器都會建立一個該變數的新版本,並將新值儲存到其中。當在條件表示式的兩個分支中都將同一個變數賦值時,需要將變數的兩個可能值合併成一個變數。此操作在 SSA 形式中表示為 PHI 函式。


SSA 形式也用於最佳化。GCC 對 SSA 樹執行 20 多種不同的最佳化。在 SSA 最佳化傳遞之後,樹被轉換回 GIMPLE 形式,然後用於生成樹的暫存器傳輸語言 (RTL) 形式。RTL 是基於硬體的表示,它對應於具有無限多個暫存器的抽象目標架構。RTL 最佳化傳遞最佳化 RTL 形式的樹。最後,GCC 後端使用 RTL 表示為目標架構生成彙編程式碼。後端的例子包括 x86 後端、mips 後端等等。

在接下來的部分,我們將描述 C 前端和 x86 後端的內部工作原理。編譯器從其初始化和命令列選項處理開始。之後,C 前端預處理原始檔,解析它並執行一些最佳化。然後,後端為目標平臺生成彙編程式碼並將其儲存到檔案。

要點: GCC 是一個編譯器集合,它包含每個程式語言的前端、一箇中間端以及每個架構的後端。每個原始檔依次透過的 主要表示形式是前端的 AST、中間端的 RTL 以及後端的彙編表示。GCC 一次編譯一個檔案。

GCC 初始化

[編輯 | 編輯原始碼]

C 前端包括 C/C++ 預處理器和 C 編譯器。程式 cc1 包含預處理器和 C 編譯器。它編譯 C 原始檔並生成彙編 (.S) 檔案。

編譯器前端和後端透過稱為語言鉤子的回撥函式相互互動。所有鉤子都被包含在檔案 langhooks.h 中定義的全域性變數 struct lang_hooks lang_hooks 中。有以下型別的鉤子:用於樹內聯的鉤子、用於呼叫圖的鉤子、用於函式的鉤子、用於樹轉儲的鉤子、用於型別的鉤子、用於宣告的鉤子以及特定於語言的鉤子。鉤子的預設值在檔案 langhooks-def.h 中定義。

GCC 初始化包括命令列選項解析、初始化後端、建立全域性範圍以及初始化內建資料型別和函式。

每個宣告都與一個範圍相關聯。例如,區域性變數與其函式的範圍相關聯。全域性宣告與全域性範圍相關聯。

函式 toplev_main() 是處理命令列選項、初始化編譯器、編譯檔案以及釋放分配資源的函式。函式 decode_options() 處理命令列選項並在編譯器中設定相應的變數。

在命令列選項解析之後,呼叫函式 do_compile()。它透過呼叫函式 backend_init() 執行後端初始化。之後,函式 lang_dependent_init() 執行特定於語言的初始化,包括前端和後端的初始化。C 初始化函式 c_objc_common_init() 建立內建資料型別,初始化全域性範圍並執行其他初始化任務。函式 c_common_nodes_and_builtins() 建立檔案 builtin-types.def 中描述的預定義型別。

標準 C 型別在初始化時建立。下表列出了幾種型別

GCC 內建型別
變數名稱 C 型別
char_type_node char
integer_type_node int
unsigned_type_node unsigned int
void_type_node void
ptr_type_node void*

GCC 內建函式是在編譯時求值的函式。例如,如果 strcpy() 函式的大小引數是一個常量,那麼 GCC 會用所需的賦值數量替換函式呼叫。編譯器用內建函式替換標準庫呼叫,並在構建函式的 AST 後對其進行求值。在 strcpy() 的情況下,編譯器會檢查大小引數,如果引數是常量,則使用 strcpy() 的最佳化內建版本。內建 builtin_constant_p() 允許確定其引數的值是否在編譯時已知。GCC 內建函式超出了 GCC 的使用範圍。例如,Linux 核心的字串處理庫使用 builtin_constant_p() 在編譯時已知字串大小的情況下呼叫字串處理函式的最佳化版本。

GCC 使用相應的 expand_builtin() 函式來評估每個內建函式。例如,builtin_strcmp() 使用 expand_builtin_strcmp() 求值。下表列出了 GCC 內建函式的例子

GCC 內建函式
內建名稱 解釋
builtin_constant_p 如果引數是常量,則返回 true
builtin_memcpy 等效於 memcpy()
builtin_strlen 等效於 strlen()


要點: GCC 初始化包括命令列選項解析、初始化後端、建立全域性範圍以及初始化內建資料型別和函式。

C 預處理器

[編輯 | 編輯原始碼]

在初始化之後,函式 do_compile() 呼叫函式 compile_file()。此函式呼叫 parse_file() 前端語言鉤子,該鉤子對於 C 語言被設定為函式 c_common_parse_file()。後面的函式呼叫函式 finish_options(),該函式初始化預處理器並處理 -D、-U 和 -A 命令列選項(分別等效於 #define、#undef 和 #assert)。C 預處理器處理原始碼中的預處理器指令,例如 #define、#include。

在預處理器初始化完成後,呼叫了 c_parse_file() 函式。該函式使用標準的 lex/bison 工具來解析檔案。預處理器作為詞法分析器的一部分實現。C 語言詞法分析器函式 c_lex() 呼叫 libcpp 函式 cpp_get_token(),它處理預處理器關鍵字。預處理器的狀態由變數 cpp_reader *parse_in 定義。型別 struct cpp_reader 最重要的是包含正在處理的文字緩衝區列表。每個緩衝區對應一個原始檔(.c 或 .h)。函式 cpp_get_token() 為合法的預處理器關鍵字呼叫相應的函式。例如,當遇到 #include 時,呼叫函式 do_include_common()。它分配一個新的緩衝區並將其放置在緩衝區棧的頂部,使其成為當前緩衝區。當編譯一個新檔案時,緩衝區從棧中彈出,舊檔案的編譯繼續進行。

當使用 #define 關鍵字定義新的宏時,呼叫函式 do_define()

要點: 預處理器負責處理預處理器指令,例如 #include 和 #ifdef。

原始碼解析

[編輯 | 編輯原始碼]

在執行預處理器後,GCC 為原始檔的每個函式構建一個抽象語法樹 (AST)。AST 是若干個連線的節點,型別為 struct tree。每個節點都有一個樹碼,它定義了樹的型別。宏 TREE_CODE() 用於引用程式碼。樹碼在檔案 tree.defc-common.def 中定義。具有不同樹碼的樹被分組到樹類中。以下樹類在 GCC 中定義,除此之外還有其他一些樹類。

GCC 樹類
樹類 解釋
'd' 宣告
'<' 比較
'2' 二元算術


樹有兩種型別:語句表示式。語句對應於 C 結構,例如表示式後跟一個 ';',條件語句,迴圈語句等等。表示式是語句的組成部分。表示式的示例包括賦值表示式,算術表示式,函式呼叫等等。樹碼的示例如下表所示。

GCC 樹碼
樹碼 樹類 解釋 運算元
MODIFY_EXPR 'e' 賦值表示式 TREE_OPERAND(t,0) - 左側;TREE_OPERAND(t,1) - 右側;
CALL_EXPR 'e' 函式呼叫 TREE_OPERAND(t,0) - 函式定義;TREE_OPERAND(t,1) - 函式引數;
FUNCTION_DECL 'd' 變數宣告 DECL_SOURCE_FILE(t) - 原始檔;DECL_NAME(t) - 變數名;
ARRAY_TYPE 't' 陣列型別 TREE_TYPE(t) - 陣列元素的型別;TYPE_DOMAIN(t) - 索引的型別;
DECL_STMT 'e' 變數宣告 TREE_OPERAND(t,0) - 變數;DECL_INITIAL(TREE_OPERAND(t,1)) - 初始值;

除了定義樹型別的樹碼之外,還有許多與每種樹型別不同的運算元可用。例如,賦值表示式有兩個運算元,對應於表示式的左側和右側。宏 TREE_OPERAND 用於引用運算元。宏 IDENTIFIER_POINTER 用於查詢 IDENTIFIER_NODE 樹表示的識別符號的名稱。下表展示了一些樹節點,它們的目的以及它們的運算元。

每棵樹都有一個型別,它對應於它表示的 C 表示式的型別。例如,MODIFY_EXPR 節點的型別是左側運算元的型別。 NOP_EXPRCONVERT_EXPR 樹用於型別轉換。

樹 NULL_TREE 等效於 NULL。函式 debug_tree() 將樹列印到 stderr。

解析器作為 bison 語法實現。語法呼叫構建 AST 的 GCC 函式。當一個新的識別符號被詞法分析時,它會被新增到 GCC 維持的字串池中。識別符號的樹碼為 IDENTIFIER_NODE。當同一個識別符號再次被詞法分析時,返回同一個樹節點。函式 get_identifier() 返回識別符號的樹節點。

新的變數宣告在多個函式呼叫中被處理。首先,函式 start_decl() 被呼叫,帶有宣告的名稱,詞法分析器返回的型別,以及它的屬性。該函式呼叫 grokdeclarator() 檢查型別和引數節點,並返回一個程式碼適合宣告的樹:VAR_DECL 用於變數,TYPE_DECL 用於型別等等。然後,宣告被新增到作用域中。作用域包含在一個函式內建立的所有宣告,但不包含全域性宣告。還有全域性作用域,它包含全域性宣告。當解析一個函式時,它的宣告作為 BLOCK 節點附加到函式體。當建立宣告時,使用 IDENTIFIER_SYMBOL_VALUE 將識別符號節點與宣告節點關聯。函式 lookup_name() 返回給定識別符號的宣告。當宣告離開作用域時,樹屬性 C_DECL_INVISIBLE 被斷言。

GCC 中沒有維護符號表。相反,編譯器使用識別符號池和 C_DECL_INVISIBLE 屬性。語言鉤子 lang_hooks.decls.getdecls() 返回作用域中的變數,這些變數連結在一起。

函式 start_init()finish_init() 被用於初始化宣告。函式 finish_decl() 完成宣告。對於陣列宣告,它計算初始化陣列的大小。然後呼叫函式 layout_decl()。它計算宣告的大小和對齊方式。

解析函式取決於函式體是否存在。函式宣告使用與變數宣告相同的函式解析。對於函式定義,呼叫函式 start_function()。然後,編譯器解析函式體。當函式結束時,呼叫函式 finish_function()

函式 start_decl()start_function() 將宣告的屬性引數作為其引數之一。屬性在 GCC 手冊 中描述。屬性是 GNU 實現 C 的擴充套件。下表展示了一些屬性並解釋了它們的目的。

函式屬性
屬性 解釋
constructor 在 main() 之前自動呼叫函式
destructor 在 main() 之後自動呼叫函式
alias 另一個函式的別名

對於每種型別的 C 語句,都有一個函式構建對應型別的樹節點。例如,函式 build_function_call() 為函式呼叫構建 CALL_EXPR 節點。它以函式名的識別符號和引數作為引數。該函式使用 lookup_name() 查詢函式宣告,並使用 default_conversion() 對引數進行型別轉換。

然後,AST 被轉換為 SSA,最終轉換為 RTL 表示。是否在解析每個函式之後或解析整個檔案時進行轉換由編譯器選項 -funit-at-a-time 控制。預設情況下為假。

要點: GCC 解析器作為 bison 語法編寫。它建立原始檔的 AST 表示。AST 由樹節點構成。每個節點都有一個程式碼。樹節點對應於 C 的語句和表示式。函式 debug_tree() 打印出樹。

彙編程式碼生成

[編輯 | 編輯原始碼]

後端為指定的目標平臺生成彙編程式碼。每個寫入彙編檔案的指令都會呼叫函式 output_asm_insn()。函式 final_start_function() 在函式儲存到彙編檔案之前生成函式的序言。

要點: 後端為指定的目標平臺生成彙編程式碼。
上一頁: 目錄 GNU C 編譯器架構 下一頁: GEM 框架
  1. ^ https://web.archive.org/web/20160410185222/https://#/magazine/002dec04/features/gcc/
華夏公益教科書