酷酷的哀殿


  • 首页

  • 技术

  • 笔记

  • 杂记

  • Todo

  • 关于

  • 搜索
close

clang 源码导读(5): clang driver 构建 Jobs

时间: 2021-03-02   |   阅读: 4813 字 ~10分钟

[toc]

前言

本篇文章会对 clang driver 的 构建 Jobs 流程进行详细的讲解

为什么需要 构建 Jobs 流程?

上篇文章曾经提到过,每个 JobAction 都可以通过单独的程序完成。

但是,这种方案会浪费很多时间进行 生成中间文件 和 解析中间文件 操作。

比如 PreprocessJobAction 会包含 源码解析+预处理+将预处理后内容格式化为文件格式+存储该文件到硬盘,随后执行的 CompileJobAction 会包含对 读取硬盘文件+解析文件内容+编译+序列化存储编译后文件+存储该文件到硬盘。

所以,clang driver 会尝试合并多个 JobAction ,避免反复的硬盘读写操作和文件生成/解析操作。

注意:构建 Jobs 包含两个步骤:

1、Bind:将 Actions 转变要运行的实际 Jobs

2、Translate:将 clang driver 接收的参数转为对应 Tools 可以理解的参数

正式分享前,我们先按照惯例分享本文涉及的主要 类图 和 流程图,方便对 构建 Jobs 的主要流程进行理解

  1. Compilation 负责维护一系列的 Actions 和 Job。本文的主要内容就是根据 Actions 构建 Jobs
  2. JobList 负责维护可以被执行的 Command,被 Compilation 持有
  3. Command 负责持有需要执行的可执行文件地址和命令,被 JobList 持有。比如,执行编译任务的clang -cc1 和执行链接任务的 ld
  4. ToolChain 用于访问单个平台的工具链。比如获取 ld 命令行的执行路径
  5. MachO 用于访问 MachO 平台的工具链
  6. Darwin 用于访问 Darwin 平台的工具链
  7. DarwinClang 用于访问被 Clang 使用的 Darwin 工具链

整理后的类图如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
classDiagram

  class Compilation {
      // 记录需要执行的 action
      ActionList Actions;
      /
      // 记录需要执行的 job
      JobList Jobs;
  }

  class JobList {
      // 记录需要执行的 Command
      SmallVector<std::unique_ptr<Command>, 4> Jobs;
  }

  class Command {
      // 可执行文件的路径,如 clang/ld
      const char *Executable;
      /
      // 参数列表
      llvm::opt::ArgStringList Arguments;
      /
      // command 需要处理的文件
      llvm::opt::ArgStringList InputFilenames
      /
      // 命令产出的文件
      std::vector<std::string> OutputFilenames;
   }

  DarwinClang --|> Darwin
  Darwin --|> MachO
  MachO --|> ToolChain
  Action <|-- JobAction

  class Driver {
      ShouldUseClangCompiler BOOL
  }

  class Action {
       /
      // 类型,比如 CompileJobClass
      ActionClass Kind;

       /
      // 产物文件类型 TY_LLVM_BC
      types::ID Type;
      /
      // 依赖的 action,比如 CompileJobAction 依赖 PreprocessJobAction
      ActionList Inputs;

      getInputs() ActionList
  }

  class ToolSelector {
      / 基础 Action
      const JobAction *BaseAction;
      getTool(ActionList &Inputs,ActionList &CollapsedOffloadAction) Tool
  }

  class ToolChain {
      SelectTool(const JobAction &JA) Tool
      getLink() Tool
  }

  class MachO {
      getTool(Action::ActionClass AC) Tool
      buildLinker() Tool
  }

  class Tool {
      /
      //工具名
      const char *Name;

      /
      // 可读性比较长的名字
      const char *ShortName;

      /
      // 所属 tool chain
      const ToolChain &TheToolChain;
  }
  Linker --|> MachOTool
  MachOTool --|> Tool
  Clang --|> Tool
  Linker o--o MachO

整理后的流程图:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
graph TD

  subgraph Action
    InputAction::getInputArg
  end
  Driver::ShouldUseClangCompiler
  Driver::getToolChain
  BuildCompilation
  BuildJobs
  for-Actions
  BuildJobsForAction
  别名1["CachedResults.find(ActionTC)"]
  BuildJobsForActionNoCache
  别名2["CachedResults[ActionTC] = Result;"]
  for-Inputs别名1["for-Inputs"]
  别名4[再次从 BuildJobsForAction 开始依次执行]



  别名5["new tools::Clang(*this)"]


