OC 與 Swift 編譯對比 - swift_0x02

目錄概要

  1. 為什么想寫這篇文章
  2. 蘋果編譯器的發(fā)展背景
  3. LLVM 簡介
  4. C Language 編譯器 Clang
  5. Swift 編譯器譯編 tfiwS
  6. OC Swift 混編中的相互識別

1. 為什么想寫這篇文章

工程一些模塊遷移到 Swift 后,編寫代碼的時候會遇到一些奇奇怪怪的問題,這些問題都是在編譯鏈接時期出現(xiàn)的镊绪,為了了解這些問題的產(chǎn)生原因树灶,以及 Swift 編譯的過程與 Clang 的區(qū)別,再以及混編工程是如何運(yùn)作的,把一些問題和了解之后的原理記錄了下來,特此總結(jié)一篇文章,嘗試系統(tǒng)地串聯(lián)起 OC 和 Swift 的編譯過程片习。

這篇文章分三大部分,第一部分介紹歷史蹬叭,第二部分通過對比學(xué)習(xí)了 Clang 和 Swift 編譯器的編譯過程都做了什么事情藕咏,第三部分記錄混編工程中 Xcode 的工作。

2. 蘋果編譯器的發(fā)展背景

這段很長一部分沒什么干貨和價值秽五,可以跳過孽查。但是歷史非常有意思,我的文筆有限坦喘,寫的非常不精彩盲再,但是把事實描述出來還是足夠了。如果說解技術(shù)問題要知其然并知其所以然瓣铣,我認(rèn)知知其然是技術(shù)底層細(xì)節(jié)架構(gòu)等答朋,所以然就是很簡單的歷史故事。

今天蘋果的開發(fā)生態(tài)的形成有很多散碎的歷史因素棠笑,這段用一萬多字把這些歷史因素整理串聯(lián)起來梦碗,做一個簡單的概述。你會發(fā)現(xiàn)有一些因素是貫穿始終的蓖救,比如 Steven.Jobs 和 Linus 這兩個人的人物性格洪规、編程技術(shù)的發(fā)展、時代的局限性等循捺。

2.1 OC + GCC ?

GCC Logo

OC 和 GCC 為什么會在一起斩例?從歷史時間線上首先從 OC 開始。OC 的發(fā)展期初萌芽階段跟 Apple 沒什么關(guān)系从橘,跟 SmallTalk 語言有關(guān)樱拴。

image

今年 1 月份,OC 之父 Brad Cox 去世了洋满。OC 是 Cox 在一家 IT 公司上班期間與同事 Tom 一起編寫的,那為什么 OC 基于 C 呢珍坊?因為這家公司用的編程語言是 C 語言牺勾,然后 Cox 和 Tom 非常喜歡當(dāng)時的一門 Smalltalk 語言,于是他倆早在 1980 年就編寫了OC阵漏,OC 現(xiàn)在的消息傳遞機(jī)制就是基于 Smalltalk 的驻民。然后 1983 年翻具,Cox 與 Love 合伙成立了 PPI 公司,開始賣 OC 以及相關(guān)庫回还,也就是后來幫主自傳里面提到的 StepStone裆泳!另外1986年Cox甚至寫了本書講OC的思想多么**。

有意思嗎柠硕?OC 開始編寫的時候工禾,剛剛好是喬幫主被 Apple 轟出來的那一年。

GCC 是一個從 1985 年就開始的 C 語言編譯器蝗柔,創(chuàng)始人是 Stallman闻葵,后來快速發(fā)展,可以編譯C++癣丧、Fortran槽畔、Pascal 等語言(當(dāng)然后來也支持了Java、OC)胁编。當(dāng)時 GCC 是非常優(yōu)秀的編譯器厢钧,比同類編譯器性能可以高達(dá)30%,并且支持多種語言嬉橙。這就使得當(dāng)時美國很多商業(yè)公司都采用了 GCC早直,甚至包括 HP。幫主的自傳里也多次提到 HP憎夷,喬布斯 12 歲的第一份暑假零工就是打電話給 HP 創(chuàng)始人要機(jī)械器件獲得到的莽鸿。

85年同年,因為百事可樂公司的原因拾给,NeXT 公司創(chuàng)立祥得。

三年后也就是 1988 年,NeXT 從 StepStone 手上買了 OC 的授權(quán)蒋得。至于為什么幫主選擇了OC级及?原因有很多,NeXT的技術(shù)硬需求是:GUI额衙、面向?qū)ο笠埂討B(tài)性,首先是GUI往回看后來用 OC 開發(fā)出來的 NeXTSTEP 操作系統(tǒng)往上支撐的是一個非常重 GUI 的系統(tǒng)窍侧。其次是面向?qū)ο?動態(tài)性县踢,OC 提供的面向?qū)ο?動態(tài)性是任何其他所謂的面向?qū)ο笳Z言都無法提供的。當(dāng)時 C++ 對于 GUI 的支持并不好伟件,相比之下喬布斯非常青睞 Smalltalk 式消息傳遞機(jī)制的 OC硼啤,這段自傳中有大量的篇幅回顧。另外就是性格原因斧账,再次創(chuàng)業(yè)的喬布斯對于技術(shù)甚至語言有非常強(qiáng)的控制欲谴返∩飞觯總之很多歷史原因使得幫主選擇了 OC 作為 NeXT 的主要開發(fā)語言。

另外 OC 中有大量的語言特性是授權(quán)給 NeXT 后以及 Apple 時期添加的嗓袱,例如@Property籍救、Protocol、ARC 等渠抹。

NeXT 技術(shù)發(fā)展的過程中蝙昙,商業(yè)和技術(shù)上的考量,喬布斯最開始是特別喜歡 Stallman 的逼肯,NeXT 把 OC 引入了 GCC耸黑,后期整個 NeXT 的技術(shù)棧就是 OC + GCC。

腦洞大一點篮幢,可以說如果沒有百事可樂的話大刊,就沒有今天我們用的 Mac、iPhone三椿、OC缺菌、LLVM、Swift等這套東西...

2.2 GCC 后期暴露的問題

當(dāng)時 Apple 一度從巔峰陷入了絕境搜锰,《連線》雜志在1997年的六月還發(fā)表了一篇文章伴郁,名為《101種拯救Apple的方法》,包括改LOGO蛋叼、賣給摩托羅拉焊傅、設(shè)計出一臺內(nèi)置卡布奇諾咖啡機(jī)的PC.....等

101種拯救Apple的方法

周所周知后來 NeXT 被 Apple 收購。促成放棄原有系統(tǒng) Copland狈涮,收購 NeXTSTEP 的關(guān)鍵人物是 Ellen Hancock狐胎,她去 Apple 之前在 IBM 工作了將近29年,一度成為IBM 資深副總裁歌馍,后來喬布斯回歸之后握巢,把她排擠出公司了。

回到 Apple 之后松却,NeXTSTEP 就演化為 Rhapsody暴浦,并最終成為 Mac OS X,隨著發(fā)展晓锻,Apple 對 OC 和 C 語言新增了很多新特性歌焦,但是當(dāng)時 GCC 因為各種原因,支持的不到位砚哆。所以相似的情節(jié)就發(fā)生了::::::

Apple 從 GCC 的主分支中拆分出來了自己的開發(fā)分支独撇,自己來維護(hù)。然后同時 GCC 的主分支繼續(xù)高歌猛進(jìn)一直在迭代版本,使得 Apple 的分支遠(yuǎn)遠(yuǎn)落后 GCC 的主分支券勺。

GCC 的第二個問題是代碼質(zhì)量問題,后來公司越發(fā)展灿里,代碼質(zhì)量越差关炼,Apple 想要很多功能例如IDE 支持、把 GCC 更好地組件模塊化匣吊。GCC 都不支持儒拂。

Apple 給了 GCC 大量的資金,前期確實解決了很多的問題色鸳。長期以往這些 GCC 的打工人社痛,可能開源自由的靈魂沒有什么打工魂,不像桔廠員工...就是他們的態(tài)度總是讓 Apple 覺得特別膈應(yīng)命雀,并以自己在開源世界的地位恫嚇蘋果蒜哀,那你想,幫主是一個完美主義+控制欲的人吏砂,他能忍撵儿?

最終 Apple 由于受不了這些項目組人員的態(tài)度、協(xié)議狐血、代碼質(zhì)量淀歇,我自己搞。因此才有了蘋果在外尋找新的合作項目的種子匈织。

當(dāng)時 GCC 在當(dāng)時比較是一個非常非常優(yōu)秀的項目浪默,像 GCC、LLVM缀匕、Linux纳决、Unix 這些宏偉的項目,不是靠研發(fā)可以研發(fā)出來的弦追,即使是 Apple 也只能靠尋覓合作來擴(kuò)展核心技術(shù)項目岳链。

插入:蘋果跟 Linux 的分道揚(yáng)鑣

如果當(dāng)時 Apple 采用了 Linux,那我覺得今天的 Apple 生態(tài)完全會是另一個局面劲件。

事情是這樣的掸哑,喬布斯回歸蘋果之后,開始構(gòu)思下一代基于 NeXTSTEP 的操作系統(tǒng)零远,當(dāng)時蘋果的 BSD 生態(tài)并比不上名聲大噪的 Linux苗分,研發(fā)需要投入大量的人力物力,然而蘋果作為一家商業(yè)公司牵辣,主要競品是 MS摔癣,更需要借力打力,聯(lián)合開源組織可以借助大量的力量推動 Mac OS 的發(fā)展,能省下很多錢择浊。所以促成了他兩的一次交流戴卜。

幫主構(gòu)思了一套把 Linux 作為 MacOS 內(nèi)核的重要組成部分的一個偉大藍(lán)圖,并且得到 Linus 的認(rèn)同合作琢岩,使得 Linus 鼓動麾下一大群優(yōu)秀的開發(fā)者加入 Apple翎苫,這是當(dāng)時 Apple 非常想看到的一個結(jié)果值漫。而且再提一嘴权旷,在 喬布斯回歸蘋果之前贞言,蘋果就以及和 Linux 系統(tǒng)有著深厚的合作,有興趣的可以深挖關(guān)鍵詞 MkLinux糕篇,這是一個把 Linux 內(nèi)核跑在 Mach 上的系統(tǒng)啄育,這個系統(tǒng)對后來 Linux 的可移植性,以及 MaxOSX 的 XNU 內(nèi)核都有關(guān)鍵的作用拌消。

當(dāng)然沒有談成挑豌,完全談崩了,兩個人的角度和立場完全不同拼坎,他倆的性格呢非常相似浮毯,唯我獨尊、控制欲強(qiáng)等等泰鸡。談崩之后债蓝,Apple 整個技術(shù)體系也徹徹底底地放棄了與 Linux 和所有合作。

喬布斯覺得 Linux 這種開源軟件的代碼質(zhì)量和推進(jìn)速度根本無法支撐他想要的軟件生態(tài)盛龄。GCC 后來確實印證了這一點饰迹。

Linus 呢?后來 Mac 轉(zhuǎn)型 Unix 內(nèi)核發(fā)布之后余舶,他多次公開嘲笑 Mac 的技術(shù)非常落后啊鸭,遠(yuǎn)遠(yuǎn)比不上他,批評 Mac 的文件管理系統(tǒng)是垃圾匿值。然而在后面 Mac 高歌猛進(jìn)的時候赠制,Linus 只能眼巴巴地看著,后來 Linus 也承認(rèn)了自己當(dāng)時的問題挟憔。

image

2.3 LLVM 的誕生

Chri Lattner 個人博客照片

2000年钟些,Chris Lattner 大學(xué)畢業(yè),考了GRE绊谭,最終前往伊利大學(xué)政恍,伊利諾伊大學(xué)讀計算機(jī)碩士博士,這個學(xué)校就是后來 Apple 投入大量資金合作LLVM的學(xué)校达传!

Chris 在伊利看了很多遍龍書篙耗,就是編譯原理迫筑,發(fā)表了一篇又一篇的論文。他在碩士畢業(yè)論文里提出了一套完整的在編譯時宗弯、鏈接時脯燃、運(yùn)行時甚至是在閑置時優(yōu)化程序的編譯思想,直接奠定了 LLVM 的基礎(chǔ)蒙保。

