首页 > 其他分享 >HNU-编译原理lab3-2022级

HNU-编译原理lab3-2022级

时间:2025-01-12 19:29:24浏览次数:3  
标签:i32 create module lab3 HNU 2022 auto builder store

文章目录

实验要求

  1. 第一部分: 了解LLVM IR。通过clang生成的.ll,了解LLVM IR与c代码的对应关系。完成1.3
  2. 第二部分: 了解LightIR。通过助教提供的c++例子,了解LightIR的c++接口及实现。完成2.3
  3. 第三部分: 理解Visitor Pattern。
  4. 实验报告:在report.md回答3个问题

实验设计

问题1: cpp与.ll的对应

请描述你的cpp代码片段和.ll的每个BasicBlock的对应关系。描述中请附上两者代码。

根据 1.2 gcd例子: 利用clang生成的.ll ,我们可以先利用样例进行学习。

输入clang -S -emit-llvm gcd_array.c指令,生成.ll文件,并输入lli gcd_array.ll; echo $?指令,验证 gcd_array.ll 的正确性:

image-20241202204044851

再运行gcd_array.c 查看运行结果:

image-20241202204501744

其输出结果也为18。 因此,可以验证 gcd_array.ll 文件正确地对应了 gcd_array.c 文件。

实验文档给出的IR Reference手册有IR的语法,我们可以先看它的一个实例:

  • easy.c
int main(){
  int a;
  int b;
  a = 1;
  b = 2;
  if(a < b)
    b = 3;
  return a + b;
}
  • easy.ll
; 注释: .ll文件中注释以';'开头
; ModuleID = 'easy.c'                                
source_filename = "easy.c"  
; 注释: target的开始
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"
; 注释: target的结束

; 注释: 全局main函数的定义
; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @main() #0 {
; 注释: 第一个基本块的开始
  %1 = alloca i32, align 4
  %2 = alloca i32, align 4
  %3 = alloca i32, align 4
  store i32 0, i32* %1, align 4
  store i32 1, i32* %2, align 4
  store i32 2, i32* %3, align 4
  %4 = load i32, i32* %2, align 4
  %5 = load i32, i32* %3, align 4
  %6 = icmp slt i32 %4, %5
  br i1 %6, label %7, label %8
; 注释: 第一个基本块的结束

; 注释: 第二个基本块的开始
7:                                                ; preds = %0
  store i32 3, i32* %3, align 4
  br label %8
; 注释: 第二个基本块的结束

; 注释: 第三个基本块的开始
8:                                                ; preds = %7, %0
  %9 = load i32, i32* %2, align 4
  %10 = load i32, i32* %3, align 4
  %11 = add nsw i32 %9, %10
  ret i32 %11                                     ; 注释: 返回语句
; 注释: 第三个基本块的结束
}

attributes #0 = { noinline nounwind optnone uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="all" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }

!llvm.module.flags = !{!0}
!llvm.ident = !{!1}

!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{!"clang version 10.0.1 "}

我们可以先理解这个代码:

基本块 0:初始化变量

; 注释: 第一个基本块的开始
%1 = alloca i32, align 4
%2 = alloca i32, align 4
%3 = alloca i32, align 4
store i32 0, i32* %1, align 4
store i32 1, i32* %2, align 4
store i32 2, i32* %3, align 4
%4 = load i32, i32* %2, align 4
%5 = load i32, i32* %3, align 4
%6 = icmp slt i32 %4, %5
br i1 %6, label %7, label %8
; 注释: 第一个基本块的结束
  1. %1 = alloca i32, align 4: 在栈上为变量分配一个i32(4字节整数)的内存块,4字节对齐。%1是变量的指针。
  2. store i32 0, i32* %1, align 4: 将整数值0存储到%1所指向的内存地址。%2存储1%3存储2
  3. %4 = load i32, i32* %2, align 4: 从%2所指的地址加载整数值到%4
  4. %6 = icmp slt i32 %4, %5: 比较%4%5,判断是否%4 < %5(有符号小于)。
  5. br i1 %6, label %7, label %8: 根据条件%6的结果:
    • 如果为真,跳转到基本块%7
    • 否则,跳转到基本块%8

基本块 7:更新变量

; 注释: 第二个基本块的开始
7:                                                ; preds = %0
store i32 3, i32* %3, align 4
br label %8
; 注释: 第二个基本块的结束
  1. store i32 3, i32* %3, align 4: 将3存储到%3的内存地址。
  2. br label %8: 无条件跳转到基本块%8

基本块 8:计算并返回

