编译器实现之旅——第九章 实现虚拟机
在这一章的旅程中,我们来实现虚拟机。CMM的虚拟机具有一套极为简单的指令集,和一个能够运行这套指令集的虚拟机。
需要注意的是:本章将不会对指令集以及虚拟机的设计和行为做过多的解释,而只是着重于展示。这是因为:在没有讲解代码生成器之前,这些解释是十分苍白的。
1. CMM虚拟机的指令集
CMM指令集一共只有24条指令。在这些指令中,除了"LDC"、"JMP"、"JZ"、"ADDR"和"CALL"指令需要附加一个整型参数外,其余所有指令均不带有参数。
CMM指令集及其对应的伪代码如下:
指令 | 伪代码 |
---|---|
LDC N | AX = N |
LD | AX = SS[BP - AX] |
ALD | AX = SS[AX] |
ST | SS[BP - AX] = SS.TOP() |
AST | SS[AX] = SS.TOP() |
PUSH | SS.PUSH(AX) |
POP | SS.POP() |
JMP N | IP += N |
JZ N | if (AX == 0) IP += N |
ADD | AX = SS.TOP() + AX |
SUB | AX = SS.TOP() - AX |
MUL | AX = SS.TOP() * AX |
DIV | AX = SS.TOP() / AX |
LT | AX = SS.TOP() < AX |
LE | AX = SS.TOP() <= AX |
GT | AX = SS.TOP() > AX |
GE | AX = SS.TOP() >= AX |
EQ | AX = SS.TOP() == AX |
NE | AX = SS.TOP() != AX |
IN | scanf("%d", &AX) |
OUT | printf("%d\n", AX) |
ADDR N | AX = SS.SIZE() - N |
CALL N | SS.PUSH(BP); BP = SS.SIZE(); SS.PUSH(IP); IP += N |
RET | IP = SS.POP(); BP = SS.POP() |
CMM指令集的枚举类型实现如下:
enum class __Instruction
{
// Load
__LDC,
__LD,
__ALD,
// Store
__ST,
__AST,
// Push, Pop
__PUSH,
__POP,
// Jump
__JMP,
__JZ,
// Arithmetic
__ADD,
__SUB,
__MUL,
__DIV,
// Relationship
__LT,
__LE,
__GT,
__GE,
__EQ,
__NE,
// I/O
__IN,
__OUT,
// Address
__ADDR,
// Functional
__CALL,
__RET,
};
2. 指令文件IO类的实现
指令文件IO类的实现非常简单,我们只需要关注上文中的这条描述即可:除了"LDC"、"JMP"、"JZ"、"ADDR"和"CALL"指令需要附加一个整型参数外,其余所有指令均不带有参数。请看:
class __IO
{
// Friend
friend class Core;
private:
// Parse Instruction File
static vector<pair<__Instruction, int>> __parseInstructionFile(const string &instructionFilePath);
// Output Instruction
static void __outputInstruction(const string &instructionFilePath, const vector<pair<__Instruction, string>> &codeList);
};
vector<pair<__Instruction, int>> __IO::__parseInstructionFile(const string &instructionFilePath)
{
vector<pair<__Instruction, int>> codeList;
ifstream f(instructionFilePath);
string line;
while (getline(f, line))
{
if (line.substr(0, 4) == "LDC ")
{
codeList.emplace_back(__Instruction::__LDC, stoi(line.substr(4)));
}
else if (line == "LD")
{
codeList.emplace_back(__Instruction::__LD, 0);
}
else if (line == "ALD")
{
codeList.emplace_back(__Instruction::__ALD, 0);
}
else if (line == "ST")
{
codeList.emplace_back(__Instruction::__ST, 0);
}
else if (line == "__AST")
{
codeList.emplace_back(__Instruction::__AST, 0);
}
else if (line == "PUSH")
{
codeList.emplace_back(__Instruction::__PUSH, 0);
}
else if (line == "POP")
{
codeList.emplace_back(__Instruction::__POP, 0);
}
else if (line.substr(0, 4) == "JMP ")
{
codeList.emplace_back(__Instruction::__JMP, stoi(line.substr(4)));
}
else if (line.substr(0, 3) == "JZ ")
{
codeList.emplace_back(__Instruction::__JZ, stoi(line.substr(3)));
}
else if (line == "ADD")
{
codeList.emplace_back(__Instruction::__ADD, 0);
}
else if (line == "SUB")
{
codeList.emplace_back(__Instruction::__SUB, 0);
}
else if (line == "MUL")
{
codeList.emplace_back(__Instruction::__MUL, 0);
}
else if (line == "DIV")
{
codeList.emplace_back(__Instruction::__DIV, 0);
}
else if (line == "LT")
{
codeList.emplace_back(__Instruction::__LT, 0);
}
else if (line == "LE")
{
codeList.emplace_back(__Instruction::__LE, 0);
}
else if (line == "GT")
{
codeList.emplace_back(__Instruction::__GT, 0);
}
else if (line == "GE")
{
codeList.emplace_back(__Instruction::__GE, 0);
}
else if (line == "EQ")
{
codeList.emplace_back(__Instruction::__EQ, 0);
}
else if (line == "NE")
{
codeList.emplace_back(__Instruction::__NE, 0);
}
else if (line == "IN")
{
codeList.emplace_back(__Instruction::__IN, 0);
}
else if (line == "OUT")
{
codeList.emplace_back(__Instruction::__OUT, 0);
}
else if (line.substr(0, 5) == "ADDR ")
{
codeList.emplace_back(__Instruction::__ADDR, stoi(line.substr(5)));
}
else if (line.substr(0, 5) == "CALL ")
{
codeList.emplace_back(__Instruction::__CALL, stoi(line.substr(5)));
}
else if (line == "RET")
{
codeList.emplace_back(__Instruction::__RET, 0);
}
else
{
throw runtime_error("Invalid instruction");
}
}
return codeList;
}
void __IO::__outputInstruction(const string &instructionFilePath, const vector<pair<__Instruction, string>> &codeList)
{
FILE *fo = fopen(instructionFilePath.c_str(), "w");
for (auto &[codeEnum, codeValStr]: codeList)
{
switch (codeEnum)
{
case __Instruction::__LDC:
fprintf(fo, "LDC %s\n", codeValStr.c_str());
break;
case __Instruction::__LD:
fprintf(fo, "LD\n");
break;
case __Instruction::__ALD:
fprintf(fo, "ALD\n");
break;
case __Instruction::__ST:
fprintf(fo, "ST\n");
break;
case __Instruction::__AST:
fprintf(fo, "__AST\n");
break;
case __Instruction::__PUSH:
fprintf(fo, "PUSH\n");
break;
case __Instruction::__POP:
fprintf(fo, "POP\n");
break;
case __Instruction::__JMP:
fprintf(fo, "JMP %s\n", codeValStr.c_str());
break;
case __Instruction::__JZ:
fprintf(fo, "JZ %s\n", codeValStr.c_str());
break;
case __Instruction::__ADD:
fprintf(fo, "ADD\n");
break;
case __Instruction::__SUB:
fprintf(fo, "SUB\n");
break;
case __Instruction::__MUL:
fprintf(fo, "MUL\n");
break;
case __Instruction::__DIV:
fprintf(fo, "DIV\n");
break;
case __Instruction::__LT:
fprintf(fo, "LT\n");
break;
case __Instruction::__LE:
fprintf(fo, "LE\n");
break;
case __Instruction::__GT:
fprintf(fo, "GT\n");
break;
case __Instruction::__GE:
fprintf(fo, "GE\n");
break;
case __Instruction::__EQ:
fprintf(fo, "EQ\n");
break;
case __Instruction::__NE:
fprintf(fo, "NE\n");
break;
case __Instruction::__IN:
fprintf(fo, "IN\n");
break;
case __Instruction::__OUT:
fprintf(fo, "OUT\n");
break;
case __Instruction::__ADDR:
fprintf(fo, "ADDR %s\n", codeValStr.c_str());
break;
case __Instruction::__CALL:
fprintf(fo, "CALL %s\n", codeValStr.c_str());
break;
case __Instruction::__RET:
fprintf(fo, "RET\n");
break;
default:
throw runtime_error("Invalid instruction");
}
}
fclose(fo);
}
通过上面实现的这些函数,我们就可以将指令列表输出到文件,或从文件中将指令列表读出了。那么,指令列表要怎么使用呢?该轮到虚拟机上场了。
3. CMM虚拟机的实现
CMM虚拟机的结构出乎意料的简单:其只含有一个指令段寄存器CS、一个指令指针寄存器IP、一个栈段寄存器SS、一个通用寄存器AX,以及一个基础指针寄存器BP。
CMM虚拟机的结构模型如下图所示:
+----+ +----+ +----+
| CS | | SS | | AX |
+----+ +----+ +----+
| .. | | .. |
+----+ +----+ +----+ +----+
| .. | <= | IP | | .. | | BP |
+----+ +----+ +----+ +----+
.. ..
CMM虚拟机的实现如下:
class __VM
{
// Friend
friend class Core;
public:
// Constructor
explicit __VM(const vector<pair<__Instruction, int>> &CS);
private:
// Attribute
vector<pair<__Instruction, int>> __CS;
int __IP;
int __AX;
int __BP;
vector<int> __SS;
// Exec __Instruction
void __execInstruction(const pair<__Instruction, int> &instructionPair);
// Run
void __run();
};
__VM::__VM(const vector<pair<__Instruction, int>> &CS):
__CS(CS),
__IP(0) {}
void __VM::__execInstruction(const pair<__Instruction, int> &instructionPair)
{
switch (instructionPair.first)
{
case __Instruction::__LDC:
__AX = instructionPair.second;
break;
case __Instruction::__LD:
__AX = __SS[__BP - __AX];
break;
case __Instruction::__ALD:
__AX = __SS[__AX];
break;
case __Instruction::__ST:
__SS[__BP - __AX] = __SS.back();
break;
case __Instruction::__AST:
__SS[__AX] = __SS.back();
break;
case __Instruction::__PUSH:
__SS.push_back(__AX);
break;
case __Instruction::__POP:
__SS.pop_back();
break;
case __Instruction::__JMP:
__IP += instructionPair.second - 1;
break;
case __Instruction::__JZ:
if (!__AX)
{
__IP += instructionPair.second - 1;
}
break;
case __Instruction::__ADD:
__AX = __SS.back() + __AX;
break;
case __Instruction::__SUB:
__AX = __SS.back() - __AX;
break;
case __Instruction::__MUL:
__AX = __SS.back() * __AX;
break;
case __Instruction::__DIV:
__AX = __SS.back() / __AX;
break;
case __Instruction::__LT:
__AX = __SS.back() < __AX;
break;
case __Instruction::__LE:
__AX = __SS.back() <= __AX;
break;
case __Instruction::__GT:
__AX = __SS.back() > __AX;
break;
case __Instruction::__GE:
__AX = __SS.back() >= __AX;
break;
case __Instruction::__EQ:
__AX = __SS.back() == __AX;
break;
case __Instruction::__NE:
__AX = __SS.back() != __AX;
break;
case __Instruction::__IN:
scanf("%d", &__AX);
break;
case __Instruction::__OUT:
printf("%d\n", __AX);
break;
case __Instruction::__ADDR:
__AX = __SS.size() - instructionPair.second;
break;
case __Instruction::__CALL:
__SS.push_back(__BP);
__BP = __SS.size() - 2;
__SS.push_back(__IP);
__IP += instructionPair.second - 1;
break;
case __Instruction::__RET:
__IP = __SS.back();
__SS.pop_back();
__BP = __SS.back();
__SS.pop_back();
break;
default:
throw runtime_error("Invalid __Instruction value");
}
}
void __VM::__run()
{
for (__IP = 0; __IP < (int)__CS.size(); __IP++)
{
__execInstruction(__CS[__IP]);
}
}
当一个虚拟机被构造时,其应当装载CS;而虚拟机的运行,就是不断执行CS[IP]中的指令,并令IP加1的过程。所以,在虚拟机的实现中,我们只需要简单的为指令集中的每个指令都分配好其行为即可。每条指令的行为都十分简单,这里也就不详细讨论了。
至此,编译器后端中的语义分析器和虚拟机,我们就都实现完成了。接下来,我们就要动身前往编译器的最后一站——代码生成器了。请看下一章:《实现代码生成器前的准备》。
本文作者GitHub:https://github.com/yingyulou;Email:yingyulou@vip.qq.com