FPGA開発日記

FPGAというより、コンピュータアーキテクチャかもね! カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages

"Creating an LLVM Backend for the Cpu0 Architecture"をやってみる(7. Cpu0 Architecture and LLVM Structure 続き)

LLVMにCpu0アーキテクチャを追加するチュートリアル。ちょっと飛ばしすぎたので一度立ち戻って、LLVMアーキテクチャについてもう一度勉強し直す。

LLVMにバックエンドを追加するチュートリアルCpu0 architecture and LLVM structure をもう一度読み直してまとめていく。

最初の方は一生懸命自分の言葉でまとめていたけど、途中からほぼ直訳みたいになってきた。纏めた分を掲載しておく。今回は以下の項目。 第1回のものはこちらから。

第2回のものはこちらから。

第3回のものはこちらから。


Cpu0 architecture and LLVM structure

ターゲットの登録

ターゲットをTargetRegistryに登録する必要がある。登録が終わると、LLVMツールは実行時にそのターゲットを探索して使用できるようになる。TargetRegistryは直接使用することができるが、ほとんどのターゲットではヘルパーのtemplateを使って使いやすくしている。

全てのターゲットはグローバルなTagrgetオブジェクトを作成する必要があり、このオブジェクトは登録時に必要となる。したがって、ターゲット内のTargetInfoライブラリでは、ターゲットこのオブジェクトを定義する必要があり、RegisterTargetテンプレートを使ってターゲットを登録する必要がある。例えば、ファイルTargetInfo/Cpu0TargetInfo.cppTheCpu0Targetをビックエンディアンとして登録し、TheCpu0elTargetをリトルエンディアンとして登録する:

  • lbdex/chapters/Chapter2/Cpu0.h
#ifndef LLVM_LIB_TARGET_CPU0_CPU0_H
#define LLVM_LIB_TARGET_CPU0_CPU0_H

#include "Cpu0Config.h"
#include "MCTargetDesc/Cpu0MCTargetDesc.h"
#include "llvm/Target/TargetMachine.h"

namespace llvm {
  class Cpu0TargetMachine;
  class FunctionPass;

} // end namespace llvm;

#endif
  • lbdex/chapters/Chapter2/TargetInfo/Cpu0TargetInfo.cpp
#include "Cpu0.h"
#include "llvm/IR/Module.h"
#include "llvm/Support/TargetRegistry.h"
using namespace llvm;

Target llvm::TheCpu0Target, llvm::TheCpu0elTarget;

extern "C" void LLVMInitializeCpu0TargetInfo() {
  RegisterTarget<Triple::cpu0,
        /*HasJIT=*/true> X(TheCpu0Target, "cpu0", "Cpu0");

  RegisterTarget<Triple::cpu0el,
        /*HasJIT=*/true> Y(TheCpu0elTarget, "cpu0el", "Cpu0el");
}
  • lbdex/chapters/Chapter2/TargetInfo/CMakeLists.txt
add_llvm_library(LLVMCpu0Info
  Cpu0TargetInfo.cpp
  )
  • lbdex/chapters/Chapter2/TargetInfo/LLVMBuild.txt
[component_0]
type = Library
name = Cpu0Info
parent = Cpu0
required_libraries = Support
add_to_library_groups = Cpu0

Files Cpu0TargetMachine.cpp and MCTargetDesc/Cpu0MCTargetDesc.cpp just define the empty initialize function since we register nothing for this moment.

Cpu0TargetMachine.cppファイルとMCTargetDesc/Cpu0MCTargetDesc.cppはからの初期化関数を定義する。この時点では何も登録しない。

  • lbdex/chapters/Chapter2/Cpu0TargetMachine.cpp
#include "Cpu0TargetMachine.h"
#include "Cpu0.h"

#include "llvm/IR/LegacyPassManager.h"
#include "llvm/CodeGen/Passes.h"
#include "llvm/CodeGen/TargetPassConfig.h"
#include "llvm/Support/TargetRegistry.h"
using namespace llvm;

#define DEBUG_TYPE "cpu0"

extern "C" void LLVMInitializeCpu0Target() {
}
  • lbdex/chapters/Chapter2/MCTargetDesc/Cpu0MCTargetDesc.h
#ifndef LLVM_LIB_TARGET_CPU0_MCTARGETDESC_CPU0MCTARGETDESC_H
#define LLVM_LIB_TARGET_CPU0_MCTARGETDESC_CPU0MCTARGETDESC_H

#include "Cpu0Config.h"
#include "llvm/Support/DataTypes.h"

namespace llvm {
class Target;
class Triple;

extern Target TheCpu0Target;
extern Target TheCpu0elTarget;

} // End llvm namespace

// Cpu0レジスタのシンボル名を定義する。この定義はレジスタ名からレジスタ番号へのマッピングを定義する。
#define GET_REGINFO_ENUM
#include "Cpu0GenRegisterInfo.inc"

// Cpu0命令のシンボル名を定義する。
#define GET_INSTRINFO_ENUM
#include "Cpu0GenInstrInfo.inc"

#define GET_SUBTARGETINFO_ENUM
#include "Cpu0GenSubtargetInfo.inc"