LLVM 的誕生是為了解決一個 OpenGL 的函數(shù)的通用調(diào)用問題曲伊,后來到他念博士時 LLVM 已經(jīng)非常成熟,使用 GCC 作為前端來對程序進(jìn)行語義分析追他,產(chǎn)生 IF(Intermidiate Format)(類似今天的 IR),然后 LLVM 使用分析結(jié)果完成代碼優(yōu)化和生成岛蚤。

這項研究讓他在 2005 年畢業(yè)時就成為了業(yè)界小有名氣的編譯器專家邑狸,然而他不知道的事,背后 Apple 的眼睛已經(jīng)盯上了他涤妒。Chris 很希望可以繼續(xù)編寫 LLVM单雾,他想把它做成顛覆性的產(chǎn)品,當(dāng)時只有蘋果允許他入職之后全職繼續(xù)實現(xiàn) LLVM她紫,他不假思索地就加入了 Apple硅堆。這段是他自己說的。

并且在短短幾年內(nèi)贿讹,LLVM 成為最領(lǐng)先的開源軟件技術(shù)渐逃。這無異于扇了 Linux 小組、GCC 小組一記響亮的耳光民褂。

剛進(jìn)入 Apple茄菊,Chris Lattner 就大展身手:首先在 OpenGL 小組做代碼優(yōu)化,把 LLVM 運(yùn)行時的編譯架在 OpenGL 棧上赊堪,這樣 OpenGL 棧能夠產(chǎn)出更高效率的圖形代碼面殖。如果顯卡足夠高級,這些代碼會直接扔入 GPU 執(zhí)行哭廉。但對于一些不支持全部 OpenGL 特性的顯卡(比如當(dāng)時的Intel卡)脊僚,LLVM 則能夠把這些指令優(yōu)化成高效的 CPU 指令,使程序依然能夠正常運(yùn)行遵绰。這個強(qiáng)大的 OpenGL 實現(xiàn)被用在了后來發(fā)布的 Mac OS X 10.5上辽幌。同時,LLVM 的鏈接優(yōu)化被直接加入到 Apple 的代碼鏈接器上街立,而 LLVM-GCC 也被同步到使用 GCC 4.0 代碼舶衬。

2.4 From GCC to Clang

除了上面這些歷史原因呢,從技術(shù)上來說 GCC 確實取得了巨大的成功赎离,GCC 的初衷是提供一款免費的開源編譯器逛犹,僅此而已,后來支持了越來越多的語言,GCC 架構(gòu)的問題也逐漸暴露出來虽画。

當(dāng)時的 Apple 對于語言舞蔽、技術(shù)等控制欲是非常強(qiáng)的。以及喬布斯有自己的一套對于技術(shù)生態(tài)的構(gòu)思码撰,這些都是 GCC 遠(yuǎn)遠(yuǎn)達(dá)不到的渗柿。

基于 LLVM 的優(yōu)秀設(shè)計,使得 GCC 以及成為了其中的一個獨立子模塊脖岛,Apple 也打算從零開始寫前端編譯器朵栖,完全替代掉 GCC。

image

Clang 2007 年開始開發(fā)柴梆,C 編譯器最早完成陨溅,而由于 Objective-C 只是 C 語言的一個簡單擴(kuò)展,相對簡單绍在,很多情況下甚至可以等價地改寫為 C 語言對 Objective-C 運(yùn)行庫的函數(shù)調(diào)用门扇,因此在 2009 年時,已經(jīng)完全可以用于生產(chǎn)環(huán)境偿渡。C++ 在后來也得到了支持臼寄。

因為實現(xiàn)了多模塊的復(fù)用以及很多優(yōu)化,Clang 發(fā)布的時候溜宽,Clang 的編譯性能是 GCC 的三倍吉拳!

時代局限性:這所謂的時代局限性也是 OC 當(dāng)時所處的局限性,這也是后期 Swift 替代 OC 的主要原因适揉。因為編程技術(shù)只有短短幾十年的歷史合武,遠(yuǎn)不及其他上百上千年的學(xué)科,每個5年都有它的歷史局限性涡扼。隨時時間推移稼跳,大量的編程實踐積累了大量的編程技術(shù)和思想上的發(fā)展,越來越多的優(yōu)秀編程語言進(jìn)行各種擴(kuò)充吃沪,而 OC 由于歷史局限和一些設(shè)計上的包袱汤善,OC 期初也只是對 C 語言進(jìn)行了一層擴(kuò)充,其一些特性是使用匯編來實現(xiàn)的票彪。大量的新特性無法進(jìn)行添加和靈活變動红淡,所以只能靠全新的語言來更替,這也跟 Swift 選擇開源 + 專職社區(qū)公開維護(hù)息息相關(guān)降铸。

參考案例在旱,項目用到地圖模塊的時候,發(fā)現(xiàn)虛擬機(jī)上地圖特別特別的卡推掸,拖動起來卡的不行桶蝎,后來運(yùn)行到真機(jī)上非常流暢驻仅,這個原因有兩點:首先工程在Debug環(huán)境運(yùn)行的時候,IR 中間代碼優(yōu)化的等級登渣,在 debug 的時候選擇的是 LowLevel噪服,基本相當(dāng)于不怎么優(yōu)化。第二個核心原因是因為 Clang 對于 X86 生成的機(jī)器碼的執(zhí)行效率是遠(yuǎn)遠(yuǎn)低于 Arm CPU 的胜茧,畢竟真機(jī)上的 A 系列 CPU 是 Apple 自家設(shè)計生產(chǎn)的粘优。

前端的代碼質(zhì)量,即使是開源社區(qū)也能做的很好呻顽,但是后端的機(jī)器碼的執(zhí)行效率的雹顺,跟前端相比,則完全是另一個工種廊遍,其性能直接與項目預(yù)算相關(guān)无拗,這是開源組織不具備的,所以 LLVM 的后端效率昧碉,是開源的 GCC 無法比擬的。另外舉栗例如揽惹,盡管英特爾的 ICC 編譯器只有少數(shù)人參與被饿,但是卻以機(jī)器碼性能而著名。

