问题1: cpp与.ll的对应
请描述你的cpp代码片段和.ll的每个BasicBlock的对应关系。描述中请附上两者代码。
assign:
.cpp:
int main() {
auto module = new Module("assign code"); // module name是什么无关紧要
auto builder = new IRBuilder(nullptr, module); /*IRBuilder生成IR的辅助类:提供了独立的接口创建各种 IR 指令,并将它们插入基本块*/
/*Type为IR的类型,该类是所有类型的超类*/
Type *Int32Type = Type::get_int32_type(module);
auto *arrayType = ArrayType::get(Int32Type, 10); /*用ArrayType下的get创建数组,数组元素的类型为Int32Type,数组元素个数为10*/
// main函数 :int main()
//define dso_local i32 @main() {}
auto mainFun = Function::create(FunctionType::get(Int32Type, {}),"main", module);
auto bb = BasicBlock::create(module, "entry", mainFun); //创建 BasicBlock
builder->set_insert_point(bb); //将当前插入指令点的位置设在基本块
auto a = builder->create_alloca(arrayType);
auto a0GEP = builder->create_gep(a, {CONST_INT(0), CONST_INT(0)}); // 得到a[0]的地址
builder->create_store(CONST_INT(10), a0GEP); //对应a[0] = 10; 向a[0]地址写数据
auto a0load=builder->create_load(a0GEP); //获得a[0]的值
auto mul=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(mul,a1GEP); //对应a[1] = a[0] * 2, 给a[1]赋值
builder->create_ret(mul); //创建ret指令,对应return a[1],因为a[i]=mul,所以返回的是mul变量的值
std::cout << module->print();
delete module;
return 0;
}
.ll:
define i32 @main() {
label_entry:
%op0 = alloca [10 x i32]
%op1 = getelementptr [10 x i32], [10 x i32]* %op0, i32 0, i32 0
store i32 10, i32* %op1
%op2 = load i32, i32* %op1
%op3 = mul i32 %op2, 2
%op4 = getelementptr [10 x i32], [10 x i32]* %op0, i32 0, i32 1
store i32 %op3, i32* %op4
ret i32 %op3
}
对应关系:
-
.cpp只创建了一个基本块:
bb = BasicBlock::create(module, "entry", mainFun)
,对应.ll中的标签为label_entry
的基本块。 -
.cpp与.ll中基本块内容对应如下:
auto mainFun = Function::create( FunctionType::get(Int32Type, {}),"main", module //define i32 @main() { ); auto bb = BasicBlock::create(module, "entry", mainFun); //label_entry: builder->set_insert_point(bb); ----------------------------------------a[0] = 10;----------------------------------------------------- auto a = builder->create_alloca(arrayType); // %op0 = alloca [10 x i32] auto a0GEP = builder->create_gep( // %op1 = getelementptr [10 x i32], [10 x a, {CONST_INT(0), CONST_INT(0)}); // i32]* %op0, i32 0, i32 0 builder->create_store(CONST_INT(10), a0GEP); // store i32 10, i32* %op1 ----------------------------------------a[1] = a[0] * 2;----------------------------------------------- auto a0load=builder->create_load(a0GEP); // %op2 = load i32, i32* %op1 auto mul=builder->create_imul(a0load,CONST_INT(2)); // %op3 = mul i32 %op2, 2 auto a1GEP=builder->create_gep( // %op4 = getelementptr [10 x i32], [10 x a,{CONST_INT(0), CONST_INT(1)}); // i32]* %op0, i32 0, i32 1 builder->create_store(mul,a1GEP); // store i32 %op3, i32* %op4 ----------------------------------------return a[1];--------------------------------------------------- builder->create_ret(mul); // ret i32 %op3 }
fun:
.cpp:
int main() {
auto module = new Module("if code"); // module name是什么无关紧要
auto builder = new IRBuilder(nullptr, module); /*IRBuilder生成IR的辅助类:提供了独立的接口创建各种 IR 指令,并将它们插入基本块*/
/*Type为IR的类型,该类是所有类型的超类*/
Type *Int32Type = Type::get_int32_type(module);
// int callee(int a)函数 :int main()
std::vector<Type *> Ints(1, Int32Type);// Ints保存所有函数参数类型
//通过返回值类型与参数类型列表得到函数类型,返回值类型为Int32Type,形参类型列表为Ints
auto calleeFunTy = FunctionType::get(Int32Type, Ints);
// 由函数类型得到函数
auto calleeFun = Function::create(calleeFunTy, "callee", module);/*创建并返回Function,gcdFunTy为待创建函数类型*/
auto bb = BasicBlock::create(module, "entry", calleeFun); //创建 BasicBlock
builder->set_insert_point(bb); //将当前插入指令点的位置设在基本块
auto retAlloca = builder->create_alloca(Int32Type); // 在内存中分配返回值的位置
auto aAlloca = builder->create_alloca(Int32Type); // 在内存中分配参数a的位置
std::vector<Value *> args; // 获取gcd函数的形参,通过Function中的迭代器
for (auto arg = calleeFun->arg_begin(); arg != calleeFun->arg_end(); arg++) {/*遍历函数的形参表*/
args.push_back(*arg); // * 号运算符是从迭代器中取出迭代器当前指向的元素,将该元素加入到args中
}
builder->create_store(args[0], aAlloca); // 将参数a的值写入aAlloca中
auto aLoad = builder->create_load(aAlloca); // 将参数a下载到aload上
auto mul = builder->create_imul(aLoad, CONST_INT(2)); //创建乘法指令: 2 * a
//return 2 * a;
builder->create_store(mul, retAlloca); // 将参数a的值写入aAlloca中
auto retLoad=builder->create_load(retAlloca); //加载返回值
builder->create_ret(retLoad);
// main函数 :int main()
//define dso_local i32 @main() {}
auto mainFun = Function::create(FunctionType::get(Int32Type, {}),"main", module);
bb = BasicBlock::create(module, "entry", mainFun); //创建 BasicBlock
builder->set_insert_point(bb); //将当前插入指令点的位置设在基本块
//return callee(110);
retAlloca = builder->create_alloca(Int32Type);
auto call = builder->create_call(calleeFun, {CONST_INT(110)}); // 为什么这里传的是{x0GEP, y0GEP}呢?
builder->create_ret(call);
std::cout << module->print();
delete module;
return 0;
}
.ll:
define i32 @callee(i32 %arg0) {
label_entry:
%op1 = alloca i32
%op2 = alloca i32
store i32 %arg0, i32* %op2
%op3 = load i32, i32* %op2
%op4 = mul i32 %op3, 2
store i32 %op4, i32* %op1
%op5 = load i32, i32* %op1
ret i32 %op5
}
define i32 @main() {
label_entry:
%op0 = alloca i32
%op1 = call i32 @callee(i32 110)
ret i32 %op1
}
对应关系:
-
.cpp创建了两个函数:callee函数和main函数,它们各有一个基本块,两个基本块都是由
bb = BasicBlock::create(module, "entry", calleeFun/mainFun)
创建的; -
.cpp与.ll中基本块内容对应如下:
std::vector<Type *> Ints(1, Int32Type); auto calleeFunTy = FunctionType::get(Int32Type, Ints); auto calleeFun = Function::create(calleeFunTy, "callee", module); //define i32 @callee(i32 %arg0) { auto bb = BasicBlock::create(module, "entry", calleeFun); //label_entry: builder->set_insert_point(bb); auto retAlloca = builder->create_alloca(Int32Type); // %op1 = alloca i32 auto aAlloca = builder->create_alloca(Int32Type); // %op2 = alloca i32 std::vector<Value *> args; for (auto arg = calleeFun->arg_begin(); arg != calleeFun->arg_end(); arg++) { args.push_back(*arg); } builder->create_store(args[0], aAlloca); // store i32 %arg0, i32* %op2 -----------------------------------------------return 2 * a;------------------------------------------- auto aLoad = builder->create_load(aAlloca); // %op3 = load i32, i32* %op2 auto mul = builder->create_imul(aLoad, CONST_INT(2)); // %op4 = mul i32 %op3, 2 builder->create_store(mul, retAlloca); // store i32 %op4, i32* %op1 auto retLoad=builder->create_load(retAlloca); // %op5 = load i32, i32* %op1 builder->create_ret(retLoad); // ret i32 %op5 -----------------------------------------------int main()---------------------------------------------- auto mainFun = Function::create( FunctionType::get(Int32Type, {}),"main", module); //define i32 @main() { bb = BasicBlock::create(module, "entry", mainFun); //label_entry: builder->set_insert_point(bb); -----------------------------------------------return callee(110);------------------------------------- retAlloca = builder->create_alloca(Int32Type); // %op0 = alloca i32 auto call = builder->create_call(calleeFun, {CONST_INT(110)}); // %op1 = call i32 @callee(i32 110) builder->create_ret(call); // ret i32 %op1 }
if:
.cpp:
int main() {
auto module = new Module("if code"); // module name是什么无关紧要
auto builder = new IRBuilder(nullptr, module); /*IRBuilder生成IR的辅助类:提供了独立的接口创建各种 IR 指令,并将它们插入基本块*/
/*Type为IR的类型,该类是所有类型的超类*/
Type *Int32Type = Type::get_int32_type(module);
auto Float = Type::get_float_type(module);
// main函数 :int main()
auto mainFun = Function::create(FunctionType::get(Int32Type, {}),"main", module);
auto bb = BasicBlock::create(module, "entry", mainFun); //创建 BasicBlock
builder->set_insert_point(bb); //将当前插入指令点的位置设在基本块
auto retAlloca = builder->create_alloca(Int32Type); // 在内存中分配返回值的位置
//float a = 5.555;
auto aAlloca = builder->create_alloca(Float); // a的存放
//store float 0x40163851E0000000, float* %1, align 4
builder->create_store(CONST_FP(5.555), aAlloca); // 创建store指令:CONST_FP()创建常量5.555,将5.555写入aAlloca中
//%2 = load float, float* %1, align 4
auto aLoad = builder->create_load(aAlloca); // 读取a
//%3=fcmp ogt float %2, 1.0
auto fcmp = builder->create_fcmp_gt(aLoad, CONST_FP(1.0)); // v和0的比较,注意ICMPEQ,CONST_INT(0)为生成一个0常量
//br i1 %3, label %4, label %5
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(fcmp, trueBB, falseBB); // create_cond_br创建条件指令br,条件icmp为真时,进入基本块trueBB,否则进入块falseBB
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; 分支的开始需要SetInsertPoint设置
builder->create_store(CONST_INT(233), retAlloca);//设置返回值为0
builder->create_br(retBB); // br retBB
/*ret分支: 进行return*/
builder->set_insert_point(retBB); // 将要返回的值从内存加载出来
auto retLoad = builder->create_load(retAlloca);
builder->create_ret(retLoad);//创建ret指令
std::cout << module->print();
delete module;
return 0;
}
.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 233, i32* %op0
br label %label4
label4: ; preds = %label_trueBB, %label_falseBB
%op5 = load i32, i32* %op0
ret i32 %op5
}
对应关系:
-
.cpp创建了四个基本块,它们标签为
label_entry,label_trueBB: ,label_falseBB,label4
。其中label_entry
会选择跳转到trueBB
和falseBB
;trueBB
和falseBB
会无条件跳转到label4
。 -
.cpp与.ll中基本块内容对应如下:
auto mainFun = Function::create (FunctionType::get(Int32Type, {}),"main", module); //define i32 @main() { auto bb = BasicBlock::create(module, "entry", mainFun);//label_entry: builder->set_insert_point(bb); ----------------------------------------float a = 5.555;----------------------------------------------- auto retAlloca = builder->create_alloca(Int32Type); // %op0 = alloca i32 auto aAlloca = builder->create_alloca(Float); // %op1 = alloca float builder->create_store(CONST_FP(5.555), aAlloca); // store float 0x40163851e0000000, float* %op1 auto aLoad = builder->create_load(aAlloca); // %op2 = load float, float* %op1 ----------------------------------------if else ------------------------------------------------------- auto fcmp = builder->create_fcmp_gt( aLoad, CONST_FP(1.0)); // %op3 = fcmp ugt float %op2,0x3ff000000000000 auto trueBB = BasicBlock::create(module, "trueBB", mainFun); auto falseBB = BasicBlock::create(module, "falseBB", mainFun); auto retBB = BasicBlock::create(module, "", mainFun); auto br = builder->create_cond_br( // br i1 %op3, label %label_trueBB, label fcmp, trueBB, falseBB); // label %label_falseBB ----------------------------------------if(a > 1) return 233;------------------------------------------ builder->set_insert_point(trueBB); //label_trueBB: builder->create_store(CONST_INT(233), retAlloca); // store i32 233, i32* %op0 builder->create_br(retBB); // br label %label4 ----------------------------------------else return 0;------------------------------------------------- builder->set_insert_point(falseBB); //label_falseBB: builder->create_store(CONST_INT(233), retAlloca); // store i32 233, i32* %op0 builder->create_br(retBB); // br label %label4 ----------------------------------------retBB---------------------------------------------------------- builder->set_insert_point(retBB); //label4: auto retLoad = builder->create_load(retAlloca); // %op5 = load i32, i32* %op0 builder->create_ret(retLoad); // ret i32 %op5
while:
.cpp:
int main() {
auto module = new Module("if code"); // module name是什么无关紧要
auto builder = new IRBuilder(nullptr, module); /*IRBuilder生成IR的辅助类:提供了独立的接口创建各种 IR 指令,并将它们插入基本块*/
/*Type为IR的类型,该类是所有类型的超类*/
Type *Int32Type = Type::get_int32_type(module);
// main函数 :int main()
//define dso_local i32 @main() {}
auto mainFun = Function::create(FunctionType::get(Int32Type, {}),"main", module);
auto bb = BasicBlock::create(module, "entry", mainFun); //创建 BasicBlock
builder->set_insert_point(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); // 创建store指令:CONST_FP()创建常量10,将10写入aAlloca中
builder->create_store(CONST_INT(0), iAlloca); // 创建store指令:CONST_FP()创建常量0,将0写入iAlloca中
// br whileBB
auto whileBB = BasicBlock::create(module, "while", mainFun); // while分支
builder->create_br(whileBB);
//while分支
builder->set_insert_point(whileBB); //分支的开始需要SetInsertPoint设置
auto iLoad = builder->create_load(iAlloca); // 读取i
auto icmp = builder->create_icmp_lt(iLoad, CONST_INT(10)); // i和10的比较,CONST_INT(10)为生成一个10常量
// while(i < 10)
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(icmp, trueBB, falseBB); // create_cond_br创建条件指令br,条件icmp为真时,进入基本块trueBB,否则进入块falseBB
// if true; 分支的开始需要SetInsertPoint设置
builder->set_insert_point(trueBB);
iLoad = builder->create_load(iAlloca); // 读取i
auto add=builder->create_iadd(iLoad,CONST_INT(1));//i+1
builder->create_store(add, iAlloca); // i=i+1
auto aLoad = builder->create_load(aAlloca); // 读取a
auto add2=builder->create_iadd(aLoad,add);//a+i
builder->create_store(add2, aAlloca); // a=a+i
builder->create_br(whileBB); // br retBB
// if false; 分支的开始需要SetInsertPoint设置
builder->set_insert_point(falseBB);
builder->create_br(retBB); // br retBB
/*ret分支: 进行return*/
builder->set_insert_point(retBB); // 将要返回的值从内存加载出来
aLoad = builder->create_load(aAlloca); // 读取a
builder->create_store(aLoad, retAlloca); // 将a的值赋给retAlloca
auto retLoad = builder->create_load(retAlloca);
builder->create_ret(retLoad);//创建ret指令 :return a;
std::cout << module->print();
delete module;
return 0;
}
.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_while
label_while: ; preds = %label_entry, %label_trueBB
%op3 = load i32, i32* %op2
%op4 = icmp slt i32 %op3, 10
br i1 %op4, label %label_trueBB, label %label_falseBB
label_trueBB: ; preds = %label_while
%op5 = load i32, i32* %op2
%op6 = add i32 %op5, 1
store i32 %op6, i32* %op2
%op7 = load i32, i32* %op1
%op8 = add i32 %op7, %op6
store i32 %op8, i32* %op1
br label %label_while
label_falseBB: ; preds = %label_while
br label %label9
label9: ; preds = %label_falseBB
%op10 = load i32, i32* %op1
store i32 %op10, i32* %op0
%op11 = load i32, i32* %op0
ret i32 %op11
}
对应关系:
-
.cpp创建了五个基本块:
label_entry,label_while,label_trueBB,label_falseBB,label9
,label_entry会直接跳转到label_while;label_trueBB为while循环内部;label_falseBB为跳出循环;label9为函数返回; -
.cpp与.ll中基本块内容对应如下:
auto mainFun = Function::create( FunctionType::get(Int32Type, {}),"main", module //define i32 @main() { ); auto bb = BasicBlock::create(module, "entry", mainFun); //label_entry: builder->set_insert_point(bb); auto retAlloca = builder->create_alloca(Int32Type); // %op0 = alloca i32 auto aAlloca = builder->create_alloca(Int32Type); // %op1 = alloca i32 auto iAlloca = builder->create_alloca(Int32Type); // %op2 = alloca i32 builder->create_store(CONST_INT(10), aAlloca); // store i32 10, i32* %op1 builder->create_store(CONST_INT(0), iAlloca); // store i32 0, i32* %op2 auto whileBB = BasicBlock::create(module, "while", mainFun); builder->create_br(whileBB); // br label %label_while -----------------------------------------------whileBB------------------------------------------------- builder->set_insert_point(whileBB); //label_while: auto iLoad = builder->create_load(iAlloca); // %op3 = load i32, i32* %op2 auto icmp = builder->create_icmp_lt(iLoad, CONST_INT(10)); // %op4 = icmp slt i32 %op3, 10 auto trueBB = BasicBlock::create(module, "trueBB", mainFun); // auto falseBB = BasicBlock::create(module, "falseBB", mainFun);// auto retBB = BasicBlock::create(module, "", mainFun); // auto br = builder->create_cond_br(icmp, trueBB, falseBB); // br i1 %op4, label %label_trueBB, // label %label_falseBB ----------------------------------------------trueBB--------------------------------------------------- builder->set_insert_point(trueBB); //label_trueBB: iLoad = builder->create_load(iAlloca); // %op5 = load i32, i32* %op2 auto add=builder->create_iadd(iLoad,CONST_INT(1)); // %op6 = add i32 %op5, 1 builder->create_store(add, iAlloca); // store i32 %op6, i32* %op2 auto aLoad = builder->create_load(aAlloca); // %op7 = load i32, i32* %op1 auto add2=builder->create_iadd(aLoad,add); // %op8 = add i32 %op7, %op6 builder->create_store(add2, aAlloca); // store i32 %op8, i32* %op1 builder->create_br(whileBB); // br label %label_while ----------------------------------------------falseBB-------------------------------------------------- builder->set_insert_point(falseBB); //label_falseBB: builder->create_br(retBB); // br label %label9 ----------------------------------------------retBB---------------------------------------------------- builder->set_insert_point(retBB); //label9: aLoad = builder->create_load(aAlloca); // %op10 = load i32, i32* %op1 builder->create_store(aLoad, retAlloca); // store i32 %op10, i32* %op0 auto retLoad = builder->create_load(retAlloca); // %op11 = load i32, i32* %op0 builder->create_ret(retLoad); // ret i32 %op11
问题2: Visitor Pattern
请指出visitor.cpp中,treeVisitor.visit(exprRoot)
执行时,以下几个Node的遍历序列:numberA、numberB、exprC、exprD、exprE、numberF、exprRoot。
下图为treeVisitor.visit(exprRoot)
执行时调用函数关系图:
分析如下:
执行时编译器会根据node
类型的不同,调用不同的visit
函数(该函数进行了重载):
-
若是
AddSubNode
类型,调用的visit
会先访问右子结点,再访问左子节点:int visit(AddSubNode& node) override { auto right = node.rightNode.accept(*this); auto left = node.leftNode.accept(*this); ... }
-
若是
MulDivNode
类型,调用的visit
会先访问左子结点,再访问右子节点:int visit(MulDivNode& node) override { auto left = node.leftNode.accept(*this); ... }
-
若是
NumberNode
类型,调用的visit
会直接返回子节点:int visit(NumberNode& node) override { return node.number; }
所以在treeVisitor.visit(exprRoot)
执行时,就会按照上图画的调用顺序进行Node的遍历,访问序列如下(不显示回溯过程中二次访问的节点):
exprRoot->numberF->exprE->exprD->numberB->numberA->exprc->numberA->numberB
问题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
解释:
注意第一次索引操作的偏移量计算由第一个type和第一次索引值决定;而对于后面的索引操作,每增加一个索引,就会使得该索引使用的基本类型和返回的指针的类型去掉一层;
对于第一种用法:
- 它给出了两个type和两次索引操作,开始时指针的地址为%1,第二个type表示指针类型为[10 x i32]* ;
- 而第一个type[10 x i32]表示第一次索引使用的基本类型,当第一次索引操作是i32 0,那么就将当前地址(基地址)加0 size([10 x i32]);*
- 第二次索引操作是i32 %0,则将当前地址加 i32 * %0变量的值,返回一个i32*类型的数给 %2;
对于第二种用法:
- 它给出了两个type和一次索引操作,开始时指针的地址为%1,第二个type表示指针类型为 i32* ;
- 而第一个type i32 表示第一次索引使用的基本类型,当第一次索引操作是i32 %0,那么就将当前地址(基地址)加 %0变量的值 size(i32);返回一个i32**类型的数给 %2;
区别:
- 第一个指令是计算int型的数组
[10]
中第%0
个元素的地址,它的类型为i32*
;而第二个指令是从一个指针i32*
出发计算偏移量为[%0] * size(i32)
的地址,它的类型为i32*
;所以它们计算地址的起始地址不同; - 对于第二个指令,由于起始地址为一个指针,无法判断计算出的地址是否是合法的;而第一个指令是的起始地址是一个数组,可以很容易知道这个数组的地址范围,这样就可以判断计算出的地址是否是合法的;
实验难点
- 在使用getelementptr指令时遇到问题:
该指令的使用格式是:<result> = getelementptr <type>, <type>* <ptrval> [, <type> <idx>]
,一开始我不知道第一个type,即第一个索引<idx>
使用的基本类型的作用,而且对于后面的索引计算也不是很理解。
该指令在手写assign.ll中需要用到,为了完成实验,我通过手册了解了getelementptr指令:该指令可以进行多次索引操作,但每次索引的idx乘以的type不一样。第一个索引的偏移量的是由第一个索引的值idx1和第一个type
指定的基本类型共同确定的;而对于后面的索引操作,每增加一个索引,就会使得该索引使用的基本类型和返回的指针的类型去掉一层;
比如:
-
在手写.ll时,对于一些用法不理解,如alloca和store后面要加一个align,add和mul等操作后面会加 nuw/nsw,在实验过程中通过阅读llvm手册得到答案:
**align:**进行对齐,即若一个结构中含有int和char,虽然char只有一字节,,为了对齐,会分配给char四个字节。
nuw: No Unsigned Wrap,若add发生无符号溢出,则add的值为一个poison value
nsw: No Signed Wrap,若add发生有符号溢出,则add的值为一个poison value
**poison value :**当提供非法操作数时,许多指令不会立即引起未定义行为,而是返回一个poison value。所以产生这个值,就是代码有问题,但程序并不会终止,依旧执行。
定义一个函数
define dso_local i32 @name()#0
:dso_local
是一个Runtime Preemption(运行时抢占)说明符,表明该函数会在所在的文件以及包含的头文件内解析符号;#0
指出了该函数的属性组。一个属性组可包含多个属性; -
在手写.ll时不知道哪些写法是合法的哪些是不合法的,通过实验中修复报的一些错误,我知道了一些.ll的不合法情况:
- 下面的for循环是不合法的,它每次循环时执行了第7条指令,本意是使变量i不断加1,不过这样操作是对%i进行了多次赋值,而在IR中每个变量是静态单一赋值(SSA)的,即每个变量使用前必须定义,且只能赋值一次:
1:label1: 2: %i=add i32 %1, 1 3: br label2 4:label2: ... 5: br %cmp, label %t, label %f 6:t: 7: %i=add i32 %i,1 ... 8: br %cmp,label %label2, label %break
若想在循环中对变量%i重复赋值(即实现可变变量),可以将该变量变为栈分配的局部变量,即使用
alloca
指令创建该变量,然后通过load
和store
指令对变量进行多次赋值;- 下面的写法也是不合法的:
1:%1=alloca i32, align 4 : 2:store i32 5.555, i32* %1, align 4 3:%2=icmp sgt i32 %1, 1
如上为我最开始写的if.ll的部分指令,在测试时会报如下错误:
错误1:
store指令是向内存中写入数据,而内存中的数据存储是十六进制的,所以不能放入十进制的5.555这个值,需要修改为
错误2:
报错地点为第3条指令,报错信息为使用icmp指令比较两个变量的值时,这两个变量的值的类型不是一样的,显示%1的类型是float*(从这个报错我才意识到alloca出的变量都是指针形式,它们的值其实是指向的地址,而不是地址中的值),所以当需要使用alloca出的变量时,需要先用load指令将该变量的值取出来:
-
未命名局部变量怎么编号:
未命名局部变量:以带前缀的无符号数字值表示,比如
%1
、%2
,它们按顺序编号,函数参数、未命名基本块都会增加计数。
1)如果一个基本块被给与一个标签名,那么未命名局部变量从该标签的下一个编号开始:
2)如果一个基本块没有被给与一个标签名,那么未命名局部变量从编号0开始,注意函数参数、未命名基本块都会增加计数!如下图:
从函数参数开始进行编号,该函数只有一个参数,所以函数参数命令为0,由于函数的第一个基本块没有命名,所以为该基本块编号%1,所以接下来的未命名变量应该从编号2开始,而不是编号1;
实验反馈
-
通过这次实验,我知道了llvm的中间表达式IR的各种指令,知道IR的特点是基于SSA(做优化也是基于SSA的),以及如何写一个.ll文件;
-
知道了IR代码结构:
- Module:LLVM程序由模块组成,每个输入程序都对应一个模块,模块包括函数、全局变量和符号表。
- Function:被Module所包含,LLVM的Function包含函数名、函数的返回值和参数类型,Function内部则包含一个或多个BasicBlock。
- BasicBlock:BasicBlock由许多Instruction组成,它必须有一个进入标签和一条结束指令,结束指令一般是br跳转指令或者ret返回执行,控制流只能从基本块的 第一个指令 进入该块。
- Instruction:就是指令,Instruction被包含在BasicBlock中,一个BasicBlock可以有多条Instruction。
-
完成手写.ll后,我将手写的和用命令生成的.ll进行了对比,发现用命令生成的.ll中会创建更多的变量和操作,有些变量和操作可以省略的,基于此我想是不是可以做一种优化,然后我上网解到LLVM对IR是根据生成的控制流和数据流进行优化处理的:
- **删除公共子表达式:**如果表达式
x op y
先前已被计算过,且计算值没有发生改变,则删掉这次的x op y
操作; - **删除无用代码:**无用代码为其计算结果永远不会被使用的语句,比如自动生成的.ll文件在函数中会生成一个返回变量,但是可能这个函数不需要返回任何值;
- **常量合并:**如果在编译时刻推导出一个表达式的值是常量,就可以使用该常量来替代这个表达式;
- **代码移动:**如果一个循环内的表达式的计算结果是不变的,那么将这个表达式提到循环外面来;
- **强度削弱:**用较快的操作代替较慢的操作,如用 加 代替 乘 ;
- **删除归纳变量:**对于一个变量x ,如果存在一个正的或负的常数c使得每次x被赋值时它的值总增加c ,那么x就称为归纳变量。在沿着循环运行时,如果有一组归纳变量的值的变化保持步调一致,常常可以将这组变量删除为只剩一个
- **删除公共子表达式:**如果表达式
-
通过 -O3 选项对这个IR进行优化,比如使用命令
clang -S -emit-llvm -O3 test.c
:
-------------------------------------------------main函数--------------------------------------------------
int main(void) {
x[0] = 90;
y[0] = 18;
return funArray(x, y);
}
-------------------------------------------main函数 优 化 前------------------------------------------------
define dso_local i32 @main() #0 {
%1 = alloca i32, align 4
store i32 0, i32* %1, align 4
store i32 90, i32* getelementptr inbounds ([1 x i32], [1 x i32]* @x, i64 0, i64 0), align 4
store i32 18, i32* getelementptr inbounds ([1 x i32], [1 x i32]* @y, i64 0, i64 0), align 4
%2 = call i32 @funArray(i32* getelementptr inbounds ([1 x i32], [1 x i32]* @x, i64 0, i64 0), i32* getelementptr inbounds ([1 x i32], [1 x i32]* @y, i64 0, i64 0))
ret i32 %2
}
---------------------------------------------main函数 优 化 后----------------------------------------------
define dso_local i32 @main() local_unnamed_addr #2 {
store i32 90, i32* getelementptr inbounds ([1 x i32], [1 x i32]* @x, i64 0, i64 0), align 4, !tbaa !2
store i32 18, i32* getelementptr inbounds ([1 x i32], [1 x i32]* @y, i64 0, i64 0), align 4, !tbaa !2
ret i32 18
}
//优化1:删除无用代码,将变量%1去掉,因为%1原本是为了存放返回结果的变量,而这里的返回结果不是从内存中取出来的,它是已知的,可以直接返回;
//优化2:常量合并,原来是返回funArray(x, y)函数的结果,而该函数的返回就是y[0],所以去掉call操作,直接返回y[0]的值(该值是store进去的,在寄存器中,所以不需要从y[0]地址中取出该值)
-------------------------------------------------gcd函数--------------------------------------------------
int gcd (int u, int v) {
if (v == 0) return u;
else return gcd(v, u / v );
}
--------------------------------------------gcd函数 优 化 前------------------------------------------------
define dso_local i32 @gcd(i32 %0, i32 %1) #0 {
%3 = alloca i32, align 4
%4 = alloca i32, align 4
%5 = alloca i32, align 4
store i32 %0, i32* %4, align 4
store i32 %1, i32* %5, align 4
%6 = load i32, i32* %5, align 4
%7 = icmp eq i32 %6, 0
br i1 %7, label %8, label %10
8: ; preds = %2
%9 = load i32, i32* %4, align 4
store i32 %9, i32* %3, align 4
br label %16
10: ; preds = %2
%11 = load i32, i32* %5, align 4
%12 = load i32, i32* %4, align 4
%13 = load i32, i32* %5, align 4
%14 = sdiv i32 %12, %13
%15 = call i32 @gcd(i32 %11, i32 %14)
store i32 %15, i32* %3, align 4
br label %16
16: ; preds = %10, %8
%17 = load i32, i32* %3, align 4
ret i32 %17
}
----------------------------------------------gcd函数优化后(O1)---------------------------------------------
define dso_local i32 @gcd(i32 %0, i32 %1) local_unnamed_addr #0 {
2:
//优化1,在块%2中,将变量%1去掉,因为%1原本是为了存放返回结果的变量,而这里没有返回操作
//优化2,在块%2中,有重复的操作,它首先将参数u,v的值传给两个变量,然后再load操作取出两个变量的值,而且块1中只用到了参数u的值,所以删去store和load的操作,直接使用参数v的值(存在%1中)和0进行比较;
%3 = icmp eq i32 %1, 0
br i1 %3, label %7, label %4
4: ; preds = %2
//优化3,未优化时,块%10中,它先store将%0 %1放入内存中,再load使用,所以删去重复的操作,直接使用%0 %1进行除法
%5 = sdiv i32 %0, %1
%6 = call i32 @gcd(i32 %1, i32 %5)
br label %7
7: ; preds = %2, %4
/*优化4:更改条件分支结构,减少块的个数,优化前块%8计算的是v==0时的返回值,然后跳转到%16;块%10计算的是v!=0时的返回值,然后跳转到%16;优化后直接用phi得到返回值:
如果是从块%4跳转过来的,那么返回值为%6,即v!=0下的返回值
如果是从块%2跳转过来的,那么返回值为%0,即v==0下的返回值
*/
%8 = phi i32 [ %6, %4 ], [ %0, %2 ]
ret i32 %8
}
----------------------------------------------gcd函数优化后(O2)---------------------------------------------
define dso_local i32 @gcd(i32 %0, i32 %1) local_unnamed_addr #0 {
%3 = icmp eq i32 %1, 0
br i1 %3, label %9, label %4
4: ; preds = %2, %4
//优化:将递归的函数调用展开为一系列一般的指令
%5 = phi i32 [ %7, %4 ], [ %1, %2 ] ;如果是从块%2跳转过来的,那么取%1,即v
;如果是从块%4跳转过来的(相当于递归调用一次),那么第二个参数取%7
%6 = phi i32 [ %5, %4 ], [ %0, %2 ] ;如果是从块%2跳转过来的,那么取%1,即u
;如果是从块%4跳转过来的(相当于递归调用一次),那么第一个参数取%5,即上一层函数的第二 ;个参数值
%7 = sdiv i32 %6, %5
%8 = icmp eq i32 %7, 0
br i1 %8, label %9, label %4 ;相当于递归出口
9: ; preds = %4, %2
%10 = phi i32 [ %0, %2 ], [ %5, %4 ]
ret i32 %10
}
-
phi : SSA是LLVM IR的一种特性,SSA为每个变量赋予一个编号,如果一个基本快有多个前驱,而每个前驱都对一个x进行定值了,分别编号为x1,x2,…,xn,这时候需要Phi指令根据之前执行的是哪一个BB选择使用xi。
phi格式为:
phi <ty> [<val0>, <label0>], [<val1>, <label1>]
,将这个变量可以使用哪些前驱的变量值通过**[ ]列举出来,其中的label0, label1就代表之前执行的BB的label,而与每一个label成对出现的val0, val1就是可供Phi指令选择的值。** -
优化级别 -O1 对程序做部分编译优化: 1)尝试将条件跳转转换为等价的无分支型式;2)试图把尽可能多的指令移动到条件分支前;3)删除无用代码;4)代码移动;5)4)强度削弱,将表达式换种更简便的计算形式 … -O2 比O1更高级的选项,进行更多的优化:1)将递归的函数调用展开为一系列一般的指令;2)删除公共子表达式;3)通过对全局数据流的分析,识别并排出无用的对空指针的检查 … -O3 比O2更高级的选项,使用伪寄存器网络,普通函数的内联,以及针对循环的更多优化 -
在LLVM中优化以pass形式实现, 每一个pass代表一种优化. pass分为两类, 一类是分析pass, 负责收集信息供其它pass使用, 使程序可视化; 另一类是变换pass, 改变程序的数据流和控制流的。实验5中将会涉及编写pass来优化IR;