bboyjing's blog

自己动手写JVM二十七【数组和字符串(二)】

本节继续来学习数组相关指令。

数组相关指令

<t>aload指令

<t>aload系列指令按索引取元素的值,然后推入操作数栈,其实现代码位于/instructions/loads/xaload.go文件中,其中一共有8条指令,代码基本相似,下面以aaload指令来详细看下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
type AALOAD struct{ base.NoOperandsInstruction }
func (self *AALOAD) Execute(frame *rtdata.Frame) {
// 获取操作数栈
stack := frame.OperandStack()
// 从操作数栈中弹出第一个操作数,作为数组索引
index := stack.PopInt()
// 从操作数栈中弹出第二个操作数,作为数组引用
arrRef := stack.PopRef()
// 判断数组是否为空
checkNotNil(arrRef)
// 获取类型为引用的数组
refs := arrRef.Refs()
// 判断索引是否合法
checkIndex(len(refs), index)
// 将通过索引获取的值入栈
stack.PushRef(refs[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")
}
}
// 省略其它7中指令
...

<t>astore指令

<t>astore系列指令按索引给数组元素赋值,其实现代码位于/instructions/stores/xastore.go文件中,其中一共有8条指令,代码基本相似,下面以aastore指令来详细看下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Store into reference array
type AASTORE struct{ base.NoOperandsInstruction }
func (self *AASTORE) Execute(frame *rtdata.Frame) {
// 获取操作数栈
stack := frame.OperandStack()
// 弹出第一个操作数,作为要赋给元素的值
ref := stack.PopRef()
// 弹出第二个操作数, 作为数组引用
index := stack.PopInt()
// 弹出第三个操作数,作为数组引用
arrRef := stack.PopRef()
// 判断数组是否为空
checkNotNil(arrRef)
// 获取类型为引用的数组
refs := arrRef.Refs()
// 判断索引是否合法
checkIndex(len(refs), index)
// 通过索引给数组赋值
refs[index] = ref
}
// 省略两个判断函数和另外7条指令
...

multianewarray指令

multianewarray指令创建多维数组,其实现代码位于/instructions/references/multianewarray.go文件中。multianewarray指令的第一个操作数是个uint16索引,通过这个索引可以从运行时常量池中找到一个类符号引用,解析这个引用就可以得到多维数组类。第二个操作数是个uint8整数,表示数组维度,这两个操作数在字节码中紧跟在指令操作码后面:

1
2
3
4
5
6
7
8
9
10
11
12
// multianewarray指令结构体
type MULTI_ANEW_ARRAY struct {
// 第一个操作数
index uint16
// 第二个操作数
dimensions uint8
}
func (self *MULTI_ANEW_ARRAY) FetchOperands(reader *base.BytecodeReader) {
self.index = reader.ReadUint16()
self.dimensions = reader.ReadUint8()
}

multianewarray指令还需要从操作数中弹出n个整数,分别代表每一个维度数组的长度,Execute()方法根据数组类、数组维度和各个维度的数组的长度创建多维数组,在继续实现该指令之前有两步要先完成:

  1. 修改/rtdata/heap/class_name_helper.go文件,添加将数组类名转换成类名的方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    /*
    将数组类名转换成类名
    [[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
    // LXXX; => XXX
    // I => int
    func toClassName(descriptor string) string {
    // 若是数组类,直接返回
    if descriptor[0] == '[' {
    // array
    return descriptor
    }
    // 若是引用类型,去掉前缀L
    if descriptor[0] == 'L' {
    // object
    return descriptor[1 : len(descriptor)-1]
    }
    // 若是基本类型,通过基本类型描述符获取基本类型
    for className, d := range primitiveTypes {
    if d == descriptor {
    // primitive
    return className
    }
    }
    panic("Invalid descriptor: " + descriptor)
    }
  2. 修改/rtdata/heap/array_class.go文件,添加获取数组元素类名,并加载类。

    1
    2
    3
    4
    5
    // 获取数组元素类名,并加载类
    func (self *Class) ComponentClass() *Class {
    componentClassName := getComponentClassName(self.name)
    return self.loader.LoadClass(componentClassName)
    }

下面继续为multianewarray指令添加Execute()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
func (self *MULTI_ANEW_ARRAY) Execute(frame *rtdata.Frame) {
// 获取运行时常量池
cp := frame.Method().Class().ConstantPool()
// 获取数组类符号引用(这里获取的直接就是数组类符号引用,所以下面可以直接解析)
classRef := cp.GetConstant(uint(self.index)).(*heap.ClassRef)
// 解析数组类符号引用
arrClass := classRef.ResolvedClass()
// 获取操作数栈
stack := frame.OperandStack()
// 获取每一维度数组的长度
counts := popAndCheckCounts(stack, int(self.dimensions))
arr := newMultiDimensionalArray(counts, arrClass)
stack.PushRef(arr)
}
// 从操作数栈中弹出n个int值
func popAndCheckCounts(stack *rtdata.OperandStack, dimensions int) []int32 {
counts := make([]int32, dimensions)
for i := dimensions - 1; i >= 0; i-- {
// 弹出每一维度数组的长度
counts[i] = stack.PopInt()
if counts[i] < 0 {
panic("java.lang.NegativeArraySizeException")
}
}
return counts
}
// 创建多维数组
func newMultiDimensionalArray(counts []int32, arrClass *heap.Class) *heap.Object {
count := uint(counts[0])
arr := arrClass.NewArray(count)
if len(counts) > 1 {
refs := arr.Refs()
for i := range refs {
refs[i] = newMultiDimensionalArray(counts[1:], arrClass.ComponentClass())
}
}
return arr
}

至此,multianewarray指令算学完了,但是该指令比较难理解,我也没找到感觉,下面我们用个例子来试着分析看看。
在ArrayDemo.java中添加如下测试代码:

1
2
3
public void test() {
int [][][] x = new int [3][4][5];
}

上面test()方法的字节码如下:
jvmgo_45
编译器先生成了三条iconst_n指令,然后又生成了一条multianewarray治理弄个,剩下的两条指令和数组创建无关。multianewarray指令的第一个操作数是0x0005,是个类引用,通过索引查看常量池可知,该数组类名为[[[I,第二个操作数是0x03,说明要创建的是int类型的三维数组,即int[][][]。
当方法执行时,三条iconst_n指令先后把整数3、4、5推入操作数栈。multianewarray指令在解码时就已经拿到了常量池索引(5)和数组维度(3)。在执行时,它先查找运行时常量池索引,知道要创建的是int[][][]数组,接着从操作数栈中弹出三个int值,依次是5、4、3。这样multianewarray指令就拿到了全部信息,从最外维开始创建数组实例即可。
到这里,新增的数组专用指令就学完了,现在修改下/instructions/factory.go文件,将新增的指令注释放开。

完善instanceof和checkcast指令

虽说是完善instanceof和checkcast指令,但实际上这两台哦指令的代码都没有任何改变,需要修改的时/heap/class_hierarchy.go文件中的isAssignableFrom()方法,而且改动很大,这部分就不解释了,我也没细看,参照项目源码吧。

测试数组

我们用经典的冒泡排序算法测试下之前写的数组相关功能,在java项目中新建BubbleSortTest.java,具体代码这里就不贴出来了,参照项目源码。下面就来测试下:

1
2
3
4
5
6
7
8
9
cd /home/zhangjing/IdeaProjects/jvmgo/go
go install cn.didadu/jvmgo
./bin/jvmgo -classpath /home/zhangjing/IdeaProjects/jvmgo/java/target/classes cn.didadu.BubbleSortTest
// 通过输出结果可以看出数组成功排序
9
10
11
...
97

数组相关内容就到此结束,下一节来学习字符串的实现。