手写JVM(二十三)-数组指令(一)

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

newarray、anewarray、multianewarray和arraylength指令属于引用类指令;aload和astore系列指令各有8条,分别属于加载类和存储类指令。

1.数组指令概述

数组操作指令

数组操作指令主要有: xastore和xaload指令。具体为:

  • 把一个数组元素加载到操作数栈的指令: baload、 caload、 saload、 iaload、 laload、faload、daload, aaload
  • 将一个操作数栈的值存储到数组元素中的指令(修改堆中):bastore、 castore、 sastore、 iastore、lastore、fastore, dastore, aastore

即(如下图:byte和boolean用同一个指令):

  • 取数组长度的指令: arraylength,该指令弹出栈顶的数组元素(索引),获取数组的长度,将长度压入栈。

创建数组的指令

创建数组的指令:newarray、anewarray、multianewarray

  • newarray:创建基本类型数组
  • anewarray:创建引用类型数组
  • multianewarray:创建多维数组

上述创建指令可以用于创建对象或者数组,由于对象和数组在Java中的广泛使用,这些指令的使用频率也非常高。

2. 说明

  • 指令xaload表示将数组的元素压栈,比如saload、caload分别表示压入short数组和char数组。指令xaload在执行时,要求操作数中栈顶元素为数组索引,栈顶顺位第2个元素为数组引用a,该指令会弹出栈顶这两个元素,并将a[i]重新压入堆栈。
  • xastore则专门针对数组操作,以iastore为例,它用于给一个int数组的给定索引赋值。在iastore执行前,操作数栈顶需要以此准备3个元素:值、索引、数组引用,iastore会弹出这3个值,并将值赋给数组中指定索引的位置。

 

2.newarray指令

newarray指令用来创建基本类型数组,包括boolean[]、byte[]、char[]、short[]、int[]、long[]、float[]和double[]8种。

整体代码

在/rtda/heap/class.go文件,添加一个Getter方法,获取当前class的ClassLoader

func (self *Class) Loader() *ClassLoader {
    return self.loader
}

 

在\instructions\references目录下创建newarray.go,在其中定义newarray指令,代码如下:

package references

import (
    "jvmgo/ch08/instructions/base"
    "jvmgo/ch08/rtda"
    "jvmgo/ch08/rtda/heap"
)

const (
    //Array Type atype
    AT_BOOLEAN = 4
    AT_CHAR = 5
    AT_FLOAT = 6
    AT_DOUBLE = 7
    AT_BYTE = 8
    AT_SHORT = 9
    AT_INT = 10
    AT_LONG = 11
)

// Create new array
type NEW_ARRAY struct {
    atype uint8
}

func (self *NEW_ARRAY) FetchOperands(reader *base.BytecodeReader) {
    self.atype = reader.ReadUint8()
}
func (self *NEW_ARRAY) Execute(frame *rtda.Frame) {
    // 获取当前帧的操作数栈
    stack := frame.OperandStack()
    // 弹出栈顶元素,作为数组的长度
    count := stack.PopInt()
    if count < 0 {
        panic("java.lang.NegativeArraySizeException")
    }
    // 获取当前类的类加载器
    classLoader := frame.Method().Class().Loader()
    // 加载数组类
    arrClass := getPrimitiveArrayClass(classLoader, self.atype)
    // 创建数组
    arr := arrClass.NewArray(uint(count))
    // 将创建的数组指针推入操作数栈
    stack.PushRef(arr)
}

// 根据atype加载对应的Class
func getPrimitiveArrayClass(loader *heap.ClassLoader, atype uint8) *heap.Class {
    switch atype {
    case AT_BOOLEAN:
        return loader.LoadClass("[Z")
    case AT_BYTE:
        return loader.LoadClass("[B")
    case AT_CHAR:
        return loader.LoadClass("[C")
    case AT_SHORT:
        return loader.LoadClass("[S")
    case AT_INT:
        return loader.LoadClass("[I")
    case AT_LONG:
        return loader.LoadClass("[J")
    case AT_FLOAT:
        return loader.LoadClass("[F")
    case AT_DOUBLE:
        return loader.LoadClass("[D")
    default:
        panic("Invalid atype!")
    }
}

 

代码解释

newarray指令需要两个操作数。第一个操作数是一个uint8整数,在字节码中紧跟在指令操作码后面,表示要创建哪种类型的数组。Java虚拟机规范把这个操作数叫作atype,并且规定了它的有效值。把这些值定义为常量,代码如下:

