RFC-0026:到處都是信封 | |
---|---|
狀態 | 已遭拒 |
區域 |
|
說明 | 讓信封的大小縮小一倍以上,藉此提升現有信封格式的效率。請使用信封做為參照所有離線物件的唯一方式。這麼做可提升線路格式的一致性,以及通訊協定設計和實作的一致性。 |
作者 | |
提交日期 (年-月-日) | 2019-01-19 |
審查日期 (年-月-日) | 2019-02-04 |
拒絕理由
考量到這個 RFC 的大量意見回饋和評論,我們決定撤銷 (即自行拒絕) 這項提案。不過,這項提案仍有幾個很棒的想法:我們會將這些想法轉化為範圍較小的個別 RFC,以便進行更明確的討論,並將獨立功能分開成各自的 RFC。
RFC-0032 是從這個 RFC 衍生而來。
摘要
這項 RFC 有兩個目標:
- 讓信封的大小縮小至原來的兩倍以上,提升現有
envelope
格式的效率。 - 請使用信封,做為參照所有離線物件的唯一方式。這麼做可提高線路格式的一致性,以及通訊協定設計和實作的一致性。
(1) 和 (2) 的副作用是,可為所有類型有效地實作選用性 (可為空值),而不僅限於結構體、句柄、向量、字串、資料表和 (可擴充) 聯集1。
提振精神
信封是可擴充、可進化的資料結構 (表格和可擴充的聯集) 的基礎。讓信封更有效率,可讓這些可擴充的結構體用於效能和線路大小較重要的更多情境。
FIDL 也提供多種常見類型,用於動態大小的資料:向量和字串。由於 FIDL 主要物件的大小應為靜態已知,因此這些類型必須為離線類型。如果信封可用於代表所有離線資料,我們就能簡化通訊協定和實作方式,降低實作成本和錯誤空間。
此外,FIDL 可從整體且一致的選用方法中受益。這可帶來更出色的人因設計、比現有機制允許的更多選項類型,以及簡化的使用者心智模型。透過以一致的方式為所有類型啟用選用性,信封就能達成這些目標。
設計
信封可用於參照下列資料:
- 離線,類似現有的信封格式,或
- inline,資料會儲存在信封本身。這可用於固定大小且小於 64 位元的「小型」類型。
離線信封
離線信封:
做為 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
包含所有遞迴子物件的句柄計數。
- 「有/無」欄位已刪除。
- 在
size
或handle_count
欄位中,非零值代表存在。 - 如未提供,
size
和handle_count
欄位都會設為零。- 我們稱之為零信封。
- 在
假設解碼器知道信封內容的靜態類型 (結構定義),則可能會使用指向信封資料的指標覆寫信封。如要瞭解如何處理內容類型不明的封套,請參閱「解碼器回呼」一節中的建議。
標記位元
離線信封明確地將大小占用位元最小位元,而句柄計數占用位元最大位元。如「封套」一節所述,
- 因為大小欄位的最低位元一律為零 (由於大小是 8 的倍數),
- 信封的最低位元「也」會一律為零。
我們稱封包的最低位元為標記位元。
- 如果標記位元為零,則信封的資料為離線。
- 如果標記位元為 1,則封套的資料會inline。
由於標記位元是內嵌資料的 1,因此在需要 64 位元對齊的架構中,內嵌信封也無法成為實際的指標,因為指標會是 8 的倍數,且需要最低三位元為零。這對於解碼器來說非常實用,因為解碼器通常會使用指向信封內容的指標覆寫離線信封 (但不會覆寫內嵌信封)。
內嵌信封
內嵌信封編碼方式如下:
做為 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 位元用於表示內嵌值,該值可以是
int8
、uint8
、int16
、uint16
、int32
、uint32
、float32
、bool
或句柄。- 如果值的位元寬度小於 32 位元,則會使用上位元 32 位元的最低位元來表示該值,這是標準的 little-endian 表示法。
- 編碼器必須將保留位元編碼為零,除非日後的 RFC 指定如何解讀這些位元。
- 除非日後的 RFC 指定這些位元應如何解讀,否則解碼器和驗證器一律會忽略保留位元。
- 解碼器在解碼期間應保留內嵌式信封。
- 由於內嵌資料會內嵌資料,而不需要以離線方式參照,因此解碼器在原地解碼時,不需要以指標取代這些資料 (不像離線信封)。
編碼器應以離線還是內嵌方式編碼?
編碼器必須符合下列條件:
- 如果類型為
bool
、(u
)int8
、(u
)int16
、(u
)int32
、float32
或句柄,則會在內文中對資料進行編碼。(非正式定義:如果型別是固定大小且 <= 32 位元)。 - 為所有其他類型離線編碼資料。(非正式說明:如果型別大於等於 64 位元或變數大小。)
帳號代碼
處理宣告有三種情境:
- 非可擴充容器中的非選用控點,例如
struct S { handle h; };
- 非可擴充容器中的選用句柄,例如
struct S { handle? h; };
- 可擴充容器中的句柄,例如
table T { handle h; }
針對 (1) 這個在不可擴充容器中不可省略的句柄,我們建議保留現有的線路格式,也就是 uint32
。由於信封的設計目的是用於傳送選用或動態大小的資料,因此在不可擴充的容器中,不必將非選用句柄設為信封。
針對 (3) 可擴充容器中的句柄:由於信封是可擴充容器的基礎,因此必須使用信封來編碼句柄。如要編碼句柄,編碼器必須將其編碼為離線封包,並將 size
設為 0,將 handle_count
設為 1:
這個編碼會指示解碼器在離線句柄表中查詢句柄值。如果解碼器想要就地解碼,則應:
- 在離線句柄表中查詢句柄,以判斷實際句柄值。
- 將標記位元設為 1,即可將封套從離線變更為內嵌。
- 將 fidl_inline_envelope_t 結構體的句柄欄位設為實際句柄值。
如需編碼/解碼句柄的範例,請參閱「範例」一節。
我們選擇這個雙重編碼/解碼表單,是因為它與離線和內嵌信封編碼相容。雖然這會導致信封中出現專屬的句柄程式碼,但我們認為,比起簡化程式碼而需要更多編碼,更一致 (即更少) 的資料編碼會是更好的折衷。
針對 (2) 在不可擴充的容器中使用選用句柄:我們也建議使用與線路格式相同的信封表示法做為背景 (3),也就是雙重離線編碼/內嵌式解碼表單。很抱歉,選用句柄的這項表示法比現有的選用句柄線路格式 (uint32
) 更不精簡。不過,我們仍建議使用以信封為基礎的表示法,因為
- 使用選用句柄的封套與使用任何選用型別的封套相同,
- 在 FIDL 訊息中,選用句柄相較於其他訊息類型2較為罕見,因此額外的 4 個位元組信封額外負擔不應對訊息大小造成重大影響。
- 如果保留選用句柄的現有
uint32
線路格式,將產生三種編碼和三個個別的句柄程式碼路徑:非選用、選用和封套內的句柄。使用選用項目的封套表示法可消除一個編碼和一個程式碼路徑,進而提高一致性並減少專屬程式碼。
針對 (2) 的編碼 (非可擴充容器中的選用句柄) 已明確列於下方的「設計決策」一節,因為選用句柄的 uint32
表示法較為精簡,值得考慮。
字串和向量
目前不支援空值的 字串和向量的電線格式會以 16 個位元組儲存:
uint64
:元素數量 (向量) 或位元組數 (字串)uint64
用於表示是否有/沒有/指標。
我們建議使用信封來表示字串和向量 (可為空值或不可為空值):
- 元素 (向量) 或位元組 (字串) 數量會移至離線位置。
- 這可讓向量/字串由封包 (僅限) 表示,因此封包成為針對所有 FIDL 類型參照任何離線資料的唯一方式,可為所有離線資料提供一致的表示法。
- 向量/字串內容位於個別離線物件中,並緊接在元素/位元組計數之後。
- 封包是否存在,取決於封包是否為零。
請注意,向量元素數量與封套大小不同:
- 封套的大小是向量元素數量乘以元素大小。
- 如果向量包含子物件 (例如
vector<Table>
、vector<vector<string>>
),則封套的大小會包含所有遞迴子物件的大小。
可為空值的字串/向量,以及可擴充容器內的字串/向量,會以與不可為空值的字串和向量相同的方式表示:零封包會用來表示缺少的字串/向量。
相反地,如果字串/向量不可為空值,驗證工具在遇到零包裝函式時,就必須發生錯誤。
對於使用 C 繫結的程式碼,這可能會造成原始碼破壞性變更,因為這類程式碼會預期 fidl_vector_t
和 fidl_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。因此,編碼器執行額外工作,但沒有明顯的好處。這個引數也適用於句柄計數。
不過,基於以下幾個原因,我們仍建議您務必提供大小和句柄計數:
- 一致性:要求大小,表示封套編碼在所有用途 (不論是否位於可擴充容器內) 皆一致。程式碼的一致性越高,所需程式碼就越少,認知模型也越簡單。
- 我們之後可以變更。未來的 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 同級元件都必須瞭解新的線路格式,並將這項資訊傳達給同級元件,才能使用新格式。
可以使用平滑轉換效果。兩種方法如下:
- 交易訊息標頭中含有
uint32
保留/旗標欄位。我們可以為發起端同端保留 1 位元,表示該端瞭解新的線路格式,並逐步進行軟性轉換:- 確保所有用戶端和伺服器都能解讀舊版和新版的線路格式。我們會繼續使用舊的傳輸格式。
- 請讓對等端在交易訊息標頭中設定位元,啟用新的線路格式。如果雙方都已設定位元,則雙方都能切換至新的線路格式。
- 當軟性轉換已在所有層中推出後,所有 Fuchsia 都能使用新的線路格式。我們可以移除交易訊息標頭中的位元設定。
- 刪除舊線路格式的程式碼,並取消保留交易訊息標頭位元。
- 我們可以使用
[WireFormat=EnvelopeV2]
屬性 (或類似的屬性) 修飾特定 FIDL 訊息類型、介面或兩者,藉此指出訊息/介面應使用新的線路格式。- 雖然使用
[WireFormat]
屬性修飾介面似乎能與線路格式變更保持一致,但在結構體上實作 WireFormat 變更應該會更容易,因為結構體可用於不同介面,而繫結需要額外的邏輯來判斷使用結構體的內容。 - 建議介面
[WireFormat]
屬性只影響介面方法引數的線路格式,而不會遞迴影響引數的結構體。 - 這可讓您部分遷移並選擇採用新的線路格式,讓團隊可自行決定遷移速度。
- 一旦所有結構體和介面都具有
[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 位元是否合理?
- 我們建議使用封套,在不可擴充的容器中編碼選用的句柄,這比目前的選用句柄編碼 (8 個位元組,而非 4 個位元組) 更不緊湊。
- 在這個情況下,您必須在精簡與專用程式碼與一致性之間取捨。我們認為,一致性和統一性比專門且更精簡的表示法更重要,因為選用句柄可能較少使用。(程式碼中 37 個選用用途,與 187 個非選用用途)。
- 我們是否立即啟用選用性?
- 我們建議在升級線路格式的過程中,透過個別的轉換作業,為所有類型提供選用性,因為變更可以逐步完成。
- 實作這類選用功能需要變更剖析器、編碼器、驗證器和解碼器,這項變更規模足以獨立為一項轉換。
- 我們建議內嵌類型為 32 位元以下,這樣就能更積極地內嵌。
- 我們可以內嵌任何資料 (小於等於 63 位元),因為標記位元在 64 位元信封中只使用一個位元。
- 我們可以使用專屬的表示法,將小字串和向量內嵌,例如使用一個位元組來計算元素/位元組數量,然後再加上字串/向量資料。(請參閱「先前的作品」尋找靈感)。
- 雖然這些方法效率更高,但我們還是捨棄了這些方法,因為以內容而非類型為依據的內嵌方式,意味著 (1) 解碼器無法預先知道是否要根據類型預期內嵌或離線信封,以及 (2) 變更欄位內容意味著可以以不同方式編碼,這似乎違背了 FIDL 的目標和靜態重點。
既有技術與參考資料
作者從現有的標記指標用法中獲得許多靈感,這些指標在動態和功能性語言中已有悠久歷史。特別是 Objective-C 64 位元執行階段會大量使用這些功能來提升效能 (甚至會使用專用的5/6 位元編碼來處理內嵌字串)。
由於目前的 64 位元平台傾向使用 48 位元 (或更少) 來編碼指標,因此我們考慮從已解碼的指標中偷取更多位元,並透過位元位移嘗試在指標中編碼離線物件的大小。不過,部分架構已將實體位址空間擴大至超過 48 位元 (ARM64、x64-64 5 層分頁),因此竊取更多指標位元可能無法滿足未來需求。