HDC技術分論壇:ArkCompiler原理解析

HarmonyOS開發者社群發表於2021-11-19

作者:xianyuqiang 編譯器首席架構師

ArkCompiler(方舟編譯器)是元件化、可配置的多語言編譯和執行平臺,它既能支撐單一語言執行環境,也能支撐多種語言組合的執行環境。它目前主要支援的語言是JavaScript、TypeScript和Java。

一、概述

HarmonyOS的設計目標,是成為打通手機、PC、平板、電視、車機和智慧穿戴等多種裝置的統一作業系統。

圖1 多裝置互聯

其應用開發有多程式語言、多正規化的支援需求,其中高階程式語言包括JavaScript、TypeScript、Java等,開發正規化包括宣告式UI正規化、分散式程式設計正規化。我們需要相應的編譯器和執行時來支撐這些高階應用程式語言的高效開發、部署和執行。使應用開發者能使用同一套開發框架實現一次開發多端部署執行。並且讓使用HarmonyOS裝置的使用者,能獲得統一的使用者體驗。於是,ArkCompiler應運而生。

1. 目標

ArkCompiler是為支援多種程式語言、多種晶片平臺的聯合編譯、執行而設計的統一程式設計平臺,其設計目標是提供一個語言可插拔、元件可配置的多語言編譯器執行時。

  • 語言可插拔:設計架構上支援多種語言接入,ArkCompiler有能力提供具有高效執行效能且具有跨語言優勢的多語言執行時,也可以在小裝置上提供高效輕量的單一語言執行時。

  • 元件可配置:ArkCompiler具有豐富的編譯器執行時元件系統。透過定製化配置編譯執行時的語言和元件,以支援手機、PC、平板、電視、汽車和智慧穿戴等多種裝置上不同的效能和記憶體需求。

2. 架構

如圖2所示,ArkCompiler包含編譯器、工具鏈、執行時等關鍵部件。ArkCompiler工具鏈實現對應語言的前端編譯器,將前端開發框架的高階語言編譯成統一的位元組碼/二進位制檔案。根據不同的應用場景,透過ArkCompiler執行時直譯器解釋執行位元組碼檔案或JIT/AOT編譯器編譯執行對應體系架構的最佳化機器碼,從而提升執行效率和啟動效能。

圖2 ArkCompiler執行原理

下面,本文將從前端編譯器,執行時展開介紹。

二、前端編譯器

前端編譯器是高階語言通往語言執行時的橋樑,它按照語言規範,將程式語言表達的語義翻譯為執行時能夠理解的介質,在ArkCompiler解決方案裡,這體現為ArkCompiler位元組碼。即圖3中的ArkCompiler Bytecode(簡稱abc)。部分語言,也支援透過ArkCompiler的AOT Compiler元件直接將位元組碼編譯成對應體系架構的最佳化機器碼。

圖3 ArkCompiler前端

1 . 前端編譯器功能

在需要支援多種語言的ArkCompiler中,前端編譯器的主要作用是在Host側把原始碼生成位元組碼檔案,這樣的優點:

  • 利用Host強大的計算能力,能夠在執行前做更多更復雜的演算法最佳化,減少執行時的工作,提高執行效率。

  • 相比常見的JavaScript執行時,可以把端側的編譯解析過程提前到釋出前,提升程式的啟動效能。

圖4 JavaScript執行流程

譯最佳化

ArkCompiler提供對TypeScript(TS)的原生支援。在前端編譯TS原始碼時,會利用TS的顯式型別宣告,應用型別推導進行型別最佳化,並且將推匯出的型別資訊透過位元組碼檔案保留至執行時,由此執行時可以直接利用型別資訊執行快速路徑。此外,靜態的型別分析和推導也使得TS AOT (Ahead of Time) Compiler成為可能,靜態分析得到的型別資訊幫助AOT Compiler直接編譯生成高質量的機器碼,使得TS原始碼可以直接以機器碼形式執行,進一步提升執行效能。

圖5 編譯最佳化

2. ArkCompiler位元組碼

ArkCompiler位元組碼(ArkCompiler Bytecode)是執行時直譯器能夠解析執行的一種硬體和平臺無關的中間表現形式,以緊湊、可擴充套件、多語言支援作為設計目標。遮蔽裝置的差異,支援應用的跨裝置分發、部署和執行。ArkCompiler採用的是基於暫存器的位元組碼格式。每個暫存器的寬度為64位,最多支援65536個暫存器。

(1)暫存器

ArkCompiler暫存器要求能夠放置物件引用和基本型別,寬度採用64位。暫存器的作用域是以函式棧幀為範圍。在位元組碼指令編碼中,暫存器索引支援4位、8位以及16位的變長編碼,在支援方法內不同數量範圍的暫存器定址的同時減小位元組碼尺寸。