#endif
  • lbdex/chapters/Chapter2/MCTargetDesc/Cpu0MCTargetDesc.cpp
#include "Cpu0MCTargetDesc.h"
#include "llvm/MC/MachineLocation.h"
#include "llvm/MC/MCELFStreamer.h"
#include "llvm/MC/MCInstrAnalysis.h"
#include "llvm/MC/MCInstPrinter.h"
#include "llvm/MC/MCInstrInfo.h"
#include "llvm/MC/MCRegisterInfo.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/MC/MCSymbol.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FormattedStream.h"
#include "llvm/Support/TargetRegistry.h"

using namespace llvm;

#define GET_INSTRINFO_MC_DESC
#include "Cpu0GenInstrInfo.inc"

#define GET_SUBTARGETINFO_MC_DESC
#include "Cpu0GenSubtargetInfo.inc"

#define GET_REGINFO_MC_DESC
#include "Cpu0GenRegisterInfo.inc"

//@2 {
extern "C" void LLVMInitializeCpu0TargetMC() {

}
//@2 }
  • lbdex/chapters/Chapter2/MCTargetDesc/CMakeLists.txt
# MCTargetDesc/CMakeLists.txt
add_llvm_library(LLVMCpu0Desc
  Cpu0MCTargetDesc.cpp
  )
  • lbdex/chapters/Chapter2/MCTargetDesc/LLVMBuild.txt
[component_0]
type = Library
name = Cpu0Desc
parent = Cpu0
required_libraries = MC 
                     Cpu0Info 
                     Support
add_to_library_groups = Cpu0

“Target Registration” [18] を参照のこと。

ライブラリとtdのビルド

私たちはLLVMソースコード/Users/Jonathan/llvm/release/src/に置いた。また、LLVMのリリースビルドは/Users/Jonathan/llvm/release/cmake_release_buildで実施している。LLVMのビルド方法については [19] を参照のこと。付録Aでは Cpu0のためのバックエンドのためのソースコード/Users/Jonathan/llvm/release/src to /Users/Jonathan/llvm/test/srcからコピーしたものを置いている。srcのサブディレクトリはデバッグビルドディレクトリのためのcmake_debug_buildソースコードを置いている。

