RFC-0097:FIDL 工具鍊

RFC-0097:FIDL 工具鍊
狀態已接受
領域
  • FIDL
說明

標準 FIDL 工具鍊的說明。

更小鳥
作者
審查人員
提交日期 (年月分)2021-04-27
審查日期 (年-月-日)2021-05-26

摘要

我們會說明 FIDL 工具鍊必須符合哪些條件,並提供解題方式的說明。

雖然特定實作計畫不在此 RFC 的範圍之內,但 Fuchsia Source Tree 中的工具 (例如 fidlcfidlgen_gobanjo) 和建構規則 (例如 fidl_library.gni) 需要經過改良,才能符合本文列出的規定。

此外,居住在 Fuchsia Source Tree 之外的 FIDL 工具鍊應遵循此處所列的要求。(這個 RFC 不具 Fuchsia 之外的授權,因此我們無法強制要求法規遵循,但強烈建議這麼做)。

術語

開始前,先釐清一些字詞FIDL 工具鍊的簡化檢視畫面可匯總如下:

簡化的工具鍊

FIDL 語言fidlc 組成,代表前端編譯器 (或簡稱「前端」)。這時所有語言驗證程序都會完成。

前端會為每個編譯的 FIDL 程式庫產生中繼表示法 (配音為「JSON IR」)。儘管名稱不大,中繼表示法不一定需要以 JSON 檔案表示。

接著,一或多個「後端」會處理 JSON IR 以產生輸出內容。請注意,從 FIDL 工具鍊的角度來看,JSON IR 的任何消費者都是後端。

最常見的輸出內容是採用目標語言 (例如 C++ 或 Rust) 的程式碼,因此您可以操控類型、與通訊協定互動、開啟服務,以及使用常數。此後端類別稱為「FIDL 繫結」1,而產生的程式碼應遵循繫結規格。我們通常會使用簡短 fidlgen (或 fidlgen_<suffix>,例如 fidlgen_rustfidlgen_dart) 參照產生 FIDL 繫結的後端。我們將「網域物件」視為使用目標語言的集合和類型組合,這些類別和類型用於代表 FIDL 類型。舉例來說,FIDL 列舉 fuchsia.fonts/Slant 在 C++ (enum class) 或 Go 中會有對應的網域物件 (做為 type Slant uint32)。

市面上還有許多其他後端,各有自己的需求和慣用方法。例如:fidldoc 會產生 FIDL 說明文件,例如 fuchsia.fonts 頁面;fidl_api_summarize 會產生 FIDL 程式庫的 API 摘要;fidlcat 則會使用 JSON IR 提供執行階段自我檢查。從 FIDL 工具鍊的角度來看,fidlcat 工具是後端,但此工具實際操作的一小部分是這項工具的。

提振精神

FIDL 在成長中。內部工具鍊的明確性已通過測試。 因值未充分符合新規定。就需要新的擴充工具鍊。

我們會先說明其中一些新要求,然後遵循支援所有新要求的方法。

整個節目檢視畫面

目前,FIDL 工具鍊假設後端是根據每個程式庫運作,因此只需要這個程式庫的 JSON IR 才能運作。

後端越來越需要存取多個 JSON IR 來滿足其需求。

舉例來說,fidldoc 需要同時記錄的所有程式庫的 JSON IR,才能產生全域索引。fidlcat 位於同一艘船中,需要查看所有程式庫才能運作。measure-tape 需要透過產生測量磁帶的目標類型,間接存取程式庫的 JSON IR。

整理中繼資料

部分後端需要程式庫的特殊中繼資料才能運作。通常,此中繼資料需要從程式庫依附元件樹狀結構的分葉 (「基礎程式庫」) 開始疊代計算,而中繼資料則要與根層級 (正在編譯的程式庫) 保持同步。

舉例來說,[fidlgen_rust] 會想瞭解類型是否可能包含浮點數,以判斷可以安全衍生哪些特徵。不含任何浮點數的 struct 可以包含 Eqstrict union,其中沒有任何變體包含任何浮點數都可能擁有 Eq,但提供一個浮點欄位的 flexible table 不能有 Eq,因為提供會違反來源相容性規則。

