RFC-0026:處處可見

RFC-0026:隨處郵件
狀態已遭拒
區域
  • FIDL
說明

讓信封比精簡兩倍,提升現有信封格式的效率。使用信封做為參照所有外部物件的唯一方式。這樣可以提升傳輸格式的一致性,並且確保通訊協定設計與實作的一致性。

作者
提交日期 (年/月)2019-01-19
審查日期 (年/月)2019-02-04

拒絕原因

考量到這個 RFC 的意見回饋和評論數量龐大,我們決定撤回 (例如自行拒絕) 提案。話雖如此,但仍有一些好點子:我們會採納這些想法,並以範圍較小的獨立 RFC 發布,讓討論更加清楚,並將獨立功能分隔成自己的 RFC。

RFC-0032 已納入這個 RFC 中。

摘要

這份 RFC 有兩個目標:

  1. 將信封設為密集的兩倍,改善現有 envelope 格式的效率。
  2. 使用信封做為參照「所有」外線物件的唯一方式。 這樣可以提升傳輸格式的一致性,以及通訊協定設計和實作的一致性。

(1) 和 (2) 的副作用是,可以有效為所有類型實作可選 (是否可為空值),而不只是結構體、控點、向量、字串、資料表和 (可擴充) 聯集1

提振精神

信封是可延伸、可持續調整的資料結構 (資料表和可擴充的聯集) 的基礎。只要提高信封效率,這些擴充結構就能應用在效能和線尺寸的其他情境下。

FIDL 還有多種可用於動態大小的資料類型:向量與字串。這些類型必須是虛線,因為系統預期會以靜態方式得知 FIDL 主要物件的大小。如果信封可用於代表所有外流資料,我們便可簡化通訊協定和實作,減少實作成本並減少出錯的空間。

此外,FIDL 也能受益於以完整且一致的方式進行選用。 這可以提升人體工學成效,而且可以選擇更多類型比目前機制允許的類型,並簡化使用者心理模型。信封可透過統一的方式,為所有類型啟用選用功能,藉此達成這些目標。

設計

信封是指符合下列其中一項條件的資料:

  • 外框,類似現有信封格式,或
  • 「內嵌」,即資料儲存在信封本身。這可用於固定大小且小於 64 位元的「小尺寸」類型。

行外信封

外包的信封如下:

圖:超信封外含 64 位元小端序、48 位元大小低於 0.00 和 16 位元的控制代碼

做為 C 結構體:

typedef struct {
  uint64_t size:48;  // Low bit will be 0
  uint16_t handle_count;
} fidl_out_of_line_envelope_t;

外包信封具有下列與現有的信封格式的變更:

  • 大小 (num_bytes) 是 48 位元,而非 32 位元,能支援較大的酬載。
    • 大小包括任何可遞迴編碼的子物件的大小。
      • 舉例來說,vector<string> 的大小包括外部向量內部字串子物件的大小。
      • 這與目前信封實作大小欄位的現有行為相符。
    • 由於跨行物件會對齊八位元組,因此正常範圍外物件的大小一律為 8 的倍數。這表示 size % 8 == 0,這表示
      • 大小欄位的最下方三位元 (以及大小欄位的 LSB) 都會是零,因此
      • 信封的 LSB (因為大小欄位位於信封的 LSB 中) 也一律為零
      • 這點很重要,如下方標記 Bit 中所述。
    • 請參閱下方的對行外郵件編碼大小一節,瞭解計算遞迴大小對效能的影響。
  • handle_count 是 16 位元,而非 32 位元。
    • 目前,Zircon 管道無法傳送超過 64 個帳號代碼,我們覺得 16 位元已足以因應未來需求。
    • handle_count 包含所有遞迴子物件的控制代碼數量。
  • 系統會捨棄「存在/不存在」欄位。
    • 狀態會以非零值表示 sizehandle_count 欄位。
    • sizehandle_count 欄位皆表示不存在。
      • 我們稱之為「零信封」

解碼器「可能會」使用信封資料指標覆寫信封,假設他們知道信封內容的靜態類型 (結構定義)。如需在內容類型不明的情況下處理信封的建議,請參閱「解碼器回呼」一節。

標記位元

外框外的信封會明確擁有佔用最少重要位元的控制代碼數量。如「信封」一節所述

  • 由於尺寸欄位的最小位元一律為零 (因為大小為 8 的倍數)
  • 信封的最低位元一律為零。

