5

我正在构建一个从类似 C 的语言到堆栈机器的玩具编译器,我现在需要弄清楚如何处理函数并阻止局部变量。抽象地思考一下,看起来我在频谱的两端有两个选择:1)为每个变量预处理和预分配堆栈空间,2)向 VM 添加特殊指令以遍历堆栈。

为每个变量预处理和预分配堆栈空间

这样做的好处是可以提前为我提供变量的所有地址,因此我不必非常聪明或向 VM 添加任何额外的指令来遍历堆栈。缺点是它可能非常浪费,因为从不执行但声明一大堆变量的条件代码将占用大量不必要的空间。例如,

a : t1 = value;
if (test) {
  b : t2; c : t3; d : t4; ...;
}

在上面的代码中,即使test总是错误的,我仍然会为条件分支中的所有这些变量分配空间。

向 VM 添加特殊指令以遍历堆栈

我能想到的另一种方法是为每个变量声明生成代码,然后添加一些特殊的 VM 指令以在运行时找出这些变量的地址。这解决了浪费堆栈空间的问题,但增加了计算开销,我可以通过一些缓存方法来解决。

那么正确的方法是什么,还有另一种我认为更好的方法吗?

4

1 回答 1

7

堆栈机的想法是它在操作数堆栈上进行计算。这并不意味着所有内容都必须存储在堆栈中。这是一个普遍的误解。通常,您的本地变量/块范围访问映射到注册操作。

.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(存储字段)和 ldfld(加载字段)的原型是相当简单的。稍后,您可以优化汇编或运行时以将基于符号命名的引用编译为整数引用,尤其是在不需要运行时按名称查找变量的情况下。但是,对于反射 API,您将需要实现额外的元数据以将数字或指针地址交叉引用回其原始名称。

PS:如果我误解了你的问题,或者你想讨论更多,请评论。

于 2014-07-19T04:04:09.883 回答