Variables 为堆栈计算机编译局部变量

Variables 为堆栈计算机编译局部变量,variables,compiler-construction,vm-implementation,stack-machine,Variables,Compiler Construction,Vm Implementation,Stack Machine,我正在构建一个从类C语言到堆栈机的玩具编译器,现在我需要弄清楚如何处理函数和块局部变量。从抽象的角度来看,我有两个选择:1)为每个变量预处理和预分配堆栈空间,2)向VM添加特殊指令以遍历堆栈 为每个变量预处理和预分配堆栈空间 这样做的好处是提前给我变量的所有地址,所以我不必非常聪明,也不必为遍历堆栈向VM添加任何额外的指令。缺点是它可能非常浪费,因为从不执行但声明一大堆变量的条件代码将占用大量不必要的空间。比如说, a : t1 = value; if (test) { b : t2; c

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

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

a : t1 = value;
if (test) {
  b : t2; c : t3; d : t4; ...;
}
在上面的代码中,即使
test
始终为false,我仍将为条件分支中的所有这些变量分配空间

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


那么,正确的方法是什么?有没有我没有想到的更好的方法?

堆栈机器的概念是它在操作数堆栈上进行计算。这并不意味着所有内容都必须存储在堆栈上。这是一种常见的误解。通常,本地vaiables/块作用域访问映射到寄存器操作

.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
在前面的回答中,我解释了基于堆栈的机器,并与注册机器进行了比较。我希望里面有一些有用的信息

用一个简单的字典或哈希表实现stfld(存储字段)和ldfld(加载字段)的原型相当简单。稍后,您可以优化汇编或运行时以将基于符号命名的引用编译为整数引用,特别是在不需要运行时按名称查找变量的情况下。但是,对于反射API,您需要实现额外的元数据,以便将数字或指针地址交叉引用回它们的原始名称


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

我也有类似的想法。您能否详细说明在每个方法开始时预先分配所有局部变量的含义。更具体地说,如何处理死代码?如果没有消除死代码,死代码部分中的局部变量也是预先分配的。让我先回答WRT死代码:不要对死代码做任何事情,这不是你的问题(作为VM/解释器)。这由编译器/程序员决定。预分配使实现变得简单,但也允许编译器灵活地映射其高级变量或临时变量(编译器为未映射到显式变量声明的对象生成的变量)。因此,是的,这意味着你不必为你的中间代码使用高级变量名(例如,当源代码中存在两个同名变量时)。在这个例子中,我是三个:vm实现者、编译器编写者、程序员,所以我试图平衡一些交叉关注点。无论如何,谢谢。这非常有用。在编译方法时,您可以决定有多少高级变量存储在局部变量中,还可以决定分配多少临时变量(用于将值保留在快速寄存器内存中)。在方法调用期间保存/恢复的某种随机访问堆栈或结构上分配它们。您可以使用操作码(如storelocal或loadlocal)将全局值、字段等中的值检索到本地值,并在表达式完成后保存这些值。本地人纯粹是象征性的,JIT可能会将他们映射到注册表。@davidk01-请随时给我发电子邮件,我的联系人信息在我的个人资料中,我很乐意回答问题,帮助您使用虚拟机。最好学习的虚拟机之一是CLR。使用VisualStudio研究它如何编译高级代码。一旦你学习了IL,你就会对VM的工作方式有一个感觉,它会给你自己的想法。但这并不意味着你必须以同样的方式工作。有许多方法可以围绕相同的基本思想设计虚拟机。