我們稱之為信封的最底部分,即標記位元

  • 如果標記位元為 0,代表信封的資料為外框
  • 如果標記位元為一個,那麼信封的資料就會內嵌

由於標記位元屬於內嵌資料,因此內嵌信封也無法是需要 64 位元對齊架構的實際指標,因為指標為 8 的倍數,且最低三位元必須設為零。這有助解碼器區分內嵌信封和實際指標,因為解碼器通常會覆寫外框外的信封,但不是內嵌信封,而是透過指標指向信封的內容。

內嵌信封

內嵌信封的編碼方式為:

圖:在行信中,64 位元小端序,最小位元的值是 1 表示標記、保留 31 位元,然後是 8、16 或 32 位元內嵌資料的值

做為 C 結構體:

typedef struct {
  uint8_t tag:1;  // == 1
  uint32_t reserved:31;
  union {
    _Bool bool;
    uint32_t uint32;
    int32_t int32;
    uint16_t uint16;
    int16_t int16;
    uint8_t uint8;
    int8_t int8;
    float float32;
    zx_handle_t handle;  // Only when decoded (see Handles for more details)
  };
} fidl_inline_envelope_t;
  • 內嵌信封會將 LSB 設為 1,以便與外包信封和實際指標區分。
  • 信封的上 32 位元會用來表示內嵌值,可以是 int8uint8int16uint16int32uint32float32bool 或控制代碼。
    • 如果值小於 32 位元寬 (標準小端子表示法),上方 32 位元的最低位元則用來表示值。
  • 除非未來的 RFC 指定如何解譯這些位元,否則編碼器「必須」將保留的位元編碼為零。
  • 解碼器和驗證工具必須忽略保留的位元,除非未來的 RFC 指定這些位元的解讀方式。
  • 解碼器「應」在解碼期間保留內嵌信封。
    • 由於內嵌資料包含內嵌資料,且不需要超行參照,因此解碼器在直接解碼時不需要將其取代為指標 (這點與行外信封不同)。

編碼器應該以 Out 行或內嵌方式進行編碼?

編碼器「必須」符合下列規定:

  • 如果類型為 bool、(u)int8、(u)int16、(u)int32float32 或控制代碼,就能夠以內嵌方式編碼資料。(如果型別是固定大小且 <= 32 位元)
  • 對所有其他類型的資料進行即時編碼。(也就是說,如果類型 >= 64 位元或可變大小)。

帳號代碼

帳號代碼宣告有三個結構定義:

  1. 在不可擴充的容器中,使用非選用控制代碼,例如 struct S { handle h; };
  2. 非擴充容器中的選用帳號代碼,例如:struct S { handle? h; };
  3. 可擴充容器中的帳號代碼,例如 table T { handle h; }

如果是 (1),非可延伸的容器中非選用控制代碼,我們提議保留現有的傳輸格式,也就是 uint32。在無法擴充的容器中,您不需要使用非選用控制代碼,因為信封的用途是傳輸選用或動態大小的資料。

對於 (3),可延伸容器中的控制代碼:由於信封是可擴充容器的基礎,因此必須用信封對控點進行編碼。如要對控制代碼進行編碼,編碼器「必須」將其編碼為行外信封,並將 size 設為 0,並將 handle_count 設為 1:

圖:Little-endian 64 位元資料欄位,底部 48 位元的大小設為 0,而後續 16 位元則指出 handle_count 設為 1

這個編碼會指示解碼器在外行控點資料表中查詢控制值。如果解碼器想直接解碼,則解碼器必須:

  • 在外行控點資料表中查詢控點,判斷實際的處理值。
  • 將標記位元設為 1,將信封從外框變更為內嵌。
  • 將 fidl_inline_envelope_t struct 的控制代碼欄位設為實際的處理常式值。

圖:Little-endian 64 位元資料欄位,最小有效位元標記設為 1,保留後續 31 位元,接下來 32 位元的 handle_value

如需經過編碼/解碼的控制代碼示例,請參閱「示例」一節。

我們選擇這個雙重編碼/解碼格式,因為這種格式同時與外部郵件編碼和內嵌信封編碼相容。雖然這樣會導致信封中的處理量專屬程式碼,但我們認為,相較於需要較多編碼的較簡單程式碼,資料編碼方式越一致。

