华中科技大学编译原理实验四攻略|完整版

华中科技大学编译原理实验四攻略|完整版,第1张

华中科技大学编译原理实验四攻略|完整版

文章目录
  • 写在最前
    • Quick Start
  • 第1关
  • 第2关
  • 第3关
    • 1. astnode.cpp需要修改的函数
    • 2. astnode.h需要修改的函数
    • 3. 一个分析过程
    • 4. 举几个典型的例子
      • 4.1. 各种List
      • 4.2 错误判断
      • 4.3 难点
      • 4.4 调试经验
  • 第4关
    • 1. astnode.cpp需要修改的函数
    • 2. 难点
      • 2.1 一些函数
      • 2.2 NArgs如何解析
    • 3. 错误判断
  • 第5关
    • 1. astnode.cpp需要修改的函数
    • 2. astnode.h需要修改的函数
    • 3. 分析思路
      • 3.1 if-else结构
      • 3.2 变量的作用域问题
      • 3.3 或运算||的解析

写在最前

刚刚看到有答案,那散了散了,这个是我写实验报告用来参考的东西,具体怎么写你们直接看答案吧。
这个实验,我从最初不知道代码仓库在哪、强行提交,到最后往头歌命令行里熟练地make和lli,收获确实是很多的。
看耗时,就知道我也只是个不聪明的、没经验的通关者。将经验分享出来,只是希望大家稍微少走一点弯路,减少一些在凌晨被头歌破防的可能。

Quick Start

使用头歌命令行调试

打开头歌,打开命令行,进入代码仓库。

cd /data/workspace/myshixun/llvmexp3

然后不要思考,直接make。

make

生成./minic文件,它是一个可执行的文件,用来生成中间代码。

中间代码:类似于br i1 %shandian7, label %then8, label %ifcont10这种东西。

在代码仓库下有测试样例,以第3关为例,对应task1case。

用minic编译一个样例,并将中间代码存储至tokens.txt:

./minic ./task1case/0.in > tokens.txt

编译任何一个样例,得到中间代码。
如果报错,就看报错。

如果生成中间代码成功了,cat tokens.txt看一下输出(你在cpp里写的std::cout也会在这个txt里)

如果这是一个正确的中间代码,而且它需要编译运行,并接收一定的输入,请这样:

echo "你要给程序输入的值(就是stdin)" > tmp
lli tokens.txt < tmp


第1关

主要是根据c语言翻译,有两个地方对我造成了困扰:

  1. task2中我一开始将putchar写进了判断体中,导致状态不够用,不得不移出。
  2. task2中涉及标号label。不像asm,llvm的标号上面不能没有跳转语句直接顺序执行,它必须添加br label %8跳转。
第2关

官方对于IF-ELSE等基本块的 *** 作,跟着官方来很好做。。
以下是难点:

  1. getchar是无参数函数,而builder->CreateCall需要接受1到3个参数,直接传递空vector行不通。
    正解:builder->CreateCall(callgetfunc);
  2. task2中if-then-else涉及3个基本块,样例中是if-then,只涉及两个模块。而且并不好理解。
    正解:根据官网的IF-THEN-ELSE写,它解析得很好。MyFirstLanguageFrontend/LangImpl05.html#lexer-extensions-for-if-then-else。
  3. 一些个人遇到的问题:在进入第三个基本块我没有f->getBasicBlockList().push_back(MergeBB);,因为我以为到结尾了可以直接顺序执行下去,犯了和关卡1相似的错误。没有写这一句时,报错会一直提示then:错误,导致我一直没定位到真正的错误位置。
第3关 1. astnode.cpp需要修改的函数

正好十个。

温馨提示:将main.cpp中的p->parser();注释掉,再编译运行样例,能看到每个样例完整的语法树。
请务必面向样例编程啊!!!!!!!!!!!

温馨提示2:请不要按照本攻略把代码一整个写完再去调试,建议边写边调试。


NIdentifier确实蛮麻烦的,提前剧透一下:

写第三题的时候可以乱来,写第五题就得考虑作用域了。

2. astnode.h需要修改的函数

写第五关的时候发现,VarDec::codegen返回值设为AllocaInst *才行。

3. 一个分析过程

4. 举几个典型的例子 4.1. 各种List

直到第五题才能发现的错误:注意一个特殊的,NDec::codegen,根据Dec: VarDec | VarDec ASSIGNOP Exp,它的Exp是要用来给VarDec的alloca赋值的,不要只是单纯地解析它然后就不管了喔。

4.2 错误判断

++ 错误类型 1:变量在使用时未经定义。
思路就是调用的时候找找curNamedValues里有没有。(摘自NAssignment::codegen函数)

++ 错误类型 2:函数在调用时未经定义。
思路就是调用的时候找找theModule里有没有。(摘自NMethodCall::codegen函数)

++ 错误类型 3:变量出现重复定义。
思路就是声明的时候找找curNamedValues里有没有。(摘自NExtDefFunDec::codegen函数)

