LLVM IR 三部曲之二 --- 创建IR

前言

上一篇文章我们讲了IR的基本语法规则,这篇文章我们讲一下,如何手动去生成IR!
生成IR有以下几种方式:
1、通过c++直接使用Instructions.h文件中的命令来生成IR
2、使用llvm提供的c接口来生成IR LLVM官方文档
3、使用IRBuilder来生成IR IRBuilder官方文档
三种方式,其实这三种方式,最复杂的就在于如何创建IR中的命令,我们查阅2、3中的文档时会发现,LLVM提供分API大部分都是创建Instruction的。
我们分别就1、3方式给出示例(2大家自己看文档吧),教实现一个IR生成器。

1、直接使用Instructions.h中的命令

这种方式可以让我们清楚的看到IR每一步的实现过程,方便我们学习如何生成IR。

1)来实现一个sum函数:(代码中我做了详细的注解)
//1、创建module
Module *createLLVMModule() {
    LLVMContext context ;
    Module *module = new Module("haoyuTestLLVMIR",context);
    { //设置datalayout和三元组
        module ->setDataLayout("e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128");
        module ->setTargetTriple("x86_64-apple-macosx10.14.0");
    }
    //2、创建函数的类型(指定返回值类型、参数类型及数量、是否有可变参数)
    SmallVector<Type *, 2>FunctionArgs;
    //各种类型都是通过get方法来构造对应的实例
    FunctionArgs.push_back(IntegerType::get(module->getContext(), 32)); //32为整型参数Tina及到vector数组中
    FunctionArgs.push_back(IntegerType::get(module->getContext(), 32));

    IntegerType *returnType = IntegerType::get(module->getContext(), 32);//返回值类型
    FunctionType *funcType = FunctionType::get(returnType, FunctionArgs, /*isVarArg*/ false);

    //3、创建一个函数
    // LinkageType是globalValue类下的一个链接类型,所有全局变量、函数都有一个链接类型
    // GlobalValue::ExternalLinkage 表示该函数可以被其他模块引用。
    Function *customFunc = Function::Create(funcType, GlobalValue::ExternalLinkage, /* 函数名 = */"haoyuSum", module);
    customFunc->setCallingConv(CallingConv::C); //CallingConv类是一个枚举类,定义了所有的函数调用公约!文档上有说明。

    //4、存储参数(获取参数的引用)
    Function::arg_iterator argsIT = customFunc->arg_begin();
    Value *param1 = argsIT ++;
    param1->setName("a");

    Value *param2 = argsIT ++;
    param2->setName("b");

    //5、创建基本块(需要制定其所属的function)
    BasicBlock *entryBlock = BasicBlock::Create(module->getContext(),"entry",customFunc,0);

    //6、添加指令,两种方式:1、直接使用具体的指令 2、使用IRBuilder(需要重点研究)
    //6.1)直接使用指令方式(便于呈现原始接口)
    //指令:alloca指令使用(操作变量必须用到指针,同OC是一个道理)
    AllocaInst *ptrA = new AllocaInst(/*要创建的内存空间的类型*/ IntegerType::get(module->getContext(), 32),/*地址空间*/module->getDataLayout().getAllocaAddrSpace(),/*名称*/"a.addr",/*BasicBlock*/entryBlock);
    ptrA->setAlignment(Align(4)); //4字节对齐的32位元素

    AllocaInst *ptrB = new AllocaInst(/*要创建的内存空间的类型*/ IntegerType::get(module->getContext(), 32),/*地址空间*/module->getDataLayout().getAllocaAddrSpace(),/*名称*/"a.addr",/*BasicBlock*/entryBlock);
    ptrB->setAlignment(Align(4));

    //7、使用store命令
    StoreInst *st0 = new StoreInst(param1,ptrA,/* 指定是否是静态存储区 */false,entryBlock);
    st0->setAlignment(Align(4));
    StoreInst *st1 = new StoreInst(param2,ptrB,/* 指定是否是静态存储区 */false,entryBlock);
    st1->setAlignment(Align(4));

    //8、使用load命令
    LoadInst *ld0 = new LoadInst(/*类型*/IntegerType::get(context, 32), ptrA, /*名称*/"",false, entryBlock);
    ld0->setAlignment(Align(4));
    LoadInst *ld1 = new LoadInst(/*类型*/IntegerType::get(context, 32), ptrB, /*名称*/"",false, entryBlock);
    ld1->setAlignment(Align(4));

    //9、添加操作
    BinaryOperator *addRes = BinaryOperator::Create(Instruction::Add, ld0, ld1, "add", entryBlock);
    //10、设置返回值
    ReturnInst::Create(module->getContext(), addRes, entryBlock);
    
    //11、校验生成的IR
    bool Result = llvm::verifyModule(*module);
    if(Result) {
        std::cout << "IR校验通过" << std::endl;
    }
    module->dump();
    return module;
}

