首页
社区
课程
招聘
[分享]vm解释器入门2
发表于: 3天前 477

[分享]vm解释器入门2

3天前
477

前言

上篇文章笔者做了一个简单的vm解释器作为入门框架,本节继续来完善这个简易vm解释器

本节中将会在之前的解释器基础上添加 栈空间、栈操作(PUSH、POP)、函数调用(CALL、RET)

以及升级宏的方式进行调用代码更加清楚,并且实现对标签的支持 再无需手写跳转位置(很容易写错 写错了崩溃又很难排查)

最后将上节涉及的阶乘例子改为递归调用的方式实现,可充分练习函数调用以及栈区操作

一、新增指令

新增:OP_PUSH, OP_POP, OP_CALL, OP_RET

enum {
    OP_MOV_IMM,
    OP_MOV_REG,
    OP_ADD,
    OP_SUB,
    OP_PRINT,
    OP_JMP,
    OP_JZ,
    OP_HALT,
    OP_PUSH,
    OP_POP,
    OP_CALL,
    OP_RET
};

二、新增解释器规则

case OP_PUSH: {
    int r = code[vm.pc++];//取出寄存器号
    vm.stack[++vm.sp] = vm.reg[r];//入栈
    break;
}
case OP_POP: {
    int r = code[vm.pc++];//取出寄存器号
    vm.reg[r] = vm.stack[vm.sp--];//出栈
    break;
}
case OP_CALL: {
    int addr = code[vm.pc++];//取出跳转位置
    vm.stack[++vm.sp] = vm.pc; // 保存返回地址
    vm.pc = addr;//改变pc跳转
    break;
}
case OP_RET: {
    vm.pc = vm.stack[vm.sp--];//取出返回地址
    break;
}

三、新增以及初始化栈区

这里写了新增栈区的结构,在使用时切记初始化栈指针为-1(栈指针永远指向栈空间,刚开始栈空所以指向-1)

struct VM {
    int reg[5];   // 5个通用寄存器 R0~R4
    int pc;       // 程序计数器
    bool running; // 是否运行

    int stack[256];//栈区
    int sp;//栈指针
};

四、实现自动标签插入

这里使用结构实现了对字节码的全程托管,原理是在设置标签时进行记录标签位置和标签名称,在后续处理根据调用名称自动插入标签位置

struct Assembler {
    // 字节码
    vector<int> code;
    // 标签列表
    unordered_map<string, int> labels;
    // 需要修复的回填跳转
    vector<pair<int, string>> fixups; 

    //插入字节码
    void emit(int v) {
        code.push_back(v);
    }

    //添加标签
    void label(const string& name) {
        labels[name] = code.size();
    }

    //记录待修复标签
    void jmp_placeholder(const string& name) {
        // 添加到待修复列表
        fixups.push_back({ (int)code.size(), name });
        // 占位
        code.push_back(0); 
    }

    //回填标签
    void patch() {
        for (auto& f : fixups) {
            int pos = f.first;
            const string& name = f.second;
            code[pos] = labels[name];
        }
    }

    //获取字节码
    int* getData()
    {
        //回填标签
        patch();
        //返回字节码
        return this->code.data();
    }
};

五、实现宏定义调用

实现宏定义调用,类似于汇编的形式,结构逻辑更清晰

// 操作
#define MOVI(as, reg, v)    do { as.emit(OP_MOV_IMM); as.emit(reg); as.emit(v); } while(0)
#define MOVR(as, r1, r2)    do { as.emit(OP_MOV_REG); as.emit(r1); as.emit(r2); } while(0)
#define ADDR(as, r1, r2)    do { as.emit(OP_ADD); as.emit(r1); as.emit(r2); } while(0)
#define SUBR(as, r1, r2)    do { as.emit(OP_SUB); as.emit(r1); as.emit(r2); } while(0)
#define PRINT(as, r)        do { as.emit(OP_PRINT); as.emit(r); } while(0)
#define HALT(as)            do { as.emit(OP_HALT); } while(0)
// 跳转
#define LABEL(as, name)     do { as.label(name); } while(0)
#define JMP(as, name)       do { as.emit(OP_JMP); as.jmp_placeholder(name); } while(0)
#define JZ(as, reg, name)   do { as.emit(OP_JZ); as.emit(reg); as.jmp_placeholder(name); } while(0)
#define CALL(as, name)      do { as.emit(OP_CALL); as.jmp_placeholder(name); } while(0)
#define RET(as)             do { as.emit(OP_RET); } while(0)
// 栈
#define PUSH(as, r)         do { as.emit(OP_PUSH); as.emit(r); } while(0)
#define POP(as, r)          do { as.emit(OP_POP);  as.emit(r); } while(0)

六、递归调用阶乘练习

这里的逻辑和上节的一样,不同的是更换成递归调用的实现方式,并且升级了宏调用以及自动标签的形式,逻辑更加清晰,代码如下:

//阶乘练习
int main() {
    Assembler as;
    VM vm = { 0 };

    //入参
    int n = 5;

    // main
    MOVI(as, R0, n);    // n = 5
    MOVI(as, R2, 1);    // const 1
    CALL(as, "fact");   //调用fast方法
    PRINT(as, R1);      //打印
    HALT(as);

    // ===== 函数 fact =====
    LABEL(as, "fact");
    JZ(as, R0, "base_case");// if (n == 0)
    PUSH(as, R0);// 保存 n(因为后面要改)
    SUBR(as, R0, R2);// n = n - 1
    CALL(as, "fact");// 递归调用
    POP(as, R0);// 恢复 n

    // ===== 乘法:R1 = R1 * R0 =====
    MOVI(as, R3, 0);// temp = 0
    MOVR(as, R4, R0);// counter = n
    LABEL(as, "mul_loop");
    JZ(as, R4, "mul_end");
    ADDR(as, R3, R1);   // temp += result
    SUBR(as, R4, R2);   // counter--
    JMP(as, "mul_loop");

    LABEL(as, "mul_end");
    MOVR(as, R1, R3);// result = temp
    RET(as);

    // ===== base_case =====
    LABEL(as, "base_case");
    MOVI(as, R1, 1);  // return 1
    RET(as);

    //执行
    run(vm, as.getData());

    return 0;
}

七、总结

本节主要内容为完善之前的vm解释器,为后续更为复杂的项目做铺垫,笔者希望借此入门学习解释器并且将其完成为一个可用的vm解释器框架,后续还会持续进行完善,欢迎大家讨论,如有错漏请留言!


传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 3天前 被mb_binusgki编辑 ,原因:
收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回