6.3.2 由EmitAssign函數產生算術運算的匯編代碼
在這1小節中,我們要討論的中間指令形如“t1: a+b;”或“t2:&number”,這些指令用于進行1元或2元算術運算,并把運算結果保存在臨時變量t1或t2中。UCC中間指令的格式以下所示:
<運算符opcode,目的操作數DST,源操作數SRC1,源操作數SRC2>
<ADD,t1,a,b> // t1: a+b;
<ADDR,t2,number,NULL> // t2: &number;
由于在1條x86匯編指令中,最多只允許出現2個操作數,而中間指令“DST:SRC1+SRC2”有3個操作數,我們需要產生多條x86匯編指令來實現該中間指令。在匯編指令中,整數加法運算對寄存器沒甚么特別要求,我們可按以下步驟來處理:
(1) 調用AllocateReg函數順次為SRC1、SRC2和DST分配寄存器。DST是用于保存運算結果的臨時變量,必定可分配到1個寄存器,無妨記為R0。而如果SRC1和SRC2不是臨時變量,則沒有分配到寄存器。
(2) 若DST和SRC1對應的寄存器不1樣,我們可產生1條movl指令,把SRC1的值傳送到寄存器R0中。
(3) 產生加法指令,進行SRC2和R0的加法,并把結果存于寄存器R0中。
按這樣的思路,我們可為“t1 : a+b;”產生以下匯編代碼:
movl a, %eax
addl b, %eax
而形如“t2: &number”的中間指令只有兩個操作數,在上述第(1)步中,我們就沒必要為
SRC2分配寄存器,其他的步驟類似,我們可為“t2: &number”產生以下匯編代碼:
leal a, %ecx
不過,有些x86匯編指令對寄存器有特定的要求,比如整數的乘法運算就要求源操作數SRC1的值被加載到寄存器eax中。而整數的除法運算或取余運算,要求源操作數SRC1的值被加載到eax中,如果要進行的是有符號數的除法運算,則寄存器edx的所有位都被設置為SRC1的符號位;如果要進行的是無符號數的除法運算,則寄存器edx被置為全0。例如我們可為中間指令“t3: a /b;”產生以下匯編代碼,其中a和b為有符號整數。
movl a, %eax //把SRC1加載到eax
cdq //把符號位擴大到edx寄存器
idivl b //進行除法運算[edx: eax] / SRC2,商存于eax,余數存于edx,
//此時eax中的值就是臨時變量t3的值
在x86“左移或右移”的匯編指令中,如果要把左移或右移的位數寄存于寄存器中,則必須使用單字節寄存器cl,以下所示:
int a, c; char len = 3;
c = a <<len;
////中間代碼//////////////
t4 : (int)(char)len; //把char提升為int
t5 : a << t4;
c = t5;
對應的匯編代碼以下所示,我們可以看到,在匯編指令“shll %cl,%edx”中,我們是用單字節寄存器cl來寄存操作數len的值。
movsbl len, %eax // t4 : (int)(char)len;
movl %eax, %ecx //t5: a << t4
movl a, %edx
shll %cl, %edx
movl %edx, c // c = t5;
有了這些基礎后,我們可以來討論1下“為算術運算生成匯編代碼”的函數EmitAssign,如圖6.3.5所示。第47至56行用于處理對寄存器沒有特別要求的2元運算,形如“DST:SRC1+SRC2;”。第44至45行用于處理形如“DST: ~SRC1”的1元運算,此時我們沒必要為SRC2分配寄存器。第33至43行用于為左移或右移指令里的SRC2分配寄存器ecx,并在第39行把SRC2加載寄存器ecx,以后在第41行把SRC2改成單字節寄存器cl,即4字節寄存器ecx的低8位。
圖6.3.5 EmitAssign()
圖6.3.5第8至32行用于為整數乘法、除法和取余運算產生匯編指令,第11至16行把SRC1的值暫存于寄存器eax中,第17行把edx寄存器的值回寫到內存中,我們會在[edx:eax]中寄存經符號位擴大后的SRC1。第19至24行用于為SRC2分配必要的寄存器,第26行產生匯編指令來進行乘法或除法運算,以后寄存器eax中寄存的是“乘法運算的結果”或“除法運算的商”,而寄存器edx中寄存的是“除法運算的余數”,第27至31行用于記錄“寄存運算結果的臨時變量DST對應的寄存器為edx或eax”。
由于浮點數運算的匯編指令與整數不同,我們需要在圖6.3.5第4行調用EmitX87Assign函數,來對浮點數算術運算進行處理。我們先舉1個例子來講明,對形如“DST: SRC1+SRC2”的2元浮點數運算來講,我們可按以下步驟來生成匯編代碼:
(1)若SRC1不在x87棧頂寄存器中,則先對x87棧頂寄存器進行必要的回寫,然后把SRC1加載到x87棧頂寄存器中。
(2)對x87棧頂寄存器與SRC2進行加法運算,結果存于x87棧頂寄存器中。
按這個思路,我們可為浮點數中間指令“t6: d+e”產生以下匯編代碼:
flds d //把浮點數d從內存加載到x87棧頂寄存器
fadds e //完成加法運算后,x87棧頂寄存器即為t6的值
而對形如“DST:-SRC1”的1元運算來講,我們在上述第(2)步中,只要對x87棧頂寄存器進行1元運算便可,運算后的結果仍存于x87棧頂寄存器中。例如,我們可為“t7: -d”產生以下匯編代碼。
flds d //把浮點數d從內存加載到x87棧頂寄存器
fchs //把符號位取反,x87棧頂寄存器即為t7的值
現在,我們可以來分析1下“用于產生這些浮點運算匯編代碼”的函數EmitX87Assign,如圖6.3.6所示。如果SRC1還未加載到x87棧頂寄存器中,我們就通過第5行進行必要的回寫,然后在第6行把SRC1從內存加載到x87棧頂寄存器中。如果SRC1的值已在x87棧頂寄存器中,此時SRC1必為臨時變量,若SRC1還要在后續的中間指令中被使用,我們需要在第10行把SRC1的值回寫到內存中,由于進行浮點運算后,棧頂寄存器保存的是DST的值,而非SRC1的值。如果操作數SRC2就是SRC1,由于遇到形如第13行的模板“faddl %2”時,我們要把x87棧頂寄存器與位于內存中的SRC2進行加法運算,而此時SRC2就是SRC1,因此我們也要在第10行把SRC1的值回寫到內存中。第15行根據匯編指令模板,調用PutASMCode函數產生浮點數運算的匯編指令。
圖6.3.6 EmitX87Assign()
上一篇 【hdoj 1005】有限狀態機
下一篇 Caused by: java.lang.ClassNotFoundException: org.aspectj.lang.annotation.Around