LLVM 之后端篇(2):如何扩展 llc 的目标后端
Author: stormQ
Created: Sunday, 02. January 2022 01:44PM
Last Modified: Saturday, 13. August 2022 01:49PM
本文基于release/15.x
版本的 LLVM 源码,以自定义的目标后端MyRISCV
为例,介绍了扩展llc
目标后端至少需要添加哪些代码以及 CMake 构建文件如何编写。
LLVM 中,不同目标后端的相关文件应该放于目录llvm/lib/Target
的不同子目录中。并且,这些文件名称最好遵循同一套命令规则。比如:XXX.td
、XXXTargetMachine.cpp
等,其中XXX
表示目标后端的名称。
step 1: 添加 MyRISCVTargetMachine.cpp
在目录llvm/lib/Target/MyRISCV
中新建源文件MyRISCVTargetMachine.cpp
。其实现如下:
#include "llvm/Support/Compiler.h"
extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeMyRISCVTarget() {}
注: llc
的目标后端都必须实现各自的void LLVMInitializeXXXTarget()
函数。否则,编译目标后端MyRISCV
时会报错undefined reference to 'LLVMInitializeMyRISCVTarget'
。
step 2: 添加 TargetInfo 目录
在目录llvm/lib/Target/MyRISCV
中新建子目录TargetInfo
。
1) 添加 MyRISCVTargetInfo.h
在目录llvm/lib/Target/MyRISCV/TargetInfo
中新建头文件MyRISCVTargetInfo.h
。其实现如下:
#ifndef LLVM_LIB_TARGET_MYRISCV_TARGETINFO_MYRISCVTARGETINFO_H
#define LLVM_LIB_TARGET_MYRISCV_TARGETINFO_MYRISCVTARGETINFO_H
namespace llvm {
class Target;
Target &getMyRISCV32Target();
} // end namespace llvm
#endif // LLVM_LIB_TARGET_MYRISCV_TARGETINFO_MYRISCVTARGETINFO_H
2) 添加 MyRISCVTargetInfo.cpp
在目录llvm/lib/Target/MyRISCV/TargetInfo
中新建源文件MyRISCVTargetInfo.cpp
。其实现如下:
#include "TargetInfo/MyRISCVTargetInfo.h"
#include "llvm/MC/TargetRegistry.h"
using namespace llvm;
namespace llvm {
Target &getMyRISCV32Target() {
static Target MyRISCV32Target;
return MyRISCV32Target;
}
} // end namespace llvm
extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeMyRISCVTargetInfo() {
RegisterTarget<Triple::myriscv32> X(getMyRISCV32Target(), "myriscv32",
"32-bit RISC-V", "RISCV");
}
注:
llc
的目标后端都必须实现各自的void LLVMInitializeXXXTargetInfo()
函数。否则,编译目标后端MyRISCV
时会报错undefined reference to 'LLVMInitializeMyRISCVTargetInfo'
。
枚举值Triple::myriscv32
定义在llvm/include/llvm/ADT/Triple.h
文件中(见下文),用于表示 32-bit 的目标后端MyRISCV
。
3) 添加 CMakeLists.txt
在目录llvm/lib/Target/MyRISCV/TargetInfo
中新建 CMake 构建文件CMakeLists.txt
。其内容如下:
add_llvm_component_library(LLVMMyRISCVInfo
MyRISCVTargetInfo.cpp
LINK_COMPONENTS
MC
ADD_TO_COMPONENT
MyRISCV
)
step 3: 添加 MCTargetDesc 目录
在目录llvm/lib/Target/MyRISCV
中新建子目录MCTargetDesc
。
1) 添加 MyRISCVMCTargetDesc.cpp
在目录llvm/lib/Target/MyRISCV/MCTargetDesc
中新建源文件MyRISCVMCTargetDesc.cpp
。其实现如下:
#include "llvm/Support/Compiler.h"
extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeMyRISCVTargetMC() {}
注: llc
的目标后端都必须实现各自的void LLVMInitializeXXXTargetMC()
函数。否则,编译目标后端MyRISCV
时会报错undefined reference to 'LLVMInitializeMyRISCVTargetMC'
。
2) 添加 CMakeLists.txt
在目录llvm/lib/Target/MyRISCV/MCTargetDesc
中新建 CMake 构建文件CMakeLists.txt
。其内容如下:
add_llvm_component_library(LLVMMyRISCVDesc
MyRISCVMCTargetDesc.cpp
ADD_TO_COMPONENT
MyRISCV
)
step 4: 添加 CMakeLists.txt
在目录llvm/lib/Target/MyRISCV
中新建 CMake 构建文件CMakeLists.txt
。其内容如下:
add_llvm_component_group(MyRISCV)
set(sources
MyRISCVTargetMachine.cpp
)
add_llvm_target(MyRISCVCodeGen ${sources}
ADD_TO_COMPONENT
MyRISCV
)
add_subdirectory(MCTargetDesc)
add_subdirectory(TargetInfo)
step 5: 修改 Triple.h
在头文件llvm/include/llvm/ADT/Triple.h
中添加如下内容:
myriscv32,
LastArchType = myriscv32
修改后的内容如下:
namespace llvm {
省略 ...
class Triple {
public:
enum ArchType {
UnknownArch,
省略 ...
myriscv32,
LastArchType = myriscv32
};
省略 ...
};
省略 ...
} // End llvm namespace
step 6: 修改 Triple.cpp(optional)
注: 本步骤是可选的,这里仅用于消除编译警告。
1) 修改 Triple::getArchTypeName() 函数
在函数Triple::getArchTypeName()
中添加如下内容:
case myriscv32: return "myriscv32";
修改后的内容为:
StringRef Triple::getArchTypeName(ArchType Kind) {
switch (Kind) {
省略 ...
case msp430: return "msp430";
case myriscv32: return "myriscv32";
省略 ...
}
省略 ...
}
2) 修改 Triple::getArchTypePrefix() 函数
在函数Triple::getArchTypePrefix()
中添加如下内容:
case myriscv32:
修改后的内容为:
StringRef Triple::getArchTypePrefix(ArchType Kind) {
switch (Kind) {
省略 ...
case myriscv32:
case riscv32:
case riscv64: return "riscv";
省略 ...
}
}
3) 修改 Triple::getArchTypeForLLVMName() 函数
在函数Triple::getArchTypeForLLVMName()
中添加如下内容:
.Case("myriscv32", myriscv32)
修改后的内容为:
Triple::ArchType Triple::getArchTypeForLLVMName(StringRef Name) {
Triple::ArchType BPFArch(parseBPFArch(Name));
return StringSwitch<Triple::ArchType>(Name)
省略 ...
.Case("msp430", msp430)
.Case("myriscv32", myriscv32)
省略 ...
}
4) 修改 parseArch() 函数
在函数parseArch()
中添加如下内容:
.Case("myriscv32", Triple::myriscv32)
修改后的内容为:
static Triple::ArchType parseArch(StringRef ArchName) {
auto AT = StringSwitch<Triple::ArchType>(ArchName)
省略 ...
.Cases("mips64el", "mipsn32el", "mipsisa64r6el", "mips64r6el",
"mipsn32r6el", Triple::mips64el)
.Case("myriscv32", Triple::myriscv32)
省略 ...
}
5) 修改 getArchPointerBitWidth() 函数
在函数getArchPointerBitWidth()
中添加如下内容:
case llvm::Triple::myriscv32:
修改后的内容为:
static unsigned getArchPointerBitWidth(llvm::Triple::ArchType Arch) {
switch (Arch) {
省略 ...
case llvm::Triple::mipsel:
case llvm::Triple::myriscv32:
省略 ...
return 32;
省略 ...
}
省略 ...
}
6) 修改 Triple::get32BitArchVariant() 函数
在函数Triple::get32BitArchVariant()
中添加如下内容:
case Triple::myriscv32:
修改后的内容为:
Triple Triple::get32BitArchVariant() const {
Triple T(*this);
switch (getArch()) {
省略 ...
case Triple::mipsel:
case Triple::myriscv32:
省略 ...
// Already 32-bit.
break;
省略 ...
}
省略 ...
}
7) 修改 Triple::get64BitArchVariant() 函数
在函数Triple::get64BitArchVariant()
中添加如下内容:
case Triple::myriscv32:
修改后的内容为:
Triple Triple::get64BitArchVariant() const {
Triple T(*this);
switch (getArch()) {
省略 ...
case Triple::msp430:
case Triple::myriscv32:
省略 ...
T.setArch(UnknownArch);
break;
省略 ...
}
省略 ...
}
8) 修改 getDefaultFormat() 函数
在函数getDefaultFormat()
中添加如下内容:
case Triple::myriscv32:
修改后的内容为:
static Triple::ObjectFormatType getDefaultFormat(const Triple &T) {
switch (T.getArch()) {
省略 ...
case Triple::msp430:
case Triple::myriscv32:
省略 ...
return Triple::ELF;
省略 ...
}
省略 ...
}
llc
工具实际注册或可支持的目标后端是由编译命令选项LLVM_TARGETS_TO_BUILD
和LLVM_EXPERIMENTAL_TARGETS_TO_BUILD
决定的。在编译成功后,我们可以通过llc --version
命令查看实际注册的目标后端。
step 1: 构建
在构建目录build_ninja
(需要自己创建)中,执行如下命令:
$ cmake -DLLVM_TARGETS_TO_BUILD="X86;RISCV" -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=MyRISCV -DLLVM_ENABLE_PROJECTS="" -DBUILD_SHARED_LIBS=ON -G Ninja ../llvm/
$ ninja
构建目录的父目录llvm-project
的目录结构如下:
$ tree -L 1 llvm-project
llvm-project
├── build_ninja
├── clang
├── clang-tools-extra
├── compiler-rt
├── CONTRIBUTING.md
├── cross-project-tests
├── flang
├── libc
├── libclc
├── libcxx
├── libcxxabi
├── libunwind
├── lld
├── lldb
├── llvm
├── mlir
├── openmp
├── parallel-libs
├── polly
├── pstl
├── README.md
├── runtimes
├── SECURITY.md
└── utils
21 directories, 3 files
step 2: 查看实际注册的目标后端
在构建目录中,执行如下命令:
$ ./bin/llc --version
输出结果如下:
LLVM (http://llvm.org/):
LLVM version 14.0.0
DEBUG build with assertions.
Default target: x86_64-unknown-linux-gnu
Host CPU: tigerlake
Registered Targets:
myriscv32 - 32-bit RISC-V
riscv32 - 32-bit RISC-V
riscv64 - 64-bit RISC-V
x86 - 32-bit X86: Pentium-Pro and above
x86-64 - 64-bit X86: EM64T and AMD64
从上面的结果可以看出,llc
工具实际注册的目标后端有myriscv32
、riscv32
、riscv64
、x86
和x86-64
。
下一篇:LLVM 之后端篇(3):如何添加 MyRISCV 目标后端的第一条指令