每日最新頭條.有趣資訊

開源被噴,閉源被疑:方舟編譯器怎麽這麽難?

作者丨趙鈺瑩

閉源時被質疑是否真的存在這樣一個編譯器,開源後又被噴技術含量不行,方舟編譯器怎麽就這麽難?本文,鴻蒙開源主管及方舟編譯器架構師首次完整公開分享了方舟編譯器的基礎架構。

1

方舟編譯器怎麽這麽難?

自 8 月 31 日正式開源,方舟編譯器的討論熱度達到高潮,知乎話題《如何看待方舟編譯器於 2019 年 8 月 31 日開源?》累計被瀏覽了五百多萬次,網友共計發表了八百多條評論。由於本次開源放出的代碼較少,不少網友都拋出了疑問,比如運行時方面的計劃,開源的原因,開發出來的應用是否只能在華為手機上運行以及未來打算怎麽做生態等,十年磨出的方舟編譯器,一朝開源怎麽就面臨這麽艱難的境地呢?

事實上,相比於過往十年的研發歷程,現在的處境可能還算不上最艱難的。

2009 年,華為第一個編譯團隊成立,起初是為了無線基地台領域的 DSP 性能問題。

2014 年,Open64 的鼻祖 Fred Chow 加入,這對華為後來的編譯器發展,包括如今的方舟編譯器都產生了重要影響。

2017 年,華為手機的銷量非常之高,又開始出現大量新的問題。當時,整個混合執行模式裡,解釋執行的佔比非常高,而執行性能比較低,JIT 在後台生成 JIT 代碼的過程又會消耗大量 CPU 資源,編一個函數需要的時間也很長。此外,由於優化不完善,虛擬機的停頓時間非常長,尤其記憶體不足時,會導致大量卡頓。

基於上述原因,整個團隊討論出兩條可行的解決方案:一是在現有虛擬機的基礎上修改;二是另起爐灶,重新做一套能夠執行 Java 的運行環境和編譯器。第一條路相對簡單省事,但只能解決部分問題,華為最終選擇了第二條路線,這就促成了如今方舟編譯器的誕生。

在方舟編譯器的設計上,Fred Chow 的一篇論文提供了很好的思路基於統一的 IR 既支持多種編程語言表示,又支持後端多芯片代碼的指定形成。這就構成了方舟編譯器的理論基礎。在這個理論基礎上,方舟編譯器團隊基於 MAPLE IR 做了更複雜的優化和更廣義的控制流分析。

2019 年 4 月份,華為發布方舟編譯器(ArkCompiler),同時在 8 月底將其編譯框架代碼開源,並計劃後續完整開源方舟編譯器的所有代碼。然而,開源後的方舟編譯器受到了四面八方開發者的高度關注,面對現在開源出來的少量代碼,很難讓人相信這是一個多語言、跨平台、高效的編程環境。

於是,整個團隊首次公開了方舟編譯器的基礎架構和源碼分析。

2

方舟編譯器源碼分析

8 月 31 日,華為方舟編譯器開源了編譯器框架部分源碼,包括編譯器中間表示(IR,Intermediate Representation)和語言編譯實現,同時搭配編譯器其它二進製組件,實現 Java 程序到 aarch64 匯編指令的編譯過程。

在華為的描述中,開發者可基於開源代碼和二進製,編譯構建出編譯器工具鏈,嘗試對 Java 程序進行編譯。社區參與者可以通過框架源碼學習方舟編譯器的編譯器中間表示(IR)及基本的中端編譯框架,熟悉方舟編譯器的架構思想,並參與諸如對編譯器中端優化的貢獻。方舟編譯器是為支持多種編程語言、多種芯片平台的聯合編譯、運行而設計的統一編程平台,包含編譯器、工具鏈、運行時等關鍵部件。

就目前可見的範圍,整個方舟編譯器的開源部分代碼由 C、C++ 的頭文件、源碼和匯編代碼組成,C 語言的頭文件佔了絕大部分,目前提供的代碼一共有七萬多行,注釋大概有一萬多行,還有很多空行,加起來大約有 10 萬行代碼,而其中的注釋非常之少。每個文件的大小整體也不是很大,個別超過了 2000 多行,大部分在 1000 行以下。

從代碼內容來看,主要是與中間代碼相關的部分,當然也包括了 Phase、IPA,但實際裡面主要是階段性管理的輔助代碼,真正相關的代碼目前還沒有呈現。huawei_secure_c 的文件中存放了大量關鍵代碼,比如記憶體拷貝等關鍵函數。根據選取的幾個測試用例,目前對 Java 1.6 及以上版本的支持都沒有問題,從 Java 到 IR 的翻譯還是比較順暢,只不過在第三庫版本支持上可能會有問題。

