LLVM目标无关代码生成器(上)(译)
内容提要
LLVM的代码生成器将LLVM IR翻译为特定目标机器的汇编指令,包括抽象目标机描述接口、目标机器代码的类、目标文件表示代码的类和算法、目标机描述的特定机器实现和目标无关的JIT组件。要增加后端,需要实现TargetMachine和DataLayout接口。代码生成器的高层设计包括指令选择、调度和生成序列化指令、基于SSA的机器代码优化、寄存器分配、插入prolog/epilog、晚期机器码优化和代码输出。机器描述使用TableGen语言描述。代码生成器还涉及寄存器、指令和堆栈布局的描述。机器码描述使用MachineInstr、MachineBasicBlock和MachineFunction类表示。MC层用于表示和处理原始机器码级别的代码,包括汇编器API、MCStreamer、MCContext、MCSymbol、MCSection和MCInst类。
延伸解读
后端开发的两个核心接口
文章指出,为LLVM增加新后端时,必须实现的接口只有TargetMachine和DataLayout。这意味着其他组件如寄存器分配、指令选择等并非强制,开发者可以根据目标机特性灵活选择。例如,以C语言为后端或针对FPGA时,可以绕过传统阶段,直接利用LLVM框架或设计全新流程。这种设计降低了新后端的入门门槛,但也要求开发者深入理解目标机描述和机器码表示类。
TableGen如何减少重复描述
LLVM使用TableGen语言描述目标机器的指令、寄存器等公共属性,并自动生成寄存器类等代码。随着LLVM发展,更多机器描述被移入.td文件,使后端移植更容易。开发者只需在TableGen中声明指令和寄存器,就能获得操作码枚举、寄存器类等基础设施。这减少了手工编写重复代码的错误,但要求开发者学习TableGen语法,并理解其与C++类的映射关系。
固定用途寄存器与代码质量
文章以X86除法为例,说明指令选择器会为固定用途寄存器(如EAX/EDX)生成拷贝指令。寄存器分配器随后会删除冗余拷贝,生成高效代码。但前提是物理寄存器生命周期要短,且在基本块边界处视为dead。若值需要跨基本块活跃,必须使用虚拟寄存器。这揭示了LLVM代码生成中物理寄存器与虚拟寄存器的分工:物理寄存器用于满足指令集约束,虚拟寄存器用于灵活分配。
MC层与机器码表示的分离
MC层处理原始机器码,不再有常量池、跳转表等高层概念,只关注label、section、指令等汇编级元素。MCStreamer作为汇编器API,可生成.s或.o文件,并被llvm-mc工具和代码生成器共用。MCInst是机器无关的指令表示,仅包含操作码和操作数。这种分层设计使LLVM能支持多种目标文件格式,但要求开发者区分MachineInstr(用于优化)和MCInst(用于输出)的不同抽象层次。
Q&A
LLVM代码生成器的主要功能是什么?
LLVM代码生成器是一个框架,提供可重用组件,将LLVM IR翻译为特定目标机器的汇编指令。
为LLVM添加新后端必须实现哪些接口?
必须实现TargetMachine和DataLayout两个接口。
LLVM代码生成器的高层设计包含哪些阶段?
包含指令选择、调度和生成序列化指令、基于SSA的机器代码优化、寄存器分配、插入prolog/epilog、晚期机器码优化和代码输出。
LLVM使用什么语言来描述目标机器?
LLVM使用TableGen语言来描述目标机器,以减少重复并提取公共部分。
MachineInstr类的作用是什么?
MachineInstr类用于表示机器指令,以抽象方式记录操作码和一系列操作数,支持SSA形式和非SSA形式。
MC层的主要作用是什么?
MC层用于表示和处理原始机器码级别的代码,处理label、机器指令、section等,用于生成.s或.o文件,以及实现独立的汇编器和反汇编器。