我正在构建一个从C语言到堆栈计算机的玩具编译器,我需要弄清楚如何处理函数和阻止局部变量。从抽象的角度思考它看起来我在频谱的两端有两个选项:1)为每个变量预处理和预分配堆栈空间,2)向VM添加特殊指令以遍历堆栈。
这样做的好处是可以提前为我提供变量的所有地址,因此我不必非常聪明,或者向VM添加任何额外的指令以便遍历堆栈。缺点是它可能非常浪费,因为从不执行但是声明一大堆变量的条件代码将占用大量不必要的空间。例如,
a : t1 = value;
if (test) {
b : t2; c : t3; d : t4; ...;
}
在上面的代码中,即使test
始终为false,我仍然会为条件分支中的所有变量分配空间。
我能提出的另一种方法是为每个变量声明生成代码,然后添加一些特殊的VM指令以在运行时找出这些变量的地址。这解决了浪费堆栈空间的问题,但随后增加了一些我可以通过一些缓存方法解决的计算开销。
那么正确的方法是什么?是否有其他方法我没有想到更好?
答案 0 :(得分:5)
堆栈计算机的想法是它在操作数堆栈上进行计算。它并不意味着必须将所有内容存储在堆栈中。这是一个普遍的误解。通常,您的本地vaiables / block作用域访问映射到寄存器操作。
.NET CLR和Java都有指令来存储和获取"本地"变量以及其他类型的变量。我建议你效仿,因为你不想走堆栈进行简单的变量访问。这是非常低效的。加载/存储变量应该是有效的,比如寄存器。大多数堆栈机器仍然具有随机存取存储。
在CLR中,我们还在每个方法的开头预先分配所有局部变量。您预先分配的变量可能是显式高级变量和编译器生成的临时变量的混合。但没有什么说他们必须在堆叠上。在我研究过的虚拟机上,我们在快速访问区域中实现了它们,如关联数组或类似矢量的结构。我建议您使用ildasm来反汇编.NET方法,并记下如何声明和处理局部变量。
示例:
total = apples + oranges
地图:
ldloc 'apples' # load a local onto stack
ldloc 'oranges' # load a local onto stack
add # add 2 operands on stack
stloc 'total' # store local from stack
在之前的回答中,我解释了基于堆栈的机器并与注册机器进行了比较。我希望其中有一些有用的信息。 https://stackoverflow.com/a/24301283/257090
使用简单的Dictionary或HashTable实现stfld(store field)和ldfld(load field)的原型是相当简单的。稍后,您可以优化汇编或运行时以编译对整数引用的基于符号命名的引用,尤其是在不需要按名称运行时查找变量的情况下。但是,对于反射API,您需要实现其他元数据以将数字或指针地址交叉引用回其原始名称。
PS:如果我误解了您的问题,或者您想再讨论一下,请发表评论。