首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C >> C 語言入門

編譯器核心技術:UCC 編譯器寄存器分配與匯編代碼生成深度剖析

     編輯:C 語言入門

背景:為什麼需要寄存器分配?

CPU 速度遠超記憶體,有效利用寄存器可大幅減少記憶體存取。UCC 編譯器在中間代碼階段用臨時變數 t 保存公共子運算式(如 t: a+b),生成匯編時盡量將這些值放入寄存器重用。

x86 寄存器資源與分配策略

  • 32 位 x86 可用通用寄存器:eax, ebx, ecx, edx, esi, edi(6 個),esp/ebp 保留給堆疊幀。
  • 臨時變數多於寄存器時,需將部分寄存器值回寫到記憶體(Spill),騰出寄存器。
  • UCC 僅為編譯器產生的臨時變數分配寄存器,簡化資料流分析;程式設計師命名的變數不參與分配,避免別名分析複雜度。

實例演示:公共子運算式重用

原始碼:

s3 = a + b;
s4 = c + d;
s5 = (a + b) + (c + d);  // 重用 t0=a+b, t1=c+d
s6 = (a + b) * (c + d);  // 再次重用

中間代碼產生 3 個臨時變數(t0, t1, t2),對應 12 位元組堆疊空間。匯編代碼中:

  • eax 分配給 t0 (a+b),全程保持直到不再使用。
  • edx 分配給 (a+b)+(c+d)。
  • 函數 g 中因基本塊結束,eax 可重新分配給新運算式。

核心分配函數 GetRegInternal

  1. 參數 width 指定寬度(1/2/4 位元組),對應不同寄存器集合。
  2. 優先尋找空閒寄存器(FindEmptyReg),排除 esp/ebp 且未被當前指令佔用。
  3. 無空閒時呼叫 SelectSpillReg 選擇犧牲寄存器:依據對應臨時變數剩餘引用次數總和,回寫引用最少者(類似 LRU)。
  4. SpillReg 執行回寫:僅當寄存器值已修改(needwb)且後續仍需使用(ref > 0)才產生存儲指令。

浮點協處理器 x87 簡化處理

僅使用棧頂寄存器保存單一浮點臨時變數,指標 X87Top 追蹤。控制流離開基本塊或函數調用前呼叫 SaveX87Top 回寫。

基本塊代碼生成 EmitBlock

  • 遍歷基本塊中間代碼,呼叫 EmitIRInst 透過查表分派具體生成函數(如 EmitJump、EmitCall)。
  • 控制流轉移(跳轉、函數調用)前必須回寫寄存器:跳轉回寫全部 6 個寄存器(ClearRegs),函數調用依呼叫慣例僅回寫 eax, ecx, edx。
  • 每條中間代碼生成後遞減操作數引用計數,影響後續溢出選擇。

條件運算式的跨基本塊臨時變數處理

d = (a > 0 ? b : c) + 1;
// 中間碼:
// if (a <= 0) goto BB4;
// BB3: t0 = b; goto BB5;
// BB4: t0 = c;
// BB5: t1 = t0 + 1; d = t1;

t0 在 BB3/BB4 分別賦值,離開塊時必須回寫,確保 BB5 讀到正確值。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved