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 可從整體且一致的選用方法中受益。這可帶來更出色的人因設計、比現有機制允許的更多選項類型,以及簡化的使用者心智模型。透過以一致的方式為所有類型啟用選用性,信封就能達成這些目標。

設計

信封可用於參照下列資料:

  • 離線,類似現有的信封格式,或
  • inline,資料會儲存在信封本身。這可用於固定大小且小於 64 位元的「小型」類型。

離線信封

離線信封:

圖:離線信封、64 位元 little endian、較低的 48 位元大小,其中最低有效位元為零,16 位元 handle_count

做為 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) 一律為零。
      • 這點很重要,請參閱下方的「標記位元」一節。
    • 如要瞭解計算遞迴大小的效能影響,請參閱下方的「為離線封套函式編碼大小」。
  • handle_count 為 16 位元,而非 32 位元。
    • 目前無法透過 Zircon 管道傳送超過 64 個句柄,我們認為 16 位元可提供足夠的空間來滿足未來的需求。
    • handle_count 包含所有遞迴子物件的句柄計數。
  • 「有/無」欄位已刪除。
    • sizehandle_count 欄位中,非零值代表存在。
    • 如未提供,sizehandle_count 欄位都會設為零。
      • 我們稱之為零信封

假設解碼器知道信封內容的靜態類型 (結構定義),則可能會使用指向信封資料的指標覆寫信封。如要瞭解如何處理內容類型不明的封套,請參閱「解碼器回呼」一節中的建議。

標記位元

離線信封明確地將大小占用位元最小位元,而句柄計數占用位元最大位元。如「封套」一節所述,

  • 因為大小欄位的最低位元一律為零 (由於大小是 8 的倍數),
  • 信封的最低位元「也」會一律為零。

我們稱封包的最低位元為標記位元

  • 如果標記位元為零,則信封的資料為離線
  • 如果標記位元為 1,則封套的資料會inline

由於標記位元是內嵌資料的 1,因此在需要 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 位元的最低位元來表示該值,這是標準的 little-endian 表示法。
  • 編碼器必須將保留位元編碼為零,除非日後的 RFC 指定如何解讀這些位元。
  • 除非日後的 RFC 指定這些位元應如何解讀,否則解碼器和驗證器一律會忽略保留位元。
  • 解碼器在解碼期間應保留內嵌式信封。
    • 由於內嵌資料會內嵌資料,而不需要以離線方式參照,因此解碼器在原地解碼時,不需要以指標取代這些資料 (不像離線信封)。

編碼器應以離線還是內嵌方式編碼?

編碼器必須符合下列條件:

  • 如果類型為 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:

圖:小端 64 位元資料欄位,其中底部 48 位元大小設為零,而下一個 16 位元表示 handle_count 設為 1

這個編碼會指示解碼器在離線句柄表中查詢句柄值。如果解碼器想要就地解碼,則應:

  • 在離線句柄表中查詢句柄,以判斷實際句柄值。
  • 將標記位元設為 1,即可將封套從離線變更為內嵌。
  • 將 fidl_inline_envelope_t 結構體的句柄欄位設為實際句柄值。

圖:小端 64 位元資料欄位,其中最低有效位元標記設為 1,下一個 31 位元保留,下一個 32 位元為 handle_value

如需編碼/解碼句柄的範例,請參閱「範例」一節。

我們選擇這個雙重編碼/解碼表單,是因為它與離線和內嵌信封編碼相容。雖然這會導致信封中出現專屬的句柄程式碼,但我們認為,比起簡化程式碼而需要更多編碼,更一致 (即更少) 的資料編碼會是更好的折衷。