subgraph ToolSelector
  描述1["当 Inputs 的长度等于1,并且满足要求类型是 JobAction 及其它要求会返回 Inputs.begin()"]
  ToolSelector::getPrevDependentAction
  ToolSelector::combineWithPreprocessor
  ToolSelector::getTool
  for-Inputs别名2["for-Inputs"]
  ToolSelector::getTool
  ToolSelector::combineAssembleBackendCompile
  ToolSelector::combineAssembleBackend
  ToolSelector::combineBackendCompile
  while
end



subgraph ToolChain
  ToolChain::SelectTool
  ToolChain::getClang

  ToolChain::getTool
  ToolChain::getLink
end


subgraph Darwin
  MachO::getTool
  MachO::buildLinker
  别名3[new tools::darwin::Linker]
end

main-->BuildCompilation--1. 构建 Jobs-->BuildJobs

BuildJobs--> 别名2_1[2.1 收集需要处理的架构]
BuildJobs--> 帮忙2_2[2.2 创建CachedResults]
BuildJobs--2.3 遍历 Actions-->for-Actions
--3.生成 InputInfo-->BuildJobsForAction_BindArchAction

BuildJobsForAction_BindArchAction--"3.1 先查找缓存,查找成功会直接返回"-->别名1["CachedResults.find(ActionTC)"]
BuildJobsForAction_BindArchAction--"3.2 查找失败,再生成 InputInfo"-->BuildJobsForActionNoCache_BindArchAction
BuildJobsForAction_BindArchAction--3.3 存储到缓存-->别名2["CachedResults[ActionTC] = Result;"]

BuildJobsForActionNoCache_BindArchAction--"4.1 计算 triple"-->computeTargetTriple

computeTargetTriple-->别名4_1_1[4.1.1 获取-target参数]
computeTargetTriple-->别名4_1_2[4.1.2 生成 triple]
computeTargetTriple-->别名4_1_3[4.1.3 triple 是否属于 mach-o]
                -->别名4-1-4[4.1.4 是否传入 archname]
                -->别名4-1-5[4.1.5 更新 triple]



BuildJobsForActionNoCache_BindArchAction-->BuildJobsForAction_LinkJobAction
																				-->BuildJobsForActionNoCache_LinkJobAction

BuildJobsForActionNoCache_LinkJobAction-->别名4.2.1[4.2.1/5.1.1 获取 JobAction 的 Inputs]

BuildJobsForActionNoCache_LinkJobAction-->别名4.2.2[4.2.2/5.1.2 创建 ToolSelector 的实例 TS]

BuildJobsForActionNoCache_LinkJobAction--4.2.3/5.1.3-->ToolSelector::getTool

ToolSelector::getTool-->while-->getPrevDependentAction
ToolSelector::getTool-->ToolChain::SelectTool

ToolChain::SelectTool-->getTool-->ToolChain::getTool-->ToolChain::getLink-->buildLinker-->tools::darwin::Linker

BuildJobsForActionNoCache_LinkJobAction--4.2.4-->BuildJobsForAction_AssembleJobAction
BuildJobsForActionNoCache_LinkJobAction--4.2.4 构建 job-->darwin::Linker::ConstructJob
darwin::Linker::ConstructJob--4.2.5-->4.2.5[创建 Command]
darwin::Linker::ConstructJob--4.2.6 更新 jobs-->Compilation::addCommand

BuildJobsForAction-->BuildJobsForActionNoCache
BuildJobsForActionNoCache-->5 处理 AssembleJobAction
ToolChain::SelectTool--5.1.4 是否需要使用 clang-->Driver::ShouldUseClangCompiler
Driver::ShouldUseClangCompiler-->别名5.1.5[5.1.5 clang 是否支持 JobAction 的源码文件类型]
Driver::ShouldUseClangCompiler-->别名5.1.6[5.1.6 clang 是否支持 JobAction 的类型]
ToolChain::SelectTool --5.1.7 获取 clang-->ToolChain::getClang--5.1.8 懒加载 clang-->tools::Clang


BuildJobsForAction--5.1/6.1.2 生成 ActionChain-->while-->ToolSelector::getPrevDependentAction-->描述1["当 Inputs 的长度等于1,并且满足要求类型是 JobAction 及其它要求会返回 Inputs.begin()"]

ToolSelector::getTool--5.2/6.1.3 尝试合并多个任务--> ToolSelector::combineAssembleBackendCompile
ToolSelector::getTool--5.3 尝试合并汇编&后端--> ToolSelector::combineAssembleBackend
ToolSelector::getTool--5.3 尝试合并汇编&后端--> ToolSelector::combineBackendCompile