4.3 难点
  1. 咋声明变量?(答:抄NExtDefFunDec::codegen的arg声明)
  2. 我怎么知道一个函数里需要解析哪些参数、哪些参数是需要被特殊处理的?(答:看上面的分析,对照parser.y和astnode.h)
  3. 咋把分析结果存到变量里面?(答:实验二里做过,不记得的话,函数声明如是:builder->CreateStore(Value, alloca);)
  4. 咋分析加?(答:实验二有提示。不记得的话,函数声明如是:builder->CreateAdd(left, right, "add");)
  5. abort怎么炸了,返回之后就提示我core dump?
    答:请检查是不是把nullptr输入到一个函数中了,举个简单的例子(摘自NAssignment::codegen函数):
    再检查是否未初始化指针就直接用了,举个retValue没初始化就返回的例子:
    Value* retValue; if(false) retValue=nullptr; return retValue;
  6. module找不到,它什么意思?(答:字面意思,找不到就找不到。执行fundec成功了就能找到main)
  7. 新加的type变量从哪里来?它是一个继承属性。它怎样继承?(答:请参考函数定义的部分,即ExtDefFunc::codegen,继承给了funcodegen。剧透一个函数NDef)
4.4 调试经验

在头歌修改代码后,命令行可能没有同步。此时先点击提交,命令行就会同步了。在命令行用make编译,编译成功后,这样调试:

./minic ./task1case/1.in

就有输出。

如果你对它的解析过程完全不了解,可以这样:

  1. 修改main.cpp,取消p->parse();的注释,输出整棵语法树。
  2. 然后对照parser.y看分析到哪一步挂掉了,为了精准定位是在哪个函数挂掉的,你可以在函数的首部和尾部加一点std::cout<<"test";用来判断。
第4关

有了第3关的基础,第四关明显简单了许多。

1. astnode.cpp需要修改的函数

只有四个,非常快乐。

注:由于第3关的时候,我顺手把NFloat::codegen之类的全部写好了,所以这里没有特意标注。
好像有同学找不到Float的取值函数,如下:

2. 难点 2.1 一些函数

出现了很多老师没给样例的函数。
如CreateSub、getType()方法、getReturnType()

参考classllvm_1_1IRBuilderbase.html找到CreateSub函数,
参考classllvm_1_1Function.html得知getReturnType(),
参考classllvm_1_1Value.html得知Value有自己的getType()函数。

我是如何查找的?【经验分享】
例如,我想获得auto *类型的变量retValue的type。

  1. 很自然地写出了retValue.type的代码,然后报错,说retValue是个指针,建议用->来访问它的成员。
  2. 于是订正为retValue->type,再次报错,说class llvm::Value *没有type成员。
  3. 那么llvm::Value有什么呢?搜索llvm::Value,跳转官网,找到getType()函数。
  4. 订正为retValue->getType(),成功。
2.2 NArgs如何解析

引言:

  1. 明显,NArgs对应多个参数,但是它的codegen()只返回一个Value*,这不合适啊!
  2. 难道参考List函数的解析方式,修改NArgs::codegen(),依次展开?不行啊,每个返回值都要用,依次展开没办法返回回去啊!

正解:参考NFunDec::funcodegen中对NVarList的解析即可。

std::vector argsTypes;
std::vector 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就可以取到了,很好写,不要想太复杂了。

3. 错误判断

++ 错误类型 4:函数出现重复定义(即同样的函数名被多次定义)。

这个老师写好了。在NFunDec::funcodegen里。

++ 错误类型 5:赋值号两边的表达式类型不匹配。

++ 错误类型 6:赋值号左边出现一个只有右值的表达式。

++ 错误类型 7:return 语句的返回类型与函数定义的返回类型不匹配。

++ 销误类型 8:函数调用时实参与形参的数目或类型不匹配。

没什么好说的叭,能写出第三题没理由写不出错误判断。

第5关 1. astnode.cpp需要修改的函数


主要是因为我前面偷懒偷得比较多,所以改动也很多。

2. astnode.h需要修改的函数


这个地方真的是,我没太动脑,返回值设错了一通乱解析。

3. 分析思路

首先看样例:

这个题没有涉及任何错误判断,需要完成的是算术运算+-*/和逻辑符号||,以及三个结构:if/ifelse/while。

3.1 if-else结构

其中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;
}
3.2 变量的作用域问题

看样例3:

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 ){
        int i = 1;
        i = i + 48;
        putchar(i);
    }else{
        i = i + 2;
        putchar(i);
    }
    i = i + 1;
    putchar(i);
	return 0;
}

因为一些因素,我没有用样例分析,而是用了更复杂的深层结构,分析以及伪代码如下。

3.3 或运算||的解析

提示:用基本块。多看看生成的中间代码和预期的区别。

贴一下报错,这个报错是因为while循环过多栈炸了,可能是因为条件判断写得有问题,心态稳住。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5650461.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-16
下一篇 2022-12-16

发表评论

登录后才能评论

评论列表(0条)

保存