對於 (2),這是非擴充容器中的選用控制代碼:我們也建議採用與傳輸格式相同 (3) 的信封表示法,也就是雙行外編碼/內嵌解碼格式。不過,這個選用帳號代碼的表示法不如現有的選用帳號代碼格式,也就是 uint32。然而,我們仍主張使用信封式表示法,因為

  • 使用信封用於選擇性控制代碼的信封,與任何選用類型的信封相同
  • 在 FIDL 訊息和其他訊息類型中,選用控制代碼相對稀少2,因此額外的 4 位元組負擔應該不會大幅影響訊息大小。
  • 如果保留選用控點現有的 uint32 傳輸格式,將會產生三種編碼,並且有三個不同的控制代碼路徑:非選用、選用和處理中的信封。將信封表示法用於選擇性,可以移除一種編碼和一個程式碼路徑,從而增加統一並減少特殊程式碼。

下方「設計決策」一節中會明確列出 (2) — 選用控點的編碼方式,因為選用處理常式的精簡 uint32 表示法可能值得考量。

字串和向量

非空值 StringVectors 的目前傳輸格式會以 16 個位元組儲存:

  • uint64 用於元素 (向量) 或位元組數 (字串) 數量。
  • 適用於是否存在/不存在/指標的 uint64

我們建議使用信封來代表字串和向量,無論該字串是否可為空值:

  • 移動元素 (向量) 或位元組 (字串) 的數量
    • 如此一來,向量/字串就能以信封 (僅限) 表示,讓信封成為所有 FIDL 類型參照「任何」外線資料的唯一方法,讓所有外線資料能以一致的方式呈現。
    • 向量/字串內容位於單獨的獨立物件物件中,緊接在元素/位元組數量之後。
  • 存在/不存在取決於信封為零或非零。

請注意,向量元素數與信封大小不同:

  • 信封大小是向量元素數量乘以元素大小。
  • 如果向量包含子物件 (例如 vector<Table>vector<vector<string>>),則信封大小會包含所有遞迴子物件的大小。

可擴充容器中的可為空值字串/向量和字串/向量,與不可為空值的字串和向量表示相同:零信封是用來表示缺少字串/向量。

反之,如果字串/向量不可為空值,則驗證工具在遇到零信封時就必須發生錯誤。

對於使用 C 繫結的程式碼,這可能是原始碼破壞性變更,預期 fidl_vector_tfidl_string_t 的記憶體版面配置必須與線路格式完全一致。不過,我們可以在線路格式變更 (例如變更 C API 以使用函式或巨集) 之前導入轉換計畫,讓這個過程變得柔性轉換。

請注意,您仍可透過彈性陣列成員 (例如 struct { uint64 element_count; element_type data[]; };),將這個新的字串/向量版面配置以 C 結構表示。

選用 (空值) 類型

目前,結構體、字串、向量、控點、聯集、資料表和可擴充聯集可以選用 (可為空值)。

將訊息用於任何地方,可讓使用者視需要選擇「所有」類型

  • 呈現的選用資料會儲存在行外或內嵌的信封中。
  • 系統會將缺少的選用資料儲存為零信封。

請注意,對於小型類型,內嵌資料可以根據容器的對齊要求,將選用類型儲存為非選用類型。

編碼/解碼表單的 C/C++ 結構體

信封的編碼形式可以是內嵌或外框的聯集。同樣地,已解碼的信封可能為內嵌、指向信封資料的指標,或回呼決定的值 (詳情請參閱解碼器回呼一節)。

typedef union {
  fidl_inline_envelope_t inline;            // Low bit is 1
  fidl_out_of_line_envelope_t out_of_line;  // Low bit is 0
} fidl_encoded_envelope_t;

typedef union {
  fidl_inline_envelope_t inline;  // Low bit is 1
  void* data;                     // Low bit is 0
  uintptr_t callback_data;  // Value determined by callback (see Decoder Callback)
} fidl_decoded_envelope_t;

static_assert(sizeof(fidl_encoded_envelope_t) == sizeof(void*));
static_assert(sizeof(fidl_decoded_envelope_t) == sizeof(void*));

不明資料

