手写JVM(十一)-指令集(五)

代码、内容参考来自于张秀宏大佬的自己动手写Java虚拟机 (Java核心技术系列)以及尚硅谷宋红康:JVM全套教程。

1.控制指令

控制指令共有11条。jsr和ret指令在Java 6之前用于实现finally子句,从Java 6开始,Oracle的Java编译器已经不再使用这两条指令了,不讨论这两条指令。

这里实现3条指令:goto、tableswitch和lookupswitch。

goto指令

整体代码

在instructions\control目录下创建goto.go文件,在其中定义goto指令,代码如下:

package control

import (
    "jvmgo/ch05/instructions/base"
    "jvmgo/ch05/rtda"
)


// Branch always

type GOTO struct{ base.BranchInstruction }

func (self *GOTO) Execute(frame *rtda.Frame) {
    base.Branch(frame, self.Offset)
}

 

代码解释

goto指令进行无条件跳转,其Execute()方法如下:

 

tableswitch指令

多条件分支跳转指令

多条件分支跳转指令是专为switch-case语句设计的,主要有tableswitch和lookupswitch。

从助记符上看,两者都是switch语句的实现,它们的区别:

  • tableswitch要求多个条件分支值是连续的,它内部只存放起始值和终止值,以及若干个跳转偏移量,通过给定的操作数index,可以立即定位到跳转偏移量位置,因此效率比较高。
  • 指令lookupswitch内部存放着各个离散的case-offset对,每次执行都要搜索全部的case-offset对,找到匹配的case值,并根据对应的offset计算跳转地址,因此效率较低。

指令tableswitch的示意图如下图所示。由于tableswitch的case值是连续的,因此只需要记录最低值和最高值,以及每一项对应的offset偏移量,根据给定的index值通过简单的计算即可直接定位到offset。

指令lookupswitch处理的是离散的case值,但是出于效率考虑,将case-offset对按照case值大小排序,给定index时,需要查找与index相等的case,获得其offset,如果找不到则跳转到default。指令lookupswitch如下图所示。

 

tableswitch举例:

//3.多条件分支跳转
public void swtich1(int select){
    int num;
    switch(select){
        case 1:
            num = 10;
            break;
        case 2:
            num = 20;
            break;
        case 3:
            num = 30;
            break;
        default:
            num = 40;
    }

}

缺少一个break情况:

//3.多条件分支跳转
public void swtich1(int select){
    int num;
    switch(select){
        case 1:
            num = 10;
            break;
        case 2:
            num = 20;
            //break;
        case 3:
            num = 30;
            break;
        default:
            num = 40;
    }

}
lookupswitch举例:
public void swtich2(int select){
    int num;
    switch(select){
        case 100:
            num = 10;
            break;
        case 500:
            num = 20;
            break;
        case 200:
            num = 30;
            break;
        default:
            num = 40;
    }
}

 

调用hashCode方法,将哈希值与下面字符串的哈希值比较,哈希值也大小排序,升序

//jdk7新特性:引入String类型
public void swtich3(String season){
    switch(season){
        case "SPRING":break;
        case "SUMMER":break;
        case "AUTUMN":break;
        case "WINTER":break;
    }
}

 

Java语言中的switch-case语句有两种实现方式:如果case值可以编码成一个索引表,则实现成tableswitch指令;否则实现成lookupswitch指令。

整体代码

在instructions\control目录下创建tableswitch.go文件,在其中定义tableswitch指令,代码如下:

package control

import (
    "jvmgo/ch05/instructions/base"
    "jvmgo/ch05/rtda"
)

/*
tableswitch
<0-3 byte pad>
defaultbyte1
defaultbyte2
defaultbyte3
defaultbyte4
lowbyte1
lowbyte2
lowbyte3
lowbyte4
highbyte1
highbyte2
highbyte3
highbyte4
jump offsets...
*/

// Access jump table by index and jump

type TABLE_SWITCH struct {
    defaultOffset int32
    low           int32
    high          int32
    jumpOffsets   []int32
}

func (self *TABLE_SWITCH) FetchOperands(reader *base.BytecodeReader) {
    reader.SkipPadding()
    self.defaultOffset = reader.ReadInt32()
    self.low = reader.ReadInt32()
    self.high = reader.ReadInt32()
    jumpOffsetsCount := self.high - self.low + 1
    self.jumpOffsets = reader.ReadInt32s(jumpOffsetsCount)
}

