手写JVM(九)-指令集(三)

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

数学指令大致对应Java语言中的加、减、乘、除等数学运算符。

数学指令包括算术指令、位移指令和布尔运算指令等。

1.算术指令

1.作用:

算术指令用于对两个操作数栈上的值进行某种特定运算,并把结果重新压入操作数栈。

注意与其他指令区别

 

2.分类:

大体上算术指令可以分为两种:对整型数据进行运算的指令与对浮点类型数据进行运算的指令。

3.byte、short、char和boolean类型说明:

在每一大类中,都有针对Java虚拟机具体数据类型的专用算术指令。但没有直接支持byte、 short、char和boolean类型的算术指令,对于这些数据的运算,都使用int类型的指令来处理。此外,在处理boolean、byte、short和char类型的数组时,也会转换为使用对应的int类型的字节码指令来处理。

 

4.运算时的溢出

数据运算可能会导致溢出,例如两个很大的正整数相加,结果可能是一个负数。其实Java虚拟机规范并无明确规定过整型数据溢出的具体结果,仅规定了在处理整型数据时,只有除法指令以及求余指令中当出现除数为0时会导致虚拟机抛出异常ArithmeticException。

 

5.运算模式

  • 向最接近数舍入模式: JVM要求在进行浮点数计算时,所有的运算结果都必须舍入到适当的精度,非精确结果必须舍入为可被表示的最接近的精确值,如果有两种可表示的形式与该值一样接近,将优先选择最低有效位为零的;
  • 向零舍入模式:将浮点数转换为整数时,采用该模式,该模式将在目标数值类型中选择一个最接近但是不大于原值的数字作为最精确的舍入结果;

 

6.NaN值使用

当一个操作产生溢出时,将会使用有符号的无穷大表示,如果某个操作结果没有明确的数学定义的话,将会使用NaN值来表示。而且所有使用NaN值作为操作数的算术操作,结果都会返回NaN;

 

算术指令又可以进一步分为加法(add)指令、减法(sub)指令、乘法(mul)指令、除法(div)指令、求余(rem)指令和取反(neg)指令6种。

 

整体代码

在instructions\math目录下创建add.go文件

package math

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

// Add double

type DADD struct{ base.NoOperandsInstruction }

func (self *DADD) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v1 := stack.PopDouble()
    v2 := stack.PopDouble()
    result := v1 + v2
    stack.PushDouble(result)
}

// Add float

type FADD struct{ base.NoOperandsInstruction }

func (self *FADD) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v2 := stack.PopFloat()
    v1 := stack.PopFloat()
    result := v1 + v2
    stack.PushFloat(result)
}

// Add int

type IADD struct{ base.NoOperandsInstruction }

func (self *IADD) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v2 := stack.PopInt()
    v1 := stack.PopInt()
    result := v1 + v2
    stack.PushInt(result)
}

// Add long

type LADD struct{ base.NoOperandsInstruction }

func (self *LADD) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v2 := stack.PopLong()
    v1 := stack.PopLong()
    result := v1 + v2
    stack.PushLong(result)
}

在instructions\math目录下创建div.go文件

package math

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


// Divide double

type DDIV struct{ base.NoOperandsInstruction }

func (self *DDIV) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v2 := stack.PopDouble()
    v1 := stack.PopDouble()
    result := v1 / v2
    stack.PushDouble(result)
}

// Divide float

type FDIV struct{ base.NoOperandsInstruction }

func (self *FDIV) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v2 := stack.PopFloat()
    v1 := stack.PopFloat()
    result := v1 / v2
    stack.PushFloat(result)
}

// Divide int

type IDIV struct{ base.NoOperandsInstruction }

func (self *IDIV) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v2 := stack.PopInt()
    v1 := stack.PopInt()
    if v2 == 0 {
        panic("java.lang.ArithmeticException: / by zero")
    }

    result := v1 / v2
    stack.PushInt(result)
}

// Divide long

type LDIV struct{ base.NoOperandsInstruction }

func (self *LDIV) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v2 := stack.PopLong()
    v1 := stack.PopLong()
    if v2 == 0 {
        panic("java.lang.ArithmeticException: / by zero")
    }

    result := v1 / v2
    stack.PushLong(result)
}

在instructions\math目录下创建mul.go文件

package math

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


// Multiply double

type DMUL struct{ base.NoOperandsInstruction }

func (self *DMUL) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v2 := stack.PopDouble()
    v1 := stack.PopDouble()
    result := v1 * v2
    stack.PushDouble(result)
}


// Multiply float

type FMUL struct{ base.NoOperandsInstruction }

func (self *FMUL) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v2 := stack.PopFloat()
    v1 := stack.PopFloat()
    result := v1 * v2
    stack.PushFloat(result)
}

// Multiply int

type IMUL struct{ base.NoOperandsInstruction }

func (self *IMUL) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v2 := stack.PopInt()
    v1 := stack.PopInt()
    result := v1 * v2
    stack.PushInt(result)
}

// Multiply long

type LMUL struct{ base.NoOperandsInstruction }