ToolSelector::getTool--"5.4 合并失败,开始正常的生成对应命令" -->ToolChain::SelectTool
-->MachO::getTool-->ToolChain::getTool--5.5 Action::LinkJobClass-->ToolChain::getLink--懒加载-->MachO::buildLinker--> 别名3[new tools::darwin::Linker]

BuildJobsForActionNoCache--6 开始遍历 Inputs-->for-Inputs别名1["for-Inputs"]--> 别名4[再次从 BuildJobsForAction 开始依次执行]--6.1.1 开始处理 AssembleJobAction-->BuildJobsForAction

ToolSelector::combineAssembleBackendCompile--"6.1.4 如果满足合 combine 条件,选择合适的 Tool"-->
ToolChain::SelectTool--6.1.5 clang 编译器是否支持 JobAction-->Driver::ShouldUseClangCompiler
ToolChain::SelectTool--"6.1.6 支持时,返回 clang"-->ToolChain::getClang--6.1.7 懒加载-->别名5["new tools::Clang(*this)"]


ToolSelector::getTool--5.6/6.1.8 尝试合并预处理任务-->ToolSelector::combineWithPreprocessor-->for-Inputs别名2["for-Inputs"]--6.1.9 for 循环的原因是为了支持多个头文件进行预处理-->ToolSelector::getPrevDependentAction


BuildJobsForActionNoCache--7.1 尝试处理 InputAction-->InputAction::getInputArg

darwin::Linker::ConstructJob
--"1、添加依赖库 libclang_rt.ios.a"-->toolchains::DarwinClang::AddLinkRuntimeLibArgs
-->toolchains::MachO::AddLinkRuntimeLib


subgraph Compilation
  addCommand
end

BuildJobsForActionNoCache--8.1 构建任务-->
Clang::ConstructJob--8.2.1 实例化-->CC1Command--8.2.2 父类实例化方法-->Command
Clang::ConstructJob--8.3 保存 command -->addCommand



一、BuildJobs

BuildJobs 是 构建 Jobs 的入口,主要完成以下两个任务:

  1. BuildJobs 方法首先会记录需要处理的架构,并创建 CachedResults 用于缓存 Action 与 InputInfo 的映射。

  2. 遍历 Actions,并调用 BuildJobsForAction 方法

    image

二、BuildJobsForAction

BuildJobsForAction 方法会先查找缓存,查找失败后,再调用 BuildJobsForActionNoCache 方法创建 InputInfo

此时,参数 A 是 BindArchAction

image

三、BuildJobsForActionNoCache

BuildJobsForActionNoCache 后续会被多次调用,并且参数 A 会不停地变化,所以,我们下面会根据 A 的类型分开讲解

BindArchAction

BuildJobsForActionNoCache 方法检测到 BindArchAction 以后,会依次进行以下处理:

  1. 通过 computeTargetTriple 计算 triple(函数的参数 DarwinArchName 是来自 BindArchAction 持有的 ArchName 属性)
  2. 通过 Driver::getToolChain 获取合适的 工具链
  3. 随后会以 BindArchAction 持有的第一个 input(类型是 LinkJobAction)为参数再次调用 BuildJobsForAction 方法

Driver::getToolChain 之前讲过,会根据 llvm::Triple 返回 toolchains::DarwinClang

image

其中,computeTargetTriple 内部会依次进行以下处理:

  1. 获取 -target 参数,并更新 TargetTriple 字符串
  2. 根据 TargetTriple 字符串生成 Triple 的实例
  3. 判断 triple 是否属于 mach-o,并进行相应处理
    1. 如果 DarwinArchName 传入有值,则更新 triple
    2. 如果不存在,则使用 -arch 参数更新 triple

image

BindArchAction 的处理流程,最后一步会再次间接调用 BuildJobsForActionNoCache 方法,并将 LinkJobAction 作为参数传入

LinkJobAction

BuildJobsForActionNoCache 方法检测到 LinkJobAction 以后,会依次进行以下处理:

  1. 获取 LinkJobAction 的 Inputs

  2. 创建 ToolSelector 的实例 TS

  3. 并调用 ToolSelector::getTool 获取支持 link 的工具

    此时,返回的 Tool 是 darwin::Linker

  4. 通过 BuildJobsForAction 处理 Inputs

  5. 调用 Compilation::getArgsForToolChain 进行参数转换

  6. 调用 darwin::Linker 的 ConstructJob 方法构建 Job

    image

    image

