我有一个带有funcDef,funcCall,literals等节点的AST。我正在编写我的编译器的代码生成器部分,为x86_64生成程序集。我的问题是生成AST的代码的“正确”(行业标准)方式是什么:它是多次传递,例如计算需要多少局部变量以便使用该值来递减堆栈指针?另外,复杂的AST如funcCall和一个参数是另一个funcCall等解析成ASM?
将AST转换为非常简单的IR(SSA?)是明智的吗?另外,由于我对代码生成理论一无所知,有没有专注于此的好书?
谢谢!
答案 0 :(得分:-1)
"适当" /生成机器代码的标准方法是使用优化编译器,通过内部表示(通常是SSA形式)进行转换,并且非常难以进行各种优化。
解释器更容易编写,如果写得好,可以提供比低效/天真生成的asm更好的性能,所以没有标准"简单"生成asm的方法,因为没人想要。 (我想,除了作为一个教你自己编程的爱好项目。)
自己编写一个好的编译器将需要几十年的工作。见Why are there so few C compilers?,尤其是Basile Starynkevitch的回答。即使对于简单的"也是如此。 CPU比现代x86-64行为更复杂;优化掉冗余工作,决定何时内联函数等等并不容易。
但是对现代x86-64的优化范围从简单(无序执行并非非常关心指令排序)到神秘(例如inc eax
保存代码大小与{{1} },but on some CPUs in some cases it's slower;多个uops或部分标志停顿)。或者,3组件LEA具有比英特尔Sandybridge系列CPU上的2个独立LEA / ADD指令更高的延迟(但可能更高的吞吐量)。 另请参阅Agner Fog's optimization guides中的the x86 tag wiki和其他效果优化链接。如果您要尝试进行优化,那么唯一值得担心的是这一点。 。有效地完成大量冗余工作并不是那么有用。
要为新语言编写一个编译器,您可以编写一个生成LLVM-IR的前端并将其提供给LLVM库,以便优化并发出asm或机器代码。 (你可以为GIMPLE做同样的事情,使用gcc优化中/后端而不是LLVM)。作为奖励,您的编译器有望在LLVM或gcc支持的大多数CPU架构上运行,而不仅仅是x86。
例如,请参阅此Implementing a Language with LLVM教程。
天真地将每个表达式的每个部分音译成asm指令将产生缓慢而臃肿的asm 。也许与您从add eax,1
获得的内容类似,但它确实在表达式中进行了优化,因此clang -O0
仍然与10 + x + 4
编译相同。 x + 14
还会增加在每个语句之后将所有内容溢出到内存中的负担,因此您可以在任何断点处使用调试器修改内存中的C变量。 (这是clang -O0
意味着的一部分:保证一致的调试,以及在优化上花费最少的精力进行快速编译。)
一个不关心它的天真编译器可能会记录哪些值存在于哪个寄存器中,并在需要新寄存器时溢出旧值。如果您没有及时了解需要哪些值,那么这可能很容易变得非常糟糕,而是希望将这些值留在寄存器中。
如果您不关心生成的asm的质量,那么请确保做任何方便。
TinyCC is a one-pass C compiler。当它发出一个函数序言时,它还没有决定它需要保留多少字节的堆栈空间。 (它返回并在它到达函数末尾时填充。)请参阅Tiny C Compiler's generated code emits extra (unnecessary?) NOPs and JMPs以获得有趣的结果:-O0
填充其函数序言的一个版本。
IDK它在内部做了什么,但可能是因为它遇到了新的变量声明,它将它们固定到它预留的堆栈帧的末尾(因此不会将偏移从nop
更改为任何现有变量,因为它可能已经使用它们发射了。)
TCC是开源的,编写为小/简单(并且编译速度快),不来创建好的asm,所以你可能想查看它的源代码来看看它是什么确实