; 注释: 第三个基本块的开始
8:                                                ; preds = %7, %0
%9 = load i32, i32* %2, align 4
%10 = load i32, i32* %3, align 4
%11 = add nsw i32 %9, %10
ret i32 %11                                     ; 注释: 返回语句
; 注释: 第三个基本块的结束
  1. 加载值%9%2地址加载值,%10%3地址加载值。
  2. %11 = add nsw i32 %9, %10: 计算%9 + %10的和,结果存储到%11
    • nsw(No Signed Wrap)指明结果不会溢出。
  3. ret i32 %11: 返回%11作为函数结果。

函数属性

attributes #0 = { noinline nounwind optnone uwtable ... }
  • 指定main函数的一组属性(之前已解释)。

更为具体的语法,可阅读IR Reference手册

(1)补充assign_hand.ll

assign.c 文件如下:

int main(){
  int a[10];
  a[0] = 10;
  a[1] = a[0] * 2;
  return a[1];
}

补充的assign_hand.ll如下:

getelementptr指令用于获取数组结构的元素的地址。 它仅执行地址计算,并且不访问内存。

inbounds检查指针是否越界

define dso_local i32 @main() #0{

    %1 = alloca [10 x i32], align 16			;int a[10];
    
    %2 = getelementptr inbounds [10 x i32], [10 x i32]* %1, i32 0, i32 0    ;获得a[0]的地址;
    
    store i32 10, i32* %2, align 4          	; 将10存入a[0]中
    
    %3 = load i32, i32* %2, align 16  			; 取出a[0],存到%3中
    %4 = mul nsw i32 %3, 2  					; 将a[0]的值乘以2,即20存在%4中
    %5 = getelementptr inbounds [10 x i32], [10 x i32]* %1, i32 0, i32 1    ;获取a[1]指针
    store i32 %4, i32* %5, align 16   			; 将%4中的值,即20写入a[1]中
    
    ret i32 %4      							; 返回a[1]
}

验证:

运行assign.c ,输入命令:gcc -o assign assign.c ./assign;echo $?

image-20241203115103372

运行assign_hand.ll ,输入命令lli assign_hand.ll; echo $?

image-20241203115202823

结果都为20,说明补充正确。

(2)补充fun_hand.ll

fun.c 文件如下:

int callee(int a){
  return 2 * a;
}
int main(){
  return callee(110);
}

补充的 fun_hand.ll如下:

define dso_local i32 @callee(i32 %0) #0 {
  %2 = alloca i32, align 4				;分配一个i32变量%2
  store i32 %0, i32* %2, align 4		;将a的值存入%2地址中
  %3 = load i32, i32* %2, align 4		;加载a到%3中
  %4 = mul nsw i32 2, %3				;2*a的值存入%4
  ret i32 %4							;返回%4,即2*a
}

define dso_local i32 @main() #0 {
  %1 = call i32 @callee(i32 110)		;直接调用函数
  ret i32 %1
}

验证:

运行fun.c ,输入命令:gcc -o fun fun.c ./fun;echo $?

image-20241203130305863

运行fun_hand.ll ,输入命令lli fun_hand.ll; echo $?

image-20241203130340883

结果都为220,说明补充正确。

(3)补充if_hand.ll

if.c文件如下:

int main(){
  float a = 5.555;
  if(a > 1)
    return 233;
  return 0;
}

补充的 if_hand.ll如下:

对于5.555的赋值,要使用机器表达形式。

ogt 是"ordered greater than"(有序且大于)。“Ordered” 表示参与比较的两个值都不是 NaN。

define dso_local i32 @main() #0 {
  %1 = alloca i32, align 4								
  %2 = alloca float, align 4							;分配 4 字节对齐的 float 变量%2
  store float 0x40163851E0000000, float* %2, align 4	;将5.555存入%2地址中,即a=5.555
  %3 = load float, float* %2, align 4					;将a取出放到%3中
  %4 = fcmp ogt float %3, 1.0							;将a与1.0进行比较,a>1?
  br i1 %4, label %5, label %6							;大于则跳转到%5,否则跳转到%6

5:                                               
  store i32 233, i32* %1, align 4						;大于则将233存入%1中,跳转到%7
  br label %7

6:                                               
  store i32 0, i32* %1, align 4							;不大于则将0存入%1中,跳转到%7
  br label %7

7:                                              
  %8 = load i32, i32* %1, align 4						;将结果取出放入%8
  ret i32 %8											;返回结果
}

验证:

运行if.c ,输入命令:gcc -o if if.c ./if;echo $?