在可改進的資料結構 (例如資料表或擴充聯集) 中使用信封時,驗證器和解碼器可能無法知道信封的類型。如果接收者不知道信封類型,請執行下列操作:

  • 您可以放心忽略內嵌信封。
    • 處理「必須」使用外行信封進行編碼,而非內嵌信封,以防止所有內嵌信封安全。
  • 線外的信封可能只有稍微剖析及略過。
    • 信封大小會決定您要略過的外線資料量。
    • 如果信封的控點數量非零,驗證工具「必須」處理指定數量的控點。
      • 預設的處理行為「必須」關閉所有帳號代碼。
    • 如果解碼器想要就地解碼,則解碼器 MAY 會使用指標指向信封的內容,覆寫未知信封。
      • 如果解碼器使用指標覆寫信封,就會失去信封的大小和處理計數資訊。如果這麼做,請參閱「解碼器回呼」一節瞭解替代方案。

請注意,將大小嵌入外部信封中,即可在需要略過許多未知類型時,透過 FIDL 訊息快速進行線性搜尋。

解碼器回呼

如「不明資料」一節所述,解碼器可能會覆寫未知的信封:如果發生此情況,解碼器就會失去大小和處理計數資訊。或者,解碼器 MAY 會附加回呼,處理信封並覆寫預設行為。回呼 API 可能會與下列函式原型類似:

void set_unknown_envelope_callback(
    unknown_envelope_callback_t callback,  // a callback
    void* context                          // client-specific data storage
);

typedef uintptr_t (*unknown_envelope_callback_t)(
    const void* message,  // pointer to the envelope's containing message
    size_t offset,        // offset in the message where the unknown envelope is
    size_t size,          // the envelope's size
    size_t handle_count,  // the envelope's handle count
    const char* bytes,    // pointer to the envelope's data
    void* context         // a context pointer set via set_unknown_envelope_callback()
);

回呼會傳回 uintptr_t,解碼器可用於覆寫未知的信封。這可讓解碼器從不明信封複製大小和處理次數,並以指標指向解碼器自己的自訂資料結構來覆寫信封。

行外信封的編碼大小

這項 RFC 要求行外信封必須具有正確的 (遞迴) 大小,才能使用現有的外線資料。這項規定可能會對編碼器造成額外負擔,因為如果接收方預期能知道信封類型,就不需要設定大小欄位,因為解碼器可以計算大小 3。因此,編碼器實際上會執行額外作業,無法提供任何明顯的好處。這個引數也適用於帳號代碼計數。

不過,我們仍建議「必須」顯示大小和帳號代碼數量,原因如下:

  1. 一致性:要求大小代表信封編碼在所有用途上都保持一致,不論是否在可擴充的容器內。統一性越高,程式碼也越少,辨識模型也變得更簡單。
  2. 我們日後可能會變更這項設定。 未來的 RFC 可以選擇使用 iinel 值 (例如 UINT48_MAX),或在大小欄位中保留其中一個 LSB 的其中一個值,表示大小不明,在這種情況下,解碼器就必須掃過行外酬載並計算大小本身。這項變更不會影響傳輸格式,因為欄位結構保持不變。解碼器可以在更新編碼器之前先實作邏輯,因此也可以設為柔性轉換。

整體而言,RFC 作者認為,要求對未知尺寸進行編碼是可能的早期最佳化,而應從簡單、更一致且統一的設計開始。如果您認為這項決策應於日後重新審視 (例如出現零複製的向量 I/O 編碼器,讓編碼器不必修補信封來撰寫正確的大小),就有明確路徑在軟轉換中實作。

範例

以內嵌方式儲存的選擇性 uint

uint32? u = 0xdeadbeef;  // an optional uint: stored inline.

C++ 表示法:

    vector<uint8_t> object{
        0x01, 0x00, 0x00, 0x00,                          // inline tag
                                0xEF, 0xBE, 0xAD, 0xDE,  // inline data
    };

儲存斷行的選擇性 vector<uint16>

vector<uint16>? v = { 10, 11, 12, 13, 14 };  // an optional vector<uint16>; stored out-of-line.

非行的廣告大小為 24:

  • 元素數量為 8 個位元組,是將該元素置於行中,當做其次要物件。
  • + 10 代表向量內容 (5 個元素 * sizeof(uint16_t))。
  • = 18,經四捨五入為 24 表示對齊。

