Cpu0のバックエンドをLLVMに追加するプロジェクト、第5章に入った。オブジェクトファイルのサポートを行う。
今回も非常にファイル量が多くて、とりあえずLLVMをビルドするためだけにパッチを作って当てているが、LLVM 7.0は未サポートになっている部分が多く、ソースコードを書き直す必要があった。
- オリジナルのパッチ。これをあてただけでは動かない。
- パッチをさらに修正したもの。関数を一部書き直している。
今回はオブジェクトファイルのサポート。これまでは基本的にバックエンドはアセンブリファイルを出力していたが、オブジェクトファイルを出力できるように変更する。
読んだのは以下の章。
オブジェクトファイルへの変換
これまでは、LLVM IRのコードをアセンブリファイルに変換していた。オブジェクトコードに変換しようとすると、現状ではエラーを出す。
$ ./bin/llc -march=cpu0 -relocation-model=pic -filetype=obj ch4_1_mult.bc -o ch4_1_mult.cpu0.o ./bin/llc: warning: target does not support generation of this file type!
そこでオブジェクトコードの生成をサポートする。Chapter5_1のファイルを実装した。
$ ./bin/llc -march=cpu0 -relocation-model=pic -filetype=obj ch4_1_mult.bc -o ch4_1_mult.cpu0.o $ objdump -s ch4_1_mult.cpu0.o ch4_1_mult.cpu0.o: file format elf32-big Contents of section .text: 0000 09ddfff8 0920000b 022d0004 012d0004 ..... ...-...-.. 0010 09220001 0f302aaa 0d33aaab 41230000 ."...0*..3..A#.. 0020 46300000 1f43001f 1d330001 11334000 F0...C...3...3@. 0030 0940000c 17334000 12223000 022d0004 .@...3@.."0..-.. 0040 012d0004 09dd0008 3ce00000 .-......<... Contents of section .comment: 0000 00636c61 6e672076 65727369 6f6e2036 .clang version 6 0010 2e302e30 2d317562 756e7475 32202874 .0.0-1ubuntu2 (t 0020 6167732f 52454c45 4153455f 3630302f ags/RELEASE_600/ 0030 66696e61 6c2900 final).
$ ./bin/llc -march=cpu0el -relocation-model=pic -filetype=obj ch4_1_mult.bc -o ch4_1_mult.cpu0el.o $ objdump -s ch4_1_mult.cpu0el.o ch4_1_mult.cpu0el.o: file format elf32-big Contents of section .text: 0000 f8ffdd09 0b002009 04002d02 04002d01 ...... ...-...-. 0010 01002209 aa2a300f abaa330d 00002341 .."..*0...3...#A 0020 00003046 1f00431f 0100331d 00403311 ..0F..C...3..@3. 0030 0c004009 00403317 00302212 04002d02 ..@..@3..0"...-. 0040 04002d01 0800dd09 0000e03c ..-........< Contents of section .comment: 0000 00636c61 6e672076 65727369 6f6e2036 .clang version 6 0010 2e302e30 2d317562 756e7475 32202874 .0.0-1ubuntu2 (t 0020 6167732f 52454c45 4153455f 3630302f ags/RELEASE_600/ 0030 66696e61 6c2900 final).
実装は、以下のファイルを変更・追加した。
- lib/Target/Cpu0/Cpu0MCInstLower.h
- lib/Target/Cpu0/Cpu0Subtarget.h
- lib/Target/Cpu0/Cpu0TargetStreamer.h
- lib/Target/Cpu0/InstPrinter/Cpu0InstPrinter.cpp
- lib/Target/Cpu0/MCTargetDesc/CMakeLists.txt
- lib/Target/Cpu0/MCTargetDesc/Cpu0AsmBackend.cpp
- lib/Target/Cpu0/MCTargetDesc/Cpu0AsmBackend.h
- lib/Target/Cpu0/MCTargetDesc/Cpu0BaseInfo.h
- lib/Target/Cpu0/MCTargetDesc/Cpu0ELFObjectWriter.cpp
- lib/Target/Cpu0/MCTargetDesc/Cpu0FixupKinds.h
- lib/Target/Cpu0/MCTargetDesc/Cpu0MCCodeEmitter.cpp
- lib/Target/Cpu0/MCTargetDesc/Cpu0MCCodeEmitter.h
- lib/Target/Cpu0/MCTargetDesc/Cpu0MCExpr.cpp
- lib/Target/Cpu0/MCTargetDesc/Cpu0MCExpr.h
- lib/Target/Cpu0/MCTargetDesc/Cpu0MCTargetDesc.cpp
- lib/Target/Cpu0/MCTargetDesc/Cpu0MCTargetDesc.h
- lib/Target/Cpu0/MCTargetDesc/Cpu0TargetStreamer.cpp
ELFのエンコーダは上記の図に示す関数群を呼び出す。AsmPrinter::OutStreamerは、llc -filetype=obj
が呼び出されたときに、MCObjectStreamer
を設定する。
命令オペランドの情報は、上記の図のようにして渡される。以下のステップを踏む。
encodeInstruction()
はMI.OpcodeをgetBinaryCodeForInstr()
に渡す。getBinaryCodeForInstr()
はMI.Operand[n]をgeMachineOpValue()
に渡す。getMachineOpValue()
を呼び出すことでレジスタ番号を取得する。getBinaryCodeForInstr()
が、全てのレジスタ番号をencodeInstruction()
に返す。
MI.Opcodeは命令センタ悪ステージで設定される。テーブル生成関数getBinaryCodeForInstr()
は全てのオペランドの情報をtdファイルから取得する。
例えば、Cpu0のバックエンドが"%0 = add %1, %2" IRから "addu \$v0, \$at, \$v1"を生成したとすると、LLVMがレジスタ%0, %1, %2のためにそれぞれ \$v0, \$at, $v1を割り当てている。MCOperand構造体は、MI.Operands[]にはレジスタ番号の集合が格納されており、LLVMはgetMachineOpValue()
から取得されるレジスタを割り当てる。
getMachineOpValue()
内のgetEncodingValue(Reg)
関数は、AT, V0, V1などのレジスタの名前から、レジスタ番号を取得する。これはCpu0RegisterInfo.tdから取得される。
Cpu0AsmBackend.cpp内のapplyFixup()
は、jeq, jub命令などの、「アドレス制御フロー文」や「関数呼び出し文」の制御を行う。Cpu0ELFObjectWriter.cpp内のneedsRelocateWithSymbol()
のリロケーションレコードをtrue/falseに設定することで、リンク時にアドレスの調整が必要かどうかが決まる。もしtrueならば隣家はアドレス値を正しい情報に修正できるチャンスがある。一方でfalseだった場合、隣家はリロケーション情報を調整するための正しい情報を持っていない。リロケーションレコードについては、ELFサポートの章で説明する。
ELFオブジェクト形式の命令を生成すると、Cpu0MCCodeEmitter.cpp内のEncodeInstruction()
関数が呼び出される。
バックエンドのターゲット登録構造
Cpu0MCTargetDesc.cppは、前の章で説明したように、ターゲットの登録を行い、アセンブリ命令の出力を行う。同様にELFオブジェクトの登録も、以下のようにして実施する。
elf streamerによる関数の登録
// Register the elf streamer. TargetRegistry::RegisterELFStreamer(*T, createMCStreamer); static MCStreamer *createMCStreamer(const Triple &TT, MCContext &Context, MCAsmBackend &MAB, raw_pwrite_stream &OS, MCCodeEmitter *Emitter, bool RelaxAll) { return createELFStreamer(Context, MAB, OS, Emitter, RelaxAll); } // MCELFStreamer.cpp MCStreamer *llvm::createELFStreamer(MCContext &Context, MCAsmBackend &MAB, raw_pwrite_stream &OS, MCCodeEmitter *CE, bool RelaxAll) { MCELFStreamer *S = new MCELFStreamer(Context, MAB, OS, CE); if (RelaxAll) S->getAssembler().setRelaxAll(true); return S; }
Cpu0MCCodeEmitter
は、ビッグエンディアン用とリトルエンディアン用を登録する。RegisterELFStraemer()
がelf streamerクラスを再利用する間に、オブジェクトフォーマットについてチェックをする。
読者は以下のような疑問を持つかもしれない: 「createCpu0MCCodeEmitterEB(const MCInstrInfo &MCII, const MCSubtargetInfo &STI, MCContext &Ctx)
の実際の引数は何なのか?」「いつ引数が設定されるのか?」そう、この場所では引数は設定されず、createXXX()関数は関数ぽいなtのみが設定される(C言語では、TargetRegistry::RegisterXXX(TheCpu0Target, createXXX())
で、createXXX
は関数ポインタである)。LLVMは、ターゲット登録の際に関数ポインタをcreateXXX()
に保持し続け、ターゲット登録処理中、つまりRegisterXXX()
を呼び出し中の引数を割り当てる際に、createXXX()
関数を呼び出すことになる。
asm backendの関数登録
Cpu0AsmBackendクラスはアセンブラからオブジェクトへの橋渡しを行う。ビッグエンディアンとリトルエンディアンについてそれぞれ考えておく必要がある。これらはMCAsmBcakendの派生クラスである。オブジェクトファイルを生成するコードのほとんどは、MCELFStreamer
クラスとその親クラスであるMCAsmBackend
クラスで実装されている。