我知道的東西就那么多搪搏,這里不細(xì)說太多狭握,不然正文沒東西寫了。疯溺。论颅。

2.5 From OC to Swift

image

隨著編程語言的不斷演進(jìn),很多很多新的編程思想的實踐囱嫩,OC 的缺點也逐漸暴露出來恃疯,比如不支持命名空間;不支持運(yùn)算符重載墨闲;不支持多重繼承今妄;使用動態(tài)運(yùn)行時類型,所有的方法都是函數(shù)點語法調(diào)用鸳碧,很多編譯時的優(yōu)化方法都用不到等盾鳞。

另一個原因是時代的局限性,上面提過了瞻离。

不僅僅滿足于 LLVM 的 Chris 腾仅,發(fā)起了 Swift 項目,后來就是大家都知道的事情了套利。

3. LLVM 簡介

3.1 經(jīng)典編譯器理論模型

在 LLVM 之前就已接有了經(jīng)典編譯器三段式模型設(shè)計理論:

前端負(fù)責(zé)源代碼:預(yù)處理 -> 詞法 -> AST語法樹 -> IR 中間代碼
優(yōu)化器負(fù)責(zé)IR優(yōu)化:專門優(yōu)化中間代碼(見下圖)
后端負(fù)責(zé)轉(zhuǎn)換機(jī)器碼:最大化利用目標(biāo)機(jī)器特殊指令推励,提高性能

對于中間的優(yōu)化器鹤耍,我們可以使用通用的中間代碼。

這種三段式的結(jié)構(gòu)還有一個好處吹艇,開發(fā)前端的人只需要知道如何將源代碼轉(zhuǎn)換為優(yōu)化器能夠理解的中間代碼就可以了惰蜜,他不需要知道優(yōu)化器的工作原理,也不需要了解目標(biāo)機(jī)器的知識受神。這大大降低了編譯器的開發(fā)難度抛猖,使更多的開發(fā)人員可以參與進(jìn)來。不同的技能棧是一個社會分工問題而不是一個技術(shù)問題鼻听,這在實踐中非常重要财著,特別是對于那些想要盡量減少協(xié)作障礙的開源項目。

通過切換不同的編譯器前前端和后端撑碴,就可以支持不同的編程語言以及目標(biāo)機(jī)器架構(gòu)撑教,如下圖

Retargetablity

當(dāng)需要支持多種「編程語言」時,只需要添加多個「前端」就可以了醉拓。
當(dāng)需要支持多種「目標(biāo)機(jī)器」時伟姐,只需要添加多個「后端」就可以了。

「雖然但是」 這種三段式的編譯器有很多優(yōu)點亿卤,并且被寫到了教科書上愤兵,但是在實際中這一結(jié)構(gòu)卻從來沒有被完美實現(xiàn)過。

3.2 LLVM

LLVM 這個名字是一個簡寫排吴,但是現(xiàn)在它是整個項目的一個統(tǒng)稱秆乳。「引用2」中 Chris 提到钻哩,LLVM 設(shè)計之初就被定義為:

一系列接口清晰的可重用庫屹堰。

在此之前沒有任何人按照三段式設(shè)計進(jìn)行過實踐,LLVM 是第一個街氢。比較類似的成功案例也有很多扯键,例如 JVM、一些復(fù)用 C 語言作為中間代碼的編譯器珊肃、以及 GCC忧陪,但是有各自的問題。JVM提供即時編譯的編譯器近范,任何對接了字節(jié)碼規(guī)范的編譯器都可以使用 JIT 即時編譯嘶摊,但是 JVM 強(qiáng)制要求前段語言對接字節(jié)碼規(guī)范以及使用 GC。C 語言作為中間代碼的編譯器不能良好的支持 C 語言不支持的特性评矩。

那 GCC 呢叶堆?GCC 雖然遵循了編譯器的三段式設(shè)計,而且成功開發(fā)了多個編譯器前端斥杜,以及根據(jù)不同的后端機(jī)提供了不同的后端虱颗。但是 GCC 從一開始就設(shè)計為一個整體沥匈,對外提供的大多都是一條龍服務(wù),你無法去操作它的IR忘渔,無法將任何功能從系統(tǒng)中剝離出來使用高帖,其大量代碼都是強(qiáng)耦合的。另外 GCC 無法抽離子模塊的原因有很多:被濫用的全局變量畦粮、不可變變量(類似 Swift 中的 let)沒有嚴(yán)格的限制散址、數(shù)據(jù)結(jié)構(gòu)設(shè)計不嚴(yán)謹(jǐn)、龐大的代碼庫等宣赔。

Chris 舉過一個栗子预麸,GCC(即使是2010年的4.5版本)的中間代碼 GIMPLE IR,后端的同學(xué)在后端生成 debug Info 的時候儒将,要遍歷前端生成的抽象語法樹吏祸,前端的同學(xué)也有類似的困擾,等等一系列不可清晰表達(dá)的問題钩蚊,使得工種的工作非常困難贡翘,開發(fā)人員非常稀少。

在 LLVM 中所有的功能都以「接口清晰的可重用庫」的形式來提供砰逻,包括對代碼進(jìn)行分析優(yōu)化生成等工作的集成庫鸣驱、在集成庫的基礎(chǔ)上提供的工具集,包括匯編器诱渤、鏈接器、調(diào)試器等谈况。

LLVM 項目最大的一個特點就是模塊化設(shè)計拆分做到極致的設(shè)計勺美,因為這良好的設(shè)計,為 LLVM 所有的特性提供了可能性碑韵。Chris 也談到:

LLVM的模塊化最初并不是為了直接實現(xiàn)本文所述的任何目標(biāo)而設(shè)計的赡茸。而是一種自我防衛(wèi)的機(jī)制:很明顯,我們很難在第一次嘗試時就能把所有事情都做到最好祝闻。而模塊化優(yōu)化程序是為了使隔離更加容易占卧,以便用更好的實現(xiàn)方式替換掉舊的實現(xiàn)。

3.3 中間代碼 IR & 相關(guān) LLVM 庫

以前狗哥線下分享的 PPT 里引用過一句話「程序中沒有什么是加一個中間層不能解決的联喘。如果有华蜒,那就兩層』碓猓」(來源世上首個CS博士)