C++ 表示法:

    vector<uint8_t> object{
      0x18, 0x00, 0x00, 0x00, 0x00, 0x00,              // envelope size (24)
                                          0x00, 0x00,  // handle count
    };

    vector<uint8_t> sub_objects{
      // element count
      0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      // vector data
      0x0A, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0D, 0x00,
      0x0E, 0x00,
      // padding
                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    };

含有三個欄位的 table

table T { 1: int8 i; 2: reserved; 3: int64 j; } = { .i: 241, .j: 71279031231 };

C++ 表示法:

    // a table is a vector<envelope>, which is represented with an
    // out-of-line envelope
    vector<uint8_t> object{
      0x28, 0x00, 0x00, 0x00, 0x00, 0x00,              // envelope size (40)
                                          0x00, 0x00,  // handle count
    };

    vector<uint8_t> sub_objects{
      // vector element count (max table ordinal)
      0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      // vector[0], 1: int8, stored inline
      0x01, 0x00, 0x00, 0x00,                          // inline tag
                              0xF1, 0x00, 0x00, 0x00   // 241
      // vector[1], 2: reserved
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // zero envelope
      // vector[2], 3: int64, stored out-of-line
      0x08, 0x00, 0x00, 0x00, 0x00, 0x00,              // envelope size
                                          0x00, 0x00,  // handle count
      // vector[2] content
      0xBF, 0xB3, 0x8F, 0x98, 0x10, 0x00, 0x00, 0x00   // 71279031231
    };

帳號代碼:

handle h;  // decoded to 0xCAFEF00D

C++ 表示法:

    vector<uint8_t> encoded_form{
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,              // envelope size
                                          0x01, 0x00,  // handle count
    };

    vector<uint8_t> decoded_form{
      0x01, 0x00, 0x00, 0x00,                          // inline tag
                              0x0D, 0xF0, 0xFE, 0xCA,  // inline data
    };

導入策略

這項 RFC 是破壞性的傳輸格式變更。兩個 FIDL 對等互連都必須瞭解新的傳輸格式,並向同業提供瞭解,以便雙方都能使用新格式。

可以進行柔性轉換。方法如下:

  1. 交易訊息標頭中有 uint32 保留/旗標欄位。我們可以保留 1 位元給啟動的對等點,以表明其瞭解新的傳輸格式,分階段進行柔性轉換:
    1. 確認所有用戶端和伺服器都能辨識新舊網路線格式。 我們會繼續使用舊線路格式。
    2. 請在交易訊息標頭中讓同業設定位元,藉此啟用新的線路格式。如果雙方都有設定位元,雙方都可以切換至新的線路格式。
    3. 當柔性轉換通過所有圖層後,所有 Fuchsia 都可以使用新的傳輸格式。我們可以移除交易訊息標頭中的位元設定。
    4. 刪除舊傳輸格式的程式碼,然後取消保留交易訊息標頭位元。
  2. 我們可以使用 [WireFormat=EnvelopeV2] 屬性 (或類似) 修飾特定 FIDL 訊息類型、介面或兩者,以表示訊息/介面應使用新的傳輸格式。
    1. 雖然使用 [WireFormat] 屬性修飾介面似乎與傳輸格式變更更加一致,但因為結構可在不同的介面中使用,所以繫結需要額外的邏輯來決定使用結構的結構定義。
    2. 我們建議介面 [WireFormat] 屬性只影響介面方法引數的傳輸格式,而不會以遞迴方式影響引數的結構。
    3. 如此即可啟用部分遷移及選擇採用新的傳輸格式,讓團隊按照自己的步調遷移。
    4. 一旦所有結構體和介面都擁有 [WireFormat] 屬性,我們可以捨棄舊的傳輸格式,並假設所有結構體和介面都使用新的傳輸格式,並忽略這個屬性。

這兩種軟轉換方法都需花費大量開發時間、測試時間和空間發生錯誤。如要正確執行任一方法,並對計畫執行及成功移除舊程式碼,是相當費力的事。

我們可能會有可同時處理新舊傳輸格式的程式碼;否則,當我們對新傳輸格式的支援時,可能無法逐步放置 CL。鑒於能處理這兩種傳輸格式的程式碼,建議您透過上述任一方法,設計能否進行軟轉換的原型設計。如果不是,則 c'est la vie;硬性轉換則為強制轉換。