ToolSelector::getTool 对 LinkJobAction 的处理流程

我们先重点看看 ToolSelector::getTool 的逻辑

ToolSelector::getTool 方法的流程如下:

  1. 先通过不停地尝试调用 getPrevDependentAction 函数获取最长的一个 ActionChain

  2. 调用 combine 相关的函数依次尝试 3 种不同的组合将多个 JobAction 合并到一个,并返回 Tool

    • Assemble + Backend + Compile;
    • Assemble + Backend
    • Backend + Compile
  3. combine 失败后,会查找 BaseAction 对应的 Tool

  4. 前面的各种处理结束后,会通过 combineWithPreprocessor 函数尝试合并 PreprocessJobAction

    image

考虑到 LinkJobAction 不满足合并的条件,本小节只对 ToolChain::SelectTool 的流程进行介绍

ToolChain::SelectTool 方法会依次进行以下逻辑

  1. 判断是否是 flang 模式,并进行相关处理

  2. 通过 Driver::ShouldUseClangCompiler 判断是否能够使用 clang 编译器,则返回 clang

    clang 支持预处理、预编译、编译、后端,但是不支持 LinkJobAction

  3. 如果使用 内置 as,则返回 ClangAs

  4. 其它情况,转发到 getTool 进一步处理

    因为 this 是之前创建的 toolchains::DarwinClang,而 DarwinClang 继承自 MachO,所以,会转发到 MachO::getTool 方法进行下一步处理

    image

  5. MachO::getTool 方法会因为 LinkJobClass 转发到 ToolChain::getTool 进行下一步的处理

    image

  6. ToolChain::getTool 会通过 ToolChain::getLink 获取对应的链接工具

    image

  7. ToolChain::getLink() 会通过懒加载方式调用 buildLinker()

    image

  8. MachO::buildLinker() 会通过 new tools::darwin::Linker(this); 创建类 tools::darwin::Linker 的实例

    image

最终,LinkJobAction 对应的 Tool 就是 tools::darwin::Linker

image

darwin::Linker::ConstructJob

BuildJobsForActionNoCache 方法的最后一步是通过 darwin::Linker::ConstructJob 完成任务的构建。

darwin::Linker::ConstructJob 主要完成以下任务:

  1. 创建 CmdArgs

  2. 获取 ld 的路径

  3. 调用 darwin::Linker::AddLinkArgs 函数添加 -demangle 等各种参数

    image

  4. 根据 clang driver 接收的各种参数,填充到 CmdArgs

    比如,如果是 objc 相关的代码,会强制链接两个库 -framework Foundation -lobjc

    image

  5. 最后,会根据 ld 和 CmdArgs 等相关信息实例化 Command,并调用 Compilation::addCommand 方法

    image

  6. Compilation::addCommand 方法内部会更新 Compilation 持有属性 Jobs

    image

小结

经过本节的处理,LinkJobAction 会变为 Compilation 的 Jobs 属性持有的一个 Com mand

AssembleJobAction

BuildJobsForActionNoCache 方法遍历 LinkJobAction 的 Inputs 时,会通过 BuildJobsForAction 函数再次调用 BuildJobsForActionNoCache (参数是 AssembleJobAction )

image

BuildJobsForActionNoCache 函数处理 AssembleJobAction 的流程与处理 LinkJobAction 的流程类似:

  1. 获取 AssembleJobAction 的 Inputs

    注意:Inputs 会在后续的合并中从 BackendJobAction 变为 InputAction

  2. 创建 ToolSelector 的实例 TS

  3. 并调用 ToolSelector::getTool 获取支持 Assemble 的工具

    此时,返回的 Tool 是 clang::driver::tools::Clang

  4. 通过 BuildJobsForAction 处理 Inputs

  5. 调用 Compilation::getArgsForToolChain 进行参数转换

  6. 调用 clang::driver::tools::Clang 的 ConstructJob 方法构建 Job

ToolSelector::getTool 对 AssembleJobAction 的处理流程

我们先重点看看 ToolSelector::getTool 的逻辑,该函数比较重要,会存在触发 Action 合并的逻辑

ToolSelector::getTool 方法的流程如下:

  1. 先通过不停地尝试调用 getPrevDependentAction 函数获取最长的一个 ActionChain

  2. 调用 combine 相关的函数依次尝试 3 种不同的组合将多个 JobAction 合并到一个,并返回 Tool

    • Assemble + Backend + Compile;
    • Assemble + Backend
    • Backend + Compile
  3. combine 失败后,会查找 BaseAction 对应的 Tool

  4. 前面的各种处理结束后,会通过 combineWithPreprocessor 函数尝试合并 PreprocessJobAction

    image