FetchOperands()方法读取atype的值,代码如下:

 

newarray指令的第二个操作数是count,从操作数栈中弹出,表示数组长度。Execute()方法根据atype和count创建基本类型数组,如果count小于0,则抛出NegativeArraySizeException异常,否则
根据atype值使用当前类的类加载器加载数组类,然后创建数组对象并推入操作数栈。

 

getPrimitiveArrayClass()函数根据atype加载对应的Class:

 

3.anewarray指令

anewarray指令用来创建引用类型数组。

代码及解释

anewarray指令也需要两个操作数。第一个操作数是uint16索引,来自字节码。通过这个索引可以从当前类的运行时常量池中找到一个类符号引用,解析这个符号引用就可以得到数组元素的类。第二个操作数是数组长度,从操作数栈中弹出。

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

package references

import (
    "jvmgo/ch08/instructions/base"
    "jvmgo/ch08/rtda"
    "jvmgo/ch08/rtda/heap"
)

// Create new array of reference
type ANEW_ARRAY struct{ base.Index16Instruction }

//根据数组元素的类型和数组长度创建引用类型数组
func (self *ANEW_ARRAY) Execute(frame *rtda.Frame) {
    cp := frame.Method().Class().ConstantPool()
    classRef := cp.GetConstant(self.Index).(*heap.ClassRef)
    componentClass := classRef.ResolvedClass()
    stack := frame.OperandStack()
    count := stack.PopInt()
    if count < 0 {
        panic("java.lang.NegativeArraySizeException")
    }

    arrClass := componentClass.ArrayClass()
    arr := arrClass.NewArray(uint(count))
    stack.PushRef(arr)
}

上面的代码基本一样,就不解释了。

修改/rtda/heap/class.go文件,添加加载数组类的方法:

func (self *Class) ArrayClass() *Class {
    arrayClassName := getArrayClassName(self.name)
    return self.loader.LoadClass(arrayClassName)
}

在\rtda\heap目录下创建class_name_helper.go文件,在其中实现getArrayClassName()函数,代码如下:

package heap

var primitiveTypes = map[string]string{
    "void": "V",
    "boolean": "Z",
    "byte": "B",
    "short": "S",
    "int": "I",
    "long": "J",
    "char": "C",
    "float": "F",
    "double": "D",
}

// [XXX -> [[XXX
// int -> [I
// XXX -> [LXXX;
func getArrayClassName(className string) string {
    //把类名转变成类型描述符,然后在前面加上方括号即可
    return "[" + toDescriptor(className)
}

// [[XXX -> [XXX
// [LXXX; -> XXX
// [I -> int
func getComponentClassName(className string) string {
    if className[0] == '[' {
        componentTypeDescriptor := className[1:]
        return toClassName(componentTypeDescriptor)
    }
    panic("Not array: " + className)
}

// [XXX => [XXX
// int => I
// XXX => LXXX;
func toDescriptor(className string) string {
    //如果是数组类名,描述符就是类名,直接返回即可
    if className[0] == '[' {
        // array
        return className
    }
    //如果是基本类型名,返回对应的类型描述符
    if d, ok := primitiveTypes[className]; ok {
        // primitive
        return d
    }
    // object
    //普通的类名,前面加上方括号,结尾加上分号即可得到类型描述符
    return "L" + className + ";"
}

对上面的代码进行解释:

Class结构体的ArrayClass()方法返回与类对应的数组类,先根据类名得到数组类名,然后调用类加载器加载数组类即可。

getArrayClassName()函数,把类名转变成类型描述符,然后在前面加上方括号即可。:

 

如果是数组类名,描述符就是类名,直接返回即可。如果是基本类型名,返回对应的类型描述符,否则肯定是普通的类名,前面加上方括号,结尾加上分号即可得到类型描述符。

 

primitiveTypes变量定义基本类型和对应类型描述符map

 

4.arraylength指令

arraylength指令用于获取数组长度。arraylength指令只需要一个操作数,即从操作数栈顶弹出的数组引用。

在instructions\references目录下创建arraylength.go,在其中定义arraylength指令,如果数组引用是null,则需要抛出NullPointerException异常,否则取数组长度,推入操作数栈顶即可。代码如下:

package references

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

// Get length of array
type ARRAY_LENGTH struct{ base.NoOperandsInstruction }

func (self *ARRAY_LENGTH) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    arrRef := stack.PopRef()
    if arrRef == nil {
        panic("java.lang.NullPointerException")
    }
    // 获取数组长度
    arrLen := arrRef.ArrayLength()
    // 讲数组长度推入操作数栈顶
    stack.PushInt(arrLen)
}

 

