zoukankan      html  css  js  c++  java
  • JIT动态编译器的原理与实现之Interpreter3

         接下来,就是要实现一个虚拟机了。记得编码高质量的代码中有一条:不要过早地优化你的代码。所以,也本着循序渐进的原则,我将从实现一个解释器开始,逐步过渡到JIT动态编译器,这样的演化可以使原理看起来更清晰。

         解释器的原理很简单,就是一条指令一条指令的解释并执行。具体流程分为:取出指令-解码指令-执行-返回主流程。这样形成一个无限循环,如下图所示:

         这里的主流程就是上篇定义的程序rom.bin。但rom.bin不能直接运行,需要一个解释器来包裹它,来解释执行。解释器放在一个无限循环中,使得主流程无限运行不停止:

    void loop()
    {
        for(;;)
        {
        Interpreter(&CPUREG);          
        }
    }

          这样,整个虚拟机的运行可以定义为:

    memInit();         //初始化内存
    ResetCPU(&CPUREG); //初始化CPU
    loadROM();         //加载rom.bin
    loop();            //执行主流程
    memFree();         //释放内存

          接下来需要做的就是取出指令送入解释器了。为此需要定义读写内存的函数memGet和memSet:

    void memSet(unsigned int, unsigned char);
     
    unsigned char memGet(unsigned int);
     
    void memSet(unsigned int addr, unsigned char data)
    {
        char Str_Err[256];
     
        if(addr>64)
        {
          sprintf(Str_Err, "MEM: invalid mem write: 0x%8x", addr);
          MessageBox(NULL, Str_Err, "Warning", MB_OK);
        }
        else
        {
          RAM[addr & 0xff]=data;
        }
     
    }
     
    unsigned char memGet(unsigned int addr)
    {
        char Str_Err[256];
        unsigned char val = 0;
     
        if(addr>64)
        {
          sprintf(Str_Err, "MEM: invalid mem read: 0x%8x", addr);
          MessageBox(NULL, Str_Err, "Warning", MB_OK);
        }
        else
        {
            val=RAM[addr & 0xff];
        }
     
        return val;
    }

          读写均为一个字节。由于上篇定义的CPU寻址范围只有64字节大小,所以超过64字节就要给出错误提示。

          然后需要为每一个CPU指令机器码实现一个解码执行函数:

    void nop(REG*);
    void mov(REG*);
    void add(REG*);
    void cmp(REG*);
    void jmp(REG*);
    void jcp(REG*);
     
    void nop(REG* cpuREG)
    {
     
        cpuREG->R_PC++;
     
        sprintf("NOP ");
     
    }
     
    void mov(REG* cpuREG)
    {
     
        memSet(cpuREG->R_PC+1, memGet(cpuREG->R_PC+2));
     
        sprintf("MOV [0x%4x], [0x%4x] ", cpuREG->R_PC+1, cpuREG->R_PC+2);
     
        cpuREG->R_PC+=3;
    }
     
    void add(REG* cpuREG)
    {
     
        memSet(cpuREG->R_PC+1, memGet(cpuREG->R_PC+1)+memGet(cpuREG->R_PC+2));
     
        sprintf("ADD [0x%4x], [0x%4x] ", cpuREG->R_PC+1, cpuREG->R_PC+2);
     
        cpuREG->R_PC+=3;
    }
     
    void cmp(REG* cpuREG)
    {
     
        if((memGet(cpuREG->R_PC+1)-memGet(cpuREG->R_PC+2)) < 0)
        {
            cpuREG->R_CMP=0;
        }
        else
        {
            cpuREG->R_CMP=1;
        }
     
        sprintf("CMP [0x%4x], [0x%4x] ", cpuREG->R_PC+1, cpuREG->R_PC+2);
        cpuREG->R_PC+=3;
    }
     
    void jmp(REG* cpuREG)
    {
     
        sprintf("JMP [0x%4x] ", cpuREG->R_PC+1);
     
        cpuREG->R_PC=memGet(cpuREG->R_PC+1);
    }
     
    void jcp(REG* cpuREG)
    {
     
        sprintf("JCP [0x%4x], [0x%4x] ", cpuREG->R_PC+1, cpuREG->R_PC+2);
     
        if(cpuREG->R_CMP==0)
        {
            cpuREG->R_PC=memGet(cpuREG->R_PC+1);
        }
        else
        {
            cpuREG->R_PC=memGet(cpuREG->R_PC+2);
        }
         
    }

         这里最重要的是要小心处理PC寄存器。一开始CPU初始化的时候,PC寄存器是设为0的,而自定义的rom.bin也是从0地址开始执行的。如果你虚拟的CPU不是从0地址开始执行,那么在CPU初始化的时候就要把PC寄存器设为相应的开始地址。另外每一条指令可能涉及的地址数不相同,那么PC寄存器的变动也要不同。最后,跳转指令也可能要根据比较寄存器的内容来改变PC寄存器。

         做了如上的准备之后就可以实现解释器了。这里用switch-case结构来决定哪条指令被执行。为了简单起见,用了一个函数指针来执行解码函数:

    void (*func)(REG*);
     
    //Interpreter
    void Interpreter(REG* cpuREG)
    {
        char Str_Err[256];
     
        switch(memGet(cpuREG->R_PC))
        {
        case 0:
            func=nop;
            break;
        case 1:
            func=mov;
            break;
        case 2:
            func=add;
            break;
        case 3:
            func=cmp;
            break;
        case 4:
            func=jmp;
            break;
        case 5:
            func=jcp;
            break;
        default:
            sprintf(Str_Err, "Unhandled Opcode (0x%4x) at [0x%4x]", memGet(cpuREG->R_PC), cpuREG->R_PC);
            MessageBox(NULL, Str_Err, "Warning", MB_OK);
            return;
     
        }
     
        func(cpuREG);
     
    }

          首先从内存中取出数据,根据机器码来决定执行解码函数,最后执行。执行结果如下:

     
     
  • 相关阅读:
    ArrayList removeRange方法分析
    LinkedHashMap源码分析(基于JDK1.6)
    LinkedList原码分析(基于JDK1.6)
    TreeMap源码分析——深入分析(基于JDK1.6)
    51NOD 2072 装箱问题 背包问题 01 背包 DP 动态规划
    51 NOD 1049 最大子段和 动态规划 模板 板子 DP
    51NOD 1006 最长公共子序列 Lcs 动态规划 DP 模板题 板子
    8月20日 训练日记
    CodeForces
    CodeForces
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3153497.html
Copyright © 2011-2022 走看看