文章目录
实验要求
- 第一部分: 了解LLVM IR。通过clang生成的.ll,了解LLVM IR与c代码的对应关系。完成1.3
- 第二部分: 了解LightIR。通过助教提供的c++例子,了解LightIR的c++接口及实现。完成2.3
- 第三部分: 理解Visitor Pattern。
- 实验报告:在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 的正确性:
再运行gcd_array.c
查看运行结果:
其输出结果也为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 = alloca i32, align 4
: 在栈上为变量分配一个i32
(4字节整数)的内存块,4字节对齐。%1
是变量的指针。store i32 0, i32* %1, align 4
: 将整数值0
存储到%1
所指向的内存地址。%2
存储1
,%3
存储2
。%4 = load i32, i32* %2, align 4
: 从%2
所指的地址加载整数值到%4
。%6 = icmp slt i32 %4, %5
: 比较%4
与%5
,判断是否%4 < %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
; 注释: 第二个基本块的结束
store i32 3, i32* %3, align 4
: 将3
存储到%3
的内存地址。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 ; 注释: 返回语句
; 注释: 第三个基本块的结束
- 加载值:
%9
从%2
地址加载值,%10
从%3
地址加载值。 %11 = add nsw i32 %9, %10
: 计算%9 + %10
的和,结果存储到%11
。nsw
(No Signed Wrap)指明结果不会溢出。
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 $?
:
运行assign_hand.ll
,输入命令lli assign_hand.ll; echo $?
:
结果都为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 $?
:
运行fun_hand.ll
,输入命令lli fun_hand.ll; echo $?
:
结果都为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 $?
:
运行if_hand.ll
,输入命令lli if_hand.ll; echo $?
:
结果都为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
:
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 $?
:
运行while_hand.ll
,输入命令lli while_hand.ll; echo $?
结果都为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
结果如下:
使用lli assign.ll; echo $?
测试输出结果如下:
结果正确。
共有一个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
如下:
使用lli fun.ll; echo $?
测试输出结果如下:
结果正确。
共有2个basic block:
auto bb = BasicBlock::create(module, "entry", calleeFun);
对应callee
函数中的标记entry
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
如下:
使用lli if.ll; echo $?
测试输出结果如下:
结果正确。
共有4个basic block
:
auto bb = BasicBlock::create(module, "entry", mainFun);
对应标记entry
auto trueBB = BasicBlock::create(module, "trueBB", mainFun);
对应标记trueBB
auto falseBB = BasicBlock::create(module, "falseBB", mainFun);
对应标记falseBB
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
如下:
使用lli while.ll; echo $?
测试输出结果如下:
结果正确。
共有5个basic block
:
auto bb = BasicBlock::create(module, "entry", mainFun);
对应entry
auto loop = BasicBlock::create(module, "loop", mainFun);
对应loop
auto trueBB = BasicBlock::create(module, "trueBB", mainFun);
对应标记trueBB
auto falseBB = BasicBlock::create(module, "falseBB", mainFun);
对应标记falseBB
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
问题3: getelementptr
请给出IR.md
中提到的两种getelementptr用法的区别,并稍加解释:
%2 = getelementptr [10 x i32], [10 x i32]* %1, i32 0, i32 %0
%2 = getelementptr i32, i32* %1 i32 %0
getelementptr [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的元素
getelementptr i32, i32* %1, i32 %0
这行代码用于访问一个单独的 i32
元素。具体解释如下:
- 类型:
i32
是32
位整数类型。 - 指针类型:
i32*
表示指向一个i32
类型元素的指针。%1
是指向某个i32
变量的指针。 - 偏移量:
i32 %0
表示偏移%0
个i32
类型元素(%0
是一个变量,表示偏移的数量)。
总结来说,
第一个指针是[10 x i32],假设第三个元素是n,第四个元素是m,返回的指针类型是[10 x i32],但是要偏移10n+m个单位
第二个指针是i32,偏移%0
实验难点
写llvm文件时和cpp文件时对代码的理解很是费劲。特别是补充cpp文件时与LLVM的对照,要一句句地读懂。
有些地方其实并不是很懂,实验文档给的也不是很多,只能对着样例照葫芦画瓢。
实验反馈
-
很多同学对于环境中的LLVM版本有问题,但我的LLVM版本是14.0,实验过程中也没出现什么问题。
-
实验代码的编写还是比较繁琐的,但是有些部分的内容是要重复编写的,也是慢慢熟练起来了。对于ll文件的编写,我觉得还是要仿照样例那样严谨一些,加入对齐方式等;对于cpp文件中的BB块,如果想要做到类似汇编那样的严谨的话,可以主动添加return的分支,结构会更加完整,当然为了图方便可以直接返回。
-
总而言之,这个实验花费的时间很长,也学到了一些LLVM的基础知识,初步了解了LightIR的用法和原理。