本章节继续学习指令集和解释器。
控制指令
控制指令共有11条,本节实现其中的3条指令:goto、tableswitch和lookupswitch。该系列指令代码位于/instructions/control包下。
goto指令
goto指令实现代码位于goto.go文件中:
tableswitch指令
Java语言中的switch-case语句有两种实现方式:如果case可以编码成一个索引表,则实现成tableswitch指令,否则实现成lookupswitch指令。借用下Java虚拟机规范中的例子来了解下这两种实现方式的区别。
可以编译成tableswitch指令的Java方法:
12345678int chooseNear(int i){switch (i) {case 0: return 0;case 1: return 1;case 2: return 2;default: return -1;}}编译成lookupswitch指令的Java方法:
12345678int chooseFar(int i){switch (i) {case -100: return -1;case 0: return 0;case 100: return 1;default: return -1;}}
tableswitch指令实现代码位于tableswitch.go文件中,先看下该指令结构体定义:
这个指令比之前的指令要稍微复杂点,我们还是结合class文件来理解下,在java项目中新建TableSwitch类,并把chooseNear()方法添加至类中,然后查看class文件:
tableswitch的字节码比较长,我们慢慢看。
- 第一个字节0xAA表示助记符tableswitch指令
后面两个字节0x00、0x00没有实质意义,因为tableswitch指令操作码的后面会有0~3个字节的padding,以保证defaultOffset在字节码中的地址是4的倍数。需要修改bytecode_reader.go文件,添加SkipPadding方法
123456// 跳过Padding字节func (self *BytecodeReader) SkipPadding() {for self.pc%4 != 0 {self.ReadUint8()}}再后面四个字节0x00000021表示default情况下执行跳转所需的字节码偏移量,就是switch语句中的default分支,对应TABLE_SWITCH结构体的defaultOffset
- 再后面四个字节0x00000000表示case取值范围的最小值,也就是例子中的case分支的最小值0,对应TABLE_SWITCH结构体的low
- 再后面四个字节0x00000002表示case取值范围的最大值,也就是例子中的case分支的最大值2,对应TABLE_SWITCH结构体的high
- 最后是不定字节数((high - low + 1) × 4),每4个字节是一个int32值,存放到TABLE_SWITCH结构体的jumpOffSets索引表中,对应各个case情况下执行跳转所需的字节码偏移量。需要修改bytecode_reader.go文件,添加ReadInt32s方法 12345678910// 读取指定数量的int32,并返回数组func (self *BytecodeReader) ReadInt32s(n int32) []int32 {ints := make([]int32, n)for i := range ints {ints[i] = self.ReadInt32()}return ints}// 省略ReadInt32(),参照源码
理解了字节码,下面可以来读取和执行了,修改tableswitch.go文件:
lookupswitch指令
lookupswitch指令的字节码和tableswitch类似,这里就不详细说明了。该指令实现代码位于lookupswitch.go文件中:
扩展指令
本节来实现一些扩展指令,相关代码位于/instructions/extended包下。
wide指令
加载类指令、存储类指令、ret指令和iinc指令需要按索引访问局部变量表,索引以uint8的形式存在字节码中。对于大部分方法来说,局部变量表大小都不会超过256,所以用一字节来表示索引就够了。但是如果有方法的局部变量表超过这限制,Java虚拟机规范定义了wide指令来扩展前述指令。wide指令实现代码位于wide.go文件中:
ifnull指令和ifnonnull指令
这两个指令的功能时根据引用是否时null进行跳转,实现代码位于ifnull.go文件中:
goto_w指令
goto_w指令和goto指令唯一的区别就是索引从2个字节变成了4个字节,代码就不列出来了,参照goto_w.go文件。
指令集就学到这里,下一节将学习如何编写一个简单的解释器。