解释器
指令集已经实现得差不多了,本节将编写一个简单的解释器。这个解释器目前只能执行一个Java方法,后面的章节中会不断完善。解释器实现代码位于/instructions/interpreter.go文件中。
要实现解释器,还有些额外的代码需要完善:
要读取method_info的code属性,我们需要修改下/classfile/member_info.go文件,添加CodeAttribute()方法:
123456789101112// 读取Code属性func (self *MemberInfo) CodeAttribute() *CodeAttribute {// 遍历一个method_info中的attributesfor _, attrInfo := range self.attributes {switch attrInfo.(type) {case *CodeAttribute:// 返回CodeAttributereturn attrInfo.(*CodeAttribute)}}return nil}修改Frame结构体,增加两个字段,修改/rtdata/frame.go文件:
123456789101112131415161718type 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方法修改/rtdata/thread.go,添加NewFrame()方法:
1234// 为线程创建新的栈帧func (self *Thread) NewFrame(maxLocals, maxStack uint) *Frame {return newFrame(self, maxLocals, maxStack)}
下面正式编写解释器,添加方法interpret()方法:
上面的方法中有两行注释掉的代码,下面我们来实现这两个方法:
因为每个方法的最后一条指令都是某个return指令,但是还没实现return指令,所以方法在执行过程中必定会出现错误,这里暂时以catchErr()结束。
1234567func 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、解码指令、执行指令”这三个步骤,直到遇到错误。
12345678910111213141516171819202122232425func loop(thread *rtdata.Thread, bytecode []byte) {// 获取虚拟机栈栈顶指针frame := thread.PopFrame()// 实例化BytecodeReaderreader := &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代码来测试本章的指令集和解释器,下面是测试步骤:
编写测试类Gauss.java,位于java项目中:
123456789public class Guass {public static void main(String[] args) {int sum = 0;for (int i = 1; i <= 100; i++) {sum += i;}System.out.println(sum);}}修改测试入口main.go文件的startJVM()函数:
123456789101112131415161718192021222324func startJVM(cmd *cmd.Cmd) {// 获取Classpathcp := 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}测试
1234567cd /home/zhangjing/IdeaProjects/jvmgo/gogo 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虚拟机可以解释执行单个方法的字节码了。