《自己动手写Java虚拟机》学习笔记(五)

第五章 指令集和解释器

本章基于第三章(解析.class文件)和第四章(运行时数据区),编写一个建议的解释器。

5.1 字节码和指令集

每一个类或者接口都可以被Java编译器编译成为一个.class文件,类或接口的方法信息就放在.class文件的method_info结构中。如果方法不是抽象的,也不是本地方法,方法的Java代码就会被编译器便已成为字节码(即使是空的,也有回一条return语句),存放在method_info的Code属性。

JVM每条指令都以一个单字节的操作码开头。即:操作码最多有256个。每条操作码都有对应的助记符。操作码后面可以跟零字节或多字节的操作数。比如:getstatic,如果其操作数是0x0002,表示常量池的第二个常量。为了让编码后的字节码更加紧凑,很多操作码本身就隐含了操作数。比如:iconst_0表示把常数0推入操作数栈。

操作数栈和局部变量表只存放数据的值,并不记录数据类型。那么,指令必须要知道自己在操作什么类型的数据。比如:iadd就是对int值进行加法。

助记符首字母及对应的java数据类型:a(reference),b(byte),c(char),d(double),f(float),i(int),l(long),s(short)。

指令按照用途分为11类:常量(constants)指令,加载(loads)指令,存储(stores)指令,操作数栈(stack)指令,数学(math)指令,转化(conversions)指令,比较(comparies)指令,控制(control)指令,引用(references)指令,扩展(extended)指令,保留(reserved)指令。

5.2 指令和指令解码

java虚拟机解释器的大致逻辑:

do {
    atomically calculate pc and fetch opcode at pc;
    if(operands) fetch operands;
    execute the action for the opcode;
} while(there is more to do);

那么我们需要一个结构体来抽象指令。基于这个结构暂时提供四种实现:NoOperandsInstruction、BranchInstruction(需要有一个offset字段表示位移)、Index8Instruction(需要一个Index字段。适用于存储和加载指令。他们都需要按照单字节索引存取局部变量)、Index16Instruction(需要一个Index字段适用于某些访问运行时常量池的指令。常量池索引是两字节)。

字节码阅读器。他需要有一个标志记录已经读到了哪个字节。我们把它成为pc。另外他需要得到方法字节码的全部内容。

各个指令按照其具体意义实现:

常量指令把常量推入操作数栈顶。常量来自与三个地方:隐含在操作码里(const系列),操作数和运行时常量池(ldc系列)。另外加上nop指令(啥也不干)。

加载指令从局部变量表获取变量,然后推入操作数栈。

存储指令把变量操作数栈顶弹出,存入局部变量表。

栈指令直接对操作数栈进行操作。

数学指令包括算术指令、位移指令和布尔运算指令。

类型转化指令大致对应java语言中的基本类型强制类型转换操作。

比较指令分为:1.比较结果推入操作数栈顶,2.根据比较结果跳转。(a>b,结果为1;a==b,结果为0;a<b,结果为-1)。

控制指令:return系列和goto,tableswitch和lookupswitch。

扩展指令:multianewarry创建多维数组;wide指令拓展前述指令;ifnull,ifnonnull根据引用是否是null进行跳转;goto_w。

5.3 解释器

1.能够得到Code属性。

2.执行方法需要的局部变量,操作数栈空间以及方法字节码。

3.要先创建一个Thread实例。

4.要创建一个帧,并推入虚拟机栈顶。

5.执行。

6.循环执行字节码:计算PC,解码指令,执行指令。

5.4 Go语言语法

Go位移操作符右侧必须是无符号整数。

Go语言没有Java中的>>>操作符,为了达到目的,需要把value转化成为无符号整数,位移操作之后,在转回有符号整数。

猜你喜欢

转载自blog.csdn.net/monkeydcoding/article/details/81298289