LLVM是什么?
官方如是说:The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
翻译过来就是:LLVM项目是模块化、可重用的编译器以及工具链技术的集合。
传统编译器的架构如下:
Frontend:前端,主要作用是词法分析、语法分析、语义分析、生成中间代码;
Optimizer:优化器,主要作用是中间代码优化;
Backend:后端,主要作用是生成机器码。
而LLVM的架构如下:
主要有以下特点:
1、不同的前端后端使用统一的中间代码LLVM Intermediate Representation (LLVM IR);
2、如果需要支持一种新的编程语言,那么只需要实现一个新的前端;
3、如果需要支持一种新的硬件设备,那么只需要实现一个新的后端;
4、优化阶段是一个通用的阶段,它针对的是统一的LLVM IR,不论是支持新的编程语言,还是支持新的硬件设备,都不需要对优化阶段做修改;
5、相比之下,GCC的前端和后端没分得太开,前端后端耦合在了一起。所以GCC为了支持一门新的语言,或者为了支持一个新的目标平台,就 变得特别困难;
6、LLVM现在被作为实现各种静态和运行时编译语言的通用基础结构(GCC家族、Java、.NET、Python、Ruby、Scheme、Haskell、D等)。
与clang的关系:
- clang的概述:LLVM项目的一个子项目,基于LLVM架构的C/C++/Objective-C编译器前端。
- clang的特点(相比于GCC,Clang具有如下优点):
1、编译速度快:在某些平台上,Clang的编译速度显著的快过GCC(Debug模式下编译OC速度比GGC快3倍);
2、占用内存小:Clang生成的AST所占用的内存是GCC的五分之一左右;
3、模块化设计:Clang采用基于库的模块化设计,易于 IDE 集成及其他用途的重用;
4、诊断信息可读性强:在编译过程中,Clang 创建并保留了大量详细的元数据 (metadata),有利于调试和错误报告;
5、设计清晰简单,容易理解,易于扩展增强。 -
clang与LLVM的关系:
备注:广义的LLVM是指整个LLVM架构;狭义的LLVM是指LLVM后端(代码优化、目标代码生成等)。
编译的过程:
- 命令行查看编译的过程:$ clang -ccc-print-phases main.m
0: input, "main.m", objective-c //编译源文件
1: preprocessor, {0}, objective-c-cpp-output //预处理,处理一些预处理指令( 比如#define、#ifdef,#else,#endif等)并将预处理后的代码进行符号化处理,以便下一步进行词法分析和语义分析
2: compiler, {1}, ir //词法分析、语义分析等操作后生成中间文件ir(类似于汇编语言)
3: backend, {2}, assembler //优化中间文件输出的汇编文件(将一些不合适且消耗内存的代码进行优化)
4: assembler, {3}, object //生成目标文件,汇编转为.o文件
5: linker, {4}, image //链接动态库
6: bind-arch, "x86_64", {5}, image //生成对应平台(x86_64)的机器码(Match-o)
-
查看preprocessor(预处理)的结果:$ clang -E main.m
源码:
- 词法分析,生成Token: $ clang -fmodules -E -Xclang -dump-tokens main.m
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
int a,b = 4' Loc=<main.m:11:1>
int 'int' [StartOfLine] Loc=<main.m:13:1>
identifier 'main' [LeadingSpace] Loc=<main.m:13:5>
l_paren '(' Loc=<main.m:13:9>
int 'int' Loc=<main.m:13:10>
identifier 'argc' [LeadingSpace] Loc=<main.m:13:14>
comma ',' Loc=<main.m:13:18>
const 'const' [LeadingSpace] Loc=<main.m:13:20>
char 'char' [LeadingSpace] Loc=<main.m:13:26>
star '*' [LeadingSpace] Loc=<main.m:13:31>
identifier 'argv' [LeadingSpace] Loc=<main.m:13:33>
l_square '[' Loc=<main.m:13:37>
r_square ']' Loc=<main.m:13:38>
r_paren ')' Loc=<main.m:13:39>
l_brace '{' [LeadingSpace] Loc=<main.m:13:41>
at '@' [StartOfLine] [LeadingSpace] Loc=<main.m:14:5>
identifier 'autoreleasepool' Loc=<main.m:14:6>
l_brace '{' [LeadingSpace] Loc=<main.m:14:22>
int 'int' [StartOfLine] [LeadingSpace] Loc=<main.m:16:9>
identifier 'a' [LeadingSpace] Loc=<main.m:16:13>
comma ',' Loc=<main.m:16:14>
identifier 'b' Loc=<main.m:16:15>
equal '=' [LeadingSpace] Loc=<main.m:16:17>
numeric_constant '4' [LeadingSpace] Loc=<main.m:16:19>
semi ';' Loc=<main.m:16:20>
identifier 'a' [StartOfLine] [LeadingSpace] Loc=<main.m:17:9>
equal '=' [LeadingSpace] Loc=<main.m:17:11>
identifier 'b' [LeadingSpace] Loc=<main.m:17:13>
plus '+' [LeadingSpace] Loc=<main.m:17:15>
numeric_constant '4' [LeadingSpace] Loc=<main.m:17:17 <Spelling=main.m:9:15>>
semi ';' Loc=<main.m:17:22>
identifier 'NSLog' [StartOfLine] [LeadingSpace] Loc=<main.m:18:9>
l_paren '(' Loc=<main.m:18:14>
at '@' Loc=<main.m:18:15>
string_literal '"Hello, World!"' Loc=<main.m:18:16>
r_paren ')' Loc=<main.m:18:31>
semi ';' Loc=<main.m:18:32>
r_brace '}' [StartOfLine] [LeadingSpace] Loc=<main.m:19:5>
return 'return' [StartOfLine] [LeadingSpace] Loc=<main.m:20:5>
numeric_constant '0' [LeadingSpace] Loc=<main.m:20:12>
semi ';' Loc=<main.m:20:13>
r_brace '}' [StartOfLine] Loc=<main.m:21:1>
eof '' Loc=<main.m:21:2>
- 语法分析,生成语法树(AST,Abstract Syntax Tree): clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
TranslationUnitDecl 0x7fa62302c8e8 <<invalid sloc>> <invalid sloc>
|-TypedefDecl 0x7fa62302d180 <<invalid sloc>> <invalid sloc> implicit __int128_t '__int128'
| `-BuiltinType 0x7fa62302ce80 '__int128'
|-TypedefDecl 0x7fa62302d1e8 <<invalid sloc>> <invalid sloc> implicit __uint128_t 'unsigned __int128'
| `-BuiltinType 0x7fa62302cea0 'unsigned __int128'
|-TypedefDecl 0x7fa62302d280 <<invalid sloc>> <invalid sloc> implicit SEL 'SEL *'
| `-PointerType 0x7fa62302d240 'SEL *' imported
| `-BuiltinType 0x7fa62302d0e0 'SEL'
|-TypedefDecl 0x7fa62302d358 <<invalid sloc>> <invalid sloc> implicit id 'id'
| `-ObjCObjectPointerType 0x7fa62302d300 'id' imported
| `-ObjCObjectType 0x7fa62302d2d0 'id' imported
|-TypedefDecl 0x7fa62302d438 <<invalid sloc>> <invalid sloc> implicit Class 'Class'
| `-ObjCObjectPointerType 0x7fa62302d3e0 'Class' imported
| `-ObjCObjectType 0x7fa62302d3b0 'Class' imported
|-ObjCInterfaceDecl 0x7fa62302d488 <<invalid sloc>> <invalid sloc> implicit Protocol
|-TypedefDecl 0x7fa62383ade8 <<invalid sloc>> <invalid sloc> implicit __NSConstantString 'struct __NSConstantString_tag'
| `-RecordType 0x7fa62383ac00 'struct __NSConstantString_tag'
| `-Record 0x7fa62302d550 '__NSConstantString_tag'
|-TypedefDecl 0x7fa62383ae80 <<invalid sloc>> <invalid sloc> implicit __builtin_ms_va_list 'char *'
| `-PointerType 0x7fa62383ae40 'char *'
| `-BuiltinType 0x7fa62302c980 'char'
|-TypedefDecl 0x7fa62383b148 <<invalid sloc>> <invalid sloc> implicit __builtin_va_list 'struct __va_list_tag [1]'
| `-ConstantArrayType 0x7fa62383b0f0 'struct __va_list_tag [1]' 1
| `-RecordType 0x7fa62383af70 'struct __va_list_tag'
| `-Record 0x7fa62383aed0 '__va_list_tag'
|-ImportDecl 0x7fa623a0d4f8 <main.m:11:1> col:1 implicit Foundation
|-FunctionDecl 0x7fa623a0d7a8 <line:13:1, line:21:1> line:13:5 main 'int (int, const char **)'
| |-ParmVarDecl 0x7fa623a0d548 <col:10, col:14> col:14 argc 'int'
| |-ParmVarDecl 0x7fa623a0d660 <col:20, col:38> col:33 argv 'const char **':'const char **'
| `-CompoundStmt 0x7fa6230a9f88 <col:41, line:21:1>
| |-ObjCAutoreleasePoolStmt 0x7fa6230a9f40 <line:14:5, line:19:5>
| | `-CompoundStmt 0x7fa6230a9f18 <line:14:22, line:19:5>
| | |-DeclStmt 0x7fa623a0da08 <line:16:9, col:20>
| | | |-VarDecl 0x7fa623a0d8f8 <col:9, col:13> col:13 used a 'int'
| | | `-VarDecl 0x7fa623a0d970 <col:9, col:19> col:15 used b 'int' cinit
| | | `-IntegerLiteral 0x7fa623a0d9d0 <col:19> 'int' 4
| | |-BinaryOperator 0x7fa623a0db00 <line:17:9, line:9:15> 'int' '='
| | | |-DeclRefExpr 0x7fa623a0da20 <line:17:9> 'int' lvalue Var 0x7fa623a0d8f8 'a' 'int'
| | | `-BinaryOperator 0x7fa623a0dad8 <col:13, line:9:15> 'int' '+'
| | | |-ImplicitCastExpr 0x7fa623a0dac0 <line:17:13> 'int' <LValueToRValue>
| | | | `-DeclRefExpr 0x7fa623a0da60 <col:13> 'int' lvalue Var 0x7fa623a0d970 'b' 'int'
| | | `-IntegerLiteral 0x7fa623a0daa0 <line:9:15> 'int' 4
| | `-CallExpr 0x7fa6230a9ed0 <line:18:9, col:31> 'void'
| | |-ImplicitCastExpr 0x7fa6230a9eb8 <col:9> 'void (*)(id, ...)' <FunctionToPointerDecay>
| | | `-DeclRefExpr 0x7fa6230a7190 <col:9> 'void (id, ...)' Function 0x7fa623a0db30 'NSLog' 'void (id, ...)'
| | `-ImplicitCastExpr 0x7fa6230a9f00 <col:15, col:16> 'id':'id' <BitCast>
| | `-ObjCStringLiteral 0x7fa6230a9e38 <col:15, col:16> 'NSString *'
| | `-StringLiteral 0x7fa6230a9e00 <col:16> 'char [14]' lvalue "Hello, World!"
| `-ReturnStmt 0x7fa6230a9f70 <line:20:5, col:12>
| `-IntegerLiteral 0x7fa6230a9f50 <col:12> 'int' 0
`-<undeserialized declarations>
- LLVM IR有3种表示形式:
1、text:便于阅读的文本格式,类似于汇编语言,拓展名.ll,clang -S -emit-llvm main.m
2、memory:内存格式
3、bitcode:二进制格式,拓展名.bc,clang -c -emit-llvm main.m
如下就是.ll文件
define i32 @main(i32, i8**) #0 {
%3 = alloca i32, align 4
%4 = alloca i32, align 4
%5 = alloca i8**, align 8
%6 = alloca i32, align 4
%7 = alloca i32, align 4
store i32 0, i32* %3, align 4
store i32 %0, i32* %4, align 4
store i8** %1, i8*** %5, align 8
%8 = call i8* @objc_autoreleasePoolPush() #2
store i32 4, i32* %7, align 4
%9 = load i32, i32* %7, align 4
%10 = add nsw i32 %9, 4
store i32 %10, i32* %6, align 4
notail call void (i8*, ...) @NSLog(i8* bitcast (%struct.__NSConstantString_tag* @_unnamed_cfstring_ to i8*))
call void @objc_autoreleasePoolPop(i8* %8)
ret i32 0
}
ir文件的基本语法格式:
1、注释以分号 ; 开头
2、全局标识符以@开头,局部标识符以%开头 p alloca,在当前函数栈帧中分配内存
3、i32,32bit,4个字节的意思
4、align,内存对齐
5、store,写入数据
6、load,读取数据
自定义xcode编译插件
我们知道Xcode苹果官方提供的IDE的编译就是clang,所以我们如果想自定义xcode编译插件,得从LLVM和clang的源码入手,然后编译得到相关版本,替换掉苹果的默认编译库,我们自定义的编译库只能本地用用,当正式发版本的时候,还是要将官方默认的编译库替换回来,因为这里面苹果做了很多优化,除非你自认为自己开发的编译库能比苹果官方更厉害。好了话不多说,看看自定义编译插件流程走起。
准备工具:
- 下载LLVM源码:
$ git clone https://git.llvm.org/git/llvm.git/;
- 下载LLVM源码后,cd到llvm/tools文件夹下,下载clang源码,我们就是基于clang进行开发的:
$ git clone https://git.llvm.org/git/clang.git/;
- 安装cmake和ninja:
$ brew install cmake
$ brew install ninja
- 在LLVM源码同级目录下新建一个【llvm_build】目录(最终会在【llvm_build】目录下生成【build.ninja】):
$ cd llvm_build
$ cmake -G Ninja ../llvm -DCMAKE_INSTALL_PREFIX=LLVM的安装路径
- 依次执行编译、安装指令
$ ninja
- 编译完毕后, 【llvm_build】目录大概 21.05 G(仅供参考)
$ ninja install
安装完毕后,安装目录大概 11.92 G(仅供参考)
- 为了后续方便开发,我们还需要一份Xcode的版本的代码(当然的先建好源码文件,后面有介绍),在llvm同级目录下新建一个【llvm_xcode】目录
$ cd llvm_xcode
$ cmake -G Xcode ../llvm
这过程中可能会报错,请参考:使用cmake生成Xcode的项目
打开生成的Xcode项目
备注:这个代码同样也可以编译,但是亲测耗时1个小时左右,而上面的方式只需要十几分钟,所以不建议这种方式编译安装,只需要到这一步。
-
最后的目录结构如下:
应用与实践:
我们拿到了源码,我们不仅可以进行clang插件开发,还可以有很多其他的应用开发,我们可以参与到LLVM的各个环节进行自定义开发,甚至开发一门新的编程语言。
- libclang、libTooling
官方参考:https://clang.llvm.org/docs/Tooling.html
应用:语法树分析、语言转换等 - Clang插件开发
官方参考:https://clang.llvm.org/docs/ClangPlugins.html
https://clang.llvm.org/docs/ExternalClangExamples.html
https://clang.llvm.org/docs/RAVFrontendAction.html
应用:代码检查(命名规范、代码规范)等 - Pass开发
官方参考:https://llvm.org/docs/WritingAnLLVMPass.html
应用:代码优化、代码混淆等 - 开发新的编程语言
https://llvm-tutorial-cn.readthedocs.io/en/latest/index.html
https://kaleidoscope-llvm-tutorial-zh-cn.readthedocs.io/zh_CN/latest/
clang插件开发
- 在【clang/tools】源码目录下(上面的llvm目录)新建一个插件目录,假设叫做【hxw-plugin】
- 在【clang/tools/CMakeLists.txt】最后加入内容: add_clang_subdirectory(hxw-plugin),小括号里是插件目录名
-
在【hxw-plugin】目录下新建一个【CMakeLists.txt】,文件内容是:add_llvm_library(HXWPlugin MODULE BUILDTREE_ONLY HXWPlugin.cpp),保存后,cd到llvm_xcode目录下,执行上述提到过的命令cmake -G Xcode ../llvm生成xcode项目:
- 在HXWPlugin.cpp文件中进行自定义开发(我们以检验类名为例),代码如下:
namespace HXWPlugin {
///语法树匹配finder及回调
class HXWHandler : public MatchFinder::MatchCallback {
private:
CompilerInstance &ci;
public:
HXWHandler(CompilerInstance &ci) :ci(ci) {}
///匹配到的回调会触发run函数
void run(const MatchFinder::MatchResult &Result) {
if (const ObjCInterfaceDecl *decl = Result.Nodes.getNodeAs<ObjCInterfaceDecl>("ObjCInterfaceDecl")) {
size_t pos = decl->getName().find('_');///类名中含有下划线
if (pos != StringRef::npos) {///有,则返回位置并report error
DiagnosticsEngine &D = ci.getDiagnostics();
SourceLocation loc = decl->getLocation().getLocWithOffset(pos);
D.Report(loc, D.getCustomDiagID(DiagnosticsEngine::Error, "呵呵:类名中不能带有下划线"));
}
}
}
};
///语法树
class HXWASTConsumer: public ASTConsumer {
private:
MatchFinder matcher;//匹配Finder
HXWHandler handler;//匹配后的回调
public:
HXWASTConsumer(CompilerInstance &ci) :handler(ci) {
///向MatchFinder添加扫面内容(objcInterfaceDecl,代表类名,上面的语法树可以看到)及回调
matcher.addMatcher(objcInterfaceDecl().bind("ObjCInterfaceDecl"), &handler);
}///构造方法
///匹配到后出发回调,并携带有上下文context
void HandleTranslationUnit(ASTContext &context) {
matcher.matchAST(context);
}
};
///语法树Action
class HXWASTAction: public PluginASTAction {
public:
unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &ci, StringRef iFile) {
///创建HXWASTConsumer实例
return unique_ptr<HXWASTConsumer> (new HXWASTConsumer(ci));
}
bool ParseArgs(const CompilerInstance &ci, const vector<string> &args) {
return true;
}
};
}
///注册插件HXWPlugin和HXWASTAction,在编译的时候,自动回到HXWASTAction的CreateASTConsumer函数和ParseArgs函数
static FrontendPluginRegistry::Add<HXWPlugin::HXWASTAction>
X("HXWPlugin", "The HXWPlugin is my first clang-plugin.");
-
选择HXWPlugin这个target进行编译,编译完会生成一个动态库文件:
Xcode集成plugin
-
Xcode10以下方式:hack Xcode,要对Xcode进行Hack,才能修改默认的编译器(Xcode默认自带Apple clang编译)。
置一下自己编译好的clang的路径:
1、下载【XcodeHacking.zip】,解压,修改【HackedClang.xcplugin/Contents/Resources/HackedClang.xcspec】的内容,设
sudo mv HackedClang.xcplugin `xcode-select -print-path`/../PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins
sudo mv HackedBuildSystem.xcspec `xcode-select -print-path`/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Specifications
3、然后再build settings的build option下面的选择自定义的编译器:4、还需要设置变量,将我们自定义编译的动态库添加到Xcode:在Xcode项目中指定加载插件动态库:BuildSettings > OTHER_CFLAGS -Xclang -load -Xclang 动态库路径 -Xclang -add-plugin -Xclang 插件名称。
-
Xcode10及以上版本,不需要hack,直接导入我们编译好的插件,然后设置几个变量即可
- 编译测试代码,我们可以发现检验到类名报错,编译不通过:
参考:LLVM & Clang 入门