另一個範例是來自 fidlgen_cpp,會產生無擁有權的網域物件。如果這些網域的物件內嵌部分是值,例如非資源,則可以安全複製。我們再重申一下,計算這個中繼資料呼叫「內嵌資源性」時,需要從葉子到根層級,反覆計算這個值。

最近在討論為程式庫產生 ABI 指紋的新後端時,必須來回瞭解該功能應在何處存在。目前的考量是基於實際原因,在 fidlc 編譯器中代管這項功能,但這個答案不太理想。

我們觀察到的是,需要中繼資料監護的功能會處於保留狀態、自行處理 (經常) 或新的編譯器功能,通常是盡早進行一般化 (例如上述的 ABI 指紋)。

此外,由於 Fuchsia FIDL 團隊能夠輕鬆變更編譯器,因此我們在這方面比第三方擁有許多優勢。因此,我們發現工具的狀態會影響我們的開放原始碼原則,也就是希望所有後端都放在關卡的遊戲領域。

依目標語言和每個程式庫後端選項

隨著用於描述核心 API 的 FIDL 語言,以及實際運作中的驅動程式 SDK,FIDL 已變得日益普及。

不過,目前「FIDL 語言」和「後端」工具鍊之間會有限制,以便處理特定程式庫。如果目標需要 fuchsia.fonts 程式庫的 Rust 程式碼,我們會叫用 fidlgen_rust

這個方法太簡單,無法說明某些程式庫需要特殊後端。舉例來說,library zx; 會由 kazoo 處理。這個每個程式庫按目標顯示的 Fidlgen 選項有進一步的缺點。列舉 zx/clock,這是我們對 kazoo 到一天的意圖,會產生目前手寫的 zx_clock_t typedef,以及各種 #define 將列舉成員具體化的方法。如果 fuchsia.fonts 程式庫使用 zx/clock,則這表示 fidlgen_cpp 需要知道 API 合約,才能產生繫結程式碼正確橋接2其程式碼產生方式和 kazoo

每個平台各有一個程式庫

目前,我們對於擁有相同名稱的 FIDL 程式庫的多個定義並不公。雖然不建議在原始碼樹狀結構的不同位置定義多個程式庫 fuchsia.confusing,並分別使用所有不同的程式庫。

較適合使用平台 ID 概念,Fuchsia Source Tree 中的預設值為 fuchsia。如此一來,我們就可以保證並強制規定,沒有任何相似名稱程式庫的兩種定義存在。

瞭解到這項限制後,我們稱之為「平台」,這是共用相同平台 ID 的 FIDL 程式庫組合。

沒有延遲驗證

目前,後端無法選擇性地指出它們可能或無法成功執行的 FIDL 程式庫。後端應處理任何有效的 JSON IR。這項限制意味著我們避免在後端執行任何延遲驗證。您可能會在後端新增驗證,以識別尚未實作的 FIDL 功能;另一個範例驗證是檢查 fidldoc 中的文件註解是否有效,並拒絕產生參考文件。(在這兩個例子中,優雅降級都是正常現象)。