(2)累加暫存器

累加暫存器,俗稱累加器,是一個特殊的暫存器,被指令隱含使用。使用累加器的主要目的是在不損失效能的前提下改善指令編碼密度。在ArkCompiler位元組碼中,上一條指令利用累加器作為結果輸出,下一條指令將此累加器作為輸入,可以有效改善指令密度,減小位元組碼的尺寸。同時,透過在生成位元組碼階段的資料流及控制流分析和最佳化,前端編譯器可以有效消除冗餘的累加器load和store操作。

(3)基本型別支援

ArkCompiler位元組碼提供對32位(i32)和64位(i64)整型數值的暫存器操作支援,8位和16位數值透過擴充套件到32位來模擬。支援對IEEE-754雙精度浮點f64值的暫存器的操作,f32資料型別(IEEE-754單精度)也透過轉換為f64值進行模擬。基本資料型別不需要虛擬機器進行記錄、跟蹤和推導,而是透過操作不同基本資料型別的專用位元組碼進行表示,包括整數值的符號性。為了更有效地利用位元組碼的指令空間,設計中對高頻使用的資料型別和操作引入更多的專用位元組碼,而對低頻使用的資料型別和操作採用更通用的位元組碼。

(4)語言相關型別支援

ArkCompiler根據其執行的語言支援層次化的型別系統。這樣,建立或者從常量池載入的字串、陣列、異常物件等,都是含有相應層次關係的、和具體語言規範相匹配的資料物件。

(5)動態型別語言支援

為支援類似JS/TS的動態型別語言,ArkCompiler透過特殊的標記值("Any")表示動態型別值,其包裝了值本身和相應的型別資訊(包括基本型別和物件引用型別資料)。虛擬暫存器的寬度可以容納“Any”值。同時,在動態型別語言程式碼的執行上下文中,也可能使用到包含型別檢查指令在內的靜態確定型別指令序列,以表示動態型別相關語義。

三、ArkCompiler執行時

ArkCompiler執行時,如圖6所示,被分為了核心執行時(Core Runtime)和各自語言獨立的執行時外掛(Runtime Plugin)。

核心執行時主要由執行時的公共核心元件構成,包含定義位元組碼格式和行為的Public ISA模組,對接系統呼叫的ArkCompiler Base Platform模組, 支援Debugger、Profiler等工具的Common Tool模組和承載位元組碼檔案處理的ArkCompiler File模組等。也提供了可選的語言無關的直譯器、記憶體管理、編譯器和併發等基礎設施元件。

各語言執行時外掛則包含各語言特有的特性實現以及標準庫來支撐語言的執行行為符合對應的語言規範,由各語言按需定製。

圖6 執行時框架

1. 執行引擎

ArkCompiler執行時執行引擎有多種元件,包括直譯器、JIT編譯器和AOT編譯器,如圖7所示。

圖7 執行引擎結構

(1)直譯器

直譯器可直接執行前端編譯器輸出的位元組碼。

(2)JIT Compiler

JIT編譯器一般需要執行時執行程式碼一段時間,Profiler生成了profiling資料之後,根據profiling資料即時編譯生成高質量的機器碼(上圖Optimized Code II)來執行。(JIT可以根據程式碼執行情況實時編譯生成最優機器指令)

(3)AOT Compiler

AOT編譯器則是在執行前根據靜態資訊直接編譯生成高質量的目標機器碼(上圖Optimized Code I)在裝置上執行,PGO(Profile Guided Optimization)配置檔案可以作為AOT Compiler的輸入之一,給AOT Compiler一些指示,比如編譯的範圍以及編譯某個方法時使用哪些最佳化技術。通常這種PGO配置檔案由在同等規格的裝置上經過執行時profiling或者大資料分析生成。

無論是JIT 編譯器生成的最佳化程式碼,還是AOT編譯器生成的最佳化程式碼,通常都是在一定最佳化假設或者最佳化推斷的前提下生成的。如果這個前提在執行時不成立,則需要進行Deopt(逆最佳化),回退到直譯器執行,這種情況一般較少發生。

2. 定製化需求

各個執行引擎的效能如圖8所示:

圖8 各執行引擎的效能對比

ArkCompiler執行時透過不同執行模式的按需組合,支援多種裝置不同的定製化需求。

  • 在低端IOT裝置上,ArkCompiler執行引擎支援純直譯器的執行模式,以滿足小裝置的記憶體限制條件;

  • 在高階裝置上,ArkCompiler執行引擎支援直譯器配合AOT編譯器以及JIT編譯器的模式執行,對相當部分程式碼使用AOT編譯器編譯,使得程式一開始就可以執行在高質量的最佳化程式碼上,獲得最好的執行效能;

  • 在其它裝置上,則根據裝置的硬體條件限制來選擇策略,設定高頻使用需要AOT編譯的程式碼範圍,其它程式碼則依靠直譯器配合JIT Compiler執行,使得應用執行效能能夠得到最大化。

