泊學翻譯自Swift在Github上發佈的Swift ABI Manifestogit
在實踐中,ABI關注的內容是緊密耦合在一塊兒的。可是,做爲一個概念模型。我更願意把它分紅6個獨立的分類:github
1.和類型相關的,例如:全部的結構和類對象應該有肯定的內存佈局。爲了達成二進制層次上的交互(這裏應該指的是不一樣版本Swift編譯器生成的結果在二進制上兼容),它們必須共享相同的佈局協議。這部份內容會在數據佈局的章節進行討論。算法
2.Swift可執行程序,運行時、反射機制、調試器以及可視化工具都和類型的metadata息息相關。所以,metadata應該有一種更穩定的讀取方式,要不爲類型的metadata設計肯定的內存佈局,要不爲訪問類型的metadata提供一套穩定的APIs。這部份內容會在類型的metadata章節繼續討論。編程
3.程序庫中每個被導出或來自外部的符號都須要一個惟一的名稱,這個名稱的識別應該在全部的二進制實體之間達成一致。因爲Swift提供了函數重載以及上下文相關的名字空間(這裏應該指的是經過module引入的名字空間),所以,在代碼中出現的任何名稱可能都不是全局惟一的。爲了把它們表達成一個全局惟一的名字,Swift使用了一種叫作name mangling的技術。具體的name mangling方案會在Mangling章節中討論。swift
4.函數必須知道如何相互調用,所以咱們須要約定調用棧在內存中是如何佈局的,哪些寄存器會在調用間被保留。這些內容統稱爲函數的調用約定。這部份內容會在Calling Convention章節中討論。數組
5.Swift發佈的時候自帶了一個運行時庫,用來處理諸如動態類型轉換、引用計數、類型反射等相關的工做。Swift程序在編譯的時候會調用這些運行時中的API。所以,Swift運行時API也是Swift ABI的一部分。運行時API的穩定性會在運行時的章節中討論。數據結構
6.除此以外,Swift還自帶了一個標準庫,其中定義了不少公共的類型、結構和基於這些結構的方法。爲了讓這份自帶的標準庫能夠被用不一樣版本Swift編寫的程序調用,標準庫也須要對外暴露一份穩定的API。所以,和標準庫中定義的類型須要有肯定的內存佈局同樣,Swift標準庫的API也是Swift ABI的一部分。關於標準庫ABI的穩定性會在標準庫的章節中進行討論。app
背景編程語言
首先,咱們來定義一些術語。函數
對象(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<T> { var number: Int var object: T }
在這種狀況下,根據特定的佈局算法,整數number的佈局以及它在struct對象中的位置都是能夠肯定的。可是,泛型屬性的存儲倒是不透明佈局,所以,整個結構的大小和對齊都是不肯定的。咱們正在調研如何用更高效的方式佈局這種「半透明」形式的組合SR-3722。這極可能會致使把不透明的部分放到佈局的末尾以保證全部靜態佈局的部分能夠正常計算偏移。
(To be continue...)