Compiler, Assembler and Interpreter 了解
C 語言
Preprocessing: 預處理,把程式碼所有用 # 開頭的指令進行替換
- C ISO 標準定義,所謂的 翻譯階段 (Phases of Translation)。C 語言的編譯過程在標準中被拆解為 8 個邏輯階段,而「預處理」主要涵蓋了前 4 個階段
- Phase 1-3: 處理字元映射、將連行符號(\)合併、把註解換成空格
- Phase 4: 執行所有 預處理指令(也就是你看到的 # 開頭的東西)並進行 巨集展開 (Macro Expansion)。
最後產出了 .i 檔案,可以參考C语言的翻译阶段。
再來就是編譯,正式編譯,也就是第七階段,編譯器會做語法分析,語意檢查,並將程式碼轉換成目標碼,基本上就是兩步驟。
- Compiler 負責把 C 變成 .s (Assembly)
- Assembler 負責把 .s 變成 .o (Object code)
這邊的 Object Code 其實就是二進位的 Machine Code,但有特定格式,像是在 Linux 是 ELF。
ELF 裡面有像是:
- .symtab:符號表 (Symbol Table): 這張表紀錄了這個檔案中定義或引用的所有「名稱」
- .strtab:字串表 (String Table)
- .rel.text / .rela.text:重定位表 (Relocation Records)
最後一步就是 Linker 針對 Object Code 裡面的重定位表,把像是 printf 函式真正的 address 塞進來,最後產生像是 a.out 類似的執行檔。
這類執行檔還是 ELF 格式,他把原本散落在各個 .o 裡的 .text(程式碼)通通黏在一起,變成一個巨大的區塊。
然後多了像是:
- Program Header (程式頭部):
- Section Header(Linker 看的): 告訴 Linker 哪裡是符號表、哪裡是重定位表
- Program Header(OS 看的): 告訴 OS「請把這 1MB 的資料載入到記憶體位址 0x400000,並給它『唯讀+執行』的權限」
最後的 ELF 執行檔,可以給 OS 直接跑,OS 會用 Kenre Loader 讀取此執行檔,然後把檔案的內容 Map 到記憶體。
在這執行的時候假如有用到動態函式庫,Kernel 會先啟動一個叫做 ld-elf.so 的小程式,同樣進行 Link。
所以我們可以把 OS 當成一個 ELF Runtime,他有 Loader 載入 ELF 程式,提供執行環境,像是:
- 記憶體管理 (Memory Management)
- 它必須定義 Stack(堆疊) 如何增長、Heap(堆積) 如何分配
- 對於 C 是 OS 核心 and libc,OS 核心管理 Paga Table 等
- 執行上下文控制 (Execution Context)
- 管理 Program Counter (PC)、Stack Pointer (SP) 和暫存器 (Registers)
- 執行緒與排程 (Threading/Scheduling)
- System Call Interface
常見的 C compiler 叫做 GCC ,各家公司也會實做自己的 C compiler (Visual studio C/C++ …),也有人對 C 語言專門設計一個直譯器,叫做 Ch。
組譯器
組譯器其實也是一種編譯器,只是因為強調是組合語言當作輸入,並且組譯器多半比編譯器還要簡單。
有些編譯器看起來是直接輸出機械語言,其實多半也是先輸出組合語言,再在背後執行組譯器。
Python
先來定義 Interpreter 是什麼:
- Compiler 是先把程式碼編譯成機器語言,之後一次執行完全部
- Interpreter 則是在執行時是一邊解釋成機器語言,一般執行
實際看 Python 他運作是,先用 Compiler 編譯成 Python bytecode,然後讓 Bytecode 在 Python VM 上面直譯,邊翻譯邊執行。
注意這邊多了一個名詞叫做 VM,他可以看作一種 Runtime,但他是一種模擬硬體行為的軟體層,它在真實硬體與程式之間多墊了一層。它不直接讓 CPU 跑機器碼,而是定義了一套自己的「虛擬指令集」(如 Python Bytecode)。
所以他要像是 OS Runtime 一樣,提供像是:
- Loading:載入
- Execution Context:PVM 維護一個 PyFrameObject 的鏈結串列。每個「框架」裡都有自己的 Bytecode 指針(模擬 PC)和局部變數表。這讓你可以在 Python 拋出異常時看到完整的 Stack Trace,這其實就是 VM 在讀取它維護的上下文
- 排程:雖然 Python 使用系統執行緒,但為了保護內部資料狀態,它加了一個 GIL (Global Interpreter Lock)。這個鎖就是 VM 層級的「簡易排程器」,決定哪一個執行緒現在可以動用 VM 的資源
- Memory 管理:不太需要 Page Table,是比較像是 GC or Object Allocator
- Call Stack:Virtual Stack Frames: PVM 自己維護一個 Call Stack 來模擬函式跳轉
- Linking:執行時才去 Namespace (dict) 查找變數或函數名稱
- System Call:提供封裝過的 API(如 os.open())來對接底層 Syscall
注意這邊是每個不同的 Python Interpreter 實作會產生自己的 Bytecode,所以你不能把 CPython Bytecode 跑在 RustPython VM 上面。
Python 成功的原因之一是它有 NumPy、TensorFlow 等極強的 C 套件。這些套件是直接跟 CPython 的內部結構(C 語言層級)溝通的(就像 ELF 有 ABI 一樣,Python 也有 Stable ABI (PEP 384)。它確保編譯好的 C 擴充套件在升級 Python 小版本時不需要重新編譯。)
Java
Java,類似 Python,他有自己的 VM 叫做 JVM,但他有一份極其嚴謹、厚重的 JVM Specification。不論是 Oracle、IBM 還是 Amazon 實作的 JVM,都必須 100% 遵守這份規格。
Java 的目標是成為一個虛擬作業系統。它不只跑 Java 語言,還能跑 Kotlin, Scala, Groovy。這些語言都編譯成同一套標準 Bytecode。JVM 不關心你是哪種語言,它只負責把標準 Bytecode 跑得飛快。
所以有了規格,不管是哪個語言,哪個 Runtime 的 Compiler,產生的 Bytecode 只要符合規格,可以在不同人的 JVM 上面跑。你在 Mac 上用 OpenJDK 編譯出來的 Main.class,可以直接丟到一台執行 Windows 的伺服器上,用 Oracle 的 JVM 跑,結果保證一模一樣。
使用的流程是:
- 先編譯成 Jave bytecode (
.class) - 直譯,逐行讀取 Bytecode 並執行
- 當 JVM 發現某個方法或迴圈跑了非常多次(稱為 Hot Spot 熱點),它會覺得:「一直直譯這段太慢了!」。背景執行緒會把這段 Bytecode 直接編譯成該平台(x86/ARM)的機器碼。下次再執行到這裡,CPU 會直接跑機器碼,速度跟原生 C++ 幾乎沒兩樣。
WASM
他把 WASM bytecode 當作是一種平台,所以不同語言的編譯器都可以編譯成他,除了自己的 WAT (WebAssembly Text format),可以當成 WASM Bytecode 的處合語言。
WASM 類似 Java,同樣有自己的標準 for WASM bytecode (.wasm),來自 W3C:
- Wasm 核心規範 (WebAssembly Core Specification):它規範的是
.wasm檔案本身的結構- 二進位編碼: 檔案開頭必須是
0x00 0x61 0x73 0x6d(即\0asm) - 指令集 (ISA): 例如
i32.add的 Opcode 是0x6A - 模組結構: 哪一段是定義變數(Globals)、哪一段是程式碼(Code Section)
- 驗證規則: 確保程式碼不會做出越權存取記憶體等危險行為
- 二進位編碼: 檔案開頭必須是
- WASI (WebAssembly System Interface):讓 WASM bytecode 有同樣的 API 能執行
如果你把 .wasm 看成一個 ELF 執行檔:
- Wasm Bytecode 就像是 ELF 裡的機器碼指令
- WASI 就像是 系統調用 (Syscall) 表 或 libc 的標準介面
WASM 也是有自己的 VM,並且通常提供兩種模式執行:
- JIT 模式,類似 Java
- AOT (Ahead-of-Time):在程式執行之前,先用編譯器把
.wasm完整地轉成該平台的原生機器碼 (Native Machine Code),並封裝成一個特殊的執行檔(在 WasmEdge 中通常是.so或特定的 AOT 格式)。就算編譯成機器碼,還是要在 Runtime 上面執行,不能直接在 OS 執行。
WASM Sandobx
另外一個特別的是 WASM 有做 Sandbox。
https://www.ithome.com.tw/news/153022
JavaScript (V8)
JavaScript 雖然表面上是腳本,但 Chrome 的 V8 引擎會進行極其複雜的編譯優化。
Deep Learning Compiler
…