手写JVM(十八)-类和对象相关指令(二)

代码、内容参考来自于张秀宏大佬的自己动手写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核心技术系列)

GO语言官网:Standard library – Go Packages

Java虚拟机规范:Chapter 4. The class File Format (oracle.com)

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