針對 (2) 在不可擴充的容器中使用選用句柄:我們也建議使用與線路格式相同的信封表示法做為背景 (3),也就是雙重離線編碼/內嵌式解碼表單。很抱歉,選用句柄的這項表示法比現有的選用句柄線路格式 (uint32) 更不精簡。不過,我們仍建議使用以信封為基礎的表示法,因為

  • 使用選用句柄的封套與使用任何選用型別的封套相同,
  • 在 FIDL 訊息中,選用句柄相較於其他訊息類型2較為罕見,因此額外的 4 個位元組信封額外負擔不應對訊息大小造成重大影響。
  • 如果保留選用句柄的現有 uint32 線路格式,將產生三種編碼和三個個別的句柄程式碼路徑:非選用、選用和封套內的句柄。使用選用項目的封套表示法可消除一個編碼和一個程式碼路徑,進而提高一致性並減少專屬程式碼。

針對 (2) 的編碼 (非可擴充容器中的選用句柄) 已明確列於下方的「設計決策」一節,因為選用句柄的 uint32 表示法較為精簡,值得考慮。

字串和向量

目前不支援空值的 字串向量的電線格式會以 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*));

不明資料

接收器 (驗證器和解碼器) 在可進化資料結構 (例如表格或可擴充的聯集) 中使用時,可能不知道封套的類型。如果收件者不清楚信封的類型,請按照下列步驟操作:

  • 您可以放心忽略內嵌信封。
    • 手柄必須使用離線封套編碼,而非內嵌封套,這樣才能安全地忽略所有內嵌封套。
  • 可將離線信封解析和略過。
    • 信封的大小決定要略過的離線資料量。
    • 如果信封的句柄數量不為零,驗證工具就必須處理指定數量的句柄。
      • 預設處理行為必須是關閉所有句柄。
    • 如果解碼器想要就地解碼,則可能會使用指向信封內容的指標覆寫不明信封。
      • 如果解碼器確實使用指標覆寫封套,就會遺失封套中的大小和句柄計數資訊。如果這會造成問題,請參閱「解碼器回呼」一節,瞭解替代做法。

請注意,如果需要略過許多不明類型,則可在離線信封中嵌入大小,以便透過 FIDL 訊息快速進行線性搜尋。

解碼器回呼

如「不明資料」一節所述,解碼器可能會覆寫不明信封:如果發生這種情況,解碼器就會遺失大小和句柄計數資訊。或者,解碼器也可能會附加回呼,以便處理封套並覆寫預設行為。回呼 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 可選擇使用用於大小的哨兵值 (例如 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 個位元組,儲存在線外做為專屬的次要物件
  • 向量內容 (5 個元素 * sizeof(uint16_t)) 的 +10,
  • = 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] 屬性修飾介面似乎能與線路格式變更保持一致,但在結構體上實作 WireFormat 變更應該會更容易,因為結構體可用於不同介面,而繫結需要額外的邏輯來判斷使用結構體的內容。
    2. 建議介面 [WireFormat] 屬性只影響介面方法引數的線路格式,而不會遞迴影響引數的結構體。
    3. 這可讓您部分遷移並選擇採用新的線路格式,讓團隊可自行決定遷移速度。
    4. 一旦所有結構體和介面都具有 [WireFormat] 屬性,我們就可以放棄舊的線路格式、假設所有結構體和介面都使用新的線路格式,並忽略該屬性。

這兩種軟性轉換方法都需要大量的開發時間、測試時間和出錯空間。實作程式碼以正確執行任一方法、執行計畫,並順利移除舊程式碼,這需要付出大量心力。

我們很可能會同時提供程式碼來處理舊版和新版的線路格式;否則,在實作支援新線路格式的同時,就無法逐步發布 CL。由於兩種線路格式都會有處理程式碼,建議您製作原型,以便判斷是否可使用這兩種方法進行軟性轉換。如果沒有,那麼c'est la vie,只能進行硬轉移。

無論是軟性或硬性轉換,Fuchsia 中任何手動捲動的 FIDL 訊息例項都必須升級至新的線格格式。

我們也應使用這項線路格式變更,將其他需要發生的變更納入其中 (例如建議的序數大小變更)。

請注意,這比 FIDL1 到 FIDL2 的轉換更容易,因為後者大幅變更了語言繫結。我們不建議呼叫這個 FIDL3,因為沒有任何使用者可見的變更4

回溯相容性