image-20241203133155070

运行if_hand.ll ,输入命令lli if_hand.ll; echo $?

image-20241203133250901

结果都为233,说明补充正确。

(4)补充while.ll

while.c 文件如下:

int main(){
  int a;
  int i;
  a = 10;
  i = 0;
  while(i < 10){
    i = i + 1;
    a = a + i;
  }
  return a;
}

补充的 while_hand.ll如下:

这里不知道为什么要先分配%1

image-20241203140553170

slt 表示有符号小于

define dso_local i32 @main() #0 {
  %1 = alloca i32, align 4
  %2 = alloca i32, align 4				;分配 4 字节对齐的 i32 整数变量
  %3 = alloca i32, align 4

  store i32 10, i32* %2, align 4    	;a赋值10	
  store i32 0, i32* %3, align 4			;i赋值0
  br label %4							;跳转%4

4:                                               
  %5 = load i32, i32* %3, align 4		;取出i放入%5中 
  %6 = icmp slt i32 %5, 10				;比较 %5(i) 是否小于 10
  br i1 %6, label %7, label %13			;小于则跳到%7,否则跳到%13

7:                                                
  %8 = load i32, i32* %3, align 4			
  %9 = add nsw i32 %8, 1				
  store i32 %9, i32* %3, align 4		;这三行是i=i+1
  
  %10 = load i32, i32* %2, align 4
  %11 = load i32, i32* %3, align 4
  %12 = add nsw i32 %10, %11
  store i32 %12, i32* %2, align 4		;这四行是a=a+1
  br label %4							;循环回去

13:                                               
  %14 = load i32, i32* %2, align 4		
  ret i32 %14							;加载最后结果并返回
}

验证:

运行while.c ,输入命令:gcc -o while while.c ./while;echo $?

image-20241203140128847

运行while_hand.ll ,输入命令lli while_hand.ll; echo $?

image-20241203140356488

结果都为65,说明补充正确。

接下来要补充.cpp文件,同样给出了样例gcd_array_generator.cpp,该cpp程序会生成与gcd_array.c逻辑相同的LLVM IR文件:

#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("Cminus code");  // module name是什么无关紧要
  auto builder = new IRBuilder(nullptr, module);
  Type *Int32Type = Type::get_int32_type(module);

  // 全局数组,x,y
  auto *arrayType = ArrayType::get(Int32Type, 1);
  auto initializer = ConstantZero::get(Int32Type, module);
  auto x = GlobalVariable::create("x", module, arrayType, false, initializer);// 参数解释:  名字name,所属module,全局变量类型type,
  auto y = GlobalVariable::create("y", module, arrayType, false, initializer);//          是否是常量定义(cminus中没有常量概念,应全都是false),初始化常量(ConstantZero类)

  // gcd函数
  // 函数参数类型的vector
  std::vector<Type *> Ints(2, Int32Type);

  //通过返回值类型与参数类型列表得到函数类型
  auto gcdFunTy = FunctionType::get(Int32Type, Ints);

  // 由函数类型得到函数
  auto gcdFun = Function::create(gcdFunTy,
                                 "gcd", module);

  // BB的名字在生成中无所谓,但是可以方便阅读
  auto bb = BasicBlock::create(module, "entry", gcdFun);
  
  builder->set_insert_point(bb);                        // 一个BB的开始,将当前插入指令点的位置设在bb
  
  auto retAlloca = builder->create_alloca(Int32Type);   // 在内存中分配返回值的位置
  auto uAlloca = builder->create_alloca(Int32Type);     // 在内存中分配参数u的位置
  auto vAlloca = builder->create_alloca(Int32Type);     // 在内存中分配参数v的位置 

  std::vector<Value *> args;  // 获取gcd函数的形参,通过Function中的iterator
  for (auto arg = gcdFun->arg_begin(); arg != gcdFun->arg_end(); arg++) {
    args.push_back(*arg);   // * 号运算符是从迭代器中取出迭代器当前指向的元素
  }

  builder->create_store(args[0], uAlloca);  // 将参数u store下来
  builder->create_store(args[1], vAlloca);  // 将参数v store下来

  auto vLoad = builder->create_load(vAlloca);           // 将参数v load上来
  auto icmp = builder->create_icmp_eq(vLoad, CONST_INT(0));  // v和0的比较,注意ICMPEQ

  auto trueBB = BasicBlock::create(module, "trueBB", gcdFun);    // true分支
  auto falseBB = BasicBlock::create(module, "falseBB", gcdFun);  // false分支
  auto retBB = BasicBlock::create(
      module, "", gcdFun);  // return分支,提前create,以便true分支可以br

  auto br = builder->create_cond_br(icmp, trueBB, falseBB);  // 条件BR
  DEBUG_OUTPUT // 我调试的时候故意留下来的,以醒目地提醒你这个调试用的宏定义方法
  builder->set_insert_point(trueBB);  // if true; 分支的开始需要SetInsertPoint设置
  auto uLoad = builder->create_load(uAlloca);
  builder->create_store(uLoad, retAlloca);
  builder->create_br(retBB);  // br retBB

  builder->set_insert_point(falseBB);  // if false
  uLoad = builder->create_load(uAlloca);
  vLoad = builder->create_load(vAlloca);
  auto div = builder->create_isdiv(uLoad, vLoad);  // SDIV - div with S flag
  auto mul = builder->create_imul(div, vLoad);  // MUL - mul
  auto sub = builder->create_isub(uLoad, mul);  // the same
  auto call = builder->create_call(gcdFun, {vLoad, sub}); // 创建call指令
  // {vLoad, sub} - 参数array
  builder->create_store(call, retAlloca);
  builder->create_br(retBB);  // br retBB

  builder->set_insert_point(retBB);  // ret分支
  auto retLoad = builder->create_load(retAlloca);
  builder->create_ret(retLoad);

  // funArray函数
  auto Int32PtrType = Type::get_int32_ptr_type(module);   // 单个参数的类型,指针
  std::vector<Type *> IntPtrs(2, Int32PtrType);           // 参数列表类型
  auto funArrayFunType = FunctionType::get(Int32Type, IntPtrs);   // 函数类型
  auto funArrayFun = Function::create(funArrayFunType, "funArray", module);
  bb = BasicBlock::create(module, "entry", funArrayFun);
  builder->set_insert_point(bb);
  auto upAlloca = builder->create_alloca(Int32PtrType);   // u的存放
  auto vpAlloca = builder->create_alloca(Int32PtrType);   // v的存放
  auto aAlloca = builder->create_alloca(Int32Type);       // a的存放
  auto bAlloca = builder->create_alloca(Int32Type);       // b的存放
  auto tempAlloca = builder->create_alloca(Int32Type);    // temp的存放

  std::vector<Value *> args1;  //获取funArrayFun函数的形参,通过Function中的iterator
  for (auto arg = funArrayFun->arg_begin(); arg != funArrayFun->arg_end(); arg++) {
    args1.push_back(*arg); // * 号运算符是从迭代器中取出迭代器当前指向的元素
  }
  builder->create_store(args1[0], upAlloca);              // 将参数u store下来
  builder->create_store(args1[1], vpAlloca);              // 将参数v store下来

  auto u0pLoad = builder->create_load(upAlloca);          // 读取u
  auto u0GEP = builder->create_gep(u0pLoad, {CONST_INT(0)});  // GEP: 获取u[0]地址
  auto u0Load = builder->create_load(u0GEP);              // 从u[0]地址 读取u[0]
  builder->create_store(u0Load, aAlloca);                 // 将u[0] 写入 a
  auto v0pLoad = builder->create_load(vpAlloca);          // 同上
  auto v0GEP = builder->create_gep(v0pLoad, {CONST_INT(0)});
  auto v0Load = builder->create_load(v0GEP);
  builder->create_store(v0Load, bAlloca);

  auto aLoad = builder->create_load(aAlloca);
  auto bLoad = builder->create_load(bAlloca);
  icmp = builder->create_icmp_lt(aLoad, bLoad);
  trueBB = BasicBlock::create(module, "trueBB", funArrayFun);
  falseBB = BasicBlock::create(module, "falseBB", funArrayFun);
  builder->create_cond_br(icmp, trueBB, falseBB);

  builder->set_insert_point(trueBB);
  builder->create_store(aLoad, tempAlloca);
  builder->create_store(bLoad, aAlloca);
  auto tempLoad = builder->create_load(tempAlloca);
  builder->create_store(tempLoad, bAlloca);
  builder->create_br(falseBB);  // 注意在下一个BB之前要Br一下

  builder->set_insert_point(falseBB);
  aLoad = builder->create_load(aAlloca);
  bLoad = builder->create_load(bAlloca);
  call = builder->create_call(gcdFun, {aLoad, bLoad});
  builder->create_ret(call);


  // main函数
  auto mainFun = Function::create(FunctionType::get(Int32Type, {}),
                                  "main", module);
  bb = BasicBlock::create(module, "entry", mainFun);
  // BasicBlock的名字在生成中无所谓,但是可以方便阅读
  builder->set_insert_point(bb);

  retAlloca = builder->create_alloca(Int32Type);
  builder->create_store(CONST_INT(0), retAlloca);             // 默认 ret 0

  auto x0GEP = builder->create_gep(x, {CONST_INT(0), CONST_INT(0)});  // GEP: 这里为什么是{0, 0}呢? (实验报告相关)
  builder->create_store(CONST_INT(90), x0GEP);
  auto y0GEP = builder->create_gep(y, {CONST_INT(0), CONST_INT(0)});  // GEP: 这里为什么是{0, 0}呢? (实验报告相关)
  builder->create_store(CONST_INT(18), y0GEP);

  x0GEP = builder->create_gep(x, {CONST_INT(0), CONST_INT(0)});
  y0GEP = builder->create_gep(y, {CONST_INT(0), CONST_INT(0)});
  call = builder->create_call(funArrayFun, {x0GEP, y0GEP});           // 为什么这里传的是{x0GEP, y0GEP}呢?

  builder->create_ret(call);
  // 给这么多注释了,但是可能你们还是会弄很多bug
  // 所以强烈建议配置AutoComplete,效率会大大提高!
  // 别人配了AutoComplete,只花1小时coding
  // 你没有配AutoComplete,找method花5小时,debug花5小时,肯定哭唧唧!
  // 最后,如果猜不到某个IR指令对应的C++的函数,建议把指令翻译成英语然后在method列表中搜索一下
  // 最后的最后,这个例子只涉及到了一点基本的指令生成,
  // 对于额外的指令,包括数组,在之后的实验中可能需要大家好好搜索一下思考一下,
  // 还有涉及到的C++语法,可以在gitlab上发issue提问或者向大家提供指导,会有bonus哦!
  // 对于这个例子里的代码风格/用法,如果有好的建议也欢迎提出!
  std::cout << module->print();
  delete module;
  return 0;
}
(5)补充assign_generator.cpp

