问题1: cpp与.ll的对应 请描述你的cpp代码片段和.ll的每个BasicBlock的对应关系。描述中请附上两者代码。
assign 对应的.ll代码如下:
1 2 3 4 5 6 7 8 9 10 11 define i32 @main() #0 {     %1 = alloca [10 x i32]    ;int a[10]     %2 = getelementptr inbounds [10 x i32], [10 x i32]* %1, i32 0, i32 0        ;a[0] addr         %3 = getelementptr inbounds [10 x i32], [10 x i32]* %1, i32 0, i32 1        ;a[1] addr     store i32 10, i32* %2     ;a[0] = 10     %4 = load i32, i32* %2    ;%4 = a[0]     %5 = mul i32 %4,2         ;%5 = a[0]*2     store i32 %5, i32* %3     ;a[1] = %5 = a[0]*2     %6 = load i32, i32* %3    ;%6 = a[1]     ret i32 %6                ;return a[1] } 
这一段程序仅有一个函数,一个BasicBlock。仿照gcd_array.cpp的写法,创建Module和BasicBlock,将.ll中的指令使用IRBuilder插入到BasicBlock的指令链表当中。与.ll中指令的对应关系在以下的cpp代码片段的注释中说明。cpp
代码片段如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include  "BasicBlock.h"  #include  "Constant.h"  #include  "Function.h"  #include  "IRBuilder.h"  #include  "Module.h"  #include  "Type.h"  #include  <iostream>  #include  <memory>  #ifdef  DEBUG   #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" );                      auto  builder = new  IRBuilder (nullptr , module );                                Type *Int32Type = Type::get_int32_type (module );     auto  mainTy = FunctionType::get (Int32Type, {});                          auto  main = Function::create (mainTy, "main" , module );                 auto  BB = BasicBlock::create (module , "BasicBlock1"  , main);     builder->set_insert_point (BB);                                                auto  *arrayType = ArrayType::get (Int32Type, 10 );                         auto  a = builder->create_alloca (arrayType);                              auto  a0P = builder->create_gep (a, {CONST_INT (0 ), CONST_INT (0 )});         auto  a1P = builder->create_gep (a, {CONST_INT (0 ), CONST_INT (0 )});         builder->create_store ({CONST_INT (10 )}, a0P);                             auto  a0 = builder->create_load (a0P);                                     auto  temp = builder->create_imul (a0, {CONST_INT (2 )});                    builder->create_store (temp, a1P);                                        auto  a1 = builder->create_load (a1P);            builder->create_ret (a1);                                                      std::cout << module ->print ();     delete  module ;     return  0 ; } 
make之后运行程序进行验证,生成的指令和.ll中的指令相同。
fun 对应的.ll代码如下:
1 2 3 4 5 6 7 8 define i32 @callee(i32 %0) #0 {     %2 = mul i32 %0, 2               ;%2 = a*2     ret i32 %2 } define i32 @main() #0 {     %1 = call i32 @callee(i32 110)   ;%1 = callee(110)     ret i32 %1                       ;return } 
共有两个函数,相应有两个BasicBlock,分别插入指令。与.ll中指令的对应关系在注释中说明,相应的cpp代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 int  main ()     auto  module  = new  Module ("fun" );     auto  builder = new  IRBuilder (nullptr , module );          Type *Int32Type = Type::get_int32_type (module );     auto  calleeTy = FunctionType::get (Int32Type, {Int32Type});                 auto  callee = Function::create (calleeTy, "callee" , module );      auto  BB = BasicBlock::create (module , "callee_BasicBlock"  , callee);     builder->set_insert_point (BB);          std::vector<Value *> args;                                                 for  (auto  arg = callee->arg_begin (); arg != callee->arg_end (); arg++) {         args.push_back (*arg);                                                  }     auto  mul = builder->create_imul (args[0 ], CONST_INT (2 ));                  builder->create_ret (mul);          auto  mainTy = FunctionType::get (Int32Type, {});     auto  main = Function::create (mainTy, "main" , module );      auto  BB1 = BasicBlock::create (module , "main_BasicBlock"  , main);     builder->set_insert_point (BB1);          auto  callret = builder->create_call (callee, {CONST_INT (110 )});            builder->create_ret (callret);                                                  std::cout << module ->print ();     delete  module ;     return  0 ; } 
运行程序,生成的指令和.ll中的指令相同。
if 对应的.ll代码如下:
1 2 3 4 5 6 7 8 9 10 11 define i32 @main() #0 {     %1 = alloca float                    ;%1 = float a addr     store float 0x40163851E0000000, float* %1     %2 = load float, float* %1           ;%2 = a     %3 = fcmp ugt float %2, 1.0          ;a>1        br i1 %3, label %4, label %5         ;if(a>1) goto 4 else goto 5 4:     ret i32 233 5:     ret i32 0 } 
本段指令有3个BasicBlock,相比与前两个多了fcmp和br跳转指令。相应的.cpp文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 int  main ()     auto  module  = new  Module ("if" );                      auto  builder = new  IRBuilder (nullptr , module );                                Type *Int32Type = Type::get_int32_type (module );     auto  mainTy = FunctionType::get (Int32Type, {});                          auto  main = Function::create (mainTy, "main" , module );     auto  BBEntry = BasicBlock::create (module , "entry"  , main);     builder->set_insert_point (BBEntry);           Type *FloatType = Type::get_float_type (module );                          auto  aP = builder->create_alloca (FloatType);                             builder->create_store (CONST_FP (5.555 ),aP);                               auto  a = builder->create_load (aP);                                       auto  fcmp = builder->create_fcmp_gt (a, CONST_FP (1.0 ));                        auto  BBTrue = BasicBlock::create (module , "true"  , main);     auto  BBFalse = BasicBlock::create (module , "false" , main);     builder->create_cond_br (fcmp, BBTrue, BBFalse);                               builder->set_insert_point (BBTrue);     builder->create_ret (CONST_INT (233 ));     builder->set_insert_point (BBFalse);     builder->create_ret (CONST_INT (0 ));          std::cout << module ->print ();     delete  module ;     return  0 ; } 
运行程序验证:
while 对应的.ll代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 define i32 @main() #0 {     %1 = alloca i32        ;%1 = a addr     %2 = alloca i32        ;%2 = i addr     store i32 0, i32* %2   ;i = 0     store i32 10, i32* %1  ;a = 10     br label %3              ;if i<10 3:     %4 = load i32, i32* %2        ;%4 = i     %5 = icmp slt i32 %4, 10      ;i < 10?     br i1 %5, label %6, label %10      6:     %7 = add nsw i32 %4, 1        ;i + 1     store i32 %7, i32* %2         ;i = i + 1     %8 = load i32, i32* %1        ;%8 = a     %9 = add nsw i32 %7, %8       ;%9 = a + i     store i32 %9, i32* %1         ;a = a + i     br label %3      10:     ret i32 %9                    ;return a } 
以上的.ll程序共有四个BasicBlock,逐个添加指令即可。.cpp中的BasicBlock与.ll的对应在注释中说明。.cpp的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 int  main ()     auto  module  = new  Module ("while" );                      auto  builder = new  IRBuilder (nullptr , module );                                Type *Int32Type = Type::get_int32_type (module );     auto  mainTy = FunctionType::get (Int32Type, {});                          auto  main = Function::create (mainTy, "main" , module );     auto  BBEntry = BasicBlock::create (module , "entry"  , main);     auto  BBWhile = BasicBlock::create (module , "while"  , main);     auto  BBTrue = BasicBlock::create (module , "true"  , main);     auto  BBFalse = BasicBlock::create (module , "false"  , main);          builder->set_insert_point (BBEntry);     auto  aP = builder->create_alloca (Int32Type);                             auto  iP = builder->create_alloca (Int32Type);                             builder->create_store (CONST_INT (0 ), iP);                                 builder->create_store (CONST_INT (10 ), aP);                                builder->create_br (BBWhile);                                                  builder->set_insert_point (BBWhile);     auto  temp = builder->create_load (iP);     auto  icmp = builder->create_icmp_lt (temp, CONST_INT (10 ));     builder->create_cond_br (icmp, BBTrue, BBFalse);                               builder->set_insert_point (BBTrue);     auto  newi = builder->create_iadd (temp, CONST_INT (1 ));                    builder->create_store (newi, iP);                                         auto  a = builder->create_load (aP);     auto  newa = builder->create_iadd (newi, a);                               builder->create_store (newa, aP);                                         builder->create_br (BBWhile);                                                  builder->set_insert_point (BBFalse);     builder->create_ret (newa);                                                    std::cout << module ->print ();     delete  module ;     return  0 ; } 
运行程序验证:
以上的.ll代码和.cpp编译运行后的.ll代码运行后均能产生正确的结果,与原.c程序的逻辑相同.
问题2: Visitor Pattern 请指出visitor.cpp中,treeVisitor.visit(exprRoot)执行时,以下几个Node的遍历序列:numberA、numberB、exprC、exprD、exprE、numberF、exprRoot。
根据visitor.cpp中的内容,每个节点会返回自身的引用,visitor会进行访问该节点,计算该节点的值,计算的方式是访问其左右节点并获取值,然后根据节点的操作类型完成值的计算,然后返回。子节点的值的获取也是相同的,直到访问到数值节点,就直接返回相应的数值。可以看出访问的过程是自顶向下递归访问的过程。main函数中创建的计算树如下:
根据visit()函数中的实现,对于AddSubNode的访问,是先访问右子节点,然后访问左子节点,对于MulDivNode的访问,先访问左子节点,后访问右子节点。因此可以得出visitor.cpp中访问该树的序列为:
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  
第一种用法:
指针类型为[10 x i32]*,指向的数据类型为[10 x i32],因此首先用i32 0表示偏移为0,这表示直接取第一个[10 x i32]数组,然后的i32 %0表示在第一个[10 x i32]数组内,偏移%0的元素地址。
第二种用法:
指针类型为i32*,%1表示的是数组的起始地址,偏移量为%0,直接取出了数组偏移%0位置的元素地址。
区别:
在第一种用法中,指针类似于指针数组,首先确定在这个指针数组上的偏移,才能得到一个数组的指针,然后通过偏移找到元素的地址。而在第二种用法中,直接对数组的指针进行偏移,找到元素的地址。
当定义全局数组或结构体时,定义的是指针,例如在给出的gcd_array.c中声明的全局数组x,y,就是[1 x i32]*类型,因此取出元素时使用的是第一种用法。
1 2 3 4 @x = common dso_local global [1  x i32] zeroinitializer, align 4  getelementptr inbounds ([1  x i32], [1  x i32]* @x, i64 0 , i64 0 ) 
实验难点 1.编写.ll 在编写if.c对应的.ll时,提示error: floating point constant invalid for type,不能用浮点常量给浮点数赋值。经查询,这是因为浮点数5.55不能用浮点数精确表示,因此不能直接给浮点数赋值。如果是4.0等可以精确表示的数,就可以直接赋值。此处只能使用十六进制的浮点数机器表示进行赋值,为0x40163851E0000000。
编写while.c对应的.ll时注意到,编号顺序必须是连续的,除了if对应的块跳转语句可以出现不连续的编号,其他编号都必须是连续的。因此br语句中的跳转编号应该最后填写。
编写.ll后,与.c源文件产生的.ll文件进行了比较,clang产生的.ll文件中的指令更多,对于值进行了更多的存储和取出的操作,但是逻辑上是和自行编写的.ll指令是相同的。自行编写时,临时变量直接使用寄存器存储,还有一些寄存器值也重复使用,相当于对直接产生的.ll指令优化后的结果。
2.编写cpp 编写cpp时,由于对接口不熟悉,创建对应的指令有些困难,在经过阅读gcd_array.cpp中创建指令的部分和整体结构,仿照并逐个指令完成assign.cpp后,后三个cpp代码的编写才比较顺利。
创建比较指令时,指令名使用了lightIR中的ugt来表示小于,编译时提示不存在这条指令,从lightIR.h中找到,比较的指令使用的是下面的表示:
1 2 3 4 5 6 7 8 9 10 public :    enum  CmpOp      {         EQ,          NE,          GT,          GE,          LT,          LE       }; 
因此修改相应的比较指令为以下形式:
1 2 auto  fcmp = builder->create_fcmp_gt (a, CONST_FP (1.0 )); auto  icmp = builder->create_icmp_lt (temp, CONST_INT (10 ));
实验反馈 通过本次实验学习了llvm,LightIR相关知识。对于LightIR的接口,文档内容没有特别清晰,通过结合实例和.h文件中的内容才逐渐熟悉。编写.ll文件时,注意到了与clang编译代码的不同,自行编写可以在许多位置直接进行优化,而编译器可能第一步只能生成未优化的代码。对于自行编写的.ll指令,使用C++生成的过程也相对较为简洁。最后通过visitor.cpp了解了访问者模式,对于这种模式比较陌生,但是通过实例理解了其工作原理和访问过程。