所提議的線路格式變更與 API (來源) 相容,但有一個例外狀況:如果我們將向量/字串元素數量移至離線,C 繫結就會變成破壞性 API 變更。我們可以提前規劃,並在新的線路格式推出前,使用巨集或函式抽象化目前的 C 繫結。

線路格式變更與 ABI 不相容,但可透過「實作策略」一節所述的策略,讓現有程式碼與 ABI 相容。

成效

這項 RFC 可大幅縮減信封所需的大小,這似乎是整體顯著的淨效益。不過,整體成效的影響則不太明確。為了提升效能:

  • 使用可擴充資料結構 (表格和可擴充的聯集) 的 FIDL 訊息,將變得更加精簡。
  • 為信封和選用項目提供一致的表示法,可能會縮減程式碼大小並改善快取位置,因為信封程式碼可供共用。

不過,請注意以下幾點:

  • 如果可擴充資料結構因效率更高而變得更普遍,這可能會因使用量增加而失去優勢,進而導致訊息不夠精簡,且動態配置的次數增加,而非使用不可擴充資料結構。
  • 為所有類型引入選用性可能會使 FIDL 訊息稍微變大,因為使用者可能會利用這項功能,將先前非選用的類型設為選用。
  • 如果我們決定為選用句柄使用封包編碼,選用句柄的效率就會降低。
  • 如同「為離線信封編碼大小」一文所述,如果接收端知道某類型在信封中編碼的大小和句柄計數,就會認為這是效能倒退的現象。

人體工學

  • 可為所有 FIDL 類型啟用選用性。這項改進可讓選用性保持一致,而非只適用於特定類型。
  • 更有效率的可擴充資料結構可用於效率至關重要的更多情境,因此使用者不必太擔心效能問題,並可在先前需要使用不可擴充結構的情境中,享有可擴充性帶來的好處。
    • 我們甚至建議您預設使用表格來處理 FIDL 資料結構,並將結構體保留給高效能情境。
    • 可擴充的聯集 (RFC-0061) 已嘗試移除靜態聯集。

說明文件

  • 需要更新線路格式說明文件。
  • 更新說明文件時,應將信封解釋為第一類概念:這樣一來,讀者遇到可選性和可延伸資料結構的電報格式時,就能進行更好的認知區塊處理
  • 我們應更新 FIDL 樣式指南,針對應使用選用類型 (與含有哨兵值的非選用類型相比) 的情況提出建議。

安全性

  • 這項 RFC 不應對安全性造成重大影響。
  • 不過,為了確保程式碼能妥善處理邊緣情況,您必須針對用於操作離線和內嵌封套格式的位元操作進行充分測試,並採取保守做法。我們確實認為,使用標準 C/C++ 結構體/聯集來表示信封 (而非手動位元位移和遮罩),可大幅提升我們對程式碼正確性的信心。

測試

  • 由於本 RFC 會變更信封的線路格式,我們認為現有的 FIDL 測試套件 (尤其是相容性測試) 將可充分測試所有使用信封的情況。
  • 我們會針對信封解析、離線和內嵌表單的編碼和解碼作業新增單元測試,因為這項作業可能會發生錯誤。
  • 如果我們同意以軟性轉換方式導入線路格式變更 (請參閱「導入策略」一節),我們會新增測試,讓同儕進行協商,並可能改用新的線路格式。
  • 如果我們同意在本次異動中讓所有類型都公開選用功能,就必須為所有可選用的類型新增測試。

缺點、替代方案和未知事項

  • 如果我們認為這項提案的效率提升效果不值得實施,可以保留現有的電報格式。如果是這樣,我們會想找出其他策略,為所有類型實作選用性。
  • 針對可擴充的容器和選用類型使用專屬的表示法,可能比在所有情況下使用信封更有效率。不過,由於這個 RFC 已存在,我們認為信封提供的通用性和一致性,遠勝於專屬表示法的效率提升。

設計決策

