LLVM_IR

LLVM IR 中间代码生成

类的设计

value

Value

  • 主要作用:llvm中的基类

Type

  • 主要作用:llvm所有类型的基类
  • 子类:
    • IntType: 整数类型,包括i32和i1
    • VoidType: 主要用于占位
    • ArrayType: 数组类型
    • PointerType: 指针类型

Module

  • 主要作用:一个Module代表一个文件,本次实验中只需要一个Module即可。

BasicBlock

  • 主要作用:基本块,部分指令的合集,一个基本块中不应该有跳转和返回指令,一个基本块中的所有指令要么全都执行要么全都不执行。

Instruction

  • 主要作用:llvm指令的基类。
  • 子类:
    • BinaryInst
    • UnaryInst
    • AllocInst
    • BranchInst
    • GetElementPtrInst
    • RetInst
    • LoadInst
    • StoreInst

更多详细的可以参考llvm的官方文档

递归下降分析

该过程就是根据语法分析生成的语法树,递归下降分析,生成llvm中间代码并生成一个新的llvm的数据结构,用于之后的中间代码优化。
个人递归下降分析的时候是用visit函数,递归分析的时候基本是按照如下模式:

1
2
if (node instance of MyClass)
visit((MyClass) node);

值得注意的是,对于expressionstatementvisit函数可能略有不同:

1
2
3
private Value visit(Expression exp) {}

private void visit(Statement stmt) {}

原因在于对statement的处理只需要生成一条或数条指令即可,但是对于expression的处理应当返回一个结果,用于外层语句的调用。
比如return a + 2;,对于a + 2的访问应当返回一个value结果,用于生成一条RetInst语句。

难点

变量声明和存储

局部变量声明是需要生成AllocInst,结果是一个该变量类型的对应指针类型。
变量的取值是需要通过LoadInst,从之前AllocInst的结果存储的指针里取出数值。
变量的存储是将一个新的值存到之前的指针中。
对一个变量的操作都离不开它的指针,因此我们需要一个新的符号表,将一个Identifier对应到一个指针类型的Value

变常量的初始化

全局变量和常量的初始化一定是编译时可求的值,一般是常数值或者是常量,对于常数值可以单独处理,在编译过程中直接求值;对于常量在访问符号表时获得其常数值然后求值。
局部变量的初始化通过StoreInst即可解决。

数组的处理

  • 数组的处理涉及到ArrayTypePointerType的转换,建议多看标准的llvm工具链如何处理数组然后学习。
  • 数组的取址需要用到GetElementPtr指令,使用方法非常灵活。在处理二维数组时,建议调用两次该指令,每次降一个维度。可以参考这里