編譯器 pass(compiler pass)
想像編輯一篇文章時,一次只做一輪聚焦的掃過:一輪改拼字、另一輪精簡冗句、再一輪查核事實。每一輪只有一個工作,並把更乾淨的草稿交給下一輪。編譯器 pass 正是如此:一個單一、自成一體的步驟,走過程式的 IR 去做一件特定的事。
pass 有兩種口味。轉換 pass(transform pass)改寫 IR——例如內聯器、死碼消除器、迴圈展開器,各自吃進 IR、產出改變後的 IR。分析 pass(analysis pass)不改變任何東西;它計算轉換 pass 需要的事實,像「哪些值是常數」或「哪些定義能到達這個使用」。最佳化器建構成一條 pass 管線:一份有序的 pass 清單,一個接一個執行,分析餵給轉換。順序與重複很重要——內聯一個函式會為下一個摺疊 pass 暴露常數,所以最佳化器會多次執行某些 pass,追逐每一個 pass 揭露的機會。
它重要在於:模組化的 pass 設計正是編譯器易於維護、也讓你能推理它的原因:每個 pass 只做一件可理解的事,你甚至能要求編譯器只跑某個指定的 pass。它也解釋了最佳化的成本——跑更多 pass(並再跑一次)要花編譯時間。一個提醒:一個 pass 只有在保留可觀察行為時才正確,而順序並非隨意;排錯或有 bug 的 pass 可能讓程式碼變慢,罕見情況下甚至誤編譯,這正是為什麼編譯器謹慎地固定並測試它們的預設管線。
$ opt -passes='instcombine,sroa,gvn' in.ll -S -o out.ll # 依序執行三個指定的 pass # instcombine: 簡化指令 | sroa: 拆解聚合型別 | gvn: 移除多餘計算
LLVM 的 opt 工具讓你依名稱、依你給的順序執行自訂的 pass 管線。
pass 順序是關鍵的,不是裝飾:同樣的 pass 以不同順序執行會得到不同結果,而最佳化器刻意重複某些 pass,因為一個轉換會為另一個解鎖工作。