自制编译器:后端代码生成
2014-10-06 10:42
309 查看
所谓的编译器后端的作用就是将语法树翻译成目标机器码。所谓目标机器码,考虑到直接翻译成具体平台(如X86,ARM等)过于复杂,因此先设计一个虚拟机,并翻译成这个虚拟机的机器码。
对于虚拟机以及其指令格式可参考这篇文章http://blog.csdn.net/roger__wong/article/details/8947720,如何去尝试实现这个虚拟机是在我的另外一个系列的博客里进行论述。
本篇文章从以下是那个方面来论述:后端架构与关键数据结构、节点翻译方式。
1、后端架构和关键数据结构
后端接受前端的语法树作为输入,对于其每一个节点根据节点类型的不同产生不同的代码。但在实现过程中为了简单方便,我并没有把后端抽象出一个单独的模块,而是在语法树每一个节点的基础上增加了一个genCode方法,通过调用这个方法来生成该节点及其所有孩子节点(通过递归)的代码。
其次编译器后端直接生成Class文件(文件结构也在上文提到的博客中有说明),程序中后端首先构造一个ClassOfClass的实体,然后再调用此类的方法生成Class文件:
[java] view
plaincopy
public class ClassOfClass {
public static int isPublic=1;
public static int isStatic=2;
public ArrayList<field> fields;
public ArrayList<function> functions;
public ArrayList<String> constPool;
public String name;
public ClassOfClass()
{
constPool=new ArrayList<String>();
fields=new ArrayList<field>();
functions=new ArrayList<function>();
}
public void WriteClassFile(String path)
{
try {
PrintWriter pw=new PrintWriter(new FileOutputStream(path));
pw.println(name);
pw.println(fields.size());
for(field f:fields)
{
pw.println(f.toString());
}
pw.println(functions.size());
for(function f:functions)
{
pw.println(f.toString());
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
其中field结构:
[java] view
plaincopy
public class field {
public int head;
public String type;
public String fieldname;
@Override
public String toString()
{
StringBuffer sb=new StringBuffer();
sb.append(head);
sb.append(" ");
sb.append(type);
sb.append(" ");
sb.append(fieldname);
return sb.toString();
}
}
其中Function结构
[java] view
plaincopy
public class function {
public int head;
public String rettype;
public int argnum;
public ArrayList<String> args;
public ArrayList<Code> codes;
public function()
{
args=new ArrayList<String>();
codes=new ArrayList<Code>();
}
@Override
public String toString()
{
StringBuffer sb=new StringBuffer();
sb.append(head);
sb.append(" ");
sb.append(rettype);
sb.append(" ");
sb.append(args.size());
sb.append(" ");
for(String s:args)
{
sb.append(s+" ");
}
sb.append("\r\n");
sb.append("{");
for(int i=0;i<=codes.size()-1;i++)
{
sb.append(i+":"+codes.get(i).toString()+"\r\n");
}
sb.append("\r\n");
sb.append("}");
return sb.toString();
}
}
其中Code结构
[java] view
plaincopy
public class Code {
public int Opcode;//操作码
public ArrayList<String> Operands;
public Code(int op)
{
Operands=new ArrayList<String>();
Opcode=op;
}
@Override
public String toString()
{
StringBuffer sb=new StringBuffer();
sb.append(Opcode);
sb.append(" ");
for(String s:Operands)
{
sb.append(s+" ");
}
return sb.toString();
}
[java] view
plaincopy
}
完成一个Class的后端代码的生成工作,只需要调用语法树根节点(classdef)的genCode即可完成,因为根节点会不断的递归调用其子树的genCode方法,因此在递归调用的时候需要某些机制进行各方法之间信息的传递,这里建立新类BackendClassManager来进行信息的传递,消除耦合。
[java] view
plaincopy
public class BackendClassManager {
public static ClassOfClass cc=new ClassOfClass();//正在生成的class对象
public static function tFunc;//正在生成代码的函数
public static memberfuncdeclare mfc;//正在生成代码的语法树中的memberfuncdeclare节点
public static constructor ct;//正在生成代码的构造函数节点
public static HashMap<String,Integer> nameSlot=new HashMap<String,Integer>();//局部变量和局部变量表槽的对应hash
public static expr expr1,expr2;//正在翻译的 expr
public static Stack<ArrayList<Code>> loopcontinue=new Stack<ArrayList<Code>>();//在循环语句中出现的continue语句,用于回填地址
public static Stack<ArrayList<Code>> loopbreak=new Stack<ArrayList<Code>>();//在循环语句中出现的break语句,用于回填地址
public static void WriteToClassFile()
{
String path="E:/test.class";
cc.WriteClassFile(path);
}
public static void genCode(classdef cd)
{
cd.genCode();
}
}
cc代表目前正在编译的class。
tFunc代表正在生成代码的函数,也就是所有genCode的方法都要把生成的代码填充到tFunc的codes域中。
memberfuncdeclare代表语法树中正在生成后端代码的memberfuncdeclare节点,该节点和其子树包含此函数的所有代码。
nameSlot对应源码中出现的局部变量与目标代码中的局部变量表槽的一个关系,因为目标代码将不再会出现局部变量名这个概念,所以需要一个hash在编译时进行对应。
expr1和expr2对应正在翻译的expr,在某些运算符需要进行类型转换时需要用到正在翻译的表达式的信息。
loopcontinue和loopbreak用于循环语句的地址回填,因为一个循环在翻译的过程中,其break需要跳转到的地址是还未确定的,需要整个循环翻译完之后对目标地址进行回填。continue虽然可以确定目标地址,但是在continue对应的语句stmt节点无法知道循环的开始地址,需要通过某些机制让stmt节点知道此循环的开始地址,因此也把continue语句进行回填处理。
除此之外还是用了一个类CodeGenHelper来封装一些常用的代码序列,比如i2d,jmp等,目的是为了简化之后的目标代码的生成。
2、节点代码生成
按照从顶至低的方式依次分析。
(1)classdef节点
[java] view
plaincopy
public void genCode() {
BackendClassManager.cc.name=cn.toString();
cb.genCode();
}
代码很简单,首先把正在编译的类名设置成classdef中出现的类名,然后调用classbody的genCode方法。
(2)classbody
[java] view
plaincopy
public void genCode() {
if(cb!=null)
{
cb.genCode();
}
}
依然很简单,如果classmembers不为空,则调用classmembers的genCode方法。
值得注意的是,这些方法本身并没有生成目标代码乃是因为一个类的定义本身并不包含任何逻辑,而代码本身是对逻辑的阐述,所以在类声明、函数声明、成员变量声明等没有生成任何有意义的代码也就不值得奇怪了。
(3)classmembers
[java] view
plaincopy
public void genCode() {
if(type==0)
{
((membervardeclare)declare).genCode();
}
else if(type==1)
{
((memberfuncdeclare)declare).genCode();
}
else if(type==2)
{
ct.genCode();
}
if(cm!=null)
{
cm.genCode();
}
}
根据此classmembers的类型,对membervardeclare、memberfuncdeclare、constructor调用genCode方法,最后对下一个classmemebers调用genCode方法,这和本系列第一篇博客中的递推式是对应的。
(4)membervardeclare
[java] view
plaincopy
public void genCode() {
// TODO Auto-generated method stub
field fd=new field();
ArrayList<field> fs=BackendClassManager.cc.fields;
if(af.toString().equals("public"))
{
fd.head+=ClassOfClass.isPublic;
}
if(isstatic==true)
{
fd.head+=ClassOfClass.isStatic;
}
fd.type=tp.toString();
fd.fieldname=ID.toString();
fs.add(fd);
}
依然没有任何代码生成,只是将成员变量的信息放到ClassOfClass对象的fields域中。
(5)memberfundeclare
[java] view
plaincopy
public void genCode() {
function func=new function();
BackendClassManager.tFunc=func;
BackendClassManager.cc.functions.add(func);
BackendClassManager.ct=null;
BackendClassManager.mfc=this;
BackendClassManager.nameSlot=new HashMap<String,Integer>();
if(af.toString().equals("public"))
{
func.head+=ClassOfClass.isPublic;
}
func.rettype=tp.toString();
if(da!=null)
{
ArrayList<type> al=da.gettypelist();
func.argnum=al.size();
for(type tp:al)
{
func.args.add(tp.toString());
}
ArrayList<id> tal=da.getidlist();
BackendClassManager.nameSlot.put("this", 0);
for(int i=0;i<=tal.size()-1;i++)
{
BackendClassManager.nameSlot.put(tal.get(i).toString(), i+1);
}
}
else
{
func.argnum=0;
}
fb.genCode();
}
成员函数的稍微有点复杂。
首先建立一个新的function对象,并把该对象设置为BackendClassManager.tFun,说明之后所有genCode都为这个函数生成的代码,并把这个函数加到classofClass对象的functions域中;判断该函数的返回值类型、是否是public、是否是静态,并把相关信息记录到function对象中重置nameslot,将函数所有参数压入nameslot表中,并注意this也当做参数放入表中,然后调用functionbody的genCode,为该函数生成代码。
(6)constructor
[java] view
plaincopy
public void genCode() {
function func=new function();
BackendClassManager.tFunc=func;
BackendClassManager.ct=this;
BackendClassManager.nameSlot=new HashMap<String,Integer>();
if(af.toString().equals("public"))
{
func.head+=ClassOfClass.isPublic;
}
func.rettype="NULL";
if(da!=null)
{
ArrayList<type> al=da.gettypelist();
func.argnum=al.size();
func.argnum=al.size();
for(type tp:al)
{
func.args.add(tp.toString());
}
ArrayList<id> tal=da.getidlist();
BackendClassManager.nameSlot.put("this", 0);
for(int i=0;i<=tal.size()-1;i++)
{
BackendClassManager.nameSlot.put(tal.get(i).toString(), i+1);
}
}
else
{
func.argnum=0;
}
ss.genCode();
}
(7)funcbody
[java] view
plaincopy
public void genCode() {
// TODO Auto-generated method stub
ss.genCode();
returnexpr.genCode();
ArrayList<Code> al=BackendClassManager.tFunc.codes;
Code code=new Code(0x19);
al.add(code);
}
首先stmts生成代码,然后为返回表达式生成代码,最后在codes中加入Code(0x19),也就是返回指令。
值得注意的是,这里做了一个约定,在expr的genCode中,总是把该expr的结果放在expr代码执行后的栈顶,因此函数返回时实际上返回的是栈顶元素的值。
(8)stmts
stmts逻辑上代表一个语句块或一组语句块,对应的生成式和在节点中使用的type如下:
stmts --> NUL| type-->0
stmt stmts| type-->1
if(expr) { stmts} stmts| type-->2
if(expr) {stmts} else {stmts} stmts| type-->3
while(expr) { stmts} stmts| type-->4
接下来给出生成后端代码的代码:
[html] view
plaincopy
public void genCode() {
if(type==1)
{
st.genCode();
stmts1.genCode();
}
else if(type==2)
{
condition.genCode();
Code code=new Code(0x18);
BackendClassManager.tFunc.codes.add(code);
stmts1.genCode();
code.Operands.add(String.valueOf(BackendClassManager.tFunc.codes.size()));
stmts3.genCode();
}
else if(type==3)
{
condition.genCode();
Code code=new Code(0x18);
code.Opcode=0x18;
BackendClassManager.tFunc.codes.add(code);
stmts1.genCode();
Code code1=new Code(0x01);
code1.Operands.add(String.valueOf(0));
BackendClassManager.tFunc.codes.add(code1);
Code code2=new Code(0x18);
BackendClassManager.tFunc.codes.add(code2);
code.Operands.add(String.valueOf(BackendClassManager.tFunc.codes.size()));
stmts2.genCode();
code2.Operands.add(String.valueOf(BackendClassManager.tFunc.codes.size()));
stmts3.genCode();
}
else if(type==4)
{
ArrayList<Code> albreak=new ArrayList<Code>();
ArrayList<Code> alcontinue=new ArrayList<Code>();
BackendClassManager.loopbreak.add(albreak);
BackendClassManager.loopcontinue.add(alcontinue);//循环入口,首先判断expr
int pos=BackendClassManager.tFunc.codes.size();
condition.genCode();
//跳转指令
Code code=new Code(0x18);
BackendClassManager.tFunc.codes.add(code);
//循环体
stmts1.genCode();
// code.Operands.add(String.valueOf(end));//表达式回填
Code code1=new Code(0x01);
code1.Operands.add(String.valueOf(0));
BackendClassManager.tFunc.codes.add(code1);//压入0
Code code2=new Code(0x18);
code2.Operands.add(String.valueOf(pos));
BackendClassManager.tFunc.codes.add(code2);//跳转到循环入口
int end=BackendClassManager.tFunc.codes.size();
code.Operands.add(String.valueOf(end));//表达式回填
for(Code c:albreak)
{
c.Operands.add(String.valueOf(end));
}
for(Code c:alcontinue)
{
c.Operands.add(String.valueOf(pos));
}
BackendClassManager.loopbreak.pop();
BackendClassManager.loopcontinue.pop();
stmts3.genCode();
}
}
对于type=0,没必要生成任何代码;对于type=1,生成stmt的代码然后递归再生成stmts的代码;对于type=2,首先生成条件expr的代码,在这段代码执行过后,会将结果放在堆栈顶,然后加入code(0x18)进行跳转,但此刻的跳转地址还不能确定,因此先要生成stmts1的代码,之后回填跳转地址,再生成stmts3的代码;对于type=3,和type=2类似,但要在stmts1代码之后加入无条件跳转指令,跳转到else块后面,无条件跳转的方法是首先压入0,再使用0x18也就是ifz进行跳转;对于type=4要稍微复杂些,首先要计算循环开始的地址(包括判断expr),然后生成循环体代码,得到循环结束地址,再回填循环判断的相关代码,除此之外,还要回填此循环体内出现的所有break和continue语句的地址。
(9)stmt
首先给出语句stmt的生成式:
stmt --> continue;| type=0
break;| type=1
var-declare;| type=2
class-init;| type=3
setvalue;| type=4
expr;| type=5
接下来给出代码:
[java] view
plaincopy
public void genCode() {
ArrayList<Code> al=BackendClassManager.tFunc.codes;
if(type==0)
{
CodeGenHelper.JMP(al, 0);
BackendClassManager.loopcontinue.peek().add(al.get(al.size()-1));
}
if(type==1)
{
CodeGenHelper.JMP(al, 0);
BackendClassManager.loopbreak.peek().add(al.get(al.size()-1));
}
if(type==5)
{
ep.genCode();
}
if(type==2)
{
vc.genCode();
}
if(type==3)
{
ci.genCode();
}
if(type==4)
{
sv.genCode();
}
}
stmt代码比较简单,对于break和continue,只需要加入一个跳转语句,然后把此跳转语句加入到相应的列表里(因为循环是嵌套的,所以使用堆栈来表示这一关系),由外层循环体进行地址回填即可,使用了CodeGenHelper的JMP方法作为辅助函数;对于其它类型则调用其相应节点的genCode方法。
另外给出CodeGenHelper的JMP方法,封装了压入0和ifz两条指令:
[java] view
plaincopy
public static void JMP(ArrayList<Code> al,int pos)
{
Code code1=new Code(0x01);
code1.Operands.add(String.valueOf(0));
BackendClassManager.tFunc.codes.add(code1);//压入0
Code code2=new Code(0x18);
code2.Operands.add(String.valueOf(pos));
BackendClassManager.tFunc.codes.add(code2);//跳转
}
(10)classinit
递推式:class-init --> ids = new
type ( args )| type[expr]
type: 0 1
代码:
[java] view
plaincopy
public void genCode() {
ArrayList<Code> al=BackendClassManager.tFunc.codes;
ag.genCode();
if(type==0)
{
Code code=new Code(0x1C);
code.Operands.add(tp.toString());
al.add(code);
}
else
{
Code code=new Code(0x1D);
code.Operands.add(tp.toString());
al.add(code);
}
if(is.type==1)
{
int slot=CodeGenHelper.SearchByName(is.ID.toString());
Code code=new Code(0x05);
code.Operands.add(String.valueOf(slot));
al.add(code);
}
if(is.type==2)
{
if(is.getLastIDS().type!=3)
{
CodeGenHelper.PutObjectToStack(al, is);
CodeGenHelper.ChangeStackTopEle(al);
CodeGenHelper.CodePutField(al, is.getLastID().toString());
}
else
{
int value=CodeGenHelper.StoreToLocalTable(al);
CodeGenHelper.PutObjectToStack(al, is);
CodeGenHelper.CodeGetField(al, is.getLastID().toString());
int array=CodeGenHelper.StoreToLocalTable(al);
is.getLastIDS().EXPR.genCode();
CodeGenHelper.LoadToStack(al, array);
CodeGenHelper.LoadToStack(al, value);
Code code=new Code(0x26);
al.add(code);
}
}
if(is.type==3)
{
int slot=CodeGenHelper.StoreToLocalTable(al);
is.EXPR.genCode();
CodeGenHelper.LoadToStackByName(al, is.ID.toString());
CodeGenHelper.LoadToStack(al, slot);
Code code=new Code(0x26);
al.add(code);
}
}
首先ag.genCode()把所有参数压栈,然后根据类别判断是初始化对象还是数组来使用0x1C或者0x1D指令并把类型作为操作数,当此条指令执行完之后,堆栈顶此时放着返回的对象或数组的句柄,接下来的任务是将该句柄赋值给ids。
接着判断ids的类型,ids的推导式如下:
ids->
* id| type 1
id.ids| type 2
id[expr] type 3
this| type=4
如果type=1,则说明是一个局部变量,从nameSlot中找到该局部变量在局部变量表里的位置(相关操作封装到了CodeGenHelper的searchByName中),赋值给这个槽即可。
如果type=2,说明是某个对象的成员变量,则需要先把这个对象压入堆栈,再通过putfield指令给此对象赋值,若是某个对象的成员变量中的某个元素(即该成员变量是个数组),则需要使用给数组赋值的指令。
如果type=3,说明给数组某个元素赋值,需要先把下标计算出来,然后通过相应指令给数组赋值。
其中的很多操作都封装在了CodeGenHelper中。
(11)vardeclare
递推式:
var-declare --> type args|type[] args
代码:
[java] view
plaincopy
public void genCode() {
ArrayList<Code> al=BackendClassManager.tFunc.codes;
ArrayList<ids> idlist=ags.getidsList();
for(ids id:idlist)
{
BackendClassManager.nameSlot.put(id.getLastID().toString(), BackendClassManager.nameSlot.size());
}
}
可以看到,在变量声明时并没有生成任何实际代码,只是在局部变量表中给其开辟了一个存储位置而已。
(12)setvalue
setvalue--> ids = expr
[java] view
plaincopy
public void genCode() {
ArrayList<Code> al=BackendClassManager.tFunc.codes;
ep.genCode();
if(is.type==1)
{
int slot=CodeGenHelper.SearchByName(is.ID.toString());
Code code=new Code(0x05);
code.Operands.add(String.valueOf(slot));
al.add(code);
}
if(is.type==2)
{
if(is.getLastIDS().type!=3)
{
CodeGenHelper.PutObjectToStack(al, is);
CodeGenHelper.ChangeStackTopEle(al);
CodeGenHelper.CodePutField(al, is.getLastID().toString());
}
else
{
int value=CodeGenHelper.StoreToLocalTable(al);
CodeGenHelper.PutObjectToStack(al, is);
CodeGenHelper.CodeGetField(al, is.getLastID().toString());
int array=CodeGenHelper.StoreToLocalTable(al);
is.getLastIDS().EXPR.genCode();
CodeGenHelper.LoadToStack(al, array);
CodeGenHelper.LoadToStack(al, value);
Code code=new Code(0x26);
al.add(code);
}
}
if(is.type==3)
{
int slot=CodeGenHelper.StoreToLocalTable(al);
is.EXPR.genCode();
CodeGenHelper.LoadToStackByName(al, is.ID.toString());
CodeGenHelper.LoadToStack(al, slot);
Code code=new Code(0x26);
al.add(code);
}
}
setvalue和classinit的代码几乎一样,唯一的区别是classinit中需要调用new和newarray指令得到需要给ids赋的值,而setvalue则是通过计算expr来得到,即使用ep.genCode();
(13)ids
ids的genCode逻辑是指将此ids的值放到堆栈顶,此值或许是个int、double,也可能是个对象的句柄。
递推式:
[java] view
plaincopy
ids->
* id| type 1
id.ids| type 2
id[expr] type 3
this| type=4
代码:
[java] view
plaincopy
public void genCode() {
ArrayList<Code> al=BackendClassManager.tFunc.codes;
if(type==1)
{
Integer slot=BackendClassManager.nameSlot.get(ID.toString());
if(slot!=null)
{
Code code=new Code(0x03);
code.Operands.add(String.valueOf(slot));
al.add(code);
}
else
{
//error
}
}
if(type==2)
{
int slot=BackendClassManager.nameSlot.get(ID.toString());
Code code=new Code(0x03);
code.Operands.add(String.valueOf(slot));
al.add(code);
IDS.genCode2nd();
}
if(type==3)
{
EXPR.genCode();//压入expr值
int slot=BackendClassManager.nameSlot.get(ID.toString());
Code code=new Code(0x03);
code.Operands.add(String.valueOf(slot));
al.add(code);//压入array
code=new Code(0x04);
al.add(code);//读取数组元素
}
if(type==4)
{
int slot=BackendClassManager.nameSlot.get("this");
Code code=new Code(0x01);
code.Operands.add(String.valueOf(slot));
}
}
public void genCode2nd() {
ArrayList<Code> al=BackendClassManager.tFunc.codes;
if(type==1)
{
Code code=new Code(0x20);
code.Operands.add(ID.toString());
al.add(code);
}
if(type==2)
{
Code code=new Code(0x20);
code.Operands.add(ID.toString());
al.add(code);
IDS.genCode2nd();
}
if(type==3)
{
EXPR.genCode();//压入expr值
Code code=new Code(0x20);
code.Operands.add(ID.toString());
al.add(code);//压入数组
code=new Code(0x04);
al.add(code);//读取数组元素
}
if(type==4)
{
//error
}
}
由于ids的结构不对称,所以不能使用递归的方式来获得其值。举例:id.id,第一个id的处理方式是通过局部变量表中拿到其句柄,第二个id的处理方式要通过getfield来拿到其值。因此这两种处理方式对应的是genCode和genCode2nd。
只有当第一次调用的时候(也就是其它节点调用)调用genCode,ids节点调用均调用genCode2nd。
(14)expr
/* Type
* expr --> (expr) 0
ids| 1
number| 2
literal| 3
func-call| 4
expr ops expr| 5
*/
[java] view
plaincopy
public void genCode() {
ArrayList<Code> al=BackendClassManager.tFunc.codes;
if(Type==0)
{
expr1.genCode();
}
else if(Type==1)
{
ids.genCode();
}
else if(Type==2)
{
Code code;
if(number.isInt==true)
{
code=new Code(0x01);
code.Operands.add(number.toString());
}
else
{
code=new Code(0x02);
code.Operands.add(number.toString());
}
al.add(code);
}
else if(Type==3)
{
literal.genCode();
}
else if(Type==4)
{
fc.genCode();
}
else if(Type==5)
{
expr1.genCode();
Code code=new Code(0x05);
int pos=BackendClassManager.nameSlot.keySet().size();
BackendClassManager.nameSlot.put(this.toString(), pos);
code.Operands.add(String.valueOf(pos));
al.add(code);
expr2.genCode();
code=new Code(0x03);
code.Operands.add(String.valueOf(pos));
al.add(code);
BackendClassManager.expr1=expr1;
BackendClassManager.expr2=expr2;
op.genCode();
}
}
expr的genCode方法产生的效果是把此expr的值放到栈顶。
如果type=0,则递归调用括号里的expr的genCode;如果type=1,则把ids的值放到栈顶;如果type=2,先判断立即数的类型之后再压入立即数;如果type=3,把字符串的句柄压入栈顶;type=4调用funccall的genCode;type=5,先生成expr1的字节码,然后将结果暂存到局部变量表,之后生成expr2的字节码,再把之前的结果压栈,调用op的genCode代码为运算符生成字节码。
值得注意的是,调用op的genCode之前首先要把参与运算的expr放到全局变量里,op的字节码生成过程需要使用这两个expr的信息。
(15)funccall
func-call --> ids . func-name(NUL|args)
[java] view
plaincopy
public void genCode() {
// TODO Auto-generated method stub
ArrayList<Code> al=BackendClassManager.tFunc.codes;
memberfuncdeclare mfc=(memberfuncdeclare)SyntaxTreeGenerator.getFunctions().get(fn.toString()).value;
ArrayList<ids> ags=ag.getidsList();
ArrayList<Integer> poses=new ArrayList<Integer>();
for(int i=0;i<=ags.size()-1;i++)
{
ags.get(i).genCode();
poses.add(CodeGenHelper.StoreToLocalTable(al));
}
for(int i=0;i<=poses.size()-1;i++)
{
CodeGenHelper.LoadToStack(al, poses.get(i));
}
if(mfc.isstatic==true)
{
Code code=new Code(0x1B);
code.Operands.add(IDS.toString()+"."+fn.toString());
al.add(code);
}
else
{
IDS.genCode();
Code code=new Code(0x1A);
code.Operands.add(fn.toString());
al.add(code);
}
}
函数调用的过程如下:
首先所有参数压栈,然后虚拟机执行call指令,给新函数分配执行环境(栈帧),把之前压栈的参数放到局部变量表中(如果有this参数的话this也要以参数的形式放到局部变量表中);函数执行完之后虚拟机释放资源,并把堆栈顶元素(返回值)放到其调用者的堆栈顶。
首先先从符号表中找到这个函数(封装在了SyntaxTreeGenerator中),然后得到所有参数,并存储到局部变量表中(之所以这么做因为参数的获取过程可能会把堆栈顺序打乱),接着把之前存过的所有参数压入堆栈,再判断该函数是否是个静态函数来使用不同的字节码进行调用。
(16)ops
ops --> bitop | logiop | artmop | cprop
[java] view
plaincopy
public void genCode() {
if(type==TYPE_BITOP)
{
bo.genCode();
}
else if(type==TYPE_LOGIOP)
{
lo.genCode();
}
else if(type==TYPE_ARMTOP)
{
ao.genCode();
}
else if(type==TYPE_CPROP)
{
co.genCode();
}
}
根据不同的类型调用不同op的genCode方法。
(17)cprop
[java] view
plaincopy
public void genCode() {
expr expr1,expr2;
ArrayList<Code> al=BackendClassManager.tFunc.codes;
expr1=BackendClassManager.expr1;
expr2=BackendClassManager.expr2;
boolean intint=true;
if(expr1.tp.toString().equals("int") && expr2.tp.toString().equals("int"))
{
intint=true;
}
if(expr1.tp.toString().equals("double") && expr2.tp.toString().equals("double"))
{
intint=false;
}
if(expr1.tp.toString().equals("int") && expr2.tp.toString().equals("double"))
{
CodeGenHelper.i2d(al);
intint=false;
}
if(expr1.tp.toString().equals("double") && expr2.tp.toString().equals("int"))
{
int pos=CodeGenHelper.StoreToLocalTable(al);
Code code=new Code(0x23);
al.add(code);
CodeGenHelper.i2d(al);
CodeGenHelper.LoadToStack(al, pos);
intint=false;
}
if(value.equals(">"))
{
if(intint==true)
{
Code code=new Code(0x16);//比较
al.add(code);
code=new Code(0x01);
code.Operands.add("1");//压入1
al.add(code);
code=new Code(0x16);//和1比较
al.add(code);
code=new Code(0x24);//取非
al.add(code);
}
else
{
Code code=new Code(0x17);//比较
al.add(code);
code=new Code(0x02);
code.Operands.add("1");//压入1
al.add(code);
code=new Code(0x17);//和1比较
al.add(code);
code=new Code(0x24);//取非
al.add(code);
}
}
if(value.equals("<"))
{
if(intint==true)
{
Code code=new Code(0x16);//比较
al.add(code);
code=new Code(0x01);
code.Operands.add("-1");//压入1
al.add(code);
code=new Code(0x16);//和1比较
al.add(code);
code=new Code(0x24);//取非
al.add(code);
}
else
{
Code code=new Code(0x17);//比较
al.add(code);
code=new Code(0x02);
code.Operands.add("-1");//压入1
al.add(code);
code=new Code(0x17);//和1比较
al.add(code);
code=new Code(0x24);//取非
al.add(code);
}
}
if(value.equals(">="))
{
if(intint==true)
{
Code code=new Code(0x16);//比较
al.add(code);
code=new Code(0x01);
code.Operands.add("-1");//压入1
al.add(code);
code=new Code(0x16);//和1比较
al.add(code);
}
else
{
Code code=new Code(0x17);//比较
al.add(code);
code=new Code(0x02);
code.Operands.add("-1");//压入1
al.add(code);
code=new Code(0x17);//和1比较
al.add(code);
code=new Code(0x24);//取非
al.add(code);
}
}
if(value.equals("<="))
{
if(intint==true)
{
Code code=new Code(0x16);//比较
al.add(code);
code=new Code(0x01);
code.Operands.add("1");//压入1
al.add(code);
code=new Code(0x16);//和1比较
al.add(code);
}
else
{
Code code=new Code(0x17);//比较
al.add(code);
code=new Code(0x02);
code.Operands.add("1");//压入1
al.add(code);
code=new Code(0x17);//和1比较
al.add(code);
}
}
if(value.equals("=="))
{
if(intint==true)
{
Code code=new Code(0x16);//比较
al.add(code);
code=new Code(0x01);
code.Operands.add("0");//压入1
al.add(code);
code=new Code(0x16);//和0比较
al.add(code);
code=new Code(0x24);//取非
al.add(code);
}
else
{
Code code=new Code(0x17);//比较
al.add(code);
code=new Code(0x02);
code.Operands.add("0");//压入1
al.add(code);
code=new Code(0x17);//和0比较
al.add(code);
code=new Code(0x24);//取非
al.add(code);
}
}
if(value.equals("!="))
{
if(intint==true)
{
Code code=new Code(0x16);//比较
al.add(code);
code=new Code(0x01);
code.Operands.add("0");//压入1
al.add(code);
code=new Code(0x16);//和0比较
al.add(code);
}
else
{
Code code=new Code(0x17);//比较
al.add(code);
code=new Code(0x02);
code.Operands.add("0");//压入1
al.add(code);
code=new Code(0x17);//和0比较
al.add(code);
}
}
}
比较运算符的genCode虽然代码比较长但是逻辑很简单。
首先需要根据待比较的expr来决定是否进行类型转换,目前在语言的设计中,只有double和int是可比的,其它类型的比较虽然在编译器不会报错但在运行时中会进行报错。
其次根据类型来调用不同的比较指令,再次根据逻辑需要进行二次比较(因为比较指令会根据大于小于等于返回1、0或-1,和运算符并不等价,需要通过比较两次使之和运算符等价)。
大体上代码就这么多,目前的编译器已经可以编译出正确的字节码了,但目前代码还无法执行,需要等到Runtime写好之后才能运行。
因此本系列博客到这里暂时告一段落,若给语言再添加一些高级点的特性,也需要等到我把Runtime写好再说了。
对于虚拟机以及其指令格式可参考这篇文章http://blog.csdn.net/roger__wong/article/details/8947720,如何去尝试实现这个虚拟机是在我的另外一个系列的博客里进行论述。
本篇文章从以下是那个方面来论述:后端架构与关键数据结构、节点翻译方式。
1、后端架构和关键数据结构
后端接受前端的语法树作为输入,对于其每一个节点根据节点类型的不同产生不同的代码。但在实现过程中为了简单方便,我并没有把后端抽象出一个单独的模块,而是在语法树每一个节点的基础上增加了一个genCode方法,通过调用这个方法来生成该节点及其所有孩子节点(通过递归)的代码。
其次编译器后端直接生成Class文件(文件结构也在上文提到的博客中有说明),程序中后端首先构造一个ClassOfClass的实体,然后再调用此类的方法生成Class文件:
[java] view
plaincopy
public class ClassOfClass {
public static int isPublic=1;
public static int isStatic=2;
public ArrayList<field> fields;
public ArrayList<function> functions;
public ArrayList<String> constPool;
public String name;
public ClassOfClass()
{
constPool=new ArrayList<String>();
fields=new ArrayList<field>();
functions=new ArrayList<function>();
}
public void WriteClassFile(String path)
{
try {
PrintWriter pw=new PrintWriter(new FileOutputStream(path));
pw.println(name);
pw.println(fields.size());
for(field f:fields)
{
pw.println(f.toString());
}
pw.println(functions.size());
for(function f:functions)
{
pw.println(f.toString());
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
其中field结构:
[java] view
plaincopy
public class field {
public int head;
public String type;
public String fieldname;
@Override
public String toString()
{
StringBuffer sb=new StringBuffer();
sb.append(head);
sb.append(" ");
sb.append(type);
sb.append(" ");
sb.append(fieldname);
return sb.toString();
}
}
其中Function结构
[java] view
plaincopy
public class function {
public int head;
public String rettype;
public int argnum;
public ArrayList<String> args;
public ArrayList<Code> codes;
public function()
{
args=new ArrayList<String>();
codes=new ArrayList<Code>();
}
@Override
public String toString()
{
StringBuffer sb=new StringBuffer();
sb.append(head);
sb.append(" ");
sb.append(rettype);
sb.append(" ");
sb.append(args.size());
sb.append(" ");
for(String s:args)
{
sb.append(s+" ");
}
sb.append("\r\n");
sb.append("{");
for(int i=0;i<=codes.size()-1;i++)
{
sb.append(i+":"+codes.get(i).toString()+"\r\n");
}
sb.append("\r\n");
sb.append("}");
return sb.toString();
}
}
其中Code结构
[java] view
plaincopy
public class Code {
public int Opcode;//操作码
public ArrayList<String> Operands;
public Code(int op)
{
Operands=new ArrayList<String>();
Opcode=op;
}
@Override
public String toString()
{
StringBuffer sb=new StringBuffer();
sb.append(Opcode);
sb.append(" ");
for(String s:Operands)
{
sb.append(s+" ");
}
return sb.toString();
}
[java] view
plaincopy
}
完成一个Class的后端代码的生成工作,只需要调用语法树根节点(classdef)的genCode即可完成,因为根节点会不断的递归调用其子树的genCode方法,因此在递归调用的时候需要某些机制进行各方法之间信息的传递,这里建立新类BackendClassManager来进行信息的传递,消除耦合。
[java] view
plaincopy
public class BackendClassManager {
public static ClassOfClass cc=new ClassOfClass();//正在生成的class对象
public static function tFunc;//正在生成代码的函数
public static memberfuncdeclare mfc;//正在生成代码的语法树中的memberfuncdeclare节点
public static constructor ct;//正在生成代码的构造函数节点
public static HashMap<String,Integer> nameSlot=new HashMap<String,Integer>();//局部变量和局部变量表槽的对应hash
public static expr expr1,expr2;//正在翻译的 expr
public static Stack<ArrayList<Code>> loopcontinue=new Stack<ArrayList<Code>>();//在循环语句中出现的continue语句,用于回填地址
public static Stack<ArrayList<Code>> loopbreak=new Stack<ArrayList<Code>>();//在循环语句中出现的break语句,用于回填地址
public static void WriteToClassFile()
{
String path="E:/test.class";
cc.WriteClassFile(path);
}
public static void genCode(classdef cd)
{
cd.genCode();
}
}
cc代表目前正在编译的class。
tFunc代表正在生成代码的函数,也就是所有genCode的方法都要把生成的代码填充到tFunc的codes域中。
memberfuncdeclare代表语法树中正在生成后端代码的memberfuncdeclare节点,该节点和其子树包含此函数的所有代码。
nameSlot对应源码中出现的局部变量与目标代码中的局部变量表槽的一个关系,因为目标代码将不再会出现局部变量名这个概念,所以需要一个hash在编译时进行对应。
expr1和expr2对应正在翻译的expr,在某些运算符需要进行类型转换时需要用到正在翻译的表达式的信息。
loopcontinue和loopbreak用于循环语句的地址回填,因为一个循环在翻译的过程中,其break需要跳转到的地址是还未确定的,需要整个循环翻译完之后对目标地址进行回填。continue虽然可以确定目标地址,但是在continue对应的语句stmt节点无法知道循环的开始地址,需要通过某些机制让stmt节点知道此循环的开始地址,因此也把continue语句进行回填处理。
除此之外还是用了一个类CodeGenHelper来封装一些常用的代码序列,比如i2d,jmp等,目的是为了简化之后的目标代码的生成。
2、节点代码生成
按照从顶至低的方式依次分析。
(1)classdef节点
[java] view
plaincopy
public void genCode() {
BackendClassManager.cc.name=cn.toString();
cb.genCode();
}
代码很简单,首先把正在编译的类名设置成classdef中出现的类名,然后调用classbody的genCode方法。
(2)classbody
[java] view
plaincopy
public void genCode() {
if(cb!=null)
{
cb.genCode();
}
}
依然很简单,如果classmembers不为空,则调用classmembers的genCode方法。
值得注意的是,这些方法本身并没有生成目标代码乃是因为一个类的定义本身并不包含任何逻辑,而代码本身是对逻辑的阐述,所以在类声明、函数声明、成员变量声明等没有生成任何有意义的代码也就不值得奇怪了。
(3)classmembers
[java] view
plaincopy
public void genCode() {
if(type==0)
{
((membervardeclare)declare).genCode();
}
else if(type==1)
{
((memberfuncdeclare)declare).genCode();
}
else if(type==2)
{
ct.genCode();
}
if(cm!=null)
{
cm.genCode();
}
}
根据此classmembers的类型,对membervardeclare、memberfuncdeclare、constructor调用genCode方法,最后对下一个classmemebers调用genCode方法,这和本系列第一篇博客中的递推式是对应的。
(4)membervardeclare
[java] view
plaincopy
public void genCode() {
// TODO Auto-generated method stub
field fd=new field();
ArrayList<field> fs=BackendClassManager.cc.fields;
if(af.toString().equals("public"))
{
fd.head+=ClassOfClass.isPublic;
}
if(isstatic==true)
{
fd.head+=ClassOfClass.isStatic;
}
fd.type=tp.toString();
fd.fieldname=ID.toString();
fs.add(fd);
}
依然没有任何代码生成,只是将成员变量的信息放到ClassOfClass对象的fields域中。
(5)memberfundeclare
[java] view
plaincopy
public void genCode() {
function func=new function();
BackendClassManager.tFunc=func;
BackendClassManager.cc.functions.add(func);
BackendClassManager.ct=null;
BackendClassManager.mfc=this;
BackendClassManager.nameSlot=new HashMap<String,Integer>();
if(af.toString().equals("public"))
{
func.head+=ClassOfClass.isPublic;
}
func.rettype=tp.toString();
if(da!=null)
{
ArrayList<type> al=da.gettypelist();
func.argnum=al.size();
for(type tp:al)
{
func.args.add(tp.toString());
}
ArrayList<id> tal=da.getidlist();
BackendClassManager.nameSlot.put("this", 0);
for(int i=0;i<=tal.size()-1;i++)
{
BackendClassManager.nameSlot.put(tal.get(i).toString(), i+1);
}
}
else
{
func.argnum=0;
}
fb.genCode();
}
成员函数的稍微有点复杂。
首先建立一个新的function对象,并把该对象设置为BackendClassManager.tFun,说明之后所有genCode都为这个函数生成的代码,并把这个函数加到classofClass对象的functions域中;判断该函数的返回值类型、是否是public、是否是静态,并把相关信息记录到function对象中重置nameslot,将函数所有参数压入nameslot表中,并注意this也当做参数放入表中,然后调用functionbody的genCode,为该函数生成代码。
(6)constructor
[java] view
plaincopy
public void genCode() {
function func=new function();
BackendClassManager.tFunc=func;
BackendClassManager.ct=this;
BackendClassManager.nameSlot=new HashMap<String,Integer>();
if(af.toString().equals("public"))
{
func.head+=ClassOfClass.isPublic;
}
func.rettype="NULL";
if(da!=null)
{
ArrayList<type> al=da.gettypelist();
func.argnum=al.size();
func.argnum=al.size();
for(type tp:al)
{
func.args.add(tp.toString());
}
ArrayList<id> tal=da.getidlist();
BackendClassManager.nameSlot.put("this", 0);
for(int i=0;i<=tal.size()-1;i++)
{
BackendClassManager.nameSlot.put(tal.get(i).toString(), i+1);
}
}
else
{
func.argnum=0;
}
ss.genCode();
}
(7)funcbody
[java] view
plaincopy
public void genCode() {
// TODO Auto-generated method stub
ss.genCode();
returnexpr.genCode();
ArrayList<Code> al=BackendClassManager.tFunc.codes;
Code code=new Code(0x19);
al.add(code);
}
首先stmts生成代码,然后为返回表达式生成代码,最后在codes中加入Code(0x19),也就是返回指令。
值得注意的是,这里做了一个约定,在expr的genCode中,总是把该expr的结果放在expr代码执行后的栈顶,因此函数返回时实际上返回的是栈顶元素的值。
(8)stmts
stmts逻辑上代表一个语句块或一组语句块,对应的生成式和在节点中使用的type如下:
stmts --> NUL| type-->0
stmt stmts| type-->1
if(expr) { stmts} stmts| type-->2
if(expr) {stmts} else {stmts} stmts| type-->3
while(expr) { stmts} stmts| type-->4
接下来给出生成后端代码的代码:
[html] view
plaincopy
public void genCode() {
if(type==1)
{
st.genCode();
stmts1.genCode();
}
else if(type==2)
{
condition.genCode();
Code code=new Code(0x18);
BackendClassManager.tFunc.codes.add(code);
stmts1.genCode();
code.Operands.add(String.valueOf(BackendClassManager.tFunc.codes.size()));
stmts3.genCode();
}
else if(type==3)
{
condition.genCode();
Code code=new Code(0x18);
code.Opcode=0x18;
BackendClassManager.tFunc.codes.add(code);
stmts1.genCode();
Code code1=new Code(0x01);
code1.Operands.add(String.valueOf(0));
BackendClassManager.tFunc.codes.add(code1);
Code code2=new Code(0x18);
BackendClassManager.tFunc.codes.add(code2);
code.Operands.add(String.valueOf(BackendClassManager.tFunc.codes.size()));
stmts2.genCode();
code2.Operands.add(String.valueOf(BackendClassManager.tFunc.codes.size()));
stmts3.genCode();
}
else if(type==4)
{
ArrayList<Code> albreak=new ArrayList<Code>();
ArrayList<Code> alcontinue=new ArrayList<Code>();
BackendClassManager.loopbreak.add(albreak);
BackendClassManager.loopcontinue.add(alcontinue);//循环入口,首先判断expr
int pos=BackendClassManager.tFunc.codes.size();
condition.genCode();
//跳转指令
Code code=new Code(0x18);
BackendClassManager.tFunc.codes.add(code);
//循环体
stmts1.genCode();
// code.Operands.add(String.valueOf(end));//表达式回填
Code code1=new Code(0x01);
code1.Operands.add(String.valueOf(0));
BackendClassManager.tFunc.codes.add(code1);//压入0
Code code2=new Code(0x18);
code2.Operands.add(String.valueOf(pos));
BackendClassManager.tFunc.codes.add(code2);//跳转到循环入口
int end=BackendClassManager.tFunc.codes.size();
code.Operands.add(String.valueOf(end));//表达式回填
for(Code c:albreak)
{
c.Operands.add(String.valueOf(end));
}
for(Code c:alcontinue)
{
c.Operands.add(String.valueOf(pos));
}
BackendClassManager.loopbreak.pop();
BackendClassManager.loopcontinue.pop();
stmts3.genCode();
}
}
对于type=0,没必要生成任何代码;对于type=1,生成stmt的代码然后递归再生成stmts的代码;对于type=2,首先生成条件expr的代码,在这段代码执行过后,会将结果放在堆栈顶,然后加入code(0x18)进行跳转,但此刻的跳转地址还不能确定,因此先要生成stmts1的代码,之后回填跳转地址,再生成stmts3的代码;对于type=3,和type=2类似,但要在stmts1代码之后加入无条件跳转指令,跳转到else块后面,无条件跳转的方法是首先压入0,再使用0x18也就是ifz进行跳转;对于type=4要稍微复杂些,首先要计算循环开始的地址(包括判断expr),然后生成循环体代码,得到循环结束地址,再回填循环判断的相关代码,除此之外,还要回填此循环体内出现的所有break和continue语句的地址。
(9)stmt
首先给出语句stmt的生成式:
stmt --> continue;| type=0
break;| type=1
var-declare;| type=2
class-init;| type=3
setvalue;| type=4
expr;| type=5
接下来给出代码:
[java] view
plaincopy
public void genCode() {
ArrayList<Code> al=BackendClassManager.tFunc.codes;
if(type==0)
{
CodeGenHelper.JMP(al, 0);
BackendClassManager.loopcontinue.peek().add(al.get(al.size()-1));
}
if(type==1)
{
CodeGenHelper.JMP(al, 0);
BackendClassManager.loopbreak.peek().add(al.get(al.size()-1));
}
if(type==5)
{
ep.genCode();
}
if(type==2)
{
vc.genCode();
}
if(type==3)
{
ci.genCode();
}
if(type==4)
{
sv.genCode();
}
}
stmt代码比较简单,对于break和continue,只需要加入一个跳转语句,然后把此跳转语句加入到相应的列表里(因为循环是嵌套的,所以使用堆栈来表示这一关系),由外层循环体进行地址回填即可,使用了CodeGenHelper的JMP方法作为辅助函数;对于其它类型则调用其相应节点的genCode方法。
另外给出CodeGenHelper的JMP方法,封装了压入0和ifz两条指令:
[java] view
plaincopy
public static void JMP(ArrayList<Code> al,int pos)
{
Code code1=new Code(0x01);
code1.Operands.add(String.valueOf(0));
BackendClassManager.tFunc.codes.add(code1);//压入0
Code code2=new Code(0x18);
code2.Operands.add(String.valueOf(pos));
BackendClassManager.tFunc.codes.add(code2);//跳转
}
(10)classinit
递推式:class-init --> ids = new
type ( args )| type[expr]
type: 0 1
代码:
[java] view
plaincopy
public void genCode() {
ArrayList<Code> al=BackendClassManager.tFunc.codes;
ag.genCode();
if(type==0)
{
Code code=new Code(0x1C);
code.Operands.add(tp.toString());
al.add(code);
}
else
{
Code code=new Code(0x1D);
code.Operands.add(tp.toString());
al.add(code);
}
if(is.type==1)
{
int slot=CodeGenHelper.SearchByName(is.ID.toString());
Code code=new Code(0x05);
code.Operands.add(String.valueOf(slot));
al.add(code);
}
if(is.type==2)
{
if(is.getLastIDS().type!=3)
{
CodeGenHelper.PutObjectToStack(al, is);
CodeGenHelper.ChangeStackTopEle(al);
CodeGenHelper.CodePutField(al, is.getLastID().toString());
}
else
{
int value=CodeGenHelper.StoreToLocalTable(al);
CodeGenHelper.PutObjectToStack(al, is);
CodeGenHelper.CodeGetField(al, is.getLastID().toString());
int array=CodeGenHelper.StoreToLocalTable(al);
is.getLastIDS().EXPR.genCode();
CodeGenHelper.LoadToStack(al, array);
CodeGenHelper.LoadToStack(al, value);
Code code=new Code(0x26);
al.add(code);
}
}
if(is.type==3)
{
int slot=CodeGenHelper.StoreToLocalTable(al);
is.EXPR.genCode();
CodeGenHelper.LoadToStackByName(al, is.ID.toString());
CodeGenHelper.LoadToStack(al, slot);
Code code=new Code(0x26);
al.add(code);
}
}
首先ag.genCode()把所有参数压栈,然后根据类别判断是初始化对象还是数组来使用0x1C或者0x1D指令并把类型作为操作数,当此条指令执行完之后,堆栈顶此时放着返回的对象或数组的句柄,接下来的任务是将该句柄赋值给ids。
接着判断ids的类型,ids的推导式如下:
ids->
* id| type 1
id.ids| type 2
id[expr] type 3
this| type=4
如果type=1,则说明是一个局部变量,从nameSlot中找到该局部变量在局部变量表里的位置(相关操作封装到了CodeGenHelper的searchByName中),赋值给这个槽即可。
如果type=2,说明是某个对象的成员变量,则需要先把这个对象压入堆栈,再通过putfield指令给此对象赋值,若是某个对象的成员变量中的某个元素(即该成员变量是个数组),则需要使用给数组赋值的指令。
如果type=3,说明给数组某个元素赋值,需要先把下标计算出来,然后通过相应指令给数组赋值。
其中的很多操作都封装在了CodeGenHelper中。
(11)vardeclare
递推式:
var-declare --> type args|type[] args
代码:
[java] view
plaincopy
public void genCode() {
ArrayList<Code> al=BackendClassManager.tFunc.codes;
ArrayList<ids> idlist=ags.getidsList();
for(ids id:idlist)
{
BackendClassManager.nameSlot.put(id.getLastID().toString(), BackendClassManager.nameSlot.size());
}
}
可以看到,在变量声明时并没有生成任何实际代码,只是在局部变量表中给其开辟了一个存储位置而已。
(12)setvalue
setvalue--> ids = expr
[java] view
plaincopy
public void genCode() {
ArrayList<Code> al=BackendClassManager.tFunc.codes;
ep.genCode();
if(is.type==1)
{
int slot=CodeGenHelper.SearchByName(is.ID.toString());
Code code=new Code(0x05);
code.Operands.add(String.valueOf(slot));
al.add(code);
}
if(is.type==2)
{
if(is.getLastIDS().type!=3)
{
CodeGenHelper.PutObjectToStack(al, is);
CodeGenHelper.ChangeStackTopEle(al);
CodeGenHelper.CodePutField(al, is.getLastID().toString());
}
else
{
int value=CodeGenHelper.StoreToLocalTable(al);
CodeGenHelper.PutObjectToStack(al, is);
CodeGenHelper.CodeGetField(al, is.getLastID().toString());
int array=CodeGenHelper.StoreToLocalTable(al);
is.getLastIDS().EXPR.genCode();
CodeGenHelper.LoadToStack(al, array);
CodeGenHelper.LoadToStack(al, value);
Code code=new Code(0x26);
al.add(code);
}
}
if(is.type==3)
{
int slot=CodeGenHelper.StoreToLocalTable(al);
is.EXPR.genCode();
CodeGenHelper.LoadToStackByName(al, is.ID.toString());
CodeGenHelper.LoadToStack(al, slot);
Code code=new Code(0x26);
al.add(code);
}
}
setvalue和classinit的代码几乎一样,唯一的区别是classinit中需要调用new和newarray指令得到需要给ids赋的值,而setvalue则是通过计算expr来得到,即使用ep.genCode();
(13)ids
ids的genCode逻辑是指将此ids的值放到堆栈顶,此值或许是个int、double,也可能是个对象的句柄。
递推式:
[java] view
plaincopy
ids->
* id| type 1
id.ids| type 2
id[expr] type 3
this| type=4
代码:
[java] view
plaincopy
public void genCode() {
ArrayList<Code> al=BackendClassManager.tFunc.codes;
if(type==1)
{
Integer slot=BackendClassManager.nameSlot.get(ID.toString());
if(slot!=null)
{
Code code=new Code(0x03);
code.Operands.add(String.valueOf(slot));
al.add(code);
}
else
{
//error
}
}
if(type==2)
{
int slot=BackendClassManager.nameSlot.get(ID.toString());
Code code=new Code(0x03);
code.Operands.add(String.valueOf(slot));
al.add(code);
IDS.genCode2nd();
}
if(type==3)
{
EXPR.genCode();//压入expr值
int slot=BackendClassManager.nameSlot.get(ID.toString());
Code code=new Code(0x03);
code.Operands.add(String.valueOf(slot));
al.add(code);//压入array
code=new Code(0x04);
al.add(code);//读取数组元素
}
if(type==4)
{
int slot=BackendClassManager.nameSlot.get("this");
Code code=new Code(0x01);
code.Operands.add(String.valueOf(slot));
}
}
public void genCode2nd() {
ArrayList<Code> al=BackendClassManager.tFunc.codes;
if(type==1)
{
Code code=new Code(0x20);
code.Operands.add(ID.toString());
al.add(code);
}
if(type==2)
{
Code code=new Code(0x20);
code.Operands.add(ID.toString());
al.add(code);
IDS.genCode2nd();
}
if(type==3)
{
EXPR.genCode();//压入expr值
Code code=new Code(0x20);
code.Operands.add(ID.toString());
al.add(code);//压入数组
code=new Code(0x04);
al.add(code);//读取数组元素
}
if(type==4)
{
//error
}
}
由于ids的结构不对称,所以不能使用递归的方式来获得其值。举例:id.id,第一个id的处理方式是通过局部变量表中拿到其句柄,第二个id的处理方式要通过getfield来拿到其值。因此这两种处理方式对应的是genCode和genCode2nd。
只有当第一次调用的时候(也就是其它节点调用)调用genCode,ids节点调用均调用genCode2nd。
(14)expr
/* Type
* expr --> (expr) 0
ids| 1
number| 2
literal| 3
func-call| 4
expr ops expr| 5
*/
[java] view
plaincopy
public void genCode() {
ArrayList<Code> al=BackendClassManager.tFunc.codes;
if(Type==0)
{
expr1.genCode();
}
else if(Type==1)
{
ids.genCode();
}
else if(Type==2)
{
Code code;
if(number.isInt==true)
{
code=new Code(0x01);
code.Operands.add(number.toString());
}
else
{
code=new Code(0x02);
code.Operands.add(number.toString());
}
al.add(code);
}
else if(Type==3)
{
literal.genCode();
}
else if(Type==4)
{
fc.genCode();
}
else if(Type==5)
{
expr1.genCode();
Code code=new Code(0x05);
int pos=BackendClassManager.nameSlot.keySet().size();
BackendClassManager.nameSlot.put(this.toString(), pos);
code.Operands.add(String.valueOf(pos));
al.add(code);
expr2.genCode();
code=new Code(0x03);
code.Operands.add(String.valueOf(pos));
al.add(code);
BackendClassManager.expr1=expr1;
BackendClassManager.expr2=expr2;
op.genCode();
}
}
expr的genCode方法产生的效果是把此expr的值放到栈顶。
如果type=0,则递归调用括号里的expr的genCode;如果type=1,则把ids的值放到栈顶;如果type=2,先判断立即数的类型之后再压入立即数;如果type=3,把字符串的句柄压入栈顶;type=4调用funccall的genCode;type=5,先生成expr1的字节码,然后将结果暂存到局部变量表,之后生成expr2的字节码,再把之前的结果压栈,调用op的genCode代码为运算符生成字节码。
值得注意的是,调用op的genCode之前首先要把参与运算的expr放到全局变量里,op的字节码生成过程需要使用这两个expr的信息。
(15)funccall
func-call --> ids . func-name(NUL|args)
[java] view
plaincopy
public void genCode() {
// TODO Auto-generated method stub
ArrayList<Code> al=BackendClassManager.tFunc.codes;
memberfuncdeclare mfc=(memberfuncdeclare)SyntaxTreeGenerator.getFunctions().get(fn.toString()).value;
ArrayList<ids> ags=ag.getidsList();
ArrayList<Integer> poses=new ArrayList<Integer>();
for(int i=0;i<=ags.size()-1;i++)
{
ags.get(i).genCode();
poses.add(CodeGenHelper.StoreToLocalTable(al));
}
for(int i=0;i<=poses.size()-1;i++)
{
CodeGenHelper.LoadToStack(al, poses.get(i));
}
if(mfc.isstatic==true)
{
Code code=new Code(0x1B);
code.Operands.add(IDS.toString()+"."+fn.toString());
al.add(code);
}
else
{
IDS.genCode();
Code code=new Code(0x1A);
code.Operands.add(fn.toString());
al.add(code);
}
}
函数调用的过程如下:
首先所有参数压栈,然后虚拟机执行call指令,给新函数分配执行环境(栈帧),把之前压栈的参数放到局部变量表中(如果有this参数的话this也要以参数的形式放到局部变量表中);函数执行完之后虚拟机释放资源,并把堆栈顶元素(返回值)放到其调用者的堆栈顶。
首先先从符号表中找到这个函数(封装在了SyntaxTreeGenerator中),然后得到所有参数,并存储到局部变量表中(之所以这么做因为参数的获取过程可能会把堆栈顺序打乱),接着把之前存过的所有参数压入堆栈,再判断该函数是否是个静态函数来使用不同的字节码进行调用。
(16)ops
ops --> bitop | logiop | artmop | cprop
[java] view
plaincopy
public void genCode() {
if(type==TYPE_BITOP)
{
bo.genCode();
}
else if(type==TYPE_LOGIOP)
{
lo.genCode();
}
else if(type==TYPE_ARMTOP)
{
ao.genCode();
}
else if(type==TYPE_CPROP)
{
co.genCode();
}
}
根据不同的类型调用不同op的genCode方法。
(17)cprop
[java] view
plaincopy
public void genCode() {
expr expr1,expr2;
ArrayList<Code> al=BackendClassManager.tFunc.codes;
expr1=BackendClassManager.expr1;
expr2=BackendClassManager.expr2;
boolean intint=true;
if(expr1.tp.toString().equals("int") && expr2.tp.toString().equals("int"))
{
intint=true;
}
if(expr1.tp.toString().equals("double") && expr2.tp.toString().equals("double"))
{
intint=false;
}
if(expr1.tp.toString().equals("int") && expr2.tp.toString().equals("double"))
{
CodeGenHelper.i2d(al);
intint=false;
}
if(expr1.tp.toString().equals("double") && expr2.tp.toString().equals("int"))
{
int pos=CodeGenHelper.StoreToLocalTable(al);
Code code=new Code(0x23);
al.add(code);
CodeGenHelper.i2d(al);
CodeGenHelper.LoadToStack(al, pos);
intint=false;
}
if(value.equals(">"))
{
if(intint==true)
{
Code code=new Code(0x16);//比较
al.add(code);
code=new Code(0x01);
code.Operands.add("1");//压入1
al.add(code);
code=new Code(0x16);//和1比较
al.add(code);
code=new Code(0x24);//取非
al.add(code);
}
else
{
Code code=new Code(0x17);//比较
al.add(code);
code=new Code(0x02);
code.Operands.add("1");//压入1
al.add(code);
code=new Code(0x17);//和1比较
al.add(code);
code=new Code(0x24);//取非
al.add(code);
}
}
if(value.equals("<"))
{
if(intint==true)
{
Code code=new Code(0x16);//比较
al.add(code);
code=new Code(0x01);
code.Operands.add("-1");//压入1
al.add(code);
code=new Code(0x16);//和1比较
al.add(code);
code=new Code(0x24);//取非
al.add(code);
}
else
{
Code code=new Code(0x17);//比较
al.add(code);
code=new Code(0x02);
code.Operands.add("-1");//压入1
al.add(code);
code=new Code(0x17);//和1比较
al.add(code);
code=new Code(0x24);//取非
al.add(code);
}
}
if(value.equals(">="))
{
if(intint==true)
{
Code code=new Code(0x16);//比较
al.add(code);
code=new Code(0x01);
code.Operands.add("-1");//压入1
al.add(code);
code=new Code(0x16);//和1比较
al.add(code);
}
else
{
Code code=new Code(0x17);//比较
al.add(code);
code=new Code(0x02);
code.Operands.add("-1");//压入1
al.add(code);
code=new Code(0x17);//和1比较
al.add(code);
code=new Code(0x24);//取非
al.add(code);
}
}
if(value.equals("<="))
{
if(intint==true)
{
Code code=new Code(0x16);//比较
al.add(code);
code=new Code(0x01);
code.Operands.add("1");//压入1
al.add(code);
code=new Code(0x16);//和1比较
al.add(code);
}
else
{
Code code=new Code(0x17);//比较
al.add(code);
code=new Code(0x02);
code.Operands.add("1");//压入1
al.add(code);
code=new Code(0x17);//和1比较
al.add(code);
}
}
if(value.equals("=="))
{
if(intint==true)
{
Code code=new Code(0x16);//比较
al.add(code);
code=new Code(0x01);
code.Operands.add("0");//压入1
al.add(code);
code=new Code(0x16);//和0比较
al.add(code);
code=new Code(0x24);//取非
al.add(code);
}
else
{
Code code=new Code(0x17);//比较
al.add(code);
code=new Code(0x02);
code.Operands.add("0");//压入1
al.add(code);
code=new Code(0x17);//和0比较
al.add(code);
code=new Code(0x24);//取非
al.add(code);
}
}
if(value.equals("!="))
{
if(intint==true)
{
Code code=new Code(0x16);//比较
al.add(code);
code=new Code(0x01);
code.Operands.add("0");//压入1
al.add(code);
code=new Code(0x16);//和0比较
al.add(code);
}
else
{
Code code=new Code(0x17);//比较
al.add(code);
code=new Code(0x02);
code.Operands.add("0");//压入1
al.add(code);
code=new Code(0x17);//和0比较
al.add(code);
}
}
}
比较运算符的genCode虽然代码比较长但是逻辑很简单。
首先需要根据待比较的expr来决定是否进行类型转换,目前在语言的设计中,只有double和int是可比的,其它类型的比较虽然在编译器不会报错但在运行时中会进行报错。
其次根据类型来调用不同的比较指令,再次根据逻辑需要进行二次比较(因为比较指令会根据大于小于等于返回1、0或-1,和运算符并不等价,需要通过比较两次使之和运算符等价)。
大体上代码就这么多,目前的编译器已经可以编译出正确的字节码了,但目前代码还无法执行,需要等到Runtime写好之后才能运行。
因此本系列博客到这里暂时告一段落,若给语言再添加一些高级点的特性,也需要等到我把Runtime写好再说了。
相关文章推荐
- 自制编译器:后端代码生成(二)
- 自制编译器:后端代码生成(一)
- 自制编译器:后端代码生成(三)
- 自己动手开发编译器(十二)生成托管代码
- [转载]LCC编译器的源程序分析(59)代码生成的源程序注释
- LCC编译器的源程序分析(57)不同目标代码生成的接口结构
- LCC编译器的源程序分析(57)不同目标代码生成的接口结构
- LCC编译器的源程序分析(45)函数代码入口和出口的代码生成
- ASP.NET中使用后端代码注册脚本 生成JQUERY-EASYUI的界面错位的解决方法
- [转载]LCC编译器的源程序分析(55)最终代码的生成
- [转载]LCC编译器的源程序分析(57)不同目标代码生成的接口结构
- js代码文件动态生成(此为一张页面的后端cs代码)
- 编译原理课程设计_C--编译器_语法分析&代码生成
- js代码动态生成代码(此为一张页面的后端cs代码)
- ASP.NET中使用后端代码注册脚本 生成JQUERY-EASYUI的界面错位的解决方法
- 编译原理课程设计_C--编译器_语法分析&代码生成 - Justin
- LCC编译器的源程序分析(45)函数代码入口和出口的代码生成
- LCC编译器的源程序分析(44)函数名称的代码生成
- LCC编译器的源程序分析(59)代码生成的源程序注释
- 如何查看cl编译器生成的汇编代码