在上一章的旅程中,我们已经实现了if语句和while语句的代码生成器分派函数,而在这一章的旅程中,我们将要实现变量存取相关的代码生成器分派函数。要讨论变量存取,我们就需要对变量存取时的栈内存情况具有充分的了解。所以接下来,就让我们先来认识一下:在变量存取过程中所涉及到的栈内存情况吧。
首先,我们需要明确的是:任何变量的存取行为,一定发生于一个正在被调用的函数中。也就是说,在实现变量存取的过程中,我们必须同时考虑对局部变量和对全局变量的存取,且局部变量将隐藏全局变量。那么,局部变量和全局变量在栈的哪儿?我们又该如何找到这些位置呢?下图便向我们展示了这两个问题的答案:
假设代码中存在全局变量:
int a;
int b[3];
int c;
且函数F存在形参:
int a, int b, int c
以及局部变量:
int d;
int e[3];
int f;
则我们可以得到符号表:
__GLOBAL__: (a: 0), (b: 1), (c: 5)
F: (a: 0), (b: 1), (c: 2), (d: 3), (e: 4), (f: 8)
在某次函数F的调用过程中,栈内存结构如下:
+-------+-----+-----+-----+-----+-----+-----+ ... +-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ ...
| 索引值 | 0 | 1 | 2 | 3 | 4 | 5 | ... | N - 9 | N - 8 | N - 7 | N - 6 | N - 5 | N - 4 | N - 3 | N - 2 | N - 1 | N | N + 1 | ...
+-------+-----+-----+-----+-----+-----+-----+ ... +-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ ...
| 值 | ? | 2 | ? | ? | ? | ? | ... | ? | ? | ? | ? | N - 8 | ? | ? | ? | ? | ? | IP | ...
+-------+-----+-----+-----+-----+-----+-----+ ... +-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ ...
^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
| | | | | | | | | | | | | | | |
a b b[0] b[1] b[2] c f e[0] e[1] e[2] e d c b a BP
这幅图对我们而言非常重要,其是这一章与接下来两章的旅程中都会用到的示意图。有了这幅图,我们就可以解释很多在前面走过的旅程中无法解释的事情了。请看:
接下来,就让我们利用上述示意图与结论,来实现变量存取相关的代码生成器分派函数吧。
Var节点用于进行一次变量访问。通过上一节的讨论我们知道,对于一个变量而言,我们需要同时关注两个问题,这两个问题都会影响到变量访问的实现:
对于第一个问题,我们的答案显而易见:如果在当前函数的符号表中能够查到这个变量名,就说明这个变量是一个局部变量;否则,这个变量就是一个全局变量。而对于第二个问题,我们的答案也不难想到:如果Var节点含有两个子节点,则这个变量是一个数组变量;否则,这个变量就不是一个数组变量。
有了上述思考作为铺垫,我们就可以开始讨论Var节点的分派函数的实现了。请看:
vector<string> CodeGenerator::__generateVarCode(AST *root) const
{
vector<string> codeList;
if (__symbolTable.at(__nowFuncName).count(root->subList[0]->tokenStr))
{
codeList.push_back("LDC " + to_string(
__symbolTable.at(__nowFuncName).at(root->subList[0]->tokenStr).first));
codeList.push_back("LD");
}
else
{
codeList.push_back("LDC " + to_string(
__symbolTable.at("__GLOBAL__").at(root->subList[0]->tokenStr).first));
codeList.push_back("ABSLD");
}
if (root->subList.size() == 2)
{
vector<string> exprCodeList = __generateExprCode(root->subList[1]);
codeList.push_back("PUSH");
codeList.insert(codeList.end(), exprCodeList.begin(), exprCodeList.end());
codeList.push_back("ADD");
codeList.push_back("POP");
codeList.push_back("ABSLD");
}
return codeList;
}
首先,正如上文中所说,我们通过在当前函数的符号表中查找这个变量名,来确定这个变量是一个局部变量,还是一个全局变量,同时,我们也可以得到这个变量在符号表中的编号。所以,我们首先通过一条LDC指令,将变量的编号装载入AX中;然后,根据变量是一个局部变量还是一个全局变量,我们分别使用一条LD或ABSLD指令,将变量的值装载入AX中。此时,AX中的值可能有两种情况:
所以接下来,如果我们发现当前的Var节点含有两个子节点,我们就需要继续对AX进行索引值偏移操作。首先,我们为Var节点的第二子节点生成代码,显然,只要这段代码运行完毕,一个索引值偏移量就会被装载入AX中,然后,我们只需要再进行一次加法,就能得到真正的索引值了。但请不要着急,我们首先需要将当前的AX压栈,保护起来,以作为加法的第一操作数;然后,我们才能装载计算第二操作数的代码;此时,第一操作数位于栈顶,而第二操作数位于AX,我们就可以执行ADD指令了;这条指令执行完毕后,AX中就装载了偏移后的索引值;此时,不要忘了再执行一次POP指令,以弹出先前我们压入的加法的第一操作数;最后,我们执行一次ABSLD指令,将数组变量的值装载入AX中。
赋值操作的实现思路和变量访问的实现思路是几乎一致的,只是用于变量访问的LD和ABSLD指令需要换为SAV和ABSSAV指令。请看:
vector<string> CodeGenerator::__generateAssignCode(AST *root) const
{
vector<string> codeList {"PUSH"};
if (__symbolTable.at(__nowFuncName).count(root->subList[0]->tokenStr))
{
codeList.push_back("LDC " + to_string(
__symbolTable.at(__nowFuncName).at(root->subList[0]->tokenStr).first));
if (root->subList.size() == 1)
{
codeList.push_back("SAV");
}
else
{
vector<string> exprCodeList = __generateExprCode(root->subList[1]);
codeList.push_back("LD");
codeList.push_back("PUSH");
codeList.insert(codeList.end(), exprCodeList.begin(), exprCodeList.end());
codeList.push_back("ADD");
codeList.push_back("POP");
codeList.push_back("ABSSAV");
}
}
else
{
codeList.push_back("LDC " + to_string(
__symbolTable.at("__GLOBAL__").at(root->subList[0]->tokenStr).first));
if (root->subList.size() == 1)
{
codeList.push_back("ABSSAV");
}
else
{
vector<string> exprCodeList = __generateExprCode(root->subList[1]);
codeList.push_back("ABSLD");
codeList.push_back("PUSH");
codeList.insert(codeList.end(), exprCodeList.begin(), exprCodeList.end());
codeList.push_back("ADD");
codeList.push_back("POP");
codeList.push_back("ABSSAV");
}
}
codeList.push_back("POP");
return codeList;
}
观察__generateAssignCode函数的调用时机可知:这个函数的root,实际上也是一个Var节点,而在调用这个函数时,AX中已经装载了需要被赋值的值。所以我们首先要做的就是:将AX压栈,以保护这个值;接下来,和上一节中一样,我们查找符号表,以确定这个变量是一个局部变量,还是一个全局变量,并通过一条LDC指令装载变量的编号;然后,如果这个变量不是一个数组变量,则我们直接通过一条SAV或ABSSAV指令,将栈顶值存储进这个变量中,完成代码生成;而如果这个变量是一个数组变量,则同样与上一节类似,我们通过一条LD或ABSLD指令,装载数组的第一个元素在栈中的索引值;接着,我们通过“PUSH,计算右操作数,ADD,POP,ABSSAV”这一系列的指令,完成索引值的偏移与变量的存储动作。最后,我们不能忘记:我们还需要将最开始压入栈中的那个被赋值的值弹出。
至此,变量存取的代码生成器分派函数的实现就全部完成了。接下来,我们将要迎来整个代码生成器,乃至整个编译器实现之旅的重头戏:函数调用的实现。请看下一章:《函数调用的代码生成器分派函数的实现》。
编译器实现之旅——第十四章 变量存取的代码生成器分派函数的实现
原文:https://www.cnblogs.com/yingyulou/p/14416593.html