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 构建文件如何编写。


如何注册 MyRISCV 目标后端

LLVM 中,不同目标后端的相关文件应该放于目录llvm/lib/Target的不同子目录中。并且,这些文件名称最好遵循同一套命令规则。比如:XXX.tdXXXTargetMachine.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");
}

注:

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_BUILDLLVM_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工具实际注册的目标后端有myriscv32riscv32riscv64x86x86-64


References


下一篇:LLVM 之后端篇(3):如何添加 MyRISCV 目标后端的第一条指令

上一篇:LLVM 之后端篇(1):零基础快速入门 TableGen

首页