当我浏览LLVM文档时,
在某些方面我有些意思并不完全明白。
如果您知道,请提供反馈。
[前端]源代码 - > Tokeniser(令牌流) - >解析器(解析器行动)
有人可以解释Tokeniser到底做了什么吗? Parser也做了什么? 可能提供一个使这更清晰的例子。
[Backend] IR - >优化器(IR) - >代码生成
我不明白这一步应该做什么优化。
我知道各种前端和后端之间存在一些差异。 但我要问的是一般情况。
感谢您的任何反馈。
答案 0 :(得分:3)
所有这些都是标准的编译器术语(参见Aho,Lam,Sethi,Ullmann:Compilers)。
编译器的输入是包含字符串的文件。
在:
int main(int argc, char* argv[])
{
printf("blimey\n");
// this is a comment
return 0;
}
标记生成器的输出是一系列标记,其中标记被定义为不包含空格的字符串(您还可以设计一个更好的标记类型,如果数字看起来像实数或整数,则可以跟踪它们,字符序列是保留字还是不...)。有时,语言的保留字被唯一标识符(通常是整数)替换,表示数字的字符序列将转换为实际数字,因此您可能会得到:
OUT:
"int" "main" "(" "int" "argc" "," "char", "*", "argv" "[" "]" "{"
"printf" "(" "blimey\n" ")" ";" "return" "0" ";" "}"
请注意,“{”之后的空行已消失,因此是注释。您不需要注释来构建解析树。我也用字符串“blimey \ n”作弊,它需要注释为常量(引用)字符串。那是标记器。分离标记化/解析的关键在于标记化可以使用比解析器更快的有限状态自动机来完成。
解析器根据上面的序列构建一棵树。在这里显示解析器的输出很困难,因为我们没有解析语言的语法。所以我采用一种更简单的语言:
'foo + 3 * bar'的标记符输出:
"foo" "+" "3" "*" "bar"
算术表达式的语言有很多语法,大多数语法解析器都会构建这个树:
+
/ \
"foo" *
/ \
3 "bar"
AST: “抽象语法树与解析树有所不同,因为形式的表面区别,不重要的翻译,不会出现在语法树中”(Compilers,Sec 2.5)
假设您编写了表达式“foo +(3 * bar)”。解析器仍然会构建上面的树,因为括号不是必需的。但如果你写了“(foo + 3)* bar”,你会得到一棵不同的树:
*
/ \
+ "bar"
/ \
"foo" 3
没有括号!抽象树结构编码所有内容。实现方式:如果您使用现代面向对象语言编写编译器,抽象语法树将由类层次结构表示。在C中,对于标记的每个节点类型(带有整数),你都有一个'struct'。
可以从树生成可执行代码(或任何您需要的代码),但这很乏味。因此,在许多情况下(Pascal的p代码,C的其他三个地址代码中间表示......),树被转换(展平)为中间表示(IR)。目的是在设计良好的IR上更容易进行优化。您可能想要进行数百万次优化:
use algebraic identities x+0 => x, x*1 => x etc
drop unused variables
simplify control flow
reorder assignments
...
优化器大部分时间都保留IR ...即,函数IR - > IR,但有一些聪明的优化器可以转换IR1 - > IR2(更改表示),使某些属性显式(使用'benton''moggi''monad'搜索将使您开始)。