本RFC介紹JSIR,一種為JavaScript設計的高階中介表示(IR):
JSIR已在Google生產環境中開發並部署,用於代碼分析與轉換的應用場景。
JSIR是開源的,代碼庫位於GitHub - google/jsir。
編譯器產業正朝向建立高階、語言專屬的中介表示發展。例如Rust與Swift編譯器會在降低至LLVM之前,先在其高階IR上執行某些分析。類似的專案還有Clang IR、Mojo與Carbon等。
為什麼JavaScript特別需要高階IR?雖然許多JavaScript工具依賴AST(如ESTree),但複雜分析需要控制流圖(CFG)與資料流分析能力,JSIR透過MLIR框架提供這些功能。
許多JavaScript工具需要輸出JavaScript代碼。
轉譯:Babel將新版JavaScript轉換為舊版以提升瀏覽器相容性。
優化:Closure Compiler優化JavaScript代碼,使其更短更快,減少下載時間並提升效能。
打包:Webpack將多個JavaScript檔案打包成單一檔案。
這些工具都需操作能回生成JavaScript的表示,因此皆以AST為基礎。
JavaScript社群擁有大量基於AST的開源工具,例如Babel提供AST遍歷與作用域工具,ESLint依賴espree提供AST與相關工具。
AST Explorer列出了眾多JavaScript AST格式,甚至有標準AST格式——ESTree。
然而,目前尚無工具提供IR而非AST的表示,JSIR正是為填補此空白而生。
JSIR在Google用於代碼分析與轉換,例如:
利用JSIR能完全還原至原始碼的能力,將Hermes字節碼反編譯回JavaScript代碼。
利用其源碼到源碼轉換能力進行JavaScript反混淆。
我們最新的論文結合Gemini大型語言模型與JSIR進行反混淆,已被ICSE 2026 SEIP軌道接受,將於2026年4月15日發表。
JSIR旨在為JavaScript社群提供公開、穩定且完整的基於IR的工具。其定義公開且穩定,且與ESTree高度對應,幾乎所有JSIR操作均有一對一的ESTree節點映射。
JSIR不適用於低階優化(例如JIT會定義更低階的IR,可能由JSIR降低而來),而是高階IR,保留所有原始碼層級資訊,支援源碼到源碼轉換與反編譯等應用。
JSIR設計目標之一是能完美將JSIR轉回JavaScript AST,實現無損的雙向轉換。
IR相較AST的關鍵優勢是能執行資料流分析,因此JSIR基於MLIR資料流分析框架,提供易於定義格與轉移函數的資料流分析框架。
JSIR的成功將證明MLIR能定義通用語言的IR,目前核心IR定義已被JSIR、ClangIR與Mojo等專案證明有效。
接下來,我們將測試更多MLIR的進階功能,例如建立資料流分析API包裝層以提升易用性,並希望將改進回饋至上游。也計畫使用並改進符號表、記憶體效應等功能,使MLIR成為未來建構任何編譯器的首選。
曾有討論是否可用MLIR表示AST,Mojo正嘗試直接解析至MLIR。
JSIR目標更進一步,打造可回升至原始碼的IR。若成功,將證明MLIR可表示AST,AST與IR的界線可能非常模糊。
若高階IR能保留AST所有資訊,則可質疑是否還需要AST。
Mojo與Carbon皆在IR上執行所有分析,顯示IR已具備取代AST的分析能力,但我們經驗顯示,開發者(尤其非編譯器專家)仍覺AST較易理解與操作。
JSIR重要目標是確保IR能準確轉回AST,配合Babel的AST到原始碼打印器,實現IR到原始碼的可逆轉換,支援源碼到源碼轉換。
內部評估數十億JavaScript樣本,AST與IR的雙向轉換成功率超過99.9%,結果與原始碼相同。
以下將說明達成高保真雙向轉換的重要設計決策。
從最簡單的直線程式碼開始,即無控制流結構的語句列表。
每個簡單的表達式或語句AST節點對應一個JSIR操作,JSIR對直線程式碼相當於AST的後序遍歷輸出。
例如以下JavaScript語句:
對應的AST可見於AST Explorer。
對應的JSIR如下:
若加上縮排,AST節點與JSIR操作的一對一映射更明顯。
將此IR轉回AST時,不能將每個操作視為獨立語句,否則每個SSA值(如%1)會變成區域變數。
但可檢測兩個語句級操作(即兩個jsir.expression_statement操作),並遞迴遍歷其使用-定義鏈。
轉換JSIR基本區塊(mlir::Block)時,事先知道其內容類型:
若區塊為語句,找到單一語句級操作並遍歷使用-定義鏈生成JsStatement AST節點。
若區塊為語句列表,找到所有語句級操作並遍歷使用-定義鏈生成JsStatement AST節點列表。
若區塊為表達式,結尾為jsir.expr_region_end (%expr)操作,遍歷%expr使用-定義鏈生成JsExpression AST節點。
若區塊為表達式列表,結尾為jsir.exprs_region_end (%e1, %e2, ...)操作,遍歷%e1, %e2等使用-定義鏈生成JsExpression AST節點列表。
JSIR區分左值與右值。例如賦值語句中,a為左值,b為右值。
AST中左值與右值表示相同,但IR中表示不同,明確表達語義差異:
左值是對物件或記憶體位置的引用;
(註:符號表示方式可能會調整。)
JSIR與AST幾乎一對一映射,為保留原始控制流結構資訊,為每種控制流結構定義獨立操作(如jshir.if_statement、jshir.while_statement等),巢狀程式區塊用MLIR區域表示。
例如if語句:
其AST與JSIR對應結構均完整保留巢狀結構,轉回AST透過標準遞迴遍歷實現。
while語句中,條件在jshir.while_statement中以區域表示,而非一般SSA值,因為條件每次迴圈都會評估,與if語句只評估一次不同。
邏輯表達式中,jshir.logical_expression的左邊是SSA值,右邊是區域,反映短路行為:左邊先評估,右邊僅在左邊為真時才評估。
JSIR提供基於MLIR資料流分析API的資料流分析API,並提升易用性:
定義JsirStateRef類封裝對AnalysisState的所有寫入,自動將依賴的WorkItem推入工作清單,使用者無需手動呼叫propagateIfChanged()。
定義JsirDataFlowAnalysis與JsirConditionalForwardDataFlowAnalysis基類,支援稀疏(附加於mlir::Value)與密集(附加於mlir::ProgramPoint)狀態分析,使用者無需撰寫兩套分析。
定義JsirGeneralCfgEdge結構統一mlir::Block間分支與區域分支,包括break與continue等早期退出,使用者無需為每個分析載入ConstantPropagation與DeadCodeAnalysis。
我們持續改進JSIR並擴大影響力,期待與MLIR社群交流想法。
目前尚未大量使用MLIR內建方言、操作與功能,未來將實際測試這些功能對通用語言的適用性。
我們相信JSIR資料流分析API的易用性改進可回饋至MLIR內建API,雖無法直接移植,但核心理念可採用,計畫另行RFC詳細討論。
我們樂意將JSIR上游至MLIR,類似WasmSSA,但實務上有幾個問題待解決:
依賴QuickJS用於常數折疊,避免重寫JavaScript語義(如ECMAScript規範中a + b涉及多步型別轉換),不確定LLVM是否接受新增此輕量JavaScript執行引擎依賴。
依賴Babel或SWC作為解析器,目前用Babel(TypeScript寫成)並透過QuickJS從C++執行,正嘗試轉向Rust寫的SWC,不確定MLIR代碼庫是否接受此依賴。
歡迎社群參與與貢獻,試用並回饋改進建議,對上述想法有興趣者也請告知!
JSIR的實現離不開眾多貢獻者的協助。
關於註解的表示,jsir代碼庫中以獨立註解欄位掛載於檔案結構中,但此方式非增量式,若在10K行檔案開頭加空白,可能導致IDE卡頓數秒。