You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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 5true分支

    加载变量,进行运算,并存储运算结果,然后通过 br label %1跳转到条件判断部分

  • label 10false分支

    循环条件不成立时加载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记录每个常量数组或常值变量的值以便在常量表达式中取出对应的值

实验总结

  • 本次实验很好的增强了对编译生成中间代码的理解,以及对语句结构的了解,通过完成该项目,进一步提高了对大项目的编写能力
  • 本次实验再次加强了对访问者模式的理解

实验反馈

组间交流