Clang前端源码分析
Posted 吴建明
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Clang前端源码分析相关的知识,希望对你有一定的参考价值。
Clang前端源码分析
C语言编译器之二,Clang
Clang编译器是由APPLE公司的编译器大牛ChrisLattner主导下编写的,其目标是替换大名鼎鼎的GCC编译器;
2.1、Clang和GCC编译器架构
从源代码到可执行程序一般经过预处理、编译、链接过程,而编译是编译器的工作,编译分为三个阶段,分别为前端、优化器、后端。
i.编译前端:将源代码转化成中间代码。其详细过程包括:词法分析、语法分析、生成中间代码;
ii.优化器:对编译器生成的中间代码进行一些优化,最终提供给编译后端;
iii.编译后端:根据不同的 cpu 架构,将中间代码汇编,产生汇编代码,最后解析汇编指令,生成目标代码,也就是机器码;
编译器的这种前端、优化器、后端的架构的优点是:
a.当为新的语言开发编译器时,只需要针对新的语言开发前端,产生标准通用的中间代码,这样优化器与后端可以不用修改;
b.当为新的架构开发编译器时,只需要针对新的架构开发后端,而无需修改前端和优化器。
所以,这种架构对编译器的开发维护工作就简单许多,同时提升执行效率。
2.2、Clang起源
GCC编译器从20世纪90年代就已经开发出来了,特别作为 Linux的当家编译器,GCC开源使得其广为流传,包括Apple在内,最开始也是使用GCC编译器,因为当时的Apple并没有自己的编译器,因此早期的XCODE使用GCC编译器编译Object-C,由于Object-C不断地引入新的特性,Apple公司不停与GCC组织协商改进GCC,可能当时的Apple还未有什么名气,GCC不太配合修改,这让Apple公司很是恼火。
转机来了,2005年刚研究生毕业的ChrisLattner被Apple招致麾下,Chris Lattner可是精通编译器理论,其博士阶段的LLVM-GCC架构的编译器在其进入Apple公司后就被完善使用,Apple公司甚至将其当作替代GCC编译器的不二之选。但是,GNU组织出台了《GCC 运行环境豁免条款》从根本上限制了LLVM-GCC 的开发,这让Apple公司下定决心,从新开始编写C编译器,由Chris Lattner牵头,这就是Clang编译器的诞生,基于LLVM架构,为LLVM Compiler 1.0,这个版本不支持C++。
这就是早期XCODE同时支持GCC、LLVM-GCC、LLVMCompiler的原因:
从XCODE4开始,也就是 MacOS X 10.6版本系统上,Apple 宣布停止更新GCC编译器,这样GCC停留在GCC4.2版本,并建议大家使用LLVM Compiler 2.0(LLVM-Clang),该版本完全支持C++/ Objective-C++,并提供libc++库来支持新的C++ standard(C++0x标准),而GCC/LLVM-GCC支持的是GCC标准库libstdc++;
从XCODE4.2开始,就默认使用LLVM-Clang,彻底抛弃了GCC;而LLVM-GCC毕竟也是亲儿子,改为一个GCC的插件DragonEgg。
由于Clang设计之初就考虑到模块化设计,因此,清晰简单,出错提示更好,易于扩展,容易与IDE集成;而GCC由于早期设计只支持C语言,后面不断扩展C++/ Java/Ada/Fortran/Go等,虽然支持更多平台;更流行,广泛使用,支持完备,但是其代码接口耦合性强,更新维护和性能等较差。
由于LLVM-Clang的优秀设计,AndroidNDK从r11开始建议大家切换到clang。并且把GCC标记为deprecated,将GCC版本锁定在GCC 4.9不再更新;Android NDK从r13起,默认使用Clang进行编译。但是暂时也没有把GCC删掉,Google考虑 libc++(LLVM-Clang的c++标准库)还不够稳定; Android NDK 在 r17 中宣称不再支持 GCC 并在后续的 r18 中删掉 GCC。现在GCC主战场只剩Linux跟部分Windows应用软件开发。
现在最新LLVM版本号已经到了12.0.1版本,官方地址如下:
LLVM编译器基础架构:http://llvm.org/
Clang:http://clang.llvm.org/
DragonEgg – LLVM-GCC:http://dragonegg.llvm.org/
二进制安装文件在github仓库中,地址为:
https://github.com/llvm/llvm-project/releases/tag/llvmorg-12.0.1
可见Clang也在x86/arm/powerpc架构下,及Linux系统下已经广泛支持,Clang与GCC进入激烈竞争的局面,对于吃瓜群众来说,应该是好事!
Clang起源具体细节可以参考“Mac OS X 背后的故事(作者王越)”,该文详细介绍了Apple公司从创立之初到现今富可敌国的公司,中间的起起伏伏,堪比一部电视连续剧。
clang 源码导读: clang driver 参数解析
本文会对 clang driver
的 参数解析 流程进行分享
为了控制 clang
的运行,clang
必须支持不同的参数对各种行为进行控制,所以,clang driver
启动后的第一个主要任务就是 参数解析
正式分享前,我们先按照惯例分享本文涉及的主要 类图 和 流程图,方便对 参数解析 的主要流程进行理解
Info[1] 是保存了预定义的各种Option
信息的结构体。比如 -v
参数的帮助信息是 Show commands to run and use verbose output
Option[2] 是持有 Info
和 OptTable
,提供了一些封装好的方法,比如通过 OptionClass getKind()
方法暴露 Info
的类型
Arg[3] 持有了 Option
和其它命令行参数信息,比如 -arch armv64
的 arm64
会被保存到 Arg
OptTable[4] 提供解析参数,并懒加载创建 Option
的相关方法
InputArgList[5] 持有了输入的原始参数和解析后的参数列表
DriverOptTable[6] 记录了 clang driver
相关的 Info
信息,是 OptTable
的子类
一、DriverOptTable
DriverOptTable 记录了 clang driver
相关的 Info
信息,是 OptTable
的子类
DriverOptions
模块提供了函数 const llvm::opt::OptTable &clang::driver::getDriverOptTable()
可以获取 clang driver
支持的所有参数信息
DriverOptTable
初始化时依赖的 InfoTable
参数是通过 clang/Driver/Options.inc
生成的
通过下图,我们可以看到 InfoTable 的长度是 2776
小知识:当我们编译 llvm
项目时,会由 TableGen
工具将 Options.td
文件生成 Options.inc
原始的文本信息如下:
因为DriverOptTable
继承自 OptTable
,所以,这里会触发 OptTable
的初始化方法imageimage
OptTable
的初始化时,会记录一些关键的 ID,用于后续使用,比如 TheInputOptionID
同时,会通过 PrefixChars
和 PrefixesUnion
记录合法的参数前缀,用于后续的快速参数合法性判断,比如 -v
参数的前缀是 -
二、Driver::ParseArgStrings
Driver::ParseArgStrings
方法的作用是将字符串数组解析为 ArgList
,并做相关的校验
具体流程如下:
调用Driver::getOpts
获取 clang driver
支持的所有参数 Info
调用 ParseArgs
解析命令行参数
对解析到的命令行参数进行判断,检测到 不支持 或者 未知 的参数时,会抛出异常
如何区分 不支持
或者 不认识
的参数
clang driver
不支持 的参数,都可以通过 Options.td
文件查到
以 -pass-exit-codes
为例,gcc
支持该参数,但是 clang
不支持 此参数
imageimage
不认识 的参数就是类似于 -test
这种,开发者随意拼写的参数
三、ParseArgs
OptTable::ParseArgs
方法负责将字符串数组解析为 ArgList
具体流程如下:
先初始化InputArgList
的实例,并存储原始的入参信息通过
while
对原始参数字符串进行遍历,并通过 OptTable::ParseOneArg
方法将所有的原始参数字符串解析为 Arg
的实例最后
Args
会持有所有的解析后的参数
通过添加调试代码,我们可以感受一下以下命令行对应的原始参数和解析后的 Arg
实例分别是什么样子
clang -arch arm64 /var/folders/4j/jqzrrjzn0nvgm4pyxrqddxnmm530jm/C/main.m -target arm64-apple-ios11.1
四、ParseOneArg
OptTable::ParseOneArg
方法负责解析单个参数
具体流程如下:
先移除参数的前缀,并通过std::lower_bound
查找第一个前缀匹配的 Info
比如,-arch
会变成 arch
Info
初始化 Option
持有参数信息
通过 Option::accept
方法校验参数是否正常
参数正常时直接返回
如果没有找到合适的参数,再判断参数是否以 /
开头,如果开始,会把参数当做源码文件进行处理
其它情况下,会当做参数当做 未知参数 进行下一步处理
std::lower_bound
会依赖下面两个方法查找第一个前缀匹配的参数
Info.Name
和 Name
的查找逻辑比较复杂,需要深入研究的同学,可以逐步调试帮助理解
imageimage
Option::accept
方法会依次进行以下处理
比如,-fembed-bitcode-marker
就是 -fembed-bitcode=marker
参数的别名,两个参数的意义完全相同
先转发到 Option::acceptInternal
方法进行参数校验
判断解析到的参数是否属于别名
如果别名,会进行特殊处理Option::acceptInternal
方法会根据 Option
的类型进行处理并生成 Arg
实例。
因为 -arch
的类型是 SeparateClass
,所以,会将下一个原始参数字符串(arm64
)当做 value
进行处理
类型
|
示例 |
Separate |
-arch arm64 |
Flag |
-v |
Joined |
-fembed-bitcode=marker |
总结
本文通过分析 DriverOptTable
的生成机制并分析Driver::ParseArgStrings
内部流程,对 clang driver
的参数解析流程做了简单的分析
参考资料
[1]Info: https://www.llvm.org/doxygen/structllvm_1_1opt_1_1OptTable_1_1Info.html#details
[2]Option: https://www.llvm.org/doxygen/classllvm_1_1opt_1_1Option.html
[3]Arg: https://www.llvm.org/doxygen/classllvm_1_1opt_1_1Arg.html
[4]OptTable: https://www.llvm.org/doxygen/classllvm_1_1opt_1_1OptTable.html#details
[5]InputArgList: https://www.llvm.org/doxygen/classllvm_1_1opt_1_1InputArgList.html
[6]DriverOptTable: https://clang.llvm.org/doxygen/DriverOptions_8cpp_source.html
参考文献链接
https://mp.weixin.qq.com/s/PjNoc7LV3sXtcw-8Utg4dg
https://mp.weixin.qq.com/s/GoEk5LJ5TkcSCKLIg4sPhQ
如何制作干净的clang前端?
【中文标题】如何制作干净的clang前端?【英文标题】:How to make a clean clang front-end? 【发布时间】:2011-12-25 03:23:23 【问题描述】:我正在开发一个 C++ 源代码分析器项目,看来 clang 是一个不错的候选者 解析工作。问题是clang严重依赖基础设施“llvm”项目, 如何配置它以获得干净的前端,而无需任何面向具体机器的后端? 就像 LCC 一样,它们为专注于解析器部分的人提供了一个“空”后端。 任何建议表示赞赏。
【问题讨论】:
除了一些支持库(提供一些实用程序类和独立于操作系统的代码)之外,您不需要完整的 LLVM。你在使用哪些 Clang 库? 你知道libclang
吗?它是一个可以公开 Clang AST 的 C 库(具有保证的稳定接口)。它要轻得多。否则,您可以简单地使用 C++ 库(请注意接口不稳定),我认为可执行文件会嵌入对您没有用处的内容。
如果你想结帐libclang
,你可能想结帐this presentation
你找到办法了吗??如果是的话,你是怎么做到的,请告诉我现在处于类似的情况......
可能离题,可能有帮助:查看文本编辑器 Sublime Text 的 SublimeClang 插件的来源。对我来说真的很好用。
【参考方案1】:
我最近在 Windows 上做了这个。
从here下载clang和llvm源代码。
安装 cmake 和 Python(与文档相反,您确实需要 Python 来构建 clang;至少,如果 cmake 找不到 Python 运行时,它会放弃)。
您还需要 VS2008 或 VS2010。
有一点不太明显是需要的目录结构:
projectRoot
build <- intermediate build files and DLLs, etc. will go here
llvm <- contents of llvm-3.0.src from llvm-3.0.tar go here
tools
clang <- contents of clang-3.0.src from clang-3.0.tar go here
并从第 4 步开始按照windows build instructions 进行操作。不要尝试使用 cmake GUI,这很可怕;只需使用构建说明中给出的命令即可。
构建完成后(需要一段时间),您将拥有:
projectRoot
build
bin
Release <- libclang.dll will be here
lib
Release <- libclang.lib will be here
llvm
tools
clang
include
clang-c <- Index.h is here
Index.h 定义了访问源代码信息的 API;它包含大量有关 API 的文档。
要开始使用 clang,您需要以下内容:
CXIndex index = clang_createIndex(1, 1);
// Support Microsoft extensions
char *args[] = "-fms-extensions";
CXTranslationUnit tu = clang_parseTranslationUnit(index, "mySource.c", args, ARRAY_SIZE(args), 0, 0, 0);
if (tu)
CXCursor cursor = clang_getTranslationUnitCursor(tu);
// Use the cursor functions to navigate through the AST
【讨论】:
谢谢arx!我正在尝试你的方法。【参考方案2】:不幸的是,如果没有特定于机器的详细信息,您将无法获得“纯”前端。 C/C++ 本质上是与机器相关的语言。考虑预处理器和内置定义,内置类型的大小等。其中一些可以抽象出来,但不能抽象出来。预处理器。
【讨论】:
以上是关于Clang前端源码分析的主要内容,如果未能解决你的问题,请参考以下文章
LLVM 之 Clang 静态分析器篇:程序缺陷诊断——除零错误