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.

9.5 KiB

PW6 实验报告

学号PB21000179 姓名 吴书让 学号PB21111627 姓名 罗胤玻

问题回答

Task1

手工代码不具代表性,以下分析针对使用 clang -S -emit-llvm 编译得到的IR代码

1-1 请给出while语句对应的LLVM IR的代码布局特点重点解释其中涉及的几个br指令的含义(包含各个参数的含义)

1-1: 生成代码并对主要部分加入注释如下:

@b = dso_local global i32 0, align 4  ; 定义一个全局变量b初始值为0对齐方式为4
@a = dso_local global i32 0, align 4  ; 定义一个全局变量a初始值为0对齐方式为4

; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @main() #0 {  ; 定义一个名为main的函数返回类型为i32
  %1 = alloca i32, align 4  ; 在栈上分配一个i32类型的空间对齐方式为4
  store i32 0, i32* %1, align 4  ; 将0存储到%1指向的地址对齐方式为4
  store i32 0, i32* @b, align 4  ; 将0存储到全局变量b对齐方式为4
  store i32 3, i32* @a, align 4  ; 将3存储到全局变量a对齐方式为4
  br label %2  ; 无条件跳转到标签2

2:  ; 标签2循环条件判断基本块
  %3 = load i32, i32* @a, align 4  ; 从全局变量a中加载一个i32类型的值对齐方式为4
  %4 = icmp sgt i32 %3, 0  ; 比较%3和0的大小关系结果存储在%4中
  br i1 %4, label %5, label %11  ; 根据%4的值进行条件跳转如果为真跳转到标签5否则跳转到标签11

5:  ; 标签5循环体基本块
  %6 = load i32, i32* @b, align 4  ; 从全局变量b中加载一个i32类型的值对齐方式为4
  %7 = load i32, i32* @a, align 4  ; 从全局变量a中加载一个i32类型的值对齐方式为4
  %8 = add nsw i32 %6, %7  ; 将%6和%7相加结果存储在%8中
  store i32 %8, i32* @b, align 4  ; 将%8存储到全局变量b对齐方式为4
  %9 = load i32, i32* @a, align 4  ; 从全局变量a中加载一个i32类型的值对齐方式为4
  %10 = sub nsw i32 %9, 1  ; 将%9减1结果存储在%10中
  store i32 %10, i32* @a, align 4  ; 将%10存储到全局变量a对齐方式为4
  br label %2  ; 无条件跳转到标签2

11:  ; 标签11循环结束后基本块
  %12 = load i32, i32* @b, align 4  ; 从全局变量b中加载一个i32类型的值对齐方式为4
  ret i32 %12  ; 返回%12作为函数的返回值
}

注意到如下的while语句对应的LLVM IR的代码布局特点

  1. while语句的条件判断部分和循环体部分分别对应一个基本块
  2. 循环体部分的最后一条语句为无条件跳转到条件判断部分的基本块,
  3. 条件判断部分的最后一条语句为条件跳转到循环体部分的基本块或者跳转到循环体部分之后的基本块。

1-2 请简述函数调用语句对应的LLVM IR的代码特点

1-2 生成代码并对主要部分加入注释如下:

define dso_local i32 @add(i32 %0, i32 %1) #0 {
  %3 = alloca i32, align 4  ; 在栈上分配一个i32类型的空间对齐方式为4
  %4 = alloca i32, align 4  ; 在栈上分配另一个i32类型的空间对齐方式为4
  store i32 %0, i32* %3, align 4  ; 将参数%0的值存储到%3指向的地址中对齐方式为4
  store i32 %1, i32* %4, align  4  ; 将参数%1的值存储到%4指向的地址中对齐方式为4
  %5 = load i32, i32* %3, align 4  ; 从%3指向的地址中加载一个i32类型的值对齐方式为4
  %6 = load i32, i32* %4, align 4  ; 从%4指向的地址中加载一个i32类型的值对齐方式为4
  %7 = add nsw i32 %5, %6  ; 将%5和%6相加结果存储在%7中
  %8 = sub nsw i32 %7,  1  ; 将%7减1结果存储在%8中
  ret i32 %8  ; 返回%8
}

define dso_local i32 @main() #0 {  ; 定义一个名为main的函数返回类型为i32
  %1 = alloca i32, align 4  ; 在栈上分配一个i32类型的空间对齐方式为4
  store i32 3, i32* %1, align 4  ; 将值3存储到%1指向的地址中对齐方式为4
  %2 = alloca i32, align 4  ; 在栈上分配另一个i32类型的空间对齐方式为4
  store i32 2, i32* %2, align 4  ; 将值2存储到%2指向的地址中对齐方式为4
  %3 = load i32, i32* %1, align 4  ; 从%1指向的地址中加载一个i32类型的值对齐方式为4
  %4 = load i32, i32* %2, align 4  ; 从%2指向的地址中加载一个i32类型的值对齐方式为4
  %5 = call i32 @add(i32 %3, i32 %4)  ; 调用add函数传入参数%3和%4并将返回值存储到%5中
  %6 = add nsw i32 %5, 1  ; 将%5加1结果存储在%6中
  ret i32 %6  ; 从函数中返回%6的值
}

