上次说到的是现在所有的必要的信息都是存储在AST这一个数据结构当中,下一个阶段就是从AST中生成的LLVM IR在代码的生成的过程当中,我们可以去使用LLVM的API,通过内建的API可以生成预定义格式的LLVM IR
我们需要在BaseAST类和其的子类当中去添加Codegen函数,因为我们需要去生成其的中间表示形式的代码,其中在有些子类当中的Codegen函数的返回值是LLVM Value对象,它表示了静态单赋值(SSA)对象,SSA其实就是静态单赋值形式,这种形式是一种特殊形式的中间码,每个变量仅仅被赋值一次,在SSA中间表示中,可以保证每个被使用的变量都有唯一的定义,即SSA能带来精确的使用–定义关系
我们需要在全局作用域当中去声明如下的静态变量
//Module_Ob模块包含了代码中的所有函数和变量
static Module *Module_Ob;
//Builder对象帮助生成LLVM IR 并且记录程序的当前点,以插入LLVM指令,以及Builder对象有创建新指令的函数
static IRBuilder<> Builder(MyGlobalContext);
//Named_Values map对象是记录当前作用域中的所有已定义的值,充当符号表的功能,对于TOY语言来说这个map会包含函数参数信息
static std::map<std::string,Value *>Named_Values;
下面我们来看下数值变量的Codegen函数的定义
Value *NumericAST::Codegen()
{
//在LLVM IR当中,整数常量由ConstantInt类表示,它的值由APInt类持有
return ConstantInt::get(Type::getInt32Ty(MyGlobalContext), numeric_val);
}
为变量表达式生成代码的函数定义如下所示
Value * VariableAST::Codegen()
{
Value * V = Named_Values[Var_Name];
return V ? V:0;
}
二元表达式的Codegen()函数,如果这里生成了多个addtmp变量,LLVM会自动的为每一个addtmp添加递增的、唯一的数值后缀加以区分
Value *BinaryAST::Codegen(){
Value * L = LHS->Codegen();
Value * R = RHS->Codegen();
if(L==0||R==0) return 0;
//atoi (表示 ascii to integer)是把字符串转换成整型数的一个函数
switch (atoi(Bin_Operator.c_str())) {
case '+':
return Builder.CreateAdd(L, R,"addtmp");
case '-':
return Builder.CreateSub(L,R, "subtmp");
case '*':
return Builder.CreateMul(L,R, "multmp");
case '/':
return Builder.CreateUDiv(L,R, "divtmp");
default:
return 0;
}
}
解析函数调用的时候,会递归的对其传递的参数调用Codegen()函数,最后再去创建LLVM调用指令
Value *FunctionCallAST::Codegen()
{
Function * CalleeF = Module_Ob->getFunction(Function_Callee);
//函数参数
std::vector<Value *>ArgsV;
//遍历函数参数
for (unsigned i = 0, e = Function_Arguments.size(); i != e; ++i) {
ArgsV.push_back(Function_Arguments[i]->Codegen());
}
return Builder.CreateCall(CalleeF,ArgsV,"calltmp");
}
函数声明的Codegen(),需要注意的是该函数的返回值类型是“Function*”而不是“Value*”。“函数原型”描述的是函数的对外接口(而不是某表达式计算出的值),返回代码生成过程中与之相对应的LLVM Function自然也合情合理
Function *FunctionDeclAST::Codegen()
{
std::vector<Type*>Integers(Arguments.size(),Type::getInt32Ty(MyGlobalContext));
//我们定义函数的参数为int类型,和常数一样,LLVM中的类型对象也是单例,应该用“get”而不是“new”来获取
//因此第一行创建了一个包含“N”个LLVM int的vector。随后,FunctionType::get方法以这“N”个int为参数类型、以单个int为返回值类型,创建出一个参数个数不可变
FunctionType * FT = FunctionType::get(Type::getInt32Ty(MyGlobalContext), Integers, false);
/*
下面实际上创建的是与该函数原型相对应的函数。其中包含了类型、链接方式和函数名等信息,还指定了该函数待插入的模块。“ExternalLinkage”表示该函数可能定义于当前模块之外,且/或可以被当前模块之外的函数调用。Name是用户指定的函数名:如上述代码中的调用所示,既然将函数定义在“TheModule”内,函数名自然也注册在“TheModule”的符号表内。
*/
Function *F = Function::Create(FT, Function::ExternalLinkage, Func_Name, Module_Ob);
/**
在这里主要是在处理名称冲突时,Module的符号表与Function的符号表类似:在模块中添加新函数时,如果发现函数名与符号表中现有的名称重复,新函数会被默默地重命名。被重命名过就代表我的名字不一致,那么就要删除相应的函数
*/
/**我们重定义函数可以允许对同一个函数进行多次extern声明,前提是所有声明中的函数原型保持一致(由于只有一种参数类型,我们只需要检查参数的个数是否匹配即可)。所以我们下面判断名字的时候下面还要去判断参数
*/
if(F->getName() != Func_Name) {
//调用eraseFunctionParent)将刚刚创建的函数对象删除
F->eraseFromParent();
//然后调用getFunction获取与函数名相对应的函数对象
F = Module_Ob->getFunction(Func_Name);
//如果函数体就说明定义过,就返回0,直接拒绝
if(!F->empty()) return 0;
//判断还是你的参数如果不等于我们之前构造的数量,那么也直接返回
if(F->arg_size() != Arguments.size()) return 0;
}
unsigned Idx = 0;
//遍历函数原型的所有参数,为这些LLVM Argument对象逐一设置参数名,并将这些参数注册倒NamedValues映射表内
for(Function::arg_iterator Arg_It = F->arg_begin(); Idx != Arguments.size(); ++Arg_It, ++Idx) {
Arg_It->setName(Arguments[Idx]);
//将函数参数放入到Named_Values
Named_Values[Arguments[Idx]] = Arg_It;
}
return F;
}
函数定义的Codegen()
Function *FunctionDefnAST::Codegen() {
/*生成函数原型(Proto)的代码并进行校验。与此同时,需要清空NamedValues映射表,确保其中不会残留之前代码生成过程中的产生的内容。函数原型的代码生成完毕后,一个现成的LLVM Function对象就到手了。*/
Named_Values.clear();
//生成函数定义的IR代码,
Function *TheFunction = Func_Decl->Codegen();
if(TheFunction == 0) return 0;
/*
模块(Module),函数(Function),代码块(BasicBlock),指令(Instruction)
模块包含了函数,函数又包含了代码块,后者又是由指令组成。除了模块以外,所有结构都是从值产生而来的。
*/
//新建了一个名为“entry”的基本块对象,稍后该对象将被插入TheFunction
BasicBlock *BB = BasicBlock::Create(MyGlobalContext,"entry", TheFunction);
//插入BasicBlock 代码块,告诉Builder,后续的新指令应该插至刚刚新建的基本块的末尾处。
//builder.setInsertPoint 会告知 LLVM 引擎接下来将指令插入何处,简而言之,它指定创建的指令应该附加到指定块的末尾
Builder.SetInsertPoint(BB);
//现在该开始设置Builder对象了。LLVM基本块是用于定义控制流图(Control Flow Graph)的重要部件。当前我们还不涉及到控制流,所以所有的函数都只有一个基本块
//函数体产生
if(Value *RetVal = Body->Codegen()) {
Builder.CreateRet(RetVal);
//验证生成的代码,检查一致性。
//简单检查一个函数的错误,在调试一个pass时有用。如果没有错误,函数返回false。如果发现错误,则向OS(如果非空)写入描述错误的消息,并返回true。
verifyFunction(*TheFunction);
return TheFunction;
}
//Error reading body, remove function.
//错误读取函数体,所以删除函数
TheFunction->eraseFromParent();
return 0;
}
然后之后我们就可以去生成可执行文件,去分析我们自己写的语言,分析的语句如下所示
def foo(x,y)
x + y * 16
结果如下所示
; ModuleID = 'my compiler'
source_filename = "my compiler"
define i32 @foo(i32 %x, i32 %y) {
entry:
%multmp = mul i32 %y, 16
%addtmp = add i32 %x, %multmp
ret i32 %addtmp
}