前言
简介:本文将主要针对该c++实现中的模板部分以及一些函数中可能造成理解障碍的部分进行分析。
背景介绍:建议通读此文[译] 400 行 C 代码实现一个虚拟机或英文版原文Write your Own Virtual Machine 对该虚拟机的构成有基本了解,并下载其c++版本源码进行阅读。
所需知识:对linux库函数的基本功能以及c++模板元编程有的大致了解,有c/c++的编程基础,了解位运算。(过程中建议打开计算器的程序员选项)
GitHub项目地址:lc3-cpp-with-notes (github.com)
一、部分函数介绍
1、
uint16_t sign_extend(uint16_t x, int bit_count)
该函数通过将输入数右移获得其最高位从而判断正负,若是负数则将其高位全部填充成为1,确保不会因为拓展到16位而引起表示数的变化。
2、
uint16_t swap16(uint16_t x)
通过低位左移和高位右移实现大小端的转换。
3、
void read_image_file(FILE* file)
该函数将文件的内容读取到内存中,并进行大小端转换。
4、
uint16_t mem_read(uint16_t address)
该函数的作用是返回输入地址在内存中的值,在键盘输入可用时将输入的内容写入内存中的键盘数据地址。
二、模板部分分析
uint16_t pc_plus_off, base_plus_off;
//以下到238行都是公共逻辑
constexpr uint16_t opbit = (1 << op);
if (0x4EEE & opbit) { r0 = (instr >> 9) & 0x7; }
//前面的均为掩码,不同的命令op导致的opbit数值不同则执行的选项也不同
if (0x12F3 & opbit) { r1 = (instr >> 6) & 0x7; }
if (0x0022 & opbit)
{
imm_flag = (instr >> 5) & 0x1;
if (imm_flag)
{
imm5 = sign_extend(instr & 0x1F, 5);
//与0x1F是为了取出立即数的五位
}
else
{
r2 = instr & 0x7;
}
}
if (0x00C0 & opbit)
{ // Base + offset(基址偏移寻址)
base_plus_off = reg[r1] + sign_extend(instr & 0x3F, 6);
}
if (0x4C0D & opbit)
{
// Indirect address(PC相对寻址)
pc_plus_off = reg[R_PC] + sign_extend(instr & 0x1FF, 9);
}
(该部分代码属于指令通用的代码)
1、在第一行中的两个变量分别是pc相对寻址和基类相对寻址,前者是指相对于寄存器中程序计数器即PC位置的偏移量,后者是指相对于指令指定的基类寄存器即r1位置的偏移量。
2、在第三行中的op是输入的模板参数名,其值与开头的枚举命令值相对应,不同的命令对应的opbit值不同。第四行往下if判断式中的十六进制数即是由需要进行该步操作的opbit值进行位或运算得出。
3、if执行语句中的右移位数是为了到达该段指令中的所需位段,并用十六进制的7即二进制的0111通过位与运算取出。不同指令的不同功能位段可见下图:
4、原代码中该部分结束后则直接进入到了不同指令的具体操作阶段,if判断式中的十六进制部分可以直接与opbit二进制值一一对应,对应于c语言版本中的switch语句。
5、
if (0x0010 & opbit) // JSR(jump register)
{
uint16_t long_flag = (instr >> 11) & 1;
reg[R_R7] = reg[R_PC];
if (long_flag) //如果该位为1说明该指令为长的JSR需要取其后11位
{
pc_plus_off = reg[R_PC] + sign_extend(instr & 0x7FF, 11);
reg[R_PC] = pc_plus_off;
}
else //否则该指令是JSRR
{
reg[R_PC] = reg[r1];
}
}
在JSR命令中,从上图可得知,JSR与JSRR命令的opbit相同,则需要用第十一位中的值来判断是两者中的哪一个,如果是JSR命令则需要重新设置pc_plus_off变量,因为前面的通用部分只取了9位pc_plus_off变量。
6、
static void (*op_table[16])(uint16_t)
该行代码为一个内含16个指向一个返回值为void、其参数为uint16_t的函数的指针的数组,数组名为op_table。
三、项目总结
这个虚拟机的结构短小精悍,麻雀虽小五脏俱全,非常适合对cpu指令执行流程和内存寻址方式不熟悉的人员进行学习。而该c++版本的代码在模板部分对各指令的通用部分进行了整合,初看可能会一脸茫然,不知所言,但深入理解后也可以感受得到编写者对于代码重用的智慧所在。 (文中图片出自:LC-3指令集 指令/状态码介绍_AKGWSB 's blog-CSDN博客_lc3指令集)