2312llvm,04后端上
后端
后端
由一套分析和转换趟
组成,任务是生成代码
,即把LLVM
中间(IR)
转换为目标代码(或汇编)
.
LLVM
支持广泛目标:ARM,AArch64,Hexagon,MSP430,MIPS,NvidiaPTX,PowerPC,R600,SPARC,SystemZ,X86,
和XCore
.
所有这些后端
共享一套,按通用API
方法抽象后端任务
的目标无关
生成代码的一部分,即公共接口
.
每个目标
必须特化
生成代码通用类
,以实现目标相关
行为.
这里,介绍LLVM
后端的一般性质
,对想编写新的后端
,维护已有后端
,或编写后端趟
,都是很有用
的.
概述
要经历多个步骤
,才能转换LLVMIR
为目标汇编代码
.把IR
转换为后端友好的指令,函数,全局变量
的表示.随着程序经历各种后端变换
,该表示
越来越接近实际目标指令
.
下面简略描述生成代码
的各个阶段
:
1,选指(InstructionSelection)
过程,表示把内存
中的IR
转换为目标相关
的SelectionDAG
节点.
起初,该过程把三地址
结构的LLVMIR
转换为DAG
有向无环图形式.
每个DAG
可表示单个基本块的计算
,即每个基本块
关联不同的DAG
.典型节点
表示指令
,而边
代表它们的数据流依赖
等.
可转换为DAG
很重要,这样LLVM
生成代码库,可运用基于树
的模式
匹配选指算法
,经过一些调整
,也能在DAG
(而不仅是树
)上工作.
该过程
结束时,DAG
已将它所有的LLVMIR
节点转换为表示机器指令
而不是LLVM
指令的目标机器节点
.
2,选指之后,就知道了,会使用哪些目标指令
来计算每个基本块
.
在SelectionDAG
类中编码
.然而,需要返回三地址
表示,来决定基本块
内部的指令顺序
,因为DAG
并不表明相互独立指令
间的顺序
.
第1个
调度指令(InstructionScheduling)
,也叫前分配
寄存器(RA)
调度,排序
指令来尽量指令级并行
.
然后按MachineInstr
三地址表示转换
这些指令.
3,在分配
寄存器(RegisterAllocation)
前LLVMIR
的寄存器集是无限
的,它把无限引用
的虚寄存器
转换为有限
的目标相关
的寄存器集
,不够时挤入(spill)
到内存.
4,第2次
调度指令,也叫后分配
寄存器(RA)
调度,此时.因此时
在该点可获得真实
寄存器信息,某些类型
寄存器有额外风险和延迟
,可用它来改进指令顺序
.
5,发射代码(CodeEmission)
阶段表示把指令
从MachineInstr
转换为MCInst
实例.该新表示
更适合汇编器和链接器
,它有两个选择
:输出汇编代码
或输出(blob)
二进制块到指定目标代码格式
.
如此,整个后端流程
用到了四种不同层次
的指令表示
:内存中的LLVMIR
,SelectionDAG
节点,MachineInstr
,和MCInst
.
使用后端工具
llc
是后端
主要工具.如果用前面的sum.bc
位码,可用下面
命令生成它的汇编代码
:
$ llc sum.bc -o sum.s
或可用下面命令,生成目标代码
:
$ llc sum.bc -filetype=obj -o sum.o
使用以上命令时,llc
会试选择匹配sum.bc
位码中指定目标三元组
的一个后端
.使用-march
选项可覆盖
它而选择
指定后端.如,用下面命令生成MIPS
目标代码:
$ llc -march=mips -filetype=obj sum.bc -o sum.o
如果运行llc-version
命令,llc
会显示所支持的-march
选项的完整列表.注意,该列表
和LLVM
配置中用到的-enable-targets
选项兼容.
注意,刚才确保让llc
用不同后端
为起初是为x86
编译的位码
生成代码.
因为C/C++
语言有目标相关
属性,所以相关性会在LLVMIR
中体现.
因此,当位码
目标三元组
和运行llc -march
的目标不匹配
时,必须小心.可能导致ABI
不匹配,坏程序行为
,甚至导致生成
代码失败.
然而一般,会成功生成代码
,但更糟糕,它有微妙
的bug
.
为了理解IR
的目标依赖.考虑程序分配了存储
不同串的char
指针的向量
,你用通用的C语句
malloc(sizeof(char*)*n)
来为串向量
分配内存.
如果给前端
指定了目标,比如32
位MIPS
架构,它生成的代码会让malloc
分配nx4
字节的内存,因为在32
位MIPS
上每个指针是4字节
.
然而,如果用llc
编译该位码
,并确保指定x86_64
架构,它就生成了个坏程序
.
运行时,会有潜在的段错误,因为x86_64
架构的每个指针是8字节
,这使得malloc
分配的内存
不够.
在x86_64
上正确的malloc
调用是分配nx8
字节.
学习后端代码结构
在LLVM
源码树的不同目录
分散有后端实现
.在lib
目录和它的CodeGen,MC,TableGen
,和Target
子目录中,是生成代码
背后的主要库:
1,CodeGen
目录包含实现了所有通用
生成代码算法
的文件和头文件
:选指,调度指令,分配寄存器,和需要的分析
.
2,MC
目录实现了汇编器(汇编解析器)
,松弛算法
(反汇编器
),和特定目标文件格式
,如ELF,COFF,Macho
等等低级功能
.
3,TableGen
目录包含TableGen
工具,根据.td
文件中的高级
目标描述生成C++
代码的完整实现
.
4,在Target
的子目录
中实现各个目标,如包括多个.cpp,.h,
和.td
文件的Target/Mips
目录.为不同目标
实现类似功能
的文件
一般有相同名字
.
如果编写新的后端
,仅在Target
目录中的一个子目录
放置代码.如,用Sparc
来阐明Target/Sparc
子目录中的组织:
文件名 | 描述 |
---|---|
SparcInstrInfo.td,SparcInstrFormats.td | 指令和格式 定义 |
SparcRegisterInfo.td | 寄存器和寄存器类定义 |
SparcISelDAGToDAG.cpp | 择指 |
SparcISelLowering.cpp | 选择DAG 节点降级 |
SparcTargetMachine.cpp | 目标相关属性(如数据布局 和ABI )信息 |
Sparc.td | 定义计算机特征 ,CPU 变体和扩展 功能 |
SparcAsmPrinter.cpp | 发射 汇编代码 |
SparcCallingConv.td | ABI 定义调用约定 |
一般,后端都按此代码结构
组织,因此开发者很容易地把一个后端
具体问题映射
到另一个后端
中.如,你正在编写Sparc
后端的SparcRegisterInfo.td
寄存器信息文件,
并且想知道x86
后端是如何实现
它的,只需要查看Target/X86
目录中的X86RegisterInfo.td
文件.
了解后端库
llc
的非共享
代码是相当小的(见tools/llc/llc.cpp
),如同其它LLVM
工具,大部分都按可重用的库
实现功能
.
对llc
,由生成代码库
提供它的功能
.这组库可分为目标相关
和目标无关
部分.在不同文件
中,保存生成目标相关
的库和目标无关
的库,这样可链接
期望的严格目标后端
.
如,配置LLVM
时设置-enable-targets=x86,arm
,这样llc
就只会链接x86
和ARM
的后端库
.
注意,所有的LLVM
库都以libLLVM
为前缀.在此
省略该前缀.下面列举了一些目标无关
生成代码的库:
1,AsmParser.a
:包含解析汇编文本
并实现汇编器
的代码
2,AsmPrinter.a
:包含打印汇编语言
,并实现生成汇编文件
后端的代码
3,CodeGen.a
:包含生成代码
算法
4,MC.a
:包含MCInst
类及其相关的类,并用来表示LLVM
允许的最低级
程序
5,MCDisassembler.a
:实现了一个读取
目标代码文件,并按MCInst
对象解码
字节的反汇编器
6,MCJIT.a
:实现(即时
)生成代码.
7,MCParser.a
:包含MCAsmParser
类的接口,用来解析
汇编文本,执行部分汇编器
工作
8,SelectionDAG.a
:包含SelectionDAG
及其相关的类
9,Target.a
:包含可让目标无关
算法请求其它库(目标相关部分
)实现的目标相关
功能的接口
另一方面,下面是目标相关
的库:
1,<Target>AsmParser.a
:包含AsmParser
库的目标相关
的部分,负责为目标机器
实现汇编器
2,<Target>AsmPrinter.a
:包含打印目标指令
的功能,并让后端生成汇编语言文件
3,<Target>CodeGen.a
:包括具体寄存器处理规则
,选指和调度
等后端目标相关功能
的主体.
4,<Target>Desc.a
:包含低级MC
设施的目标机器信息
,负责注册如MCCodeEmitter
等目标相关的MC
对象.
5,<Target>Disassembler.a
:用目标相关
的功能补充了MCDisassembler
库,来建造可读字节
并解码它们为MCInst
目标指令的系统
.
6,<Target>Info.a
:负责在LLVM
生成代码系统中注册目标
,提供了让目标无关
的生成代码库可访问目标相关
功能的接口类
.
在这些库名字中,用目标名
替换<Target>
,如,X86AsmParser.a
是X86
后端的解析库
的名字.完整的LLVM
安装在<LLVM_INSTALL_PATH>/lib
目录中包含
这些库.
学习LLVM
后端,如何用TableGen
LLVM
使用TableGen
面向记录的语言,来描述多个
编译器阶段
用到的信息.如,在前端中,简单讨论了如何用TableGen
文件(以.td
为扩展名)描述前端的不同诊断信息
.
最初,LLVM
团队开发TableGen
是为了帮助编写LLVM
后端的.尽管生成代码库设计
强调,要干净分离
不同目标特性
,
如,用不同的类
表示指令的寄存器信息和其他
,但是最终后端代码
,不得不在多个不同
文件中表示相同
的某种机器特征
.
问题是,不仅要编写后端代码
,还在代码中引入了信息冗余
,必须手工同步
.
如,想修改后端
如何处理寄存器,要修改代码
中几处不同部分:在分配
寄存器器中说明支持
的寄存器类型
;
在汇编打印器
中说明如何打印
该寄存器;在汇编解析器
中说明,按汇编如何解析
;及在反汇编器
中,说明它要知道的寄存器编码方式
.
这样,很难维护
后端代码.
为此,创造了关于目标的TableGen
中央信息库.
想法是:在单独位置
声明机器的某种特性
,如在<Target>InstrInfo.td
中描述机器指令
,然后TableGen
后端用该信息库
去具体实现,如生成自己写很烦的匹配模式选指
算法等任务.
如今,用TableGen
来描述各种
目标相关信息,如指令格式,指令,寄存器
,匹配模式DAG
,选指匹配顺序,调用惯例
,和目标CPU
属性(支持的指令集架构(ISA)
特征和处理器族)等.
注意,还在追求全自动
为处理器生成后端,模拟器,和硬件综合描述文件
.
典型方法
是用类似TableGen
的声明描述语言
表示所有机器信息
,然后用工具继承
要求的各种软件(和硬件)
,并求值,测试
处理器架构.
但这很难,和手写
工具相比,自动生成
工具质量很差.LLVMTableGen
是辅助
完成较小任务,但仍给你完整的控制权
,让你用C++
代码实现自定义逻辑
.
语言
TableGen
语言由创建记录
的定义和类(class)
组成.def
定义用来根据class
和multiclass
关键字实例化
记录.
由TableGen
后端进一步处理
这些记录,如为以下组件生成领域相关
信息:生成代码,Clang
诊断,Clang
驱动选项,和静态解析器检查器
.
因此,由后端
给出记录
所表示的实际意思
,而记录
仅保存信息.
如,假设想为1个架构定义ADD
和SUB
指令,而ADD
有两种形式
:所有操作数
都是寄存器
,一个操作数是寄存器
一个是立即数
.
SUB
指令只有第1种
形式.看下面insns.td
文件的示例代码:
class Insn<bits <4> MajOpc, bit MinOpc> {bits<32> insnEncoding;let insnEncoding{15-12} = MajOpc;let insnEncoding{11} = MinOpc;
}
multiclass RegAndImmInsn<bits <4> opcode> {def rr : Insn<opcode, 0>;def ri : Insn<opcode, 1>;
}
def SUB : Insn<0x00, 0>;
defm ADD : RegAndImmInsn<0x01>;
Insn
类表示一个普通指令
,RegAndImmInsn
表示另一种形式的指令.def SUB
定义了SUB
记录,而defm ADD
定义了两个记录:ADDrr
和ADDri
.
用llvm-tblgen
工具,可处理一个.td
文件并检查
结果记录:
$ llvm-tblgen -print-records insns.td
------------- Classes -----------------
class Insn<bits<4> Insn:MajOpc = {?,?,?,?}, bit Insn:MinOpc = ?> {bits<5> insnEncoding = { Insn:MinOpc, Insn:MajOpc{0},Insn:MajOpc{1}, Insn:MajOpc{2}, Insn:MajOpc{3} };string NAME = ;
}
------------- Defs -----------------
def ADDri { //Insn ribits<5> insnEncoding = { 1, 1, 0, 0, 0 };string NAME = "ADD";
}
def ADDrr { //Insn rrbits<5> insnEncoding = { 0, 1, 0, 0, 0 };string NAME = "ADD";
}
def SUB { //Insnbits<5> insnEncoding = { 0, 0, 0, 0, 0 };string NAME = ;
}
通过llvm-tblgen
工具还可使用TableGen
后端;输入llvm-tblgen -help
,会列举所有后端选项
.注意此例没有用LLVM
相关的域,且未与后端工作.
TableGen
更多信息
了解生成代码的.td
文件
如前,生成代码
广泛使用TableGen
记录来表达目标相关
信息.看看生成代码的TableGen
文件.
目标属性
<Target>.td
文件(如,X86.td
)定义了所支持的ISA
特性和处理器族.如,X86.td
定义了AVX2
扩展:
def FeatureAVX2 : SubtargetFeature<"avx2", "X86SSELevel", "AVX2", "Enable AVX2 instructions", [FeatureAVX]>;
def
关键字从SubtargetFeature类
类型定义
了FeatureAVX2
记录.最后参数
是在定义
中已包含的其它特性
的一个列表
.
因此,带AVX2
的处理器
包含所有AVX
指令.
此外,还可定义包含它提供的ISA
扩展和特性的处理器类型
:
def : ProcessorModel<"corei7-avx", SandyBridgeModel, [FeatureAVX, FeatureCMPXCHG16B, ..., FeaturePCLMUL]>;
<Target>.td
文件还包含了所有其它的.td
文件,且是描述目标相关
域信息的主文件
.llvm-tblgen
工具必须总是从它那获得目标
的任意TableGen
记录.
如,用下面命令,输出x86
的一切记录:
$ cd <llvm_source>/lib/Target/X86
$ llvm-tblgen -print-records X86.td -I ../../../include
X86.td
文件有TableGen
用来生成X86GenSubtargetInfo.inc
文件的部分信息
,但不止,一般,不能从.td
文件映射
到.inc
文件.
为此,考虑<Target>.td
文件是个用TableGen
的include
指令包含
了所有其它的.td
文件的重要的顶层文件
.
因此,生成C++
代码时,TableGen
总是解析
所有后端.td
文件,使你可自由地在任意
的最合适位置放置
记录.
即使X86.td
包含了所有其它的后端.td
文件,除了include
指令,``内容也要同Subtargetx86
子目标定义保持一致
.
如果查看实现x86Subtarget
类的X86Subtarget.cpp
文件,会发现一个调用"#include"X86GenSubtargetInfo.inc"
的C++
预处理器指令,表明如何在普通的codebase
中嵌入TableGen
生成的C++
代码.
该特别的include
文件包含关联
了串描述及其它相关的资源特征的处理器特征常量
及处理器特性向量
.
寄存器
在<Target>RegisterInfo.td
文件中,定义寄存器和寄存器类
.之后在定义指令
中,寄存器类
把指令操作数
绑定到特定
寄存器集合中.
如,X86RegisterInfo.td
用下面语句
定义了16
位的寄存器:
let SubRegIndices = [sub_8bit, sub_8bit_hi], ... in {
def AX : X86Reg<"ax", 0, [AL,AH]>;
def DX : X86Reg<"dx", 2, [DL,DH]>;
def CX : X86Reg<"cx", 1, [CL,CH]>;
def BX : X86Reg<"bx", 3, [BL,BH]>;
...
此处let
构建指令,用来定义
额外的即{...}
区域中的所有记录
都有的SubRegIndices
字段.
从X86Reg
类继承16
位寄存器的定义,为每个
寄存器保存它的名字,数目
,及8位
子寄存器的列表
.如下重新产生16
位寄存器的寄存器类
定义:
def GR16 : RegisterClass<"X86", [i16], 16,(add AX, CX, DX, ..., BX, BP, SP,R8W, R9W, ..., R15W, R12W, R13W)>;
GR16
寄存器类,包含所有的16
位寄存器和它们各自分配寄存器
的优先顺序.在TableGen
处理后,每个寄存器类
的会得到RegClass
后缀,如,GR16
变成了GR16RegClass
.
TableGen
会生成寄存器和寄存器类
的定义,来收集它们的相关信息
,汇编器
的二进制编码
,和DWARF
(Linux
调试记录格式)信息.
可用llvm-tblgen
查看TableGen
生成的代码:
$ cd <llvm_source>/lib/Target/X86
$ llvm-tblgen -gen-register-info X86.td -I ../../../include
也可查看LLVM
编译过程中生成的C++
文件:
<LLVM_BUILD_DIR>/lib/Target/X86/X86GenRegisterInfo.inc
X86RegisterInfo.cpp
包含来辅助
定义X86RegisterInfo
类,inc
文件包含了寄存器的枚举
,调试后端
且不知道16
表示什么
寄存器时,它是一份有用的参考
.
指令
分别在<Target>InstrFormats.td
和<Target>InstInfo.td
文件中定义指令格式和指令
.指令格式
包含按二进制格式
写指令所必需的指令编码字段
,而指令记录
按单个记录
表示一条指令
.
可创建TableGen
类用来继承
指令记录的中间指令类
,以找出公共特征
,如相似数据处理
指令的公共编码
.
然而,每个指令或格式
必须是在include/llvm/Target/Target.td
中定义的指令
的TableGen
类的直接或间接
子类.
它的字段显示了在指令记录
中,TableGen
后端期望找到的内容
:
class Instruction {dag OutOperandList;dag InOperandList;string AsmString = "";list<dag> Pattern;list<Register> Uses = [];list<Register> Defs = [];list<Predicate> Predicates = [];bit isReturn = 0;bit isBranch = 0;
...
dag
是个用来保存SelectionDAG
节点的特殊TableGen
类型.这些节点
表示选指过程
中的操作码,寄存器,或常量
.代码中这些字段
的意义:
1,OutOperandList
字段存储
结果节点,让后端
确定代表指令输出
的DAG
节点.
如,在MIPS
的ADD
指令中,按(outs GP32Opnd:$rd)
定义字段
.此例中:
1,outs
是个指示其子
是输出操作数
的特殊DAG
节点
2,GPR32Opnd
是MIPS
特有的指示MIPS32
位的通用
寄存器实例的DAG
节点
3,$rd
是用来识别节点
的任意寄存器名字
.
2,InOperandList
字段保存
输入节点,如,在MIPSADD
指令中,它是
(ins GPR32Opnd:$rs, GPR32Opnd:$rt)
3,AsmString
字段表示指令汇编串
,如,在MIPS
的ADD
指令中,它是"add$rd,$rs,$rt"
.
4,Pattern
是选择指令
时匹配
模式的dag
对象列表.如果匹配一个模式
,选指
会用该指令
替换匹配节点
.
如,在MIPS
的ADD
指令:
(set GPR32Opnd:$rd, (add GPR32Opnd:$rs, GPR32Opns:$rt))
模式中,[and]
表示只有一个在类似LISP
表示法的小括号
间定义的dag
元素列表的内容
.
5,Uses
和Defs
记录在执行指令
时,隐式使用和定义
的寄存器列表
.如,RISC
处理器的return
指令隐式使用返回地址寄存器
,而call
指令隐式定义返回地址寄存器
.
6,Predicates
字段,在选指
试匹配指令
前,存储
要检查的前提列表
.如果检查
失败了,就没有匹配
.如,一个前提
可能说明,该指令只对特定子目标
有效.
如果用选择了另一个子目标
的目标三元组
运行生成代码
,该前提
会求值
为假,而该指令
就不会匹配.
7,此外,其它还包括isReturn
和isBranch
字段,它们用指令行为信息
增强生成代码
.如,如果isBranch=1
,则生成代码
就知道该指令
是分支
指令,因此必须放在基本块尾
.
下面代码块中,可见在SparcInstrInfo.td
中的XNORrr
指令的定义.它用到了(在SparcInstrFormats.td
中定义的)F3_1
格式,它包括了SPARCV8
架构手册的F3
格式的一部分:
def XNORrr : F3_1<2, 0b000111,(outs IntRegs:$dst), (ins IntRegs:$b, IntRegs:$c), "xnor $b, $c, $dst",[(set i32:$dst, (not (xor i32:$b, i32:$c)))]>;
该XNORrr
指令有两个IntRegs
(一个表示SPARC32
位整数寄存器类
的目标相关
的DAG
节点)源操作数和一个IntRegs
结果,类似:
OutOperandList = (outs IntRegs:$dst)
InOperandList = (ins IntRegs:$b, IntRegs:$c)
AsmString
汇编通过$记号
引用指定的操作数
:"xnor $b,$c,$dst"
.模式
列表元素包含
应该匹配到该指令
的SelectionDAG
节点.
如,每当not
反转xor
的结果位,且xor
的两个操作数
都是寄存器
时,匹配XNORrr
指令.
为了查看XNORrr
指令记录字段,可用如下命令序列
:
$ cd <llvm_sources>/lib/Target/Sparc
$ llvm-tblgen -print-records Sparc.td -I ../../../include | grep XNORrr -A 10
多个TableGen
后端,用指令记录
信息干活,从相同指令记录
生成不同
的.inc
文件.这跟创建中心仓库
,用它给后端
各个部分生成代码
的TablenGen
的目标是一致的.
下面的每个文件
是由不同的TableGen
后端生成的:
1,<Target>GenDAGISel.inc
:用指令记录
中的模式
字段信息来发射选择SelectionDAG
数据结构的指令的代码
.在<Target>ISelDAGtoDAG.cpp
文件中包含它.
2,<Target>GenInstrInfo.inc
:包含在其它描述指令的表
中,列举目标
所有指令的枚举
.
在<Target>InstrInfo.cpp,<Target>InstrInfo.h,<Target>MCTargetDesc.cpp
,和<Target>MCTargetDesc.h
中包含.
然而,在包含TableGen
生成文件,改变如何在每个环境
中解析和使用
前,每个文件会定义一组特定宏
.
3,<Target>GenAsmWriter.inc
:包含映射用来打印
每个指令汇编
的串的代码.在<Target>AsmPrinter.cpp
文件中包含.
4,<Target>GenCodeEmitter.inc
:包含为每条指令
映射要输出的二进制代码
,从而生成机器代码
以填写目标文件
的函数.在<Target>CodeEmitter.cpp
中包含.
5,<Target>GenDisassemblerTables.inc
:实现可解码
字节序列并识别代表的目标指令
的表和算法
.用来实现反汇编工具
,在<Target>Disassembler.cpp
文件中包含它.
6,<Target>GenAsmMatcher.inc
:实现目标指令
的汇编器的解析器
.在<Target>AsmParser.cpp
文件中包含
了它两次,每次都有一组不同预处理宏
,从而改变解析
方式.
理解选指
选指是转换LLVMIR
为代表目标指令
的SelectionDAG
节点(SDNode)
的过程.第一步
是根据LLVMIR
指令创建DAG
,创建带IR
操作节点
的SelectionDAG
对象.
接着,降级这些节点
后,组合DAG
,及标准化
等过程,使它更易匹配目标指令
.然后,选指
用节点匹配模式
方法来从DAG
到DAG
转换,转换SelectionDAG
节点为代表目标指令
的节点
.
注意,选指
是其中最耗时
的后端趟
.一项编译SPECCPU2006
基准测试的函数的研究表明,在LLVM3.0
中,以-O2
运行llc
工具,平均来说,选指趟
几乎花去一半
的时间.
SelectionDAG
类
SelectionDAG
类,用DAG
表示每个基本块的计算
,每个SDNode
对应一个指令或操作数
.
DAG
的边通过use-def
关系确保操作
之间的顺序
.如果B
节点(如,add
)连接到A
节点(如,Constant<-10>
),即A节点
定义了一个值(32
位的-10
整数),而B节点
使用它(用作加法
).
因此,必须在B
前执行A
操作.黑色箭头
表示指示数据流依赖
的普通连线
,如add
示例.蓝色虚线
箭头表示确保两条指令
顺序的非数据流链
,否则它们是不相关的,如,load
和store
指令,如果访问相同内存位置
,则必须按原始程序顺序
.
前面图中,CopyToReg
操作,因为蓝色虚线
箭头,必须在X86ISD::RET_FLAG
之前.红色
连线保证
相邻节点必须结合
在一起,即必须紧挨
着执行,之间不能有其它指令
.
如,因为红色连线
,表明相同节点CopyToReg
和X86ISD::RET_FLAG
必须紧挨着调度.
根据它和它的用户
的关系,每个节点
可提供不同的值类型
.值
不必是具体
的,也可能是个(token)
抽象令牌.它可能有任意如下类型
:
1,节点
所提供的值可以是表示整数,浮点数,向量,或指针
等具体值类型
.从它的操作数
计算新值的数据处理
节点,就是一例.
类型
可以是i32,i64,f32,v2f32
(有两个f32
元素的向量),和iPTR
等.在LLVM
示意图中,当另一个
节点使用
该值时,由普通黑色连线
描绘生产者-消费者
关系.
2,Other
类型是表示链值
(ch
)的抽象令牌
.在LLVM
示意图中,另一
节点使用
一个Other
类型的值时,按蓝色虚线
打印连接
两者的连线
.
3,Glue
类型表示组合.在LLVM
示意图中,另一节点
使用Glue
类型值时,按红色
来画连接
两者的连线.
SelectionDAG
对象有个表示基本块入口
的EntryToken
的特殊令牌
,它通过消费首节点
,提供Other
类型的值,让链结
的节点以它为起点
.
SelectionDAG
对象也可引用
正好是按Other
类型的值链编码
关系的最后一条指令
的后续节点的图的根节点
.
在该阶段,可同时有目标无关
和目标相关
节点,这是执行预备步骤
的结果,如负责
准备选指DAG
的降级和合法化
.
然而,选指
结束时,所有目标指令
匹配的节点
都会是目标相关
的.前面,有如下目标无关
的节点:CopyToReg,CopyFromReg,Register(%vreg0),add,
和Constant
.
此外,如下为已预处理
且是目标相关
的节点(尽管选指后仍可改变
):TargetConstant,Register(%EAX)
,和X86ISD::REG_Flag
.
可观察到下面的语义:
1,Register
:可能引用虚或(目标相关的)物理
的寄存器.
2,CopyFromReg
:复制当前基本块域
外定义的寄存器,示例中,它复制
函数参数.
3,CopyToReg
:不提供其它节点
使用的具体值
的,复制值到指定寄存器
.
然而,该节点,要用不生成
具体值的其它节点
,产生一个要链接(Other
类型)的链值
.
如,为了使用写到EAX
的值,X86ISD::RET_FLAG
节点使用由Register(%EAX)
提供的i32
结果,且还消费由CopyToReg
产生的链,这样确保用CopyToReg
更新%EAX
,因为该链
会确保在X86ISD::RET_FLAG
前调度CopyToReg
.
SelectionDAG
细节,见llvm/include/llvm/CodeGen/SelectionDAG.h
头文件.对节点
结果类型,见llvm/include/llvm/CodeGen/ValueTypes.h
头文件.
llvm/include/llvm/CodeGen/ISDOpcodes.h
头文件定义
了目标无关
节点,而lib/Target/<Target>/<Target>ISelLowering.h
头文件定义了目标相关
节点.
降级
如果这是选指
输入,为何在SelectionDAG
中,已有一些目标相关
节点?
为此,首先在下图中给出选指前
步骤全局图
,在左上角从LLVMIR
步骤开始:
IR,1=>映射指令到SD节点,2=>SD节点=>组合1->标准化类型1
组合->合法化向量->合法化2=>组合=>合法化=>组合2=>选指
中间步骤中一堆降级.
首先,SelectionDAGBuilder
实例(见SelectionDAGISel.cpp
)访问每个函数
,为每个基本块
创建一个SelectionDAG
对象.
在此时,一些特殊的IR
指令如call
和ret
已要求目标相关
语句,如,如何传递调用参数
及如何从一个要转换为SelectionDAG
节点的函数
返回.
为此,第一次使用TargetLowering
类中的算法.该类
是每个目标
都必须实现的抽象接口
,且有大量所有后端
可使用的共享功能
.
为了实现该抽象接口
,每个目标
声明一个叫<Target>TargetLowering
的TargetLowering
的子类.每个目标还重载
实现具体的目标无关
高级节点应如何降级
到更接近的机器级
的方法.
如期,仅有小部分节点
必须这样降级
,而大部分在选指时就匹配和替换了其它节点
.如,在sum.bc
的SelectionDAG
中,用X86TargetLowering::LowerReturn()
方法(见lib/Target/X86/X86ISelLowering.cpp
)降级IR
的ret
指令.
同时,生成了复制函数结果
到EAX
的X86ISD::RET_FLAG
节点,按目标相关
方式处理函数
返回.
DAG
结合与合法化
从SelectionDAGBuilder
输出的SelectionDAG
并不能直接选指
,必须经历附加
转换.在选指
前执行的趟
序列如下:
1,可获利时,DAG
结合趟
通过匹配
一系列节点
,并用简化
结构替换它们,来优化
次优化的SelectionDAG
结构.
如,可把(add(RegisterX),(constant0))
子图合并为(RegisterX)
.
类似,目标相关
组合方法可识别
节点模式,并根据是否可提高
此目标选择指令的质量
,决定是否合并
和折叠它们.
可在lib/CodeGen/SelectionDAG/DAGCombiner.cpp
文件中,找到LLVM
通用的DAG
结合的实现,在lib/Target/<Target_Name>/<Target>ISelLowering.cpp
文件中找到目标相关
的组合实现
.
setTargetDAGCombine()
方法,标记目标
想要结合的节点
.如,MIPS
后端试结合加法:见lib/Target/Mips/MipsISelLowering.cpp
中的setTargetDAGCombine(ISD::ADD)
和performADDCombine()
.
注意,在每次合法化
后运行DAG
结合,来最小化SelectionDAG
冗余.而且,DAG
结合知道在趟
链的何处
运行,(如在合法化类型
或向量
后),可运用
这些信息以更精确.
2,类型合法化趟
,确保选指
只需要处理目标天然支持
的类型的合法类型
.如,在只支持i32
类型的目标
上,i64
操作数的加法
是非法
的.
此时,合法化类型
,展开整数
把i64
操作数分为两个i32
操作数,同时生成合适节点
以操作它们.目标定义
了每种类型
所关联的寄存器
,并显式
声明了支持
类型.
这样,必须检测
并相应处理非法类型
:可提升,展开,或软化
标量类型,而可分解,标量化,或放宽
向量类型,见llvm/include/llvm/Target/TargetLowering. h
对每种情况的解释.
此外,目标还可设置
自定义方法来合法化
类型.两次运行
合法化类型
,第一次组合DAG
后,及在合法化向量
后.
3,有时,后端
直接支持向量类型
,即有个寄存器类
,但是没有处理给定向量类型
的具体操作
.如,x86
的SSE2
支持v4i32
向量类型.
然而,并没有x86
指令支持v4i32
类型的ISD::OR
操作,而只有v2i64
的.因此,向量合法化
会为指令
用合法类型
来处理,提升或扩展
.
目标
还可自定义
合法化.对前面提到的ISD::OR
,会提升操作
而使用v2i64
类型.看一看下面的lib/Target/X86/X86ISelLowering.cpp
的代码片:
setOperationAction(ISD::OR, v4i32, Promote);
AddPromotedToType (ISD::OR, v4i32, MVT::v2i64);
DAG
合法化类似向量
合法化,但是它用不支持
类型(标量或向量
)处理剩余
的操作.它支持相同动作
:提升,扩展,和自定义
节点.
如,x86
不支持以下三种
:i8
类型的有符号整数
到浮点
的转化操作(ISD::SINT_TO_FP)
,再请求合法化
提升操作;
32
位操作数的有符号除法
,(ISD::SDIV)
,发起一个产生库调用
处理该除法的扩展请求
;f32
操作数的浮点数绝对值
,用自定义
处理器生成有相同效果
的等价代码
.
x86
以如下发起这些动作(见lib/Target/X86/X86ISelLowering.cpp
):
setOperationAction(ISD::SINT_TO_FP, MVT::i8, Promote);
setOperationAction(ISD::SDIV, MVT::i32, Expand);
setOperationAction(ISD::FABS, MVT::f32, Custom);
DAG
到DAG
的选指
DAG
到DAG
选指目的,是用匹配模式
转换目标无关
节点为目标相关
节点.选指
算法是本地的,每次在SelectionDAG
(基本块)的实例
上工作.
如,后面给出了选指
后最终的SelectionDAG
结构.直到分配
寄存器,CopyToReg,CopyFromReg
,和Register
节点不变.
选指过程
甚至可能增加节点
.选指之后,按X86
指令ADD32ri8
转换ISD::ADD
节点,而X86ISD::RET_FLAG
变为RET
.
注意,在同一个DAG
中并存有三个指令表示类型
:通用的LLVMISD
节点比如ISD::ADD
,目标相关的<Target>ISD
节点比如X86ISD::REG_FLAG
,目标物理指令
比如X86::ADD32ri8
.
相关文章:
2312llvm,04后端上
后端 后端由一套分析和转换趟组成,任务是生成代码,即把LLVM中间(IR)转换为目标代码(或汇编). LLVM支持广泛目标:ARM,AArch64,Hexagon,MSP430,MIPS,NvidiaPTX,PowerPC,R600,SPARC,SystemZ,X86,和XCore. 所有这些后端共享一套,按通用API方法抽象后端任务的目标无关生成代码的一部…...
springboot学习笔记(五)
MybatisPlus进阶 1.MybatisPlus一对多查询 2.分页查询 1.MybatisPlus一对多查询 场景:我有一个表,里面填写的是用户的个人信息(姓名,生日,密码,用户ID)。我还有一个表填写的订单信息&#x…...
文件上传——后端
文件上传流程: 创建阿里云OSS(对象存储服务)的bucket 登录阿里云,并完成实名认证,地址:https://www.aliyun.com/. 可以通过搜索,进入以下页面: 点击立即使用后: 点击…...
虾皮开通:如何在虾皮上开通跨境电商店铺
在当今的数字时代,跨境电商已经成为了全球贸易的一种重要形式。虾皮(Shopee)作为东南亚市场份额第一的跨境电商平台,为卖家提供了广阔的销售机会。如果您想在虾皮上开通店铺,以下是一些步骤和注意事项供您参考。 先给…...
C语言—每日选择题—Day60
明天更新解析 第一题 1. 下列for循环的循环体执行次数为() for(int i 10, j 1; i j 0; i, --j) A:0 B:1 C:无限 D:以上都不对 答案及解析 A for循环的判断条件是 i j 0;赋值语句做判断条件…...
【3D生成与重建】SSDNeRF:单阶段Diffusion NeRF的三维生成和重建
系列文章目录 题目:Single-Stage Diffusion NeRF: A Unified Approach to 3D Generation and Reconstruction 论文:https://arxiv.org/pdf/2304.06714.pdf 任务:无条件3D生成(如从噪音中,生成不同的车等)、…...
计算机网络:应用层
0 本节主要内容 问题描述 解决思路 1 问题描述 不同的网络服务: DNS:用来把人们使用的机器名字(域名)转换为 IP 地址;DHCP:允许一台计算机加入网络和获取 IP 地址,而不用手工配置࿱…...
现代雷达车载应用——第3章 MIMO雷达技术 3.2节 汽车MIMO雷达波形正交策略
经典著作,值得一读,英文原版下载链接【免费】ModernRadarforAutomotiveApplications资源-CSDN文库。 3.2 汽车MIMO雷达波形正交策略 基于MIMO雷达技术的汽车雷达虚拟阵列合成依赖于不同天线发射信号的可分离性。当不同天线的发射信号正交时&#x…...
Unresolved plugin: ‘org.apache.maven.plugins‘解决报错
新建springboot项目报Unresolved plugin: ‘org.apache.maven.plugins:maven-surefire-plugin:3.1.2’ 缺什么插件 引入什么插件的依赖就行 <dependency><groupId>org.apache.maven.plugins</groupId><artifactId>maven-install-plugin</artifact…...
阿里云林立翔:基于阿里云 GPU 的 AIGC 小规模训练优化方案
云布道师 本篇文章围绕生成式 AI 技术栈、生成式 AI 微调训练和性能分析、ECS GPU 实例为生成式 AI 提供算力保障、应用场景案例等相关话题展开。 生成式 AI 技术栈介绍 1、生成式 AI 爆发的历程 在 2022 年的下半年,业界迎来了生成式 AI 的全面爆发,…...
从0开始学Git指令
从0开始学Git指令 因为网上的git文章优劣难评,大部分没有实操展示,所以打算自己从头整理一份完整的git实战教程,希望对大家能够起到帮助! 初始化一个Git仓库,使用git init命令。 添加文件到Git仓库,分两步…...
B039-SpringMVC基础
目录 SpringMVC简介复习servletSpringMVC入门导包配置前端控制器编写处理器实现Contoller接口普通类加注解(常用) 路径问题获取参数的方式过滤器简介自定义过滤器配置框架提供的过滤器 springMVC向页面传值的三种方式视图解析器springMVC的转发和重定向 SpringMVC简介 1.Sprin…...
Tomcat报404问题解决方案大全(包括tomcat可以正常运行但是报404)
文章目录 Tomcat报404问题解决方案大全(包括tomcat可以正常运行但是报404)1、正确的运行页面2、报错404问题分类解决2.1、Tomcat未配置环境变量2.2、IIs访问权限问题2.3、端口占用问题2.4、文件缺少问题解决办法: Tomcat报404问题解决方案大全(包括tomcat可以正常运…...
debian10安装配置vim+gtags
sudo apt install global gtags --version gtags //生成gtag gtags-cscope //查看gtags gtags与leaderf配合使用 参考: 【VIM】【LeaderF】【Gtags】打造全定制化的IDE开发环境! - 知乎...
vue跳转方式
Vue的页面跳转有两种方式,第一种是标签内跳转,第二种是编程式路由导航 1. <router-link to/Demo><button>点击跳转1</button> </router-link>2.router.push("/Demo");一、标签内通过 router-link跳转 通常用于点击 …...
基于ssm+jsp学生综合测评管理系统源码和论文
网络的广泛应用给生活带来了十分的便利。所以把学生综合测评管理与现在网络相结合,利用java技术建设学生综合测评管理系统,实现学生综合测评的信息化。则对于进一步提高学生综合测评管理发展,丰富学生综合测评管理经验能起到不少的促进作用。…...
网络基础篇【网线的制作,OSI七层模型,集线器和交换机的介绍,路由器的介绍与设置】
目录 一、网线制作 1.1 工具介绍 1.1.1网线 1.1.2 网线钳 1.1.3 水晶头 1.1.4 网线测试仪 二、OSI七层模型 2.1 简介 2.2 OSI模型层次介绍 2.2.1 结构图 2.2.2 数据传输过程 2.3 相关网站 二、集线器 2.1 介绍 2.2 适用场景 三、交换机 3.1 介绍 3.2 适用场景…...
CSRF检测工具(XSRF检测工具)使用说明
目录 检查类型 测试单个端点 抓取网站 添加Cookie 自定义用户代理...
docker 部署kafka
随笔记录 目录 1. 安装zookeeper 2. 安装Kafka 2.1 拉取kafka image 2.2 查询本地docker images 2.3 查看本地 容器(docker container) 2.3.1 查看本地已启动的 docker container 2.3.2 查看所有容器的列表,包括已停止的容器。 2.3.…...
Android 架构 - 组件化
一、概念 组件化是对单个功能进行开发,使得功能可以复用。将多个功能组合起来就是一个业务模块,因此去除了模块间的耦合,使得按业务划分的模块成了可单独运行的业务组件。(一定程度上的独立,还是依附于整个项目中&…...
数字图像处理-空间域图像增强-爆肝18小时用通俗语言进行超详细的总结
目录 灰度变换 直方图(Histogram) 直方图均衡 直方图匹配(规定化) 空间滤波 低通滤波器 高通滤波器 本文章讲解数字图像处理空间域图像增强,大部分内容来源于课堂笔记中 灰度变换 图像增强&…...
【Java】【SQL】DATE_FORMAT函数详解
在实际应用开发中,使用sql语句也属于开发者的一部分,这次来说说DATE_FORMAT函数。 引言:实际上在使用Java开发过程中,有很多业务场景下,都有时间类型的参数参与。前后端进行交互的时候,针对时间类型的格式…...
Pooling方法总结(语音识别)
Pooling layer将变长的frame-level features转换为一个定长的向量。 1. Statistics Pooling 链接:http://danielpovey.com/files/2017_interspeech_embeddings.pdf The default pooling method for x-vector is statistics pooling. The statistics pooling laye…...
Java可变参数(学习推荐版,通俗易懂)
定义 可变参数本质还是一个数组 示例代码 注意事项 1.形参列表中,可变参数只能有一个 2.可变参数必须放在形参列表的最后面 注意是最后面。 name也可以为int类型...
异步编程Promise
文章目录 前言一、关于 Promise 的理解与使用1.相关知识补充区别实例对象和函数对象同步回调异步回调Js中的错误(error)和错误处理 2.promise是什么 二、Promise 原理三、Promise 封装 Ajax四、async 与 await总结 前言 在项目中,promise的使…...
Centos上的默认文本编辑器vi的操作方法积累
打开一个文本后,常见的操作方法积累如下: 001-进入或退出插入模式的方法 按下 i 进入插入模式。 按下 Esc 退出插入模式。 002-进入命令模式的方法: 按下 Esc 退出插入模式,然后输入冒号:进入命令模式。 003-退出vi编辑器的方…...
海康rtsp拉流,rtmp推流,nginx部署转flv集成
海康rtsp拉流,rtmp推流,nginx部署转flv集成 项目实际使用并测试经正式使用无问题,有问题欢迎评论留言 核心后台java代码: try {// FFmpeg命令String command "ffmpeg -re -i my_video.mp4 -c copy -f flv rtmp://localho…...
【Python百宝箱】时序之美:Python 时间序列探秘与创新
时光漫游:Python 时间序列分析全指南 前言 在数字化时代,时间序列数据扮演着关键的角色,从金融到气象再到生产制造。本文将引导你穿越Python丰富的时间序列分析工具,探索从基础统计到机器学习和深度学习的各个层面。无论你是初学…...
flutter开发实战-第一帧布局完成回调实现
flutter开发实战-第一帧布局完成回调实现 在开发中,我们有时候需要在第一帧布局完成后调用一些相关的方法。这里记录一下是实现过程。 Flutter中有多种不同的Binding,每种Binding都负责不同的功能。下面是Flutter中常见的Binding: 这里简单…...
Windows11编译VTM源码生成Visual Studio 工程
VTM介绍 VTM作为H266/VVC标准的官方参考软件,一直用作H266/VVC标准的研究和迭代。关于H2666/VVC标准的介绍、代码、提案、文档等,可以参考H266/VVC编码标准介绍。 官方代码地址: https://vcgit.hhi.fraunhofer.de/jvet/VVCSoftware_VTM&…...
平面设计有哪些网站/南宁网站seo外包
http://www.cnblogs.com/bakari/p/3562244.html perties类的操作 知识学而不用,就等于没用,到真正用到的时候还得重新再学。最近在看几款开源模拟器的源码,里面涉及到了很多关于Properties类的引用,由于Java已经好久没用了&#x…...
有没有做淘宝客网站的/网络推广员是干嘛的
Java设计模式——工厂模式Java设计模式——工厂模式1 概述2 简单工厂模式2.1 结构2.2 实现2.4 优缺点3 工厂方法模式3.1 概念3.2 结构3.3 实现3.4 优缺点4 抽象工厂模式4.1 概念4.2 结构4.2 实现4.3 优缺点4.4 使用场景5 模式扩展6 JDK源码解析-Collection.iterator方法Java设计…...
好看的做地图分析图的网站/2022最新小学生新闻
我新注册的coder.cn域名怎么样? 呵呵。准备用来开发一些开发人员的东东。还可以开设一些二级域名的服务供大家使用,例如 http://blog.coder.cn http://domain.coder.cn http://bbs.coder.cn http://gbook.coder.cn http://dotnet.coder.cn http://java.c…...
cms网站开发/怎么样建立自己的网站
例如:var strq1207526854 str.substring(form,to):从字符串里截取下标为form到下标为to的字符串(不包括to对应的字符)alert(str.substring(2,6)) // 2075当form>to时,substring会把较小的参数作为第一参数 al…...
梅陇做网站/百度热搜seo
这次写一下精灵创建的几种类型: 一、通过文件创建: 在原有的基础上加入例如以下代码: //一、通过文件创建精灵 CCSprite *bg CCSprite::create("map.png"); CCSize winSize CCDirector::sharedDirector()->getWinSize(); //得到屏幕的尺寸…...
店铺出租转让信息网站建设多少钱/百度sem竞价推广pdf
前言 介于上一篇 「今日头条」前端面试题和思路解析 中提到的 async/await,让我想起了之前写过的一篇文章,在此做个分享。它细说了什么是async函数,以及其相较于 Promise 的优势。 温故而知新,正文开始。 async 函数是什么&#x…...