接下来,就是要实现一个虚拟机了。记得编码高质量的代码中有一条:不要过早地优化你的代码。所以,也本着循序渐进的原则,我将从实现一个解释器开始,逐步过渡到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\n");
 
}
 
void mov(REG* cpuREG)
{
 
    memSet(cpuREG->R_PC+1, memGet(cpuREG->R_PC+2));
 
    sprintf("MOV [0x%4x], [0x%4x]\n", 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]\n", 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]\n", cpuREG->R_PC+1, cpuREG->R_PC+2);
    cpuREG->R_PC+=3;
}
 
void jmp(REG* cpuREG)
{
 
    sprintf("JMP [0x%4x] \n", cpuREG->R_PC+1);
 
    cpuREG->R_PC=memGet(cpuREG->R_PC+1);
}
 
void jcp(REG* cpuREG)
{
 
    sprintf("JCP [0x%4x], [0x%4x]\n", 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);
 
}

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

 
 

JIT动态编译器的原理与实现之Interpreter3的更多相关文章

  1. JIT动态编译器的原理与实现之Interpreter(解释器)的实现(三)

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

  2. gcc/g++等编译器 编译原理: 预处理,编译,汇编,链接各步骤详解

    摘自http://blog.csdn.net/elfprincexu/article/details/45043971 gcc/g++等编译器 编译原理: 预处理,编译,汇编,链接各步骤详解 C和C+ ...

  3. java9新特性-21-java的动态编译器

    1. 官方Feature 243: Java-Level JVM Compiler Interface 295: Ahead-of-Time Compilation 2. 产生背景 Oracle 一直 ...

  4. Java动态编译技术原理

    这里介绍Java动态编译技术原理! 编译,一般来说就是将源代码转换成机器码的过程,比如在C语言中中,将C语言源代码编译成a.out,,但是在Java中的理解可能有点不同,编译指的是将java 源代码转 ...

  5. Java 动态调试技术原理及实践

    本文转载自Java 动态调试技术原理及实践 导语 断点调试是我们最常使用的调试手段,它可以获取到方法执行过程中的变量信息,并可以观察到方法的执行路径.但断点调试会在断点位置停顿,使得整个应用停止响应. ...

  6. Android JIT实时编译器的设置

    在Android  JIT实时编译是在Android 2.2之后才引入的,JIT编译器可以显著的提高机器的性能,经过测试,android 2.2的性能较android 2.1提高了 2-5倍.JIT提 ...

  7. java高级---->Java动态代理的原理

    Java动态代理机制的出现,使得 Java 开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类.代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执行的过程 ...

  8. Cglib动态代理实现原理

    Cglib动态代理实现方式 我们先通过一个demo看一下Cglib是如何实现动态代理的. 首先定义个服务类,有两个方法并且其中一个方法用final来修饰. public class PersonSer ...

  9. RxJava RxPermissions 动态权限 简介 原理 案例 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

随机推荐

  1. MonkeyDevcie API 实践全记录

    1.    背景 使用SDK自带的NotePad应用作为实践目标应用,目的是对MonkeyDevice拥有的成员方法做一个初步的了解. 以下是官方列出的方法的Overview. Return Type ...

  2. yii性能调节

    网络应用程序的性能受很多因素的影响.数据库存取,文件系统操作,网络带宽等都是潜在的影响因素. Yii 已在各个方面减少框架带来的性能影响.但是在用户的应用中仍有很多地方可以被改善来提高性能. 1. 开 ...

  3. 一个可以直接使用的MsgBox基于form居中API

    可直接复制DialogBox项目(文件夹)到需要的项目中然后直接引用和using CodeProject.Dialog 已修正原作者代码错误的地方,可直接使用,VS2010测试成功 具体可以参考案例T ...

  4. php学习之路:php在iconv功能 详细解释

    iconv函数库可以完毕各种字符集间的转换,是php编程中必不可少的基础函数库. 使用方法例如以下: $string = "亲爱的朋友欢迎訪问胡文芳的博客.希望给您带来一点点的帮助!&quo ...

  5. leetcode第29题--Substring with Concatenation of All Words

    problem: You are given a string, S, and a list of words, L, that are all of the same length. Find al ...

  6. Redis源代码分析(二十四)--- tool工具类(2)

    在上篇文章中初步的分析了一下,Redis工具类文件里的一些使用方法,包含2个随机算法和循环冗余校验算法,今天,继续学习Redis中的其它的一些辅助工具类的使用方法.包含里面的大小端转换算法,sha算法 ...

  7. 项目管理工具 Redmine 安装试用手记

    原文:项目管理工具 Redmine 安装试用手记 项目管理工具 Redmine 安装试用手记 分类: Redmine2009-06-01 10:12 996人阅读 评论(1) 收藏 举报 项目管理工具 ...

  8. WebIM(4)----Comet的特殊之处

    WebIM系列文章 在一步一步打造WebIM(1)一文中已经使用Comet实现了一个简单的WebIM,那么,Comet究竟和一般的打开网页有何区别,本文将通过编写一个简单的HTTP服务器来说明两者的区 ...

  9. [置顶] android调用第三方库——第四篇——调用多个第三方库

    0:前言: 在前面三篇中我们介绍了android调用第三方库的形式,在这一篇中我们介绍调用多个第三方库的Android.mk的写法,由于其他三篇介绍的很详细,这里只给出Android.mk的内容. [ ...

  10. WCF入门教程(图文)VS2012

    WCF入门教程(图文)VS2012 上一遍到现在已经有一段时间了,先向关注本文的各位“挨踢”同仁们道歉了.小生自认为一个ITer如果想要做的更好,就需要将自己的所学.所用积极分享出来,接收大家的指导和 ...