當(dāng)前編譯器已經(jīng)能夠把很多C語(yǔ)言的源程序編譯成可以在java虛擬機(jī)上運(yùn)行的字節(jié)碼,但一直存在一個(gè)問(wèn)題是,編譯出的字節(jié)碼存有冗余語(yǔ)句,例如賦值語(yǔ)句: a = 1; 它編譯成java字節(jié)碼后情況如下:
aload 0
sipush 1
astore 0
假設(shè)變量a在虛擬機(jī)局部變量隊(duì)列中的存儲(chǔ)位置為0,那么上面代碼冗余之處在于多出了一條語(yǔ)句aload 0, 要給變量a賦值,只需下面兩條語(yǔ)句便足夠了。之所以產(chǎn)生冗余語(yǔ)句,是因?yàn)榫幾g器的實(shí)現(xiàn)有問(wèn)題,在編譯器解析代碼時(shí),一旦遇到變量名,它就會(huì)把該變量加載到虛擬機(jī)的執(zhí)行堆棧上,或者是解析到數(shù)字字符常量時(shí),它也會(huì)把字符代表的數(shù)值壓到堆棧上。解析數(shù)字字符常量和代碼變量的語(yǔ)法表達(dá)式是:
NUMBER -> UNARY
NAME -> UNARY
所以產(chǎn)生冗余語(yǔ)句的編譯器實(shí)現(xiàn)代碼如下:
public class UnaryNodeExecutor extends BaseExecutor implements IExecutorReceiver{
private Symbol structObjSymbol = null;
private Symbol monitorSymbol = null;
@Override
public Object Execute(ICodeNode root) {
....
case CGrammarInitializer.Number_TO_Unary:
text = (String)root.getAttribute(ICodeKey.TEXT);
boolean isFloat = text.indexOf('.') != -1;
if (isFloat) {
value = Float.valueOf(text);
root.setAttribute(ICodeKey.VALUE, Float.valueOf(text));
} else {
value = Integer.valueOf(text);
root.setAttribute(ICodeKey.VALUE, Integer.valueOf(text));
}
ProgramGenerator.getInstance().emit(Instruction.SIPUSH, "" + value);
break;
case CGrammarInitializer.Name_TO_Unary:
symbol = (Symbol)root.getAttribute(ICodeKey.SYMBOL);
if (symbol != null) {
root.setAttribute(ICodeKey.VALUE, symbol.getValue());
root.setAttribute(ICodeKey.TEXT, symbol.getName());
ICodeNode func = CodeTreeBuilder.getCodeTreeBuilder().getFunctionNodeByName(symbol.getName());
if (func == null && symbol.getValue() != null) {
ProgramGenerator generator = ProgramGenerator.getInstance();
int idx = generator.getLocalVariableIndex(symbol);
generator.emit(Instruction.ILOAD, "" + idx);
}
}
break;
....
}
}
上面代碼是編譯器解析變量名和數(shù)字字符常量的地方,其中有一部分代碼是使用sipush指令把數(shù)字常量壓入堆棧或是通過(guò)iload指令把變量從隊(duì)列加載到堆棧的,產(chǎn)生冗余語(yǔ)句的也正是這部分代碼,要消除冗余,我們需要把帶有g(shù)enerator.emit的語(yǔ)句給注釋掉。
為了保證代碼修改不影響編譯器的正確性,我們還需對(duì)相關(guān)部分進(jìn)行修改。編譯器解析賦值語(yǔ)句,例如 a = 1;是通過(guò)NoCommaExprExecutor實(shí)現(xiàn)的,所以該執(zhí)行器的代碼需要做相應(yīng)修改如下:
public class NoCommaExprExecutor extends BaseExecutor{
ExecutorFactory factory = ExecutorFactory.getExecutorFactory();
ProgramGenerator generator = ProgramGenerator.getInstance();
@Override
public Object Execute(ICodeNode root) {
....
child = root.getChildren().get(0);
String t = (String)child.getAttribute(ICodeKey.TEXT);
IValueSetter setter;
setter = (IValueSetter)child.getAttribute(ICodeKey.SYMBOL);
child = root.getChildren().get(1);
//newly add
value = child.getAttribute(ICodeKey.SYMBOL);
if (value == null) {
value = child.getAttribute(ICodeKey.VALUE);
}
try {
setter.setValue(value);
} catch (Exception e) {
....
}
....
}
}
如果賦值語(yǔ)句形式為 a = b; 也就是用一個(gè)變量給另一個(gè)變量賦值,那么通過(guò)ICodeKey.Symbol 就可以得到變量b對(duì)應(yīng)的Symbol對(duì)象,如果賦值語(yǔ)句形式為 a = 1; 那么ICodeKey.VALUE 就會(huì)把數(shù)值1返回給變量value, setter對(duì)應(yīng)的是變量a的Symol對(duì)象,調(diào)用其setValue函數(shù)完成賦值功能,因此我們需要進(jìn)入Symbol.java修改相應(yīng)代碼:
public void setValue(Object obj) {
....
ProgramGenerator generator = ProgramGenerator.getInstance();
if (obj instanceof Symbol) {
Symbol symbol = (Symbol)obj;
this.value = symbol.value;
int i = generator.getLocalVariableIndex(symbol);
generator.emit(Instruction.ILOAD, "" + i);
} else if (obj instanceof Integer){
Integer val = (Integer)obj;
generator.emit(Instruction.SIPUSH, "" + val);
this.value = obj;
}
....
}
執(zhí)行賦值操作時(shí),如果是用變量給變量賦值,那么編譯器生成iload語(yǔ)句,把用來(lái)賦值的變量加載到虛擬機(jī)的堆棧上,如果用數(shù)字字符常量賦值,那么需要使用sipush語(yǔ)句把該數(shù)值壓到堆棧上。 最后我們還需修改下實(shí)現(xiàn)printf函數(shù)調(diào)用的代碼,在clibcall.java里:
private void generateJavaAssemblyForPrintf(String argStr, int argCount) {
.....
ArrayList<Object> argList = FunctionArgumentList.getFunctionArgumentList().getFuncArgSymsList(true);
int localVariableNum = list.size();
int count = 0;
int argSymCount = 0;
while (count < argCount) {
Symbol argSym = (Symbol)argList.get(argSymCount);
argSymCount++;
int d = generator.getLocalVariableIndex(argSym);
generator.emit(Instruction.ILOAD, "" + d);
generator.emit(Instruction.ISTORE, "" + (localVariableNum + count));
count++;
}
....
}
這部分代碼用于把printf語(yǔ)句中的變量加載到虛擬機(jī)堆棧上,例如printf("value of a and b is %d, %d", a, b);
代碼會(huì)把變量a,b從虛擬機(jī)的局部變量隊(duì)列加載到堆棧上,以便打印輸出。經(jīng)過(guò)上面的修改后,在生成java字節(jié)碼時(shí),就不再會(huì)有冗余語(yǔ)句了。現(xiàn)在我們看看,如何把if else 這些分支控制語(yǔ)句轉(zhuǎn)換為java字節(jié)碼。
在jvm中,有專門用于比較大小然后跳轉(zhuǎn)到指定分支的指令,例如 Int1>=Int2 對(duì)應(yīng)的指令為if_icmpge, Int1> Int2對(duì)應(yīng)的指令為if_icmpgt, Int1<=Int2對(duì)應(yīng)的指令為if_icmplt,Int1<Int2對(duì)應(yīng)的指令為if_icmple,Int1==Int2 對(duì)應(yīng)的指令為if_icmpeq, Int1 != Int2 對(duì)應(yīng)指令為:if_icmpne。
要使用比較指令時(shí),需要把相互比較的對(duì)象壓到堆棧上,比較指令會(huì)把堆棧上的兩個(gè)對(duì)象取出,比較大小后,根據(jù)比較結(jié)果進(jìn)行代碼跳轉(zhuǎn),例如C語(yǔ)言代碼:
if ( 1 < 2) {
a = 1;
} else {
a = 2;
}
a = 3;
編譯成java字節(jié)碼后如下:
sipush 1
sipush 2
if_cmpge branch0
sipush 1
astore 0
goto out_branch0
branch0:
sipush 2
istore 0
out_branch0:
sipush 3
istore 0
要比較1和2大小,先要把兩個(gè)數(shù)值壓到堆棧上,C語(yǔ)言代碼使用的是1<2,編譯成字節(jié)碼時(shí),我們要使用指令if_cmpge 也就是>=, 于是當(dāng)堆棧上兩個(gè)數(shù)的值是大于等于關(guān)系時(shí),跳轉(zhuǎn)到branch0所在位置的代碼去執(zhí)行,要不然繼續(xù)往下執(zhí)行,然后通過(guò)goto 直接跳轉(zhuǎn)到out_branch0的位置去執(zhí)行代碼,上面指令執(zhí)行的分支跳轉(zhuǎn)邏輯與C語(yǔ)言是一致的。
如果有多個(gè)平行并列的if else, 我們則把branch 和 out_branch后面的0加1,變成branch1和out_branch1,需要注意的還有間套的if else, 例如下面的C代碼:
if (a > 1 ) {
if (b > 2) {
b = 5;
}
}
else {
b = 4;
}
我們編譯間套里面的if else時(shí),把內(nèi)部的ifelse對(duì)應(yīng)的分支名稱在前面加上i,比如一層間套,那么它對(duì)應(yīng)的就是ibranch0,兩層就是iibranch0,同理一層間套對(duì)應(yīng)iout_branch0,兩層就是iiout_branch0. 由于存在間套原因,ifelse語(yǔ)句編譯比較困難,且容易出錯(cuò)。我們看看實(shí)現(xiàn)編譯的代碼實(shí)現(xiàn),首先是修改program_generator.java:
public class ProgramGenerator extends CodeGenerator {
....
private int branch_count = 0;
private int branch_out = 0;
private String embedded = "";
public int getIfElseEmbedCount() {
return embedded.length();
}
public void incraseIfElseEmbed() {
embedded += "i";
}
public void decraseIfElseEmbed() {
embedded = embedded.substring(1);
}
public void emitBranchOut() {
String s = "\n" + embedded+"branch_out" + branch_out + ":\n";
this.emitString(s);
branch_out++;
}
public String getBranchOut() {
String s = embedded + "branch_out" + branch_out;
return s;
}
public String getCurrentBranch() {
String str = embedded + "branch" + branch_count;
return str;
}
public void increaseBranch() {
branch_count++;
}
public String getAheadBranch(int ahead) {
String str = embedded + "branch" + branch_count + ahead + ":";
this.emitString(str);
return str;
}
....
}
如果編譯時(shí)發(fā)現(xiàn)有ifelse間套,那么需要調(diào)用getIfElseEmbedCount這樣使得編譯出的跳轉(zhuǎn)分支名稱前面能加上一個(gè)字母i,完成間套后要調(diào)用decraseIfElseEmbed(), 其余接口調(diào)用都是用來(lái)編譯ifelse跳轉(zhuǎn)時(shí)的分支名稱的。
如果ifelse 在代碼中一起出現(xiàn)時(shí),ElseStatementExecutor會(huì)被執(zhí)行,如果代碼中只有if出現(xiàn)時(shí),那么只有IfStatementExecutor會(huì)被執(zhí)行,我們看看相關(guān)代碼:
public class ElseStatementExecutor extends BaseExecutor {
private ProgramGenerator generator = ProgramGenerator.getInstance();
@Override
public Object Execute(ICodeNode root) {
BaseExecutor.inIfElseStatement = true;
//先執(zhí)行if 部分
ICodeNode res = executeChild(root, 0);
BaseExecutor.inIfElseStatement = false;
String branch = generator.getCurrentBranch();
branch = "\n" + branch + ":\n";
generator.emitString(branch);
if (generator.getIfElseEmbedCount() == 0) {
generator.increaseBranch();
}
Object obj = res.getAttribute(ICodeKey.VALUE);
if ((Integer)obj == 0 || BaseExecutor.isCompileMode) {
generator.incraseIfElseEmbed();
//if 部分沒(méi)有執(zhí)行,所以執(zhí)行else部分
res = executeChild(root, 1);
generator.decraseIfElseEmbed();
}
copyChild(root, res);
generator.emitBranchOut();
return root;
}
}
在編譯ifelse時(shí),如果if條件不成立就會(huì)跳轉(zhuǎn)到else部分,我們用'branchX'來(lái)表示else部分代碼分支開(kāi)始之處,由于編譯器在執(zhí)行ifelse語(yǔ)句時(shí),IfStatementExecutor先會(huì)被執(zhí)行,當(dāng)它執(zhí)行時(shí)需要知道當(dāng)前代碼是ifelse還是僅僅包含if語(yǔ)句,如果inIfElseStatement設(shè)置成true,那表明當(dāng)前代碼是ifelse形式,如果是false表明當(dāng)前代碼是if形式,兩種形式不同,輸出的字節(jié)碼就不同。在輸出else部分的指令時(shí),編譯器先把else部分的代碼分支名稱輸出來(lái)。else之后的代碼就是branch_outX分支所對(duì)應(yīng)的代碼,如果if條件成立,那么if接下來(lái)的指令會(huì)被執(zhí)行,執(zhí)行完后直接通過(guò)goto跳轉(zhuǎn)到branch_outX部分,避開(kāi)else部分指令的執(zhí)行。
在看看IfStatementExecutor的實(shí)現(xiàn):
public class IfStatementExecutor extends BaseExecutor {
@Override
public Object Execute(ICodeNode root) {
ProgramGenerator generator = ProgramGenerator.getInstance();
ICodeNode res = executeChild(root, 0);
Integer val = (Integer)res.getAttribute(ICodeKey.VALUE);
copyChild(root, res);
if ((val != null && val != 0) || BaseExecutor.isCompileMode) {
generator.incraseIfElseEmbed();
boolean b = BaseExecutor.inIfElseStatement;
BaseExecutor.inIfElseStatement = false;
executeChild(root, 1);
BaseExecutor.inIfElseStatement = b;
generator.decraseIfElseEmbed();
}
if (BaseExecutor.inIfElseStatement == true) {
String branchOut = generator.getBranchOut();
generator.emitString(Instruction.GOTO.toString() + " " + branchOut);
} else {
String curBranch = generator.getCurrentBranch();
generator.emitString(curBranch + ":\n");
}
return root;
}
}
在編譯if部分的指令時(shí),如果沒(méi)有else部分,那么就不需要輸出goto指令,執(zhí)行完if部分的代碼后,繼續(xù)往下執(zhí)行就可以,如果有else部分,那么需要輸出goto指令,越過(guò)else部分的代碼。
在編譯if部分的代碼時(shí),一定要調(diào)用incraseIfElseEmbed,因?yàn)閕f內(nèi)部很可能會(huì)出現(xiàn)ifelse的間套,同理在編譯else部分的代碼時(shí),也要調(diào)用這個(gè)接口,因?yàn)閑lse部分也會(huì)出現(xiàn)ifelse間套。
完成上面的代碼后,我們嘗試編譯下面的C語(yǔ)言代碼:
void main () {
int a;
int b;
a = 2;
b = 3;
if (a > 1 ) {
if (b > 2) {
b = 5;
}
}
else {
b = 4;
}
printf("value of b is :%d", b);
}
上面代碼中存有ifelse間套,我們看看編譯出來(lái)的java字節(jié)碼時(shí)怎樣的,運(yùn)行修改代碼后的編譯器,然后輸入上面C語(yǔ)言代碼,得到的編譯結(jié)果如下:
.class public CSourceToJava
.super java/lang/Object
.method public static main([Ljava/lang/String;)V
sipush 2
istore 1
sipush 3
istore 0
iload 1
sipush 1
if_icmple branch0
iload 0
sipush 2
if_icmple ibranch0
sipush 5
istore 0
ibranch0:
goto branch_out0
branch0:
sipush 4
istore 0
branch_out0:
iload 0
istore 2
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "value of b is :"
invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V
getstatic java/lang/System/out Ljava/io/PrintStream;
iload 2
invokevirtual java/io/PrintStream/print(I)V
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "
"
invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V
return
.end method
.end class
在ifcmple指令前,iload 1表示把變量a加入堆棧,sipush 1把數(shù)字常量1壓入堆棧,如果變量a的值小于1的話則跳轉(zhuǎn)到branch0處執(zhí)行,branch0處的指令作用是把數(shù)值4賦值給變量b, 如果a的值大于1,則繼續(xù)往下執(zhí)行,iload 0表示把變量b加載到堆棧,sipush 2表示把數(shù)值2壓入堆棧如果變量b的值小于數(shù)值2則跳轉(zhuǎn)到ibranch0執(zhí)行,這個(gè)分支名稱前面的i就是因?yàn)閕felse間套而添加的。branch_out0處指令的意識(shí)是通過(guò)printf把相關(guān)信息打印出來(lái)。
把編譯出來(lái)的java匯編轉(zhuǎn)換成二進(jìn)制字節(jié)碼運(yùn)行后結(jié)果如下:
從結(jié)果上看,打印出來(lái)的b的值是5,由此可見(jiàn)我們編譯輸出的結(jié)果應(yīng)該是正確的。ifelse編譯由于需要考慮到間套,所以邏輯上比較復(fù)雜,具體的理解需要通過(guò)視頻講解后,并親手調(diào)試代碼才好掌握,請(qǐng)參看視頻用java開(kāi)發(fā)C語(yǔ)言編譯器
當(dāng)前我們的編譯方法面對(duì)更復(fù)雜的ifelse間套時(shí)可能還會(huì)有問(wèn)題,基于盡可能簡(jiǎn)單原則,我們先這么做,以后遇到問(wèn)題時(shí)才進(jìn)一步完善。
更多技術(shù)信息,包括操作系統(tǒng),編譯器,面試算法,機(jī)器學(xué)習(xí),人工智能,請(qǐng)關(guān)照我的公眾號(hào):