感觉自己这个辣鸡,深入怕是难度太大,还是先简单的介绍下上层的使用吧,这里的讲解还是基于之前所讲的来的,我们主要进行某些方法的改动以及另一个AST类的添加和解析
首先我们如果要去添加对if、else、then的判断,那么我们的token的解析肯定是要有的,所以我们需要在枚举类型当中去添加它们相应的token,这样方便判断
enum Token_Type {
EOF_TOKEN = 0,
NUMERIC_TOKEN,
IDENTIFIER_TOKEN,
LEFT_PARAN_TOKEN,
RIGHT_PARAN_TOKEN,
DEF_TOKEN,
COMMA_TOKEN,
IF_TOKEN,
THEN_TOKEN,
ELSE_TOKEN
};
然后我们在获取文件的时候去分析相应的字符的时候肯定是需要返回相应的token的,也就是说我们可以在get_token当中进行操作,总的判断如下所示
//获取token的函数
static int get_token(){
static int LastChar = ' ';
//判断是不是空格
while (isspace(LastChar)) {
LastChar = fgetc(file);
}
//先判断是不是字符
if(isalpha(LastChar))
{
Identifier_string = LastChar;
while(isalnum((LastChar=fgetc(file))))
Identifier_string+=LastChar;
if(Identifier_string=="def")
{
return DEF_TOKEN;
}
//判断if字符串,如果是的话就返回IF_TOKEN
if(Identifier_string=="if")
{
return IF_TOKEN;
}
//判断then字符串,如果是的话就返回THEN_TOKEN
if(Identifier_string=="then")
{
return THEN_TOKEN;
}
//判断ELSE_TOKEN
if(Identifier_string=="else")
{
return ELSE_TOKEN;
}
return IDENTIFIER_TOKEN;
}
//判断是不是数字
if(isdigit(LastChar))
{
std::string NumStr;
do{
NumStr+=LastChar;
LastChar = fgetc(file);
}while(isdigit(LastChar));
//strtod是C语言及C++中的重要函数,功能是将字符串转换成浮点数,表头文件是#include <stdlib.h>,相关函数有atoi,atol,strtod,strtol。
Numeric_Val = strtod(NumStr.c_str(), 0);
return NUMERIC_TOKEN;
}
// if(LastChar == '(')
// {
// LastChar = fgetc(file);
// return LEFT_PARAN_TOKEN;
// }
// if(LastChar == ')')
// {
// LastChar = fgetc(file);
// return RIGHT_PARAN_TOKEN;
// }
// if(LastChar==',')
// {
// LastChar = fgetc(file);
// return COMMA_TOKEN;
// }
if(LastChar=='#')
{
LastChar = fgetc(file);
while (LastChar!=EOF && LastChar !='\n' &&LastChar != '\r');
if(LastChar!=EOF)
return get_token();
}
if(LastChar==EOF)
return EOF_TOKEN;
int ThisChar = LastChar;
LastChar = fgetc(file);
return ThisChar;
}
有了token,由于我们是要判断的是if/then/else,所以我们就需要去添加其相应的AST类进行构造出这样的结构,方便生成后面的IR
//定义if表达式的AST节点
class ExprIfAST : public BaseAST {
//关于Cond、Then、Else的AST节点
BaseAST *Cond, *Then, *Else;
public:
//构造方法
ExprIfAST(BaseAST *cond, BaseAST *then, BaseAST *else_st)
: Cond(cond), Then(then), Else(else_st) {}
//需要去重写Codegen构造函数
Value *Codegen() override;
};
由于我们需要去根据token去构造AST结构,所以我们需要一个对if/then/else结构的解析逻辑
static BaseAST *If_parser() {
//读取下一个字符,因为当前的Current_token是If_token,所以需要读入下一个
next_token();
//解析if的表达式,返回值类型是BinaryAST
BaseAST *Cond = expression_parser();
if (!Cond)
return 0;
if (Current_token != THEN_TOKEN)
return 0;
//读取下一个字符
next_token();
//解析then的表达式
BaseAST *Then = expression_parser();
if (Then == 0)
return 0;
//判断当前的TOKEN是不是ELSE_TOKEN
if (Current_token != ELSE_TOKEN)
return 0;
//读取下一个字符
next_token();
BaseAST *Else = expression_parser();
if (!Else)
return 0;
return new ExprIfAST(Cond, Then, Else);
}
既然我们定义了if_parser()方法,那么我们肯定是需要去使用的,所以我们需要在Base_Parser中使用
static BaseAST * Base_Parser(){
switch (Current_token) {
default:return 0;
case IDENTIFIER_TOKEN: return identifier_parser();
case NUMERIC_TOKEN: return numeric_parser();
case '(':return paran_passer();
//根据IF_TOKEN去返回,说明当前Current_token就是IF_TOKEN
case IF_TOKEN:return If_parser();
}
}
下面主要是对ExprIfAST结构的代码生成的方法,下面均已给出了解析
Value *ExprIfAST::Codegen() {
//进行条件的代码
Value *CondV = Cond->Codegen();
if (CondV == 0)
return 0;
//这里生成的就是icmp ne指令就比如说icmp ne i32 %booltmp, 0
CondV = Builder.CreateICmpNE(
CondV, ConstantInt::get(Type::getInt32Ty(MyGlobalContext), 0), "ifcond");
//在结构良好的LLVM IR中,每条指令都嵌入在一个基本块中。您可以从getParent()获取BasicBlock。getParent()将始终在LLVM IR层次结构中向前走一步,即。您可以从BasicBlock中获得父函数,从函数中得到模块。
//返回当前插入点
/**
此代码创建与if/then/else语句相关的基本块,并直接对应于上面示例中的块,下面就是去获取正在构建的当前函数对象。它通过请求构建器获得当前的BasicBlock,并请求该块获得它的“父块”(它当前嵌入的函数)。
*/
Function *TheFunction = Builder.GetInsertBlock()->getParent();
/*
一旦它有了这个,它就会创建三个块。注意,它将“TheFunction”传递给“then”块的构造函数。这导致构造函数自动将新块插入到指定函数的末尾。创建了另外两个块,但还没有插入到函数中。
*/
//创建then的代码块
BasicBlock *ThenBB =
BasicBlock::Create(MyGlobalContext, "then", TheFunction);
//创建else的代码块
BasicBlock *ElseBB = BasicBlock::Create(MyGlobalContext, "else");
//如果指定了父参数,那么基本块将自动插入到函数的末尾(如果InsertBefore是0),或者在指定的基本块之前。
//ifcont:块
BasicBlock *MergeBB = BasicBlock::Create(MyGlobalContext, "ifcont");
/**
一旦这些块被创建,我们就可以发出在它们之间进行选择的条件分支。注意,创建新块不会隐式地影响IRBuilder,因此它仍然插入到条件进入的块中。还要注意,它正在创建一个分支到“then”块和“else”块,即使“else”块还没有插入到函数中。这都没问题:这是LLVM支持转发引用的标准方式。
*/
//创建一个有条件的“br Cond, TrueDest, falseDest的指令。
//br i1 %ifcond, label %then, label %else
Builder.CreateCondBr(CondV, ThenBB, ElseBB);
/**
在插入条件分支之后,我们将构建器移动到“then”块中。严格地说,这个调用将插入点移动到指定块的末尾。但是,由于“then”块是空的,它也从在块的开始插入开始,其实就是去设置插入点
*/
Builder.SetInsertPoint(ThenBB);
//生成Then的IR表示,一旦设置了插入点,我们就递归地将AST中的“then”进行调用Codegen
//所以我们这里需要先去生成不影响我们phi的设置
Value *ThenV = Then->Codegen();
if (ThenV == 0)
return 0;
//为了完成“then”块,我们为merge块创建一个无条件的分支,我们为merge block创建一个无条件的分支
//就是br label %ifcont这条指令
Builder.CreateBr(MergeBB);
/*
下面这一行非常微妙,但非常重要。基本的问题是,当我们在合并块中创建Phi节点时,我们需要设置块/值对,以指示Phi如何工作
重要的是,Phi节点期望在CFG中为块的每个前身其实就是
then: ; preds = %entry
br label %ifcont。
这李的then就表示是前身
CFG就是控制流图(Conttol flow graph)是用在编译器的一种抽象的数据结构,它是一个过程或程序的抽象表现,由编译器在内部维护
为什么,当我们把它设置到上面的5行时,我们得到当前块了吗?
问题是,“Then” expression本身可能会改变构建器所释放的块,例如,它包含一个嵌套的“if/ Then /else”表达式。因为递归地调用codegen()可以任意改变当前块的概念,所以我们需要为设置Phi节点的代码获取最新的值。
*/
//Then的codegen可以改变当前的块,在PHI中更新ThenBB
ThenBB = Builder.GetInsertBlock();
/*
else”块的代码生成基本上与“then”块的代码生成相同。唯一显著的区别是第一行,它将“else”块添加到函数中。还记得之前创建了“else”块,但没有添加到函数中。既然已经发出了“then”和“else”块,我们就可以用合并代码结束
*/
TheFunction->getBasicBlockList().push_back(ElseBB);
Builder.SetInsertPoint(ElseBB);
Value *ElseV = Else->Codegen();
if (ElseV == 0)
return 0;
//创建br指令,也就是br label %ifcont
Builder.CreateBr(MergeBB);
ElseBB = Builder.GetInsertBlock();
/**
ifcont: ; preds = %else, %then
%iftmp = phi i32 [ 1, %then ], [ %addtmp3, %else ]
ret i32 %iftmp
}
*/
//插入MergeBB块:
TheFunction->getBasicBlockList().push_back(MergeBB);
Builder.SetInsertPoint(MergeBB);
//分支语句:需要phi merge节点
PHINode *PN =
Builder.CreatePHI(Type::getInt32Ty(MyGlobalContext), 2, "iftmp");
//这里就是生产 %iftmp = phi i32 [ 1, %then ], [ %addtmp3, %else ]
PN->addIncoming(ThenV, ThenBB);
PN->addIncoming(ElseV, ElseBB);
return PN;
}
然后我们来看下生成的程序对一些代码段的分析
def fib(x,y,z)
if x < x+y*z then
1
else
fib(x-1)+fib(x-2);
从下面我们可以看到指令是分先后顺序的,先去计算了y*z,然后将这条指令赋值给了%multmp,然后我们再去进行addtmp的指令的生成,每一个then:else:都是一个块,隶属于一个函数
这句代码做的就是插入这个then块的意思
BasicBlock *ThenBB = BasicBlock::Create(MyGlobalContext, “then”, TheFunction);
这里解析器会识别if/then/else结构以及根据条件真假执行的相应语句,将数据存储在AST当中,以此构建AST,之后代码生成器会把AST转换成LLVM IR,条件语句随之生成,无论条件为真还是假,都会生成IR,而具体执行哪一个相应的分支,则取决于执行时条件变量的状态