無論是何種轉換或強制轉換,Fchsia 中手動捲動 FIDL 訊息的任何執行個體都必須升級為新的線路格式。

我們也應使用這個線路格式變更,在需要發生的其他變更 (例如建議的序數大小變更) 時折疊。

請注意,這比 FIDL1 轉換至 FIDL2 容易,因為 FIDL2 會大幅變更語言繫結。由於沒有任何使用者可察覺的變更4,因此我們不建議呼叫這個 FIDL3。

回溯相容性

提議的傳輸格式變更與 API (來源) 相容,但有一個例外狀況:如果我們將向量/字串元素數量移到外線,C 繫結就會造成破壞性的 API 變更。在新電線格式到達前,我們可以預先規劃,並使用巨集或函式擷取目前的 C 繫結來減緩此問題。

傳輸格式變更與 ABI 不相容,但可以透過「實作策略」一節所述的策略達到 ABI 與現有程式碼的相容性。

效能

這個 RFC 會大幅縮減信封所需的大小,看起來就像是整體的淨利益。不過,整體效能影響不明顯。有利於提升成效:

  • 使用可擴充資料結構 (資料表和擴充聯集) 的 FIDL 訊息將更加精簡。
  • 針對信封和選用性使用統一的表示法,可能會縮減程式碼大小並提升快取位置,因為信封代碼可以共用。

不受影響:

  • 如果可擴充的資料結構因效率較佳而變得越來越普遍,這可能會被使用增加的情形所得的值。相較於使用無法擴充的資料結構,這可能會導致訊息縮小且更頻繁的動態分配。
  • 引入所有類型的選用功能可能會使 FIDL 訊息稍微放大,因為使用者可能會利用此功能將某些先前非選用類型設為選用。
  • 如果決定將信封編碼用於選用的控點,選用控點的效率會降低。
  • 針對外線郵件編碼大小所述,針對接收者知道類型,將訊息大小和處理計數進行編碼,以便接收方知道類型是目前行為的效能迴歸。

人體工學

  • 您可以為所有 FIDL 類型啟用選用功能。 這是符合人體工學的改善,因為選用性會趨於一致,而不只是特定類型。
  • 有效率的可擴充資料結構有助於在效率關鍵的情境下使用資料結構,因此使用者無須擔心效能,並可在之前需要使用無法擴充的結構時,享有擴充能力帶來的好處。
    • 我們甚至希望 FIDL 資料結構預設應使用資料表,並應將結構保留給高效能的結構定義。
    • 可擴充聯集 (RFC-0061) 已嘗試移除靜態聯集。

說明文件

  • 傳輸格式說明文件需要更新。
  • 更新說明文件時,訊息應做為一流概念說明,當讀者遇到可選性和可擴充的資料結構的傳輸格式時,這樣做就能獲得更好的認知區塊化效果。
  • 我們應更新 FIDL 樣式指南,以推薦使用選用類型的時機 (相較於含有 sentinel 值的非選用類型)。

安全性

  • 這項 RFC 不會對安全性造成重大影響。
  • 不過,處理外線和內嵌信封格式所需的位元微調格式應經過嚴謹的測試與保守,以確保程式碼能正確處理邊緣案例。我們認為使用標準 C/C++ 結構/聯集代表信封 (與手動位元轉移和遮蓋不同),可大幅提升程式碼正確性。

測試

  • 由於這個 RFC 將變更信封的傳輸格式,因此我們認為現有的 FIDL 測試套件 (特別是相容性測試) 能對所有使用信封的情況充分測試。
  • 由於這是個容易出錯的區域,我們將新增信封剖析、編碼和解碼作業的單元測試,檢查外部和內嵌表單。
  • 如果同意將線路格式變更視為軟式轉場 (請參閱「實作策略」一節),我們就會新增測試給對等點進行協商,並有可能切換至新的線路格式。
  • 如果同意在這項變更中公開所有類型的選用設定,我們將需要為可選用的所有類型新增測試。

缺點、替代方案和未知

  • 如果我們認為本提案的效率提升效益並不值得,可以保留現有的傳輸格式。在這種情況下,我們會想尋找替代策略,對所有類型實作選用性。
  • 針對可擴充容器和選用類型使用專屬表示法,可能會比在所有情況下使用信封更有效率。不過,由於這個 RFC 存在,我們顯然認為信封的基因性和統一性越大,對特殊表示法的效率也就高出了。