func (self *TABLE_SWITCH) Execute(frame *rtda.Frame) {
    index := frame.OperandStack().PopInt()

    var offset int
    if index >= self.low && index <= self.high {
        offset = int(self.jumpOffsets[index-self.low])
    } else {
        offset = int(self.defaultOffset)
    }

    base.Branch(frame, offset)
}

tableswitch指令的操作数比较复杂,它的FetchOperands()方法如下:

在class文件,对应位置,第一个字节0xAA表示助记符tableswitch指令,后面两个字节0x00、0x00没有实质意义,tableswitch指令操作码的后面有0~3字节的padding,以保证defaultOffset在字节码中的地址是4的倍数。BytecodeReader结构体的SkipPadding()方法如下,我们之前就加入的方法派上用场:

// used by lookupswitch and tableswitch

func (self *BytecodeReader) SkipPadding() {
    for self.pc%4 != 0 {
        self.ReadUint8()
    }
}

 

defaultOffset对应默认情况下执行跳转所需的字节码偏移量;low和high记录case的取值范围;jumpOffsets是一个索引表,里面存放high-low+1个int值,对应各种case情况下,执行跳转所需的字节码偏移量。BytecodeReader结构体的ReadInt32s()方法如下,我们之前就加入的方法派上用场:

// used by lookupswitch and tableswitch

func (self *BytecodeReader) ReadInt32s(n int32) []int32 {
    ints := make([]int32, n)
    for i := range ints {
        ints[i] = self.ReadInt32()
    }
    return ints
}

 

Execute()方法先从操作数栈中弹出一个int变量,然后看它是否在low和high给定的范围之内。如果在,则从jumpOffsets表中查出偏移量进行跳转,否则按照defaultOffset跳转。代码如下:

 

lookupswitch指令

整体代码

在instructions\control目录下创建lookupswitch.go文件,在其中定义lookupswitch指令,代码如下:

package control

import (
    "jvmgo/ch05/instructions/base"
    "jvmgo/ch05/rtda"
)


/*
lookupswitch
<0-3 byte pad>
defaultbyte1
defaultbyte2
defaultbyte3
defaultbyte4
npairs1
npairs2
npairs3
npairs4
match-offset pairs...
*/
// Access jump table by key match and jump

type LOOKUP_SWITCH struct {
    defaultOffset int32
    npairs int32
    matchOffsets  []int32
}

func (self *LOOKUP_SWITCH) FetchOperands(reader *base.BytecodeReader) {
    reader.SkipPadding()
    self.defaultOffset = reader.ReadInt32()
    self.npairs = reader.ReadInt32()
    self.matchOffsets = reader.ReadInt32s(self.npairs * 2)
}

func (self *LOOKUP_SWITCH) Execute(frame *rtda.Frame) {
    key := frame.OperandStack().PopInt()
    for i := int32(0); i < self.npairs*2; i += 2 {
        if self.matchOffsets[i] == key {
            offset := self.matchOffsets[i+1]
            base.Branch(frame, int(offset))
            return
        }
    }
    base.Branch(frame, int(self.defaultOffset))
}

 

代码解释

FetchOperands()方法也要先跳过padding,代码如下:

 

读取各个case跳转指令偏移量,matchOffsets中每个case存储8个字节,前4个字节是case的值,后4个字节是跳转偏移量,matchOffsets有点像Map,它的key是case值,value是跳转偏移量。Execute()方法先从操作数栈中弹出一个int变量,然后用它查找matchOffsets,看是否能找到匹配的key。如果能,则按照value给出的偏移量跳转,否则按照defaultOffset跳转。代码如下:

 

2.扩展指令

multianewarray指令用于创建多维数组,在第8章讨论数组时实现该指令。本节实现剩下的4条指令。

wide指令

加载类指令、存储类指令、ret指令和iinc指令需要按索引访问局部变量表,索引以uint8的形式存在字节码中。对于大部分方法来说,局部变量表大小都不会超过256,所以用一字节来表示索引就够了。如果有方法的局部变量表超过256,Java虚拟机规范定义了wide指令来扩展前述指令。

整体代码

在instructions\extended目录下创建wide.go文件,在其中定义wide指令,代码如下:

package extended

import (
    "jvmgo/ch05/instructions/base"
    "jvmgo/ch05/instructions/loads"
    "jvmgo/ch05/instructions/math"
    "jvmgo/ch05/instructions/stores"
    "jvmgo/ch05/rtda"
)


// Extend local variable index by additional bytes

type WIDE struct {
    modifiedInstruction base.Instruction
}

