Ubuntu18.04 编译安装llvm-clang

背景知识

LLVM和GCC的区别
传统编译器
传统编译器的工作原理基本上都是三段式的,可以分为前端(Frontend)、优化器(Optimizer)、后端(Backend)。前端负责解析源代码,检查语法错误,并将其翻译为抽象的语法树(Abstract Syntax Tree)。优化器对这一中间代码进行优化,试图使代码更高效。后端则负责将优化器优化后的中间代码转换为目标机器的代码,这一过程后端会最大化的利用目标机器的特殊指令,以提高代码的性能。
事实上,不光静态语言如此,动态语言也符合上面这个模型,例如Java。Java Virtual Machine也利用上面这个模型,将Java代码翻译为Java bytecode。
这一模型的好处是,当我们要支持多种语言时,只需要添加多个前端就可以了。当需要支持多种目标机器时,只需要添加多个后端就可以了。对于中间的优化器,我们可以使用通用的中间代码。
这种三段式的结构还有一个好处,开发前端的人只需要知道如何将源代码转换为优化器能够理解的中间代码就可以了,他不需要知道优化器的工作原理,也不需要了解目标机器的知识。这大大降低了编译器的开发难度,使更多的开发人员可以参与进来。
虽然这种三段式的编译器有很多有点,并且被写到了教科书上,但是在实际中这一结构却从来没有被完美实现过。做的比较好的应该属Java和.NET虚拟机。虚拟机可以将目标语言翻译为bytecode,所以理论上讲我们可以将任何语言翻译为bytecode,然后输入虚拟机中运行。但是这一动态语言的模型并不太适合C语言,所以硬将C语言翻译为bytecode并实现垃圾回收机制的效率是非常低的。
GCC也将三段式做的比较好,并且实现了很多前端,支持了很多语言。但是上述这些编译器的致命缺陷是,他们是一个完整的可执行文件,没有给其它语言的开发者提供代码重用的接口。即使GCC是开源的,但是源代码重用的难度也比较大。
LLVM
LLVM最初是Low Level Virtual Machine的缩写,定位是一个虚拟机,但是是比较底层的虚拟机。它的出现正是为了解决编译器代码重用的问题,LLVM一上来就站在比较高的角度,制定了LLVM IR这一中间代码表示语言。LLVM IR充分考虑了各种应用场景,例如在IDE中调用LLVM进行实时的代码语法检查,对静态语言、动态语言的编译、优化等。
LLVM与GCC在三段式架构上并没有本质区别。LLVM与其它编译器最大的差别是,它不仅仅是Compiler Collection,也是Libraries Collection。举个例子,假如说我要写一个XYZ语言的优化器,我自己实现了PassXYZ算法,用以处理XYZ语言与其它语言差别最大的地方。而LLVM优化器提供的PassA和PassB算法则提供了XYZ语言与其它语言共性的优化算法。那么我可以选择XYZ优化器在链接的时候把LLVM提供的算法链接进来。LLVM不仅仅是编译器,也是一个SDK。

编译安装llvm

1.安装build-essential(要的是里面的make和gcc),cmake,svn

sudo apt-get install build-essential
sudo apt install cmake
sudo apt-get install subversion

2.下载llvm源代码
进入官网https://releases.llvm.org/download.html,这里我下载的5.0.0
在这里插入图片描述
3.解压(注意这里要解压两次)

xz -d 文件
tar -xvf 文件

把cfe改为clang,llvm改为llvm,将clang放入llvm/tools文件夹下,并创建一个LLVM,将llvm放入,并创建build,test文件夹
在这里插入图片描述
注:可能涉及到的开锁命令:sudo chown 用户名 文件夹/ -R
4.build目录下:cmake ../llvm -DLLVM_TARGETS_TO_BUILD=X86 -DCMAKE_BUILD_TYPE=Release
make -j4(这里我多次出现了Makefile151的问题,有两种可能,一个是虚拟机内存不够,另外一个是llvm版本与Ubuntu当前版本不兼容)

make install

如图,安装成功

手写pass(这里原理还不是很明白)

1.准备输入文件,点开test,新建input.c

int add(int a,int b)
{
	return a+b;
}

int sub()
{
	int a = 1;
	int b = 2;
	int c = b-a;

	return c;
}

