本节开始中间代码的生成,同时处理上几节中遗留的作用域的问题。在代码实现里,我们在语义解析的各个动作中去插入相应的中间代码实现,生成的中间代码以函数为主体存储在变量符号表中。如果后续支持代码优化,使用优化器对中间代码进行处理,再生成具体的汇编代码。
作用域
作用域在高级编程语言中十分重要,不同函数块中定义的局部变量互不可见,所以在语义解析过程中要对不同的符号进行标记,如果出现不可见的变量被使用,应该及时报错处理。如下图代码示例,采用序号和层级的方式表述作用域。
int a = 1;
int func(){
int b = 2;
if(b!=2){
int c = 3;
}else{
d = 4;
}
int e = 5;
}
a的作用域:0;
b的作用域:0/0;
c的作用域:0/0/0;
d的作用域:0/0/1;
e的作用域:0/0;
具有相同作用域的变量拥有相同的序号和层级数,比如b和e都为0/0。c和d也拥有相同的层级数,0/0/0和0/0/1,最后一级序号不同,表明它们都可以直接使用“0/0”两层的变量,但归属于不同的层级,彼此之间不可见。
我们采用数组的结构表示上述作用域的关系,进入一个代码块,则向数组中添加一位序号。初始化时,默认作用域为0;
Symtab::Symtab() {
scope = 0;
scope_seq.push_back(0);
func = nullptr;
}
进入func时,作用域增加一层,退出时减去一层。函数中的代码块需要特殊处理,不同代码块序号加1;
void Parser::defFun(Tag t, string name) {
symtab.incScope();//增加作用域层级
next();
vector<Var *> params;
defParams(params);
if (token->tag != RPR) {
printf("func is not end with ')'");
exit(1);
}
Func* func = new Func(t, name, params);
symtab.addFunc(func);
defFunImpl(func);
symtab.decScope();//减少作用域层级
}
中间代码
我们写一个稍复杂点的自定义代码示例,并尝试构造其中间代码,包括变量和函数声明,函数调用、基本运算符、函数返回等。
int func(int x, int y){
int m = 2;
int z = x + y * m;
return z;
}
int main(){
int a = 10;
int b = 20;
int c = func(a,b);
return c;
}
不同编译器有中间代码形式,常见的有抽象语法树、有向无环图、三元式和四元式等形式。三元式由运算符OP,运算对象arg1和运算对象arg2组成,四元式多了一个运算结果result。我们采用四元式的表达形式,它更接近于人理解的范畴。
result = arg1 op arg2
定义常用的中间代码操作指令枚举。
/**
* 中间代码操作符
*/
enum Operation {
OP_LAB,//标签
OP_ENTRY,//函数入口
OP_EXIT,//函数出口
OP_DEF,//符号声明
OP_ASSIGN,//赋值运算
OP_ADD, OP_SUB,
OP_MUL, OP_DIV,
OP_NE,
OP_PUSH_ARG,//参数入栈
OP_CALL,//函数调用
OP_RETURN,//函数返回
};
中间代码指令。
//intermediate.h
class Order {
Operation op; //操作符
Var* val; //左值
Var* var; //右值
Var* res; //结果
Func* func; //函数
Var* farg; //函数调用的入参
Order* next; //下一条指定
string label; //标签
public:
Order();
Order(string label);
Order(Operation o, Var *l, Var *r, Var *s, Func *f);
Order(Operation o, Func *f);
...
};
中间代码的指令放在符号表中,指令是以函数为主体执行的,所以在函数符号中新建一个中间代码指令集合,用来存放该函数内部的所有指令语句。
class Func{
string name;
Tag rtype; //返回值类型
vector<Var*> params; //参数列表
vector<int> scope; //作用域
...
vector<Order*> orders; //中间代码指令
public:
...
void addOrder(Order* order);
Func();
Func(Tag t, string n, vector<Var*> &ps);
};
class Symtab {
public:
...
void addOrder(Order *order); //添加中间代码指令
...
Symtab();
~Symtab();
};
中间代码的生成器。
class OrderGenerator{
Systab& tab; //变量符号表
FILE* outfile; //中间代码输出文件
public:
string genTempNo(int type, string prefix); //生成临时变量的序号
void genEntryCode(Func* func); //生成函数入口指令
void genExitCode(Func* func); //生成函数出口指令
void genOpCode(Operation op, Var* res, Var* val, Var* var); //生成操作类指令
void genVarAssign(Var* var, Func* func); //生成变量赋值指令
void genRetCode(Var* var, Func* func); //生成函数返回指令
void genFuncCode(Operation op, Func* func, vector<Var*> args, Var* res); //生成函数体指令
OrderGenerator(Symtab& s);
};
在函数实现的地方生成函数入口的中间代码,下面注释部分。
void Parser::defFun(Tag t, string name) {
symtab.incScope();
next();
vector<Var *> params;
defParams(params);
if (token->tag != RPR) {
printf("func is not end with ')'");
exit(1);
}
Func* func = new Func(t, name, params);
symtab.addFunc(func);
generator.genEntryCode(func); //函数入口中间代码生成
defFunImpl(func);
generator.genExitCode(func); //函数出口中间代码生成
symtab.decScope();
}
函数入口中间代码实现。
void OrderGenerator::genEntryCode(Func *func) {
string funcName = genTempNo(1, func->getName()); //为当前函数生成一个唯一的标签名
func->setName(funcName);
Order *label = new Order(funcName); //生成函数标签
symtab.addOrder(label); //添加函数标签指令
symtab.addOrder(new Order(OP_ENTRY, func)); //添加函数入口指令
func->setRet(label); //设置函数返回标签,生成函数返回标签用
}
生成临时编号。
string OrderGenerator::genTempNo(int type, string prefix) {
tempNo++; //编号递增,或者uuid都行
if (type == 0) { //根据类型区分是变量名还是函数标签名
return prefix + "_" + to_string(tempNo);
}
函数出口相对简单。
void OrderGenerator::genExitCode(Func *func) {
symtab.addOrder(new Order(OP_EXIT, func));
}
最终生成的中间代如下,由于篇幅影响,略过剩余部分中间代码的生成过程。
Lfunc1 //生成func函数的标签,翻译成汇编代码时需要以它作为函数调用的地址
entry Lfunc1 //进入函数
def m //声明变量m,为变量m开辟栈内空间
m=2 //对变量进行赋值
def temp_1 //声明临时变量temp_1
temp_1 = y * m //乘法运算
def temp_2 //声明临时变量temp_2
temp_2 = x + temp_1 //加法运算
def z //声明变量z
z = temp_2 //对z进行赋值
ret z //返回z
exit Lfunc1 //离开函数
Lmain1 //生成main主函数的标签
entry Lmain1 //进入主函数
def a //声明变量a
a = 10 //对变量a进行赋值
def b //声明变量b
b = 20 //对变量b进行赋值
push arg b //将参数b的值压入栈
push arg a //将参数a的值压入栈
call Lfunc1 //调用函数func
def func_1 //声明并将函数返回结果赋值给func_1
def c //声明变量c
c = func_1 //将函数返回结果赋值给变量c
ret c //返回主函数值
exit Lmain1 //退出主函数
本节主要讲解语义解析中对作用域的表述过程,中间代码的设计以及具体实现细节。篇幅较长,大部分为代码的实现细节。生成完中间代码后,下一节就可以进行汇编文件的生成了。
目录
1.一个hello world的诞生
2.词法解析器
3.从自然语言认识文法
4.构造文法
5.文法及语义代码实现
6.代码函数的帧栈调用过程
7.生成中间代码
8.汇编
9.编译和链接
10.终于跑起来了
11.多文件编译
12.丰富数据类型
13.流程控制语句
14.编译优化算法
15.文件读取
16.一个线程的实现
17.什么是锁
18.网络编程
19.面向对象
20.其他规划