Lab3 实验文档
0. 前言
本次实验作为Lab4的前驱实验,独立于Lab1、Lab2。
本次实验的目的是让大家熟悉Lab4所需要的相关知识: LLVM IR、 LightIR(LLVM IR的轻量级C++接口)和 Visitor Pattern(访问者模式)。
在开始实验之前,如果你使用的不是助教提供的虚拟机镜像,请根据之前的环境准备确保LLVM的版本为10.0.1,且PATH环境变量配置正确。可以通过lli --version
命令是否可以输出10.0.1的版本信息来验证。
主要工作
- 第一部分: 了解LLVM IR。通过clang生成的.ll,了解LLVM IR与c代码的对应关系。完成1.3
- 第二部分: 了解LightIR。通过助教提供的c++例子,了解LightIR的c++接口及实现。完成2.3
- 第三部分: 理解Visitor Pattern。
- 实验报告:在report.md中回答3个问题。
1. LLVM IR部分
1.1 LLVM IR介绍
根据维基百科的介绍,LLVM是一个自由软件项目,它是一种编译器基础设施,以C++写成,包含一系列模块化的编译器组件和工具链,用来开发编译器前端和后端。IR的全称是Intermediate Representation,即中间表示。LLVM IR是一种类似于汇编的底层语言。
LLVM IR的具体指令可以参考Reference Manual。但是你会发现其内容庞杂。虽然助教认为,高效地查阅官方文档及手册是非常必要的一项技能,但是由于其手册过于复杂,因此助教筛选了后续实验中将要用到的子集,总结为了精简的IR Reference手册。
作为一开始的参考,你可以先阅读其中IR Features
和IR Format
两节,后续有需要再反复参考。实验的最后,你需要在report.md中回答问题3。
1.2 gcd例子: 利用clang生成的.ll
阅读tests/lab3/ta_gcd/gcd_array.c。
根据clang -S -emit-llvm gcd_array.c
指令,你可以得到对应的gcd_array.ll
文件.你需要结合gcd_array.c阅读gcd_array.ll
,理解其中每条LLVM IR指令与c代码的对应情况。
通过lli gcd_array.ll; echo $?
指令,你可以测试gcd_array.ll
执行结果的正确性。其中,
lli
会运行*.ll
文件$?
的内容是上一条命令所返回的结果,而echo $?
可以将其输出到终端中
后续你会经常用到这两条指令。
1.3 你的提交1: 手动编写.ll
助教提供了四个简单的c程序,分别是tests/lab3/c_cases/
目录下的assign.c、fun.c、if.c和while.c.你需要在tests/lab3/stu_ll/
目录中,手工完成自己的assign_hand.ll、fun_hand.ll、if_handf.ll和while_hand.ll,以实现与上述四个C程序相同的逻辑功能.你需要添加必要的注释..ll
文件的注释是以";"开头的。
必要的情况下,你可以参考clang -S -emit-llvm
的输出,但是你提交的结果必须避免同此输出一字不差。
助教会用lli
检查你结果的正确性,并用肉眼检查你的注释。
2. LightIR部分
2.1 LightIR - LLVM IR的C++接口
由于LLVM IR官方的C++接口的文档同样过于冗长,助教提供了LightIR
这一C++接口库。你需要阅读LightIR核心类的介绍。
lab4部分会要求大家通过LightIR
根据AST
构建生成LLVM IR。所以你需要仔细阅读文档了解其接口的设计。
2.2 gcd例子: 利用LightIR + cpp 生成.ll
为了让大家更直观地感受并学会LightIR
接口的使用,助教提供了tests/lab3/ta_gcd/gcd_array_generator.cpp。该cpp程序会生成与gcd_array.c逻辑相同的LLVM IR文件。助教提供了非常详尽的注释,一定要好好利用!
该程序的编译与运行请参考4.2节。
2.3 你的提交2: 利用LightIR + cpp编写生成.ll的程序
你需要在tests/lab3/stu_cpp/
目录中,编写assign_generator.cpp、fun_generator.cpp、if_generator.cpp和while_generator.cpp,以生成与1.3节的四个C程序相同逻辑功能的.ll
文件。你需要添加必要的注释。你需要在report.md中回答问题1。
3. Lab4的准备
3.1 了解Visitor Pattern
Visitor Pattern(访问者模式)是一种在LLVM项目源码中被广泛使用的设计模式。在遍历某个数据结构(比如树)时,如果我们需要对每个节点做一些额外的特定操作,Visitor Pattern就是个不错的思路。
Visitor Pattern是为了解决稳定的数据结构和易变的操作耦合问题而产生的一种设计模式。解决方法就是在被访问的类里面加一个对外提供接待访问者的接口,其关键在于在数据基础类里面有一个方法接受访问者,将自身引用传入访问者。这里举一个应用实例来帮助理解访问者模式: 您在朋友家做客,您是访问者;朋友接受您的访问,您通过朋友的描述,然后对朋友的描述做出一个判断,这就是访问者模式。
有关 Visitor Pattern 的含义、模式和特点,有梯子的同学可参考维基百科。
下面的例子可以清晰地展示Visitor Pattern的运作方式。这是助教编写的计算表达式 4 * 2 - 2 / 4 + 5
结果的C++程序。
其中较为重要的一点原则在于,C++中对函数重载特性的支持。在代码treeVisitor.visit(node)
中,根据node
对象具体类型的不同,编译器会在visit(AddSubNode& node)
、visit(NumberNode& node)
、visit(MulDivNode& node)
三者中,选择对应的实现进行调用。你需要理解下面这个例子中tree是如何被遍历的。请在report.md中回答问题2。
#include <iostream>
#include <vector>
class TreeVisitor; // Forward declare TreeVisitor
class Node { // Parent class for the elements (AddSubNode, NumberNode and
// MulDivNode)
public:
// This function accepts an object of any class derived from
// TreeVisitor and must be implemented in all derived classes
virtual int accept(TreeVisitor& treeVisitor) = 0;
};
// Forward declare specific elements (nodes) to be dispatched
class AddSubNode;
class NumberNode;
class MulDivNode;
class TreeVisitor { // Declares the interface for the treeVisitor
public:
// Declare overloads for each kind of a node to dispatch
virtual int visit(AddSubNode& node) = 0;
virtual int visit(NumberNode& node) = 0;
virtual int visit(MulDivNode& node) = 0;
};
class AddSubNode : public Node { // Specific element class #1
public:
// Resolved at runtime, it calls the treeVisitor's overloaded function,
// corresponding to AddSubNode.
int accept(TreeVisitor& treeVisitor) override {
return treeVisitor.visit(*this);
}
Node& leftNode;
Node& rightNode;
std::string op;
AddSubNode(Node& left, Node& right, std::string op): leftNode(left), rightNode(right), op(op){}
};
class NumberNode : public Node { // Specific element class #2
public:
// Resolved at runtime, it calls the treeVisitor's overloaded function,
// corresponding to NumberNode.
int accept(TreeVisitor& treeVisitor) override {
return treeVisitor.visit(*this);
}
int number;
NumberNode(int number){
this->number = number;
}
};
class MulDivNode : public Node { // Specific element class #3
public:
// Resolved at runtime, it calls the treeVisitor's overloaded function,
// corresponding to MulDivNode.
int accept(TreeVisitor& treeVisitor) override {
return treeVisitor.visit(*this);
}
Node& leftNode;
Node& rightNode;
std::string op;
MulDivNode(Node& left, Node& right, std::string op): leftNode(left), rightNode(right), op(op){}
};
class TreeVisitorCalculator : public TreeVisitor { // Implements triggering of all
// kind of elements (nodes)
public:
int visit(AddSubNode& node) override {
auto right = node.rightNode.accept(*this);
auto left = node.leftNode.accept(*this);
if (node.op == "add") {
return left + right;
}
else {
return left - right;
}
}
int visit(NumberNode& node) override {
return node.number;
}
int visit(MulDivNode& node) override {
auto left = node.leftNode.accept(*this);
auto right = node.rightNode.accept(*this);
if (node.op == "mul") {
return left * right;
}
else {
return left / right;
}
}
};
int main() {
// construct the expression nodes and the tree
// the expression: 4 * 2 - 2 / 4 + 5
auto numberA = NumberNode(4);
auto numberB = NumberNode(2);
auto exprC = MulDivNode(numberA, numberB, "mul");
auto exprD = MulDivNode(numberB, numberA, "div");
auto exprE = AddSubNode(exprC, exprD, "sub");
auto numberF = NumberNode(5);
auto exprRoot = AddSubNode(exprE, numberF, "add");
TreeVisitorCalculator treeVisitor;
// traverse the tree and calculate
int result = treeVisitor.visit(exprRoot);
std::cout << "4 * 2 - 2 / 4 + 5 evaluates: " << result << std::endl;
return 0;
}
该文件的执行结果如下:
$ g++ visitor.cpp -std=c++14; ./a.out
4 * 2 - 2 / 4 + 5 evaluates: 13
4. 实验要求
4.1 目录结构
除了下面指明你所要修改或提交的文件,其他文件请勿修改。
.
├── CMakeLists.txt
├── Documentations
│ ├── ...
| ├── common <- LightIR 相关文档
│ └── lab3
│ └── README.md <- lab3实验文档说明(你在这里)
├── include <- 实验所需的头文件
│ ├── ...
│ ├── lightir
├── README.md
├── Reports
│ ├── ...
│ └── lab3
│ └── report.md <- lab3所需提交的实验报告,含3个问题(你要交)
├── src
│ ├── ...
│ └── lightir
└── tests
├── CMakeLists.txt
├── ...
└── lab3 <- lab3文件夹
├── c_cases <- 4个c程序
│ ├── assign.c
│ ├── fun.c
│ ├── if.c
│ └── while.c
├── CMakeLists.txt <- 你在2.3节需要去掉注释(我们不收,你要改)
├── stu_cpp <- lab3所需提交的cpp目录(你要交)
│ ├── assign_generator.cpp
│ ├── fun_generator.cpp
│ ├── if_generator.cpp
│ └── while_generator.cpp
├── stu_ll <- lab3所需提交的.ll目录(你要交)
│ ├── assign_hand.ll
│ ├── fun_hand.ll
│ ├── if_hand.ll
│ └── while_hand.ll
└── ta_gcd
├── gcd_array.c
└── gcd_array_generator.cpp <- 助教提供的生成gcd_array.ll的cpp
4.2 编译、运行和验证
- 编译与运行
在${WORKSPACE}/build/
下执行:
你可以得到对应# 如果存在 CMakeCache.txt 要先删除 # rm CMakeCache.txt cmake .. make make install
gcd_array_generator.cpp
的可执行文件。
在完成2.3时,在${WORKSPACE}/tests/lab3/CMakeLists.txt
中去掉对应的注释,再在${WORKSPACE}/build/
下执行cmake ..
与make
指令,即可得到对应的可执行文件。 - 验证
本次试验测试案例只有${WORKSPACE}/tests/lab3/c_cases
中的4个样例。请大家自行验证。
助教会执行你们的代码,并使用diff
命令进行验证。
实验报告
问题1: cpp与.ll的对应
请描述你的cpp代码片段和.ll的每个BasicBlock的对应关系。描述中请附上两者代码。
assign
(1)如下是assign_generator.cpp
中的内容
#include "BasicBlock.h"
#include "Constant.h"
#include "Function.h"
#include "IRBuilder.h"
#include "Module.h"
#include "Type.h"
#include <iostream>
#include <memory>
#ifdef DEBUG // 用于调试信息,大家可以在编译过程中通过" -DDEBUG"来开启这一选项
#define DEBUG_OUTPUT std::cout << __LINE__ << std::endl; // 输出行号的简单示例
#else
#define DEBUG_OUTPUT
#endif
#define CONST_INT(num) \
ConstantInt::get(num, module)
#define CONST_FP(num) \
ConstantFP::get(num, module) // 得到常数值的表示,方便后面多次用到
int main() {
auto module = new Module("Assign code");
auto builder = new IRBuilder(nullptr, module); //该类提供了独立的接口创建各种 IR 指令
Type *Int32Type = Type::get_int32_type(module);
// main函数
auto mainFun = Function::create(FunctionType::get(Int32Type, {}),"main", module);
auto bb = BasicBlock::create(module, "entry", mainFun);
builder->set_insert_point(bb);
auto retAlloca = builder->create_alloca(Int32Type); //分配一个保存返回值的内容
auto *arrayType = ArrayType::get(Int32Type, 10); //数组类型,参数依次是数组元素的类型Int32Type,数组元素个数10
auto a=builder->create_alloca(arrayType); //数组a[10]
auto a0GEP = builder->create_gep(a, {CONST_INT(0), CONST_INT(0)}); //获取a[0]的地址
builder->create_store(CONST_INT(10), a0GEP); //将整数10写入a[0]的地址
auto a0Load = builder->create_load(a0GEP); //读取a[0]
auto m=builder->create_imul(a0Load,CONST_INT(2)); //a[0]*2
auto a1GEP = builder->create_gep(a, {CONST_INT(0), CONST_INT(1)}); //获取a[1]的地址
builder->create_store(m, a1GEP); //将a[0]*2写入a[1]的地址
auto a1Load = builder->create_load(a1GEP); //获取a[1]的值
builder->create_store(a1Load, retAlloca); //将a[1]的值写入retAlloca地址中
auto retLoad=builder->create_load(retAlloca); //读取retAlloca地址的值
builder->create_ret(retLoad); //创建ret指令,即返回
std::cout << module->print();
delete module;
return 0;
}
(2)在build目录下执行命令make
生成可执行文件stu_assign_generator
。执行命令./stu_assign_generator >assign.ll
,,如下是生成的assign.ll
中的内容
define i32 @main() {
label_entry:
%op0 = alloca i32
%op1 = alloca [10 x i32]
%op2 = getelementptr [10 x i32], [10 x i32]* %op1, i32 0, i32 0
store i32 10, i32* %op2
%op3 = load i32, i32* %op2
%op4 = mul i32 %op3, 2
%op5 = getelementptr [10 x i32], [10 x i32]* %op1, i32 0, i32 1
store i32 %op4, i32* %op5
%op6 = load i32, i32* %op5
store i32 %op6, i32* %op0
%op7 = load i32, i32* %op0
ret i32 %op7
}
(3)只有一个basicblock
,对应关系为:
auto bb = BasicBlock::create(module, "entry", mainFun);
对应标签main函数中的label_entry
。
fun
(1)如下是fun_generator.cpp
中的内容
#include "BasicBlock.h"
#include "Constant.h"
#include "Function.h"
#include "IRBuilder.h"
#include "Module.h"
#include "Type.h"
#include <iostream>
#include <memory>
#ifdef DEBUG // 用于调试信息,大家可以在编译过程中通过" -DDEBUG"来开启这一选项
#define DEBUG_OUTPUT std::cout << __LINE__ << std::endl; // 输出行号的简单示例
#else
#define DEBUG_OUTPUT
#endif
#define CONST_INT(num) \
ConstantInt::get(num, module)
#define CONST_FP(num) \
ConstantFP::get(num, module) // 得到常数值的表示,方便后面多次用到
int main() {
auto module = new Module("fun code");
auto builder = new IRBuilder(nullptr, module); //该类提供了独立的接口创建各种 IR 指令
Type *Int32Type = Type::get_int32_type(module);
//函数callee
std::vector<Type *> Ints(1, Int32Type); // 函数参数类型的vector
auto calleeFunTy = FunctionType::get(Int32Type, Ints); //通过返回值类型与参数类型列表得到函数类型
auto calleeFun = Function::create(calleeFunTy,"callee", module); // 由函数类型得到函数
auto bb = BasicBlock::create(module, "entry", calleeFun);
builder->set_insert_point(bb); // 一个BB的开始,将当前插入指令点的位置设在bb
auto aAlloca = builder->create_alloca(Int32Type); // 在内存中分配参数a的位置
std::vector<Value *> args; // 获取函数的形参,通过Function中的iterator
for (auto arg = calleeFun->arg_begin(); arg != calleeFun->arg_end(); arg++) {
args.push_back(*arg); // * 号运算符是从迭代器中取出迭代器当前指向的元素
}
builder->create_store(args[0], aAlloca); // 将参数a store下来
auto aLoad=builder->create_load(aAlloca); //获取参数a的值
auto m=builder->create_imul(aLoad,CONST_INT(2)); //a*2
auto retAlloca = builder->create_alloca(Int32Type); //分配一个保存返回值的内容
builder->create_store(m, retAlloca); //将m的值写入retAlloca地址中
auto retLoad=builder->create_load(retAlloca); //读取retAlloca地址的值
builder->create_ret(retLoad); //创建ret指令,即返回
// main函数
auto mainFun = Function::create(FunctionType::get(Int32Type, {}),"main", module);
bb = BasicBlock::create(module, "entry", mainFun); //创建BasicBlock
builder->set_insert_point(bb); //将当前插入指令点的位置设在bb
auto cs= builder->create_alloca(Int32Type); // 在内存中分配参数cs的位置
builder->create_store(CONST_INT(110), cs); //将整数10写入cs的地址
auto csLoad = builder->create_load(cs); //读取cs
auto call = builder->create_call(calleeFun, {csLoad}); //创建call指令
builder->create_ret(call); //创建ret指令,返回
std::cout << module->print();
delete module;
return 0;
}
(2)在build目录下执行命令make
生成可执行文件stu_fun_generator
。执行命令./stu_fun_generator >fun.ll
,,如下是生成的fun.ll
中的内容
define i32 @callee(i32 %arg0) {
label_entry:
%op1 = alloca i32
store i32 %arg0, i32* %op1
%op2 = load i32, i32* %op1
%op3 = mul i32 %op2, 2
%op4 = alloca i32
store i32 %op3, i32* %op4
%op5 = load i32, i32* %op4
ret i32 %op5
}
define i32 @main() {
label_entry:
%op0 = alloca i32
store i32 110, i32* %op0
%op1 = load i32, i32* %op0
%op2 = call i32 @callee(i32 %op1)
ret i32 %op2
}
(3)有两个basicblock
,对应关系为:
auto bb = BasicBlock::create(module, "entry", calleeFun);
对应callee
函数中的标签label_entry
。bb = BasicBlock::create(module, "entry", mainFun);
对应main
函数中的标签label_entry
。
if
(1)如下是if_generator.cpp
中的内容
#include "BasicBlock.h"
#include "Constant.h"
#include "Function.h"
#include "IRBuilder.h"
#include "Module.h"
#include "Type.h"
#include <iostream>
#include <memory>
#ifdef DEBUG // 用于调试信息,大家可以在编译过程中通过" -DDEBUG"来开启这一选项
#define DEBUG_OUTPUT std::cout << __LINE__ << std::endl; // 输出行号的简单示例
#else
#define DEBUG_OUTPUT
#endif
#define CONST_INT(num) \
ConstantInt::get(num, module)
#define CONST_FP(num) \
ConstantFP::get(num, module) // 得到常数值的表示,方便后面多次用到
int main() {
auto module = new Module("if code");
auto builder = new IRBuilder(nullptr, module); //该类提供了独立的接口创建各种 IR 指令
Type *Int32Type = Type::get_int32_type(module);
Type *FloatType = Type::get_float_type(module);
// main函数
auto mainFun = Function::create(FunctionType::get(Int32Type, {}),"main", module);
auto bb = BasicBlock::create(module, "entry", mainFun); //创建BasicBlock
builder->set_insert_point(bb); //将当前插入指令点的位置设在bb
auto retAlloca = builder->create_alloca(Int32Type); // 在内存中分配返回值的位置
auto aAlloca=builder->create_alloca(FloatType); //在内存中分配参数a的位置
builder->create_store(CONST_FP(5.555), aAlloca); //将常数5.555写入a的地址
auto aLoad = builder->create_load(aAlloca); //读取a
auto cmp = builder->create_fcmp_gt(aLoad, CONST_FP(1.000)); //判断a是否大于1
auto trueBB = BasicBlock::create(module, "trueBB", mainFun); // true分支
auto falseBB = BasicBlock::create(module, "falseBB", mainFun); // false分支
auto retBB = BasicBlock::create(module, "", mainFun); // return分支,提前create,以便true分支可以br
auto br = builder->create_cond_br(cmp, trueBB, falseBB); // 条件BR
builder->set_insert_point(trueBB); // if true; 分支的开始需要SetInsertPoint设置
builder->create_store(CONST_INT(233), retAlloca); //将整数233存储到返回值的地址中
builder->create_br(retBB); // br retBB
builder->set_insert_point(falseBB); // if false
builder->create_store(CONST_INT(0), retAlloca); //将整数0存储到返回值的地址中
builder->create_br(retBB); // br retBB
builder->set_insert_point(retBB); // ret分支
auto retLoad = builder->create_load(retAlloca); //获取返回值地址中值
builder->create_ret(retLoad); //创建ret指令,返回
std::cout << module->print();
delete module;
return 0;
}
(2)在build目录下执行命令make
生成可执行文件stu_if_generator
。执行命令./stu_if_generator >if.ll
,,如下是生成的if.ll
中的内容
define i32 @main() {
label_entry:
%op0 = alloca i32
%op1 = alloca float
store float 0x40163851e0000000, float* %op1
%op2 = load float, float* %op1
%op3 = fcmp ugt float %op2,0x3ff0000000000000
br i1 %op3, label %label_trueBB, label %label_falseBB
label_trueBB: ; preds = %label_entry
store i32 233, i32* %op0
br label %label4
label_falseBB: ; preds = %label_entry
store i32 0, i32* %op0
br label %label4
label4: ; preds = %label_trueBB, %label_falseBB
%op5 = load i32, i32* %op0
ret i32 %op5
}
(3)有4个basicblock
,对应关系为:
auto bb = BasicBlock::create(module, "entry", mainFun);
对应main
函数中的标签label_entry
auto trueBB = BasicBlock::create(module, "trueBB", mainFun);
对应标签label_trueBB
auto falseBB = BasicBlock::create(module, "falseBB", mainFun);
对应标签label_falseBB
auto retBB = BasicBlock::create(module, "", mainFun);
对应标签label4
while
(1)如下是while_generator.cpp
中的内容
#include "BasicBlock.h"
#include "Constant.h"
#include "Function.h"
#include "IRBuilder.h"
#include "Module.h"
#include "Type.h"
#include <iostream>
#include <memory>
#ifdef DEBUG // 用于调试信息,大家可以在编译过程中通过" -DDEBUG"来开启这一选项
#define DEBUG_OUTPUT std::cout << __LINE__ << std::endl; // 输出行号的简单示例
#else
#define DEBUG_OUTPUT
#endif
#define CONST_INT(num) \
ConstantInt::get(num, module)
#define CONST_FP(num) \
ConstantFP::get(num, module) // 得到常数值的表示,方便后面多次用到
int main() {
auto module = new Module("while code");
auto builder = new IRBuilder(nullptr, module); //该类提供了独立的接口创建各种 IR 指令
Type *Int32Type = Type::get_int32_type(module);
// main函数
auto mainFun = Function::create(FunctionType::get(Int32Type, {}),"main", module);
auto bb = BasicBlock::create(module, "entry", mainFun); //创建BasicBlock
builder->set_insert_point(bb); //将当前插入指令点的位置设在bb
auto retAlloca = builder->create_alloca(Int32Type); // 在内存中分配返回值的位置
auto aAlloca=builder->create_alloca(Int32Type); //在内存中分配参数a的位置
auto iAlloca=builder->create_alloca(Int32Type); //在内存中分配参数i的位置
builder->create_store(CONST_INT(10), aAlloca); //将10写入a的地址
builder->create_store(CONST_INT(0), iAlloca); //将0写入i的地址
auto condBB = BasicBlock::create(module, "condBB", mainFun); // cond分支,用于while循环的判断
auto trueBB = BasicBlock::create(module, "trueBB", mainFun); // true分支,条件为真时
auto retBB = BasicBlock::create(module, "retBB", mainFun); // ret分支,当循环条件不满足时
builder->create_br(condBB);
builder->set_insert_point(condBB); // cond分支判断条件,分支的开始需要SetInsertPoint设置
auto iLoad = builder->create_load(iAlloca); //获取i地址中的值
auto cmp = builder->create_icmp_lt(iLoad, CONST_INT(10)); //判断i是否小于10
auto br = builder->create_cond_br(cmp, trueBB, retBB); // 条件BR
builder->set_insert_point(trueBB); // if true;
auto aLoad = builder->create_load(aAlloca); //从a地址读取a
iLoad = builder->create_load(iAlloca); //从i地址读取i
auto add1=builder->create_iadd(iLoad,CONST_INT(1)); //进行i+1
builder->create_store(add1, iAlloca); //把i+1的值写入i的地址
iLoad = builder->create_load(iAlloca); //从i地址读取i
auto add2=builder->create_iadd(iLoad,aLoad);//进行i+a
builder->create_store(add2, aAlloca); //把i+a的值写入a的地址
builder->create_br(condBB); // br condBB
builder->set_insert_point(retBB); // ret分支
aLoad = builder->create_load(aAlloca); //从a地址读取a
builder->create_store(aLoad,retAlloca ); //把a的值写入返回值的地址
auto retLoad = builder->create_load(retAlloca); //获取返回值地址中值
builder->create_ret(retLoad); //创建ret指令,返回
std::cout << module->print();
delete module;
return 0;
}
(2)在build目录下执行命令make
生成可执行文件stu_while_generator
。执行命令./stu_while_generator >while.ll
,,如下是生成的while.ll
中的内容
define i32 @main() {
label_entry:
%op0 = alloca i32
%op1 = alloca i32
%op2 = alloca i32
store i32 10, i32* %op1
store i32 0, i32* %op2
br label %label_condBB
label_condBB: ; preds = %label_entry, %label_trueBB
%op3 = load i32, i32* %op2
%op4 = icmp slt i32 %op3, 10
br i1 %op4, label %label_trueBB, label %label_retBB
label_trueBB: ; preds = %label_condBB
%op5 = load i32, i32* %op1
%op6 = load i32, i32* %op2
%op7 = add i32 %op6, 1
store i32 %op7, i32* %op2
%op8 = load i32, i32* %op2
%op9 = add i32 %op8, %op5
store i32 %op9, i32* %op1
br label %label_condBB
label_retBB: ; preds = %label_condBB
%op10 = load i32, i32* %op1
store i32 %op10, i32* %op0
%op11 = load i32, i32* %op0
ret i32 %op11
}
(3)有4个basicblock
,对应关系为:
auto bb = BasicBlock::create(module, "entry", mainFun);
对应main
函数中的标签label_entry
auto condBB = BasicBlock::create(module, "condBB", mainFun);
对应标签label_condBB
auto trueBB = BasicBlock::create(module, "trueBB", mainFun);
对应标签label_trueBB
auto retBB = BasicBlock::create(module, "retBB", mainFun);
对应标签label_retBB
问题2: Visitor Pattern
请指出visitor.cpp中,treeVisitor.visit(exprRoot)
执行时,以下几个Node的遍历序列:numberA、numberB、exprC、exprD、exprE、numberF、exprRoot。
序列请按如下格式指明: exprRoot->numberF->exprE->numberA->exprD
答:遍历的树如下所示。从三个visit方法可以看出,遍历序列为:
exprRoot->numberF->exprE->exprD->numberB->numberA->exprC->numberA->exprB
问题3: getelementptr
请给出IR.md
中提到的两种getelementptr用法的区别,并稍加解释:
%2 = getelementptr [10 x i32], [10 x i32]* %1, i32 0, i32 %0
%2 = getelementptr i32, i32* %1 i32 %0
答 :getelementptr
指令用于获取聚合数据结构的子元素的地址。它仅执行地址计算,不访问内存。
- 第一种方法:数组在C中会分割指针,但在LLVM IR中,它只能确定数组类型的大小然后强制转换为指针,但不会分割它们。%1是我们的基址,有两个索引0和%0。因为它是数组,但是我们是通过指针访问它,所以我们需要两个索引:第一个用于分割指针(因为用的指针是[10 x i32],但是我们返回的是一个i32的,所以需要分割),第二个用于索引数组本身(即偏移量)。该方法适用的数组为
int nums[] = {1, 2, 3};
或者int a[10]
,例如在assign.ll
中定义数组a[10]后获取a[0],a[1]用的是这个方式。
define i32 @main() {
label_entry:
%op0 = alloca i32
%op1 = alloca [10 x i32]
%op2 = getelementptr [10 x i32], [10 x i32]* %op1, i32 0, i32 0
store i32 10, i32* %op2
%op3 = load i32, i32* %op2
%op4 = mul i32 %op3, 2
%op5 = getelementptr [10 x i32], [10 x i32]* %op1, i32 0, i32 1
store i32 %op4, i32* %op5
%op6 = load i32, i32* %op5
store i32 %op6, i32* %op0
%op7 = load i32, i32* %op0
ret i32 %op7
}
- 第二种方法:%1做为我们的基址地址,%0做为我们的索引(偏移量),并把计算出来的地址给%2。该方法适用的数组为
int *nums = {1, 2, 3};
实验难点
描述在实验中遇到的问题、分析和解决方案
- 实验过程中需要先看懂助教所给的样例,把
gcd_array.c
中的内容分别和gcd_array.ll
和gcd_array_generator.cpp
对应起来,然后再查看一些LLVM IR的相关资料才能较顺利地完成。 - 在填写
if_hand.ll
文件时,有一句C语句是float a = 5.555;
即要给浮点数a赋值为5.555,样例gcd_array.c
并没有使用浮点数类型,所以就模仿整数赋值方式,得到了如下语句store float 5.555, float* %1
。但是编译运行时报错了,后面查找资料后才知道不能这样写,因为5.555转化成二进制是无限循环的(只能近似表示),而编译器只接受精确的小数,所以需要换一种表示,即store float 0x40163851E0000000, float* %1
。
- 在回答第三个问题的过程中,即思考两种
getelementptr
用法的区别时,查阅了大量的英文文档,才弄明白真正的区别。