- 论坛徽章:
- 3
|
第三步骤 - 中间代码生成
llvm 是一个完善的编译器框架, 但这并不意味着, 你写了语法解析后就没事情做了
从 AST 生成 llvm 能使用的中间过程依然是一个充满挑战性的工作. llvm 是一个中间化的汇编语言, 意味着, 事实上你的代码生成器是在生成 llvm 汇编语言 , 所以依然是一个完备的编译器. 你如果觉得乐意, 完全可以不生成 llvm 汇编代码, 直接生成具体机器平台的汇编语言. llvm 的汇编语言依然是一个比较底层的语言. 和一般的汇编器不一样的是, llvm 的汇编器能对这个汇编语言进行 "优化" . 而其他平台的汇编器则需要编译器自己生成优化的结果. 注意, llvm 执行的优化是比较底层的, 专注于机器平台的, 比如 乘法指令转 SSE 指令, 指令重排序这样的优化. 语言层面的,如常量折叠, 死代码清除, 尾递归优化等 , 依然需要编译器作者进行优化. 所以千万不要认为有了 llvm 写编译器就是非常简单的事情了.
llvm 的好处就是可以专注于语言层面的实现和优化, 而具体机器指令层面的优化交给 llvm 来做, 分工协作.
生成 llvm 的代码有两种形式:
第一种是手动生成. 适用于实现编译器所使用的语言没有 llvm 可用的库的时候使用. 编译器生成文本格式的 llvm 汇编代码然后调用 llvm 汇编器生成本机代码.
第二种是使用 llvm 的 C++ 库. llvm 提供了比较丰富的 类库用来简化 llvm 代码的生成. 我的QBASIC编译器就采用这种形式.
生成 llvm 代码的步骤非常简单, 为每个语法树节点调用 codegen 虚函数即可. 因为是虚函数, 所以调用基类的 codegen 会自动调用对应类的 codegen .
我在这里举一个加法表达式的 codegen
- llvm::Value * AddExprAST::Codegen( BasicBlocks * inserpoint)
- {
- IRBuilder<> builder(insertpoint); // 一个 LLVM 辅助库, 用来生成 llvm 代码
- /// 因为加法这样的表达式是递归定义的, 所以 ...
- LHS = this->leftnode->Codegen(insertpoint); // 就像这里, 递归调用子节点, 获得的就是子节点生成的加法指令的结果的 llvm 寄存器的引用.
- RHS = this->rightnode->Codegen(insertpoint);
- // 生成 llvm 加法指令. 并返回保存加法结果的寄存器的引用.
- return builder.CreateAdd( LHS, RHS); // 返回结果是 llvm::Value * , 也就是一个 寄存器的引用.
- }
复制代码 注意 llvm 的 builder 构造一个加法(减乘除等都一样的啦) , 会生成(一系列, 有可能是一系列)的寄存器操作, 然后是加法指令, 然后是返回该加法指令的结果寄存器的引用
如果后续使用到了这个引用, 就告诉 llvm 的代码生成器, 要安排好这个寄存器的使用, 重新进入下一次运算操作哦 ~
llvm 实际上就是一个 寄存器分配器. 如果可用的寄存器不足, llvm 会自动生成压栈指令, 将一些暂时用不到的数据移出寄存器.
所以, 可以把 llvm 看成有无限个寄存器的机器. 这样像例子代码中的递归调用, 层层返回指令结果寄存器引用, 是可以的. llvm 生成机器代码的时候自动安排好寄存器的使用.
|
|