LLVM -Learn-Work2
@2022-04-28 03:05:00
@sizaif
Work2:如果加法结果大于 100,则将结果改为 100。(基本块操作)
//main.c
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
puts("plz input two numbers");
size_t bufsize = 100;
char* buf = malloc(bufsize);
getline(&buf, &bufsize, stdin);
double f1 = atof(buf);
getline(&buf, &bufsize, stdin);
double f2 = atof(buf);
printf("%.2f + %.2f = %.2f \n", f1, f2, f1+f2);
free(buf);
return 0;
}
一) 思路
整体思想为: 获得fadd
指令的结果,if
语句判断一下结果与100的大小,如果大于100,则修改指令为100
一种思想:
先找到fadd
指令与fadd
指令的后一个指令call printf
- 根据源码,
main
只有一个默认基本块 - 在
fadd
指令前,先存入常数值100.0
- 在
fadd
指令后面(call printf
指令前添加fcmp
判断指令 - 从
call printf
指令开始将默认块一分为:defaut
块和defaut_ret
块 - 插入两个新的基本块
change_prt
和defaut_prt
change_prt
基本块的内容为读取100
, 调用printf
函数输出新的值,跳转到defaut_ret
free内存defaut_prt
基本块的内容为读取fadd
的结果,调用printf
函数输出,跳转到defaut_ret
free内存
关系图如下:
原来IR代码
%20 = load double, double* %5, align 8
%21 = fadd double %19, %20
%22 = call i32 (i8*, ...) @printf(8* noundef getelementptr inbounds ([21 x i8], [21 x i8]* @.str.1, i64 0, i64 0), double noundef %17, double noundef %18, double noundef %21)
%23 = load i8*, i8** %3, align 8
call void @free(i8* noundef %23) #4
ret i32 0
目标IR代码
·····
store double %15, double* %4, align 8
%16 = load double, double* %3, align 8
%17 = load double, double* %4, align 8
%18 = load double, double* %3, align 8
%19 = load double, double* %4, align 8
%ina1 = alloca double, align 8
store double 1.000000e+02, double* %ina1, align 8
%20 = fadd double %18, %19
%inf1 = fcmp ugt double %20, 1.000000e+02
br i1 %inf1, label %change_prt, label %defaut_prt
defaut_ret: ; preds = %defaut_prt, %change_prt
%21 = load i8*, i8** %2, align 8
call void @free(i8* noundef %21) #4
ret i32 0
change_prt: ; preds = %defaut
%22 = load double, double* %ina1, align 8
%23 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([21 x i8], [21 x i8]* @0, i32 0, i32 0), double %16, double %17, double %22)
br label %defaut_ret
defaut_prt: ; preds = %defaut
%24 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([21 x i8], [21 x i8]* @0, i32 0, i32 0), double %16, double %17, double %20)
br label %defaut_ret
}
·····
二) 执行
2.1 初始化(前置)
LLVMContext context;
IRBuilder<> builder(context);
std::unique_ptr<Module> M;
std::unique_ptr<MIRParser> MIR;
SMDiagnostic Err;
/**
* #include "llvm/IRReader/IRReader.h"
* 2022-04-26 03:04:32
* TODO:
* from IR file get Moudle class
* */
M = parseIRFile(StringRef(argv[1]),Err,context);
if(M){
std::cout<<"debug: M is not null"<<std::endl;
// M->print(llvm::outs(),nullptr);
}
else{
std::cout<<"M is null"<<std::endl;
Err.print(argv[0],errs());
}
2.2 找到fadd
指令并添加相关必要指令
/**
* 2022-04-26 03:25:52
* TODO:
* 获取main函数
* */
auto main_fn = M->getFunction(StringRef("main"));
/**
* 2022-04-28 02:14:46
* TODO:
* 找到fadd指令,与fadd的后一个指令
* */
Instruction *in_fadd,*in_prt;
for (inst_iterator I = inst_begin(*main_fn), E = inst_end(*main_fn); I != E; ++I){
if(I->isBinaryOp() && I->getOpcode()==Instruction::FAdd){
// outs() << *I << "\n";
in_fadd = &*I;
in_prt = &*(I.operator++());
}
}
/**
* 2022-04-28 02:42:32
* TODO:
* %20 = load double, double* %5, align 8
* %21 = fadd double %19, %20
* 在fadd指令前面
* 添加以下两个指令:
* <x> = alloca double, align 8
* store double 1.000000e+02, double* <x>, align 8
*
* */
auto f32 = builder.getDoubleTy();
auto const100 = ConstantFP::get(f32,double(100));
builder.SetInsertPoint(in_fadd);
auto in_alloc = builder.CreateAlloca(f32,0,"ina1");
auto in_store = builder.CreateStore(const100,in_alloc);
/**
* 2022-05-03 12:47:30
* TODO:
* 在printf指令前添加条件判断(fcmp)指令
* %21 = call i32 (i8*, ...) @printf(i8* noundef ····
* fcmp:
* <x> = fcmp ugt double <fadd>, 1.000000e+02
* */
builder.SetInsertPoint(in_prt);
auto in_fcmp = builder.CreateFCmpUGT(in_fadd,const100,"inf1");
2.3 切割基本块
/**
* 2022-05-03 12:49:38
* TODO:
* 将main中默认块分为2块: 主体块和默认ret块
* 并添加两个新块: 修改输出块和默认输出块
* */
auto oldblk = &main_fn->getEntryBlock();
oldblk->setName("defaut");
auto bb2 = SplitBlock(oldblk,in_prt);
if(!bb2){
errs()<<"debug: split block wrong! \n";
}else{
bb2->setName("defaut_ret");
}
BasicBlock *change_prt = BasicBlock::Create(context, "change_prt", main_fn);
BasicBlock *defaut_prt = BasicBlock::Create(context, "defaut_prt", main_fn);
切割基本块SplitBlock(oldblk,in_prt);
函数,此时切割效果如下:
# auto bb2 = SplitBlock(oldblk,in_prt);
·······
store double %15, double* %4, align 8
%16 = load double, double* %3, align 8
%17 = load double, double* %4, align 8
%18 = load double, double* %3, align 8
%19 = load double, double* %4, align 8
%ina1 = alloca double, align 8
store double 1.000000e+02, double* %ina1, align 8
%20 = fadd double %18, %19
%inf1 = fcmp ugt double %20, 1.000000e+02
br label %.split
.split: ; preds = %0
%21 = call i32 (i8*, ...) @printf(i8* noundef getelementptr inbounds ([21 x i8], [21 x i8]* @.str.1, i64 0, i64 0), double noundef %18, double noundef %19, double noundef %20)
%22 = load i8*, i8** %3, align 8
call void @free(i8* noundef %23) #4
ret i32 0
}
补充后效果如下:
defuat
·····
store double %15, double* %4, align 8
%16 = load double, double* %3, align 8
%17 = load double, double* %4, align 8
%18 = load double, double* %3, align 8
%19 = load double, double* %4, align 8
%ina1 = alloca double, align 8
store double 1.000000e+02, double* %ina1, align 8
%20 = fadd double %18, %19
%inf1 = fcmp ugt double %20, 1.000000e+02
br label %.split
defaut_ret: ; preds = %defaut
%21 = load i8*, i8** %2, align 8
call void @free(i8* noundef %21) #4
ret i32 0
change_prt: ;
defaut_prt: ;
}
·····
2.4 在块中填充内容
/**
* 2022-05-03 12:51:10
* TODO:
* 修改输出块:
* 读取前面存储默认100值,
* 构造printf函数输出
* */
builder.SetInsertPoint(change_prt);
auto in_load = builder.CreateLoad(f32,in_alloc);
auto i32p = builder.getIntPtrTy(M->getDataLayout(),0);
auto printf_prototype = FunctionType::get(i32p, true);
auto printf_fn = M->getFunction("printf");
if(!printf_fn){
printf_fn = Function::Create(printf_prototype,Function::ExternalLinkage, "printf", M.get());
}
auto format_str = builder.CreateGlobalStringPtr("%.2f + %.2f = %.2f \n");
builder.CreateCall(printf_fn, {format_str,in_prt->getOperand(1),in_prt->getOperand(2),in_load});
builder.CreateBr(bb2);
/**
* 2022-05-03 12:51:57
* TODO:
* 默认输出块:
* 构造printf默认函数输出
* */
builder.SetInsertPoint(defaut_prt);
builder.CreateCall(printf_fn, {format_str,in_prt->getOperand(1),in_prt->getOperand(2),in_fadd});
builder.CreateBr(bb2);
/**
* 2022-05-03 12:52:18
* TODO:
* 构造新的br跳转指令替换默认分块指令
* br label defaue_ret -> br <cond> label A lable B
* */
auto old_cond_br = &*(oldblk->getIterator()->rbegin());
auto new_cond_br = BranchInst::Create(change_prt,defaut_prt,in_fcmp);
ReplaceInstWithInst(old_cond_br,new_cond_br);
/**
* 2022-05-03 14:54:46
* TODO:
* 删除原先printf的调用指令
* */
in_prt->eraseFromParent();
2.5 修改后Module验证并写入文件中
/**
* #include "llvm/IR/Verifier.h"
* 2022-05-02 11:43:34
* TODO:
* 修改完Module后进行验证Module的完整性
* verifyModule(*M,&errs()): true if the module is broken.
* */
if(!llvm::verifyModule(*M,&errs())){
outs()<<"debug: Module verify is ok \n";
// M->print(outs(),nullptr);
}
/**
* #include "llvm/Bitcode/BitcodeWriter.h"
* 2022-05-03 12:55:33
* TODO:
* 将修改后的IR 存储到文件中, 执行
* */
std::error_code EC;
llvm::raw_fd_ostream OS("aftermain.bc", EC,llvm::sys::fs::OpenFlags());
llvm::WriteBitcodeToFile(*M,OS,true);
OS.flush();
2.x) 全部源码
/**
* 2022-04-26 01:02:37
* 2022-05-03 15:55:43
* sizaif
*
* clang++ $(llvm-config --cxxflags --ldflags --system-libs --libs) -o main main.cpp
* ./main main.ll
* lli aftermain.bc
* */
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
#include <llvm/IR/IRBuilder.h>
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/InstIterator.h"
#include "llvm/IR/Instruction.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/InstrTypes.h"
#include "llvm/IR/Verifier.h"
#include "llvm/IRReader/IRReader.h"
#include "llvm/CodeGen/MIRParser/MIRParser.h"
#include "llvm/Support/InitLLVM.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Support/SourceMgr.h"
#include <llvm/Support/raw_os_ostream.h>
#include "llvm/Bitcode/BitcodeWriter.h"
#include <iostream>
#include <string>
#include <fstream>
#include <cstdio>
using namespace llvm;
int main(int argc,char **argv){
LLVMContext context;
IRBuilder<> builder(context);
std::unique_ptr<Module> M;
std::unique_ptr<MIRParser> MIR;
SMDiagnostic Err;
/**
* #include "llvm/IRReader/IRReader.h"
* 2022-04-26 03:04:32
* TODO:
* from IR file get Moudle class
* */
M = parseIRFile(StringRef(argv[1]),Err,context);
if(M){
std::cout<<"debug: M is not null"<<std::endl;
// M->print(llvm::outs(),nullptr);
}
else{
std::cout<<"M is null"<<std::endl;
Err.print(argv[0],errs());
}
/**
* 2022-04-26 03:25:52
* TODO:
* 获取main函数
* */
auto main_fn = M->getFunction(StringRef("main"));
/**
* 2022-04-28 02:14:46
* TODO:
* 找到fadd指令,与fadd的后一个指令
* */
Instruction *in_fadd,*in_prt;
for (inst_iterator I = inst_begin(*main_fn), E = inst_end(*main_fn); I != E; ++I){
if(I->isBinaryOp() && I->getOpcode()==Instruction::FAdd){
// outs() << *I << "\n";
in_fadd = &*I;
in_prt = &*(I.operator++());
}
/**
* 2022-04-28 02:42:32
* TODO:
* %20 = load double, double* %5, align 8
* %21 = fadd double %19, %20
* 在fadd指令前面
* 添加以下两个指令:
* <x> = alloca double, align 8
* store double 1.000000e+02, double* <x>, align 8
*
* */
auto f32 = builder.getDoubleTy();
auto const100 = ConstantFP::get(f32,double(100));
builder.SetInsertPoint(in_fadd);
auto in_alloc = builder.CreateAlloca(f32,0,"ina1");
auto in_store = builder.CreateStore(const100,in_alloc);
/**
* 2022-05-03 12:47:30
* TODO:
* 在printf指令前添加条件判断(fcmp)指令
* %21 = call i32 (i8*, ...) @printf(i8* noundef ····
* fcmp:
* <x> = fcmp ugt double <fadd>, 1.000000e+02
* */
builder.SetInsertPoint(in_prt);
auto in_fcmp = builder.CreateFCmpUGT(in_fadd,const100,"inf1");
outs()<<"debug: fadd op : "<<*in_fadd<<" \n";
outs()<<"debug: alloc op : "<<*in_alloc<<" \n";
outs()<<"debug: store op : "<<*in_store<<" \n";
outs()<<"debug: fcmp op : "<<*in_fcmp<<" \n";
/**
* 2022-05-03 12:49:38
* TODO:
* 将main中默认块分为2块: 主体块和默认ret块
* 并添加两个新块: 修改输出块和默认输出块
* */
auto oldblk = &main_fn->getEntryBlock();
oldblk->setName("defaut");
auto bb2 = SplitBlock(oldblk,in_prt);
if(!bb2){
errs()<<"debug: split block wrong! \n";
}else{
bb2->setName("defaut_ret");
}
BasicBlock *change_prt = BasicBlock::Create(context, "change_prt", main_fn);
BasicBlock *defaut_prt = BasicBlock::Create(context, "defaut_prt", main_fn);
/**
* 2022-05-03 12:51:10
* TODO:
* 修改输出块:
* 读取前面存储默认100值,
* 构造printf函数输出
* */
builder.SetInsertPoint(change_prt);
auto in_load = builder.CreateLoad(f32,in_alloc);
auto i32p = builder.getIntPtrTy(M->getDataLayout(),0);
auto printf_prototype = FunctionType::get(i32p, true);
auto printf_fn = M->getFunction("printf");
if(!printf_fn){
printf_fn = Function::Create(printf_prototype,Function::ExternalLinkage, "printf", M.get());
}
auto format_str = builder.CreateGlobalStringPtr("%.2f + %.2f = %.2f \n");
builder.CreateCall(printf_fn, {format_str,in_prt->getOperand(1),in_prt->getOperand(2),in_load});
builder.CreateBr(bb2);
/**
* 2022-05-03 12:51:57
* TODO:
* 默认输出块:
* 构造printf默认函数输出
* */
builder.SetInsertPoint(defaut_prt);
builder.CreateCall(printf_fn, {format_str,in_prt->getOperand(1),in_prt->getOperand(2),in_fadd});
builder.CreateBr(bb2);
/**
* 2022-05-03 12:52:18
* TODO:
* 构造新的br跳转指令替换默认分块指令
* br label defaue_ret -> br <cond> label A lable B
* */
auto old_cond_br = &*(oldblk->getIterator()->rbegin());
outs()<<"debug: old cond br : "<<*old_cond_br<<" \n";
auto new_cond_br = BranchInst::Create(change_prt,defaut_prt,in_fcmp);
outs()<<"debug: new cond br : "<<*new_cond_br<<" \n";
ReplaceInstWithInst(old_cond_br,new_cond_br);
/**
* 2022-05-03 14:54:46
* TODO:
* 删除原先printf的调用指令
* */
in_prt->eraseFromParent();
/**
* #include "llvm/IR/Verifier.h"
* 2022-05-02 11:43:34
* TODO:
* 修改完Module后进行验证Module的完整性
* verifyModule(*M,&errs()): true if the module is broken.
* */
if(!llvm::verifyModule(*M,&errs())){
outs()<<"debug: Module verify is ok \n";
// M->print(outs(),nullptr);
}
/**
* #include "llvm/Bitcode/BitcodeWriter.h"
* 2022-05-03 12:55:33
* TODO:
* 将修改后的IR 存储到文件中, 执行
* */
std::error_code EC;
llvm::raw_fd_ostream OS("aftermain.bc", EC,llvm::sys::fs::OpenFlags());
llvm::WriteBitcodeToFile(*M,OS,true);
OS.flush();
return 0;
}
2.6 效果
三) 总结
基本块操作涉及到:
-
对基本块的遍历(迭代器应用)
-
对基本块的拆分, 基本块的拆分时,根据方法的使用会将当前指令放到新基本块或旧基本块(注意区分)
-
构造指令插入(可以使用
IRbuilder
) 来构造基本指令与函数或基本块 -
指令删除操作
-
相应的修改后Module 需要进行验证