|
|
|
|
# PW6 实验报告
|
|
|
|
|
|
|
|
|
|
学号PB2100 姓名 吴书让
|
|
|
|
|
学号PB21111627 姓名 罗胤玻
|
|
|
|
|
|
|
|
|
|
## 问题回答
|
|
|
|
|
|
|
|
|
|
### Task1
|
|
|
|
|
|
|
|
|
|
**手工代码不具代表性,以下分析针对使用** `clang -S -emit-llvm` **编译得到的IR代码**
|
|
|
|
|
|
|
|
|
|
> 1-1 请给出while语句对应的LLVM IR的代码布局特点,重点解释其中涉及的几个`br`指令的含义(包含各个参数的含义)
|
|
|
|
|
|
|
|
|
|
**1-1:**
|
|
|
|
|
生成代码并对主要部分加入注释如下:
|
|
|
|
|
```llvm
|
|
|
|
|
@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:**
|
|
|
|
|
生成代码并对主要部分加入注释如下:
|
|
|
|
|
```llvm
|
|
|
|
|
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`指令返回调用者。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 实验设计
|
|
|
|
|
|
|
|
|
|
## 实验难点及解决方案
|
|
|
|
|
|
|
|
|
|
## 实验总结
|
|
|
|
|
|
|
|
|
|
## 实验反馈
|
|
|
|
|
|
|
|
|
|
## 组间交流
|