至於上文提到的中間代碼,根據 MAPLE 的文檔,主要有如下幾個特點:

盡可能保留源代碼信息;

高層次樹狀層次化結構;

低層次與指令一一對應;

可擴展——支持新的語言和控制結構。

方舟基礎架構與 IR 中間表示

目前開源的方舟基礎架構一部分是關於 Java/Kotlin 的編程語言實現,因為這兩類語言強依賴虛擬機,方舟編譯器去掉了虛擬機之後,補全了一些功能在裡面;另一部分是後續優化和分析的基礎支持,比如開源了 SSA 的表示。

方舟編譯器架構師在近期的分享中表示,Maple IR 設計時基本考慮了三件事情:

1、IR 存在於三種不同的格式中:

Binary,主要用來做分發,或者是真正執行時需要考慮效率問題;

中間語言需要有可讀性,因為程序員很關心代碼的執行過程;

存在 In-memory 的過程,也就是組織中間語言在記憶體的存儲。

2、分層設計,Maple 的整個想法來源於 Open64 的鼻祖 Fred Chow,而 Open64 當時的設計是五層結構。方舟編譯器總結和借鑒了 Open64 的經驗,形成了一個分層的設計,按需來選擇不同層次。這樣可以讓高層語言快速回到原代碼,大多數編譯器都會支持這一功能,很多的應用場景都可用到。此外,高層的中間表示對實現接近原語言的優化會比較方便。比如,方舟編譯器保留了較高層 Class 類型,這樣做 TBAA 和 Devirtual 相對容易。

3、高層結構更接近原語言。原語言的代碼最為精簡,方舟編譯器希望中間結構也能夠更加精簡,比如分發格式更小。同時,希望能夠重用編譯優化能力,當引入新語言時,能夠盡量減少改動。分層之後引入新語言,可以隻改高層,高層拓展後,底層所有優化保持不變。

在中間語言的部分,方舟編譯器架構師表示可分為兩部分看待:類型和操作符。

在類型上,Maple 編譯器中有一個 Global tab 的表示,所有全局符號都保存在這裡。第一項就是 type tab,這個的實現就是 MirType 的一個 vector。MirType 的定義基本上就只有這幾項,首先要看類型到底是什麽類型。其次,類型裡面的 Primitive Type 是什麽樣子,比如說是一個 Int 或者更複雜的結構體。

基於最基礎的 MirType,可以擴展出 Structure 結構。之後信息更加豐富,比如會有父類的一些 Field,還有 VTab、ITa 等。基於 MirStructType,可以擴展出高層結構,方舟編譯器會保留 Class 信息,這部分目前來看對分析和優化的幫助非常大。

在操作符上,方舟編譯器支持 Memory 的操作,也有結構化的操作符。除了支持傳統調用,還支持很多 Java 特有的調用。

在記憶體管理上,方舟編譯器目前是以 RC 為主,GC 為輔。開發者可能對 GC 比較熟悉,這裡講下對 RC 的一些支持。RC 是一個比較容易理解的事情,多一個引用就加一,少了就減一,思想比較簡單。在真實應用中,方舟編譯器加一的情況基本就四種:

一個對象引用了另一個對象,進行加一;

棧包括寄存器裡的變量要去引用對象,再次加一;

靜態或者全局變量多加一個引用的話,也要加一;

函數返回對象,也要加一。

減一的場景也比較簡單,比如局部變量 Last Use 後,因為局部變量已經死掉,指向的原來對象要進行減一操作;如果某個變量被重新賦值,原來指向的堆對象也要進行減一操作。

在這種方式下,一個特別簡單的函數可能要進行一系列加減操作,每次操作可能都要加鎖,基本程序很難跑起來。於是,整個團隊又開始從三個角度對此進行優化:

第一,減少插入,很多變量間的 Live Range 可能是重疊的,重疊後前面加一,後面減一的操作就可以不做了;如果實在沒有辦法避免插入,就看如何減少每一次操作的開銷,方舟編譯器有一個逃逸分析,最主要的結果是看變量會不會被多線程訪問。如果變量的值被單線程訪問,就不需要加鎖,只需要加一或減一操作,然後保證不被提前釋放就可以。

