自制Runtime:虚拟执行环境设计

来源:互联网 发布:广数928tc端面切削编程 编辑:程序博客网 时间:2024/05/01 10:06

编译器前端基本设计已经完成(详见我的自制编译器系列博文),但在考虑编译器后端之前,首先要考虑代码的运行环境,因此考虑再三我便把制作Runtime的过程写成博文,记录从中所学到的知识。

Runtime的设计以JVM为原型,加以部分的简化来去掉复杂的功能,并使得简单的Runtime可以在不多的代码行数内加以实现。

总的来说我们的Runtime需要实现以下几个功能:

1、运行特定的程序指令

2、以控制台的方式进行输出

3、可检查出运行时错误(如数组越界,空指针错误等)

4、有垃圾收集机制


一、Runtime结构设计

(1)帧栈

因为运行时暂且不考虑多线程并发或伪并发,所以整个Runtime具有一个帧栈,帧栈中存放栈帧。帧栈用于函数调用,每个调用函数都有着自己独立的栈帧,并在调用时将此帧压栈,返回时将栈帧弹出。

一个栈帧具有以下结构:

1、方法信息

记录当前调用的方法的签名

2、IP

指令寄存器,记录下一条将要执行的指令的位置,根据此之前的方法信息和IP从静态方法区获取指令。值得注意的是,每个方法具有自己独享的IP,这样可以简化方法退出时的恢复现场的工作。

3、局部变量表

局部变量表存放局部变量的信息,非static调用时this指针压在变量表的第一个位置。如果局部变量是一个基本数据类型(char,int ,double),局部变量表存放局部变量的值,否则存放一个对象的句柄。同时函数调用时参数均是通过局部变量表进行传递的。

4、操作数栈

虚拟执行环境设计成基于栈的执行环境,因此每个方法都有自己的操作数栈,方法调用时给其栈分配空间,返回时释放栈的空间。值得注意的是,每个方法都有专属于自己的栈所带来的好处是简化了方法进入和退出时的清理堆栈的工作,但需要额外的机制来进行返回值的传递。在设计中我们首先通过帧栈找到其调用者的栈帧,然后将返回值压入其调用者的操作数栈中。


(2)堆

所有对象的数据部分存储在堆上,一个堆可以理解为一段连续的地址空间,并且当堆的大小不够时可以进行动态的扩展。垃圾收集工作只在堆上进行。


(3)对象表

一个对象表由若干表项构成,一个表项为一个三元组,包括对象句柄、堆中位置及额外信息。使用对象表来存储程序运行过程中所有的对象的信息。

所谓对象句柄为一个32为整数,是某一个对象在Runtime的唯一标识,指令中所有对对象的操作均通过对象句柄来完成。

堆中位置描述一个对象在堆中的存储位置,并通过方法区类的相应接口来解析出该对象中的数据。此位置不固定,每次GC之后堆中位置或许会发生改变。

信息包含该对象的基本信息,包括但不仅限于一个指向该对象从属类的指针、是否是静态变量标识、数组长度、访问权限等。


(4)静态方法区

Runtime加载类之后将类的信息存储于静态方法区,这其中包括类的字段、类的方法签名、类的方法的指令序列。

堆中的内容将通过静态方法去类字段的内存布局来解析,同时执行引擎通过查找特定类的方法指令序列来执行指令。


二、类文件格式设计

为了使解析、阅读方便,在类文件格式设计中,信息以“行”的形式进行存放,每行之间通过\r\n进行分割。

类文件开头第一行:类名

第二行:字段数n

接下来的n行为字段

字段格式如下:

开头是一个0--4的整数,其中第一位代表是否是public,第二位代表是否是static;其后是字段类型,最后是字段名。三个信息以空格进行分割。

接下来一行是方法数m

接下来有m个方法体。

每个方法体开头为0--4的整数,其中第一位代表是否是public,第二位代表是否是static。接着是返回值类型,之后是方法名,之后是参数数量p,之后是p个参数类型。各信息之间以空格进行分割。

