让我们来看看什么是一遍编译器,并尝试实现一遍编译器。
一遍编译器在分析过程中直接发出汇编(或二进制代码),而不创建中间表示,如AST。这是一种罕见的技术,在计算机内存匮乏的时代使用。这限制了可能的语言功能和生成的代码的质量。但是,这种技术产生的快速编译器让比尔·盖茨羡慕不已。这是一种非常罕见的技术,在计算机内存匮乏的时候使用。这既限制了可能的语言功能,也限制了所生成代码的质量。但是,这种技术产生的快速编译器让比尔·盖茨羡慕不已。
Turbo Pascal的快速编译速度通常被认为是Turbo Pascal成功的决定性因素。
比尔·盖茨认为Turbo Pascal的成功是“非常私人的”,他不能理解为什么(微软的)东西这么慢。他会把格雷格·惠顿(微软语言编程总监)找来,对他大喊大叫半个小时。他无法理解为什么卡恩能够击败像微软这样的老牌竞争对手。
让我们创建一个简单的一遍编译器。不是针对整个编程语言,而是仅针对简单的算术表达式,如下所示:
我们将以x86-64为目标,并将分别使用flex和bison来生成我们的词法分析器和解析器,我使用了Wikipedia bison示例作为灵感。
[\r\n\t]*{继续;/*跳过空白。*/}[0-9]+{sscanf(yytext,";%d";,&;yylval->;value);return TOKEN_NUMBER;}";*";{RETURN TOKEN_STAR;}";+";{RETURN TOKEN_PLUS;}";(";{RETURN TOKEN_LPAREN;}";)";{RETURN TOKEN_RPAREN;{继续;/*跳过意外字符。*/}。
现在,我们的表达式语言的语法。让我们暂时省略语义动作。
输入:exprexpr:";number";|expr";+";expr|expr";*";expr|";(";expr";)";
它们都是左结合的:2+3+4表示((2+3)+4)。这对于加法和乘法来说不是一个非常重要的性质,因为运算本身是结合的。
我们需要在添加到语法中的每个语义操作中直接发出一些汇编代码。如果需要,我们可以在以后更有趣一些,但现在,让我们将emit函数定义为printf的别名。
因此,我们会将汇编指令直接输出到标准输出通道,如果需要,我们可以通过管道将其输出到文件。现在,我们可以将其输出到语义操作上。每次我们遇到一个数字时,我们都会将其推入堆栈:每个语义操作触发的顺序都很重要。因此,当我们遇到像加法这样的操作时,两个内部表达式已经发出。因此,我们可以预期它们的值位于堆栈的顶部。我们所能做的就是将值弹出到某个。
|expr";+";expr{emit(";pop%%rax\n";);emit(";pop%%rbx\n";);emit(";add%%rbx,%%rax\n";);}。
我们需要对寄存器使用双百分号,因为这是printf格式的字符串。
除了累加器寄存器%rax是mul指令的隐式参数外,我们对乘法也执行同样的操作。
|expr";*";expr{emit(";pop%%rax\n";);emit(";pop%%rbx\n";);emit(";mul%%rbx\n";);emit(";push%%rax\n";);}
当我们遇到括号时怎么办呢?我们什么也不做,因为内在的表达式已经发出了。
现在,我们可以在给定算术表达式的情况下生成程序集片段,但是,一堆推和弹出并不能构成一个完整的程序集清单,我们需要一个main(假设我们链接到libc)或a_start。
作为最后的语义操作,我们弹出唯一的期望值,并将其作为程序的退出代码从Main返回。
.global mainmain:PUSH$4 PUSH$2 PUSH$10 POP%RAX POP%RBX muL%RBX PUSH%RAX POP%RAX POP%RBX ADD%RBX ADD%RBX,%RAX PUSH%RAX PUSH$3 PUSH$5 PUSH$1 POP%RAX POP%RBX ADD%RBX PUSH%RAX POP%RAX POP%RBX muL%RBX PUP%RAX POP%RAX POP%RBX ADD%RBX,%rax PUP%rax POP%rax POP%rax。
如果我们将它管道到一个名为test.s的文件中,将其汇编并执行,程序将不会产生任何输出,但是,我们可以检查退出代码:
我可以想象通过将变量的值压入堆栈并在哈希表…中记住它们的堆栈偏移量来实现变量。与函数的签名…类似的内容。看起来一遍编译器需要很多全局可变状态才能工作。
你怎么优化这样的东西呢?优化编译器会提前把我们的表达式常量折叠成一个数字。尽管如此,在一次通过的情况下,您只能获得非常短视的代码视图。
完整的代码,包括词法分析器、解析器和Makefile,可以在GitHub上找到作为要点。
你知道吗,我正在写一本关于编译器的书?不幸的是,书中描述的编译器不是一遍的。我知道,对吧?但它是一个生成ARM汇编代码的两遍编译器。这本书甚至教会了你足够的汇编语言编程。示例是用打字稿写的,但它可以在任何语言中遵循,所以请查看它!