LLVM 設(shè)計中最重要的一環(huán)就是「LLVM IR(中間碼)」叭喜,LLVM IR 就是代碼在編譯器中的表達(dá)形式。LLVM IR 是一種采用 SSA 形式的 IR(Immediate Representation)中間代碼表達(dá)蓖谢,也是一種有明確語義的語言捂蕴。其主要作用有2:

  • 向前對接所有高級語言的編譯器前段譬涡,在 IR 中所有的高級語言都是平等的
  • 在編譯優(yōu)化層用來做中間分析和轉(zhuǎn)換的載體。

LLVM IR 的設(shè)計考慮了許多具體目標(biāo)啥辨,包括支持輕量運(yùn)行時優(yōu)化涡匀、跨功能/過程間優(yōu)化、全程序分析溉知、重構(gòu)轉(zhuǎn)換等等

其中陨瘩,SSA 代表 static single-assignment,它表示 IR 中出現(xiàn)的每個變量只會被賦值一次着倾,幫助簡化編譯器優(yōu)化算法拾酝。在觀察 Clang 和 Swift 編譯過程產(chǎn)生的 SIL 以及中間 IR 代碼有大量的中間過程變量(命名為%0%11、以及!0!14等卡者,見 Clang 和 Swift 編譯過程舉例篇幅)蒿囤,原因就是因為 SSA 分析產(chǎn)生的中間變量。

這樣的一段C代碼對應(yīng)的IR如下:

unsigned add1(unsigned a, unsigned b) {
  return a+b;
}
unsigned add2(unsigned a, unsigned b) {
  if (a == 0) return b;
  return add2(a-1, b+1);
}
define i32 @add1(i32 %a, i32 %b) {
entry:
  %tmp1 = add i32 %a, %b
  ret i32 %tmp1
}

define i32 @add2(i32 %a, i32 %b) {
entry:
  %tmp1 = icmp eq i32 %a, 0
  br i1 %tmp1, label %done, label %recurse

recurse:
  %tmp2 = sub i32 %a, 1
  %tmp3 = add i32 %b, 1
  %tmp4 = call i32 @add2(i32 %tmp2, i32 %tmp3)
  ret i32 %tmp4

done:
  ret i32 %b
}

@代表全局標(biāo)識崇决,%是局部變量材诽,i32就是int32,i32**是int32指針恒傻,剩下一些 br call add sub 就是一些常見的匯編指令脸侥。

IR 看起來像是一個奇奇怪怪的匯編語言,使用強(qiáng)類型的簡單類型系統(tǒng)盈厘,與 x86 和 arm64 Assembly 另一個重要區(qū)別是 LLVM IR 不使用固定的命名寄存器睁枕,它使用以 % 字符命名的臨時寄存器,對應(yīng)上面提到的 SSA沸手。

LLVM IR 對于前面的各種語言來說可以輕松對應(yīng)轉(zhuǎn)換成語言明確的 IR外遇,對后面的編譯器來說更是完美,有很強(qiáng)的表現(xiàn)力契吉,可以高效地被 Pass 進(jìn)行優(yōu)化跳仿。

LLVM 中有三中不同形式的 IR 表示:

  1. InMemory IR:內(nèi)存中的 IR,編譯器使用
  2. OnDisk IR:硬盤中用 bitcode 表示的 IR捐晶,適用于 JIT 編譯器快速加載
  3. LLVM assembly language:這種就是 .ll 文件菲语,人類能看懂的文本格式匯編

這三種 IR 是等價的,可以互相轉(zhuǎn)換惑灵,見下圖:

LLVM對于源碼山上、IR、匯編英支、可執(zhí)行文件的轉(zhuǎn)化

使用的一些 LLVM 庫:

  • llvm-as:把 LLVM IR 從「第三種」轉(zhuǎn)換成「第二種」
  • llvm-dis:as 的逆過程
  • opt:對 IR 做優(yōu)化
  • llc:把 IR 編譯成匯編代碼
  • lli:JIT 編譯器 實時解釋執(zhí)行 IR

這些庫作為 LLVM 的 Tools胶哲,源碼在這里

學(xué)習(xí) Swift 的最佳途徑 Playground 的原理就是使用 On-disk IR 去運(yùn)行 llvm-lli,直接讓 JIL 編譯器進(jìn)行解釋潭辈。

LLVM工具官方文檔 LLVM CommandGuide
Clang 官方文檔 Clang User Manual

3.4 IR 的 Pass 優(yōu)化

LLVM 的優(yōu)化通過代碼中指定 IR 優(yōu)化等級來完成鸯屿,因為 SSA 的簡單性質(zhì)澈吨,LLVM 的優(yōu)化通過集成父類 Pass 來完成一趟一趟的遍歷優(yōu)化。

在 Xcode 中寄摆,我們通過設(shè)置這個屬性了修改優(yōu)化的等級谅辣,優(yōu)化的等級越高,編譯時間越長婶恼,代碼的執(zhí)行性能越高:


Xcode設(shè)置Optimization Level

clang 生成 LL 文件或者 s 匯編文件的時候桑阶,通過參數(shù) -O 來指定優(yōu)化等級:-O0、-O1勾邦、-O2蚣录、-O3、-Os

    clang -Os -S main.m
    clang -Os -S -emit-llvm main.m

在 LLVM 2.8 版本中眷篇,一個-O3級別的優(yōu)化萎河,會足足執(zhí)行 67 個 Pass 進(jìn)行優(yōu)化,這些 Pass 子實例通過 PassManager 來管理蕉饼,在進(jìn)行一個優(yōu)化程序執(zhí)行的時候 Manager 讀取信息來管理整個的優(yōu)化過程虐杯。

因為 LLVM 極致模塊化拆分的設(shè)計,LLVM 的代碼優(yōu)化不僅僅在編譯時昧港,在鏈接時和安裝時也會進(jìn)行一些根據(jù)跨文件的更深程度的一些優(yōu)化擎椰,例如針對鏈接產(chǎn)物的內(nèi)聯(lián)、跨文件的常量合并创肥、跨源文件的級別的冗余代碼消除达舒、以及根據(jù)目標(biāo)機(jī)器的特性進(jìn)行優(yōu)化,如圖