src/lib/Target/Cpu0ディレクトリに加え、cpu0ターゲットをサポートするためのいくつかの変更を加えたファイルを置いている。これにはCpu0のマシンID、マシン名、リロケーションレコードを置いている。コマンドcp -rf lbdex/src/modify/src/* <yourllvm/workingcopy/sourcedir>/で、LLVMのワーキングコピーをアップデートして、簡単に差分を見つけることができる。

118-165-78-230:test Jonathan$ pwd
/Users/Jonathan/test
118-165-78-230:test Jonathan$ cp -rf lbdex/src/modify/src/* ~/llvm/test/src/.
118-165-78-230:test Jonathan$ grep -R "cpu0" ~/llvm/test/src/include
src/cmake/config-ix.cmake:elseif (LLVM_NATIVE_ARCH MATCHES "cpu0")
src/include/llvm/ADT/Triple.h:#undef cpu0
src/include/llvm/ADT/Triple.h:    cpu0,       // For Tutorial Backend Cpu0
src/include/llvm/ADT/Triple.h:    cpu0el,
src/include/llvm/Support/ELF.h:  EF_CPU0_ARCH_32R2 = 0x70000000, // cpu032r2
src/include/llvm/Support/ELF.h:  EF_CPU0_ARCH_64R2 = 0x80000000, // cpu064r2
...

次に、Cpu0のサンプルコードを第2章のものに合わせる。

  • ~/llvm/test/src/lib/Target/Cpu0/Cpu0SetChapter.h
#define CH       CH2

次に、cmakeコマンドを実行し、tdファイルをビルドする(以下のcmakeコマンドは筆者のための設定である)。

118-165-78-230:cmake_debug_build Jonathan$ cmake -DCMAKE_CXX_COMPILER=clang++
-DCMAKE_C_COMPILER=clang -DCMAKE_BUILD_TYPE=Debug -G "Xcode" ../src/

-- Targeting Cpu0
...
-- Targeting XCore
-- Configuring done
-- Generating done
-- Build files have been written to: /Users/Jonathan/llvm/test/cmake_debug_build

118-165-78-230:cmake_debug_build Jonathan$

ビルドが完了すると、llc -versionによりCpu0のバックエンドが追加されていることが分かる。

118-165-78-230:cmake_debug_build Jonathan$ /Users/Jonathan/llvm/test/
cmake_debug_build/Debug/bin/llc --version
LLVM (http://llvm.org/):
...
  Registered Targets:
  arm      - ARM
  ...
  cpp      - C++ backend
  cpu0     - Cpu0
  cpu0el   - Cpu0el
...

llc -versionを実行すると、ターゲットとして"cpu0""cpu0el"が追加されていることが分かる。これはターゲットの登録の章において、TargetInfo/Cpu0TargetInfo.cppを変更してターゲットを追加したためである。

以下に、lbdex/chapters/Chapter2のビルド方法を示す。

118-165-75-57:test Jonathan$ pwd
/Users/Jonathan/test
118-165-75-57:test Jonathan$ cp -rf lbdex/Cpu0 ~/llvm/test/src/lib/Target/.

118-165-75-57:test Jonathan$ cd ~/llvm/test/cmake_debug_build
118-165-75-57:cmake_debug_build Jonathan$ pwd
/Users/Jonathan/llvm/test/cmake_debug_build
118-165-75-57:cmake_debug_build Jonathan$ rm -rf *
118-165-75-57:cmake_debug_build Jonathan$ cmake -DCMAKE_CXX_COMPILER=clang++
-DCMAKE_C_COMPILER=clang -DCMAKE_BUILD_TYPE=Debug -DLLVM_TARGETS_TO_BUILD=Cpu0
-G "Xcode" ../src/
...
-- Targeting Cpu0
...
-- Configuring done
-- Generating done
-- Build files have been written to: /Users/Jonathan/llvm/test/cmake_debug_build

時間を短縮するために、Cpu0ターゲットのビルドには-DLLVM_TARGETS_TO_BUILD=Cpu0おぷしょを追加している。CMakeの実行後には、付録Aに示すようにXcodeを開いてXcodeプロジェクトをビルドしてほしい。Linux/Unixプラットフォームを使っている場合は付録Aに示すようにビルドしてほしい。ビルド後には/Users/Jonathan/llvm/test/cmake_debug_build/lib/Target/Cpu0ディレクトリ内に*.incファイルが生成されていることが分かる。

  • cmake_debug_build/lib/Target/Cpu0/Cpu0GenRegisterInfo.inc
namespace Cpu0 {
enum {
  NoRegister,
  AT = 1,
  EPC = 2,
  FP = 3,
  GP = 4,
  HI = 5,
  LO = 6,
  LR = 7,
  PC = 8,
  SP = 9,
  SW = 10,
  ZERO = 11,
  A0 = 12,
  A1 = 13,
  S0 = 14,
  S1 = 15,
  T0 = 16,
  T1 = 17,
  T9 = 18,
  V0 = 19,
  V1 = 20,
  NUM_TARGET_REGS     // 21
};
}
...

llvm-tblgenによって、ディレクトcmake_debug_build/lib/Target/Cpu0内に*.incファイルが生成される。これらのファイルはCpu0バックエンドの*.tdから生成される。llvm-tblgenは/Users/Jonathan/llvm/test/src/lib/Target/Cpu0/CMakeLists.txt内でtablegenによって起動される。これらの*.incファイルはCpu0のバックエンドファイルである*.cppと*.h無いで使用される。TableGenは重要なツールであり、これについては".td: LLVM’s Target Description Files"章で説明した。以下に再度掲載する。

Mix and Matchアプローチは、ターゲットの作成者は個のアーキテクチャに合ったものを選択し、異なるターゲット間で大量のコードを再利用できる。

TableGenの詳細については、 [21] [22] [23]を参照のこと。

では、llcを使ってch3.cppをコンパイルしてみる。

  • lbdex/input/ch3.cpp
int main()
{
  return 0;
}

まず、clangを使ってch3.bcを作成する。

118-165-78-230:input Jonathan$ pwd
/Users/Jonathan/llvm/test/lbdex/input
118-165-78-230:input Jonathan$ clang -target mips-unknown-linux-gnu -c
ch3.cpp -emit-llvm -o ch3.bc

上記のように、Cファイルをclang -target mips-unknown-linux-gnuコンパイルして.bcを生成しても問題ないのは、Cpu0のABIはMipsから借りているためである。次に、ビットコードファイル.bcを読める形式に変換する:

118-165-78-230:test Jonathan$ llvm-dis ch3.bc -o -

// ch3.ll
; ModuleID = 'ch3.bc'
target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f3
2:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:6
4-S128"
target triple = "mips-unknown-linux-gnu"

define i32 @main() nounwind uwtable {
  %1 = alloca i32, align 4
  store i32 0, i32* %1
  ret i32 0
}

次に、ch3.bcファイルをコンパイルするとエラーが発生する:

118-165-78-230:input Jonathan$ /Users/Jonathan/llvm/test/cmake_debug_build/
Debug/bin/llc -march=cpu0 -relocation-model=pic -filetype=asm ch3.bc -o
ch3.cpu0.s
...
... Assertion `target.get() && "Could not allocate target machine!"' failed
...

ここまでで、Cpu0バックエンドのターゲット登録が完了した。バックエンドコンパイラのコマンドllcはCpu0のバックエンドを認識できるようになった。ここまででは、ターゲットのtdファイル(Cpu0.td, Cpu0Other.td, Cpu0RegisterInfo.td ...)を単に登録しただけである。LLVMの構造によると、ターゲットマシンの定義とそれに関連するtdファイルを定義する必要がある。エラーメッセージでは、Cpu0のターゲットマシンが登録されていないということを意味する。本書ではバックエンドの開発についてステップ倍ステップで説明する。第2章の非常に多くのコードをレビューして、どのようにターゲットを登録するのか確認することができる。