【实验代码及报告地址:Gitee传送门】
不擅长写报告昂,很多地方能省全省了。
助力来年编译原理加大难度!(hhh)
我根据我的实验报告重置了攻略。
将Mini-C源程序,手工翻译或用LLVM API翻译成LLVM IR(一种中间语言),并完成语法检查。
结合给出的例子,认识并熟悉LLVM IR的语法。
主要涉及了align/alloca/store/load/call/icmp/br等。【可以自行结合代码展开说说】
主要是根据Mini-C语言翻译,有两个地方对我造成了困扰:【相信每个人困扰的点不一样,自圆其说即可】
熟悉并使用LLVM IR API,主要涉及了llvm::IRBuilderBase类的API,还有一些Constant、BasicBlock、Type类的API。【可以自行结合代码展开说说】
难点:【相信每个人的难点不一样,自圆其说即可】
builder->CreateCall(callgetfunc);
f->getBasicBlockList().push_back(MergeBB);
,因为我以为到结尾了可以直接顺序执行下去,犯了和关卡1相似的错误。没有写这一句时,报错会一直提示then:错误,导致我一直没定位到真正的错误位置。【因为实在很长,所以实验报告里我没有写过程,只写了难点。不建议大家这样做,因为每个人的难点都是不一样的】
【如果没话写,可以着重写一下“错误判断”部分】
builder->CreateStore(Value, alloca);
)builder->CreateAdd(left, right, "add");
)core dump
?nullptr
输入到一个函数中了,举个简单的例子(摘自NAssignment::codegen函数):Value* retValue; if(false) retValue=nullptr; return retValue;
NDef
。std::cout
调试,定位出错的位置。cat tokens.txt
调试,通过阅读生成的中间代码,判断语义分析的哪一步出了问题,或者根本没解析出来。vim ./task1case/1.in
修改样例调试。当你觉得是某个逻辑写得很有问题,但是样例太长了,中间代码眼花缭乱,就直接修改样例。p->parser();
输出语法树。将main.cpp中的p->parser();注释掉,再编译运行样例,能看到每个样例完整的语法树。建议顺着语法树的解析顺序,逐个填充codegen,每填充完一个,就去编译运行试试。使用头歌命令行调试
打开头歌,打开命令行,进入对应的代码仓库。
cd /data/workspace/myshixun/llvmexp3
检查一下当前目录下的文件。
ls
应该能看到judge_taskx.sh
文件,以第三关为例,看一下judge_task1.sh
的内容。
具体内容就不贴了,总之是这样用的:
./minic
文件,它是一个可执行的文件,用来生成中间代码LLVM IR。make
中间代码LLVM IR:类似于
br i1 %shandian7, label %then8, label %ifcont10
这种东西。
task1case
。tokens.txt
。./minic ./task1case/0.in > tokens.txt
cat tokens.txt
看一下输出(你在cpp里写的std::cout、输出的报错Error信息也会在这个txt里)。echo "你要给程序输入的值(就是stdin)" > tmp
lli tokens.txt < tmp
正好十个。
注:写第三题的时候可以乱来,写第五题就得考虑作用域了。
写第五关的时候发现,VarDec::codegen返回值设为AllocaInst *才行。
直到第五题才能发现的错误:注意一个特殊的,NDec::codegen,根据Dec: VarDec | VarDec ASSIGNOP Exp,它的Exp是要用来给VarDec的alloca赋值的,不要只是单纯地解析它然后就不管了喔。
++ 错误类型 1:变量在使用时未经定义。
思路就是调用的时候找找curNamedValues里有没有。(摘自NAssignment::codegen函数)
++ 错误类型 2:函数在调用时未经定义。
思路就是调用的时候找找theModule里有没有。(摘自NMethodCall::codegen函数)
++ 错误类型 3:变量出现重复定义。
思路就是声明的时候找找curNamedValues里有没有。(摘自NExtDefFunDec::codegen函数)
有了第3关的基础,第四关明显简单了许多。
【实验报告我依旧只写了难点,注意口语书面化】
【如果没话写,可以着重写一下“错误判断”部分】
注:由于第3关的时候,我顺手把NFloat::codegen之类的全部写好了,所以这里没有特意标注。
好像有同学找不到Float的取值函数,如下:
出现了很多老师没给样例的函数。
如CreateSub
、getType()方法
、getReturnType()
参考classllvm_1_1IRBuilderBase.html找到CreateSub函数,
参考classllvm_1_1Function.html得知getReturnType(),
参考classllvm_1_1Value.html得知Value有自己的getType()函数。
我是如何查找的?【经验分享】
例如,我想获得auto *
类型的变量retValue
的type。
retValue.type
的代码,然后报错,说retValue是个指针,建议用->来访问它的成员
。retValue->type
,再次报错,说class llvm::Value *没有type成员
。llvm::Value
有什么呢?搜索llvm::Value
,跳转官网,找到getType()
函数。retValue->getType()
,成功。引言:
- 明显,NArgs对应多个参数,但是它的codegen()只返回一个Value*,这不合适啊!
- 难道参考List函数的解析方式,修改NArgs::codegen(),依次展开?不行啊,每个返回值都要用,依次展开没办法返回回去啊!
正解:参考NFunDec::funcodegen中对NVarList的解析即可。
std::vector<Type *> argsTypes;
std::vector<std::string> argNames;
for (NVarList *item = arguments; item; item = item->nVarList) {
auto tmp = item->nParamDec.getType();
argNames.push_back(tmp.first);
argsTypes.push_back(tmp.second);
}
注意,exp解析得到的Value*的类型,直接用getType就可以取到了,很好写,不要想太复杂了。
++ 错误类型 4:函数出现重复定义(即同样的函数名被多次定义)。
这个老师写好了。在NFunDec::funcodegen里。
++ 错误类型 5:赋值号两边的表达式类型不匹配。
++ 错误类型 6:赋值号左边出现一个只有右值的表达式。
++ 错误类型 7:return 语句的返回类型与函数定义的返回类型不匹配。
++ 销误类型 8:函数调用时实参与形参的数目或类型不匹配。
没什么好说的叭,能写出第三题没理由写不出错误判断。
【报告只写了分析思路部分】
首先看样例:
这个题没有涉及任何错误判断,需要完成的是算术运算+-*/
和逻辑符号||
,以及三个结构:if
/ifelse
/while
。
其中ifelse结构直接参考实验2的,对应的是NIfElseStmt::codegen,应该没什么好说的吧。写了这个之后样例1就都过了。
样例1:
int read(){
int a=0;
a = getchar();
return a - 48;
}
int main(){
int m,n;
int i=48;
m = read();
n = read();
if(m == n ){
putchar(i);
}else{
i = i + 1;
putchar(i);
}
i = i + 1;
putchar(i);
return 0;
}
因为一些因素,我没有用样例分析,而是用了更复杂的深层结构,分析以及伪代码如下。
提示:用基本块。多看看生成的中间代码和预期的区别。