- Lua解釋器構建:從虛擬機到編譯器
- 吳尹杰
- 5字
- 2023-06-28 15:30:26
1.1 Lua解釋器
1.1.1 Lua解釋器的整體架構
Lua是一門腳本語言,Lua腳本能夠邊編譯邊執行,符合解釋器的所有特征。編譯和運行Lua腳本的程序稱為Lua解釋器。
本節將對Lua的整體架構進行討論。在開始詳細討論之前,首先向讀者介紹一下什么是解釋器。按照編譯原理相關書籍的介紹,解釋器是將輸入的源代碼(腳本),直接編譯并且直接運行,源代碼被加載到解釋器以后,不會輸出目標代碼,而是直接被解釋執行,直接輸出結果,如圖1-1所示。

·圖1-1
前文比較抽象地介紹了什么是解釋器,現在來看一個比較直觀的例子。例子在ubuntu16.04的云服務器上進行。首先,創建一個~/workspace/lua的目錄,并且通過cd命令(Dos系統的目錄切換命令),進入這個目錄。通過wget https://www.lua.org/ftp/lua-5.3.5.tar.gz命令語句,獲得lua-5.3.5的源碼壓縮包,如圖1-2所示。
接下來,使用tar-zxvf lua-5.3.5.tar.gz命令進行解壓,得到Lua源碼目錄lua-5.3.5,如圖1-3所示。

·圖1-2

·圖1-3
通過cd命令,進入到lua-5.3.5的目錄中,執行make linux指令,等待編譯完成。然后,在lua-5.3.5/src目錄下獲得一個名稱為lua和luac的可執行文件。這里的可執行文件lua就是Lua解釋器,luac則是將Lua腳本編譯成字節碼的編譯器。
在lua-5.3.5目錄下,創建一個scripts目錄,并且創建一個test.lua腳本,腳本里的代碼只是一個print("hello world")語句,輸入.../src/lua test.lua指令,可以得到圖1-4所示的結果。

·圖1-4
結合圖1-1可以很自然地聯想到,test.lua就是源代碼(腳本),位于../src目錄下名為lua的可執行文件就是解釋器,而輸出的hello world就是運行結果。現在,讀者對Lua解釋器應該有了更為直觀的認識了。那么解釋器和編譯器又有什么區別呢?按照編譯器的定義,編譯器的作用就是將一種語言轉化為另一種語言。在編譯器實踐中,通常編譯器的輸入語言是相對高級的語言,而輸出語言通常是更貼近底層的語言,如圖1-5所示。

·圖1-5
編譯器在對源語言執行編譯時,并不會直接運行源語言的邏輯,而是將其轉化為目標語言。比如前面對Lua源碼進行編譯,會得到很多“.o”文件。那么Lua源碼則是源語言,GCC是編譯器,“?.o”文件則包含了目標語言(二進制機器碼)。此外,還需要通過鏈接器,將諸多“.o”文件整合成可執行文件lua和luac。在lua和luac程序被啟動之前,Lua源代碼在編譯和鏈接的過程中是沒有被執行的,而解釋器則會直接對輸入的腳本代碼進行執行操作,這就是編譯器和解釋器的核心區別。
在了解完編譯器和解釋器的概念之后,現在開始探討Lua解釋器的整體架構。Lua解釋器主要有兩種運行模式:一種是前面展示的,直接加載Lua腳本并執行,直接獲得運行結果;另一種則是通過Lua編譯器(即可執行文件),將Lua腳本編譯成字節碼暫存在磁盤中,當需要使用的時候,再去加載執行,如圖1-6所示。

