RFC-0061:可擴充聯集

RFC-0061:可擴充聯集
狀態已接受
區域
  • FIDL
說明

為了提供更多方式表示酬載可能隨時間變化的酬載,我們建議以擴充的聯集取代目前存在的聯集。

作者
提交日期 (年/月)2018-09-26
審查日期 (年/月)2018-10-11

「迎合夏威夷和阿拉斯加」

摘要

如要提供更多方式表示酬載可能隨時間變化的酬載,我們建議您用可擴充的聯集取代目前存在的聯集

提振精神

如今,聯集沒有隨著時間推移進化,我們甚至會警告「一般而言,變更聯集的定義會破壞二進位檔的相容性。」

目前有許多聯集在需要擴充性的情況下,例如 fuchsia.modular/TriggerCondition、欄位在不移除的情況下就會淘汰,或 fuchsia.modular/Interaction

後文所述,還有許多目前代表的聯集為合適,因為它們不太可能隨著近期的發展而改變。但是,保留 static unionsextensible unions 會產生不必要的複雜性,請參閱優缺點

設計

為了引進可擴充的聯集,我們必須修改 FIDL 的多個部分:語言和 fidlc、JSON IR、線路格式和所有語言繫結。我們也需要將這項新功能記錄在各處。 我們會逐一討論各項變更。

語言

延伸、可擴充的聯集看起來與靜態聯集完全相同:

union MyExtensibleUnion {
    Type1 field1;
    Type2 field2;
     ...
    TypeN fieldN;
}

而在幕後,每個欄位都會被指派一個序數:這與資料表如何具有每個欄位的序數以及自動指派方法的序數不相上下。

具體違規事項如下:

  • 序數會採用與方法序數相同的演算法計算 (詳細資料),將程式庫名稱「.」、可擴充聯集名稱、「/」和成員名稱串連,然後取用 SHA256 和使用 0x7fffffff 遮罩。
  • 序數為 uint32沒有兩個欄位可聲明相同序數,而我們不允許 0。如果發生序數衝突,應使用 [Selector] 屬性提供替代名稱 (或已重新命名的成員)。
  • 序數「可以稀疏」,即與資料表的運作方式不同,需要密集序數。
  • 可延伸的聯集不允許空值欄位
  • 可擴充的聯集必須至少有一名成員

可擴充的聯集可用於目前語言可用的聯集。特別是:

  • 結構體、桌子和可擴充的聯集可以含有可擴充的聯集;
  • 可擴充的聯集可包含結構體、資料表和可擴充的聯集;
  • 介面引數或回傳可以是可以擴充的聯集;
  • 可擴充的聯集可以設為空值。

JSON IR

在接下來的資料表,我們會在每個聯集欄位宣告「一般」中新增一個鍵。

電匯格式

電線上,可擴充的聯集會以序數表示,藉此在選項中 (填充至 8 個位元組),然後是許多已知生產者的信封。具體而言,就是:

  • uint32 標記,包含正在編碼的成員的序數;
  • 用來對齊 8 個位元組的 uint32 邊框間距
  • 儲存信封中位元組數的 uint32 num_bytes,一律為 8 的倍數,如果信封為空值,則必須為 0
  • 儲存信封中控點數量的 uint32 num_handles,如果信封為空值,則必須為 0。
  • uint64 資料指標,用於表示外線資料是否存在:
    • 0 表示信封為空值;
    • FIDL_ALLOC_PRESENT (或 UINTPTR_MAX) 表示有封信,且是下一個物件外物件;
  • 解碼以供使用時,如果信封為空值,則這個 data 指標會是 nullptr,否則為信封的「有效指標」
  • 信封會在內容結束後立即為控點保留儲存空間。

「可為空值的擴充聯集」的標記是 0num_bytes 設為 0num_handles 設為 0資料點是 FIDL_ALLOC_ABSENT,即0。基本上,空值可延伸聯集為 24 個位元組的 0 秒。

語言繫結

可擴充的聯集與聯集類似,但前者在讀取聯集時也需要處理「未知」的案例。在理想情況下,大部分的語言繫結都會

union Name { Type1 field1; ...; TypeN fieldN; };

如同可擴充的聯集,使得程式碼可以輕鬆切換,對未知情況的模數支援,這只會在擴充的聯集的情況下有意義。

首先,建議您不要設定任何語言繫結向保留成員公開:雖然這是 JSON IR 中的保留成員,但我們不認為在語言繫結中公開這些成員會有幫助。

導入策略

導入作業包含兩個步驟。

首先,我們會支援可擴充的聯集:

  1. 透過不同關鍵字 (xunion) 區分靜態聯集和擴充聯集,以語言 (fidlc) 介紹這項功能。
  2. 實作各種核心語言繫結 (C、C++、Rust、Go、Dartt)。擴充相容性測試和其他測試。