之后一行为{ ,代表方法的指令码开始。

之后跟若干行指令,指令以如下格式存放:

地址:指令码  操作数1,操作数2

其中地址为从1开始递增的整数,指令码为0--256的整数,详细参加之后定义,操作数可为立即数、字符串或没有操作数。

之后跟若干行为长量表,格式为#i  const,目前主要用来存储常量字符串。

例如: #0  hello,world

            #1  Runtime 

之类的 

三、指令码设计

基本参考Java字节码,并在此基础上加以简化。

字节码                助记符和指令格式             含义

0x00                      nop                                什么都不做

0x01                      iconst  X                         整形常量X压入堆栈

0x02                      dconst  X                        浮点常量压入堆栈

0x03                      load  X                            将本地变量表的第X位置的值压入堆栈

0x04                      aload                      将栈顶元素所指向的数组的第X个元素压入堆栈,X是栈自顶向下第二个元素

0x05                      store X                            将栈顶元素存入本地变量表的X位置

0x06                      astore   Y                将栈顶元素指向的数组的第X个元素存入本地变量表的Y位置,X是栈自顶向下第二个元素

0x07                      iadd                                 将栈顶两元素作为int相加并将结果压入栈

0x08                      isub                                 将栈顶两元素作为int相减并将结果压入堆栈

0x09                      imul                                  将栈顶两元素作为int相乘并将结果压入堆栈

0x0A                      idiv                                    将栈顶两元素作为int相除并将结果压入堆栈

0x0B                      dadd                                 将栈顶两元素作为double相加并将结果压入栈

0x0C                     dsub                                 将栈顶两元素作为double相减并将结果压入堆栈

0x0D                      dmul                                  将栈顶两元素作为double相乘并将结果压入堆栈

0x0E                      ddiv                                    将栈顶两元素作为double相除并将结果压入堆栈

0x0F                      ishl                                   将栈顶元素作为int左移X位并将结果压入堆栈,X是第二个元素

0x10                      ishr                                   将栈顶元素作为int右移X位并将结果压入堆栈,X是第二个元素

0x11                      iand                                  栈顶两元素按位与,结果压入堆栈

0x12                      ior                                      栈顶两元素按位或,结果压入堆栈

0x13                      ixor                                      栈顶两元素按位异或,结果压入堆栈

0x14                      i2d                                     栈顶int转double ,结果压入堆栈

0x15                      d2i                                     栈顶double转int,结果压入堆栈

0x16                      icmp                                   栈顶两元素当作int比较,结果压入堆栈(-1,0,1)

0x17                      dcmp                                  栈顶两元素作为double比较,结果压入堆栈

0x18                      ifz  X                                   如果栈顶元素是0,则跳转到X处执行

0x19                      ret                                      函数返回

0x1A                      invoke  X                            调用栈顶对象的非静态函数。

0x1B                      invokestatic X                     调用静态函数,X为函数全限定名,如Class.function

0x1C                      new                                    新建对象,并将对象句柄压入堆栈

0x1D                      newarray X                           新建类型X数组,并将数组句柄压入堆栈

0x1E                      arraylength                          将栈顶指向数组的长度压入堆栈

0x1F                      putfield   X                            将栈顶元素赋值给栈第二个元素指定对象的X域中

0x20                      getfield  X                             栈顶元素所指向的对象的X域的值压入堆栈

0x21                      putstatic X                            栈顶元素赋值给X

0x22                      getstatic X                             X所指向的静态变量压入堆栈

0x23                      pop                                      栈顶元素弹出

0x24                      NAND                                    栈顶元素取非

0x25                      ldc X                                     将常量表下标为X的字符串对象句柄压栈

0x26                      astore_1                               栈顶元素赋值给栈第二个元素指向的数组的下标为栈第三个元素的值 

原创粉丝点击