前言
2011年前后,GCC后端代码的阅读陷入了举步维艰的境地。GGC-3.4.6后端代码的可读性不友好(当前版本没看过,不予评价。不过据说4.0进行的重构,应该会好些),充斥着动辄数千行的函数,包含着庞大的switch块以及if块,加之函数间的相互调用,看得人昏头转向。
穷极思变,转而看了一下LLVM的源代码。一撇之下,惊为天人。
LLVM是第一次碰到的、几乎完全用C++开发的大规模开源项目。LLVM将C++的潜力发挥得淋漓尽致,相较于直接使用指针,LLVM通过泛型技术构建了大量的容器与迭代器来遍历各种数据结构,使代码的安全性与模块化都得到很大的提高。
通过良好的模块化设计,善用C++提供的抽象支持,LLVM功能模块间实现了松耦合,替换模块或者增加模块就能扩展功能,无需在框架上修改。这一点使LLVM成为了学术界的宠儿。因为与GCC相比,LLVM不要求改写者深入了解各个部分,只需了解接口即可,而GCC则要求改写者对整个GCC代码有相当程度的精通。曾有团队先后使用LLVM与GCC为一款DSP(tricore)开发编译器,结果发现LLVM所需的工作量与难度都远小于GCC。从这点来说,LLVM非常成功。
此外,LLVM的指令生成技术十分先进。它使用一种称为tlbgen的语言来描述目标机器的方方面面(该语言的许多语法成分被LLVM的发明人ChrisLatter借鉴到新发明的Swift语言里),然后在编译LLVM的过程里,通过工具解析生成C++的源代码。
这个过程相当复杂,不过随之而来的是,LLVM有许多代码可以自动生成。而且现有的描述架构可以方便地支持新的处理器。不过,对tblgen语言以及解析工具,LLVM文档并没有深入描述。这也正是我的兴趣所在。
因为集成了大量的算法以及数据结构,编译器从来都是最复杂的软件之一。无论LLVM还是GCC,它们的行数都是以千万计的。如何构建一个模块化、扩展性良好,考虑维护友好性的编译器,绝对是一个困难的任务。但很可惜,常见的编译原理的书重点都在编译器的理论,给出常见算法的概要、框架,以及编译器系统的一般性描述。从这一点来说,编译原理给出的连编译器蓝图都算不上,充其量只是一张比较详细的草图。从草图到活生生的例子、具体的代码,期间的差别,不可以道里计。
因此,看完编译原理,总觉得似懂非懂,好像知道一些,却又说不出所以然——很多细节、答案都藏在源代码里。但另一方面,看编译器源代码也容易陷入细节,只见树木而不见森林,而且代码量也确实很大。不过,不管怎么说,饭要一口口吃,路要一步步走,退一万步来说,LLVM的架构极其先进,如果能从其软件架构中学到些什么,也是有益的。
从今天起(2017-3-10),将不定期更新我学习LLVM的笔记(就我个人经验,阅读复杂的程序,做详细笔记总能事半功倍)。因为LLVM实在庞大,虽穷数年勤苦,就X86目标机器,尚有很大部分代码没有触及,希望慢慢能补齐。
另,原创不易,转载请注明出处。若有错漏,请留言或发邮至[email protected],望不吝赐教。谢谢!
第一部分目录
1. 概述(更新)
1.1. DAG指令选择生成器
1.2. SelectionDAG
1.3. v7.0的变化
2. LLVM的后端描述(更新)
2.1. 类型的描述
2.2. 指令的描述
2.2.1. TablgeGen的
2.2.1.1. dag
2.2.1.2. List
2.2.1.3. String
2.2.1.4. Bit、Int
2.2.1.5. Bits
2.2.2. 参数描述(更新)
2.2.2.1. 寄存器
2.2.2.1.1. Register
2.2.2.2.1. X86的例子
2.2.2.3.1. RegisterClass
2.2.2.4.1. ARM的例子
2.2.2.2. 一般操作数(更新)
2.2.3. 约束条件
2.2.4. 匹配模板(更新)
2.2.4.1. SDNode
2.2.4.1.1. 类型的描述
2.2.4.1.2. 属性的描述
2.2.4.1.3. SDNode的派生定义
2.2.4.2. 可复用的结构(更新)
2.2.4.2.1. PatFrag
2.2.4.2.1.1. SDNodeForm
2.2.4.2.1.2. PatFrag的例子
2.2.4.2.2. 其他特殊PatFrag派生类
2.2.5. 另一个匹配方式
2.2.5.1. 谓词Predicate
2.2.5.2. 指令定义的例子
2.2.5.3. 指令展开的例子
2.2.6. 调度信息(更新)
2.2.7. X86指令的定义
2.2.8. 指令展开的例子
2.3. 汇编处理描述(更新)
2.4. 目标机器描述
2.4.1. 特征描述
2.4.2. 调度信息
2.4.2.1. ATOM的描述
2.4.2.2. Sandy Bridge的描述
2.4.2.2.1. 资源的描述
2.4.2.2.2. 执行的描述
2.4.2.2.3. Sandy Bridge的定义
2.4.2.3. 总结
3. TableGen生成的代码(更新)
3.1. 概述
3.2. TD文件的解析结果
3.3. 寄存器的后端描述(更新)
3.3.1. 概述
3.3.2. SetTheory
3.3.3. CodeGenTarget
3.3.3.1. V7.0的扩展——CodeGenHwModes
3.3.4. CodeGenRegBank
3.3.4.1. 复合索引
3.3.4.2. Register DAG
3.3.4.2.1. 寄存器的表示
3.3.4.2.2. 构建子寄存器关系
3.3.4.2.3. V7.0推导寄存器串接闭包
3.3.4.2.4. 隐含的孙子寄存器
3.3.4.2.5. 构建上级寄存器关系
3.3.4.3. RegisterClass DAG
3.3.4.3.1. 寄存器类的表示
3.3.4.3.2. 进一步的推导
3.3.4.3.2.1. 包含指定寄存器索引的最大子集
3.3.4.3.2.2. 两个寄存器类的公共子集
3.3.4.3.2.3. 超级寄存器类
3.3.4.3.3. 寄存器类的包含关系
3.3.5. 信息的完善(更新)
3.3.5.1. 完整的索引信息
3.3.5.2. 寄存器单元的权重
3.3.5.3. 寄存器单元的同类集
3.3.5.4. 寄存器的包含关系
3.3.5.5. 其他信息的设置
3.3.6. 输出代码(更新)
3.3.6.1. 枚举常量
3.3.6.2. MC使用的寄存器描述
3.3.6.2.1.MC对寄存器的定义
3.3.6.2.2. 差分编码
3.3.6.2.3. 生成的差分编码表
3.3.6.2.4. 与GCC/GDB编号间的映射
3.3.6.3. X86GenRegisterInfo的定义
3.3.6.4. CodeGen使用的寄存器描述(更新)
3.3.6.4.1. 寄存器类型与索引
3.3.6.4.2. 寄存器类的描述
3.3.6.4.3. 定义composeSubRegIndicesImpl方法(更新)
3.3.6.4.4. 定义getSubClassWithSubReg方法
3.3.6.4.5. 定义获取权重及压力数据方法(更新)
3.3.6.4.6. X86GenRegisterInfo的构造函数(更新)
3.3.6.4.7. 被调用者保存的寄存器
3.4. DAG指令选择器的代码生成(更新)
3.4.1. 概述
3.4.2. CodeGenDAGPatterns对象
3.4.2.1. SDNode的处理
3.4.2.2. SDNodeXForm的处理
3.4.2.3. ComplexPattern的处理
3.4.2.4. PatFrag的处理(更新)
3.4.2.4.1. 模式树的处理
3.4.2.4.2. PatFrag的展开(更新)
3.4.2.4.3. 类型推导(更新)
3.4.2.4.3.1. 应用类型限定
V7.0的处理
3.4.2.4.3.2. 简化(更新)
3.4.2.4.3.3. 类型的最后确定
3.4.2.4.4. OperandWithDefaultOps
3.4.2.5. Instruction的处理(更新)
3.4.2.5.1. 无模式匹配的Instruction
3.4.2.5.2. 有模式匹配的instruction(更新)
V7.0的处理
3.4.2.5.3. ApplyTypeConstraints对Instruction的处理(更新)
3.4.2.5.4. 构建PatternToMatch实例
V7.0的处理
3.4.2.6. Pattern的处理(更新)
V7.0的处理
3.4.2.7. 展开基于硬件模式的类型(v7.0)
3.4.2.8. PattenToMatch的变体(更新)
3.4.2.9. 推导、验证指令的性质(更新)
V7.0的处理
3.4.3. 指令选择代码的自动生成(更新)
3.4.3.1. PatternToMatch的排序
3.4.3.2. 从PatternToMatch到Matcher(更新)
3.4.3.2.1. MatcherGen对象
3.4.3.2.2. 生成匹配代码的Matcher对象
3.4.3.2.3. 生成结果代码的Matcher对象(更新)
V7.0的处理
3.4.3.3. Match对象序列的优化(更新)
3.4.3.3.1. 合并Matcher对象
3.4.3.3.2. 下调谓词(V7.0删除)
3.4.3.3.3. 提取等价对象
3.4.3.4. 生成MatcherTable(更新)
3.4.3.5. 辅助函数的生成(更新)
3.4.4. 生成代码如何辅助指令选择(更新)
3.4.4.1. 概述
3.4.4.2. 基本数据结构
3.4.4.3. 选择过程(更新)
3.4.4.3.1. 概述
3.4.4.3.2. OPC_SwitchOpcode与谓词匹配
3.4.4.3.3. OPC_SwitchOpcode与OPC_SwitchType等(更新)
3.4.4.3.4. OPC_EmitMergeInputChainsN等(更新)
3.4.4.3.5. OPC_MorphNodeTo等(更新)
3.5. 指令信息的生成(更新)
3.5.1. CodeGenSchedModels对象
3.5.1.1. SchedMachineModel定义
3.5.1.2. SchedReadWrite的处理
3.5.1.3. 初步构建调度类型(更新)
3.5.1.4. 执行步骤的关联
3.5.1.5. ItinRW定义的处理
3.5.1.6. V7.0收集不支持的特性
3.5.1.7. 推导调度类型(更新)
3.5.1.7.1. 从ItinRW定义推导
3.5.1.7.2. 从InstRW定义推导(更新)
3.5.1.7.3. 从CodeGenSchedClass的Writes容器内容推导
3.5.1.8. 保存资源对象
3.5.1.9. 收集可选的处理器信息(V7.0)
3.5.1.9.1. RegisterFile的定义
3.5.1.9.2. 处理器回收控制单元定义
3.5.1.9.3. 处理器的性能计数器定义
3.5.1.9.4. 检查完整性
3.5.2. 代码生成(更新)
3.5.2.1. 枚举常量
3.5.2.2. 操作数描述数组
3.5.2.3. 指令描述数组(更新)
3.5.2.4. 指令名差分表
3.5.2.5. getNamedOperandIdx方法与操作数类型
3.6. 描述目标机器的数据结构(更新)
3.6.1. 对目标机器描述的解析
3.6.2. 代码生成
3.6.2.1. 处理器特征的描述
3.6.2.2. 资源及其使用的描述(更新)
3.6.2.3. 功能单元与旁路定义
3.6.2.4. 资源描述的调度(更新)
3.6.2.4.1. SchedReadWrite数据的收集
3.6.2.4.2. SchedReadWrite及资源间的关联
3.6.2.5. 输出代码与数据结构(更新)
3.6.2.5.1. 资源使用与时延
3.6.2.5.2. 处理器资源模型
V7.0的变化(更新)
V7.0的变化——resolveVariantSchedClassImpl()等
3.6.2.6.3. 处理器特征数据(更新)
V7.0的变化——resolveSchedClass()等(更新)
V7.0的变化——getHwMode()
3.7. 描述调用惯例的数据结构(更新)
3.7.1. TD的基本类型与描述
3.7.2. X86调用惯例的TD描述
3.7.2.1. 返回值惯例
3.7.2.2. 参数传递惯例(更新)
3.7.2.3. 调用惯例的分发
3.7.2.4. 被调用者保存的寄存器
3.7.3. TableGen的处理(更新)
3.7.3.1. 基本数据结构
3.7.3.2. 代码的生成
3.8. RegisterBank代码的自动生成(v7.0)
3.8.1. 数据结构
3.8.2. TableGen定义的解析
3.8.3. 代码生成
3.9. X86 EVEX2VEX映射表的生成(v7.0)
3.10. X86折叠表的生成(v7.0)
3.10.1. 表的查找
3.11. GlobalISel代码的自动生成(v7.0)
3.11.1. 概述
3.11.1.1. 指令的定义
3.11.1.2. 其他定义
3.11.2. 模式分析(v7.0)
3.11.2.1. 准备工作
3.11.2.2. 匹配表的生成对象(v7.0)
3.11.2.2.1. 对象体系
4. 指令选择
4.1. 总述
4.1.1. LLVM IR语法
4.1.1.1. LLVM IR内存中模型
4.1.2. 理解指令选择阶段
4.1.1.2. 类SelectionDAG