[toc]
前言
本篇文章会对 clang driver
的 构建 Actions 流程进行详细的讲解
正式分享前,我们先按照惯例分享本文涉及的主要 类图 和 流程图,方便对 构建 Action 的主要流程进行理解
-
Compilation 负责维护一系列的
Actions
和Jobs
。本文的主要内容就是构建Actions
-
Action 是执行的编译步骤基类,持有
Input
、Action
类型,产物类型等信息;可以理解为将某种输入转为输出文件的操作步骤,比如,PreprocessJobAction
可以将源码main.m
转为main.im
-
InputAction 是特例,只代表原始的输入文件/参数
-
PreprocessJobAction 是将源码进行预处理的过程
-
CompileJobAction 是将上一步的结果转为
bitcode
的过程 -
BackendJobAction 是将
bitcode
转为.s
文件的过程 -
AssembleJobAction 是将
.s
文件转为.o
二进制文件的过程 -
LinkJobAction 是将
.o
文件合并为静态库/动态库/可执行文件的过程 -
BindArchAction 是特例,将
.o
文件与特定的架构做绑定 -
LipoJobAction 是用于将多个
BindArchAction
输入合并为单一的fat mach-o
文件 -
JobAction 可以理解能够通过单独的程序执行的过程,注意:
Input
和BindArchAction
没有对应任何的过程
|
|
|
|
构建 Actions 的目的是什么?
构建 Actions 的目的是为了满足以下目的:
-
clang driver
需要根据 参数 计算需要进行的步骤比如,当
-emit-llvm
参数传入时,编译器只需要 预处理、编译器前端 两步,不再需要进行 编译器后端 和 汇编-emit-llvm
的含义是将输入文件编译为bitcode
文件 -
clang driver
需要根据 输入文件类型 计算需要进行的步骤比如,当输入的源码文件是汇编类(扩展名是
.s
)型时,只需要最后 汇编 阶段
BuildUniversalActions
方法源码分析
BuildUniversalActions
方法负责构建 Actions
-
根据
-arch
参数生成需要处理的Archs
,留待后续的处理使用 -
如果没有传入
-arch
参数,可以通过ToolChain::getDefaultUniversalArchName()
方法获取triple
对应的架构 -
调用
BuildActions
计算每个输入文件对应的SingleActions
(可能包含预处理、编译、后端、汇编等)注意:
BuildUniversalActions
的SingleActions
参数传到BuildActions
方法后,名字会变为Actions
-
BuildActions
会先调用Driver::handleArguments
方法对参数进行一些处理 -
随后,会遍历输入源码文件
Inputs
,并通过llvm::SmallVector<phases::ID, phases::MaxNumberOfPhases> types::getCompilationPhases(const clang::driver::Driver &Driver,llvm::opt::DerivedArgList &DAL, ID Id)
获取需要对输入文件进行处理的phase
数组 -
types::getCompilationPhases
内部会根据传入的参数获取需要执行的最后一个phase
通过
-ccc-print-phases
参数可以对比两种场景的差异,比如,当-emit-llvm
参数传入时,就会将移除Backend
后面的Assemble
-
随后,
types::getCompilationPhases
会通过函数llvm::SmallVector<phases::ID, phases::MaxNumberOfPhases> types::getCompilationPhases(ID Id, phases::ID LastPhase)
根据文件类型TY_ObjC
和LastPhase
获取后续的phase
列表两个函数名相同,参数不一样
Types.def
文件维护了不同文件类型默认情况下需要经历的phase
.m
需要经历以下 5 个阶段 -
下一步,
Driver::BuildActions
方法会先组装一个InputAction
(每个Job
都包含一个Kind
属性,代表该Job
的类型,InputAction
的Kind
是InputClass
),InputAction
就相当于输入文件的占位符 -
随后,会依次遍历
phase
,并根据phase
创建Action
(通过调用ConstructPhaseAction
函数实现)注意,每个 NewCurrent 都会持有 Current
Action
创建流程介绍
本节会介绍通过除 InputAction
以外的 Action
创建流程
Preprocess
ConstructPhaseAction
方法检测到 phases::Preprocess
时会依次进行以下处理:
-
根据文件类型获取
TT_ObjC
-
根据
TT_ObjC
获取输出文件的类型TY_PP_ObjC
-
通过
Input
和OutputTy
构建PreprocessJobAction
.m
文件支持的第一个phase
是phases::Preprocess
.m
的预处理类型同样由Types.def
文件维护
Compile
phases::Compile
代表编译器的 前端 流程
phases::Compile
同样会根据传入的参数判断需要组装的类型,比如是否存在 -rewrite-objc
、-emit-ast
等参数
本例中,会构建 CompileJobAction
(该 action
会生成 types::TY_LLVM_BC
文件)
Backend
phases::Backend
就是我们通常所说的 编译器后端
phases::Backend
负责组装 BackendJobAction
,本例中,该 JobAction
的输出文件类型是 TY_PP_Asm
(文件扩展名是 .s
)
Assemble
phases::Assemble
会组装 AssembleJobAction
,该 JobAction
的输出文件类型为 TY_Object
(文件扩展名是 .o
)
Link
因为 link
是可以将一个或多个源码文件产出的 .o
文件进行链接,所以,LinkAction
会稍微复杂一些:
-
Driver::BuildActions
方法会维护一个LinkerInputs
数组,负责记录需要进行link
操作的JobAction
当某个源码文件需要进行
link
操作时,就会先临时保存到LinkerInputs
数组 -
当所有源码文件循环完毕后,会判断
LinkerInputs
是否为空;如果非空,会增加一个LinkJobAction
进行下一步处理截止到这一步, 所有的 Action 就会构造为一个类似于链表的构造
bind & Lipo
link action
创建完毕后,会根据 BuildUniversalActions
生成的 Archs
数组创建对应数量的 BindArchAction
,该JobAction
记录需要产出文件的架构,比如 arm64
或者 armv7
如果 Arch
数量大于 1,会新增一个 LipoJobAction
,LipoJobAction
会将不同的架构的二进制合并为一个 fat mach-o
文件
经过这一步后,所有的 Action
就可以组成下面这种结构:
总结
本文通过对 BuildUniversalActions
方法的源码分析,介绍了 clang driver 构建 Actions 的流程。