补充的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"); // module name是什么无关紧要
	auto builder = new IRBuilder(nullptr, module);
	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的开始,将当前插入指令点的位置设在bb

    auto retAlloca = builder->create_alloca(Int32Type); // 在内存中分配返回值的位置
	builder->create_store(CONST_INT(0), retAlloca); // 默认 ret 0
	
    auto *arrayType = ArrayType::get(Int32Type,10);   //数组的定义
	auto aAlloca = builder->create_alloca(arrayType); // 在内存中分配参a[10]的位置

	auto a0GEP = builder->create_gep(aAlloca, {CONST_INT(0), CONST_INT(0)}); //a[0]的地址
	builder->create_store(CONST_INT(10), a0GEP); //将常量 10 存储到 a[0]。

	auto a1GEP = builder->create_gep(aAlloca, {CONST_INT(0), CONST_INT(1)});  //a[1]的地址

	auto a0Load = builder->create_load(a0GEP); //加载 a[0] 的值
	auto mul = builder->create_imul(a0Load, CONST_INT(2)); // a[0] * 2
	builder->create_store(mul, a1GEP); //a[0] * 2存到a[1]
    	
    builder->create_store(mul, retAlloca); //存储结果 mul 到 retAlloca
	auto retLoad = builder->create_load(retAlloca);
	builder->create_ret(retLoad); //加载返回值,并创建 ret 指令返回。
    
	std::cout << module->print();
	delete module;
	return 0;
}

生成的assign.ll结果如下:

image-20241203172215435

使用lli assign.ll; echo $?测试输出结果如下:

image-20241203165311609

结果正确。

共有一个BasicBlock,auto bb = BasicBlock::create(module, "entry", mainFun);
对应标记entry

(6)补充fun_generator.cpp

