編譯器管線(compiler pipeline)
把它想成工廠的生產線。你打的字元從一端進去,機器碼從另一端出來,但這不是一步魔法完成的。程式會經過一連串階段,每個階段都把它轉成比前一個更接近機器的形式。這個有順序的階段序列就是編譯器管線。
管線習慣上分成三大區。前端(front end)讀取原始語言(例如 C 或 Rust),檢查它格式正確,並把它轉成內部的中介表示法(intermediate representation,IR)——一種編譯器其餘部分拿來操作、而非直接操作文字的中立資料結構。中端(middle end)執行最佳化階段(pass),把這個 IR 改寫成更快但等價的 IR。後端(back end)再把 IR 降階(lower)為目標處理器真正的指令:它挑選指令、決定順序、分配暫存器,並輸出最終程式碼。一個關鍵想法是 IR 坐在中間,因此許多原始語言可以共用同一個最佳化器,同一個最佳化器也能對應多種處理器。
它之所以重要,是因為編譯器幾乎所有令人困惑的行為——為什麼它刪掉了你的檢查、為什麼程式碼被「重排」、為什麼某個 bug 只在 -O2 出現——其實都是這條管線中某個特定階段做的事。知道哪個階段負責哪件工作,能讓你問對問題。一個誠實的提醒:真實的編譯器比三個乾淨的方塊亂得多。階段之間會模糊、IR 會逐步降階為好幾種形式,而單一次 -O2 執行可能多次造訪數十個 pass。
source.c --> [前端] --> IR --> [中端/最佳化器] --> IR' --> [後端] --> 機器碼 $ clang -S -emit-llvm source.c # 在前端+中端後停下,把 IR 以文字傾印出來
三個區段,IR 在它們之間流動。clang 讓你中途停下並檢視 IR。
管線是一個模型,不是硬性界線:實務編譯器會經過好幾層 IR 並重複造訪 pass,所以「一個前端、一個中端、一個後端」是教學上的簡化,不是字面上的三步驟程式。