生成中间代码 | 从零实现一门语言

    本节开始中间代码的生成,同时处理上几节中遗留的作用域的问题。在代码实现里,我们在语义解析的各个动作中去插入相应的中间代码实现,生成的中间代码以函数为主体存储在变量符号表中。如果后续支持代码优化,使用优化器对中间代码进行处理,再生成具体的汇编代码。

作用域

    作用域在高级编程语言中十分重要,不同函数块中定义的局部变量互不可见,所以在语义解析过程中要对不同的符号进行标记,如果出现不可见的变量被使用,应该及时报错处理。如下图代码示例,采用序号和层级的方式表述作用域。

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.hclass 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.其他规划

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值