补充的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"); // module name是什么无关紧要
	auto builder = new IRBuilder(nullptr, module);
	Type *Int32Type = Type::get_int32_type(module);

	// callee function
	// 函数参数类型的vector
	std::vector<Type *> Ints(1, Int32Type);

	// 由函数类型得到函数
	auto calleeFun = Function::create(FunctionType::get(Int32Type, Ints), "callee", module);

	// BB的名字在生成中无所谓,但是可以方便阅读
	auto bb = BasicBlock::create(module, "entry", calleeFun);

	builder->set_insert_point(bb); // 一个BB的开始,将当前插入指令点的位置设在bb

	auto retAlloca = builder->create_alloca(Int32Type); // 在内存中分配返回值的位置
	builder->create_store(CONST_INT(0), retAlloca);		// 默认 ret 0

	auto aAlloca = builder->create_alloca(Int32Type); // 在内存中分配参数a的位置

	std::vector<Value *> args; // 获取callee函数的形参,通过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 load上来
	auto mul = builder->create_imul(aLoad, CONST_INT(2)); // a[0] * 2
	builder->create_store(mul, retAlloca);
	// builder->set_insert_point(retBB); // ret分支
	auto retLoad = builder->create_load(retAlloca);
	builder->create_ret(retLoad);

	// main函数
	auto mainFun = Function::create(FunctionType::get(Int32Type, {}),"main", module);
	bb = BasicBlock::create(module, "entry", mainFun);
	// BasicBlock的名字在生成中无所谓,但是可以方便阅读
	builder->set_insert_point(bb); // 一个BB的开始,将当前插入指令点的位置设在bb

	retAlloca = builder->create_alloca(Int32Type); // 在内存中分配返回值的位置
	builder->create_store(CONST_INT(0), retAlloca);		// 默认 ret 0

	auto call = builder->create_call(calleeFun, {CONST_INT(110)}); //调用函数
	builder->create_ret(call);

	std::cout << module->print();
	delete module;
	return 0;
}

生成的fun.ll如下:

image-20241203171933491

使用lli fun.ll; echo $?测试输出结果如下:

image-20241203172039693

结果正确。

共有2个basic block:

  1. auto bb = BasicBlock::create(module, "entry", calleeFun);
    对应callee函数中的标记entry
  2. bb = BasicBlock::create(module, "entry", mainFun);
    对应main中的标记entry
(7)补充if_generator.cpp

补充的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"); // module name是什么无关紧要
	auto builder = new IRBuilder(nullptr, module);
	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的开始,将当前插入指令点的位置设在bb

	auto retAlloca = builder->create_alloca(Int32Type); // 在内存中分配返回值的位置
	builder->create_store(CONST_INT(0), retAlloca);		// 默认 ret 0

	auto aAlloca = builder->create_alloca(FloatType); // 在内存中分配参a的位置
	builder->create_store(CONST_FP(5.555), aAlloca);  //赋值a=5.555

	auto aLoad = builder->create_load(aAlloca);				  // 将参数a load上来
	auto fcmp = builder->create_fcmp_gt(aLoad, CONST_FP(1)); // a和1的比较,注意ICMPEQ

	auto trueBB = BasicBlock::create(module, "trueBB", mainFun); // true分支
	auto falseBB = BasicBlock::create(module, "falseBB", mainFun); // false分支
    auto retBB = BasicBlock::create(module, "", mainFun); // return分支

	auto br = builder->create_cond_br(fcmp, 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);
	std::cout << module->print();
	delete module;
	return 0;
}

生成的if.ll如下:

image-20241203174038754

使用lli if.ll; echo $?测试输出结果如下:

image-20241203174055607

结果正确。

共有4个basic block

  1. auto bb = BasicBlock::create(module, "entry", mainFun);
    对应标记entry
  2. auto trueBB = BasicBlock::create(module, "trueBB", mainFun);
    对应标记trueBB
  3. auto falseBB = BasicBlock::create(module, "falseBB", mainFun);
    对应标记falseBB
  4. auto retBB = BasicBlock::create(module, "", mainFun);
    对应标记4
(8)补充while_generator.cpp