設計決策

這份 RFC 也會提供建議,但我們仍會針對以下決策,主動尋求相關意見及共識:

  • 如要瞭解如何移動元素數量 (向量) 和位元組數量 (字串),這會影響 C 繫結,請參閱「字串和向量」一節。我們可以選擇不這麼做,但一致性較低:字串和向量會變成例外,導致所有外線參照都使用信封。(信封仍可用於參照外線向量/字串資料)。
  • 我們是否考慮過慢轉換或強制轉換?請參閱「實作策略」一節,瞭解優缺點。
  • 我們建議使用 48 位元 (48 位元) 的大小,使用 16 位元當做線上信封的控點。而目前的信封格式會使用 32/32 位元。48 位元的大小合理嗎?
    • 至於大小,我們可以將編碼格式向右移 2 位元,因為信封尺寸一律為 8 的倍數,最多可以編碼 50 位元。(我們無法將三個位元向右移動,因為無法保證標記位元為 0)。解碼器會將兩個位元往左移,以決定大小。然後我們失去兩個可能用於標記或更多標記的額外部分。
    • 雖然目前的 64 位元架構通常不允許處理整個 64 位元記憶體空間,且通常最多允許 48 位元,但部分架構已允許最大 57 位元的位址空間。詳情請參閱參考資料
    • 16 位元的帳號代碼是否合理?
  • 我們建議在無法擴充的容器中使用信封對選用控制代碼進行編碼,這個容器比目前選用的控點編碼還要精簡 (8 個位元組與 4 個位元組)。
    • 需要在密集、更特殊的程式碼和一致性之間取得取捨。我們認為,一致性和統一性比特殊、更精簡的表示法更加重要,因為選用的控點很可能是相對罕見的用途。(程式碼中使用的 37 個選擇性用途,以及 187 個非選用項目)。
  • 是否可以立即啟用選填功能?
    • 由於系統可以逐步執行變更,因此建議您在個別轉換格式中揭露所有類型的選用設定。
    • 如要實作這類選用功能,就必須變更剖析器、編碼器、驗證工具和解碼器,而這會顯然夠大,足以證明其為系統進行轉換。
  • 建議使用 <= 32 位元的內嵌類型,我們可以更積極地內嵌。
    • 我們可以內嵌任何小於 63 位元的資料,因為標記位元在 64 位元信封中只使用一個位元。
    • 我們可以使用專門的表示法來內嵌小型字串和向量,例如:元素/位元組計數有一個位元組,後面接著字串/向量資料。請參閱先前圖片中的靈感。
    • 我們捨棄這些方法雖然效率較高,因為根據內容進行內嵌,意味著 (1) 解碼器無法事先得知是否預期內嵌或外行信封是否根據類型而改變;(2) 變更欄位內容代表其編碼方式與 FIDL 的目標不同,且與 FIDL 的目標不同。

先前的圖片和參考資料

目前使用標記指標的用法很久,作者在動態和功能語言方面擁有許多經驗。特別是 Objective-C 64 位元執行階段會大量使用這類 API 以提升效能 (甚至至今已為內嵌字串使用專用的 5/6 位元編碼)。

目前的 64 位元平台通常使用 48 位元 (或更短) 對指標進行編碼,因此我們考慮透過位元偏移從已解碼的指標中竊取更多位元,嘗試在指標中一併為外行物件的大小進行編碼。不過,部分架構已將實體位址空間擴展到 48 位元 (ARM64x64-64 5 層級分頁) 之後,因此竊取更多指標位元可能無法未來非常穩定。


  1. 信封對所有類型都可選用 enable 選用;不過,向使用者顯示這項選用功能可以 (如有需要) 分別完成。

  2. 截至 2019 年 1 月 28 日,Fuchsia 程式碼集已有 37 個選用控制代碼。這是保守的數字,因為不計算選用的通訊協定帳號或通訊協定要求控制。 

  3. 這僅適用於無法擴充的容器 (即結構體和靜態聯集) 中的信封。可擴充的容器必須對遞迴大小進行編碼,因為解碼器不知道類型,並需要知道要忽略多少資料。 

  4. 除了允許更多類型的選用設定外,如果我們希望同時這麼做。