一、LLVM的简单介绍
-
1.1、什么是LLVM
官网:https://llvm.org/- LLVM官网解释:The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
- 翻译为中文:LLVM 项目是模块化、可重用的编译器以及工具链技术的集合
- 美国计算机协会 (ACM) 将其2012 年软件系统奖项颁给了LLVM,之前曾经获得此奖项的软件和技术包括:
Java
、Apache
、
Mosaic
、the World Wide Web
、Smalltalk
、UNIX
、Eclipse
等等
-
1.2、LLVM 的创始人
LLVM 的创始人
Chris Lattner
,亦是Swift之父
-
1.3、LLVM命名的由来,有些文章把LLVM当做Low Level Virtual Machine(低级虚拟机)的缩写简称,官方描述如下
- The name
LLVM
itself is not an acronym; it is the full name of the project. -
LLVM
这个名称本身不是首字母缩略词; 它是项目的全名
- The name
二、编译器架构
-
2.1、传统的编译器架构
- Frontend:前端
词法分析、语法分析、语义分析、生成中间代码 - Optimizer:优化器
中间代码优化 - Backend:后端
生成机器码
- Frontend:前端
-
2.2、LLVM架构
LLVM架构
- 不同的前端后端使用统一的中间代码LLVM Intermediate Representation (
LLVM IR
) - 如果需要支持一种新的编程语言,那么只需要实现一个新的前端
- 如果需要支持一种新的硬件设备,那么只需要实现一个新的后端
- 优化阶段是一个通用的阶段,它针对的是统一的LLVM IR,不论是支持新的编程语言,还是支持新的硬件设备,都不需要对优化阶段做修改
- 相比之下,GCC的前端和后端没分得太开,前端后端耦合在了一起。所以GCC为了支持一门新的语言,或者为了支持一个新的目标平台,就
变得特别困难 LLVM
现在被作为实现各种静态和运行时编译语言的通用基础结构(GCC家族、Java、.NET、Python、Ruby、Scheme、Haskell、D等)
- 不同的前端后端使用统一的中间代码LLVM Intermediate Representation (
-
2.3、Clang 编译器
- 什么是Clang?
- LLVM项目的一个子项目
- 基于LLVM架构的C/C++/Objective-C编译器前端
- 官网:http://clang.llvm.org/
- 相比于GCC,Clang 具有如下优点
- 编译速度快:在某些平台上,Clang的编译速度显著的快过GCC(Debug模式下编译OC速度比GGC快3倍)
- 占用内存小:Clang生成的AST所占用的内存是GCC的五分之一左右
- 模块化设计:Clang采用基于库的模块化设计,易于 IDE 集成及其他用途的重用
- 诊断信息可读性强:在编译过程中,Clang 创建并保留了大量详细的元数据 (metadata),有利于调试和错误报告
- 设计清晰简单,容易理解,易于扩展增强
- 什么是Clang?
-
2.4、Clang 与 LLVM
- 广义的LLVM:整个LLVM架构
- 狭义的LLVM:LLVM后端(代码优化、目标代码生成等)
三、OC源文件的编译过程
创建一个命令行项目
-
3.1、命令行查看编译的过程:
clang -ccc-print-phases main.m
-
3.2、查看 preprocessor(预处理)的结果:
clang -E main.m
查看 preprocessor(预处理)的结果
-
3.3、词法分析
词法分析,生成Token:clang -fmodules -E -Xclang -dump-tokens main.m
词法分析
-
3.4、语法树-AST
语法分析,生成语法树
(AST,Abstract Syntax Tree):clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
语法树-AST
语法树-AST -
3.5、LLVM IR
-
LLVM IR有3种表示形式(但本质是等价的,就好比水可以有气体、液体、固体3种形态)
- text:便于阅读的文本格式,类似于汇编语言,拓展名
.ll
, $ clang -S -emit-llvm main.m - memory:内存格式
- bitcode:二进制格式,拓展名
.bc
,clang -c -emit-llvm main.m
- IR基本语法
- 注释以分号 ; 开头
- 全局标识符以
@
开头,局部标识符以%
开头 - alloca,在当前函数栈帧中分配内存
- i32,32bit,4个字节的意思
- align,内存对齐
- store,写入数据
- load,读取数据
- text:便于阅读的文本格式,类似于汇编语言,拓展名
-
四、LLVM 源码下载
-
4.1、下载LLVM 和 下载clang
-
创建一个文件夹 LLVM_ALL ,其他的名字也可以
-
先下载 LLVM 到 LLVM_ALL 文件夹下 ,大小 648.2 M,仅供参考
git clone https://github.com/llvm-mirror/llvm.git
-
再下载clang,大小 240.6 M,仅供参考,放到
/LLVM_ALL /llvm/tools
目录下// clang 放到 /LLVM_ALL /llvm/tools 目录下 cd llvm/tools // 下载 clang git clone https://github.com/llvm-mirror/clang.git
-
-
4.2、源码编译
-
安装
cmake
和ninja
(先安装brew,https://brew.sh/)brew install cmake brew install ninja
提示:如果 ninja 如果安装失败,可以直接从github获取release版放入【
下载ninja/usr/local/bin
】中
-
在
llvm
源码同级目录下新建一个【llvm_build】目录(最终会在【llvm_build】目录下生成【build.ninja】)llvm_build
cd llvm_build
cmake -G Ninja ../llvm -DCMAKE_INSTALL_PREFIX=llvm的安装路径
提示:更多cmake相关选项,可以参考: https://llvm.org/docs/CMake.html
-
依次执行编译、安装指令
-
编译完毕后, 【llvm_build】目录大概 21.05 G(仅供参考)
ninja
-
安装完毕后,安装目录大概 11.92 G(仅供参考)
ninja install
-
-
-
4.3、如果不想按照
4.2
的编译方式,也可以生成Xcode项目再进行编译,但是速度很慢(可能需要1个多小时)-
在 llvm 同级目录下新建一个【llvm_xcode】目录
llvm_xcode
cd llvm_xcode cmake -G Xcode ../llvm
-
五、应用与实践
-
5.1、libclang、libTooling
官方参考:https://clang.llvm.org/docs/Tooling.html
应用:语法树分析、语言转换等 -
5.2、 Clang插件开发
官方参考
https://clang.llvm.org/docs/ClangPlugins.html
https://clang.llvm.org/docs/ExternalClangExamples.html
https://clang.llvm.org/docs/RAVFrontendAction.html
应用:代码检查(命名规范、代码规范)等 -
5.3、Pass开发
官方参考:https://llvm.org/docs/WritingAnLLVMPass.html
应用:代码优化、代码混淆等 -
5.4、开发新的编程语言
https://llvm-tutorial-cn.readthedocs.io/en/latest/index.html
https://kaleidoscope-llvm-tutorial-zh-cn.readthedocs.io/zh_CN/latest/
六、clang插件开发
-
6.1、clang插件开发1 – 插件目录
-
在
clang/tools
源码目录下新建一个插件目录,假设叫做mj-plugin
在 clang/tools 源码目录下新建一个插件目录,假设叫做 mj-plugin
-
在
clang/tools/CMakeLists.txt
最后加入内容:add_clang_subdirectory(mj-plugin)
,小括号里是插件目录名
-
-
6.2、插件必要文件
插件必要文件目录-
在
mj-plugin
下创建MJPlugin.cpp
文件,插件使用 C++ 编写touch MJPlugin.cpp
-
在
mj-plugin
里面也要有一份CMakeLists.txt
文件,里面写清插件需要加载哪些 C++ 代码,文件内容如下add_llvm_loadable_module(MJPlugin MJPlugin.cpp)
提示:如果多个 C++ 文件,可以如下
如果多个 C++ 文件目录
-
-
6.3、clang插件开发3 – 编写插件源码
-
在 llvm 同级目录下新建一个【llvm_xcode】目录
cd llvm_xcode cmake -G Xcode ../llvm
image -
找到我们的插件文件进行开发,如下
找到我们的插件文件进行开发
-
具体的源码
#include <iostream> #include "clang/AST/AST.h" #include "clang/AST/ASTConsumer.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendPluginRegistry.h" using namespace clang; using namespace std; using namespace llvm; using namespace clang::ast_matchers; namespace MJPlugin { class MJHandler : public MatchFinder::MatchCallback { private: CompilerInstance &ci; public: MJHandler(CompilerInstance &ci) :ci(ci) {} 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) { DiagnosticsEngine &D = ci.getDiagnostics(); SourceLocation loc = decl->getLocation().getLocWithOffset(pos); D.Report(loc, D.getCustomDiagID(DiagnosticsEngine::Error, "M了个J:类名中不能带有下划线")); } } } }; class MJASTConsumer: public ASTConsumer { private: MatchFinder matcher; MJHandler handler; public: MJASTConsumer(CompilerInstance &ci) :handler(ci) { matcher.addMatcher(objcInterfaceDecl().bind("ObjCInterfaceDecl"), &handler); } void HandleTranslationUnit(ASTContext &context) { matcher.matchAST(context); } }; class MJASTAction: public PluginASTAction { public: unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &ci, StringRef iFile) { return unique_ptr<MJASTConsumer> (new MJASTConsumer(ci)); } bool ParseArgs(const CompilerInstance &ci, const vector<string> &args) { return true; } }; } // 注册插件 // 左边是插件的名称,右边是插件的描述,X 可以随便写,官方使用的是 X static FrontendPluginRegistry::Add<MJPlugin::MJAction> X("MJPlugin", "The MJPlugin is my first clang-plugin.");
-
-
6.4、clang插件开发4 – 编译插件
- 利用cmake生成的Xcode项目来编译插件(第一次编写完插件,需要利用cmake重新生成一下Xcode项目)
- 插件源代码在【Sources/Loadable modules】目录下可以找到,这样就可以直接在Xcode里编写插件代码
- 选择MJPlugin这个target进行编译,编译完会生成一个动态库文件
编译插件
-
6.5、clang插件开发5 – 加载插件
- 在Xcode项目中指定加载插件动态库:
Build Settings
>OTHER_CFLAGS
-Xclang -load -Xclang 动态库路径 -Xclang -add-plugin -Xclang 插件名称
加载插件
- 在Xcode项目中指定加载插件动态库:
-
6.6、clang插件开发6 – Hack Xcode (Xcode 破解 由于制作Xcode插件)
-
首先要对Xcode进行Hack,才能修改默认的编译器
-
下载【XcodeHacking.zip】,解压,修改
HackedClang.xcplugin/Contents/Resources/HackedClang.xcspec
】的内容,设
置一下自己编译好的clang的路径设置一下自己编译好的clang的路径
-
然后在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
-
-
6.7、clang插件开发7 – 修改Xcode的编译器,修改位置如下,使用我们自己编译的 clang
-
6.8、clang插件开发8 – 编译项目
编译项目后,会在编译日志看到MJPlugin插件的打印信息(如果插件更新了,最好先Clean一下项目) -
6.9、clang插件开发9 – 更多
想要实现更复杂的插件功能,就需要利用clang的API针对语法树(AST)进行相应的分析和处理
关于AST的资料
网友评论