支援函式庫¶
摘要¶
本文檔提供關於 LLVM 支援函式庫的一些細節,其原始碼位於 lib/Support
和 include/llvm/Support
。此函式庫的目的是使 LLVM 免於受到作業系統差異的影響,僅需作業系統提供少數 LLVM 需要的服務。LLVM 的大部分程式碼是使用標準 C++ 的可移植性功能編寫的。然而,在少數領域,需要系統相依的設施,而支援函式庫是這些系統呼叫的包裝器。
透過集中 LLVM 對作業系統介面的使用,我們使得 LLVM 工具鏈和執行時期函式庫更容易移植到新的平台,因為(理論上)只需要移植 lib/Support
。這個函式庫也使 LLVM 的其他部分擺脫了 #ifdef 的使用和特定作業系統的特殊情況。這些使用方式被替換為對 include/llvm/Support
中提供的介面的簡單呼叫。
請注意,支援函式庫並非旨在成為完整的作業系統包裝器(例如 Adaptive Communications Environment (ACE) 或 Apache Portable Runtime (APR)),而僅提供支援 LLVM 所需的功能。
支援函式庫最初被稱為系統函式庫,由 Reid Spencer 編寫,他根據源自 eXtensible Programming System (XPS) 的類似工作制定了設計。有幾個人協助了這項工作;特別是 Jeff Cohen 和 Henrik Bach 在 Win32 移植方面。
保持 LLVM 的可移植性¶
為了保持 LLVM 的可移植性,LLVM 開發人員應遵守一組與支援函式庫相關的可移植性規則。遵守這些規則應有助於支援函式庫實現其目標,即保護 LLVM 免於作業系統介面變化的影響,並有效率地做到這一點。以下章節定義了實現此目標所需的規則。
不要包含系統標頭檔¶
除了在 lib/Support
中,任何 LLVM 原始碼都不應直接 #include
系統標頭檔。在開發 lib/Support
時,已謹慎地從 LLVM 中移除所有此類 #includes
。具體而言,這表示像 “unistd.h
”、“windows.h
”、“stdio.h
” 和 “string.h
” 這樣的標頭檔,在 lib/Support
的實作之外,禁止被 LLVM 原始碼包含。
為了獲得系統相依的功能,應使用 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 的其餘部分不必處理。
沒有 throw 規格¶
任何 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 甚至在所有平台上都不支援。與其「支援」System V IPC,不如 lib/Support
應提供一個介面來處理跨進程通訊的基本概念。如果 System V IPC 可用,則實作可以使用 System V IPC,或者使用具名管道,或者任何可以有效地完成給定作業系統工作的方式。在所有情況下,介面和實作在語意上必須一致。