目錄

Compiler, Assembler and Interpreter 了解

C 語言

Preprocessing: 預處理,把程式碼所有用 # 開頭的指令進行替換

  1. C ISO 標準定義,所謂的 翻譯階段 (Phases of Translation)。C 語言的編譯過程在標準中被拆解為 8 個邏輯階段,而「預處理」主要涵蓋了前 4 個階段
    1. Phase 1-3: 處理字元映射、將連行符號(\)合併、把註解換成空格
    2. 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 語言編譯流程
C 語言編譯流程

常見的 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 一樣,提供像是:

  1. Loading:載入
  2. Execution Context:PVM 維護一個 PyFrameObject 的鏈結串列。每個「框架」裡都有自己的 Bytecode 指針(模擬 PC)和局部變數表。這讓你可以在 Python 拋出異常時看到完整的 Stack Trace,這其實就是 VM 在讀取它維護的上下文
  3. 排程:雖然 Python 使用系統執行緒,但為了保護內部資料狀態,它加了一個 GIL (Global Interpreter Lock)。這個鎖就是 VM 層級的「簡易排程器」,決定哪一個執行緒現在可以動用 VM 的資源
  4. Memory 管理:不太需要 Page Table,是比較像是 GC or Object Allocator
  5. Call Stack:Virtual Stack Frames: PVM 自己維護一個 Call Stack 來模擬函式跳轉
  6. Linking:執行時才去 Namespace (dict) 查找變數或函數名稱
  7. System Call:提供封裝過的 API(如 os.open())來對接底層 Syscall
Python 語言編譯流程
Python 語言編譯流程

注意這邊是每個不同的 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 跑,結果保證一模一樣。

使用的流程是:

  1. 先編譯成 Jave bytecode (.class)
  2. 直譯,逐行讀取 Bytecode 並執行
  3. 當 JVM 發現某個方法或迴圈跑了非常多次(稱為 Hot Spot 熱點),它會覺得:「一直直譯這段太慢了!」。背景執行緒會把這段 Bytecode 直接編譯成該平台(x86/ARM)的機器碼。下次再執行到這裡,CPU 會直接跑機器碼,速度跟原生 C++ 幾乎沒兩樣。
Java 語言執行流程
Java 語言執行流程

WASM

他把 WASM bytecode 當作是一種平台,所以不同語言的編譯器都可以編譯成他,除了自己的 WAT (WebAssembly Text format),可以當成 WASM Bytecode 的處合語言。

WASM 類似 Java,同樣有自己的標準 for WASM bytecode (.wasm),來自 W3C:

  1. Wasm 核心規範 (WebAssembly Core Specification):它規範的是 .wasm 檔案本身的結構
    1. 二進位編碼: 檔案開頭必須是 0x00 0x61 0x73 0x6d (即 \0asm)
    2. 指令集 (ISA): 例如 i32.add 的 Opcode 是 0x6A
    3. 模組結構: 哪一段是定義變數(Globals)、哪一段是程式碼(Code Section)
    4. 驗證規則: 確保程式碼不會做出越權存取記憶體等危險行為
  2. WASI (WebAssembly System Interface):讓 WASM bytecode 有同樣的 API 能執行

如果你把 .wasm 看成一個 ELF 執行檔:

  1. Wasm Bytecode 就像是 ELF 裡的機器碼指令
  2. WASI 就像是 系統調用 (Syscall) 表 或 libc 的標準介面

WASM 也是有自己的 VM,並且通常提供兩種模式執行:

  1. JIT 模式,類似 Java
  2. 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

Reference