支援函式庫

摘要

本文檔提供關於 LLVM 支援函式庫的一些細節,其原始碼位於 lib/Supportinclude/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 上,這涉及以下作業系統呼叫:getenvforkexecvewaitlib/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 工具終止,因此不要羞於拋出它們。

經驗法則

  1. 不要拋出軟錯誤,只拋出硬錯誤。

  2. 如果您想拋出軟錯誤,請重新思考介面。

  3. 在內部處理最常見的正常/良好/軟錯誤條件,以便 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,或者使用具名管道,或者任何可以有效地完成給定作業系統工作的方式。在所有情況下,介面和實作在語意上必須一致。