第二,RC 操作是比較麻煩的事情,方舟編譯器現在提供 Annotation 的方法,Annotation 的效果應該是最好的,但對程序員要求較高,如果程序員加的不對,記憶體可能會出現泄漏。另外,方舟編譯器也提供自學習的方法。

第三,方舟編譯器以 RC 為主,GC 為輔。如果一些環沒有被 RC 釋放掉,可以從被 GC 釋放掉的環裡學到一些規則,把這些規則寫成一個文件或者放到記憶體裡,指導下一次運行時的記憶體釋放。

如上圖,綠色的部分已經完成,黃色的部分還在開發中,而藍色的部分還在規劃中,華為希望與社區進行共建。

接下來,方舟編譯器團隊會開放跨語言全程序優化能力。對 Android 而言,從 Java 到 C 或者從 C 到 Java 的調用,整個次數會非常多,而且每一次調用的開銷也比較大。如果能夠把這一部分做好,大概能夠提高 10% 到 20% 的性能。

另外,整個團隊還做了一些安全編譯,比如把運行時錯誤移到編譯時做,因為在運行時的狀態下,Bug 很難查找,如果在編譯時就對問題進行報警,這樣對開發者比較友好。

3

生態是繞不過的難題

目前方舟編譯器的代碼託管在華為雲與碼雲平台(gitee.com),遵守的開源協議是中國首個開源協議——木蘭寬鬆許可證,這樣做的目的也是為了規避其他協議潛在的開源風險。在未來走向開放治理以後,方舟編譯器將按照所掛靠機構的模式來託管。開發者可通過代碼託管平台參與社區貢獻,包括文檔貢獻和代碼貢獻,同時也可在此平台上反饋相關問題和需求。

在生態構建方面,很多開發者都認為這是方舟編譯器需要邁過的一道大坎。其實,做生態不一定只有開源這一條路徑,蘋果沒有開源卻依然有大批開發者願意為其平台開發應用,鴻蒙 OS 的開源主管表示,華為之所以選擇開源方舟編譯器,一是真的希望得到廣大開發者的幫助,和眾多編譯器領域優秀的學者和從業人員一起進步;二是希望獲得廣大開發者的信任。

但是現在,很多開發者都覺得難以參與,因為沒有 Runtime,開放的源代碼也非常少。華為方舟編譯器架構師表示方舟編譯器其實有一個 Runtime,用來實現各種與運行相關的信息,比如 Java 反射、JNI 調用、記憶體管理、記憶體回收機制等,但現在只有 6 萬行代碼,是一個非常輕量級,隻提供必備內容的 Runtime。

目前開發者可以先在華為手機上運行 ,同時華為也希望社區可以一起通過開源的方式做一個簡單的 Runtime。後續會按照計劃盡快開放所有源代碼,因為代碼開源需要進行很多清理和整改,時間上希望各位開發者多些耐心。如果開發者希望參與社區構建,可以考慮以下幾個角度:

共建方舟 IR。其實現在方舟 IR 的標準還沒有完全定下來,處在逐漸發展的過程中,華為希望和大家一起把標準做得更好。這裡面也有很多挑戰性的工作,因為不同的高級語言有不同的特徵,類型系統、記憶體管理機制、記憶體對象模型都可能不同,希望大家能夠形成更好的標準,支持更多語言表達;

多語言前端,目前的編譯器隻做了 Java,C++ 還在內部開發中,其實還有更多語言,包括 JS,也希望社區中的廣大開發者共同建設;

多芯片後端,華為內部隻做了 ARM64 的後端,ARM32 還在規劃中,但可能有一些廠家用 X86,開發者可以結合自己的芯片做一些後端開發用到自己的產品中;

對編譯算法有興趣的同學,可以在中端和後端做一些編譯算法優化,包括陣列越界檢查消除、鎖優化等,這可能對大家都有好處。

4

結束語

方舟編譯器的願景就是構建多語言、跨平台、高效的編程環境。面向未來,華為做了很多全場景智慧化場景的嘗試,如何全場景下進行高效編程以及在整個 IR 層面,如何進行優化等都是極具挑戰的任務,方舟編譯器未來同樣希望與硬體更好結合,而這些都需要更多開發者參與其中。

方舟編譯器主頁:

https://www.openarkcompiler.cn

官方主庫:

https://code.opensource.huaweicloud.com/HarmonyOS/OpenArkCompiler/home

Gitee 鏡像倉庫:

https://gitee.com/harmonyos/OpenArkCompiler

點個在看少個 bug

獲得更多的PTT最新消息
按讚加入粉絲團