支援函式庫¶
摘要¶
本文件提供一些關於 LLVM 支援函式庫的詳細資訊,它位於原始碼中的 lib/Support
和 include/llvm/Support
。該函式庫的目的是為 LLVM 屏蔽不同作業系統之間的差異,僅提供 LLVM 需要從作業系統中獲得的少量服務。LLVM 的大部分程式碼都是使用標準 C++ 的可移植性功能編寫的。然而,在某些情況下,需要使用系統相關的功能,而支援函式庫就是這些系統呼叫的包裝器。
通過集中 LLVM 對作業系統介面的使用,我們可以讓 LLVM 工具鏈和執行時函式庫更容易移植到新的平台,因為(理論上)只需要移植 lib/Support
。這個函式庫還減少了 LLVM 其他部分對 #ifdef 使用和針對特定作業系統的特殊情況的處理。這些使用都被替換為對 include/llvm/Support
中提供的介面的簡單呼叫。
請注意,支援函式庫並不打算成為一個完整的作業系統包裝器(例如自適應通訊環境 (ACE) 或 Apache 可移植執行時 (APR)),而只是提供支援 LLVM 所需的功能。
支援函式庫最初被稱為系統函式庫,由 Reid Spencer 編寫,他根據可擴展程式設計系統 (XPS) 的類似工作制定了設計。許多人為此付出了努力;特別是 Jeff Cohen 和 Henrik Bach 在 Win32 移植方面做出了貢獻。
保持 LLVM 的可移植性¶
為了保持 LLVM 的可移植性,LLVM 開發人員應遵守與支援函式庫相關的一組可移植性規則。遵守這些規則應有助於支援函式庫實現其目標,即有效地屏蔽 LLVM 免受作業系統介面差異的影響。以下各節定義了實現此目標所需的規則。
不要包含系統標頭檔¶
除了在 lib/Support
中之外,任何 LLVM 原始碼都不應直接 #include
系統標頭檔。在開發 lib/Support
的過程中,我們已經仔細地從 LLVM 中刪除了所有這些 #includes
。具體來說,這意味著 LLVM 原始碼(lib/Support
的實現除外)禁止包含“unistd.h
”、“windows.h
”、“stdio.h
”和“string.h
”等標頭檔。
若要取得系統相依的功能,應該使用 include/llvm/Support
中現有的系統介面。如果沒有適當的介面可用,則應將其添加到 include/llvm/Support
並在 lib/Support
中為所有支援的平台實作。
不要公開系統標頭檔¶
支援函式庫必須將 LLVM 與**所有**系統標頭檔隔離。若要取得系統層級的功能,LLVM 原始碼必須 #include "llvm/Support/Thing.h"
而不是其他任何東西。這表示 Thing.h
不能公開任何系統標頭檔。這可以保護 LLVM 不會意外使用系統特定的功能,並且僅允許透過 lib/Support
介面使用。
使用標準 C 標頭檔¶
**標準** C 標頭檔(以「c」開頭的標頭檔)允許透過 lib/Support
介面公開。這些標頭檔及其宣告的內容被視為與平台無關。LLVM 原始碼檔可以直接包含它們,或透過 lib/Support
介面取得它們的包含。
使用標準 C++ 標頭檔¶
標準 C++ 函式庫和標準樣板函式庫中的**標準** C++ 標頭檔可以透過 lib/Support
介面公開。這些標頭檔及其宣告的內容被視為與平台無關。LLVM 原始碼檔可以包含它們,或透過 lib/Support
介面取得它們的包含。
高階介面¶
lib/Support
介面中指定的進入點必須旨在完成 LLVM 所需的一些合理的高階任務。我們不希望只是簡單地包裝每個作業系統呼叫。最好是包裝 LLVM 總是一起使用的多個作業系統呼叫。
舉例來說,請考慮執行程式、等待其完成並傳回其結果代碼所需的操作。在 Unix 上,這涉及以下作業系統呼叫:getenv
、fork
、execve
和 wait
。lib/Support
應該提供一個函數(例如 ExecuteProgramAndWait
)來完整地實作此功能。我們不想要的是涉及的作業系統呼叫的包裝函數。
作業系統呼叫和支援函式庫的介面之間**不得**存在一對一的關係。任何此類介面函數都將令人懷疑。
沒有未使用的功能¶
lib/Support
的介面中指定的任何功能都必須是 LLVM 實際使用的。我們在這裡編寫的不是通用的作業系統包裝函數,而只是足以滿足 LLVM 的需求。而且,LLVM 不需要太多。此設計目標旨在保持 lib/Support
介面的簡潔易懂,這應該有助於其實際使用和採用。
沒有重複的實作¶
給定平台的功能實作必須只寫一次。這表示如果多個作業系統可以共享相同的實作,那麼該功能的實作就必須要能應用在這些作業系統上。這個規則適用於給定作業系統類別(例如 Unix、Win32)所支援的所有作業系統。
無虛擬方法¶
LLVM 可能会非常频繁地调用支援函式庫介面。为了尽可能提高这些调用的效率,我们不鼓励使用虚拟方法。不需要使用继承来处理实现差异,这只会增加复杂性。#include
机制就能很好地工作。
無公開函式¶
任何由系統函式庫定義的函式(亦即非由 lib/Support
定義)皆不得透過 lib/Support
介面公開,即使該函式的標頭檔未公開也一樣。這可以防止不小心使用系統專屬功能。
舉例來說,stat
系統呼叫就以其提供的資料有所差異而聞名。lib/Support
不得宣告 stat
,也不允許宣告它。相反的,它應該提供自己的介面來探索有關檔案和目錄的資訊。這些介面可以用 stat
來實作,但這純粹是實作細節。支援函式庫提供的介面必須在所有平台上實作(即使是沒有 stat
的平台)。
無公開資料¶
任何由系統函式庫定義的資料(亦即非由 lib/Support
定義)皆不得透過 lib/Support
介面公開,即使該資料的標頭檔未公開也一樣。如同函式,這可以防止不小心使用可能並非所有平台上都存在的資料。
盡量減少軟錯誤¶
作業系統介面通常會針對每個可能出錯的地方提供錯誤結果。在大多數情況下,您可以將這些錯誤結果分為兩組:正常/良好/軟性和異常/錯誤/硬性。也就是說,有些錯誤只是資訊,例如「找不到檔案」、「權限不足」等等,而其他錯誤則嚴重得多,例如「空間不足」、「磁碟區損壞」或「系統呼叫中斷」。我們將第一組稱為「*軟*」錯誤,將第二組稱為「*硬*」錯誤。
lib/Support
必須始終嘗試將軟錯誤降到最低。這是一項設計需求,因為盡量減少軟錯誤可能會影響介面的粒度和性質。一般來說,如果您發現您想要拋出軟錯誤,則必須檢查介面的粒度,因為您很可能嘗試實作太低階的東西。經驗法則是提供*無法*失敗的介面函式,除非遇到硬錯誤。
舉例來說,假設我們想要新增一個 “OpenFileForWriting
” 函式。對於許多作業系統來說,如果檔案不存在,嘗試開啟檔案會產生錯誤。但是,lib/Support
不應該在發生這種情況時簡單地拋出錯誤,因為這是一個軟錯誤。問題在於介面函式 OpenFileForWriting
的層級太低。它應該是 OpenOrCreateFileForWriting
。在發生軟性的「不存在」錯誤時,這個函式應該建立檔案並開啟它以供寫入。
這個設計原則需要在 lib/Support
中維持,因為它避免了軟錯誤處理在 LLVM 其他部分的傳播。硬錯誤通常只會導致 LLVM 工具終止,因此不要害怕拋出它們。
經驗法則
不要拋出軟錯誤,只拋出硬錯誤。
如果你想拋出軟錯誤,請重新思考介面設計。
在內部處理最常見的正常/良好/軟錯誤情況,以便 LLVM 的其餘部分不必處理。
無拋出規格¶
任何 lib/Support
介面函式都不能使用 C++ throw()
規格宣告。這個要求確保編譯器不會在介面函式中插入額外的異常處理程式碼。這是一個效能考量:lib/Support
函式位於許多呼叫鏈的底部,因此可能會被頻繁呼叫。我們需要它們盡可能地有效率。但是,系統程式庫中的任何常式都不應該實際拋出異常。
程式碼組織¶
支援程式庫介面的實作會根據其作業系統的通用類別分開。目前僅定義了 Unix 和 Win32 類別,但可以為其他作業系統分類新增更多類別。為了區分要編譯哪個實作,lib/Support
中的程式碼使用了 LLVM_ON_UNIX
和 _WIN32
#defines
。在實作通用(作業系統獨立)功能之後,lib/Support
中的每個原始碼檔案都需要使用一組 #if defined(LLVM_ON_XYZ)
指令來包含正確的實作。例如,如果我們有 lib/Support/Path.cpp
,我們預計會在該檔案中看到
#if defined(LLVM_ON_UNIX)
#include "Unix/Path.inc"
#endif
#if defined(_WIN32)
#include "Windows/Path.inc"
#endif
lib/Support/Unix/Path.inc
中的實作應該處理所有 Unix 變體。lib/Support/Windows/Path.inc
中的實作應該處理所有 Windows 變體。這樣做可以快速包含將提供實作的基本作業系統類別。特定平台的具體細節仍然必須透過使用 #ifdef
來確定。
一致的語義¶
在不同平台上,lib/Support
介面的實作方式可能會有很大的差異。只要介面函式的最終結果相同,這就沒有問題。舉例來說,在所有作業系統上,建立目錄的函式都相當直接。另一方面,並非所有平台都支援 System V IPC。lib/Support
不應該「支援」 System V IPC,而是應該提供程序間通訊基本概念的介面。實作可能會使用 System V IPC(如果有的話)或命名管道,或任何在給定作業系統上能有效完成工作的機制。在所有情況下,介面和實作必須在語義上保持一致。