bboyjing's blog

自己动手写JVM十四【指令集和解释器(二)】

本章节继续学习指令集和解释器。

存储指令

和加载指令刚好相反,存储指令把变量从操作数栈顶弹出,然后存入局部变量表。和加载指令一样,存储指令也可以分为6类。存储指令相关代码位于/instructions/stores包下。下面以lstore系列指令为例来学习下,其实现代码位于lstore.go文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// lstore结构体
type LSTORE struct{ base.Index8Instruction }
// 将操作数存入指定局部变量表
func (self *LSTORE) Execute(frame *rtdata.Frame) {
_lstore(frame, uint(self.Index))
}
// 统一的lstore函数
func _lstore(frame *rtdata.Frame, index uint) {
// 弹出位于操作数栈顶的操作数
val := frame.OperandStack().PopLong()
// 设置局部变量
frame.LocalVars().SetLong(index, val)
}
// 将操作数存入第0号局部变量表,索引隐含在操作码中
type LSTORE_0 struct{ base.NoOperandsInstruction }
func (self *LSTORE_0) Execute(frame *rtdata.Frame) {
_lstore(frame, 0)
}
// 省略LSTORE_1、LSTORE_2、LSTORE_3
...

栈指令

栈指令直接对操作数栈进行操作,共9条:pop和pop2指令将栈顶变量弹出,dup系列指令复制栈顶变量,swap指令交换栈顶的两个变量。栈指令实现代码位于/instructions/stack包下。和其他类型的指令不同,栈指令并不关心变量类型。为了实现栈指令,需要给OperandStack结构体添加两个方法,修改/rtdata/operand_stack.go文件:

1
2
3
4
5
6
7
8
9
// 推入、弹出一个slot
func (self *OperandStack) PushSlot(slot Slot) {
self.slots[self.size] = slot
self.size++
}
func (self *OperandStack) PopSlot() Slot {
self.size--
return self.slots[self.size]
}

pop和pop2指令

pop指令相关代码位于pop.go文件中:

1
2
3
4
5
6
7
8
9
10
11
// pop结构体
type POP struct{ base.NoOperandsInstruction }
func (self *POP) Execute(frame *rtdata.Frame) {
stack := frame.OperandStack()
// 弹出slot
stack.PopSlot()
}
// pop2类似,略
...

dup指令

dup指令相关代码位于dup.go文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// dup结构体,复制栈顶单个变量
type DUP struct{ base.NoOperandsInstruction }
func (self *DUP) Execute(frame *rtdata.Frame) {
stack := frame.OperandStack()
// 先弹出栈顶变量
slot := stack.PopSlot()
// 将弹出的变量2次入栈,达到复制效果
stack.PushSlot(slot)
stack.PushSlot(slot)
}
// 还有其它5条dup指令实现参照项目源码
...

swap指令

swap指令相关代码位于swap.go文件中:

1
2
3
4
5
6
7
8
9
10
11
12
// swap结构体,交换栈顶两个变量
type SWAP struct{ base.NoOperandsInstruction }
func (self *SWAP) Execute(frame *rtdata.Frame) {
stack := frame.OperandStack()
// 栈顶两个变量出栈
slot1 := stack.PopSlot()
slot2 := stack.PopSlot()
// 按弹出的顺序将两个变量入栈,达到交换效果
stack.PushSlot(slot1)
stack.PushSlot(slot2)
}

数学指令

数学指令大致对应Java语言中的加、减、乘、除等数学运算符。数学指令包括算数指令、位移指令和布尔运算指令等,共37条,本节将全部实现,其实现代码位于/instructions/math包下。

算数指令

算数指令又可以进一步分为加法(add)指令、减法(sub)指令、乘法(mul)指令、除法(dive)指令、求余(rem)指令和取反(reg)指令6种。下面以稍微复杂一些的求余指令为例来演示下,其余算数指令代码参照项目源码。求余指令实现代码位于rem.go文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// int求余结构体
type IREM struct{ base.NoOperandsInstruction }
func (self *IREM) Execute(frame *rtdata.Frame) {
stack := frame.OperandStack()
// 弹出栈顶元素,作为除数
v2 := stack.PopInt()
// 弹出栈顶元素,作为被除数
v1 := stack.PopInt()
// 若除数为0,报错
if v2 == 0 {
panic("java.lang.ArithmeticException: / by zero")
}
// 求余运算
result := v1 % v2
// 将求余的结果入栈
stack.PushInt(result)
}
// 省略long、double和float结构体
...