5.aload指令

aload系列指令按索引取数组元素值,然后推入操作数栈。

  • 把一个数组元素加载到操作数栈的指令: baload、 caload、 saload、 iaload、 laload、faload、daload, aaload

在\instructions\loads目录下创建xaload.go文件,在其中定义8条指令,代码如下:

package loads

import (
    "jvmgo/ch08/instructions/base"
    "jvmgo/ch08/rtda"
    "jvmgo/ch08/rtda/heap"
)

// Load reference from array
type AALOAD struct{ base.NoOperandsInstruction }

func (self *AALOAD) Execute(frame *rtda.Frame) {
    // 获取操作数栈
    stack := frame.OperandStack()
    //先从操作数栈中弹出第一个操作数:数组索引
    index := stack.PopInt()
    //然后弹出第二个操作数:数组引用
    arrRef := stack.PopRef()
    // 判断数组是否为空
    checkNotNil(arrRef)
    // 获取类型为引用的数组
    refs := arrRef.Refs()
    // 判断索引是否合法
    checkIndex(len(refs), index)
    // 将通过索引获取的值入栈
    stack.PushRef(refs[index])
}

// Load byte or boolean from array
type BALOAD struct{ base.NoOperandsInstruction }

func (self *BALOAD) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    index := stack.PopInt()
    arrRef := stack.PopRef()

    checkNotNil(arrRef)
    bytes := arrRef.Bytes()
    checkIndex(len(bytes), index)
    stack.PushInt(int32(bytes[index]))
}

// Load char from array
type CALOAD struct{ base.NoOperandsInstruction }

func (self *CALOAD) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    index := stack.PopInt()
    arrRef := stack.PopRef()

    checkNotNil(arrRef)
    chars := arrRef.Chars()
    checkIndex(len(chars), index)
    stack.PushInt(int32(chars[index]))
}

// Load double from array
type DALOAD struct{ base.NoOperandsInstruction }

func (self *DALOAD) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    index := stack.PopInt()
    arrRef := stack.PopRef()

    checkNotNil(arrRef)
    doubles := arrRef.Doubles()
    checkIndex(len(doubles), index)
    stack.PushDouble(doubles[index])
}

// Load float from array
type FALOAD struct{ base.NoOperandsInstruction }

func (self *FALOAD) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    index := stack.PopInt()
    arrRef := stack.PopRef()

    checkNotNil(arrRef)
    floats := arrRef.Floats()
    checkIndex(len(floats), index)
    stack.PushFloat(floats[index])
}

// Load int from array
type IALOAD struct{ base.NoOperandsInstruction }

func (self *IALOAD) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    index := stack.PopInt()
    arrRef := stack.PopRef()

    checkNotNil(arrRef)
    ints := arrRef.Ints()
    checkIndex(len(ints), index)
    stack.PushInt(ints[index])
}

// Load long from array
type LALOAD struct{ base.NoOperandsInstruction }

func (self *LALOAD) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    index := stack.PopInt()
    arrRef := stack.PopRef()

    checkNotNil(arrRef)
    longs := arrRef.Longs()
    checkIndex(len(longs), index)
    stack.PushLong(longs[index])
}

// Load short from array
type SALOAD struct{ base.NoOperandsInstruction }

func (self *SALOAD) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    index := stack.PopInt()
    arrRef := stack.PopRef()

    checkNotNil(arrRef)
    shorts := arrRef.Shorts()
    checkIndex(len(shorts), index)
    stack.PushInt(int32(shorts[index]))
}

// 判断数组引用是否为空
func checkNotNil(ref *heap.Object) {
    if ref == nil {
        panic("java.lang.NullPointerException")
    }
}

// 判断索引范围是否在数组长度内
func checkIndex(arrLen int, index int32) {
    if index < 0 || index >= int32(arrLen) {
        panic("ArrayIndexOutOfBoundsException")
    }
}

这8条指令的实现基本一样,以aaload指令为例进行说明。首先从操作数栈中弹出第一个操作数:数组索引,然后弹出第二个操作数:数组引用。如果数组引用是null,则抛出NullPointerException异常。如果数组索引小于0,或者大于等于数组长度,则抛出ArrayIndexOutOfBoundsException。如果一切正常,则按索引取出数组元素,推入操作数栈顶。

 

checkNotNil()函数判断数组引用是否为空,checkIndex()函数判断索引范围是否在数组长度内

 

6.参考

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