想好好熟悉一下llvm开发一个新后端都要干什么,于是参考了老师的系列文章:
LLVM 后端实践笔记
代码在这里(还没来得及准备,先用网盘暂存一下):
链接: https://pan.baidu.com/s/1V_tZkt9uvxo5bnUufhMQ_Q?pwd=ggu5 提取码: ggu5
之前的章节只介绍了汇编代码生成的内容,这一章,我们将介绍对 ELF 目标格式文件的支持以及如何使用 objdump 工具来验证生成的目标文件。这一章的功能上的较复杂的代码其实不是很多,主要是框架上的东西。(因此这一章节参考原作者的内容较多)
当 llc 指定 -filetype=obj 时,编译器会生成目标文件(而不是汇编文件),此时,AsmPrinter::OutStreamer 所引用的是 MCObjectStreamer(汇编时引用的是 MCAsmStreamer)。LLVM 官方认为这个结构是后端代码生成阶段非常好的一个设计。
关键的一个接口是Cpu0AsmPrinter::EmitInstruction(),这个接口调用 MCObjectStreamer::EmitInstruction() ,进而根据选择生成的目标文件格式(ELF,COFF等)调用对应的编码发射函数,如 ELF 使用 MCELFStreamer()::EmitInstToData()。此时会进入到 Cpu0MCCodeEmitter.cpp 文件的实现中,调用 Cpu0MCCodeEmitter::encodeInstruction(),配合 TableGen 生成的 Cpu0MCCodeEmitter::getBinaryCodeForInstr()等接口完成最后的发射。
获取待发射指令编码的调用过程为:
Cpu0MCCodeEmitter::encodeInstruction()中,调用TableGen生成的 Cpu0GenMCCodeEmitter.inc 中的 getBinaryCodeForInstr(),传入 MI.Opcode; getBinaryCodeForInstr() 将 MI.Operand 传入 Cpu0MCCodeEmitter::getMachineOpValue() 来获取操作数的编码,这还需要再配合 Cpu0GenRegisterInfo.inc 和 Cpu0GenInstrInfo.inc 中的编码信息;getBinaryCodeForInstr() 将操作数的编码和指令操作码统一返回给 encodeInstruction();
比如一个加法操作,%0 = add %1, %2 生成为 adds $v0, $at, $v1,除了 adds 指令的编码需要在 Cpu0GenInstrInfo.inc 中查看外,还需要通过getEncodingValue(Reg) 到 Cpu0GenRegisterInfo.inc 中查看寄存器的编码,寄存器的编码和编码位置都在 Cpu0RegisterInfo.td 文件中描述了。
目录
1. 新增的文件
1.1 MCTargetDesc/Cpu0AsmBackend.cpp/.h
1.2 MCTargetDesc/Cpu0ELFObjectWriter.cpp
1.3 MCTargetDesc/Cpu0ELFStreamer.cpp/.h
1.4 MCTargetDesc/Cpu0FixupKinds.h
1.5 MCTargetDesc/Cpu0MCCodeEmitter.cpp/.h
1.6 MCTargetDesc/Cpu0MCExpr.cpp/.h
1.7 MCTargetDesc/Cpu0TargetStreamer.cpp/.h
2. 修改的文件
2.1 MCTargetDesc/Cpu0MCTargetDesc.cpp/.h
1. 新增的文件
1.1 MCTargetDesc/Cpu0AsmBackend.cpp/.h
比较重要的一个文件,实现了 Cpu0AsmBackend 类,继承自 MCAsmBackend 类。这个类作为汇编器后端实现类,目前对 Fixup 信息的操作提供了接口,比如 applyFixup() 用来使能 Fixup 状态,getFixupKindInfo() 用来获取 Fixup 类型信息,getNumFixupKinds() 用来获取 Fixup 类型的数量,mayNeedRelaxation() 返回需要 relaxation 的指令的状态(目前是空),fixupNeedsRelaxation() 返回给定 fixup 下的指令是否需要 relaxation 的状态(目前是空)。
已经定义了一些常用的 fixup 类型,比如 32 位类型:fixup_Cpu0_32, fixup_Cpu0_HI16, fixup_Cpu0_LO16,还有 GOT 的一些 fixup 类型。这些函数都是对基类函数的覆写,有关于重定向的功能都将在之后的章节讲解,所以目前会留空。我们还在其中实现了两个工厂函数,createCpu0AsmBackendEL32() 和 createCpu0AsmBackendEB32(),用来返回一个 AsmBackend 的实例。
1.2 MCTargetDesc/Cpu0ELFObjectWriter.cpp
定义了一个叫 Cpu0ELFObjectWriter 的类,继承自 MCELFObjectTargetWriter 类。这个类将用来完成最终的 ELF 文件格式的写入任务。其中提供了 getRelocType() 方法用来获取重定位类型,needsRelocateWithSymbol() 判断某种重定位类型是否是符号重定位,默认大多数都是符号重定位。
1.3 MCTargetDesc/Cpu0ELFStreamer.cpp/.h
定义了一个叫 Cpu0ELFStreamer 的类,继承自 MCELFStreamer 类。另外定义了这个类的工厂函数 createCpu0ELFStreamer(),用来返回其对象。ELFStreamer 对象会注册到后端模块中。TargetStreamer 和 ELFStreamer 在生成 ELF 文件中同时起作用,ELFStreamer 是我们自定义的一个类,在其中可以做一些改动来调整输出内容。目前这些文件中都还是比较空的状态,我们先搭建整个框架。
1.4 MCTargetDesc/Cpu0FixupKinds.h
这个头文件中定义了 llvm::Cpu0::Fixups 的枚举值,这里的定义顺序必须与Cpu0AsmBackend.cpp 中的 MCFixupKindInfo 保持一致。
1.5 MCTargetDesc/Cpu0MCCodeEmitter.cpp/.h
另一个比较重要的类,用来为Streamer类提供直接发射编码的实现接口。定义了比如 encodeInstruction() 等重要接口。我们在 encodeInstruction() 中检查一些未完成编码设计的指令,这还需要考虑一些特殊情况,比如要排除编码为 0 的情况,排除伪指令(伪代码不应该出现在这这个阶段了)。getBinaryCodeForInstr() 函数是 TableGen 自动生成的,可以通过传入给定的 MI 指令,获取该指令的编码。
1.6 MCTargetDesc/Cpu0MCExpr.cpp/.h
针对操作数是表达式的情况,我们需要额外做处理。其中定义了Cpu0MCExpr类,继承自 MCTargetExpr类。其中声明了表达式类型 Cpu0ExprKind,还提供了 create(), getKind() 等接口。
1.7 MCTargetDesc/Cpu0TargetStreamer.cpp/.h
定义了一个叫 Cpu0TargetStreamer 的类,继承自 MCTargetStreamer 类。定义了一个叫 Cpu0TargetAsmStreamer 的类,继承自 Cpu0TargetStreamer 类,这个类用来完成汇编器 Streamer 的功能。AsmStreamer 对象会注册到后端模块中。
2. 修改的文件
2.1 MCTargetDesc/Cpu0MCTargetDesc.cpp/.h
我们知道这个文件中会完成注册一些后端模块的功能。首先定义了两个函数,createMCStreamer() 调用 createCpu0ELFStreamer() 用来建立 ELFStreamer 对象,createCpu0AsmTargetStreamer() 直接建立 Cpu0TargetAsmStreamer 对象。
然后就是调用TargetRegistry::RegisterELFStreamer()和 TargetRegistry::RegisterAsmTargetStreamer()来注册这两个对象模块。另外,还调用 TargetRegistry::RegisterMCCodeEmitter() 来注册大小端的 MCCodeEmitter 对象,以及调用 TargetRegistry::RegisterMCAsmBackend() 来注册大小端的 MCAsmBackend 对象。