同目录下:clang -O0 -S -emit-llvm input.c -o input.ll
其中-O0是指optimazation 0,也就是优化等级为0的意思。
也可以用下面这条输出机器码类型:clang -O0 -c -emit-llvm input.c -o input.ll
input.ll:
在这里插入图片描述
2.(1)在llvm/lib/Transforms文件夹下创建一个文件,这里取名如下,自定义的pass就放在这里
在这里插入图片描述
从Hello文件夹下复制三个文件到OpcodeCounter下,并修改对应的文件名
在这里插入图片描述
(2)修改OpcodeCounter.cpp内容:

#include "llvm/IR/Function.h"
#include "llvm/Pass.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm;

namespace//这个所谓的匿名namespace,实际就是一个子类,下面写了继承自FunctionPass,说明咱们搞的这个类输入是一个Function
{
	// OpcodeCounter - The first implementation, without getAnalysisUsage.
	struct OpcodeCounter : public FunctionPass 
	{
		static char ID; // Pass identification, replacement for typeid
		OpcodeCounter() : FunctionPass(ID) {}//构造函数

		bool runOnFunction(Function &F) override
		{
			std::map<std::string,int> opcode_map;//一个类似python中dict的类型,不了解python就理解成结构体

			errs() << "Function name: ";
			errs().write_escaped(F.getName()) << '\n';//显示遍历的函数名字
			for(Function::iterator bb = F.begin(),e = F.end();bb!=e;bb++)//在funciton中遍历basicblock
			{
				errs() << "BasicBlock name: "<< bb->getName() <<"\n";
				errs() << "BasicBlock size: "<< bb->size() <<"\n";//显示basicblock的名字和大小
				for(BasicBlock::iterator i = bb->begin(),i2 = bb->end();i!=i2;i++)//在bb中遍历statement
				{
					errs() << "              "<< *i<<"\n";
					if(opcode_map.find(i->getOpcodeName())==opcode_map.end())//找statement中的运算符,为了数ll文件中各个运算符出现的次数
					{
						opcode_map[i->getOpcodeName()] = 1;//第一次见到某个运算符置1,以后再见到就+1
					}
					else
					{
						opcode_map[i->getOpcodeName()]+=1;
					}
					//examples of user and use
					Instruction *inst = dyn_cast<Instruction>(i); 
					if(inst->getOpcode() == Instruction :: Add)//如果见到运算符是add,下面先显示用到它的语句,再显示它所调用的语句,注意一个用指针,一个用地址
					{
						for(User *U :inst ->users())
						{
							if(Instruction *Inst = dyn_cast<Instruction>(U))
							{
								errs()<<"result of add used in : "<<*Inst <<"\n";
							}
						}
						for(Use &U :inst ->operands())
						{
							Value *v =U.get();
							errs()<<"input of add originate from : " <<*v<<"\n";
						}
					}
				}

			}
			errs()<<"------------------------------\n";//这里把前面数出来的各个运算符的个数显示出来
			errs()<<"summary:\n";
			std::map<std::string,int>::iterator i = opcode_map.begin();
			std::map<std::string,int>::iterator e = opcode_map.end();
			while(i!=e)
			{
				errs()<<"number of "<< i->first << ":"<< i->second <<"\n";
				i++;
			}
			errs()<<"\n";
			opcode_map.clear();
			return false;
		}
	};
}

char OpcodeCounter::ID = 0;
static RegisterPass<OpcodeCounter> X("OpcodeCounter", "OpcodeCounter Pass");//在RegisterPass中注册我们自定义的这个pass

(3)改Transform文件夹下的CMakeList.txt
在最后加一行add_subdirectory(OpcodeCounter)告诉llvm我们加了这么一个文件
第二个是我们自己新建的OpecodCounter文件夹里面的CMakeList.txt
在这里插入图片描述
第一处为同文件夹下exports的名字,第二处为将来通过llvm的opt命令最终生成的.so文件起的名字,第三处为同文件夹下cpp的名字。
(4)生成so文件
在build目录下执行make命令
在这里插入图片描述
5)用so文件优化ll文件
在test目录下:

opt -load /home/czh/Desktop/LLVM/build/lib/LLVMOpcodeCounter.so -OpcodeCounter input.ll

输出结果为:
在这里插入图片描述
在这里插入图片描述

https://www.cnblogs.com/zuopeng/p/4141467.html
https://zhuanlan.zhihu.com/p/118664682

  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值