6.0 KiB
PW6 实验报告
PB21111707 赵浩怡
PB21000178 梅陶然
问题回答
1-1
请给出while语句对应的LLVM IR的代码布局特点,重点解释其中涉及的几个 br指令的含义(包含各个参数的含义)
while语句对应的 LLVM IR代码由条件判断块、true分支、false分支组成。
-
先对变量赋值,然后通过无条件跳转
br label %1进入条件判断部分 -
label 1:判断循环条件
br i1 %4, label %5, label %10:条件跳转,%4保存比较结果若%4非0(条件成立),跳转到
label 5部分(true分支)否则跳转到
label 10部分(false分支) -
label 5:true分支
加载变量,进行运算,并存储运算结果,然后通过
br label %1跳转到条件判断部分 -
label 10:false分支
循环条件不成立时,加载b的值并返回
1-2
请简述函数调用语句对应的LLVM IR的代码特点
- 在被调用函数部分,函数定义中声明参数类型和数量;进入函数体,先为参数分配空间,并把通过寄存器传递的参数保存在内存中;之后通过寄存器进行相应运算(在LLVM生成的代码中,存储参数后再次进行了加载,然后才进行运算);最后,函数返回
i32类型的返回值 - 在调用者部分,先计算出各个参数的值,并加载到寄存器中;然后通过
call i32 @add()调用函数,这条指令call后指明返回值类型,@后为调用函数名,函数名后()内为参数
2-1
请给出 SysYFIR.md中提到的两种getelementptr用法的区别, 并解释原因:
%2 = getelementptr [10 x i32], [10 x i32]* %1, i32 0, i32 %0%2 = getelementptr i32, i32* %1, i32 %0- 第一种用来获取数组元素所在地址,第二种用来获取指针的地址;第一个偏移0首先计算得到%1所指向的一维数组的首元素,接着进入该一维数组内部,第二个偏移0计算得到首元素向后偏移0个i32类型,即其自身,然后该指令返回所得元素的地址,类似于二维数组的寻址方式。对于第二种用法,偏移0计算得到%1所指的变量,然后返回该变量的地址。
- 实际上,%1是一个指向一维数组的指针,第一个偏移量0代表以该指针大小的偏移量,而该指针大小是10 x i32,类似于二维数组
3-1
在scope内单独处理func的好处有哪些。
- 使得函数和变量可以重名,分开查找还提高了查找效率,便于处理特殊函数
- 层次结构更清晰,便于处理不同函数域中的变量
实验设计
-
首先说明全局状态的设计
-
两个PtrVec<Block>continue_addr和break_addr在循环嵌套时依次记录相应的位置
-
now_func记录当前所在函数,为了在如变量定义中给出函数名
-
param_type_list给出形参类型列表
-
func_block标记是否处于函数最外层的block,为了让形参和最外层block位于同一个scope
-
get_literal标记给出是否在计算数组下标对应的表达式,如果是则不需要生成中间代码,只需计算常量表达式的值
-
exp_val和left_val分别记录表达式分析出的值和左值,便于在不同函数之间传递状态
-
ret_addr记录返回地址
-
visit(initval)中只需处理单个变量初始化的情况,数组初始化在vardef中展开为单个变量的形式即可
-
visit(fundef)
-
首先确定返回值类型和形参类型,访问形参列表
-
创建函数,构建函数作用域,创建入口bb和Ret bb,并在入口bb中给形参和返回值分配空间
-
接着处理body,最后创建ret bb部分的返回代码
-
对于body中已经有返回分支的函数,跳转到ret bb的语句由visit(returnstmt)生成;对于body中没有生成返回语句的函数,在visit(fundef)中生成跳转到ret bb的语句
-
当返回值计算结果的类型和实际指定的返回类型不一致时,需要进行类型转化
-
-
-
visit(vardef)时可以根据scope.in_global判断是否是全局变量,并根据node.array_length.size判断是否是数组,并具体进行初始化,全局变量需要初始化为0
-
visit(lval)中如果in_global或者有get_literal标记的话则是直接返回常量值,否则生成中间代码即可
-
visit(assignstmt)中要关注左值和exp类型不匹配的问题
-
visit(blockstmt)中若有func_block标记则不需要新建scope,在funcdef中已经有了
-
visit(ifstmt)中需要true_bb,和false_bb分别执行两个分支,最后转入after_bb中继续
- 条件判断语句产生的结果可能为不同类型(INT1/INT32_T/FLOAT_T),需要分别处理
- else语句缺省的情况单独处理,条件不满足时跳转到after_bb执行
-
visit(whilestmt)中需要cond_bb计算条件表达式,while_bb执行循环体,并最后跳转回cond_bb,还有after_bb,在这里需要在continue_addr中插入cond_bb,break_addr中插入after_bb便于下面的处理
实验难点及解决方案
- 首先不同label之间比较难区分,所以我们使用一个label_num来标记每个label,以类型plus label_num的形式来区分
- 短路计算的时候比较复杂,需要分清楚什么时候跳转到哪里,另外表达式中类型不匹配比较复杂,需要调整好整体架构,减少类型转换的代码量
- 对于全局变量初始化和数组长度定义都是一个常量表达式,且不能生成中间代码,需要提前计算出值,故我们用一个global类封装这些操作,以简化代码,global类中有两个vector,分别记录int和float数值,每次取出最后插入的两个数执行op操作算出值并放回,要关注类型转换,另外还有一个常量表string->vector,记录每个常量数组或常值变量的值,以便在常量表达式中取出对应的值
实验总结
- 本次实验很好的增强了对编译生成中间代码的理解,以及对语句结构的了解,通过完成该项目,进一步提高了对大项目的编写能力
- 本次实验再次加强了对访问者模式的理解