UP | HOME

什麼是啟動編譯器?

當一個語言逐漸成熟,開發者常常會有一個想法就是用自己開發的語言寫這個語言的編譯器,但是這個決策會導致程式庫中有兩套目標一樣但是實現語言不同的編譯器程式。其一是本來的實現,另一個是用語言自身新寫的。一來這樣開發任何新功能的時候都需要寫兩次;二來這會減少可能的參與者人數,因為這下他得要熟悉兩個語言才行!解決這個問題的辦法就是啟動編譯器,通常是一個在多平台上可以直接執行的檔案。

1. 原理

約略是下面描述的樣子:

  1. 語言本身後端的目標語言可以生成這個啟動編譯器,讓我們先用可執行檔簡單理解它,後面再闡述為什麼用普通的可執行檔可能不是一個好主意
  2. 每次編譯器用到新的特性的時候,都需要更新啟動編譯器檔案並提交進程式庫
  3. 有了上述準備,就可以在第一次編譯編譯器自身的時候,用啟動編譯器編譯出最佳化過的編譯器執行檔

現在有了最佳化過的編譯器執行檔,就可以正常的使用語言自己對語言自己進行開發了。

現在可以來想一下為什麼是這樣的流程,首先一個程式語言除非被大量作業系統支援(比如 C、Python),否則系統上是不會有這個程式語言的編譯器的!這樣問題就來了:假設有人試圖參與這個編譯器專案,用的平台剛好跟既有開發者都不一樣,請問要由誰提供已經用語言自身編寫的編譯器的編譯器呢?想當然除了一些喪心病狂的作法,是不可能完成這個任務的!所以一個無論如何都已經可以執行的編譯器,哪怕效能比較差,也具有存在的價值。

而語言自身的編譯器用到新功能的時候,也一定要更新啟動編譯器,否則下一輪的新參與者就沒辦法正確的進行初始編譯。

2. 用普通的可執行檔可能不是一個好主意

前面提過說啟動編譯器通常是一個在多平台上可以直接執行的檔案,也提過哪怕效能不佳也可以,這兩個理由使得特定平台的可執行檔不是一個好的選擇。 當然,我們也可以選擇維護每個平台的啟動編譯器,chez scheme 就維護了一堆根據不同平台套用組合的二進位檔案做這件事。

回到正題上,啟動編譯器通常不會選擇這麼複雜的方式,而是會生成可以在多平台上執行的檔案。舉例來說,生成 C 或是 Python 這種已經被許多系統廣泛支援的語言,最近 zig 就是使用 wasm 達成這個目標。但這個選擇也不能太過隨便,比如要是語言自己的 type checking 任務很長,生成 Python 就會得到一個大家都不想等它跑完就不玩了的啟動編譯器。而限制太多的語言像是 haskell 或是 rust 可能也不是優秀的選擇,因為你的語言模型很可能跟這些目標語言非常不相配,進而讓編譯過程非常的痛苦。

3. 結論

有了這些概念,我想你已經有能力為自己的語言寫出啟動編譯器了!那麼剩下的篇幅我就可以來寫點 murmur,我覺得這個概念可以進一步推升到選擇一個夠容易編譯出來,也夠容易寫出通用最佳化編譯器的語言來達成。我個人是很看好 wasm,因為它確實容易當目標、最佳化,只可惜現在還有不少重要的功能都沒有定案。llvm 的 bytecode 確實也是一個方案,寫一個把 llvm bytecode 編譯成 executable 的 python 檔案並不困難,但 llvm 的連結性自己就是一個天殺的大麻煩所以這個方案我很少看到有人使用。Java bytecode 則是讓那些本來就預計只在 JRE 生態系內部的語言從一開始就沒有這個問題,微軟的 CLR 也是類似的狀況,缺陷是最後語言受制於 runtime,這個問題會有多大條取決於語言的目的是什麼。

好像是今年最後一天了,祝你不是在今天看到這篇文章的!

Date: 2022-12-31 Sat 00:00

Author: Lîm Tsú-thuàn