bboyjing's blog

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

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

类型转换指令

类型转换指令大致对应Java语言中的基本类型强制转换操作。按照被转换变量的类型,类型转换指令可以分为4种:i2x系列指令把int变量强制转换成其他类型;l2x系列指令把long变量强制转换成其他类型;f2x系列指令把float变量强制转换成其他类型;d2x系列指令把double变量强制转换成其他类型。该指令实现代码位于/instructions/conversions包下,下面以d2x系类指令为例来学习下,其代码位于d2x.go文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// double强转成float结构体
type D2F struct{ base.NoOperandsInstruction }
func (self *D2F) Execute(frame *rtdata.Frame) {
stack := frame.OperandStack()
// 弹出栈顶double型变量
d := stack.PopDouble()
// 强转成float
f := float32(d)
// 将强转后的float变量入栈
stack.PushFloat(f)
}
//省略D2I、D2I
...

比较指令

比较指令可以分为两类:一类将比较结果推入操作数栈,一类根据比较结果跳转。比较指令时编译器实现if-else、for、while等语句的基石。该系列指令实现代码位于/instructions/comparisons包下。

lcmp指令

lcmp指令用于比较long变量,其实现代码位于lcmp.go文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// long比较指令结构体
type LCMP struct{ base.NoOperandsInstruction }
func (self *LCMP) Execute(frame *rtdata.Frame) {
stack := frame.OperandStack()
// 弹出栈顶long型变量,作为比较符后面的操作数
v2 := stack.PopLong()
// 弹出栈顶long型变量,作为比较府前面的操作数
v1 := stack.PopLong()
if v1 > v2 {
// 若v1大于v2,则将1入栈
stack.PushInt(1)
} else if v1 == v2 {
// 若v1等于v2,则将0入栈
stack.PushInt(0)
} else {
// 若v1小于v2,则将-1入栈
stack.PushInt(-1)
}
}

fcmp<op>和dcmp<op>指令

fcmpg和fcmpl指令用于比较float变量。由于浮点数计算有可能产生NaN值,所以比较两个浮点数时除了大于、等于、小于之外,还有第4种结果:无法比较。fcmpg和fcmpl指令的区别就在于对第4种结果的定义。其实现代码位于fcmp.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
// fcmpg指令结构体
type FCMPG struct{ base.NoOperandsInstruction }
func (self *FCMPG) Execute(frame *rtdata.Frame) {
_fcmp(frame, true)
}
// fcmpl指令结构体
type FCMPL struct{ base.NoOperandsInstruction }
func (self *FCMPL) Execute(frame *rtdata.Frame) {
_fcmp(frame, false)
}
func _fcmp(frame *rtdata.Frame, gFlag bool) {
stack := frame.OperandStack()
// 弹出栈顶float型变量,作为比较符后面的操作数
v2 := stack.PopFloat()
// 弹出栈顶long型变量,作为比较府前面的操作数
v1 := stack.PopFloat()
if v1 > v2 {
stack.PushInt(1)
} else if v1 == v2 {
stack.PushInt(0)
} else if v1 < v2 {
stack.PushInt(-1)
} else if gFlag {
// fcmpg指令对于第四种结果入栈1
stack.PushInt(1)
} else {
// fcmpl指令对于第四种结果入栈0
stack.PushInt(-1)
}
}

dcmpg和dcmpl指令用来比较double变量,和fcmp指令实现类似,相关代码参照dcmp.go文件

if<cond>指令

if<cond>指令把操作数栈顶的int变量弹出,然后跟0进行比较,满足条件则跳转。假设从栈顶弹出的变量时x,则指令执行跳转操作的条件如下:

  • ifeq: x == 0
  • ifne: x != 0
  • iflt: x < 0
  • ifle: x <= 0
  • ifgt: x > 0
  • ifge: x >= 0

该指令实现代码位于ifcond.go文件中,下面以ifeq为例来看下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ifeq指令结构体
type IFEQ struct{ base.BranchInstruction }
func (self *IFEQ) Execute(frame *rtdata.Frame) {
// 弹出栈顶int变量
val := frame.OperandStack().PopInt()
if val == 0 {
// 满足条件跳转
base.Branch(frame, self.Offset)
}
}
//省略ifne、iflt、ifle、ifgt、ifge
...

真正的跳转逻辑在base.Branch()函数中,因为这个函数在很多指令中都会用到,所以把它定义在/base/branch_logic.go文件中,其代码如下:

1
2
3
4
5
func Branch(frame *rtdata.Frame, offset int) {
pc := frame.Thread().PC()
nextPC := pc + offset
frame.SetNextPC(nextPC)
}

上面Thread()和SetNextPC()都还没定义,暂时编译不过没关系,后面会补充。

if_icmp<cond>指令

if_icmp<cond>指令把栈顶的两个int变量弹出,然后进行比较,满足条件则跳转。跳转条件和if<cond>指令类似,下面以if_icmpne为例来演示下,相关嗲吗位于if_icmp.go文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// if_icmpne指令结构体
type IF_ICMPNE struct{ base.BranchInstruction }
func (self *IF_ICMPNE) Execute(frame *rtdata.Frame) {
// 获取栈顶两个变量的值,并比较
if val1, val2 := _icmpPop(frame); val1 != val2 {
// 若不相等,则跳转
base.Branch(frame, self.Offset)
}
}
func _icmpPop(frame *rtdata.Frame) (val1, val2 int32) {
stack := frame.OperandStack()
// 弹出栈顶int型变量
val2 = stack.PopInt()
// 弹出栈顶int型变量
val1 = stack.PopInt()
return
}
// 省略其他比较符

if_acmp<cond>指令

if_acmpeq和if_acmpne指令把栈顶的两个引用弹出,根据引用是否相同进行跳转。其实现代码位于if_acmp.go文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// if_acmpeq指令结构体
type IF_ACMPEQ struct{ base.BranchInstruction }
func (self *IF_ACMPEQ) Execute(frame *rtdata.Frame) {
if _acmp(frame) {
base.Branch(frame, self.Offset)
}
}
func _acmp(frame *rtdata.Frame) bool {
stack := frame.OperandStack()
// 弹出栈顶引用
ref2 := stack.PopRef()
// 弹出栈顶引用
ref1 := stack.PopRef()
// 判断是否相等
return ref1 == ref2
}
// 省略if_acmpne
...

下节继续。。。