Link-Time & Install-Time Optimization:

Link-Time & Install-Time Optimization

3.5 IR 的測試用例工具

強(qiáng)大的項目需要更加強(qiáng)大的 QA叹侄,沒有 QA 再大的船也是注定要翻的巩搏。

LLVM 使用 LLVM BugPoint 工具來自動執(zhí)行所有的功能的自動化測試,通過 opt 命令來執(zhí)行圈膏,通過 FileCheck 來對比 opt 的執(zhí)行輸出和預(yù)期正確的輸出塔猾,如果有問題篙骡,BugPoint 就會最小范圍的拋出有問題的IR代碼稽坤、以及遞送相關(guān)的出錯的組件和正常工作的組件。

以上 LLVM 簡介部分結(jié)束糯俗。

SSA 部分參考:
LLVM SSA 介紹
知乎問題:Phi node 是如何實現(xiàn)它的功能的尿褪?

其他參考:
IR語法講解
基于LLVM 設(shè)計實現(xiàn)新的編程語言
編寫自定義的后端pass實現(xiàn)代碼優(yōu)化混淆等

4. C Language 編譯器 Clang

Clang 是服務(wù)于 C、CXX得湘、OC 的 LLVM 前端編譯器杖玲。Clang 生成的 AST 僅僅是 GCC 的五分之一不到,編譯 OC 的效率更是 3 倍之多淘正。

另外的一篇文章摆马,展開兩個前端編譯器的編譯過程步驟臼闻,戳我:
Clang & Swift 編譯

里面提到通過命令獲取Clang編譯過程如下:

               +- 0: input, "main.m", objective-c
            +- 1: preprocessor, {0}, objective-c-cpp-output
         +- 2: compiler, {1}, ir
      +- 3: backend, {2}, assembler
   +- 4: assembler, {3}, object
+- 5: linker, {4}, image
6: bind-arch, "x86_64", {5}, image

4.1: 預(yù)處理 preprocessor

clang -E main.m -o main.i

Clang 的編譯第一步預(yù)處理,做頭文件的展開替換囤采、預(yù)編譯指令例如#ifndef等述呐、宏定義的展開等。

Clang 對每個.m 文件的預(yù)處理結(jié)果蕉毯,我們可以通過Xcode Related item 窗口中的 Preprocess 按鈕來查看乓搬。

Xcode Preprocess

(頭文件替換的部分需要在混編段落詳細(xì)展開,OC Swift 混編工程實踐的時候遇到了一些這方面的問題)

4.2 Clang 詞法分析 lexical anaysis

clang -Xclang -dump-tokens main.m

Clang 的詞法分析通過遞歸遍歷源文件字節(jié)流代虾,組織成有意義的詞素 (lexeme)进肯,輸出單詞序列 tokens。

int 'int'    [StartOfLine] [LeadingSpace]   Loc=<main.m:4:5>
identifier 'a'   [LeadingSpace] Loc=<main.m:4:9>
equal '='    [LeadingSpace] Loc=<main.m:4:11>
identifier 'add'     [LeadingSpace] Loc=<main.m:4:13>
l_paren '('     Loc=<main.m:4:16>
numeric_constant '1'        Loc=<main.m:4:17>
comma ','       Loc=<main.m:4:18>
numeric_constant '2'        Loc=<main.m:4:19>
r_paren ')'     Loc=<main.m:4:20>
semi ';'        Loc=<main.m:4:21>

4.3 Clang 的語法分析 semantic analysis

clang -Xclang -ast-dump main.m

語法分析將上一步的tokens解析成抽象語法樹木棉磨,通過節(jié)點 Node 的特性江掩,進(jìn)行了語法是否正確的檢驗,例如不識別的方法調(diào)用含蓉、不匹配的類型等频敛。

Clang ASY

大量的 Clang 插件都是通過遍歷 AST 來完成的,比如最簡單的 warning 一個 NSString 的 property 建議使用 copy 關(guān)鍵詞來修飾這種馅扣。

參考
https://clang.llvm.org/docs/IntroductionToTheClangAST.html

4.4 生成中間代碼 codegen

clang -S -emit-llvm main.m

CodeGen 通過對 AST 進(jìn)行遍歷斟赚,生成 ll 格式的 IR。并且完成了一系列動作:

生成各種結(jié)構(gòu)體差油,包括不僅限于類生成拗军、Category&Protocol的生成、OC的一些語法降級成objc_msgSend這種C的函數(shù)調(diào)用蓄喇、自動實現(xiàn)Getter/Setter发侵、處理synthesize、Block結(jié)構(gòu)生成妆偏、ARC自動插入objc-storeStrong&objc-storeWeak刃鳄、自動釋放池插入。

大部分的處理在這里:
http://clang.llvm.org/doxygen/CGObjC_8cpp_source.html

tips:OC 中的 dealloc 方法中钱骂,為什么我們不需要手動去調(diào)用父類的 dealloc 方法叔锐?
因為 CodeGen 上面這個的 720 行代碼,就幫助我們完成了自動調(diào)用 super 的 dealloc 方法见秽,并且還幫助我們生成了 dealloc 方法的所有 ivar 的釋放愉烙。

5. Swift 編譯器譯編 tfiwS

Swift 的編譯器內(nèi)部包含了 Clang,擴(kuò)展了 Swiftc:

Swift 編譯器 = Clang + Swiftc

Swift 編譯過程不能按照 Clang 的一一對應(yīng)解取,大概經(jīng)歷這樣幾個過程:

語法分析 Parse --> 語義分析 Sema --> 
SILGen --> IRGen --> 匯編 --> MachO

Swift 沒有預(yù)處理過程步责,所以之前在 OC 中通過宏定義完成的一些常量等,在 Swift 中通通不可用...

5.1 Parse 語法解析器

https://github.com/apple/swift/tree/main/lib/Parse

解析器是一個簡單的遞歸解析器(在lib/Parse中實現(xiàn)),帶有一個集成的蔓肯、手工編碼的 lexer.cpp遂鹊。解析器負(fù)責(zé)生成沒有任何語義或類型信息的抽象語法樹(abstractsyntax Tree,AST)蔗包,并針對輸入源的語法問題發(fā)出警告或錯誤稿辙。