补充的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"); // module name是什么无关紧要
	auto builder = new IRBuilder(nullptr, module);
	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的开始,将当前插入指令点的位置设在bb

	auto retAlloca = builder->create_alloca(Int32Type); // 在内存中分配返回值的位置
	builder->create_store(CONST_INT(0), retAlloca);		// 默认 ret 0

	auto aAlloca = builder->create_alloca(Int32Type); // 在内存中分配参a的位置
	builder->create_store(CONST_INT(10), aAlloca);    //初值为10
    
	auto iAlloca = builder->create_alloca(Int32Type); // 在内存中分配参a的位置
	builder->create_store(CONST_INT(0), iAlloca);	  //初值为0

	auto aLoad = builder->create_load(aAlloca);				  // 将参数a load上来
	auto iLoad = builder->create_load(iAlloca);				  // 将参数i load上来

	auto loop = BasicBlock::create(module, "loop", mainFun);
	builder->create_br(loop);		 // br loop
	builder->set_insert_point(loop); // the entry for loop

	iLoad = builder->create_load(iAlloca);				   // 将参数i load上来
	auto icmp = builder->create_icmp_lt(iLoad, CONST_INT(10)); // a和1的比较,注意ICMPEQ

	auto trueBB = BasicBlock::create(module, "trueBB", mainFun);   // inside while
	auto falseBB = BasicBlock::create(module, "falseBB", mainFun); // after while
	auto retBB = BasicBlock::create(module, "", mainFun);		   // return分支

	auto br = builder->create_cond_br(icmp, trueBB, falseBB); // 条件BR

	builder->set_insert_point(trueBB); // if true; 分支的开始需要SetInsertPoint设置
	iLoad = builder->create_load(iAlloca);	  // 将参数i load上来
	auto addi = builder->create_iadd(iLoad, CONST_INT(1)); // i=i+1
	builder->create_store(addi, iAlloca);
    
	aLoad = builder->create_load(aAlloca); // 将参数a load上来
	iLoad = builder->create_load(iAlloca); // 将参数i load上来
	auto adda = builder->create_iadd(iLoad, aLoad); // a=a+i
	builder->create_store(adda, aAlloca);
	builder->create_br(loop);			   // br loop
	builder->set_insert_point(loop);	   // the entry for loop

	builder->set_insert_point(falseBB); // after while
	aLoad = builder->create_load(aAlloca); // 将参数a load上来
	builder->create_store(aLoad, retAlloca);
	builder->create_br(retBB); // br retBB

	builder->set_insert_point(retBB); // ret分支
	auto retLoad = builder->create_load(retAlloca);
	builder->create_ret(retLoad);
	std::cout << module->print();
	delete module;
	return 0;
}

生成的while.ll如下:

image-20241203174806800

使用lli while.ll; echo $?测试输出结果如下:

image-20241203174932543

结果正确。

共有5个basic block

  1. auto bb = BasicBlock::create(module, "entry", mainFun);
    对应entry
  2. auto loop = BasicBlock::create(module, "loop", mainFun);
    对应loop
  3. auto trueBB = BasicBlock::create(module, "trueBB", mainFun);
    对应标记trueBB
  4. auto falseBB = BasicBlock::create(module, "falseBB", mainFun);
    对应标记falseBB
  5. auto retBB = BasicBlock::create(module, "", mainFun);
    对应标记13

问题2: Visitor Pattern

请指出visitor.cpp中,treeVisitor.visit(exprRoot)执行时,以下几个Node的遍历序列:numberA、numberB、exprC、exprD、exprE、numberF、exprRoot。

序列请按如下格式指明:
exprRoot->numberF->exprE->numberA->exprD

根据visit()函数中的实现,对于AddSubNode的访问,是先访问右子节点,然后访问左子节点,对于MulDivNode的访问,先访问左子节点,后访问右子节点。

根据main函数的调用:

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;
}

我们可以确定顺序为:

exprRoot->numberF->exprE->exprD->numberB->numberA->exprC->numberA->numberB

971D4B3079FF1F0A10009CD54EED947B

问题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
  1. getelementptr [10 x i32], [10 x i32]* %1, i32 0, i32 %0

这行代码用于访问一个数组中的元素。具体解释如下:

  • 数组类型: [10 x i32] 是一个包含 10 个 32 位整数(i32)的数组。
  • 指针类型: [10 x i32]* 表示指向该数组的指针。%1 是指向数组的指针。
  • 偏移量: i32 0 表示第一个[10 x i32]数组
  • 元素索引: i32 %0 表示在第一个[10 x i32]数组,偏移量为0的元素
  1. getelementptr i32, i32* %1, i32 %0

这行代码用于访问一个单独的 i32 元素。具体解释如下:

  • 类型: i3232 位整数类型。
  • 指针类型: i32* 表示指向一个 i32 类型元素的指针。%1 是指向某个 i32 变量的指针。
  • 偏移量: i32 %0 表示偏移 %0i32 类型元素(%0 是一个变量,表示偏移的数量)。

总结来说,

第一个指针是[10 x i32],假设第三个元素是n,第四个元素是m,返回的指针类型是[10 x i32],但是要偏移10n+m个单位

第二个指针是i32,偏移%0

实验难点

写llvm文件时和cpp文件时对代码的理解很是费劲。特别是补充cpp文件时与LLVM的对照,要一句句地读懂。

有些地方其实并不是很懂,实验文档给的也不是很多,只能对着样例照葫芦画瓢。

