Kaleidoscope: LLVM中间代码(IR)生成

原文:
`Kaleidoscope: Code generation to LLVM IR

`__http://llvm.org/docs/tutorial/LangImpl3.html

介绍""

欢迎来到“用LLVM实现语言”教程的第三章。这一章将介绍你如何从AST(第二章我们已经建好的)转换到LLVM中间码。你将接触到一点LLVM的工作原理,你会发现使用LLVM相当容易——生成LLVM中间码要比建立词法分析器和解析器容易得多。

注意:这部分的代码要求LLVM版本大于等于2.2。2.1以及更早的版本不兼容这部分代码。还有,你引入的库文件应当和你使用的LLVM版本相同:如果你在使用官方的release版,你可以在llvm.org releases page 上找到相应的库文件。llvm.org releases page: http://llvm.org/releases/

中间码生成配置

为了生成中间码,我们需要代码上一些简单的配置。首先我们要在每一个AST类中添加虚函数(codegen):

  1. /// ExprAST - Base class for all expression nodes.
  2. class ExprAST {
  3. public:
  4. virtual ~ExprAST() {}
  5. virtual Value *Codegen() = 0;
  6. };
  7.  
  8. /// NumberExprAST - Expression class for numeric literals like "1.0".
  9. class NumberExprAST : public ExprAST {
  10. double Val;
  11. public:
  12. NumberExprAST(double val) : Val(val) {}
  13. virtual Value *Codegen();
  14. };

``Codegen()`运行后会生成中间码以及其它运行时需要的信息,这些信息以LLVM value对象形式返回。"Value"类用来表示LLVM中的“静态单赋值寄存器(Static Single Assignment register)"或者“SSA value”。SSA值的特点是,它在经过相关指令计算出,并不能被改变(除非程序从头来过)。换句话说,SSA是个常量。你想了解SSA更多的话,请阅读 [静态单赋值`](#id9) —一旦你了解它,你会发现这相当简单。.. 静态单赋值: http://

值得说的是,除了添加Codegen()虚函数外,使用访客模式也是一种很好的方法。重申一遍,这个教程并不是停留在使用优雅的方法实践软件工程上;对于我们的目的来说,使用虚函数是最简单的方法。

第二件事情要注意的是,我们在解析器中使用的“Error”方法将用来报告错误(比如,使用了一个未声明的变量。)

  1. Value *ErrorV(const char *Str) { Error(Str); return 0; }
  2.  
  3. static Module *TheModule;
  4. static IRBuilder<> Builder(getGlobalContext());
  5. static std::map<std::string, Value*> NamedValues;

静态变量会在整个代码生成阶段中被中使用,TheModule便是一个用来储存这些函数以及全局变量的LLVM结构体。在某种程度上,这就是LLVM中间码所构造的顶层结构。

Builder是一个辅助对象,用来为生成LLVM指令提供方便。它是IRBuilder类的实例,用来标记当前位置以插入新的指令。

NameValues键值表保存了当前的代码范围内定义的值,和记录并表示这些值的LLVM对象(换句话说,这就是当前代码的符号表)。在这种形式下,唯一可以参考的是函数参数(In this form of Kaleidoscope, the only things that can be referenced are function parameters. )。因此,当生成函数体代码时,函数参数会被记录到这个表里去。

当这些搭建完毕后,我们离为每句表达式生成代码更近了一步。我们还需要做的是配置好Builder,但现在,假设我们已经将Builder已经配置完毕,开始用它来生成代码。

表达式代码生成

从表达式生成LLVM代码相当直接:4种表达式节点总共不到45行带注释的代码。我们依次将这四种节点列出来:

  1. Value *NumberExprAST::Codegen() {
  2. return ConstantFP::get(getGlobalContext(), APFloat(Val));
  3. }

在LLVM中间码里,数字常量用ConstantFP类来表示,它将数字储存在内部的APFloat中(APFloat可以存储任意精度的浮点数)。这段代码主要用来创建和返回一个ConstantFP。注意在LLVM中间码中,所有的常量都是唯一并共享的。所以,我们用了LLVM中的API"foo::get(…)"而不是“new foo(..)”或者“foo::Create(..)”。

  1. Value *VariableExprAST::Codegen() {
  2. // Look this variable up in the function.
  3. Value *V = NamedValues[Name];
  4. return V ? V : ErrorV("Unknown variable name");
  5. }

引用变量也很简单,在Kaleidoscope的最初版本中,我们假定变量已经在某处被声明,且值是有效的。因为只有已经在NamedValues被声明的才是函数参数,这段代码检查变量名是否在NamedValues中(假如不在,说明引用了一个未知变量)并返回它的值。在接下来的章节里,我们会在符号表中添加对循环变量和本地变量的支持。

  1. Value *BinaryExprAST::Codegen() {
  2. Value *L = LHS->Codegen();
  3. Value *R = RHS->Codegen();
  4. if (L == 0 || R == 0) return 0;
  5.  
  6. switch (Op) {
  7. case '+': return Builder.CreateFAdd(L, R, "addtmp");
  8. case '-': return Builder.CreateFSub(L, R, "subtmp");
  9. case '*': return Builder.CreateFMul(L, R, "multmp");
  10. case '<':
  11. L = Builder.CreateFCmpULT(L, R, "cmptmp");
  12. // Convert bool 0/1 to double 0.0 or 1.0
  13. return Builder.CreateUIToFP(L, Type::getDoubleTy(getGlobalContext()),
  14. "booltmp");
  15. default: return ErrorV("invalid binary operator");
  16. }
  17. }

原文:

https://github.com/moevis/Kaleidoscope-LLVM-tutorial-zh-cn/blob/master/source/chapter-3.rst