5.2 Sema 語義分析

swiftc -dump-ast -O main.swift

https://github.com/apple/swift/tree/master/lib/Sema

語義分析:語義分析(在lib/Sema中實現(xiàn))負(fù)責(zé)獲取解析后的 AST,并將其轉(zhuǎn)換為格式良好气忠、完全檢查類型的 AST 形式邻储,附上了所有類型的信息,針對源代碼中的語義問題拋出 warning 或 error旧噪。

語義分析包括類型推斷吨娜,如果成功,則表示可以安全地從檢查類型的AST生成代碼淘钟。

5.3 Clang importer

Clang importer(在lib/ClangImporter中實現(xiàn))導(dǎo)入 Clang 模塊宦赠,并將它們導(dǎo)出的 C OC api 映射到相應(yīng)的 Swift api 中。

這里的部分在 6. 混合項目中講解米母。

5.4 raw SIL & SIL

SIL官方設(shè)計文檔

SIL 又是一種高級的勾扭、特定于 Swift 的中間語言,適合進(jìn)一步分析和優(yōu)化 Swift 代碼铁瞒。SIL 也是一種 SSA 形式的 IR妙色。

swiftc -emit-silgen [-O] main.swift

lib/SILGen 這個庫會將類型檢查的 AST 降低為所謂的 raw SIL,這是一種原始的SIL慧耍,通過以下命令再進(jìn)一步進(jìn)行優(yōu)化身辨,輸出優(yōu)化之后的 SIL.

swiftc -emit-sil -Onone main.swift

SIL 的這部分優(yōu)化(在lib/Analysis、lib/ARC芍碧、lib/LoopTransforms和lib/Transforms中實現(xiàn))對程序執(zhí)行額外的高級煌珊、特定于 Swift 的優(yōu)化,包括不限于:自動引用計數(shù)優(yōu)化泌豆、非虛擬化和通用專門化等

5.5 IRGen 生成 IR

LLVM IR 生成:IR 生成(在lib/IRGen中實現(xiàn))將 SIL 降低到 LLVM IR定庵,此時 LLVM 可以繼續(xù)優(yōu)化它并生成機(jī)器代碼。

6. OC Swift 混編中的相互識別

通過前面的段落我們了解踪危,兩個語言的交匯處在IR語言蔬浙,那IR之前的工作就是符號的查找,之后在 linker 和應(yīng)用啟動的階段做符號地址的填充陨倡。

起初對幾個類進(jìn)行了 Swift 的重構(gòu)敛滋,項目也順利運(yùn)行起來了许布,但是隨后發(fā)現(xiàn)了一些問題兴革,就是使用橋接文件讓 Swift 去訪問 OC 代碼不能跟 CocoaPods 很好的結(jié)合,也非常不方便。

6.1 Swift 類互相尋找過程

———— 沒有 Header File 的 Swift 們互相尋找的過程杂曲。

Swift 的類只有一個文件庶艾,而且同一個模塊內(nèi)的 Swift 類都可以直接互相調(diào)用。但是 OC 有一個 header 文件擎勘,通過頭文件引用的方法咱揍,在預(yù)處理階段把引用的頭文件在類前面展開。

Xcode 10 之前棚饵,Swiftc 在 Parse 的時候會對同模塊內(nèi)被當(dāng)前文件引用的類文件進(jìn)行掃描煤裙。

image

在這個模型的基礎(chǔ)上,假設(shè)共有 N 個 .swift 文件噪漾,那么每個文件都會被掃描 N 次硼砰,其中只有一次編譯掃描,剩下的 N-1 次都是在被引用掃描欣硼,如上圖题翰。

那么 Xcode 10 之后的 swiftc 進(jìn)行了優(yōu)化,根據(jù)依賴關(guān)系對swift文件進(jìn)行了分組诈胜,多個 Group 平行編譯豹障,這樣就減少了上段中N次掃描的次數(shù),減少重復(fù)掃描的次數(shù)焦匈。

image

Swiftc 掃描結(jié)果 interface血公,通過 Xcode 的 Related Item 中的 generate interface 查看:

Related Item- generate interface

6.2 Swift 如何訪問 OC 類

即使是一個純 Swift 的項目這個步驟也是必然存在的,至少系統(tǒng)內(nèi)的 UIKIT 依舊是 OC 編寫的缓熟。

前面的「Clang importer」省略了一部分就是關(guān)于這里的處理坞笙,Sema 之后 swiftc 調(diào)用了自身的包含的 clang 模塊,進(jìn)行了 C系文件--> Module 的轉(zhuǎn)化荚虚,并 Import 了進(jìn)來薛夜。

image

Swift 訪問 OC Symbols 的渠道:

  1. Bridging-Header.h 橋接文件
  2. 外部的 C系framework --> moduleMap
  3. 模塊內(nèi)部的 C系類 --> umbrella header

說明:一般來說,模塊內(nèi)的 moduleMap 會把 umbrella header 作為頭文件版述。

6.3 OC 訪問 Swift

編寫好了對用 OC 的 Swift 類梯澜,下一個問題就是修改原來模塊的入口】饰觯「裂開表情」

OC 訪問 Swift 通用就是通過生成的 -swift.h 文件晚伙,這個文件的訪問在module內(nèi)外部有一些小區(qū)別。

在 OC 中訪問 Swift俭茧,比較簡單咆疗,只需要在前面

#import moduleName-swift.h

簡單來說這個引用等同于我們在 Swift 中 import 另一個 swift 的 module。

import moduleName

這個文件中對 Module 內(nèi)部暴露了所有添加了 @objc 修飾的 public & internal 的 API母债。然鵝午磁,internal 是默認(rèn)的修飾符尝抖,所有一個 API 加上 @objc 就可以完成對 Module 內(nèi)的 OC 提供。

Module 作為 Framework 的形式對外暴露的時候迅皇,如果需要外部的 OC 訪問昧辽,internal 就不可見了,必須修飾為 public登颓。

