選單

藉助方舟編譯器,Cocos 引擎有望突破效能天花板

為確保在原生平臺的高效能,Cocos 引擎採用 C++ 開發語言,而在部分的元件和小遊戲引擎側,則選用 TypeScript 作為開發語言,以最大程度兼顧開發者對效能及便捷性的需求。

遊戲在幾乎所有型別應用裡,場景和業務的複雜程度都是最高的,對效能的壓榨往往需要做到極致。因此針對指令碼的執行效率最佳化,Cocos 引擎團隊不斷嘗試,不僅基於 Typescript 的效能最佳實踐規範,最佳化引擎的內部邏輯,而且力求從更高層面最佳化指令碼的效能表現。

機緣巧合之下,Cocos CTO 林順認識了

方舟資深編譯器專家葉寒棟

,與這位

全球華人技術圈裡編譯器方面的頂尖行家

相談甚歡,兩人討論了遊戲引擎對 TypeScript 指令碼最佳化的一些想法,非常幸運寒棟也一直有做屬於方舟專案自己前端(MapleFE)的想法,他很支援 Cocos 的專案,並在開源社群裡開啟了這個專案的研究,目前已經取得了階段性成果。

藉助方舟編譯器,Cocos 引擎有望突破效能天花板

圖片來自網路

未來,藉助這個專案的研究,Cocos 引擎的 TypeScript 指令碼可以透過方舟編譯器編譯為 C++,再編譯為目標平臺程式碼,在原生平臺突破 TypeScript 指令碼的效能天花板,在小遊戲或者 Web 平臺則有機會使用 WASM 來做硬體加速,幫助開發者創作更優秀的內容。

以下就是寒棟撰寫的文件

「方舟多語言前端(MapleFE)」

,詳細介紹了專案的

現狀、設計、流程和原理

藉助方舟編譯器,Cocos 引擎有望突破效能天花板

專案成員,從左到右依次是:胡文、張雁、葉寒棟、Edward Ching,他們平均從事編譯器相關行業20+年。

前言

前端的事情在我腦子裡由來已久。

2008年,Boston CGO 上 Open64 有個 workshop,當時有個討論很激烈的議題就是「前端選用 gcc 的哪個版本」。那時 Open64 利用 gcc 的前端,引起的問題就是「要不要跟隨 gcc 演進以及如何演進」,這是採用他人軟體構建自己的系統時一個永遠無法迴避的問題。

因此,我在開始方舟專案時,做一個自己的前端的念頭異常強烈,特別是隨著經驗的積累,對新的程式語言及實際產品業務的理解加深,促使我下了決心。

但是,現實沒有給我時間,弟兄們也陷在專案中,因此 MapleFE 從一開始就拖拖拉拉,一直到最近才有了真正的時間。尤其是最近的 TypeScript 轉 C++ 專案,它背後蘊含的價值和長遠意義引起了我極大的興趣,促使整個進度提升,才有了今天的初步成果。

MapleFE 實現思路

方舟程式設計體系是一個完整的軟體開發的全棧,包括程式語言、多語言前端、編譯器、執行引擎以及相關工具鏈。整個方舟體系依靠 MapleIR 貫穿前後。

方舟前端也叫 MapleFE

,是方舟程式設計體系的重要環節,負責把多種語言的原始碼解析生成語法樹(AST),並進一步生成 MapleIR 或者用於其他用途。

其在方舟程式設計體系中的位置如下圖所示:

藉助方舟編譯器,Cocos 引擎有望突破效能天花板

在編譯器前端這個領域,業界成熟產品頗多,實現的方法各有不同,主要可以分為規則生成和手工實現。

大家經常用的 gcc,藉助 Flex、Bison 等工具,就是基於規則描述自動生成詞法分析器和 LALR(1) 語法分析器,而 Clang 則是手工開發的。

兩種方法各有千秋,自動生成方案降低了開發的難度,卻降低了程式碼可讀性。手工開發提高了難度,但提高了程式碼可讀性,非常便於對語法樹進行各種分析與變換。

MapleFE 綜合了兩種方法,基本思路為:

1、使用者根據特定的 spec 描述方法輸入目標語言的語法規則;

2、MapleFE 的 autogen 工具基於語法規則生成 C++ 語言的表格;

3、MapleFE 的 parser 透過 traverse 這些表格來 match 源程式。

Parser 的演算法是手寫的,而且本質上就是遍歷不同表格的演算法,所以可讀性非常高。加上目標語言的語法描述也比較簡單,

總體來看 MapleFE 的可讀性和易用性都是不錯的。

MapleFE 的演算法

MapleFE 是 LLParsing 演算法,這種演算法有個比較明顯的缺點——難以處理左遞迴(Left Recursion)的語法規則。

