bboyjing's blog

自己动手写JVM二十四【方法调用和返回(二)】

本节继续实现方法调用和返回。

返回指令

方法执行完毕之后,需要把结果返回给调用方,这一工作由返回指令完成。返回指令属于控制指令,一共6条。其中return指令用于没有返回值的情况,areturn、ireturn、lreturn、和dreturn分别用于返回引用、int、long、float和double类型的值。该系列指令实现代码位于/instructions/control/return.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
// return指令结构体
type RETURN struct{ base.NoOperandsInstruction }
func (self *RETURN) Execute(frame *rtdata.Frame) {
// 无返回值,直将当前帧从Java虚拟机栈中弹出
frame.Thread().PopFrame()
}
// areturn指令结构体
type ARETURN struct{ base.NoOperandsInstruction }
func (self *ARETURN) Execute(frame *rtdata.Frame) {
// 获取当前线程
thread := frame.Thread()
// 弹出当前帧
currentFrame := thread.PopFrame()
// 获取前一帧,也就是调用方栈帧
invokerFrame := thread.TopFrame()
// 弹出当前帧的操作数栈顶引用变量的值
ref := currentFrame.OperandStack().PopRef()
// 将返回值推入前一帧的操作数栈顶
invokerFrame.OperandStack().PushRef(ref)
}
// 省略其他指令实现
...

另外Thread结构体需要添加TopFrame()方法,和CurrentFrame()代码一样,用不同的名称只是为了避免混淆。

方法调用指令

本章不考虑接口的静态方法和默认方法,本章实现的4条指令并不能满足Java虚拟机规范第8版的规定,我们还在学习中。

invokestatic指令

该指令实现代码位于/instructions/references/invokestatic.go文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// invokestatic指令结构体
type INVOKE_STATIC struct{ base.Index16Instruction }
func (self *INVOKE_STATIC) Execute(frame *rtdata.Frame) {
// 获取运行时常量池
cp := frame.Method().Class().ConstantPool()
// 通过索引获取方法符号引用
methodRef := cp.GetConstant(self.Index).(*heap.MethodRef)
// 解析方法
resolvedMethod := methodRef.ResolvedMethod()
// 判断方法是否是static
if !resolvedMethod.IsStatic() {
panic("java.lang.IncompatibleClassChangeError")
}
base.InvokeMethod(frame, resolvedMethod)
}

invokespecial指令

修改/instructions/references/invokespecial.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
// invokespecial指令结构体
type INVOKE_SPECIAL struct{ base.Index16Instruction }
func (self *INVOKE_SPECIAL) Execute(frame *rtdata.Frame) {
// 获取当前class
currentClass := frame.Method().Class()
// 获取当前class的运行时常量池
cp := currentClass.ConstantPool()
// 通过索引获取方法符号引用
methodRef := cp.GetConstant(self.Index).(*heap.MethodRef)
// 解析需要调用的方法所属的类
resolvedClass := methodRef.ResolvedClass()
// 解析需要调用的方法
resolvedMethod := methodRef.ResolvedMethod()
// 如果方法是构造函数(init(...)方法),则声明该方法的类必须是之前通过方法符号引用解析出来的类
if resolvedMethod.Name() == "<init>" && resolvedMethod.Class() != resolvedClass {
panic("java.lang.NoSuchMethodError")
}
// 判断方法是否是static
if resolvedMethod.IsStatic() {
panic("java.lang.IncompatibleClassChangeError")
}
// 获取距离操作数栈顶n个slot的引用变量,也就是this引用
ref := frame.OperandStack().GetRefFromTop(resolvedMethod.ArgSlotCount() - 1)
// 如果this引用为空,抛出NullPointerException异常
if ref == nil {
panic("java.lang.NullPointerException")
}
// 省略一系列判断方法
...
base.InvokeMethod(frame, methodToBeInvoked)
}

在实现invokespecial指令代码中,有一些其他部分需要完善,如下:

  1. 添加GetRefFromTop()方法,该方法位于/rtdata/operand_stack.go文件中,方法的作用是返回距离操作数栈顶ngeslot的引用变量。要注意的是,在传递参数之前,不能破坏操作数栈的状态,所以该方法的实现只是获取了引用变量,而没有从栈中弹出。

    1
    2
    3
    4
    // 获取距离操作数栈顶n个slot的引用变量
    func (self *OperandStack) GetRefFromTop(n uint) *heap.Object {
    return self.slots[self.size-1-n].ref
    }
  2. 修改/rtdata/heap/class.go,将getPackageName()方法改为GetPackageName(),注意refactor,因为其他文件用到了将getPackageName()方法,并添加如下方法:

    1
    2
    3
    func (self *Class) SuperClass() *Class {
    return self.superClass
    }
  3. 修改/rtdata/heap/class_hierarchy.go文件,将isSubClassOf()方法改为IsSubClassOf(),并且添加如下方法:

    1
    2
    3
    func (self *Class) IsSuperClassOf(other *Class) bool {
    return other.IsSubClassOf(self)
    }
  4. 修改/rtdata/heap/method.go文件,添加如下方法:

    1
    2
    3
    func (self *Method) IsAbstract() bool {
    return 0 != self.accessFlags&ACC_ABSTRACT
    }

