LLVMにCpu0アーキテクチャを追加するチュートリアル。ちょっと飛ばしすぎたので一度立ち戻って、LLVMのアーキテクチャについてもう一度勉強し直す。
LLVMにバックエンドを追加するチュートリアル の Cpu0 architecture and LLVM structure をもう一度読み直してまとめていく。
最初の方は一生懸命自分の言葉でまとめていたけど、途中からほぼ直訳みたいになってきた。纏めた分を掲載しておく。今回は以下の項目。 第1回のものはこちらから。
Cpu0 architecture and LLVM structure
ターゲットの登録
ターゲットをTargetRegistryに登録する必要がある。登録が終わると、LLVMツールは実行時にそのターゲットを探索して使用できるようになる。TargetRegistryは直接使用することができるが、ほとんどのターゲットではヘルパーのtemplateを使って使いやすくしている。
全てのターゲットはグローバルなTagrgetオブジェクトを作成する必要があり、このオブジェクトは登録時に必要となる。したがって、ターゲット内のTargetInfoライブラリでは、ターゲットこのオブジェクトを定義する必要があり、RegisterTargetテンプレートを使ってターゲットを登録する必要がある。例えば、ファイルTargetInfo/Cpu0TargetInfo.cpp
はTheCpu0Target
をビックエンディアンとして登録し、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章の非常に多くのコードをレビューして、どのようにターゲットを登録するのか確認することができる。