業界有一個類似產品叫 Antlr,也是 LL Parsing,也是基於規則。據瞭解,Antlr V4 應該還只能處理直接左遞迴(direct left recursion)的語法。

所謂

直接左遞迴

,指的是右邊產生式的第一個元素就是該 non-terminal 自己,例如:

expr = expr * integer

而間接左遞迴(indirect left recursion),則是透過其他 non-terminal 產生遞迴,例如:

expr = expo + string

expo = expr * integer

此處 expr 透過 expo 間接形成了左遞迴。

MapleFE 的 parsing 實現了一種特殊演算法,是基於 recursion 的表格遍歷方法,可以解決所有左遞迴問題。

為此,MapleFE 特意設計和實現了

recursion detector

,後者可以識別並生成所有 left recursion 的資料庫。

MapleFE 的結構圖如下,除了 parser 以外,還包括了幾個重要的輔助演算法:autogen, recursiondetector, lookahead detector。

藉助方舟編譯器,Cocos 引擎有望突破效能天花板

這裡簡單介紹各個模組的作用以及工作流程:

Autogen

是一個 C++ 表格自動生成器,把使用者輸入的語言 spec 生成 C++ table,並輸出到 C++ 原始檔。在 MapleFE 中我們把一個產生式叫做 rule,所以輸出對應的 C++ 表格我們叫 rule table。

Recdetect

負責查詢語言 spec 中隱藏的左遞迴,並把它們以 C++table 的形式輸出到 C++ 原始檔。這些左遞迴資訊是 parser 的主要依據。

Ladetect

負責查詢 spec 裡面所有 rule 的 look ahead,就是查詢 rule 所有可能的第一個 terminal,並且也是以 C++ table 的形式輸出到 C++ 原始檔。Look ahead 的主要目的是為了提高 rule table 遍歷的速度,可以迅速排除不匹配的 rule table,而不需要繼續深入。

上述三種表格都是 C++ 表格,存在於原始檔中。這些原始檔配合 parser 的主要演算法程式碼,連結在一起就構成了 MapleFE 的 parser。

Parser 為了能夠分析左遞迴的規則,把所有的規則透過產生式的關聯,形成一個圖(也可能是幾個圖),圖中就包含了所有的 loop,也就是左遞迴。

Parser 碰到一個 loop 的時候會進行迭代。每一次迭代,如果嘗試成果,則可以得到一棵子樹,並繼續下一次迭代。每個迭代的分析會建立在前一次迭代的分析結果之上,直到迭代結束,就形成一個完整的樹。

語義檢查和報錯

是另外一個大課題,它需要建立在複雜的語義分析基礎之上,是特定語言相關的。目前我們並沒有短期計劃把它做完善。每個語言的設計者可以自行補充。

Ast2mpl 和 ast2cpp

是兩個示範用例,展示如何使用生成的語法樹。Ast2mpl 把生成的語法樹分析並生成 MapleIR,作為編譯器的輸入。Ast2cpp 則是把生成的語法樹生成 C++ 原始碼。目前,ast2cpp 是我們正在工作的重點。

MapleFE 當前社群的主要狀態如下:

1、parsing 部分已經比較成熟,目前可以正常 parse 的語言包括 Java 和 Typescript(含 Javascript)。

2、ast2mpl 是做了一個初步的原型,當前可以把 Java 程式的語法樹生成 MapleIR,但我們沒有精力完善它,大家可以在社群程式碼中看到具體情況。

3、最有意思的事情是現在我們重點做的 Ast2cpp,當前正在做的事情是把 TypeScript 程式的語法樹生成 C++ 程式,這裡的工作包括非常多的內容,此處不一一列出。MapleFE 作為前端,只是其中不大的一塊內容。

在這個專案中,很多非常有意義的題目引起我們的深思,尤其是對於程式語言的設計,多語言混合程式設計、跨語言函式呼叫等。這些問題我們在17年正式啟動方舟編譯器專案的時候就開始思考,今天仍舊在理解類似的問題,積累相關的經驗。

前端是一個程式設計體系的入口,它把程式設計師的程式碼翻譯成語法樹和相關資料結構,這個工作相當於給程式設計體系開了個大門,基於此可以做很多事情,生成編譯器的 IR 只是很小的一部分,我相信它的用處更多的會用在程式的高階分析以及最佳化,驗證,新語言設計以及解決跨語言的問題。

原始碼連結:

https://gitee。com/openarkcompiler-incubator/MapleFE

Discord 討論區:

https://discord。gg/3mpZ8TDZ

大家可以參考 Java 和 TypeScript 的實現做嘗試,有問題可以在 Discord 或者 Gitee 裡面發帖子。