·圖1-6
兩種模式主要的區別是,編譯腳本代碼發生在不同的時期。第一種是解釋器運行期間,加載腳本代碼后,直接編譯并直接運行。第二種模式則是預先將腳本編譯,并以文件的形式存入磁盤,需要的時候,再加載到解釋器中進行執行,此時省去了編譯的過程。
這里就涉及一個新的問題,預先編譯的模式能否在運行期比直接加載腳本并執行的模式更快呢?答案是否定的。因為不論是預先編譯,還是直接加載腳本并且編譯執行,它們生成的指令是一樣的,因此在運行期間不會有效率上的差別。但是預先編譯的方式,確實會在加載和執行時省去編譯所需要的時間。
官方Lua中,luac和lua兩個可執行文件都包含了一個內置編譯器。它們內部包含的內置編譯器是一樣的,只是luac增加了輸出字節碼的邏輯。本書不會對如何構建luac編譯器進行討論,而是集中時間精力探索Lua解釋器,因為只要搞懂Lua解釋器的運行機制,讀者回顧luac的時候就自然會駕輕就熟了。
繼續研究Lua解釋器的整體內部構造。前面將Lua當作是一個黑盒子,現在要做的則是打開這個黑盒子。Lua解釋器可以分割成兩大部分,分別是編譯器和虛擬機。回顧一下圖1-1的情景,腳本會被解釋器加載編譯并執行,直接輸出結果。將其內部繼續細化,得到圖1-7所示的結果。從圖中可以看到,解釋器內部被分割成編譯器和虛擬機。該運行方式和圖1-6的方式非常相似,只是通過編譯器編譯后的字節碼,圖1-6的方式是存放在文件里,并且在編譯的過程中,解釋器程序未被啟動。而圖1-7的方式則是將字節碼信息存在一個被叫作Proto的內存結構中,這個結構主要是存放編譯結果(指令、常量等信息)。虛擬機在運行的過程中,會從Proto結構中取出一個個的字節碼,然后再執行。

·圖1-7
前面對編譯器和解釋器進行了說明,現在發現解釋器內部還包含了一個編譯器,如圖1-7所示。有些讀者可能會有些困惑,既然編譯器和解釋器是有區別的,那Lua解釋器為什么又包含了編譯器呢?前文也已經提到過,只要能將一種語言轉化成另一種語言的程序,都是符合編譯器的定義的。Lua解釋器內部的編譯器,本質上是將腳本代碼編譯成字節碼,符合這個定義,因此也是編譯器。
在編譯原理中,有編譯型語言和解釋型語言之分。編譯型語言的編譯器,能夠將源代碼直接編譯成機器碼生成可執行文件,運行源碼的邏輯需要將可執行文件加載到進程中執行,比如C/C++語言就是這種類型。編譯型語言編譯出來的機器碼是和平臺相關的,比如在x86平臺上編譯的程序,是不能在ARM平臺上運行的。解釋型語言則不同,只要目標平臺能夠運行該語言的解釋器,它們的邏輯腳本可以不經過任何修改,就能在這些目標平臺上運行。Lua就是這樣的解釋型語言。
Lua解釋器的內置編譯器和編譯型語言(比如C、C++)是有很大的區別的。編譯型語言的編譯器強大且復雜,根據《編譯原理:原理、技術與工具》一書中的定義,它包含了前端和后端。前端負責對源代碼進行詞法分析、語法分析、語義分析,再通過中間碼生成器生成中間表示,然后交給編譯器后端的代碼生成器生成目標機器碼,最后通過編譯器后端的目標碼優化器優化目標機器碼,如圖1-8所示。

·圖1-8
Lua解釋器的內置編譯器,則沒有這么復雜。首先它不負責生成機器碼,主要工作就是將Lua腳本編譯成虛擬機能夠識別的字節碼。其次,它的構造也很簡單,主要包含了詞法分析器和語法分析器。其語法分析器也不生成抽象語法樹,而是直接生成字節碼。
截止到現在,已完成對Lua解釋器整體架構的介紹。下一節,將對Lua解釋器的整體運行機制進行簡要的介紹。
- Cocos2d Cross-Platform Game Development Cookbook(Second Edition)
- R語言數據分析從入門到精通
- Mastering AWS Lambda
- Mastering Natural Language Processing with Python
- C語言程序設計
- Python數據可視化之Matplotlib與Pyecharts實戰
- 網站構建技術
- Instant Ext.NET Application Development
- Python算法指南:程序員經典算法分析與實現
- R語言與網絡輿情處理
- Extending Puppet(Second Edition)
- 學習OpenCV 4:基于Python的算法實戰
- 微服務架構深度解析:原理、實踐與進階
- Internet of Things with ESP8266
- HTML5開發精要與實例詳解