# lab3 实验报告 PB20111654 李晓奇 ## 实验要求 ## 实验难点 1. syntax_tree和ast的区别、为什需要ast? 阅读AST的构造函数,`AST(syntax_tree *)` 实际上这部分在上个实验就有看calc_ast的代码,但是印象不深刻。。 2. 访问者模式究竟有什么用? 比如这样一系列函数调用: ```cpp ASTPrinter::visit(ASTProgram &) { //... for (auto decl : node.declarations) { decl->accept(*this); } //... } ASTVarDeclaration::accept(ASTVisitor &) { //... auto var_decl = dynamic_cast(this); if (var_decl) { var_decl->accept(visitor); return; } //... } void ASTVarDeclaration::accept(ASTVisitor &visitor) { visitor.visit(*this); } ``` 假设就是vardeclaration,直接调用(在`ASTPrinter::visit`内)`visit(dynamic_cast(this))`不是一样的吗? 参考[攻略](https://www.jianshu.com/p/1f1049d0a0f4),接受一点,感觉还是不太明白,但是不影响写实验。因为经过反复的阅读,我已经知道了我们要实现的`CminusfBuilder`是一个访问者类! 3. 无从下手! 从变量开始! 自底向上做自然多了,不然像在摸黑拧魔方。 4. 如何获得返回值? 加入一个全局变量保存语句的返回值。 5. 数组or指针?这个关乎地址的计算、类型转换…… 答案在于:中间代码是如何生成的? 按我的理解, - 在翻译为中间代码时,函数内部声明的只能是数组,计算地址时会得到指针。 - 在传递给函数时,只能传递数组,但是传递为指针。 6. 记录遇到的坑 - 形如这种产生式子`term→term mulop factor ∣ factor`,我为了图方便先求了`factor`,在根据`term`的有调用`accept()`,但是这样会导致左右操作数颠倒。 - 我对于类型转换的控制比较严格,没考虑到的一率abort,因此很快发现了漏掉了bool类型的转换。 - 接上条,一些类型转换的bug:`i32`到`i1`也是需要转换的,开始做的时候还没意识到这些(主要是没意识到`i1`的存在)。 - **核心问题**:函数调用是值传递,比如`f(a)`,这里在中间代码看来,a返回的是一个指针,需要load出来再传过去。 但是对于字面量,又不需这样提取数据。可以使用`dynamic_cast`尝试解决。 但是出现了加减乘除时,这个又不能动态类型转换。 换句话说,访问`ASTCall`时,需要依次调用args的`accept()`,但是这个args!!!!expression应该返回的就是值才对。问题出现在变量的访问那里。 这个在访问`ASTVar`时解决掉:加入全局变量`LV`表式当前处理的是左值,所以要返回对应的地址,否则返回load出来的右值。 LV为`true`时,对应的是赋值语句的左式、函数的指针引用。 另外嵌套递归调用可能导致LV发生变化,要注意。 > 关于函数传参的探讨 > > - 值传递:由于LV是false,所以访问ASTVar的时候会load出来。可能需要类型转换。 > > - 指针传递:设置LV是true,这时ASTVar会返回一个指针,**指向基本元素类型**(int|float)。 > > 所以保证ASTVar返回的Value*变量符合要求,应该可以拿来直接使用,否则抛出异常。 - 针对ASTVar的探讨 - LV:如果标记为true,则做左值,返回数据地址,否则返回提取出来的数据。 - node.expression:如果非空,则有数组下标 - Var的类型:这里断言是指针类型,所有push操作都应该维护这个性质。其指向的类型应该有三种:基本类型(int\*, float\*),数组类型([i32 x n]\*, [float x n]\*),二级指针类型(int\*\*, float\*\*)。 每种类型都有不同的对待逻辑,如何区分不同的Var类型呢? - 全局变量要特殊对待 - 数组下标为负时要调用`neg_idx_except()`,这个函数的call后边也要加上br - 神奇,bool变量不能比较,所以在比较、计算的时候要做类型转换 - store的顺序颠倒了!! - GEP和Load的翻译有问题。 定位问题:源操作数不是array类型! 那一定要看声明时push进scope的是什么,发现声明的数组类型其实也是指针类型(指向数组的指针),并不是数组?? 但后来发现,原来问题出在调用GEP,我直接吧0传入了,“胡闹吗这不是”。 - 不要乱给lable命名,因为lable的命名不会自动(针对自定义)去重。 - 发现ifelse语句忘了无条件跳转了…… - 返现布尔表达式必须使用i1类型,类型要求真的太严格了!! - 知道了为什么clang生成的中间代码,会把实参声明一遍再拷贝了。 在我看来方便编译器代码的生成,因为重新声明,push给scope的值是指针类型而不是纯值,这在后边访问Var中有用。 - 发现ret会有乱跳转的问题,这个在最后一个测试的abs中发现。 问题出在:我为if_else语句设置了三个测试块,分别负责if,else,和跳出。但是有一处的块是空的,就会导致生成默认返回。 后来发这里其实并没有问题,如果有返回值,在if或者else内部就返回了,并不会跳出去被默认返回扰乱。 - 最后一个测试文件能够正常执行,但是输出有错, 发现一个好的debug方法: 利用裁剪定位问题,即注释掉一部分,分别利用clang、cminusfc生成可执行文件,查看输出是否一致。 ## 实验设计 请写明为了顺利完成本次实验,加入了哪些亮点设计,并对这些设计进行解释。 可能的阐述方向有: 1. 如何设计全局变量 2. 遇到的难点以及解决方案 3. 如何降低生成 IR 中的冗余 4. ... ### 实验总结 此次实验有什么收获 不管多困难,先开始。~~因为更多的困难在动手前是永远想不到的~~。 ### 实验反馈 (可选 不计入评分) 对本次实验的建议