注意到如下的函数调用语句对应的LLVM IR的代码特点

  1. 函数调用语句对应的LLVM IR的代码中会先将函数的参数存储到栈上分配的空间中然后再调用函数。
  2. 函数调用语句对应的LLVM IR的代码中会将函数的返回值存储到一个临时变量中然后再对临时变量进行操作。
  3. 调用者使用call指令调用被调用者,被调用者使用ret指令返回调用者。

Task2

2-1 请给出SysYFIR.md中提到的两种getelementptr用法的区别, 并解释原因:

2-1

  • getelementptr实际上是一条指针计算语句,不进行任何数据的访问或修改,作用是计算指针并修改计算后指针的类型。
    • 第一个参数为要进行计算原始指针的类型;
    • 第二个参数是原始指针,往往是一个结构体指针,或数组首地址指针。
    • 第二个参数及以后的参数都称为index表示要进行计算的参数作用在第二个参数给出的初始指针如结构体的第几个元素数组的第几个元素。
  • 第一种用法获取数组元素所在地址一个偏移0得到%1指向数组的第一个用第二个偏移0得到首元素向后偏移量返回此地址
  • 第二种用法获取指针地址第一个偏移0计算得到%1所指的变量并返回该变量的地址

Task3

3-1. 在scope内单独处理func的好处有哪些。

3-1 在不同的scope中可以使用相同的变量名而不必担心命名冲突的问题同时也可以提高代码的可读性和可维护性。

实验设计

本次实验循序渐进分三步指导完成一个sy语言的机器语言自动生成器。

  1. 手写机器语言,熟悉机器语言结构及各类语句含义和用途
  2. 手动使用接口设置机器语言,相比第一步抽象层次更高,熟悉各种接口含义以及使用方法
  3. 结合PW5中学习的AST以及访问者模式为sy语言自动翻译机器语言。前两步中的四个例子几乎已经涵盖所有情况可以作为参考。

实验难点及解决方案

  1. educoder设计麻烦
  2. 多维数组处理麻烦:因为一开始陷入了想用多维数组来存储的思维,导致很多不必要的尝试;换成用一维数组实现就很简单。
  3. 函数全为void型不能返回变量设置全局临时变量作为返回值定时整理删减全局临时变量。
  4. 一开始随意选择了某个语法书节点开始编程但是前置节点尚未编写导致了理解上的困难审视全体语法树节点尽可能选取最上层节点或着依赖较少的节点开始编写比如funcDef。
  5. 测试目录中只有20个文件但是test.py提示进行了21次测试且仅有第一次测试出错删去.DS_Store并加入.gitignore。
  6. 重复表达式过多,修改麻烦:定期整理重复代码抽象出来作为单独函数。

实验总结

挺好的一次实验,但是感觉第二步到第三步之间缺少指引,比如翻译器的整体规划(一般涉及到一些全局变量,转换等)。这也导致了,如果没有事先商量仅仅指派某些函数来分工的话,难以同步进度和各种操作。

实验反馈

  1. 建议完全抛弃educoder。
  2. 可以在2、3步之间加入一个阶段让学生分列出各语法树节点的可能情况以及处理方案方便整体审视检查。之后只需要对着事先计划好的处理方案编程即可 相比直接编写难度会小很多。

组间交流

提前商定好各函数之间必要信息交换,以及常用辅助函数和全局变量的设计。

然后各自负责一部分visit的编写完成后进行整体调试和debug。

在Pass所有20个test后针对各个test的情况进行讨论见下对未覆盖的情况增加test新增的test检查出了BB命名重复的错误

1. main函数
2. [pass] 变量定义未检查float类型->21 - 
3. 空语句
4. [pass] 一维数组类型定义未检查float类型与赋初值情况->22
5. [pass] 一维数组赋值情况->22
6. [pass] 常量定义,未检查非全局变量情况->23
7. [pass] 常量一维数组定义未检查float类型->24
8. 函数定义,未检查函数含参数情况
9. [pass] 函数定义未检查函数参数为float类型情况->25
10. if语句
11. [pass] if-else语句未检查if嵌套情况-> 26
12. while语句未检查while嵌套-> 27未看出错误原因
13. break语句
14. continue语句
15. 输入语句 getint
16. 复杂函数定义
17. 等于关系测试,未检查其他关系-> 28多个if语句块之间疑似未按照原有顺序编译
18. scop作用检查变量作用域问题
19. 最大公因数
20. 汉诺塔问题,递归功能检测

完成主要任务后开始编写多维数组首先讨论方案决定用一维数组代替其次编写了多种test辅助验证正确性。

在完成的过程中时刻进行测试保证之前的test依然Pass。

组内主要使用SNS及时沟通使用git同步进度。