泊學翻譯自Swift在Github上發布的Swift ABI Manifesto
Swift ABI的構成在實踐中,ABI關注的內容是緊密耦合在一起的。但是,作為一個概念模型。我更願意把它分成6個獨立的分類:
1.和類型相關的,例如:所有的結構和類對象應該有確定的內存布局。為了達成二進制層次上的交互(這裡應該指的是不同版本Swift編譯器生成的結果在二進制上兼容),它們必須共享相同的布局協議。這部分內容會在數據布局的章節進行討論。
2.Swift可執行程序,運行時、反射機制、調試器以及可視化工具都和類型的metadata息息相關。因此,metadata應該有一種更穩定的讀取方式,要不為類型的metadata設計確定的內存布局,要不為訪問類型的metadata提供一套穩定的APIs。這部分內容會在類型的metadata章節繼續討論。
3.程序庫中每一個被導出或來自外部的符號都需要一個唯一的名稱,這個名稱的識別應該在所有的二進制實體之間達成一致。由於Swift提供了函數重載以及上下文相關的名字空間(這裡應該指的是通過module引入的名字空間),因此,在代碼中出現的任何名稱可能都不是全局唯一的。為了把它們表達成一個全局唯一的名字,Swift使用了一種叫做name mangling的技術。具體的name mangling方案會在Mangling章節中討論。
4.函數必須知道如何相互調用,因此我們需要約定調用棧在內存中是如何布局的,哪些寄存器會在調用間被保留。這些內容統稱為函數的調用約定。這部分內容會在Calling Convention章節中討論。
5.Swift發布的時候自帶了一個運行時庫,用來處理諸如動態類型轉換、引用計數、類型反射等相關的工作。Swift程序在編譯的時候會調用這些運行時中的API。因此,Swift運行時API也是Swift ABI的一部分。運行時API的穩定性會在運行時的章節中討論。
6.除此之外,Swift還自帶了一個標准庫,其中定義了很多公共的類型、結構和基於這些結構的方法。為了讓這份自帶的標准庫可以被用不同版本Swift編寫的程序調用,標准庫也需要對外暴露一份穩定的API。因此,和標准庫中定義的類型需要有確定的內存布局一樣,Swift標准庫的API也是Swift ABI的一部分。關於標准庫ABI的穩定性會在標准庫的章節中進行討論。
數據布局背景
首先,我們來定義一些術語。
對象(object)是指某個類型的存儲實體,它可以存儲在內存中的某個位置,或存儲在寄存器裡。對象可以是 struct
/ enum
類型的值、class
的實例、class
實例的引用、protocol
類型的值,甚至是closures。而在那些視class
為全部的面向對象編程語言中,對象則就是指的class的一個實例,這是Swift有別於它們的地方;
對象的數據成員(data member)是指任意需要存放在類對象內存布局內的值。數據成員包括了一個對象所有的stored properties和associated values;
閒置位(spare bit)是某種類型對象(的內存布局)中,沒有被使用的部分。這些部分通常是為了對齊內存地址而填充的地址空間。稍後,會更深入討論這個話題;
在對象的布局中,除了那些表示對象值的bit之外,還有一類bit並沒有實際的意義。例如,對於一個包含3個case
的傳統C風格enum
來說,它的值用兩個bit就可以表示了(數字0,1,2分別對應三個case,它們對應二進制的00,01和10)。這時,這兩個bit可以表示的第4個值3,它的二進制表示中的第一個1就是無意義的;
數據布局,也被稱作類型布局,定義了一個對象的數據在內存中的布局。這包括了對象在內存中的大小,對象的對齊(稍後會定義)以及如何在對象中找到每一個數據成員。
如果編譯器可以在編譯期確定一個對象的布局,這個對象的布局就是靜態的。如果對象的布局只有在運行時在可以確定,這類對象的布局就是不透明的(opaque layout)。我們會在opaque layout章節中深入討論這類對象。
布局和類型的屬性在Swift裡,對於每一個靜態布局的類型T,ABI指定了計算以下內容的方式:
關於類型的對齊:對於x: T
來說,對象x
的起始地址對當前硬件平台的內存對齊值取模一定是0(也就是說總在內存地址對齊的位置開始);
關於類型的尺寸:一個類型對象的大小,按對象占用的字節數計算(可以是0),但是不包含填充在對象結尾的字節;
關於每個數據成員的偏移(如果可行):每一個數據成員的地址,都是從對象起始地址開始計算的;
把計算對齊和對象大小的方式結合在一起,就是這個類型的對象占用內存時的步進計算方式,它等於把對象的尺寸按照內存對齊的大小向上取整一個單位(最小是1單位。例如,對象的大小是7,內存要求8字節對齊,那麼對象占用的內存就向上調整到8)。這種計算方式對於在連續內存地址空間中排列對象(例如:數組)時很有幫助。
一些類型有以下兩種有趣的屬性:
如果一個類型僅僅用來存儲數據(注:很多struct
就是如此,但不是全部),在拷貝、移動或銷毀這類對象時,就不會有額外的復雜語義。我們管這種類型叫做POD(Plain of Data),也叫做trivial type。這種類型的對象在拷貝時,直接復制它的值即可,銷毀時,可以直接回收分配給它的存儲資源。只有在一個類型的所有數據成員都是trivial type時,這個類型才是一個trivial type。
如果一個對象的地址沒有被其它輔助設計的表結構(注:這裡應該是指為了實現對象布局而引入的額外數據結構)引用,這種類型的對象就是可以按位移動的(bitwise ovable)。當一個對象要從一個地址拷貝到另外一個地址,並且原地址的對象已經不再需要的時候,就可以把原地址的對象按位拷貝到新地址,然後把原地址對象標記為不可用的狀態。如果一個類型所有數據成員都是可以按位移動的,則這個類型的對象也是可以按位移動的。並且,所有的trivial type的對象都是可以按位移動的。
例如,一個struct Point
,它有兩個Double
類型的屬性x
和y
,表示平面上X軸和Y軸的坐標。此時,Point
就是一個trivial type。復制Point對象的時候,我們只要按位拷貝對象的內容就可以,銷毀的時候我們也無需做任何額外的工作。
再來看一個可以按位移動的非POD類型的例子,就是包含類對象引用的struct
。在拷貝這類對象時,我們不能只是簡單拷貝struct
對象的值,還要retain
其包含的類對象。而在銷毀這類struct
對象的時候,我們也要release
其引用的類對象。但是,這類對象卻是可以在不同的內存地址間移動的,只要每次移動後,我們都把原地址的對象標記為不可用,保持其包含的類對象總體引用計數不變就好了。
最後,來看一個不可按位移動的非POD類型的例子,就是一個包含weak
引用的struct
。所有的weak
引用都是通過一個輔助表格維護的。因此,當它們引用的對象被銷毀的時候,這些weak
references才可以被設置成nil
。當移動這類對象的時候,必須要更新表格中的weak
reference,讓它引用到新的對象地址。
當一個對象的布局只有在運行時才可以確定時,這類對象的布局就叫做不透明的。例如,一個泛型對象,我們就無法在編譯期確定對象的布局。再有,就是一種更具適應性的類型(resilient type),我們會在下一節描述這個概念。
對於一個具有不透明布局的對象來說,它的大小、對齊,是否是一個POD類型或者是否可以按位移動都是通過查詢它的value witness table來確定的。我們會在value witness table這一節中深入討論這個話題。數據成員的偏移是通過查詢類型的metadata得到的,我們會在value metadata的章節中討論。擁有不透明布局的對象必須通過間接的方式傳遞,我們會在函數底層簽名(Function Signature Lowering)的章節中討論。Swift運行時,通過一些指針和擁有不透明布局的對象進行交互,因此,這類對象必須是可以取地址的。我們會在抽象級別的章節中進一步進行描述。
在實踐中,編譯器可以在編譯期對布局有部分的了解。例如,對於下面這樣的struct
:
struct Type {
var number: Int
var object: T
}
在這種情況下,根據特定的布局算法,整數number的布局以及它在struct對象中的位置都是可以確定的。但是,泛型屬性的存儲卻是不透明布局,因此,整個結構的大小和對齊都是不確定的。我們正在調研如何用更高效的方式布局這種“半透明”形式的組合SR-3722。這很可能會導致把不透明的部分放到布局的末尾以保證所有靜態布局的部分可以正常計算偏移。
(To be continue...)
以上就是對譯:Swift ABI (二)的相關介紹,希望對您學習IOS有所幫助,感謝您關注本站!
[db:作者簡介][db:原文翻譯及解析]【譯:Swift ABI (二)】的相關資料介紹到這裡,希望對您有所幫助! 提示:不會對讀者因本文所帶來的任何損失負責。如果您支持就請把本站添加至收藏夾哦!