其次,我們會將所有靜態聯集遷移至可擴充的聯集:

  1. 產生靜態聯集的序數並放置在 JSON IR 中。後端一開始應忽略這些序數。

  2. 在讀取路徑上,兩種讀取聯集模式就像是靜態聯集模式一樣,也是可以擴充聯集的模式 (如要達到上述目標,則需要一般性)。根據交易訊息標頭中的旗標選擇其中一種。

  3. 更新寫入路徑,以便將聯集編碼為可擴充的聯集,然後透過設定交易訊息標頭中的旗標指明情況。

  4. 更新、部署和傳播所有寫入器後,請移除靜態聯集處理,以及軟式轉換的鷹架程式碼。

說明文件與範例

如要這麼做,至少需要在下列位置提供說明文件:

回溯相容性

可擴充聯集明確「不」與「靜態」聯集相容

效能

不使用時不會影響效能。建構期間對效能的影響微乎其微。

安全性

對安全性沒有影響。

測試

編譯器中的單元測試、各種語言繫結編碼/解碼的單元測試,以及相容性測試,以便同時檢查各種語言繫結。

缺點、替代方案和未知

可擴充聯集的效率低於無法擴充的聯集。此外,也無法透過該語言中的其他方式表達不擴充的聯集。因此,我們建議兩種功能並排使用。

不過,我們可以決定只有可擴充的聯集應該存在,並與目前定義的聯集一起進行。這種做法適用於富奇西亞中許多地方,而聯集代表效能至關重要訊息,而且對擴充功能的需求很少,例如 fuchsia.io/NodeInfofuchsia.net/IpAddress

保留靜態聯合的優缺點

優點

  • 與聯集相比,可延伸聯集會產生 8 位元組的費用 (針對信封大小和控點數量)。此外,可擴充的聯集資料一律會以外部方式儲存 (即資料指標額外儲存 8 個位元組),而只有可為空值的聯集資料則以外線方式儲存。
  • 由於聯集的編碼是無法在 FIDL 中使用其他原始物件表示的。因此,如果這些內容從語言中移除,部分訊息類別可能無法再以精簡且有效率的方式呈現。
  • 在某些情況下,聯集作業能以有效率的方式表示,但也會有所不同,但這兩者並非常態。舉例來說,只有在可使用 InterfaceAddress 直接編寫 InterfaceAddressfuchsia.net.stack/InterfaceAddressChange 的情況下,可以使用 fuchsia.net.stack/InterfaceAddressChangeEvent 進行重新編寫,而其中的 enum 指出其是否新增或移除。

缺點

  • 同時保留靜態聯集和可擴充聯集,會使得編譯器、JSON IR、所有後端以及編碼/解碼造成複雜度。改善效果極低:在世界上,FIDL 編碼在一開始並不會特別有效大小,因此大小差異相差不大。此外,您也可以視需要解碼擴充的聯集。
  • 以下是取得最低益處的分析範例,以下是 fuchsia.io/NodeInfo 的分析:
    • 目前 NodeInfo 有 6 個選項:服務 (大小 1)、檔案 (大小 4)、目錄 (大小 1)、管道 (大小 4)、vmofile (尺寸 24)、裝置 (大小 4)。
    • 因此,NodeInfo 的總大小一律為 32 個位元組,即標記 + max(選項大小) = 8 + 24 = 32。
    • 使用可擴充聯集時,NodeInfo 大小取決於編碼的選項。一律有 16 個位元組的「tax」值 (vs.8),因此個別大小為:service = 24、file = 24、directory = 24、pipe = 24、vmofile = 40、device = 24。
    • 因此,不論在什麼情況下,我們都會把 8 個位元組轉換成 8 個位元組,但在 vmofile 的情況中,我們會新增額外的 8 個位元組。
  • 對於同時擁有靜態聯集和擴充聯集的語言而言,複雜度也比較高。我們希望程式庫作者在使用這種模式時多半願意使用彼此,而選擇擴充聯集是較安全的長期選擇,而且費用極低。

總而言之,我們決定以可擴充的聯集取代靜態聯集。

標記與序數

我們使用「一般」來表示指派給欄位的內部數值,也就是透過雜湊計算得出的值。我們會使用「標記」來表示繫結中的變化版本表示法:在 Go 中,這可能是類型 alias 的常數,在 Dart 中,這可能為 enum

fidlc 編譯器只會處理序數。開發人員很可能會只處理代碼。繫結則提供從高階標記轉譯為低階內部序數的轉譯。

沒有任何空白的可擴充聯集

在設計階段,我們視為擴充了空白。不過,我們最終選擇禁止這種情況:透過單一變數 (例如空白結構) 選擇可以空值的聯集,即可清楚地模擬意圖。這還可避免擴充聯集具有兩個「單位」值,即空值和空白值。

先前的圖片和參考資料

  • 通訊協定緩衝區有其中一項
  • 除非有特殊情況,否則 FlatBuffers 的聯集無法擴充。