7.3 基于栈和寄存器的指令集


本章节描述的是作者按照 RISC 指令系统设计的虚拟机指令集。

其类似与硬件指令集,可作为编译器后端指令系统目标,用于提供类似硬件指令系统环境,避免编译原理初学者陷入对硬件指令系统不了解的深渊。 降低学习难度,提高学习效率。

这类指令的主要特点如下:

  • 1 寄存器的数量固定;
  • 2 指令所操作的寄存器数量固定;
  • 3 指令的含义简单;

1 寄存器

虚拟机的寄存器分为三种:

  • 1 整数寄存器:主要用于整数运算,位宽为 64 bit,根据指令的含义可选择 32 bit 和 64 bit 运算模式。
  • 2 浮点寄存器:主要用于浮点数运算,位宽为 64 bit,和通用寄存器一致,可选择位宽模式。
  • 3 系统寄存器:对用户不可见,与虚拟机运行系统相关,对其进行操作隐藏在相关指令的实现细节中。
  • 4 根据指令格式,整数寄存器、浮点寄存器最多可以有32个。

2 指令格式

每条指令是 32bit 大小对齐的,其中最低 9bit 是操作码,其余23bit作为操作数或者操作码的补充。如下表所示:

操作码操作数
9 bit23 bit

2.1 格式1

操作码操作数
9 bit23 bit

2.2 格式2

多数指令使用此格式。

操作码目的操作数源操作数源操作数操作子码
9 bit5 bit5 bit5 bit8 bit

2.3 格式3

该格式主要用于比较指令后的三元选择赋值操作。

操作码目的操作数源操作数源操作数源操作数操作子码
9 bit5 bit5 bit5 bit5 bit3 bit

2.4 格式4

当指令中需要使用立即数时,且在指令中没有足够的空间可以存储时,可以使用指令后跟32bit对齐的立即数操作数。

  • 32bit立即数:32bit 指令跟 1 个 32bit 立即数,虚拟机解析执行时,按照指令表达的语义参与运算。
  • 64bit立即数:32bit 指令跟 2 个 32bit 立即数,虚拟机解析执行时,按照指令表达的语义,组合成 64bit,后参与运算。
  • 其他位宽的立即数类似。

格式如下所示:

指令立即数 1立即数 2立即数 n
32 bit32 bit32 bit32 bit

3 寄存器与寄存器—指令集

3.1 空指令

空指令一般用于对齐,在本指令集中没有特殊含义,执行空操作。

格式如下:

助记符操作码操作数
9 bit23 bit
Nop00

3.2 算术运算

算术运算包含 +、-、*、/、%,支持的运算类型有 int32、uint32、int64、uint64、flt32、flt64。

  • 文本格式: op des,src,src2
  • 指令含义:des = src op src2
  • 子操作码:可以用于将运算结果进行截断、扩展。
  • 助记符后缀的数字(32、64)标识指令运算使用的寄存器位宽。
  • 助记符后缀的 i 表示进行有符号运算。
  • 助记符后缀的 u 表示进行无符号运算。
  • 助记符后缀的 f 表示进行有符号运算。

指令格式如下所示:

助记符操作码目的操作数源操作数源操作数操作子码
9 bit5 bit5 bit5 bit8 bit
add-i32dessrcsrc2
sub-i32dessrcsrc2
mul-i32dessrcsrc2
div-i32dessrcsrc2
mod-i32dessrcsrc2
add-u32dessrcsrc2
sub-u32dessrcsrc2
mul-u32dessrcsrc2
div-u32dessrcsrc2
mod-u32dessrcsrc2
add-i64dessrcsrc2
sub-i64dessrcsrc2
mul-i64dessrcsrc2
div-i64dessrcsrc2
mod-i64dessrcsrc2
add-u64dessrcsrc2
sub-u64dessrcsrc2
mul-u64dessrcsrc2
div-u64dessrcsrc2
mod-u64dessrcsrc2
add-f32dessrcsrc2
sub-f32dessrcsrc2
mul-f32dessrcsrc2
div-f32dessrcsrc2
add-f64dessrcsrc2
sub-f64dessrcsrc2
mul-f64dessrcsrc2
div-f64dessrcsrc2

3.2 位运算

位运算包括位相关的与、或、非、异或、移位、取反等运算。

支持的运算类型有 uint32、uint64。

  • 文本格式: op des,src,src2
  • 指令含义:des = src op src2
  • 子操作码:可以用于将运算结果进行截断、扩展。
  • i32:表示进行 32bit 的位运算。
  • i64:表示进行 64bit 的位运算。
  • 位运算都是看作无符号运算。

指令格式如下所示:

助记符操作码目的操作数源操作数源操作数操作子码
9 bit5 bit5 bit5 bit8 bit
sl-i32dessrcsrc2
sr-i32dessrcsrc2
sra-i32dessrcsrc2
and-i32dessrcsrc2
or-i32dessrcsrc2
xor-i32dessrcsrc2
andn-i32dessrcsrc2
orn-i32dessrcsrc2
xorn-i32dessrcsrc2
not-i32dessrcsrc2
sl-i64dessrcsrc2
sr-i64dessrcsrc2
sra-i64dessrcsrc2
and-i64dessrcsrc2
or-i64dessrcsrc2
xor-i64dessrcsrc2
andn-i64dessrcsrc2
orn-i64dessrcsrc2
xorn-i64dessrcsrc2
not-i64dessrcsrc2

3.3 逻辑比较

逻辑比较运算包括:<、>、<=、>=、==、!=、 ==0、!=0. 支持的运算类型有 int32、uint32、int64、uint64、flt32\flt64。

  • 文本格式: op des,src,src2
  • 指令含义:des = src op src2
  • 子操作码:可以用于标识比较的方法。
  • 比较的结果,存储在整数寄存器中,且使用位宽 64 bit,即des是整数寄存器。

