![]()
前言:在編譯器技術(shù)領(lǐng)域,LLVM 憑借其精妙的 IR 設(shè)計、靈活的 Pass 架構(gòu)成為工業(yè)標(biāo)桿,但其底層依賴的 C++ 卻日益成為負(fù)擔(dān)。從編寫 Lua/C 編譯器到參與 LLVM 開發(fā),我親歷了 C++ 的復(fù)雜性如何侵蝕開發(fā)效率:模板元編程的“黑魔法”、標(biāo)準(zhǔn)庫的陷阱、緩慢的編譯調(diào)試體驗,以及構(gòu)建系統(tǒng)的混亂,迫使團隊將精力耗費在語言細(xì)節(jié)而非核心算法上。 這種困境催生了 MoonLLVM 的構(gòu)想——它并非替代 LLVM,而是作為其“友好伴侶”,通過輕量級設(shè)計規(guī)避 C++ 的弊端。
MoonLLVM 基于 MoonBit 語言,提供簡潔的字符串處理、快速編譯和模塊化結(jié)構(gòu),同時保持與原生 LLVM 的互操作性。其目標(biāo)是降低編譯器開發(fā)門檻,讓開發(fā)者更專注于語言語義與優(yōu)化創(chuàng)新,而非與工具鏈搏斗。本文將探討這一實踐如何為編譯器工程提供新思路。
求學(xué)期間,我曾經(jīng)非常迷戀 C++:模板元編程很酷,標(biāo)準(zhǔn)庫看起來很「工程」、很「專業(yè)」。那會兒,我也用 C++ 寫過不少東西:Lua 編譯器、C 編譯器,還有各種各樣的小工具等等。后來畢業(yè)以后,又在國內(nèi)某知名芯片公司,也做過 LLVM / MLIR 相關(guān)工作,寫過優(yōu)化 Pass、后端代碼生成等。
但當(dāng)我的經(jīng)驗越來越豐富,見到的東西越來越多之后,我越來越有一種感覺:
如果LLVM不是建構(gòu)在C++上的就好了。LLVM -- 杰出的設(shè)計
作為知名的編譯器后端框架,LLVM 的架構(gòu)設(shè)計是非常優(yōu)秀的。它的 IR 設(shè)計、Pass 管線、Def-Use 鏈,以及對硬件的抽象,都是教科書級別的。也正因如此,LLVM 成了工業(yè)界編譯器后端的標(biāo)準(zhǔn)。2013年,LLVM被授予了2012ACM軟件系統(tǒng)獎,已足以說明工業(yè)界對它的認(rèn)可。
但 LLVM 的基石語言C++,則是另一個極端。
問題頻出的C++ 1. 語言太復(fù)雜
我們在“對付語言”上花的精力,可能超過了“對付業(yè)務(wù)”。編譯器本身已經(jīng)是一個超級復(fù)雜的系統(tǒng),但 C++ 又額外疊加了一層復(fù)雜度:
一個變量可能有二十幾種初始化方式
一個類可以有足足六種構(gòu)造函數(shù);
模板元編程的各種黑魔法,且與C++主體編程范式大為不同的函數(shù)式范式。
標(biāo)準(zhǔn)庫里大量行為細(xì)節(jié)、陷阱、爛尾特性和詞不達(dá)意,例如變長數(shù)組叫vector,而真正的變長數(shù)組valarray是一個爛尾的特性;vector 并不是bool的容器;std::regex離譜的性能問題;std::remove實際上的作用是把元素移動到末尾等等。
各種看起來炫酷,實際上有些“故作高深”的名詞,例如SFINAE,CRTP,RTTI,RAII,monostate等等。
寫編譯器的工程師,本來應(yīng)該把大部分時間花在「語言語義、優(yōu)化算法、后端架構(gòu)」上,但在 C++ 世界,團隊不得不在很長時間里花大量精力,放在給新人培訓(xùn) C++ 語法和習(xí)慣用法,或者是與各種構(gòu)建配置、ABI、模板錯誤信息搏斗。
2. 字符串處理上問題頻出
編譯器本質(zhì)上你可以看做是一個復(fù)雜的字符串處理程序,但一個很不幸的事實是: C++ 標(biāo)準(zhǔn)庫在字符串處理上的支持并不友好,缺少真正可靠的字符串處理庫,常用的std::string或者std::regex都有不小的問題。這使得現(xiàn)實世界中,很多經(jīng)典的庫可能會選擇自己動手制作字符串庫,但是C++難以集成第三方庫的特點由阻礙了開發(fā)者去使用它們。
而這對于想用 C++ 入門寫編譯器的學(xué)生來說就非常不友好了,很多人連第一關(guān)「詞法分析」可能都過不去。
3. 編譯慢,調(diào)試體驗差
C++的編譯實在是太慢了,越大的項目這個問題越明顯。這是C++本身模板展開和頭文件的重復(fù)編譯導(dǎo)致的這個問題,很多情況下,開發(fā)者稍微改一點點,就要重新等很久。
但編譯器又是一個「必須頻繁讀源碼、單步調(diào)試」的系統(tǒng)軟件,因為編譯器與Web程序或者游戲程序不同,很多 bug 無法靠打 log 簡單定位;需要頻繁重編譯 + GDB / LLDB 調(diào)試。
這使得日常開發(fā)體驗非常不友好,debug 版 LLVM + C++ 的組合,有時慢到讓人懷疑人生。編譯和調(diào)試的效率低下結(jié)果導(dǎo)致的問題就是企業(yè)的開發(fā)成本非常高。
4. 構(gòu)建系統(tǒng)和第三方依賴太折騰
幾乎每個寫 C++ 的人都和各種構(gòu)建系統(tǒng)打過交道,Makefile / Ninja / Bazel / MSBuild / SCons / ...等等。而CMake 雖然是事實標(biāo)準(zhǔn),但在語法和使用體驗上,實在是一言難盡。
構(gòu)建系統(tǒng)混亂帶來的直接后果就是引入第三方庫極其麻煩,因為不同的包可能使用了不同的構(gòu)建工具。即使都使用了CMake,ABI、編譯選項、鏈接方式的兼容性問題也常常出現(xiàn)。一些GitHub Star很高的項目,會推崇單頭文件模式,但是這樣又會帶來編譯速度的問題。
構(gòu)建系統(tǒng)和第三方庫的問題,造成了一個C++特色:C++ 程序員,對「重復(fù)造輪子」往往已經(jīng)習(xí)以為常。
如果有一個「非 C++ 的 LLVM 伴侶」……
假如我們有這樣一個東西:
能和真實的 LLVM 平滑互操作,能夠生成兼容 LLVM 工具鏈的 IR / bytecode;
有著非常優(yōu)秀的字符串處理,ADT,模式匹配語法,而且語法簡單,上手容易,AI友好。
更輕量、編譯速度快、源碼更容易讀;
這就是 MoonLLVM 想做的事情。
MoonLLVM:A Tiny, Friendly Companion to LLVM
GitHub 地址:moonbitlang/MoonLLVM
MoonLLVM 的定位:不是「重寫 LLVM」,而是「LLVM 的友好伴侶」
先把預(yù)期講清楚:
MoonLLVM 不是 LLVM 的重構(gòu)版;也不打算直接取代 LLVM。
現(xiàn)實是:LLVM 過去二十多年集結(jié)了多家大公司的巨大投入,在優(yōu)化、多后端支持、生態(tài)廣度上,有壓倒性的優(yōu)勢。MoonLLVM 短期內(nèi)不可能、也沒必要在這些維度上正面進攻。
MoonLLVM 想填補的是另一塊空白:
讓學(xué)生和初學(xué)者更容易接觸 IR / Pass / 后端;
讓小型芯片公司、小團隊可以用更低成本做原型和特定場景的后端;
讓熟悉 LLVM 的工程師多一個更輕量、可退出的選項。
MoonLLVM 和很多「自建小框架」,例如QBE,Cranelift等最大的不同,是我們從一開始就認(rèn)真設(shè)計了與真 LLVM 的互操作,而不是造一個完全封閉的「私有宇宙」。
可以分三層來看:
1.1 代碼級互操作
我們有一個 llvm.mbt 包,它是一個真 LLVM 的 MoonBit 綁定,需要本地安裝 LLVM。調(diào)用llvm.mbt得到的IR,就是原版LLVM生成出來的IR。 github: https://github.com/moonbitlang/llvm
MoonLLVM 的 API 與 llvm.mbt 有意識地對齊:數(shù)據(jù)結(jié)構(gòu)、接口設(shè)計都保持相似。
用戶只需改一處:調(diào)整 moon.pkg.json 配置,即可在 MoonLLVM 和真 LLVM 之間切換:
設(shè)計目標(biāo)是:MoonLLVM → 真 LLVM 平滑切換;
需要注意的是,反向切回來我們無法保證,這是因為MoonLLVM還是比真LLVM簡單太多。
MoonLLVM / llvm.mbt 的 API 很大程度上參考了原版 C++ LLVM:
Context / Module / Function / BasicBlock / IRBuilder 等核心概念一一對應(yīng);
操作順序、調(diào)用方式盡量保持一致;
對熟悉 C++ LLVM 的工程師來說,幾乎不需要換腦子。
MoonLLVM 與 llvm.mbt 可以在同一工程中共存;
提供轉(zhuǎn)換函數(shù),將 MoonLLVM 的中間數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)成 llvm.mbt 的數(shù)據(jù)結(jié)構(gòu);
這意味著:你可以在 MoonLLVM 中做自定義 IR 生成、輕量優(yōu)化,然后把 IR 交給真 LLVM 做后續(xù)優(yōu)化和后端。
MoonLLVM 生成的 LLVM IR / bytecode:
可以被現(xiàn)有 LLVM 工具鏈(如 llc、opt)識別和處理;
MoonLLVM 對其能力范圍內(nèi)的 IR 支持 Parse 和再處理。
一條典型鏈路可以是:
MoonLLVM → LLVM IR → llvm-opt 優(yōu)化 → LLVM IR →(可選)再回 MoonLLVM
從一開始,MoonLLVM 就內(nèi)建了一個清晰的「退出機制」:
用戶不用擔(dān)心「用了 MoonLLVM 以后就被架死在這里」;
未來如果有需要:可以逐步把關(guān)鍵路徑遷回真 LLVM;或者只在某些階段用 MoonLLVM 快速試驗和開發(fā)。
運行速度:用「適度取舍」換來輕盈
MoonLLVM 一開始就明確做了一個選擇:不追求覆蓋所有稀奇古怪的 C 語言特性(比如 VLA 等),C++特性(例如各種C++專用的異常)。也不追求支持所有冷門架構(gòu)與擴展。
這樣做的好處是:數(shù)據(jù)結(jié)構(gòu)和算法可以更簡單,內(nèi)部可以大量使用定長整數(shù)和更直接的實現(xiàn),在「生成 IR / 做基礎(chǔ)轉(zhuǎn)換」這類場景下,MoonLLVM 在 MoonBit 里調(diào)用的整體運行速度,有機會顯著快于直接調(diào)用真 LLVM。
編譯速度快,組件細(xì)粒度模塊化 MoonLLVM 有意識地把組件拆得更細(xì),做到用到哪個編譯哪個,沒用到的完全不參與構(gòu)建;整體編譯時間可以維持在較低水平;對開發(fā)者來說:改一個 Pass,不需要重編整個「工程」;非常適合做在線 Playground / REPL 的后端;或者是新 Pass / 新后端的實驗平臺。
無外部依賴:只依賴 MoonBit 工具鏈 MoonLLVM 只需要 MoonBit 自身的工具鏈即可:不需要外部 C++ 編譯器;也不需要 CMake / TableGen 等復(fù)雜構(gòu)建工具。對于只會 MoonBit 的開發(fā)者來說,安裝過程非常簡單;
輕量,適合「弱環(huán)境」和多種部署形態(tài)
得益于整體設(shè)計的輕量化和 MoonBit 語言本身的特性:MoonLLVM 可以跑在性能不算強的 PC 上,也可以通過 WebAssembly 部署到瀏覽器中,這使得它在部分嵌入式 / 邊緣計算環(huán)境中也有實際落地的希望;
這為「MoonBit 在線 Playground」、「嵌入式腳本 / DSL 環(huán)境」等場景,提供了很自然的技術(shù)路徑。
下面這個示例展示了如何用 MoonLLVM 創(chuàng)建一個簡單的加法函數(shù):
輸出的 LLVM IR 大致如下:
注意頂部的注釋部分明確標(biāo)注了「Generated by MoonLLVM, not llvm」,這與原版 C++ LLVM 的輸出可以清晰區(qū)分。
這份 IR 既可以交給官方 LLVM 工具鏈(例如 llc)繼續(xù)編譯,也可以作為 MoonMIR 的輸入,進一步生成 riscv64 或 aarch64 匯編。
對應(yīng)的 C++ LLVM 程序?qū)Ρ?/p>
下面是一段等價的 C++ LLVM API 示例代碼,實現(xiàn)同樣的 add(i32, i32) -> i32 函數(shù):
MoonLLVM 目前已經(jīng)做到什么?
當(dāng)前,MoonLLVM 已經(jīng)具備以下能力:
支持 LLVM IR 的構(gòu)建;
在此基礎(chǔ)上,完成了初步的后端代碼生成,可以獨立生成 RISC-V 匯編代碼,和 AArch64 匯編代碼。
基于 MoonLLVM,我們實現(xiàn)了一個 MiniMoonBit 編譯器,除基礎(chǔ)特性外,還支持模式匹配、高階函數(shù)等特性,完成MiniMoonBit編譯器后,我們還用 MiniMoonBit 跑了一個光線追蹤程序,效果可以在 B 站視頻中看到:
B 站視頻:MiniMoonBit + MoonLLVM 光線追蹤示例
性能測試:與 tcc / clang 的對比
為了測試 MoonLLVM 的實際表現(xiàn),我們設(shè)計了 5 個小例子:
ack.mbt:Ackermann 遞歸函數(shù);
fib.mbt:遞歸 Fibonacci;
eigen.mbt:矩陣求特征值;
svd.mbt:矩陣 SVD 分解;
queen.mbt:八皇后問題。
測試方式如下:
MiniMoonBit(基于 MoonLLVM)
MiniMoonBit 生成 AArch64 匯編;
使用 clang -O0 編譯匯編文件和 runtime.c,得到可執(zhí)行程序。
tcc
使用 MoonBit 將對應(yīng) .mbt 文件通過 --target native 轉(zhuǎn)為 C 程序;
使用 tcc 將 C 文件與 MoonBit 標(biāo)準(zhǔn) runtime.c 編譯為可執(zhí)行程序。
clang -O0
同樣先轉(zhuǎn)為 C,再用 clang -O0 編譯。
clang -O1
同樣先轉(zhuǎn)為 C,再用 clang -O1 編譯。
然后分別運行所得可執(zhí)行程序,記錄時間(單位:秒):
配圖如下:
![]()
性能結(jié)果分析
整體來看:
基于 MoonLLVM 的 MiniMoonBit 性能明顯優(yōu)于 tcc 和 clang -O0;
與 clang -O1 相比,MiniMoonBit 仍有差距,但在同數(shù)量級之內(nèi)。
主要原因在于:
MoonLLVM 當(dāng)前后端已經(jīng)做了寄存器分配(采用圖著色寄存器分配);
而 tcc 和 clang -O0 不做寄存器分配,因此在部分算例中會出現(xiàn)明顯性能劣勢;
clang -O1 在寄存器分配之外,還開啟了更多優(yōu)化 Pass,因此通常會比當(dāng)前版本的 MiniMoonBit 更快。
換句話說,在仍然相對「年輕」的優(yōu)化管線下,MoonLLVM 已經(jīng)能在不少場景中跑到接近 clang -O1 的表現(xiàn);后續(xù)優(yōu)化空間依然很大。
展望:MoonLLVM 接下來要做什么?
未來一段時間內(nèi),MoonLLVM 會重點在以下方向持續(xù)演進:
兌現(xiàn)互操作承諾:持續(xù)完善與真 LLVM 的互操作能力;保證「隨時可以退出到真 LLVM」這條路徑長期有效。
擴展指令與類型系統(tǒng):豐富 IR 指令和類型支持;增強優(yōu)化能力,引入更多調(diào)試與診斷信息。
增加更多體系結(jié)構(gòu)后端:補充 x86_64 等后端;在更多架構(gòu)上驗證當(dāng)前抽象方案的通用性與簡潔性。
打造完整工具鏈閉環(huán):開發(fā)配套匯編器(MoonAs)與鏈接器(MoonLD);構(gòu)建由 MoonLLVM 驅(qū)動的、可獨立運作的工具鏈閉環(huán)。
我們希望,MoonLLVM 既能成為教學(xué)與研究的友好平臺,也能在特定場景下,成為真正落地可用的輕量級編譯后端。
如果你對 LLVM 生態(tài)已經(jīng)很熟悉,希望有一個更輕量、可實驗、可與真 LLVM 平滑切換的伴侶;或者你只是想用一種更現(xiàn)代、更干凈的方式來認(rèn)識「編譯器后端」這個世界,歡迎試試 MoonLLVM:
[1]: MoonLLVM鏈接:https://github.com/moonbitlang/MoonLLVM
[2]: MiniMoonBit編譯器:https://github.com/moonbitlang/MiniMoonBit2025
[3]: B站視頻,用MoonBit寫個MiniMoonBit跑了一個光線追蹤: https://www.bilibili.com/video/BV1kSS4BqETn
特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺“網(wǎng)易號”用戶上傳并發(fā)布,本平臺僅提供信息存儲服務(wù)。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.