LLVM的概述及自定义Xcode编译插件

LLVM是什么?

官方如是说:The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
翻译过来就是:LLVM项目是模块化、可重用的编译器以及工具链技术的集合。
传统编译器的架构如下:


传统编译器的架构.png

Frontend:前端,主要作用是词法分析、语法分析、语义分析、生成中间代码;
Optimizer:优化器,主要作用是中间代码优化;
Backend:后端,主要作用是生成机器码。
而LLVM的架构如下:


LLVM的架构.png

主要有以下特点:
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的关系:


    clang与LLVM的关系.png

    备注:广义的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

    源码:
    源码.png
    预处理后:
    预处理后.png
  • 词法分析,生成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项目

cmake编译生成的Xcode项目.png

备注:这个代码同样也可以编译,但是亲测耗时1个小时左右,而上面的方式只需要十几分钟,所以不建议这种方式编译安装,只需要到这一步。

  • 最后的目录结构如下:
    目录结构.png

应用与实践:

我们拿到了源码,我们不仅可以进行clang插件开发,还可以有很多其他的应用开发,我们可以参与到LLVM的各个环节进行自定义开发,甚至开发一门新的编程语言。

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项目:
    生成的xcode项目目录.png

    打开xcode项目.png
  • 在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进行编译,编译完会生成一个动态库文件:


    选择对应的target编译.png

    编译的结果得到动态库.png

Xcode集成plugin

  • Xcode10以下方式:hack Xcode,要对Xcode进行Hack,才能修改默认的编译器(Xcode默认自带Apple clang编译)。
    1、下载【XcodeHacking.zip】,解压,修改【HackedClang.xcplugin/Contents/Resources/HackedClang.xcspec】的内容,设

    置一下自己编译好的clang的路径:
    image.png
    2、然后在XcodeHacking目录下进行命令行,将XcodeHacking的内容剪切到Xcode内部:
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下面的选择自定义的编译器:
image.png

4、还需要设置变量,将我们自定义编译的动态库添加到Xcode:在Xcode项目中指定加载插件动态库:BuildSettings > OTHER_CFLAGS -Xclang -load -Xclang 动态库路径 -Xclang -add-plugin -Xclang 插件名称。

  • Xcode10及以上版本,不需要hack,直接导入我们编译好的插件,然后设置几个变量即可
    导入插件,与上面第4步一样.png
    设置clang路径.png
  • 编译测试代码,我们可以发现检验到类名报错,编译不通过:
    监测到命名不正确,编译不通过.png

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