func (self *LMUL) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v2 := stack.PopLong()
    v1 := stack.PopLong()
    result := v1 * v2
    stack.PushLong(result)
}

 

在instructions\math目录下创建neg.go文件

package math

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

// Negate double

type DNEG struct{ base.NoOperandsInstruction }

func (self *DNEG) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    val := stack.PopDouble()
    stack.PushDouble(-val)
}

// Negate float

type FNEG struct{ base.NoOperandsInstruction }

func (self *FNEG) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    val := stack.PopFloat()
    stack.PushFloat(-val)
}

// Negate int

type INEG struct{ base.NoOperandsInstruction }

func (self *INEG) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    val := stack.PopInt()
    stack.PushInt(-val)
}

// Negate long

type LNEG struct{ base.NoOperandsInstruction }

func (self *LNEG) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    val := stack.PopLong()
    stack.PushLong(-val)
}

 

在instructions\math目录下创建sub.go文件

package math

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


// Subtract double

type DSUB struct{ base.NoOperandsInstruction }

func (self *DSUB) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v2 := stack.PopDouble()
    v1 := stack.PopDouble()
    result := v1 - v2
    stack.PushDouble(result)
}

// Subtract float

type FSUB struct{ base.NoOperandsInstruction }

func (self *FSUB) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v2 := stack.PopFloat()
    v1 := stack.PopFloat()
    result := v1 - v2
    stack.PushFloat(result)
}

// Subtract int

type ISUB struct{ base.NoOperandsInstruction }

func (self *ISUB) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v2 := stack.PopInt()
    v1 := stack.PopInt()
    result := v1 - v2
    stack.PushInt(result)
}

// Subtract long

type LSUB struct{ base.NoOperandsInstruction }

func (self *LSUB) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v2 := stack.PopLong()
    v1 := stack.PopLong()
    result := v1 - v2
    stack.PushLong(result)
}

 

在instructions\math目录下创建rem.go文件,在其中定义4条求余指令,代码如下:

package math

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

// Remainder double

type DREM struct{ base.NoOperandsInstruction }

func (self *DREM) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v2 := stack.PopDouble()
    v1 := stack.PopDouble()
    result := math.Mod(v1, v2) // todo
    stack.PushDouble(result)
}

// Remainder float

type FREM struct{ base.NoOperandsInstruction }

func (self *FREM) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v2 := stack.PopFloat()
    v1 := stack.PopFloat()
    result := float32(math.Mod(float64(v1), float64(v2))) // todo
    stack.PushFloat(result)
}

// Remainder int

type IREM struct{ base.NoOperandsInstruction }

func (self *IREM) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v2 := stack.PopInt()
    v1 := stack.PopInt()
    if v2 == 0 {
        panic("java.lang.ArithmeticException: / by zero")
    }

    result := v1 % v2
    stack.PushInt(result)
}

// Remainder long

type LREM struct{ base.NoOperandsInstruction }

func (self *LREM) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v2 := stack.PopLong()
    v1 := stack.PopLong()
    if v2 == 0 {
        panic("java.lang.ArithmeticException: / by zero")
    }

    result := v1 % v2
    stack.PushLong(result)
}

 

代码解释

加、减、乘、除和取反指令都比较简单,所以就解释一下稍微复杂一些的求余指令。

irem和lrem代码差不多,以irem为例,先从操作数栈中弹出两个int变量,求余,然后把结果推入操作数栈。这里注意一点,对int或long变量做除法和求余运算时,是有可能抛出ArithmeticException异常的。其Execute()方法如下:

 

frem和drem指令差不多,其Execute()方法如下:

Go语言没有给浮点数类型定义求余操作符,所以需要使用math包的Mod()函数。另外,浮点数类型因为有Infinity(无穷大)值,所以即使是除零,也不会导致ArithmeticException异常抛出。

 

math包的Mod()函数用法:Mod返回x/y的浮点余数。结果的大小小于y,其符号与x的符号一致。

 

2.位移指令

位移指令可以分为左移和右移两种,右移指令又可以分为算术右移(有符号右移)和逻辑右移(无符号右移)两种。算术右移和逻辑位移的区别仅在于符号位的扩展,如下面的Java代码所示。

int x = -1;
System.out.println(Integer.toBinaryString(x)); // 11111111111111111111111111111111
System.out.println(Integer.toBinaryString(x >> 8)); // 11111111111111111111111111111111
System.out.println(Integer.toBinaryString(x >>> 8)); // 00000000111111111111111111111111

 

整体代码

在instructions\math目录下创建sh.go文件,在其中定义6条位移指令,代码如下:

package math

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

// Shift left int

type ISHL struct{ base.NoOperandsInstruction }

func (self *ISHL) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v2 := stack.PopInt()
    v1 := stack.PopInt()
    s := uint32(v2) & 0x1f
    result := v1 << s
    stack.PushInt(result)
}

// Arithmetic shift right int

type ISHR struct{ base.NoOperandsInstruction }