ToolSelector::getTool 详细流程

下面,我们对 ToolSelector::getTool 流程进行详细的分析

  1. getPrevDependentAction 函数会先检测 Inputs 长度是否等于 1,再将尝试将 Inputs 转为 JobAction 类型

    因为 1、类型转换失败后,会返回 null,2、 InputAction 没有继承自 JobAction

    所以,ActionChain 会在 InputAction 处截止,最后的结果是将 AssembleJobClass、BackendJobClass、CompileJobClass、PreprocessJobClass 填充到 ActionChain

    image

  2. clang::driver::Tool *combineAssembleBackendCompile 函数会检测前 3 个任务的类型是否符合要求

    符合要求后,会通过 CompileJobAction 调用 ToolChain::SelectTool 方法查找对应的 Tool

    注意,合并成功后,Inputs 会被替换为 CompileJobAction 持有的 inputs

    image

    敲重点

    滥用 -fembed-bitcode 是导致编译耗时增长的一个重要原因

    如果想通过Custom Compiler Flags 控制 Release 模式下产生携带 bitcode的二进制文件,推荐使用下面的方法。

    image

    更多 bitcode 相关文章,可以参考 #bitcode

  3. ToolChain::SelectTool 会转发到 Driver::ShouldUseClangCompiler 函数判断 clang 是否支持该 CompileJobAction

    image

  4. Driver::ShouldUseClangCompiler 会依次进行以下判断:

    1. clang 是否支持 JobAction 的源码文件类型
    2. clang 是否支持 JobAction 的类型

    image

    image

    因为,文件类型是 TY_PP_ObjC,JobAction 类型是 CompileJobAction,满足上面的条件,所以,ToolChain::SelectTool 会转发到 ToolChain::getClang 方法进行处理

  5. ToolChain::getClang 内部会以懒加载的方式创建 tools::Clang 的实例

    image

  6. combineAssembleBackendCompile 执行完毕后,会通过 combineWithPreprocessor 再次尝试合并

    该函数处理完成后,PreprocessJobAction 会被替换为 InputAction

    image

Clang::ConstructJob

Clang::ConstructJob 方法负责构建一个 Command,该 Command 负责完成前面被合并的 预处理、编译、后端、汇编 4 个 JobAction

  1. 创建 CmdArgs

    image

  2. 拼装 clang 编译 参数,其中,比较重要的 -cc1 参数就是在这里品拼接

    image

  3. 根据 JobAction 的类型拼装参数

    -emit-obj 代表产出 mach-o 文件

    image

  4. 实例化 CC1Command ,并调用 Compilation::addCommand 方法

    image

InputAction

BuildJobsForActionNoCache 方法在合并 PreprocessJobAction 的过程中,会将 Inputs 变为 InputAction,并在遍历 Inputs 时,会通过 BuildJobsForAction 函数再次调用 BuildJobsForActionNoCache 处理

此时,BuildJobsForActionNoCache 会进行以下处理:

  1. 获取 clang driver 接收的 OPT_INPUT 参数
  2. 实例化 InputInfo ,并返回

image

总结

经过 构建 Jobs 流程,最终会有两个命令行被构建出来

  1. 第一个被构建的命令是 clang -cc1 ... -emit-obj ...

    clang cc1 可以理解执行编译任务的 编译器

  2. 第二个被创建的命令是 ld

x

相关推荐

  • clang 源码导读(1): clang 导读
  • clang 源码导读(2): clang driver 流程简介
  • clang 源码导读(3): clang driver 参数解析
  • clang 源码导读(4): clang driver 构建 Actions
  • clang 源码导读(6): clang driver 执行命令
#clang# #clang driver#
clang 源码导读(4): clang driver 构建 Actions
clang 源码导读(6): clang driver 执行命令
  • 文章目录
  • 站点概览
酷酷的哀殿

酷酷的哀殿

单身狗

74 日志
83 标签
    • 前言
    • 为什么需要 构建 Jobs 流程?
    • 一、BuildJobs
    • 二、BuildJobsForAction
    • 三、BuildJobsForActionNoCache
      • BindArchAction
      • LinkJobAction
      • AssembleJobAction
      • InputAction
    • 总结
© 2021 酷酷的哀殿 京ICP备18052541号-2
Powered by - Hugo v0.80.0
Theme by - NexT
0%