[toc]
前言
本篇文章会对 clang driver
的 构建 Jobs 流程进行详细的讲解
为什么需要 构建 Jobs 流程?
上篇文章曾经提到过,每个 JobAction
都可以通过单独的程序完成。
但是,这种方案会浪费很多时间进行 生成中间文件 和 解析中间文件 操作。
比如 PreprocessJobAction
会包含 源码解析+预处理+将预处理后内容格式化为文件格式+存储该文件到硬盘,随后执行的 CompileJobAction
会包含对 读取硬盘文件+解析文件内容+编译+序列化存储编译后文件+存储该文件到硬盘。
所以,clang driver
会尝试合并多个 JobAction
,避免反复的硬盘读写操作和文件生成/解析操作。
注意:构建
Jobs
包含两个步骤:1、
Bind
:将Actions
转变要运行的实际Jobs
2、
Translate
:将clang driver
接收的参数转为对应Tools
可以理解的参数
正式分享前,我们先按照惯例分享本文涉及的主要 类图 和 流程图,方便对 构建 Jobs 的主要流程进行理解
- Compilation 负责维护一系列的
Actions
和Job
。本文的主要内容就是根据Actions
构建Jobs
- JobList 负责维护可以被执行的
Command
,被 Compilation 持有 - Command 负责持有需要执行的可执行文件地址和命令,被
JobList
持有。比如,执行编译任务的clang -cc1
和执行链接任务的ld
- ToolChain 用于访问单个平台的工具链。比如获取
ld
命令行的执行路径 - MachO 用于访问
MachO
平台的工具链 - Darwin 用于访问
Darwin
平台的工具链 - DarwinClang 用于访问被
Clang
使用的Darwin
工具链
整理后的类图如下:
|
|
整理后的流程图:
|
|
一、BuildJobs
BuildJobs
是 构建 Jobs 的入口,主要完成以下两个任务:
-
BuildJobs
方法首先会记录需要处理的架构,并创建CachedResults
用于缓存Action
与InputInfo
的映射。 -
遍历
Actions
,并调用BuildJobsForAction
方法
二、BuildJobsForAction
BuildJobsForAction
方法会先查找缓存,查找失败后,再调用 BuildJobsForActionNoCache
方法创建 InputInfo
此时,参数
A
是BindArchAction
三、BuildJobsForActionNoCache
BuildJobsForActionNoCache
后续会被多次调用,并且参数 A
会不停地变化,所以,我们下面会根据 A
的类型分开讲解
BindArchAction
BuildJobsForActionNoCache
方法检测到 BindArchAction
以后,会依次进行以下处理:
- 通过
computeTargetTriple
计算triple
(函数的参数DarwinArchName
是来自BindArchAction
持有的ArchName
属性) - 通过
Driver::getToolChain
获取合适的 工具链 - 随后会以
BindArchAction
持有的第一个input
(类型是LinkJobAction
)为参数再次调用BuildJobsForAction
方法
Driver::getToolChain
之前讲过,会根据llvm::Triple
返回toolchains::DarwinClang
其中,computeTargetTriple
内部会依次进行以下处理:
- 获取
-target
参数,并更新TargetTriple
字符串 - 根据
TargetTriple
字符串生成Triple
的实例 - 判断
triple
是否属于mach-o
,并进行相应处理- 如果
DarwinArchName
传入有值,则更新triple
- 如果不存在,则使用
-arch
参数更新triple
- 如果
BindArchAction
的处理流程,最后一步会再次间接调用 BuildJobsForActionNoCache
方法,并将 LinkJobAction
作为参数传入
LinkJobAction
BuildJobsForActionNoCache
方法检测到 LinkJobAction
以后,会依次进行以下处理:
-
获取
LinkJobAction
的Inputs
-
创建
ToolSelector
的实例TS
-
并调用
ToolSelector::getTool
获取支持link
的工具此时,返回的
Tool
是darwin::Linker
-
通过
BuildJobsForAction
处理Inputs
-
调用
Compilation::getArgsForToolChain
进行参数转换 -
调用
darwin::Linker
的ConstructJob
方法构建Job
ToolSelector::getTool 对 LinkJobAction
的处理流程
我们先重点看看 ToolSelector::getTool
的逻辑
ToolSelector::getTool
方法的流程如下:
-
先通过不停地尝试调用
getPrevDependentAction
函数获取最长的一个ActionChain
-
调用
combine
相关的函数依次尝试 3 种不同的组合将多个JobAction
合并到一个,并返回 Tool- Assemble + Backend + Compile;
- Assemble + Backend
- Backend + Compile
-
combine
失败后,会查找BaseAction
对应的Tool
-
前面的各种处理结束后,会通过
combineWithPreprocessor
函数尝试合并PreprocessJobAction
考虑到 LinkJobAction
不满足合并的条件,本小节只对 ToolChain::SelectTool
的流程进行介绍
ToolChain::SelectTool
方法会依次进行以下逻辑
-
判断是否是
flang
模式,并进行相关处理 -
通过
Driver::ShouldUseClangCompiler
判断是否能够使用clang 编译器
,则返回clang
clang
支持预处理、预编译、编译、后端,但是不支持LinkJobAction
-
如果使用
内置 as
,则返回ClangAs
-
其它情况,转发到
getTool
进一步处理因为
this
是之前创建的toolchains::DarwinClang
,而DarwinClang
继承自MachO
,所以,会转发到MachO::getTool
方法进行下一步处理 -
MachO::getTool
方法会因为LinkJobClass
转发到ToolChain::getTool
进行下一步的处理 -
ToolChain::getTool
会通过ToolChain::getLink
获取对应的链接工具 -
ToolChain::getLink()
会通过懒加载方式调用buildLinker()
-
MachO::buildLinker()
会通过new tools::darwin::Linker(this);
创建类tools::darwin::Linker
的实例
最终,LinkJobAction
对应的 Tool
就是 tools::darwin::Linker
darwin::Linker::ConstructJob
BuildJobsForActionNoCache
方法的最后一步是通过 darwin::Linker::ConstructJob
完成任务的构建。
darwin::Linker::ConstructJob
主要完成以下任务:
-
创建
CmdArgs
-
获取
ld
的路径 -
调用
darwin::Linker::AddLinkArgs
函数添加-demangle
等各种参数 -
根据
clang driver
接收的各种参数,填充到CmdArgs
比如,如果是
objc
相关的代码,会强制链接两个库-framework Foundation -lobjc
-
最后,会根据
ld
和CmdArgs
等相关信息实例化Command
,并调用Compilation::addCommand
方法 -
Compilation::addCommand
方法内部会更新Compilation
持有属性Jobs
小结
经过本节的处理,LinkJobAction
会变为 Compilation
的 Jobs
属性持有的一个 Com mand
AssembleJobAction
BuildJobsForActionNoCache
方法遍历 LinkJobAction
的 Inputs
时,会通过 BuildJobsForAction
函数再次调用 BuildJobsForActionNoCache
(参数是 AssembleJobAction
)
BuildJobsForActionNoCache
函数处理 AssembleJobAction
的流程与处理 LinkJobAction
的流程类似:
-
获取
AssembleJobAction
的Inputs
注意:
Inputs
会在后续的合并中从BackendJobAction
变为InputAction
-
创建
ToolSelector
的实例TS
-
并调用
ToolSelector::getTool
获取支持Assemble
的工具此时,返回的
Tool
是clang::driver::tools::Clang
-
通过
BuildJobsForAction
处理Inputs
-
调用
Compilation::getArgsForToolChain
进行参数转换 -
调用
clang::driver::tools::Clang
的ConstructJob
方法构建Job
ToolSelector::getTool
对 AssembleJobAction
的处理流程
我们先重点看看 ToolSelector::getTool
的逻辑,该函数比较重要,会存在触发 Action
合并的逻辑
ToolSelector::getTool
方法的流程如下:
-
先通过不停地尝试调用
getPrevDependentAction
函数获取最长的一个ActionChain
-
调用
combine
相关的函数依次尝试 3 种不同的组合将多个JobAction
合并到一个,并返回 Tool- Assemble + Backend + Compile;
- Assemble + Backend
- Backend + Compile
-
combine
失败后,会查找BaseAction
对应的Tool
-
前面的各种处理结束后,会通过
combineWithPreprocessor
函数尝试合并PreprocessJobAction
ToolSelector::getTool
详细流程
下面,我们对 ToolSelector::getTool
流程进行详细的分析
-
getPrevDependentAction
函数会先检测Inputs
长度是否等于 1,再将尝试将Inputs
转为JobAction
类型因为 1、类型转换失败后,会返回
null
,2、InputAction
没有继承自JobAction
所以,
ActionChain
会在InputAction
处截止,最后的结果是将AssembleJobClass
、BackendJobClass
、CompileJobClass
、PreprocessJobClass
填充到ActionChain
-
clang::driver::Tool *combineAssembleBackendCompile
函数会检测前 3 个任务的类型是否符合要求符合要求后,会通过
CompileJobAction
调用ToolChain::SelectTool
方法查找对应的Tool
注意,合并成功后,
Inputs
会被替换为CompileJobAction
持有的inputs
敲重点
滥用
-fembed-bitcode
是导致编译耗时增长的一个重要原因如果想通过
Custom Compiler Flags
控制Release
模式下产生携带bitcode
的二进制文件,推荐使用下面的方法。更多
bitcode
相关文章,可以参考 #bitcode -
ToolChain::SelectTool
会转发到Driver::ShouldUseClangCompiler
函数判断clang
是否支持该CompileJobAction
-
Driver::ShouldUseClangCompiler
会依次进行以下判断:clang
是否支持JobAction
的源码文件类型clang
是否支持JobAction
的类型
因为,文件类型是
TY_PP_ObjC
,JobAction
类型是CompileJobAction
,满足上面的条件,所以,ToolChain::SelectTool
会转发到ToolChain::getClang
方法进行处理 -
ToolChain::getClang
内部会以懒加载的方式创建tools::Clang
的实例 -
combineAssembleBackendCompile
执行完毕后,会通过combineWithPreprocessor
再次尝试合并该函数处理完成后,
PreprocessJobAction
会被替换为InputAction
Clang::ConstructJob
Clang::ConstructJob
方法负责构建一个 Command
,该 Command
负责完成前面被合并的 预处理、编译、后端、汇编 4 个 JobAction
-
创建
CmdArgs
-
拼装 clang 编译 参数,其中,比较重要的
-cc1
参数就是在这里品拼接 -
根据
JobAction
的类型拼装参数-emit-obj
代表产出mach-o
文件 -
实例化
CC1Command
,并调用Compilation::addCommand
方法
InputAction
BuildJobsForActionNoCache
方法在合并 PreprocessJobAction
的过程中,会将 Inputs
变为 InputAction
,并在遍历 Inputs
时,会通过 BuildJobsForAction
函数再次调用 BuildJobsForActionNoCache
处理
此时,BuildJobsForActionNoCache
会进行以下处理:
- 获取
clang driver
接收的OPT_INPUT
参数 - 实例化
InputInfo
,并返回
总结
经过 构建 Jobs 流程,最终会有两个命令行被构建出来
-
第一个被构建的命令是
clang -cc1 ... -emit-obj ...
clang cc1
可以理解执行编译任务的 编译器 -
第二个被创建的命令是
ld