若允許延遲驗證,會帶來令人不悅的破壞行為 (例如 https://fxbug.dev/42144169):在 FIDL 程式庫以 SDK 構件的形式提供並整合至下游存放區的世界中,執行後端的開發人員可能與 FIDL 程式庫作者不同。因此,如果能修正 FIDL 程式庫作者適用的內容,將警告或錯誤給使用 FIDL 程式庫的開發人員,而且最令人困擾,也最糟的是使用 FIDL 程式庫。

因此,禁止延遲驗證的政策在 fidlc 編譯器上維持良好的壓力,以便「驗證所有事物」,並在後端「支援所有事物」。這通常能避免發生過距離失敗的這類故障,但代價是缺乏細微差異的位置 (「後端沒有驗證」)。

限制來源存取權

在不考慮長期後果的情況下,我們逐漸允許 JSON IR 複製其來源的 FIDL 來源的一部分。舉例來說,隨著已新增更複雜的運算式,我們公開解析值可讓後端發出常數,同時在 IR 中保留運算式本身 (文字)。雖然具有運算式文字有助於在產生的程式碼中產生有意義的註解,卻有助於減少 SDK 發布者 (選擇不提供 FIDL 成果) 的隱私。

很容易想像,如果未來路徑會讓更多 FIDL 來源流入 IR,這並不是理想的結果:這都是重複的情況,而且可能會導致隱私權邊界可能遭到侵犯。

相反地,我們的目標是設計 FIDL 工具鍊,只納入不必要的來源。對於需要來源存取權的罕見後端 (例如 fidl-lsp),我們仰賴 Span 的參照。請參閱「設計」一節的詳細資料

資源調度編譯

為了方便起見,fidlc 編譯器最初的設計範圍僅限在來源 (例如 .fidl 檔案) 上運作。如果程式庫有依附元件,程式庫的編譯就需要遞移對其所有依附元件進行編譯。

舉例來說,編譯 fuchsia.fonts 程式庫時,也必須編譯 fuchsia.memfuchsia.intl 程式庫,然後以這種方式遞移。這表示現今的編譯作業效率極低。fuchsia.mem 等核心程式庫會經過多次重新編譯。這種架構效率不彰從未存在問題:目前 SDK 中只有 64,000 個 FIDL 來源的 LoC 檔案,而且由於其遞移性相對淺,這種效率並不明顯。

不過,在考慮「理想」的 FIDL 工具鍊時,我們希望與編譯器設計中的標準做法保持一致。傳統上,編譯器會擷取來源檔案等輸入內容,並產生 x86 組合等輸出內容。隨著程式碼集增加,編譯器還必須滿足其他條件,才能提供工作的某種分區,因此對輸入項目進行小幅更新,並不需要重新編譯整個程式碼集。

例如 javac 編譯器:如果您變更部分檔案 SomeCode.javafor 迴圈的條件,就必須重新編譯數千個檔案才能再次執行程式。而是只重新編譯該單一檔案,並能重複使用所有其他預先編譯的來源 (做為 .class 檔案)。

為了成功分割工作,標準做法是定義編譯單位 (例如 FIDL 的程式庫) 並產生中介結果 (例如 JSON IR),因此編譯程序的輸入內容既是直接依附元件的來源和中繼結果。這樣就能將總編譯時間 (假設無限平行) 限制在依附元件鏈 (編譯時間最長)。這樣也簡化了建構規則,也就是 dujour 主題。

設計

設計分為三個部分:

  1. 首先是指導原則:做出設計選擇 並確立方法和路徑
  2. 標準 FIDL 工具鍊的說明範例,說明如何分解建構 FIDL 以滿足上述所有要求。
  3. 最後,Fussia FIDL 團隊為了配合 RFC 指南而設置的特定清理

指導原則

IR 應能輕鬆因應一般後端

雖然有複雜的後端應能使用 (例如整個程式檢視畫面),但 IR 的設計必須讓一般後端只能經由處理單一 IR 來建構用於處理的程式庫。

經驗顯示,大多數後端都相當簡單。相較於專家用途,我們只需投入一些簡單的用途,就能盡可能簡化 IR,同時確保我們致力確保蓬勃發展的後端生態系統。

為實踐這項原則,請考慮在 fidlc 中完成「類型形狀」計算。建議改為將這個後端移至專屬的「循環」後端。不過,這麼做會強制所有產生目標代碼的後端 (主要用途) 都依賴 IR 和這個「類型形狀」後端。

IR 應盡量精簡

追求最低限度是重要的因應對策,不僅能輕鬆配合常見的後端,也可以很輕鬆,甚至令人迫不及待想「納入所有事情」並考慮完成的工作。

為實踐這項原則,請考慮目前在 fidlc 中計算「宣告順序」的反模式。只有少數後端會依賴此順序 (C-family,甚至更短於日常),進而在編譯器中增加複雜性。另外,也會將水化到需要下訂單的原因,而且往往會引發混淆。這也屬於缺乏彈性的做法,因為後端希望與核心編譯器獨立開發,這已經是執行個體支援遞迴類型的進展

IR 不得包含來源

IR 不應包含足以容納一般後端所需的來源 (例如名稱)。IR 可視情況提供來源時距參照。來源 Span 參照就是三欄:

(filename, start position, end position)

位置為元組 (line number, character number)

後端不應仰賴來源存取權來運作。如果後端必須有權存取來源才能運作 (例如 fidl-lsp),必須清楚說明這項需求,並在無法存取來源時優雅失敗。

選擇這種分解方式時,我們會明確選擇提供 SDK 發布者 (負責發布 FIDL 構件的 SDK 發布者) 來選擇是否要納入來源 FIDL。目前,使用者不會完全做出選擇,因為來源的某些部分最後位於 IR。

除了名稱以外,IR 中原始碼的重要部分則是說明文件註解。這些註解是屬於 API 的一部分,也就是 FIDL 程式庫作者明確選擇公開這些註解。再者,大多數後端會使用這些說明文件註解 (例如在產生的程式碼中發布註解),因此屬於熟悉的常見後端原則。這些說明文件註解不會以原始來源的形式顯示在註解中,而會預先處理 (醒目縮排、/// 和剩餘空白字元)。正如簡單探索,我們計畫日後會進一步處理文件註解。

後端一視同仁

FIDL 語言、其實作做為 fidlc 編譯器,以及中繼表示法的定義應能夠實現多元包容的後端生態系統,其中所有後端 (無論是否在 Fauchsia 專案中建構) 皆位於平等的位置。

在選擇這種分隔線時,我們明確選擇避免因 Fuchsia FIDL 團隊擁有的後端短期需求而有負擔,而非專注於 FIDL 生態系統的長期可行性。

後端無法無法運作

後端在處理有效的 IR 時必須成功。如果後端在環境發生問題 (例如檔案系統存取錯誤),或是 IR 無效,後端可能會失敗。如果後端無法處理符合 IR 結構定義的 IR,則不應因發生錯誤而失敗。

選擇此分隔線時,我們會明確強制要求所有驗證在前端發生,即驗證必須提升為 FIDL 語言限制。這麼做有兩個非常重要的理由:

  1. 此規則的一個共同點是,已指定有效的 IR,所有與此 IR 相容的後端皆可使用。換言之,SDK 發布者必須確保成功編譯 FIDL 程式庫的所有消費者都能使用這些程式庫,確保所有使用者與發布者使用的 FIDL 工具鍊相容。
  2. 從語言設計的角度來看,這項嚴格要求是有利於強制函式設計符合後端需求。舉例來說,如果後端擁有者因為某些原因而必須進行驗證,就會向 Fuchsia FIDL 團隊提出這個問題 (fidl-dev@fuchsia.dev 或透過 RFC) 提交給 Fuchsia FIDL 團隊,以便納入語言規格中。這可能會改善語言以使所有內容受惠,或是重新調整後端,以便更符合 FIDL 工具鍊的原則。

為實踐這項原則,請考慮 Rust 中的特徵衍生:含有浮點數的類型無法衍生 Eq 特徵。您可能會嘗試為包含浮點數 (float32float64) 的 FIDL 類型新增屬性 @for_rust_has_floats,然後在 fidlgen_rust 中使用這項屬性,同時有條件地發出 Eq 特徵,並驗證該屬性的使用方式是否正確無誤 (類似值資源資源分佈)。但這種誘惑卻違反原則,因為這表示 fidlgen_rust 是可行的。我們也建議在 fidlc 中驗證這類小眾屬性,因為這會導致 FIDL 因為有眾多指定語言的特定疑慮而複雜。3

標準 FIDL 工具鍊

標準 FIDL 工具鍊以程式庫分解為中心,並且將擁有兩種建構節點。

對照建構節點

「對照節點」會為工具提供程式庫和直接依附元件的程式庫和物件檔案來源,並產生最終結果和目標物件檔案。

懸浮的節點

舉例來說,大多數 Firedlgen 後端目前都會遵循這個模式:其來源為 JSON IR,而最終結果會產生程式碼。這些物件沒有相依物件檔案 (DOF),也不會產生目標物件檔案 (TOF)。

另一個範例是規劃的 ABI 指紋工具,需要計算類型的結構屬性。這項工具會使用 JSON IR (來源),並產生 ABI 摘要 (結束結果) 和隨附的目標物件檔案 (TOF)。當程式庫作業在具有依附元件的程式庫時,會使用這些程式庫的 TOF,也就是其 DOF,以及 JSON IR 來產生下一個最終結果。可能可能是因為最終結果和 TOF 的格式不同,指的是可透過人類和工具剖析的最終結果。

整個檢視畫面建構節點

系統會提供「整個檢視節點」來源,包括所有可連帶存取的依附程式庫至叫用工具,並產生最終結果。

整個檢視畫面節點

舉例來說,measure-tape 需要所有可移植的程式庫的 IR,以便定義要編譯的類型,因此自然會顯示為整個檢視節點。目前 fidlc 節點會做為整個檢視節點運作,因為需要所有來源的存取權才能運作 (詳情請參閱「資源調度編譯」一節)。fidlcatfidldoc 都需要整個檢視畫面,其依附元件涉及編譯的整個房源平台

雖然整個檢視畫面節點的效率確實低於依組合節點的效率,但我們可能不想重新建構所有工具,以利使用緊密結合的方式運作,而是選擇選擇將部分複雜程度推入建構系統。

在 Fuchsia 來源樹狀結構中,我們產生了 all_fidl_json.txt 檔案。考量到整個檢視區塊節點的明確要求後,我們就能妥善建構這項匯總資料。舉例來說,我們能夠「依平台」整理這項匯總資料,為每個程式庫來源、JSON IR 和直接依附元件記錄資料,進而輕鬆運用這項匯總資料,快速產生整個檢視工具所需的輸入內容。此外,fidl-lspfidlbolt 等開發人員工具也會使用此匯總資料。

工具選項

指定建構節點中的工具選項應取決於目標 (例如「產生低階 c++ 程式碼」),以及要編譯的程式庫 (例如「library zx」)。我們會定義使用元組 (目標產生、程式庫) 和傳回工具 (例如 kazoofidlgen_cpp) 為工具鍊全域設定的總函式。

例如,在 Fuchsia 來源樹狀結構中,我們預期設定如下:

(*, library zx) → kazoo
(low_level_cpp, not library zx) → fidlgen_llcpp
(high_level_cpp, not library zx) → fidlgen_hlcpp
(rust, not library zx) → fidlgen_rust
(docs, *) → fidldoc

使用統一的 C++ 繫結時,這項設定會變更為:

(*, library zx) → kazoo
(cpp, not library zx) → fidlgen_cpp
(rust, not library zx) → fidlgen_rust
(docs, *) → fidldoc

對漸進式編譯的影響

查看漸進式編譯時,即希望將現有已編譯的構件與新編譯的構件結合,以執行最少的工作來回應來源變更,而本文說明的這兩種節點的表現不盡相同。

一般而言,當一或多個來源 (也稱為「來源集」) 變更時,需要叫用編譯圖中的節點。

散播節點的來源集比整個檢視節點小得多,其來源集是直接的「來源」和「目標物件檔案」(TOF)。儘管如此,這些項目的對應行為 (如果運用) 會將來源變更傳播到 TOF 變更,進而變更依附節點的來源集。舉例來說,假設 fidlgen_rust 後端已經過擴增,也會產生 TOF fuchsia.some.library.fidlgen_rust.tof。當一個程式庫變更時,如果其 TOF 也變更,則所有依附程式庫也都需要變更,因此導致對 fidlgen_rust 後端發出的更多叫用 (依此類推)。

與排列節點相比,整個檢視畫面節點的來源集更加廣泛。整個檢視區塊節點分成兩種廣泛類別:一種是依據所有遞移依附元件 (例如 measure-tape),另一種則取決於平台中的所有程式庫 (例如 fidldoc)。因此,任何變更都可能會導致這些節點需要叫用。

整個檢視畫面節點的漸進式編譯成本加倍,這些節點需要更頻繁地執行,而且由於查看更多來源,因此會執行更多工作。但多件工作是,有人說「簡單的程式設計」,任何需要完整檢視畫面的後端都可以演變成運作節點。要考慮這類演變的健康壓力通常不僅相當複雜,維護負擔也相當高,此時要考慮漸進式編譯帶來的速度優勢,以及在費用無法負擔的情況下,於該路徑上創業。

除了工具鍊本身的增量編譯成本外,還須考量到下游的影響。由於大部分的工具鍊都會產生原始碼 (例如 C++、Rust、Dart),因此通常更接近整體建構圖表的根層級,因此對工具鍊的輸出內容做出任何變更 (例如變更產生的 C++ 標頭),都會對下游編譯 (例如所有程式碼直接或間接) 造成重大影響。因此應設法盡量減少對已產生來源所做的變更。舉例來說,將產生器的輸出內容標準化 (避免變更非有意義的空白字元),或將輸出內容與快取版本進行比較,以避免覆寫含有相同內容的內容,避免只變更時間戳記 (詳情請參閱 GN 輸出範例)。

清理舊有技術債,並避免更多

按照此處說明的原則,我們會將 C 繫結和程式設計資料表產生的 C 繫結和程式設計資料表從 fidlc 移出。由於過去的建構小工具,無法將這兩個代入核心編譯器中嵌入。

我們計劃從 IR 移除「宣告順序」,改為將任何特殊順序向下推送到特定後端。

縮放編譯所述,FIDL 編譯器 fidlc 會改變做法,只要求來自直接依附元件的輸出內容 (可能是 JSON IR 本身),而非所有遞移依附元件的來源,藉此將工作分區。

最後,我們現在將避免累積更多技術債,而是專注於讓工作與本文所述的方向保持一致。舉例來說,做為下一個後端的 ABI 數位指紋採集將是每個後端,而不是內嵌在核心編譯器中。

既有藝術

將此項目與 C++ 編譯比較/對比:C++ 編譯器通常會用到一個 C++ 來源檔案,並產生一個物件檔案。在最後的「連結」階段組合階段,連接器會將所有物件檔案合併為一個二進位檔。此方法之所以有效,是因為在單獨編譯一個 C++ 來源檔案時,編譯器會透過使用標頭,在其他 C++ 來源檔案中看到有關外部函式的函式宣告。同樣地,目前的 JSON IR 與函式宣告一樣,提供大部分外資料庫類型的相關資訊。

然而,如果需要進行更深入的最佳化調整,這個 C++ 編譯模型太差:當編譯器只能查看宣告時,必須非常保守函式的實際行為 (例如,它是否會一直終止指標 X?是否會保留指標 Y?) 是否會保留指標 Y?同樣地,在 FIDL 中,如果程式碼產生後端能進一步瞭解參照的外型類型,或許就能產生更精簡且更完善的程式碼。就資源和原始碼相容性而言,我們的要求已成為後端無法產生正確程式碼的影響,除非知道所有參照外類型的資源是否可用。

為解決這個問題,在 C++ 中,各種編譯器實作開始在物件檔案中插入更多和輔助資料。舉例來說,GCC 和 Clang 皆開發了自己的可序列化 IR 格式,以進一步表示這些 C++ 函式的行為,並連同組件一起封裝。連結器會耗用組合和 IR,並產生更優質的程式碼 (稱為「連結時間最佳化」)。在 FIDL 中,由於各種後端可能需對外型類型有不同知識,因此將「輔助資料」與「物件檔案」分離,例如在主要 JSON IR 旁產生後端專屬的補充資訊。實際上,資源性是許多後端所需的常見屬性。但將來,LLPP 等公司在產生要關閉處理的程式碼時,最好也知道該型別是否包含外行物件 (同樣地適用於編碼和解碼)。Rust 希望在更多情況下,判斷類型是否包含浮點值,藉此衍生 Eq (不過,還是需要編譯器保證才能避免來源相容性問題)。

說明文件

此 RFC 是改善 FIDL 工具鍊說明文件的基礎,建議工具鍊作者正確記錄其提供的建構規則。

實作

如上所述。

效能

對效能沒有影響,這個 RFC 描述需求和已經達成的問題分解,雖然不像乾淨度一樣簡單。

人體工學

不適用。

回溯相容性

不適用。

安全性考量

沒有安全性考量。

隱私權注意事項

由於來源與智慧聯網 (IR) 之間的分離更明確,進而改善隱私權。

測試

工具鍊的標準測試。

缺點、替代項目和未知

如文字所述。


  1. 就技術上,我們將程式碼產生工具呼叫 FIDL 繫結、支援使用產生的程式碼所需的執行階段程式庫,以及產生的程式碼。

  2. C-family fidlgen 不想為 zx/clock 產生自己的網域物件,而是選擇 #includekazoo 產生的標頭。同樣地,Rust fidlgen 會匯入 kazoo 產生的 zx 繫結,而不是根據 zx 程式庫定義產生自己的網域物件。

  3. 目前我們無法在 FIDL 中新增 @has_floats 屬性 (或 has_float 修飾符),因為這個用途只有 fidlgen_rust,即使這沒有重大問題。如果這些問題發生變化 (例如其他幾個後端有類似 PartialEq/Eq 問題),或許就會有所改變。