cminus_compiler编译原理实验3

问题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会选择跳转到trueBBfalseBBtrueBBfalseBB会无条件跳转到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和第一次索引值决定;而对于后面的索引操作,每增加一个索引,就会使得该索引使用的基本类型和返回的指针的类型去掉一层;

对于第一种用法:

  1. 它给出了两个type和两次索引操作,开始时指针的地址为%1,第二个type表示指针类型为[10 x i32]* ;
  2. 而第一个type[10 x i32]表示第一次索引使用的基本类型,当第一次索引操作是i32 0,那么就将当前地址(基地址)加0 size([10 x i32]);*
  3. 第二次索引操作是i32 %0,则将当前地址加 i32 * %0变量的值,返回一个i32*类型的数给 %2;

对于第二种用法:

  1. 它给出了两个type和一次索引操作,开始时指针的地址为%1,第二个type表示指针类型为 i32* ;
  2. 而第一个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指令创建该变量,然后通过loadstore指令对变量进行多次赋值;

    • 下面的写法也是不合法的:
    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这个值,需要修改为image-20211216095714488

错误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;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值