func (self *WIDE) FetchOperands(reader *base.BytecodeReader) {
    opcode := reader.ReadUint8()
    switch opcode {
    case 0x15:
        inst := &loads.ILOAD{}
        inst.Index = uint(reader.ReadUint16())
        self.modifiedInstruction = inst
    case 0x16:
        inst := &loads.LLOAD{}
        inst.Index = uint(reader.ReadUint16())
        self.modifiedInstruction = inst
    case 0x17:
        inst := &loads.FLOAD{}
        inst.Index = uint(reader.ReadUint16())
        self.modifiedInstruction = inst
    case 0x18:
        inst := &loads.DLOAD{}
        inst.Index = uint(reader.ReadUint16())
        self.modifiedInstruction = inst
    case 0x19:
        inst := &loads.ALOAD{}
        inst.Index = uint(reader.ReadUint16())
        self.modifiedInstruction = inst
    case 0x36:
        inst := &stores.ISTORE{}
        inst.Index = uint(reader.ReadUint16())
        self.modifiedInstruction = inst
    case 0x37:
        inst := &stores.LSTORE{}
        inst.Index = uint(reader.ReadUint16())
        self.modifiedInstruction = inst
    case 0x38:
        inst := &stores.FSTORE{}
        inst.Index = uint(reader.ReadUint16())
        self.modifiedInstruction = inst
    case 0x39:
        inst := &stores.DSTORE{}
        inst.Index = uint(reader.ReadUint16())
        self.modifiedInstruction = inst
    case 0x3a:
        inst := &stores.ASTORE{}
        inst.Index = uint(reader.ReadUint16())
        self.modifiedInstruction = inst
    case 0x84:
        inst := &math.IINC{}
        inst.Index = uint(reader.ReadUint16())
        inst.Const = int32(reader.ReadInt16())
        self.modifiedInstruction = inst
    case 0xa9: // ret
        panic("Unsupported opcode: 0xa9!")
    }
}

func (self *WIDE) Execute(frame *rtda.Frame) {
    self.modifiedInstruction.Execute(frame)
}

 

代码解释

wide指令改变其他指令的行为,modifiedInstruction字段存放被改变的指令。wide指令需要自己解码出modifiedInstruction,FetchOperands()方法先从字节码中读取一字节的操作码,然后创建子指令实例,最后读取子指令的操作数。因为没有实现ret指令,所以暂时调用panic()函数终止程序执行。加载指令和存储指令都只有一个操作数,需要扩展成2字节,以iload为例:

 

iinc指令有两个操作数,都需要扩展成2字节,代码如下:

wide指令只是增加了索引宽度,并不改变子指令操作,所以其Execute()方法只要调用子指令的Execute方法即可:

 

ifnull和ifnonnull指令

整体代码

在instructions\extended目录下创建ifnull.go文件,在其中定义ifnull和ifnonnull指令,代码如下:

package extended

import (
    "jvmgo/ch05/instructions/base"
    "jvmgo/ch05/rtda"
)


// Branch if reference is null

type IFNULL struct{ base.BranchInstruction }

func (self *IFNULL) Execute(frame *rtda.Frame) {
    ref := frame.OperandStack().PopRef()
    if ref == nil {
        base.Branch(frame, self.Offset)
    }
}

// Branch if reference not null

type IFNONNULL struct{ base.BranchInstruction }

func (self *IFNONNULL) Execute(frame *rtda.Frame) {
    ref := frame.OperandStack().PopRef()
    if ref != nil {
        base.Branch(frame, self.Offset)
    }
}

 

代码解释

根据引用是否是null进行跳转,ifnull和ifnonnull指令把栈顶的引用弹出。以ifnull指令为例,它的Execute方法如下:

 

goto_w指令

整体代码

在instructions\extended目录下创建goto_w.go文件,在其中定义goto_w指令,代码如下:

package extended

import (
    "jvmgo/ch05/instructions/base"
    "jvmgo/ch05/rtda"
)


// Branch always (wide index)

type GOTO_W struct {
    offset int
}

func (self *GOTO_W) FetchOperands(reader *base.BytecodeReader) {
    self.offset = int(reader.ReadInt32())
}
func (self *GOTO_W) Execute(frame *rtda.Frame) {
    base.Branch(frame, self.offset)
}

 

代码解释

goto_w指令和goto指令的唯一区别就是索引从2字节变成了4字节。FetchOperands()方法代码如下:

 

3.参考

尚硅谷宋红康: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
小恐龙
花!
上一篇
下一篇