代码、内容参考来自于张秀宏大佬的自己动手写Java虚拟机 (Java核心技术系列)以及尚硅谷宋红康:JVM全套教程。
这里就剩下ldc系列了,下面完成ldc系列指令
指令ldc系列:
- 常量入栈指令的功能是将常数压入操作数栈,根据数据类型和入栈内容的不同,又可以分为const系列、push系列和ldc指令。
- 如果const系列、push系列指令都不能满足需求,那么可以使用万能的ldc指令,它可以接收一个8位的参数,该参数指向常量池中的int、float或者String的索引,将指定的内容压入堆栈。
- 类似的还有ldc_w.它接收两个8位参数,能支持的索引范围大于ldc,如果要压入的元素是long或者double类型的,则使用ldc2_w指令,使用方式都是类似的。
- ldc系列指令从运行时常量池中加载常量值,并把它推入操作数栈。ldc系列指令属于常量类指令,共3条。
- 其中ldc和ldc_w指令用于加载int、float和字符串常量,java.lang.Class实例或者MethodType和MethodHandle实例。
- ldc2_w指令用于加载long和double常量。ldc和ldc_w指令的区别仅在于操作数的宽度。
我们现在只处理int、float、long和double常量。
在\instructions\constants目录下创建ldc.go文件,在其中定义ldc、ldc_w和ldc_2w指令,代码如下:
package constants
import (
"jvmgo/ch06/instructions/base"
"jvmgo/ch06/rtda"
)
// Push item from run-time constant pool
type LDC struct{ base.Index8Instruction }
func (self *LDC) Execute(frame *rtda.Frame) {
_ldc(frame, self.Index)
}
// Push item from run-time constant pool (wide index)
type LDC_W struct{ base.Index16Instruction }
func (self *LDC_W) Execute(frame *rtda.Frame) {
_ldc(frame, self.Index)
}
func _ldc(frame *rtda.Frame, index uint) {
stack := frame.OperandStack()
cp := frame.Method().Class().ConstantPool()
c := cp.GetConstant(index)
switch c.(type) {
case int32:
stack.PushInt(c.(int32))
case float32:
stack.PushFloat(c.(float32))
// case string:
// case *heap.ClassRef:
// case MethodType, MethodHandle
default:
panic("todo: ldc!")
}
}
// Push long or double from run-time constant pool (wide index)
type LDC2_W struct{ base.Index16Instruction }
func (self *LDC2_W) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
cp := frame.Method().Class().ConstantPool()
c := cp.GetConstant(self.Index)
switch c.(type) {
case int64:
stack.PushLong(c.(int64))
case float64:
stack.PushDouble(c.(float64))
default:
panic("java.lang.ClassFormatError")
}
}ldc和ldc_w指令的逻辑一样,区别仅在于操作数的宽度。在_ldc()函数中实现逻辑,如下:

_ldc()函数会先从当前类的运行时常量池中取出常量。如果是int或float常量,则提取出常量值,则推入操作数栈。其他情况还无法处理,暂时调用panic()函数终止程序执行。

ldc_2w指令的Execute()方法,

为了通过frame变量拿到当前类的运行时常量池,给Frame结构体添加了method字段,同时加入对应Getter方法,newFrame()函数也要加入method的操作,在ch06/rtda/frame.go中,下面直接给出全部代码了。
package rtda
import "jvmgo/ch06/rtda/heap"
// stack frame
type Frame struct {
lower *Frame // stack is implemented as linked list
localVars LocalVars
operandStack *OperandStack
thread *Thread
method *heap.Method
nextPC int // the next instruction after the call
}
func newFrame(thread *Thread, method *heap.Method) *Frame {
return &Frame{
thread: thread,
method: method,
localVars: newLocalVars(method.MaxLocals()),
operandStack: newOperandStack(method.MaxStack()),
}
}
// getters & setters
func (self *Frame) LocalVars() LocalVars {
return self.localVars
}
func (self *Frame) OperandStack() *OperandStack {
return self.operandStack
}
func (self *Frame) Thread() *Thread {
return self.thread
}
func (self *Frame) Method() *heap.Method {
return self.method
}
func (self *Frame) NextPC() int {
return self.nextPC
}
func (self *Frame) SetNextPC(nextPC int) {
self.nextPC = nextPC
}
修改\instructions\factory.go文件,在其中添加这些指令的case语句。
前面factory.go代码已全部给出,将对应这些指令的注释取消即可。
再加多一条import
import . "jvmgo/ch06/instructions/references"
打开main.go文件,修改import语句,代码如下:
package main
import (
"fmt"
"jvmgo/ch06/classpath"
"jvmgo/ch06/rtda/heap"
"strings"
)
func main() {
cmd := parseCmd()
if cmd.versionFlag {
fmt.Println("version 0.0.1")
} else if cmd.helpFlag || cmd.class == "" {
printUsage()
} else {
startJVM(cmd)
}
}
func startJVM(cmd *Cmd) {
cp := classpath.Parse(cmd.XjreOption, cmd.cpOption)
classLoader := heap.NewClassLoader(cp)
className := strings.Replace(cmd.class, ".", "/", -1)
mainClass := classLoader.LoadClass(className)
mainMethod := mainClass.GetMainMethod()
if mainMethod != nil {
interpret(mainMethod)
} else {
fmt.Printf("Main method not found in class %s\n", cmd.class)
}
}main()函数不变,删掉其他函数,然后修改startJVM()函数,先创建ClassLoader实例,然后用它来加载主类,最后执行主类的main()方法。