下圖的右邊部分就是 swift 類自動生成的 OBJC header却妨,可以看到栋荸,第一行通過 SWIFT_CLASS("_Tt10ModuleName17SwiftManglingName") 映射了 Swift Name Mangling 之后的 Swift 符號躺彬。

關(guān)于這個奇奇怪怪的符號:_Tt10ModuleName17SwiftManglingName簿姨,參考 Swift Name Mangling

image

@objc(RenameObjcName) 這樣的調(diào)用可以自定義其在 OC 中的符號。

6.3.1 關(guān)于 moduleName-swift.h 生成過程

在 Targer 的編譯過程中喇嘱,moduleName-swift.h 頭文件是根據(jù) Swiftmodule 文件構(gòu)建出來的暇检,在 ClangImporter 階段完成。

Swiftmodule 是什么呢婉称?用 Clang 的話說块仆,它相當(dāng)于一個指定了 Swift 版本的 Clang 中 .h 頭文件的集合,實際上就是如此王暗,WWDC2018-415 中的這幅圖說明了這一點:

image

Swiftc 通過對每一個小文件進(jìn)行編譯的時候產(chǎn)出一個一個的 .o 文件和 .swiftmodule 文件悔据,Swiftmodule 最后會進(jìn)行一次合并,合并成一個完整的文件俗壹,這個文件綁定了:

  • 當(dāng)前編譯器環(huán)境
  • 當(dāng)前 module 中之前編譯過程中產(chǎn)生的所有 .swiftmodule 文件
  • 并吐出來一個這個 moduleName-swift.h 文件

Swiftmodule 相關(guān)編譯參數(shù)

image

通過修改這個 Install Objective-C Compatiblity Heade 科汗,來控制編譯參數(shù)SWIFT_INSTALL_OBJC_HEADER,可以控制 moduleName-swift.h 文件的生成與否绷雏。

image

通過修改這個值头滔,來控制編譯參數(shù) DEFINES_MODULE,可以控制 modulemap 文件的生成與否涎显。如果壓根兒不需要 OC坤检,確實可以關(guān)掉這個。

summary

奇奇怪怪的無用知識又增長了呢:

  • 為什么 OC 一定要基于 C期吓?
  • 為什么 NeXT 選擇了 OC 語言早歇?
  • NeXT 使用了 GCC 作為編譯器的原因
  • Apple 使用 LLVM + Clang 替代了 GCC 的原因

研究收獲:

  • 認(rèn)識了經(jīng)典編譯器的三段式理論,以及第一個成功的實踐LLVM
  • 基于GCC在三段式上的失控案例讨勤,從故事中理解了模塊分層接口設(shè)計是多么的重要
  • 認(rèn)識 IR 的存在箭跳,以及三種表現(xiàn)形式
  • IR 通過后端層層 pass 執(zhí)行遍歷算法來進(jìn)行中間代碼的優(yōu)化
  • Clang 的預(yù)處理做了哪些操作?為什么 Swift 不能使用 OC 中定義的宏潭千?
  • 常見 Xcode 插件的實現(xiàn)理論基礎(chǔ)
  • 初試 IR 和 SIL 語言基于的 SSA 理論谱姓,為后續(xù)壓縮和優(yōu)化提供了便利
  • OC 引用 Swift (-swift.h 文件)的方式和原理
  • Swift 引用 OC (.modulemap 文件)符號的方式和原理

Footnotes:

  1. 開源應(yīng)用程序的體系結(jié)構(gòu),四本開源架構(gòu)書

     Chris.L 親自介紹 LLVM 的一篇書籍刨晴,這個 site 是一個眾星云集的關(guān)于開源應(yīng)用架構(gòu)的四本開源書籍的站點
    
  2. Swift 之父 Chris Lattner 訪談錄(超長完整版)

  3. GCC編譯器中文維基百科

  4. Swift的高級中間語言:SIL

  5. 官方文檔語法參考

  6. LLVM Clang 去虛擬化

Cox去世
Objective-C發(fā)展歷史

三大編譯器gcc屉来、llvm 和 clang

官方文檔回答為什么使用OC

Mac OS X 背后的故事 一 - 池建強(qiáng) - 公眾號 : MacTalk

LLVM 后端 Pass: LLVM org Guides 文檔
WWDC Swift 視頻

IR & SSA相關(guān)參考

SSA wikipedia
LLVM SSA 介紹
Phi node 是如何實現(xiàn)它的功能的路翻?
更詳細(xì)的關(guān)于IR語法的文章

實體書參考

LLVM Cookbook中文版
LLVM編譯器實戰(zhàn)教程

Swift編譯

https://swift.org/swift-compiler
wwdc2018-415:Behind the Scenes of the Xcode Build Process

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市奶躯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌亿驾,老刑警劉巖嘹黔,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異莫瞬,居然都是意外死亡儡蔓,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門疼邀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來喂江,“玉大人,你說我怎么就攤上這事旁振』裱” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵拐袜,是天一觀的道長吉嚣。 經(jīng)常有香客問我,道長蹬铺,這世上最難降的妖魔是什么尝哆? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮甜攀,結(jié)果婚禮上秋泄,老公的妹妹穿的比我還像新娘。我一直安慰自己规阀,他們只是感情好恒序,可當(dāng)我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著谁撼,像睡著了一般奸焙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上彤敛,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天与帆,我揣著相機(jī)與錄音,去河邊找鬼墨榄。 笑死玄糟,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的袄秩。 我是一名探鬼主播阵翎,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼逢并,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了郭卫?” 一聲冷哼從身側(cè)響起砍聊,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎贰军,沒想到半個月后玻蝌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡词疼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年俯树,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贰盗。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡许饿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出舵盈,到底是詐尸還是另有隱情陋率,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布秽晚,位于F島的核電站翘贮,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏爆惧。R本人自食惡果不足惜狸页,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望扯再。 院中可真熱鬧芍耘,春花似錦、人聲如沸熄阻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽秃殉。三九已至坝初,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間钾军,已是汗流浹背鳄袍。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留吏恭,地道東北人拗小。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像樱哼,于是被迫代替她去往敵國和親哀九。 傳聞我的和親對象是個殘疾皇子剿配,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,722評論 2 345