invokevirtual指令

该指令实现代码位于/instructions/references/invokesvirtual.go文件中,大部分代码和invokespecial指令类似,就不贴出来了,参照项目源码。

invokeinterface指令

和上面山条方法调用指令略有不同,在字节码中,invokeinterfase指令的操作吗后面跟着4个字节,而非2个字节。前两个字节的含义和其他指令相同,是个uint16运行时常量池索引。第3个字节时给方法传递参数需要的slot数,其含义和给Method结构体定义的argSlotCount字段相同,这个数是可以根据方法描述符计算出来的,它的存在仅仅是因为历史原因。第4个字节是留给Oracle的某些Java虚拟机实现用的,它的值必须是0,该字节的存在是为了保证Java虚拟机可以向后兼容。Execute()方法和之前类似,下面就不贴出来了,参照项目源码。Execute()方法中IsImplements()方法在/rtdata/heap/class_hierarchy.go文件中,也不贴出来了。

1
2
3
4
5
6
7
8
9
10
11
12
13
// invokeinterface指令结构体
type INVOKE_INTERFACE struct {
index uint
// count uint8
// zero uint8
}
func (self *INVOKE_INTERFACE) FetchOperands(reader *base.BytecodeReader) {
self.index = uint(reader.ReadUint16())
// 下面两个字节读出来丢弃
reader.ReadUint8() // count
reader.ReadUint8() // must be 0
}

至此,4条方法调用指令都实现完毕了,最后不要忘了把factory.go文件中本章节相关指令项的注释去掉。

改进解释器

之前写的解释器只能执行单个方法,现在可以扩展它,让它支持方法调用。修改/instructions/interceptor.go文件:

  1. 修改Interpret()方法,添加logInst参数,类型为bool,作用式控制是否把指令执行信息打印到控制台。

  2. 修改/rtdata/jvm_stack.go文件,添加isEmpty()方法,修改/rtdata/thread.go文件,添加IsStackEmpty()方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 判断栈顶指针是否为空
    func (self *Stack) isEmpty() bool {
    return self._top == nil
    }
    // 判断虚拟机栈是否为空
    func (self *Thread) IsStackEmpty() bool {
    return self.stack.isEmpty()
    }
  3. 修改catchErr()方法实现方式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    func catchErr(thread *rtdata.Thread) {
    if r := recover(); r != nil {
    logFrames(thread)
    panic(r)
    }
    }
    // 打印Java虚拟机栈错误信息
    func logFrames(thread *rtdata.Thread) {
    for !thread.IsStackEmpty() {
    frame := thread.PopFrame()
    method := frame.Method()
    className := method.Class().Name()
    fmt.Printf(">> pc:%4d %v.%v%v \n",
    frame.NextPC(), className, method.Name(), method.Descriptor())
    }
    }
  4. 修改loop()函数,也是本次改造最红要的变化之处:

    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
    func loop(thread *rtdata.Thread, logInst bool) {
    // 实例化BytecodeReader
    reader := &base.BytecodeReader{}
    // 遍历Java虚拟机栈,直到栈顶指针为空
    for {
    // 获取Java虚拟机栈当前帧(栈顶指针)
    frame := thread.CurrentFrame()
    // 获取下个pc寄存器地址(也就是下一条将要执行的指令所在的位置)
    pc := frame.NextPC()
    // 设置线程pc寄存器为当前帧的下一个指令地址
    thread.SetPC(pc)
    // 重置reader
    reader.Reset(frame.Method().Code(), pc)
    // 获取指令操作码
    opcode := reader.ReadUint8()
    // 根据操作码常见指令,参照factory.go文件
    inst := NewInstruction(opcode)
    // 读取操作数
    inst.FetchOperands(reader)
    // 设置下一个指令起始地址(下一条指令操作码在字节码中的位置)
    frame.SetNextPC(reader.PC())
    // 是否将执行信息打印到控制台
    if logInst {
    logInstruction(frame, inst)
    }
    // 执行指令
    inst.Execute(frame)
    // 若栈顶指针为空,表示线程执行结束
    if thread.IsStackEmpty() {
    break
    }
    }
    }
    // 打印指令执行信息
    func logInstruction(frame *rtdata.Frame, inst base.Instruction) {
    method := frame.Method()
    className := method.Class().Name()
    methodName := method.Name()
    pc := frame.Thread().PC()
    fmt.Printf("%v.%v() #%2d %T %v\n", className, methodName, pc, inst, inst)
    }

下节继续。。。