Class结构体的GetMainMethod()方法,调用了getStaticMethod()方法而已,代码如下:(在ch06\rtda\heap\class.go文件中):
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
}
接下来编辑ch06\interpreter.go文件,修改import语句,修改interpret()函数,代码如下:
func interpret(method *heap.Method) {
thread := rtda.NewThread()
frame := thread.NewFrame(method)
thread.PushFrame(frame)
defer catchErr(frame)
loop(thread, method.Code())
}因为每个方法的最后一条指令都是某个return指令,而还没有实现return指令,所以方法在执行过程中必定会出现错误,此时解释器逻辑会转到catchErr()函数,未免报错而终止程序,将catchErr的代码注释掉。

改变Thread结构体的NewFrame()方法(在ch06\rtda\thread.go文件中):
func (self *Thread) NewFrame(method *heap.Method) *Frame {
return newFrame(self, method)
}
但是,还需要添加两个hack。因为对象是需要初始化的,所以每个类都至少有一个构造函数。在创建类实例时,编译器会在new指令的后面加入invokespecial指令来调用构造函数初始化对象。
我们将在后面实现invokespecial指令,但为了测试putfield和getfield等指令,这里先给它一个空的实现。
在\instructions\references目录下创建invokespecial.go文件,把下面的代码复制进去:
package references
import (
"jvmgo/ch06/instructions/base"
"jvmgo/ch06/rtda"
)
// Invoke instance method;
// special handling for superclass, private, and instance initialization method invocations
type INVOKE_SPECIAL struct{ base.Index16Instruction }
// hack!
func (self *INVOKE_SPECIAL) Execute(frame *rtda.Frame) {
frame.OperandStack().PopRef()
}另外一个hack来解决这个问题。在instructions\references目录下创建invokevirtual.go文件,把下面的代码复制进去:
package references
import (
"fmt"
"jvmgo/ch06/instructions/base"
"jvmgo/ch06/rtda"
"jvmgo/ch06/rtda/heap"
)
// Invoke instance method; dispatch based on class
type INVOKE_VIRTUAL struct{ base.Index16Instruction }
// hack!
func (self *INVOKE_VIRTUAL) Execute(frame *rtda.Frame) {
cp := frame.Method().Class().ConstantPool()
methodRef := cp.GetConstant(self.Index).(*heap.MethodRef)
if 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()
}
}代码会在下一章解释,同时在factory.go将invokespecial和invokevirtual指令的注释取消。

Java例子,代码如下:
package jvmgo.book.ch06;
public class MyObject {
public static int staticVar;
public int instanceVar;
public static void main(String[] args) {
int x = 32768; // ldc
MyObject myObj = new MyObject(); // new
MyObject.staticVar = x; // putstatic
x = MyObject.staticVar; // getstatic
myObj.instanceVar = x; // putfield
x = myObj.instanceVar; // getfield
Object obj = myObj;
if (obj instanceof MyObject) { // instanceof
myObj = (MyObject) obj; // checkcast
System.out.println(myObj.instanceVar);
}
}
}编译后,将class文件放到你想放到的目录

打开命令行窗口,执行下面的命令编译本章代码:
go install jvmgo\ch06
ch06 -classpath D:\MAT_log -Xjre "D:\software\java\jre" MyObject
结果如图:

参考:
尚硅谷宋红康:JVM全套教程:https://www.bilibili.com/video/BV1PJ411n7xZ
周志明:深入理解java虚拟机
张秀宏:自己动手写Java虚拟机 (Java核心技术系列)