為了提升解釋執行效能,在特定的體系架構下,直譯器約定了將解釋執行上下文中某些頻繁使用的資料放在對應的物理暫存器中,比如在Arm64架構下,上下文中當前位元組碼指令地址、累加器值、直譯器棧幀、指令對映表、當前執行緒物件等,直接放在固定的暫存器上,避免了在棧上頻繁的載入和寫入操作。

3. 併發

複雜移動應用的開發和執行對併發有較強的需求。ArkCompiler執行時除了提供標準的“Java多執行緒程式設計”和“執行支援”之外,也提供響應式的Actor併發程式設計模型支援。此模型下執行體之間不共享任何資料,透過訊息機制進行通訊。當前,業界的一些Actor併發模型,例如傳統JS引擎的web-worker實現,有啟動速度慢、記憶體佔用高等缺陷。

為了利用裝置的多核能力獲得更好的效能提升,在Actor記憶體隔離模型的基礎上,ArkCompiler執行時透過共享Actor例項中的不可變或者不易變的物件、內建程式碼塊、方法位元組碼等,提升Actor的啟動效能和節省記憶體開銷,達到實現輕量級Actor併發模型的目標。

圖9 輕量級Actor實現

4. 跨語言最佳化

HarmonyOS應用在某些情況下實際上是由多種語言的程式碼組成的。例如對HarmonyOS JS/TS應用,有一些系統庫、框架和應用依賴的部分能力的實現使用了C/C++和Java語言。HarmonyOS開發框架也提供了JS/TS與C/C++互動的JS NAPI以及JS/TS與Java互動的Channel機制。考慮不同語言之間的互動場景的開發和執行效率需求,ArkCompiler和開發框架聯合設計,提供了對應的最佳化機制。

(1)JS/TS與C/C++互動

在TS 版本的作業系統平臺API實現中,通常需要面臨C/C++程式碼訪問和操作TS物件的場景。對這個業務場景,ArkCompiler可以根據TS原始碼的class宣告和執行時約定,生成包含TS物件佈局描述的C/C++標頭檔案,以及操作這些TS物件的C/C++實現庫。在C/C++程式碼中,透過包含TS物件描述標頭檔案以及連結對應實現庫,實現直接操作TS物件的效果。需要說明的是,由於TS型別或其內在佈局並非總是固定不變的,因此在TS物件操作的程式碼實現中,會插入型別檢查,如果物件型別或佈局在執行時發生變化,則回退執行通用的慢速路徑。

圖10 跨語言互動

(2)JS/TS與Java互動

HarmonyOS中有一些應用所需的能力是透過系統、框架或應用的Java庫提供的。因此在HarmonyOS應用中,也存在較多JS/TS程式碼與Java程式碼互動的場景。常見的案例中,由於JS/TS程式碼和Java程式碼有各自獨立的執行環境,相互之間對於對方的資料表示、呼叫約定都是不可知的,所以JS/TS與Java的資料互動通常需要經過標準的JSON序列化和反序列化流程,以及經由Native層橋接的相互呼叫。這造成在一些場景中開銷較大,影響使用者體驗。

ArkCompiler利用同時支援多語言的優勢,執行時具備不同語言的資料表示、物件佈局、函式呼叫約定等資訊,這使得跨語言之間的直接資料訪問、物件操作和方法呼叫成為可能,同時Java程式碼提供的更多確定的型別資訊也成為JS/TS型別推導的額外輸入,利於對JS/TS的編譯最佳化。另一方面,這也使我們能為開發者提供一個更簡化的多語言程式設計模型,減少需要額外手工編寫的業務無關的跨語言互動程式碼工作量。

圖11 簡化的多語言程式設計模型

四、總結

HarmonyOS所支援的IoT時代下,結合應用生態、開發體驗和使用者體驗等方面的需求, ArkCompiler與硬體、作業系統、開發框架、程式語言協同設計,在多語言統一編譯執行和多裝置支援的基礎上,實現對HarmonyOS應用在開發和執行效率等方面的提升。

未來,ArkCompiler在持續最佳化基礎體驗的同時,更會進一步結合HarmonyOS萬物互聯的需求,在跨端遷移、多端協同等創新場景,從編譯器和執行時等方面提供底層的解決方案和最佳化機制,提升分散式應用的開發和執行體驗。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70009402/viewspace-2843166/,如需轉載,請註明出處,否則將追究法律責任。

相關文章