bboyjing's blog

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

解释器

指令集已经实现得差不多了,本节将编写一个简单的解释器。这个解释器目前只能执行一个Java方法,后面的章节中会不断完善。解释器实现代码位于/instructions/interpreter.go文件中。

要实现解释器,还有些额外的代码需要完善:

  1. 要读取method_info的code属性,我们需要修改下/classfile/member_info.go文件,添加CodeAttribute()方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 读取Code属性
    func (self *MemberInfo) CodeAttribute() *CodeAttribute {
    // 遍历一个method_info中的attributes
    for _, attrInfo := range self.attributes {
    switch attrInfo.(type) {
    case *CodeAttribute:
    // 返回CodeAttribute
    return attrInfo.(*CodeAttribute)
    }
    }
    return nil
    }
  2. 修改Frame结构体,增加两个字段,修改/rtdata/frame.go文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    type Frame struct {
    ...
    // 线程指针
    thread *Thread
    // 下个pc寄存器地址(为了实现跳转)
    nextPC int
    }
    // 实例化栈帧
    func newFrame(thread *Thread, maxLocals, maxStack uint) *Frame {
    return &Frame{
    thread: thread,
    localVars: newLocalVars(maxLocals),
    operandStack: newOperandStack(maxStack),
    }
    }
    // 省略thread、nextPC的Get和Set方法
  3. 修改/rtdata/thread.go,添加NewFrame()方法:

    1
    2
    3
    4
    // 为线程创建新的栈帧
    func (self *Thread) NewFrame(maxLocals, maxStack uint) *Frame {
    return newFrame(self, maxLocals, maxStack)
    }

下面正式编写解释器,添加方法interpret()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 获取method_info的code属性
func Interpret(methodInfo *classfile.MemberInfo) {
// 获取单个方法的Code属性
codeAttr := methodInfo.CodeAttribute();
// 获取局部变量表大小
maxLocals := codeAttr.MaxLocals()
// 获取操作数栈的最大深度
maxStack := codeAttr.MaxStack()
// 获取方法的字节码
bytecode := codeAttr.Code()
// 创建一个Thread实例
thread := rtdata.NewThread()
// 创建栈帧
frame := thread.NewFrame(maxLocals, maxStack);
// 栈帧推入虚拟栈
thread.PushFrame(frame)
//defer catchErr(frame)
//loop(thread, bytecode)
}

上面的方法中有两行注释掉的代码,下面我们来实现这两个方法:

  • 因为每个方法的最后一条指令都是某个return指令,但是还没实现return指令,所以方法在执行过程中必定会出现错误,这里暂时以catchErr()结束。

    1
    2
    3
    4
    5
    6
    7
    func catchErr(frame *rtdata.Frame) {
    if r := recover(); r != nil {
    fmt.Printf("LocalVars:%v\n", frame.LocalVars())
    fmt.Printf("OperandStack:%v\n", frame.OperandStack())
    panic(r)
    }
    }
  • loop()函数循环执行”计算pc、解码指令、执行指令”这三个步骤,直到遇到错误。

    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
    func loop(thread *rtdata.Thread, bytecode []byte) {
    // 获取虚拟机栈栈顶指针
    frame := thread.PopFrame()
    // 实例化BytecodeReader
    reader := &base.BytecodeReader{}
    for {
    // 获取下个pc寄存器地址
    pc := frame.NextPC()
    // 设置线程pc寄存器
    thread.SetPC(pc)
    // 重置
    reader.Reset(bytecode, pc)
    // 获取操作码
    opcode := reader.ReadUint8()
    // 根据操作码常见指令,参照factory.go文件
    inst := NewInstruction(opcode)
    inst.FetchOperands(reader)
    // 设置下一个指令起始地址
    frame.SetNextPC(reader.PC())
    // 执行指令
    fmt.Printf("pc:%2d inst:%T %v\n", pc, inst, inst)
    inst.Execute(frame)
    }
    }

测试

下面我们用一段简单的简单的Java代码来测试本章的指令集和解释器,下面是测试步骤:

  1. 编写测试类Gauss.java,位于java项目中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Guass {
    public static void main(String[] args) {
    int sum = 0;
    for (int i = 1; i <= 100; i++) {
    sum += i;
    }
    System.out.println(sum);
    }
    }
  2. 修改测试入口main.go文件的startJVM()函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    func startJVM(cmd *cmd.Cmd) {
    // 获取Classpath
    cp := classpath.Parse(cmd.XjreOption, cmd.CpOption)
    // 将.替换成/(java.lang.String -> java/lang/String)
    className := strings.Replace(cmd.Class, ".", "/", -1)
    // 加载类
    cf := loadClass(className, cp)
    mainMethod := getMainMethod(cf)
    if mainMethod != nil {
    instructions.Interpret(mainMethod)
    } else {
    fmt.Printf("Main method not found in class %s\n", cmd.Class)
    }
    }
    // 获取main()方法
    func getMainMethod(cf *classfile.ClassFile) *classfile.MemberInfo {
    for _, m := range cf.Methods() {
    if m.Name() == "main" && m.Descriptor() == "([Ljava/lang/String;)V" {
    return m
    }
    }
    return nil
    }
  3. 测试

    1
    2
    3
    4
    5
    6
    7
    cd /home/zhangjing/IdeaProjects/jvmgo/go
    go install cn.didadu/jvmgo
    #指定-classpath
    ./bin/jvmgo -classpath /home/zhangjing/IdeaProjects/jvmgo/java/target/classes cn.didadu.Guass
    #列出部分输出结果,可以看出局部变量表出现5050这个数字,证明解释器正常工作了
    LocalVars:[{0 <nil>} {5050 <nil>} {101 <nil>}]
    OperandStack:&{0 [{101 <nil>} {100 <nil>}]}

本章到此就结束了,现在我们的Java虚拟机可以解释执行单个方法的字节码了。