循环结构通常需要先初始化一个归纳变量,然后之后就要对这个变量做更新(增加或减少其数值),以及一个表示循环结束的终止条件,我们的for循环可以定义如下所示,其中i=1表示的是条件、i
def printstar(n x)
for i = 1, i < n, 1.0 in
x + 1
还是一样的我们先去配置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,
FOR_TOKEN, //for循环的支持
IN_TOKEN //in的支持
};
然后对于实现词法分析逻辑的函数get_token当中增加了两个判断
if(Identifier_string=="for")
{
return FOR_TOKEN;
}
if(Identifier_string=="in")
{
return IN_TOKEN;
}
然后我们再去定义其的AST的类结构
//为for循环去定义AST
class ExprForAST : public BaseAST {
//定义变量标识
std::string Var_Name;
//这里是定义的Start、End、Step、Body的结构,可能是表达式也可能是数字也可能是字母等,所以用BaseAST去表示
BaseAST *Start, *End, *Step, *Body;
public:
ExprForAST(const std::string &varname, BaseAST *start, BaseAST *end,
BaseAST *step, BaseAST *body)
: Var_Name(varname), Start(start), End(end), Step(step), Body(body) {}
Value *Codegen() override;
};
然后我们再去为循环结构去定义其解析的函数
Value *ExprForAST::Codegen() {
//生成start条件的中间代码生成
Value *StartVal = Start->Codegen();
if (StartVal == 0)
return 0;
//获得函数实体
Function *TheFunction = Builder.GetInsertBlock()->getParent();
//获得插入的点
BasicBlock *PreheaderBB = Builder.GetInsertBlock();
//这里就是将loop:给插入到TheFunction当中
BasicBlock *LoopBB =
BasicBlock::Create(MyGlobalContext, "loop", TheFunction);
//然后去创建br指令跳转也就是直接是在entry下面的br label %loop指令
Builder.CreateBr(LoopBB);
//设置插入点为loop块
Builder.SetInsertPoint(LoopBB);
//创建phi节点
PHINode *Variable = Builder.CreatePHI(Type::getInt32Ty(MyGlobalContext),
2, Var_Name.c_str());
//生成phi节点可以从两个基本块当中得到变量i的两个值
//其中一个是%entry块表示在循环初始化的时候对i的赋值是1
//而%loop块对i的值进行更新,完成循环的一次迭代
/*
%i = phi i32 [ 1, %entry ], [ %nextvar, %loop ]
*/
//preheaderBB块代表是entry块
Variable->addIncoming(StartVal, PreheaderBB);
//根据Var_Name也就是i获取旧的值
Value *OldVal = Named_Values[Var_Name];
//然后将新的值给穿进去也就是Variable其实就是PHInode节点
Named_Values[Var_Name] = Variable;
//然后判断函数体生成是否失败,失败就返回0
if (Body->Codegen() == 0)
return 0;
//然后去判断步长
Value *StepVal;
if (Step) {
//如果步长存在就直接去调用IR代码生成的函数
StepVal = Step->Codegen();
if (StepVal == 0)
return 0;
} else {
//步长不存在就去使用默认的步长
StepVal = ConstantInt::get(Type::getInt32Ty(MyGlobalContext), 1);
}
//创建一个加法指令
Value *NextVar = Builder.CreateAdd(Variable, StepVal, "nextvar");
//结束代码的生成
Value *EndCond = End->Codegen();
if (EndCond == 0)
return EndCond;
//创建一个比较指令
EndCond = Builder.CreateICmpNE(
EndCond, ConstantInt::get(Type::getInt32Ty(MyGlobalContext), 0), "loopcond");
//获取插入点
BasicBlock *LoopEndBB = Builder.GetInsertBlock();
//插入到函数当中
BasicBlock *AfterBB =
BasicBlock::Create(MyGlobalContext, "afterloop", TheFunction);
//创建跳转指令
Builder.CreateCondBr(EndCond, LoopBB, AfterBB);
//设置插入点,之后的插入就都在这个afterloop块里面的
Builder.SetInsertPoint(AfterBB);
//再去设置phi节点的另外一个label,其实就是loop块,LoopEndBB代表的就是loop块
Variable->addIncoming(NextVar, LoopEndBB);
//旧的值存在的就再去重新设置为1
if (OldVal)
Named_Values[Var_Name] = OldVal;
else
//删除这个节点像上面这样只是删除单个节点
Named_Values.erase(Var_Name);
return Constant::getNullValue(Type::getInt32Ty(MyGlobalContext));
}
然后我们还需要在Base_Parser当中去根据token类型去调用相应的解析函数
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();
//根据for
case FOR_TOKEN:return For_parser();
}
}
这里的Base_Parser函数会在Driver函数中被间接调用我们可以看下Driver函数,这个函数可以理解为是驱动函数,就去去做对整个程序进行分析的
static void Driver(){
while (1) {
switch (Current_token) {
case EOF_TOKEN:return;
case ';':next_token();break;
case DEF_TOKEN:HandleDefn();break;
default:HandleTopExpression(); break;
}
}
}
对上面的代码段总的分析过程其实就是遇到def先去执行HandleDefn函数,然后这个函数会去执行func_defn_parser函数
static void HandleDefn(){
if(FunctionDefnAST * F = func_defn_parser())
{
if(Function * LF = F->Codegen())
{
}
}
else
{
next_token();
}
}
关于func_defn_parser函数其实就是分析函数实现的函数,然后就会去调用expression_parser函数
static FunctionDefnAST *func_defn_parser(){
next_token();
//创建函数声明的AST
FunctionDeclAST * Decl = func_decl_parser();
//判断Decl
if(Decl==0) return 0;
//如果函数实现存在的话
if(BaseAST * Body = expression_parser())
{
//分析结束返回这个,然后就返回
return new FunctionDefnAST(Decl,Body);
}
return 0;
}
关于表达式分析函数,先对左边进行分析,其实就是去调用Base_Parser方法进行分析,这个函数上面提到过,然后就可以去找到相应的token进行分析了
//解析表达式的函数
static BaseAST * expression_parser(){
//进行左边部分的解析
BaseAST * LHS = Base_Parser();
if(!LHS) return 0;
return binary_op_parser(0,LHS);
}
没有经过优化的分析结果如下所示
我们这里可以看到其生成了两条add指令,其实就是因为在使用BinaryAST::Codegen()函数的时候会再去生成
//二元表达式的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)是把字符串转换成整型数的一个函数
//LLVM指令遵循严格的约束:例如,add指令的Left、Right操作数必须同属一个类型,结果的类型则必须与操作数的类型相容
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");
//表示运算符如果是小于的话
case '<':
//生成一个比较指令,icmp”指令根据其两个整数、整数向量、指针或指针向量操作数的比较返回布尔值或布尔值向量。
//ult表示少于
//相当于会生成这样的指令%cmptmp = icmp ult i32 %x, %addtmp
L=Builder.CreateICmpULT(L,R,"cmptmp");
//生成一个bool指令,“zext”指令接受要转换的值,以及要转换的类型。这两种类型都必须是整数类型,或者是相同数量的整数的向量。该值的位大小必须小于目标类型ty2的位大小。
// 相当于会生成这样的指令 %booltmp = zext i1 %cmptmp to i32
return Builder.CreateZExt(L,Type::getInt32Ty(MyGlobalContext),"booltmp");
default:
break;
}
如果我们使用了pass进行优化得到的指令就可以了