- LLVM是模块化、可重用的编译器以及工具链技术的
- GCC、LLVM、clang
- 传统的编译器架构:前端-优化器(中间代码)-后端->机器代码
- 不同的前端后端使用统一的中间代码LLVM Intermediate Representation(LLVM IR)
- 如果需要支持一种新的编程语言,只要实现一个新的前端
- 如果需要支持一种新的硬件设备,只要实现一个新的后端
- 优化阶段使用统一的LLVM IR,不需要做修改
- GCC 前端后端耦合在一起,需要每个都实现
- LLVM现在被作为实现各种静态和运行时编译语言的通用基础架构(GCC家族、Java、.Net、Python、Ruby、Scheme、Haskell、D等)
Clang
- LLVM项目的一个子项目
- 基于LLVM架构的C/C++/Objective-C编译
- 相比GCC,Clang编译速度快(Debug模式下编译OC速度比GCC快三倍)、占用内存效(Clang生成的AST占用内存是GCC的1/5)、模块化设计(Clang基于库的模块化设计,易于IDE集成和其他用途的重用)、诊断信息可读性强(在编译过程种,Clang创建并保留了大量详细的元数据(metaData),有利于调试)
- clang代码需要放在./llvm/tools下
Clang+LLVM
- 前端使用Clang进行词法分析、语法分析、语义分析、生成中间代码
- 优化器和后端使用LLVM
OC源文件的编译过程
- 通过命令行查看编译过程:$ clang -ccc-print-phases main.m
- 词法分析,生成token:$ clang -fmodules -E -Xclang -dump-tokens main.m
- 生成语法树-AST:$ clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
LLVM IR
LLVM IR有3种表现形式:
- text:文本格式,$ clang -S -emit-llvm main.m
- memory: 内存格式
- bitcode:二进制格式,$ clang -C -emit-llvm main.m
LLVM IR语法:
- 注释以分号;开头
- 全局标识符以@开头,局部标识符以%开头
- alloca,在当前函数栈帧种分配内存
- i32,32bit
- align,内存对齐
- load,读取数据
- store,写入数据
LLVM源码编译
- 安装cmake和ninja,ninja可以直接从github下载解压到bin目录
brew install cmake
brew install ninja
- 编译ninja模板:
cmake -G Ninja ../llvm -DCMAKE_INSTALL_PREFIX=LLVM的安装路径
- $ ninja
- $ ninja install
- 也可以生成xcode模板进行编译
应用与实践
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://clang.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插件开发
- 在llvm源码目录下clang/tools目录下开发,新建目录XXPlugin,在CMakeList.txt添加该目录
- XXPlugin下新建一个cpp文件,新建一个txt,txt中添加这个cpp
add_llvm_loadable_module(XXPlugin XXPlugin.cpp)
//如果又多个Plugin,都放在括号中,逗号分割
- 在cpp中写一个class XXAction继承自PluginASTAction
- 注册插件
static FrontendPluginRegistry::Add<XXPlugin::XXAction>
X("XXPlugin", "description");
- 重写CreateASTConsumer和ParseArgs(直接返回true)两个方法
- 定义一个Consumer类继承自ASTConsumer
- 重写HandleTranslationUnit方法,该方法当Clang编译完语法树时调用
- 编译完成后的dylb动态库放在Xcode中,Build Settings中Other C Flags添加该动态库路径以及插件名称,同时Compiler需要改成自己编译好的llvm编译器(Default自带编译器不允许加载插件)。如果重新编译插件代码生成dylb,使用这个插件的工程需要Clean重新加载插件。
- 需要对Xcode进行Hack,才能修改默认编译器。下载XcodeHacking.zip,修改HackedClang文件中的ExecPath的路径为llvm本地路径。然后在XcodeHacking目录下执行命令
$ 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
- 构造一个Consumer,用来抛出警告或错误信息
- MatchFinder可以用来查找语法树上下文
注:资料整理自MJ公开课。
网友评论