概述
编译器前端将源代码转换为等效形式的IR,IR经过优化器优化后传递到后端,由代码生成器将IR 代码转换为汇编代码或目标代码
在这个过程中,后端首先通过指令选择器目标机器支持的指令来实现IR指令
至于指令的顺序,可由后续的指令调度器决定
指令选择器
指令选择器选择指令的基本要求是确保指令能在目标机器上实现与IR相同的功能。其次,在特定的目标机器上,尽量使某些指令序列在执行时比其他指令效率更高
指令选择器应在条件允许下,尽可能选择高效的指令序列。因此,指令选择的目标可以归结为两个方面:即模式匹配和模式选择
模式匹配
模式匹配要解决的问题是找出可以实现IR的候选指令序列,这类候选指令序列通常有若干个
模式选择
模式选择要解决的问题是从若干个候选指令集中选出性能最好的指令序列
模式选择问题可以看作一个优化问题,即指令序列中每条指令的执行都有代价,模式选择的目标是最小化所选指令序列的代价的总和
指令选择的目标
指令执行代价的度量标准多种多样,可以是执行时间、占用内存大小或功耗
最常见的度量标准是执行时间,通过指令执行时间的最小化,实现程序在目标机器上整体执行性能的最大化
这里的目标机器通常是指程序编译针对的硬件平台,目标机器中必须包括可以连续解释和执行机器代码的处理器
目标机器的所有可用指令集合称为指令集,其行为由目标机器的指令集架构(Instruction Set Architecture, ISA) 规定
指令的实现方式
在LLVM中有三种指令选择实现方式:基于SelectionDAG的指令选择、快速指令选择和全局指令选择
所有的指令选择pass 都是基于MachineFunctioinPass类
在LLVM的指令选择阶段,Instrcution 类表示的LLVM IR 转换为 MachineInstr实例,MachineInstr类可用于表示后端所有目标相关机器指令
与Instrunction类相比,MachineInstr类表示的指令更接近目标平台。LLVM后端的SelectionDAGBuilder模块通过遍历LLVM IR 文件,将其中的基本块转换为图形式的SelectionDAG对象
构建SelectionDAG对象是指令选择的第一步。为了适应指令选择的需要,SelectionDAG类以低阶数据依赖DAG的形式表示LLVM IR,在此基础上可以实现目标相关优化和代码简化
SelectionDAG 类的定义(代码 <llvm_root>/llvm/include/llvm/CodeGen/SelectionDAG.h)
LLVM 后端将LLVM IR 转换为机器指令的标准方式是通过DAG实现的。即目标无关的SelectionDAG经过指令选择后,转换为目标相关的、包含机器代码的新DAG
利用目标描述中提供的模式,IR指令通过模块匹配可转换为机器指令。为此,LLVM实现了一种复杂的模式匹配算法
在编译LLVM工程时,会调用到TableGen工具。TableGen是LLVM 项目的重要的组成部分,该工具根据.td目标描述文件中的指令定义生成模式匹配表,并建立ISD(Instrunction Selection DAG) 和 ISD 与机器指令之间的映射关系
以AMDGPU后端的S_NOT_B64指令定义为例
def S_NOT_B64 : SOP1_64 <"s_not_b64", [(set i64:$sdet, (not i64: $src0))]>
其中,[(set i64: $ sdst, (not i64: $src0))] 是S_NOT_B64指令的选择模式,not 是LLVM的预定义操作符,定义如下:
def not: PatFrag<(ops node: $in), (xor node: $in, -1)>
PatFrag 类也定义在TargetSelectionDAG.td 文件中,其作用类似于 “%result = xor i64 % a, -1” 的匹配片段(matching fragment) 替换为not 预定操作符
S_NOT_B64 指令的选择模式描述了S_NOT_B64指令的功能,即调用具有64位整数类型参数的not节点,并返回64位整数类型的结果
当指令选择器在DAG中检测到符合模式的序列时,即会将其匹配到S_NOT_B64指令
上述选择模式是最简单的用法。除此之外,TableGen还支持多种模式类型,如PatFrags、OutPatFrag等。这种模式类型定义参见TargetSelectioinDAG.td