实验反馈

  1. 很多同学对于环境中的LLVM版本有问题,但我的LLVM版本是14.0,实验过程中也没出现什么问题。

  2. 实验代码的编写还是比较繁琐的,但是有些部分的内容是要重复编写的,也是慢慢熟练起来了。对于ll文件的编写,我觉得还是要仿照样例那样严谨一些,加入对齐方式等;对于cpp文件中的BB块,如果想要做到类似汇编那样的严谨的话,可以主动添加return的分支,结构会更加完整,当然为了图方便可以直接返回。

  3. 总而言之,这个实验花费的时间很长,也学到了一些LLVM的基础知识,初步了解了LightIR的用法和原理。

标签:i32,create,module,lab3,HNU,2022,auto,builder,store
From: https://blog.csdn.net/2301_76658831/article/details/145097665

相关文章

  • HNU-编译原理lab2-2022级
    文章目录实验要求主要工作实验难点1、Bison文件2、分析树的生成3、理解Bison和Flex的关系①YYSTYPE②归约③%type④%token⑤`yylval`实验设计1、补充`lexical_analyzer.l`2、补充`syntax_analyzer.y`(1)union(2)%token与%type(4)%type的解析规则实验结果验证1、执行命......
  • Hnu-体能测试
    【问题描述】小张老师负责信息学院m(0<m<=100)个学生的k(1<k<=10)项体能测试,体能测试顺序按照编号来,学生测试完一项就在小张老师这里登记一次,现在小张老师手上有n(n<=m*k)个数据,需要根据学生登记的情况,判定出哪些学生还缺体能项目测试,缺的项目是哪一项,然后提醒这些学生尽快完成......
  • 【计算机网络】lab3 802.11 (无线网络帧)
    ......
  • [NOISG2022 Qualification] Dragonfly Solution in O(d log d)
    [NOISG2022Qualification]DragonflySolutioninO(dlogd)提供一个使用线段树合并、栈、树状数组的严格单\(\log\)离线做法。题目大意:给你一棵树,每个点有权值和颜色,每次问你一个从\(1\)开始的路径,求权值不为\(0\)的节点的颜色种类数,并且把所有权值不为\(0\)的节点权......
  • 2022-2023 集训队互测 Round 6 - >.<
    不能包含某一条路径,这个东西看起来很像字符串啊!我们把这些路径插入到trie中,建立AC自动机,然后再把\(n\)个单点插进去。在建出来的AC自动机上跑最短路,钦定某些点不能被进入即可。但是因为字符集是\(\mathcalO(n)\)的,所以直接暴力连边复杂度无法接受。考虑连边的过程,是继......
  • (即插即用模块-Attention部分) 三十四、(2022) FACMA 频率感知跨通道注意力
    文章目录1、Frequency-AwareCross-ModalityAttention2、WeightedCross-ModalityFusionmodule3、代码实现paper:FCMNet:Frequency-awarecross-modalityattentionnetworksforRGB-DsalientobjectdetectionCode:https://github.com/XiaoJinNK/FCMNet1、......
  • Adobe Premiere Pro 2022 下载安装教程,亲测有效
    简介嗨咯,大家好,今天为大家带来的事AdobePremierePro2022下载安装教程,亲测有效。AdobePremierePro是一款领先的视频编辑软件,适用于电影、电视和网络内容创作。该软件结合强大的创意工具、Adobe应用程序和服务的深度集成以及AdobeSensei的AI技术,可帮助用户轻......
  • vs2022遇到“停止生成”的问题
    关闭vs2012,提示必须停止生成,第一次遇见不知道怎么办,查了下,第一种解决了。在使用VisualStudio2022时,如果遇到“停止生成”的问题,可以尝试以下几种解决方案:取消生成:在VisualStudio的主界面,点击“生成”菜单,然后选择“取消”。使用快捷键 Ctrl+Break 来取消当前正......
  • Visual Studio 2022 上架腾讯云 AI 代码助手了
    近期在VisualStudio市场上上架了腾讯云AI代码助手。该插件可以在VisualStudio2022版本(含社区版,版本不低于17.6即可)使用智能辅助编码能力,助力VisualStudio的开发者提高效率。我们在该平台上支持技术对话、代码补全、单元测试生成、解释代码、修复代码等场景。如何安装......
  • 用 2025 年的工具,秒杀了 2022 年的题目。
    你好呀,我是歪歪。前几天打开知乎的时候,在付费咨询模块,我看到了一个差不多两年半前没有回答的技术问题。其实这个问题问的很清晰了,但是当时我拒绝了:虽然过去快两年半的时间,但是我记得还是比较清楚,当时拒绝的理由是如果让我来回答这个问题,我肯定是首选基于Redis来做。大家想......