类和对象相关指令
本节继续来学习类和对象剩下的ldc指令,和测试。
ldc指令
ldc系列指令从运行时常量池中加载常量值,并把它推入操作数栈。ldc系列指令属于常量类指令,共3条。其中ldc和ldc_w指令用于加载int、float和字符串常量,java.lang.Class实例或者MethodType和MethodHandle实例。ldc2_w指令用于加载long和double常量。ldc和ldc_w指令的区别仅在于操作数的宽度。本节先处理int、float、long和double常量,部分其余的后面再学。
ldc系列指令位于/instructions/constants/ldc.go文件中:
|
|
测试
下面我们来测试下本章实现的类和对象相关代码,列出测试步骤:
修改/rtdata/heap/class.go文件,添加获取main方法的函数:
1234567891011121314151617// 获取main()方法func (self *Class) GetMainMethod() *Method {return self.getStaticMethod("main", "([Ljava/lang/String;)V")}// 通过方法名和描述符获取静态方法func (self *Class) getStaticMethod(name, descriptor string) *Method {// 遍历运行时常量池中的方法信息for _, method := range self.methods {if method.IsStatic() &&method.name == name &&method.descriptor == descriptor {return method}}return nil}修改/instructions/interpreter.go文件的Interpret()函数,并且注释catchErr()函数中的部分代码:
1234567891011121314151617181920// 传入入口方法,初始化线程func Interpret(method *heap.Method) {// 创建一个Thread实例thread := rtdata.NewThread()// 创建栈帧frame := thread.NewFrame(method)// 栈帧推入虚拟栈thread.PushFrame(frame)defer catchErr(frame)loop(thread, method.Code())}func catchErr(frame *rtda.Frame) {if r := recover(); r != nil {//fmt.Printf("LocalVars:%v\n", frame.LocalVars())//fmt.Printf("OperandStack:%v\n", frame.OperandStack())//panic(r)}}在测试之前还需要添加两个hack:
因为对象是需要初始化的,所以每个类至少有一个构造函数。即使用户自己不定义,编译器也会自动生成一个默认构造函数。在创建类实例时,编译器会在new指令的后面加入invokespecial指令来调用构造函数初始化对象。后面才会实现invokespecial指令,为了测试putfield和getfield等指令,这里先给一个空的实现。新建/instructions/references/invokespecial.go文件:
123456type INVOKE_SPECIAL struct{ base.Index16Instruction }// hack!func (self *INVOKE_SPECIAL) Execute(frame *rtdata.Frame) {frame.OperandStack().PopRef()}指令集和解释器章节通过打印局部变量表和操作数的方式观察计算结果,这样很不方便。这里用另外一个hack来解决这个问题,新建/instructions/references/invokevirtual.go文件:
123456789101112131415161718192021222324252627282930313233// Invoke instance method; dispatch based on classtype INVOKE_VIRTUAL struct{ base.Index16Instruction }// hack!func (self *INVOKE_VIRTUAL) Execute(frame *rtdata.Frame) {// 获取运行时常量池cp := frame.Method().Class().ConstantPool()// 获取方法符号引用methodRef := cp.GetConstant(self.Index).(*heap.MethodRef)// 当方法为printlnif methodRef.Name() == "println" {// 获取操作数栈stack := frame.OperandStack()// 通过描述符从操作数栈中弹出对应的值switch methodRef.Descriptor() {case "(Z)V":fmt.Printf("%v\n", stack.PopInt() != 0)case "(C)V":fmt.Printf("%c\n", stack.PopInt())case "(I)V", "(B)V", "(S)V":fmt.Printf("%v\n", stack.PopInt())case "(F)V":fmt.Printf("%v\n", stack.PopFloat())case "(J)V":fmt.Printf("%v\n", stack.PopLong())case "(D)V":fmt.Printf("%v\n", stack.PopDouble())default:panic("println: " + methodRef.Descriptor())}stack.PopRef()}}
修改/instructions/factory.go文件,添加本章实现的指令,只要把注释放开就好,代码就不贴出来了。
修改测试入口main.go文件的startJVM()函数:
12345678910111213141516171819func startJVM(cmd *cmd.Cmd) { if mainClass != nil {// 获取Classpathcp := classpath.Parse(cmd.XjreOption, cmd.CpOption)// 创ClassLoader实例classloader := heap.NewClassLoader(cp)// class权限定名,将.替换成/(java.lang.String -> java/lang/String)className := strings.Replace(cmd.Class, ".", "/", -1)// 加载主类mainClass := classloader.LoadClass(className);// 获取主类的main()方法mainMethod := mainClass.GetMainMethod()if mainClass != nil {instructions.Interpret(mainMethod)} else {fmt.Printf("Main method not found in class %s\n", cmd.Class)}}测试
1234567891011cd /home/zhangjing/IdeaProjects/jvmgo/gogo install cn.didadu/jvmgo#指定-classpath./bin/jvmgo -classpath /home/zhangjing/IdeaProjects/jvmgo/java/target/classes cn.didadu.MyObject// 部分打印信息如下[Loaded java/lang/Object from /usr/lib/jvm/jdk1.8.0_91/jre/lib/rt.jar][Loaded cn/didadu/MyObject from /home/zhangjing/IdeaProjects/jvmgo/java/target/classes]pc: 0 inst:*constants.LDC &{{2}}pc: 2 inst:*stores.ISTORE_1 &{{}}...32768至此,本章的代码已实现完毕,对类的加载过程有了进一步的了解。