中端(compiler middle end)
假設前端交給你一份正確但天真的程式描述——每一個乘法、每一次多餘的載入,都照字面寫著。通常有快得多的方式算出同樣的答案。中端就是編譯器中專門尋找這些「更快但等價」版本的部分。用白話講,它就是最佳化器。
中端完全在中介表示法(IR)上工作,從不碰原始文字或機器指令。它組織成一連串的 pass,每個 pass 是一個自成一體的轉換:一個摺疊常數、一個刪除死碼、一個把工作搬出迴圈、一個內聯小函式。pass 在管線中執行並互相餵料——內聯一個函式會暴露出新的可摺疊常數、摺疊常數又暴露出新的死碼——所以最佳化器會反覆繞行它的 pass,持續尋找機會。整個工作受一條規則約束:每次改寫都必須保留程式的可觀察行為。最佳化器可以改變答案如何被算出,但絕不改變答案本身(這就是 as-if 規則)。
它重要在於:這是 -O2 下你大部分速度的來源,也是令人意外的行為所在:中端會「刪除」程式碼、「重排」程式碼,並積極利用前端給出的承諾——你的程式沒有未定義行為。因為它與語言、目標都無關,一個好的中端(如 LLVM 的)能服務多種語言與多種晶片。一個提醒:更多 pass 不一定產生更快的程式碼——最佳化器用啟發式估計成本,它可能猜錯,這正是為什麼「量測」勝過「假設」。
// 中端之前: // 跑過幾個 pass 之後: int n = 10; int total = 36; // n*n 被摺疊、迴圈 int total = 0; // 直接化簡成答案 for (int i = 0; i < 4; i++) total += i + n;
最佳化器可以在編譯期算出常數迴圈的答案,因此執行期不再有任何迴圈執行。
最佳化器受 as-if 規則約束,而非受你的意圖約束:它只需保留可觀察行為,因此任何它能證明是死的、多餘的或不可達的(包括只因為「到達它會是未定義行為」才不可達的程式碼)都可以被刪除。