指令格式如下所示:

助记符操作码目的操作数源操作数源操作数操作子码
9 bit5 bit5 bit5 bit8 bit
cmp-i32dessrcsrc2
cmp-u32dessrcsrc2
cmp-i64dessrcsrc2
cmp-u64dessrcsrc2
cmp-f32dessrcsrc2
cmp-f64dessrcsrc2

操作子码定义如下:

助记符操作码含义
lt<
le<=
gt>
ge>=
eq==
ne!=
ez==0,此时没有源操作数src2
nz!=0,此时没有源操作数src2

当不使用操作子码,而是使用下面方式运算:

  • src < src2,设置des寄存器为 -1。
  • src == src2,设置des寄存器为 0。
  • src > src2,设置des寄存器为 -1。

3.4 条件赋值

条件赋值指令使用比较指令的结果,对另外 2 个源操作数进行选择,传递个目标操作数。

指令格式如下所示:

助记符操作码目的操作数源操作数源操作数源操作数操作子码
9 bit5 bit5 bit5 bit5 bit3 bit
sel-i32dessrcsrc2cond
sel-u32dessrcsrc2cond
sel-i64dessrcsrc2cond
sel-u64dessrcsrc2cond
sel-f32dessrcsrc2cond
sel-f64dessrcsrc2cond

当比较系列指令使用操作子码方式时:

  • cond == 0 :des = src
  • cond != 0 :des = src2

当比较系列指令不使用操作子码方式时,本系列指令要求使用操作子码进行判断:

操作子码定义如下:

助记符操作码含义
lt< :des = cond == -1 ? src : src2
le<= :des = cond < 1 ? src : src2
gt> :des = cond == 1 ? src : src2
ge>= :des = cond > -1 ? src : src2
eq== :des = cond == 0 ? src : src2
ne!= :des = cond != 0 ? src : src2

3.5 赋值指令

此系列包括寄存器间赋值或类型转换。

助记符操作码目的操作数源操作数源操作数操作子码
9 bit5 bit5 bit5 bit8 bit
movdessrc

操作子码用于获取源操作数并转换到响应的类型。

操作子码定义如下:

助记符操作码含义
i8-i32
u8-i32
i8-i64
u8-u64
i16-i32
u16-i32
i16-i64
u16-i64
i32_i32
i32-i64
i32-f32
i32-f64
u32-u32
u32-u64
u32-f32
u32-f64
i64-i64
i64-f32
i64-f64
u64-u64
u64-f32
u64-f64
f32-i32
f32-u32
f32-i64
f32-u64
f32-f32
f32-f64
f64-i32
f64-u32
f64-i64
f64-u64
f64-f32
f64-f64
f32-bit-u32位转换
u32-bit-f32位转换
f64-bit-u64位转换
u64-bit-f64位转换

3.6 加载存储指令

此系列指令功能是从内存中读取、存储特定类型数据。

助记符操作码目的操作数源操作数源操作数操作子码注释
9 bit5 bit5 bit5 bit8 bit
loaddesbaseoffset从基址内存中获取数据
storedesbaseoffset
gloaddesbaseoffset从全局内存中获取数据
gstoredesbaseoffset

操作子码定义如下:

助记符操作码含义
i8
u8
i16
u16
i32
u32
i64
u64
f32
f64

3.7 出入栈指令

此系列指令用于将数据入栈,同时可以进行类型转换。 注意:入栈数据应当进行 32bit 对齐。以加快虚拟机执行速度数据

助记符操作码目的操作数源操作数源操作数操作子码
9 bit5 bit5 bit5 bit8 bit
pushdes
popdes
操作子码定义如下:
助记符操作码含义
i8
u8
i16
u16
i32
u32
i64
u64
f32
f64

3.8 栈内存分配释放

此系列指令用于栈内存分配释放。 注意:立即数是无符号整数。

助记符操作码目的操作数立即数注释
9 bit5 bit18 bit
growndesimm分配栈内存,将分配后的栈指针值传递到 des 寄存器
9 bit23 bit释放栈内存,
shrunkimm

3.9 跳转指令

  • 跳转的偏移量是无符号整数值;
  • 相对于函数代码的起始地址;
  • 偏移量是偏移的指令条数;
助记符操作码目的操作数立即数注释
9 bit23 bit
jmpimm直接跳转
9 bit5 bit18 bit
jmpxreg偏移量在 reg 寄存器中
9 bit5 bit18 bit
jtabregimmreg 表示偏移量索引,imm表示偏移量数组中偏移数据条数,指令后面接 32 bit 对齐的偏移量数组

3.10 分支指令

此系列指令用于条件分支跳转。

助记符操作码目的操作数源操作数源操作数操作子码偏移立即数
9 bit5 bit5 bit5 bit8 bit32 bit
jbr-i32srcsrc2condoffset
jbr-u32srcsrc2condoffset
jbr-i64srcsrc2condoffset
jbr-u64srcsrc2condoffset
jbr-f32srcsrc2condoffset
jbr-f64srcsrc2condoffset

操作子码定义如下:

助记符操作码含义
lt<
le<=
gt>
ge>=
eq==
ne!=
ez==0,此时没有源操作数src2
nz!=0,此时没有源操作数src2

3.11 函数指令

助记符操作码目的操作数立即数注释
9 bit23 bit32 bit
callimmimm表示函数的偏移量或者是编号,看虚拟机的具体实现
9 bit5 bit18 bit
callxregreg 寄存器中保存函数的偏移量或者是编号,看虚拟机的具体实现
9 bit23 bit
ret函数返回

4 寄存器与立即数–指令集

待续