dump一下结果是:

; ModuleID = 'haoyuTestLLVMIR'
source_filename = "haoyuTestLLVMIR"
target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx10.14.0"

define i32 @haoyuSum(i32 %a, i32 %b) {
entry:
  %a.addr = alloca i32, align 4
  %a.addr1 = alloca i32, align 4
  store i32 %a, i32* %a.addr, align 4
  store i32 %b, i32* %a.addr1, align 4
  %0 = load i32, i32* %a.addr, align 4
  %1 = load i32, i32* %a.addr1, align 4
  %add = add i32 %0, %1
  ret i32 %add
}

2)创建一个while函数的IR

C++原始代码如下:

int whileTest() {
    int a = 10,i = 20;
    while (i < 10) {
        i=i+1;
        a=a*2;
    }
    return 0;
}

我们IR生成代码:(代码中同样做了详细解释)

void customWhileIR () {
    LLVMContext context ;
    Module *module = new Module("haoyuWhile",context);
    IRBuilder<> builder(context);
    //创建void函数
    FunctionType *funcType = FunctionType::get(builder.getVoidTy(),false);
    Function *customFunc = Function::Create(funcType, Function::ExternalLinkage, "whileTest",module);
    //创建最外层函数的basic
    BasicBlock *entryBlock = BasicBlock::Create(module->getContext(),"entry",customFunc,0);
    //创建br用的basicblock
    BasicBlock *BB1 = BasicBlock::Create(module->getContext(),"label1",customFunc,0);
    BasicBlock *BB2 = BasicBlock::Create(module->getContext(),"label2",customFunc,0);
    BasicBlock *BB3 = BasicBlock::Create(module->getContext(),"label3",customFunc,0);
    //添加Instruction
    // 1) alloca指令
    AllocaInst *ptr1 = new AllocaInst(/*要创建的内存空间的类型*/ IntegerType::get(module->getContext(), 32),/*地址空间*/module->getDataLayout().getAllocaAddrSpace(),/*名称*/"1",/*BasicBlock*/entryBlock);
    AllocaInst *ptr2 = new AllocaInst(builder.getInt32Ty(),module->getDataLayout().getAllocaAddrSpace(),"2",entryBlock);
    AllocaInst *ptr3 = new AllocaInst(builder.getInt32Ty(),module->getDataLayout().getAllocaAddrSpace(),"3",entryBlock);
    // 2) store指令
    auto *IntType = builder.getInt32Ty();
    StoreInst *st1 = new StoreInst(ConstantInt::get(IntType,0),ptr1,entryBlock);
    StoreInst *st2 = new StoreInst(ConstantInt::get(IntType,10),ptr2,entryBlock);
    StoreInst *st3 = new StoreInst(ConstantInt::get(IntType, 20),ptr3,false,entryBlock);
    //3)br指令
    BranchInst::Create(BB1,entryBlock);

    //2.1)设置BB1
    //load指令
    LoadInst *load1 = new LoadInst(IntegerType::get(context, 32), ptr3, /*名称*/"",false, BB1);
    //icmp指令
    ICmpInst *icmp = new ICmpInst(*BB1,ICmpInst::ICMP_SLT,load1,ConstantInt::get(IntType,10) );
    //br
    BranchInst::Create(BB2,BB3,icmp,BB1); //!!!:- 如果使用 BranchInst::Create(BB2,BB3,icmp) 这个构造函数会触发ICmp的assert

    //2.2)设置BB2
    //load指令
    LoadInst *load8 = new LoadInst(IntegerType::get(context, 32), ptr3, /*名称*/"",false, BB2);
    Instruction *add9 = BinaryOperator::Create(Instruction::Add, load8, ConstantInt::get(IntType, 1), "add", BB2);
    StoreInst *stBB2 = new StoreInst(add9,ptr3,BB2);
    LoadInst *load10 = new LoadInst(IntegerType::get(context, 32), ptr2, /*名称*/"",false, BB2);
    Instruction *mul11 = BinaryOperator::Create(Instruction::Mul, load10, ConstantInt::get(IntType, 2), "MUL", BB2);
    StoreInst *stBB3 = new StoreInst(mul11, ptr2,BB2);
    BranchInst::Create(BB1,entryBlock);
//
//    //2.3)设置BB3
    ReturnInst::Create(context,ConstantInt::get(IntType, 0),BB3);
    
    module->dump();
    
    
}

最终生产能的IR如下:

; ModuleID = 'haoyuWhile'
source_filename = "haoyuWhile"

define void @whileTest() {
entry:
  %"1" = alloca i32
  %"2" = alloca i32
  %"3" = alloca i32
  store i32 0, i32* %"1"
  store i32 10, i32* %"2"
  store i32 20, i32* %"3"
  br label %label1
  br label %label1

label1:                                           ; preds = %entry, %entry
  %0 = load i32, i32* %"3"
  %1 = icmp slt i32 %0, 10
  br i1 %1, label %label2, label %label3

label2:                                           ; preds = %label1
  %2 = load i32, i32* %"3"
  %add = add i32 %2, 1
  store i32 %add, i32* %"3"
  %3 = load i32, i32* %"2"
  %MUL = mul i32 %3, 2
  store i32 %MUL, i32* %"2"

label3:                                           ; preds = %label1
  ret i32 0
}

上面的结果是跟我们之前一篇文章中的while生成IR是一样的,可以参考下。

另外在编写IR生成代码时,可能遇到很多命令不知道该如何使用,一个Tip是:去LLVM工程中搜索相关的示例,看一下官方是如何使用的

2、使用IRBuilder来生成IR

IRBuilder是LLVM中专门提供用来生产Instruction命令的,对比上一种方式,使用IRBuilder会更加方便,它提供了更加友好的封装,省去了我们直接一个个手动调用原始命令的繁琐和枯燥!
下面是一段生成代码,同样在代码中给出详细注释:

void createIRWithIRBuilder() {
    LLVMContext Context;
    Module *mod = new Module("sum.ll", Context);
    
    //1、创建IRBuilder
    IRBuilder<> builder(Context);
    //2、创建main函数
    FunctionType *ft = FunctionType::get(builder.getInt32Ty(),false);
    Function *mainfunc = Function::Create(ft, Function::ExternalLinkage, "main", mod);
    //到此为止之创建了main函数,但是函数体内的包含的Instruction没有添加,因此需要添加。
    
    //3、创建基本块(这个基本块是空的无内容)
    BasicBlock *entry = BasicBlock::Create(Context,"entrypoint",mainfunc);
    
    //4、设置插入点:插入点设置成相应BasicBlock,<#后面用builder创建的指令都会追加到这个BasicBlock里了#>
    //!!!: - 理解:上面的方式是通过直接往BasicBloock中添加Instruction方式来构造基本的basicBlock,这里借助IRBuilder方式,往basicBlock中添加命令。
    builder.SetInsertPoint(entry);
    
    //5、添加全局字符串(IR中字符串全部为全局变量,使用数据序列来表示,每个元素是一个char类型)
    Value *helloWorld = builder.CreateGlobalStringPtr("hello world!\n");
    //6、创建put函数
    //1)指定函数参数类型,装在一个数组中`
    std::vector<Type*> putsargs;
    putsargs.push_back(builder.getInt8Ty()->getPointerTo());
    ArrayRef<Type*>  argsRef(putsargs);
    //2)指定函数返回值类型
    FunctionType *putsType = FunctionType::get(builder.getInt32Ty(),argsRef,false);
    //3)创建“函数调用”,而不是创建函数
    FunctionCallee putsFunc = mod->getOrInsertFunction("puts", putsType);
    
    //7、调用函数(<#理解:通过createXXX创建出来的所有指令都在SetInsertPoint后面#>)
    builder.CreateCall(putsFunc,helloWorld); //这是创建方法的指令
    
    //8、创建返回ret指令
    ConstantInt *zero = ConstantInt::get(IntegerType::getInt32Ty(Context), 0);
    builder.CreateRet(zero);
    
    //9、验证。这一步待定!
    llvm::VerifierAnalysis::Result Res;
    Res.IRBroken = llvm::verifyModule(*mod, &dbgs(), &Res.DebugInfoBroken);

    mod->dump();
 
}

生成IR如下:

; ModuleID = 'sum.ll'
source_filename = "sum.ll"

@0 = private unnamed_addr constant [14 x i8] c"hello world!\0A\00", align 1

define i32 @main() {
entrypoint:
  %0 = call i32 @puts(i8* getelementptr inbounds ([14 x i8], [14 x i8]* @0, i32 0, i32 0))
  ret i32 0
}

declare i32 @puts(i8* %0)

综上示例,例举了生成IR的几种方式,以及一些常用命令的使用,更加复杂的IR编写,需要在开发时查阅上面给出的文档。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,898评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,401评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,058评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,539评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,382评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,319评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,706评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,370评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,664评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,715评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,476评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,326评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,730评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,003评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,275评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,683评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,877评论 2 335

推荐阅读更多精彩内容