位移指令

位移指令可以分为左移和右移两种,右移指令有可以分为算数右移(有符号右移)和逻辑右移(无符号右移)两种。算数右移和逻辑右移的区别仅在于是否保留符号位,如下面Java代码所示:

1
2
3
4
5
6
7
int x = -1;
//输出-1的2进制补码形式,11111111111111111111111111111111
System.out.println(Integer.toBinaryString(x));
//输出-1算数右移8位的2进制补码形式,11111111111111111111111111111111
System.out.println(Integer.toBinaryString(x >> 8));
//输出-1逻辑右移8位的2进制补码形式,00000000111111111111111111111111
System.out.println(Integer.toBinaryString(x >>> 8));

位移指令实现代码位于sh.go文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// int左移结构体
type ISHL struct{ base.NoOperandsInstruction }
func (self *ISHL) Execute(frame *rtdata.Frame) {
stack := frame.OperandStack()
// 弹出栈顶元素,指定左移位数
v2 := stack.PopInt()
// 弹出栈顶元素,作为操作数
v1 := stack.PopInt()
/*
因为int变量只有32位,所以取v2的前5位就足够表示位移位数了
0x1f为31,二进制00011111,正好和v2进行&操作
*/
s := uint32(v2) & 0x1f
// Go位移操作符右侧必须是无符号数,所以上面对v2进行了uint32转换
result := v1 << s
stack.PushInt(result)
}
//省略long左移操作
...
// int算数右移结构体(>>)
type ISHR struct{ base.NoOperandsInstruction }
func (self *ISHR) Execute(frame *rtdata.Frame) {
stack := frame.OperandStack()
// 弹出栈顶元素,指定右移位数
v2 := stack.PopInt()
// 弹出栈顶元素,作为操作数
v1 := stack.PopInt()
s := uint32(v2) & 0x1f
// 算数右移操作
result := v1 >> s
// 将移位操作结果入栈
stack.PushInt(result)
}
// int逻辑右移结构体(>>>)
type IUSHR struct{ base.NoOperandsInstruction }
func (self *IUSHR) Execute(frame *rtdata.Frame) {
stack := frame.OperandStack()
// 弹出栈顶元素,指定右移位数
v2 := stack.PopInt()
// 弹出栈顶元素,作为操作数
v1 := stack.PopInt()
s := uint32(v2) & 0x1f
// Go没有>>>运算符,需要先把v1转成无符号整数,移位操作后再转回有符号整数
result := int32(uint32(v1) >> s)
// 将移位操作结果入栈
stack.PushInt(result)
}
// 省略long右移操作
...

布尔运算指令

布尔运算指令只能操作int和long变量,分别安慰与(and)、按位或(or)、按位异或(xor)3种。下面以按位与为例来学习下,相关代码位于and.go文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// int与操作结构体
type IAND struct{ base.NoOperandsInstruction }
func (self *IAND) Execute(frame *rtdata.Frame) {
stack := frame.OperandStack()
// 弹出栈顶元素作为操作数
v2 := stack.PopInt()
// 弹出栈顶元素作为操作数
v1 := stack.PopInt()
// 将弹出元素进行与操作
result := v1 & v2
// 将与操作结果入栈
stack.PushInt(result)
}

iinc指令

iinc指令给局部变量表中的int变量增加常量值,局部变量表索引和常量值都由指令的操作数提供。其实现代码位于iinc.go文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// innc结构体
type IINC struct {
// 常量池索引
Index uint
// 常量值
Const int32
}
func (self *IINC) FetchOperands(reader *base.BytecodeReader) {
// 读取单字节操作数,作为局部变量表索引
self.Index = uint(reader.ReadUint8())
// 读取单字节操作数,作为常量值
self.Const = int32(reader.ReadInt8())
}
func (self *IINC) Execute(frame *rtdata.Frame) {
localVars := frame.LocalVars()
// 通过索引获取局部变量
val := localVars.GetInt(self.Index)
// 局部变量 + 常量
val += self.Const
// 回写局部变量
localVars.SetInt(self.Index, val)
}

下节继续。。。