编译器实现之旅——第九章 实现虚拟机

在这一章的旅程中,我们来实现虚拟机。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的过程。所以,在虚拟机的实现中,我们只需要简单的为指令集中的每个指令都分配好其行为即可。每条指令的行为都十分简单,这里也就不详细讨论了。

至此,编译器后端中的语义分析器和虚拟机,我们就都实现完成了。接下来,我们就要动身前往编译器的最后一站——代码生成器了。请看下一章:《实现代码生成器前的准备》。

posted @ 2021-02-19 16:38  樱雨楼  阅读(287)  评论(0编辑  收藏  举报