我或多或少对编译器如何将 AST 转换为机器代码感到困惑。我的假设是一些编译器遍历树并用相应的汇编指令替换每个节点。我正在尝试编写小型编译器,但我仍然需要获得有关这部分是如何完成的更多信息。
例如
// code representation
int a = 1+2;
// AST representation
=
/ \
int +
| / \
a 1 2
# final assembly after traversing the tree
# Allocate space for variable 'a'
ALLOC a, 4 # Assuming 'int' is 4 bytes
# Evaluate the expression '1 + 2'
LOAD r1, 1 # Load constant 1 into register r1
LOAD r2, 2 # Load constant 2 into register r2
ADD r3, r1, r2 # Add values in registers r1 and r2, store result in r3
# Store the result in variable 'a'
STORE a, r3
我知道我所描述的可能效率低下。然而,将 AST 转换为 IR 语言更加令人困惑。
谢谢!
这并不重要,但我的 AST 会省略/删除
INT
部分,作为声明单独处理,留下 a=1×2 用于代码生成。
最简单的做法是假设一个底层堆栈机,然后对寄存器机进行优化和其他调整。
对于最简单的 a=1+2,我们将从 = 开始,然后评估 lhs 的地址,然后评估 rhs 的值,然后间接存储。
因此,生成的代码将类似于pushaddr a,push 2 push 2,add istore。
您可以看到 lhs 的计算方式与 rhs 不同,因此这是传递给评估器/代码生成器的参数。这个a[i]=b[j]生成a[i]的地址和b[j]的值,然后istore..
变得更复杂,我们还将提供除了 lhs/rhs 之外的仅副作用选项,因此 a+b;例如,不会生成任何实际代码。
Furtee,我们可以通过询问地址或变量来延迟 a= 的 cofe 的生成。这样,我们就可以延迟 Pushaddr a 的代码生成,而是将一个值返回给变量 a 的调用者 (=)。结果代码是序列,然后是push 1,push 2,add,pop/store a - 直接存储到a而不是存储到a的地址。
接下来,我们可以切换到寄存器机器,并将稍后解析的真实或虚假寄存器分配给 resl 寄存器。我们不假设 + 的操作数是堆栈之一,而是查看 1、2 求值返回的值,并假设它们现在位于寄存器中。
我们可以通过这种方式继续渐进式改进。
与 if/while 一起使用的另一种评估方法是评估分支到目标的条件表达式。因此,不是为 i 形成 0 1 结果 < 10, then branching on true/false we might branch more directly on a comparison of i with 10.
我们可以应用很多像这样的小改进。