下面就简单的介绍下,我们去构造 | (逻辑或运算符)作为样例,TOY语言当中的 | 运算符是这样使用的,其中前面的binary代表的是标识符,代表的是二元运算符的表示,然后后面就是| 运算符,再就是(LHS RHS) 根据这两个参数进行选择
def binary | 5 (LHS RHS)
if LHS then
1
else if RHS then
1
else
0;
这段代码的意思就是只要LHS或者是RHS任何一个不为0,那么就返回1,如果LHS和RHS都是null,则返回0
首先我们需要去做的就是先去判断出token,这样的话我们就要去为二元运算符增加enum类型,如果遇到binary关键字就返回相应的枚举类型
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,
IN_TOKEN,
//二元运算符的标识符
BINARY_TOKEN
};
相应的对于分析token的函数get_token()当中就增加上相应的二元运算符的识别
//返回二元运算符
if (Identifier_string=="binary") {
return BINARY_TOKEN;
}
以及由于我们是基于def进行操作的所有其实我们去定义二元运算符的AST结构的时候,只需要对函数定义的AST结构进行修改就可以了
class FunctionDeclAST
{
//函数名
std::string Func_Name;
//参数列表
std::vector<std::string> Arguments;
//是否是运算符
bool isOperator;
//优先级
unsigned Precedence;
public:
FunctionDeclAST(const std::string &name,const std::vector<std::string> &args,bool isoperator = false,unsigned prec = 0):Func_Name(name),Arguments(args),isOperator(isoperator),Precedence(prec){}
//获取一元运算符的名字
bool isUnaryOp() const {return isOperator && Arguments.size()==1;}
//获取二元运算符的名字
bool isBinaryOp() const {return isOperator && Arguments.size();}
//获取操作符的名字
char getOperatorName() const {
assert(isUnaryOp() || isBinaryOp());
return Func_Name[Func_Name.size()-1];
}
//返回优先级的函数
unsigned getBinaryPrecedence() const {
return Precedence;
}
Function *Codegen();
};
然后我们再去修改函数声明的解析器也就是func_decl_parser函数
static FunctionDeclAST *func_decl_parser()
{
//定义的是函数的名字
std::string FnName;
//种类标识码
unsigned Kind = 0;
//运算符的优先级
unsigned BinaryPrecedence = 30;
//判断当前的token
switch (Current_token) {
default:
return 0;
//表示是标识符
case IDENTIFIER_TOKEN:
//设置标识符
FnName = Identifier_string;
//设置种类码
Kind = 0;
//读取下一个token
next_token();
break;
//表示是二元标识符
case BINARY_TOKEN:
//读取下一个token
next_token();
//判断当前的token在不在ASCII码当中
if (!isascii(Current_token))
return 0;
//设置函数名
FnName = "binary";
FnName += (char)Current_token;
//设置种类码
Kind = 2;
//读取下一个token
next_token();
if (Current_token == NUMERIC_TOKEN) {
if (Numeric_Val < 1 || Numeric_Val > 100)
return 0;
//设置优先级
BinaryPrecedence = (unsigned)Numeric_Val;
next_token();
}
break;
}
//判断当前运算符是不是(,如果是的话就返回0
if (Current_token != '(')
return 0;
std::vector<std::string> ArgNames;
//判断是不是IDENTIFIER_TOKEN,如果是,就放入容器中
while (next_token() == IDENTIFIER_TOKEN)
ArgNames.push_back(Identifier_string);
//判断标识符是不是)如果不是了
if (Current_token != ')')
return 0;
//接着去判断下一个token
next_token();
//判断kind和参数的个数是不是一致的
if (Kind && ArgNames.size() != Kind)
return 0;
return new FunctionDeclAST(FnName, ArgNames, Kind != 0, BinaryPrecedence);
}
之后我们再去修改二元AST的Codegen()函数也就是BinaryAST函数,我们其实在里面就是加了下面的内容
//返回指令所处的函数
/**
LLVM Module的符号表中查找函数名。如前文所述,LLVM Module是个容器,待处理的函数全都在里面。只要保证各函数的名字与用户指定的函数名一致,我们就可以利用LLVM的符号表替我们完成函数名的解析
*/
Function *F = Module_Ob->getFunction(std::string("binary") + Bin_Operator);
assert(F && "binary operator not found!");
Value *Ops[] = { L, R };
//传入参数和函数以及指令名字,创建一条LLVM call指令
return Builder.CreateCall(F, Ops, "binop");
由于我们可以在我们的语言当中设置了优先级,所以我们在函数声明需要去解析优先级问题,因为可能里面使用到了 | 运算,修改就是增加了下面的两段代码
//根据函数声明的AST结构里面的BinaryOp设置
if (Func_Decl->isBinaryOp()) {
Operator_Precedence[Func_Decl->getOperatorName()] = Func_Decl->getBinaryPrecedence();
}
if(Func_Decl->isBinaryOp())
Operator_Precedence.erase(Func_Decl->getOperatorName());
修改是在FunctionDefnAST::Codegen()函数中进行修改的,完整的代码如下所示
//函数定义
Function *FunctionDefnAST::Codegen() {
/*生成函数原型(Proto)的代码并进行校验。与此同时,需要清空NamedValues映射表,确保其中不会残留之前代码生成过程中的产生的内容。函数原型的代码生成完毕后,一个现成的LLVM Function对象就到手了。*/
Named_Values.clear();
//生成函数定义的IR代码,
Function *TheFunction = Func_Decl->Codegen();
if(TheFunction == 0) return 0;
//根据函数声明的AST结构里面的BinaryOp设置
if (Func_Decl->isBinaryOp()) {
Operator_Precedence[Func_Decl->getOperatorName()] = Func_Decl->getBinaryPrecedence();
}
/*
模块(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()) {
//创建一个ret指令,如果我们增加了if-then-else的话,这里返回的RetVal就是Phi类型的
/**
ifcont: ; preds = %else, %then
%iftmp = phi i32 [ 1, %then ], [ %addtmp, %else ]
ret i32 %iftmp
}
*/
Builder.CreateRet(RetVal);
//验证生成的代码,检查一致性。
//简单检查一个函数的错误,在调试一个pass时有用。如果没有错误,函数返回false。如果发现错误,则向OS(如果非空)写入描述错误的消息,并返回true。
verifyFunction(*TheFunction);
//使用PassManager的run方法就可以进行优化
//安排Pass进行执行,跟踪是否有任何传递修改函数,如果有,返回true。
Global_FP->run(*TheFunction);
return TheFunction;
}
//Error reading body, remove function.
//错误读取函数体,所以删除函数
TheFunction->eraseFromParent();
if(Func_Decl->isBinaryOp())
Operator_Precedence.erase(Func_Decl->getOperatorName());
return 0;
}
接下来我们就去对最上面提到的代码进行生成IR指令,没有经过优化的结果如下所示
; ModuleID = 'my compiler'
source_filename = "my compiler"
define i32 @"binary|"(i32 %LHS, i32 %RHS) {
entry:
%ifcond = icmp eq i32 %LHS, 0
br i1 %ifcond, label %else, label %then
then: ; preds = %entry
br label %ifcont4
else: ; preds = %entry
%ifcond1 = icmp eq i32 %RHS, 0
br i1 %ifcond1, label %else3, label %then2
then2: ; preds = %else
br label %ifcont
else3: ; preds = %else
br label %ifcont
ifcont: ; preds = %else3, %then2
%iftmp = phi i32 [ 1, %then2 ], [ 0, %else3 ]
br label %ifcont4
ifcont4: ; preds = %ifcont, %then
%iftmp5 = phi i32 [ 1, %then ], [ %iftmp, %ifcont ]
ret i32 %iftmp5
}
当我们增加了下面一句代码就可以进行优化,其实就是利用了LLVM当中的Pass
My_FP.add(createCFGSimplificationPass());
优化过后的生成的结果如下所示
; ModuleID = 'my compiler'
source_filename = "my compiler"
define i32 @"binary|"(i32 %LHS, i32 %RHS) {
entry:
%ifcond = icmp eq i32 %LHS, 0
%ifcond1 = icmp eq i32 %RHS, 0
%. = select i1 %ifcond1, i32 0, i32 1
%iftmp5 = select i1 %ifcond, i32 %., i32 1
ret i32 %iftmp5
}
总结来说其实就是要去根据相应的binary的标识符然后识别出BINARY_TOKEN,然后由于我们这里定义的结构是def binary | (LHS RHS)以这样的形式定义的,而我们的函数则是以def foo(x,y)定义的,所以这里我们对函数声明的AST结构还是需要去重新定义,在这个结构当中去定义了getOperatorName()函数获取二元运算符的名字用于设置优先级,以及获取优先级的方法getBinaryPrecedence()
主要就是在
Operator_Precedence[Func_Decl->getOperatorName()]=Func_Decl->getBinaryPrecedence();中使用进行设置,之后在使用到 | 运算符的时候就会进行判断运算符的优先级