雖然本 RFC 提出了建議,但我們仍積極尋求有關下列決策的意見和共識:

  • 請參閱「字串和向量」一節,瞭解如何將元素計數 (向量) 和位元組計數 (字串) 移至離線位置,以便影響 C 繫結。我們可以選擇不這樣做,但代價是一致性降低:字串和向量會成為信封的例外狀況,信封會用於所有離線參照。(仍可使用信封參照離線向量/字串資料)。
  • 我們要考慮採用軟性轉換還是硬性轉換?如要瞭解優點和缺點,請參閱「導入策略」一節。
  • 我們建議在離線信封中使用 48 位元大小和 16 位元句柄。為方便比較,目前的封套格式使用 32/32 位元。大小為 48 位元是否合理?
    • 就大小而言,我們可以在編碼表單中向右移動 2 位元,編碼大小最多可達 50 位元,因為封套大小一律是 8 的倍數。(我們無法右移三個位元,因為這樣無法保證標記位元為 0)。解碼器會將兩個位元左移,以便判斷大小。接著,我們會失去兩個可能用於旗標或更多標記的額外位元。
    • 雖然目前的 64 位元架構通常不允許使用整個 64 位元記憶體空間,且通常最多只允許 48 位元,但部分架構已啟用最多 57 位元的位址空間。詳情請參閱「參考資料」。
    • 是否適合使用 16 位元句柄?
  • 我們建議使用封套,在不可擴充的容器中編碼選用的句柄,這比目前的選用句柄編碼 (8 個位元組,而非 4 個位元組) 更不緊湊。
    • 在這個情況下,您必須在精簡與專用程式碼與一致性之間取捨。我們認為,一致性和統一性比專門且更精簡的表示法更重要,因為選用句柄可能較少使用。(程式碼中 37 個選用用途,與 187 個非選用用途)。
  • 我們是否立即啟用選用性?
    • 我們建議在升級線路格式的過程中,透過個別的轉換作業,為所有類型提供選用性,因為變更可以逐步完成。
    • 實作這類選用功能需要變更剖析器、編碼器、驗證器和解碼器,這項變更規模足以獨立為一項轉換。
  • 我們建議內嵌類型為 32 位元以下,這樣就能更積極地內嵌。
    • 我們可以內嵌任何資料 (小於等於 63 位元),因為標記位元在 64 位元信封中只使用一個位元。
    • 我們可以使用專屬的表示法,將小字串和向量內嵌,例如使用一個位元組來計算元素/位元組數量,然後再加上字串/向量資料。(請參閱「先前的作品」尋找靈感)。
    • 雖然這些方法效率更高,但我們還是捨棄了這些方法,因為以內容而非類型為依據的內嵌方式,意味著 (1) 解碼器無法預先知道是否要根據類型預期內嵌或離線信封,以及 (2) 變更欄位內容意味著可以以不同方式編碼,這似乎違背了 FIDL 的目標和靜態重點。

既有技術與參考資料

作者從現有的標記指標用法中獲得許多靈感,這些指標在動態和功能性語言中已有悠久歷史。特別是 Objective-C 64 位元執行階段會大量使用這些功能來提升效能 (甚至會使用專用的5/6 位元編碼來處理內嵌字串)。

由於目前的 64 位元平台傾向使用 48 位元 (或更少) 來編碼指標,因此我們考慮從已解碼的指標中偷取更多位元,並透過位元位移嘗試在指標中編碼離線物件的大小。不過,部分架構已將實體位址空間擴大至超過 48 位元 (ARM64x64-64 5 層分頁),因此竊取更多指標位元可能無法滿足未來需求。


  1. 信封啟用所有類型的選用功能;不過,向使用者提供這項選用功能時,可以 (也許應該) 分開進行。 

  2. 截至 1/28/19,Fuchsia 程式碼庫中似乎有 37 個使用選用句柄的用途。這項數字偏保守,因為它不計算選用通訊協定句柄或通訊協定要求句柄。 

  3. 這項功能僅適用於不可擴充容器中的信封,也就是結構體和靜態聯集。可擴充的容器必須編碼遞迴大小,因為解碼器可能不知道類型,且需要知道要忽略多少資料。 

  4. 除了允許在更多類型上使用選用值,如果我們希望同時執行這項操作。