func (self *ISHR) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v2 := stack.PopInt()
    v1 := stack.PopInt()
    s := uint32(v2) & 0x1f
    result := v1 >> s
    stack.PushInt(result)
}

// Logical shift right int

type IUSHR struct{ base.NoOperandsInstruction }

func (self *IUSHR) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v2 := stack.PopInt()
    v1 := stack.PopInt()
    s := uint32(v2) & 0x1f
    result := int32(uint32(v1) >> s)
    stack.PushInt(result)
}

// Shift left long

type LSHL struct{ base.NoOperandsInstruction }

func (self *LSHL) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v2 := stack.PopInt()
    v1 := stack.PopLong()
    s := uint32(v2) & 0x3f
    result := v1 << s
    stack.PushLong(result)
}

// Arithmetic shift right long

type LSHR struct{ base.NoOperandsInstruction }

func (self *LSHR) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v2 := stack.PopInt()
    v1 := stack.PopLong()
    s := uint32(v2) & 0x3f
    result := v1 >> s
    stack.PushLong(result)
}

// Logical shift right long

type LUSHR struct{ base.NoOperandsInstruction }

func (self *LUSHR) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v2 := stack.PopInt()
    v1 := stack.PopLong()
    s := uint32(v2) & 0x3f
    result := int64(uint64(v1) >> s)
    stack.PushLong(result)
}

 

代码解释

左移指令比较简单,以ishl指令为例,先从操作数栈中弹出两个int变量v2和v1。v1是要进行位移操作的变量,v2指出要移位多少比特。位移之后,把结果推入操作数栈。这里注意两点:第一,int变量只有32位,所以只取v2的前5个比特(二进制11111转换十进制是31,也就是与0x1F就行)就足够表示位移位数了;第二,Go语言位移操作符右侧必须是无符号整数,所以需要对v2进行类型转换。其Execute()方法如下:

 

算术右移指令需要扩展符号位,代码和左移指令基本上差不多。以lshr指令为例,long变量只有64位,所以只取v2的前6个比特(二进制111111转换十进制是63,也就是与0x3F就行)就足够表示位移位数了,其Execute()方法如下:

 

最后以iushr为例,Go语言并没有Java语言中的>>>运算符,为了达到无符号位移的目的,需要先把v1转成无符号整数,位移操作之后,再转回有符号整数。

 

3.布尔运算指令

布尔运算指令只能操作int和long变量,分为按位与(and)、按位或(or)、按位异或(xor)3种。以按位与为例介绍布尔运算指令。

整体代码

在instructions\math目录下创建and.go文件,在其中定义iand和land指令,代码如下:

package math

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

// Boolean AND int

type IAND struct{ base.NoOperandsInstruction }

func (self *IAND) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v2 := stack.PopInt()
    v1 := stack.PopInt()
    result := v1 & v2
    stack.PushInt(result)
}

// Boolean AND long

type LAND struct{ base.NoOperandsInstruction }

func (self *LAND) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v2 := stack.PopLong()
    v1 := stack.PopLong()
    result := v1 & v2
    stack.PushLong(result)
}

 

在instructions\math目录下创建or.go文件,代码如下:

package math

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


// Boolean OR int

type IOR struct{ base.NoOperandsInstruction }

func (self *IOR) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v2 := stack.PopInt()
    v1 := stack.PopInt()
    result := v1 | v2
    stack.PushInt(result)
}

// Boolean OR long

type LOR struct{ base.NoOperandsInstruction }

func (self *LOR) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v2 := stack.PopLong()
    v1 := stack.PopLong()
    result := v1 | v2
    stack.PushLong(result)
}

 

在instructions\math目录下创建xor.go文件,代码如下:

package math

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


// Boolean XOR int

type IXOR struct{ base.NoOperandsInstruction }

func (self *IXOR) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v1 := stack.PopInt()
    v2 := stack.PopInt()
    result := v1 ^ v2
    stack.PushInt(result)
}

// Boolean XOR long

type LXOR struct{ base.NoOperandsInstruction }

func (self *LXOR) Execute(frame *rtda.Frame) {
    stack := frame.OperandStack()
    v1 := stack.PopLong()
    v2 := stack.PopLong()
    result := v1 ^ v2
    stack.PushLong(result)
}

代码比较简单,就不多解释了。

 

4.iinc指令

iinc指令给局部变量表中的int变量增加常量值,局部变量表索引和常量值都由指令的操作数提供。

整体代码

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

package math

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


// Increment local variable by constant

type IINC struct {
    Index uint
    Const int32
}

func (self *IINC) FetchOperands(reader *base.BytecodeReader) {
    self.Index = uint(reader.ReadUint8())
    self.Const = int32(reader.ReadInt8())
}

func (self *IINC) Execute(frame *rtda.Frame) {
    localVars := frame.LocalVars()
    val := localVars.GetInt(self.Index)
    val += self.Const
    localVars.SetInt(self.Index, val)
}

代码解释

FetchOperands()函数从字节码里读取操作数:

 

Execute()方法从局部变量表中读取变量,给它加上常量值,再把结果写回局部变量表,代码如下:

 

5.参考

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