FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

"Creating an LLVM Backend for the Cpu0 Architecture"をやってみる(8. BackEnd追加のまとめ2.)

f:id:msyksphinz:20181123225150p:plain

Cpu0のバックエンドをLLVMに追加するチュートリアル、第3章(?)のBackendの追加の章をやり終えた。 やり終えたといっても、ひたすら写経して、ビルドして、テストパタンを動かして終わりである。 これではさすがにもったいないので、もう少しまとめておきたい。

この辺りまで読んだ。が、最後の方はあまり理解できていない。

Add Cpu0DAGToDAGISel class

  • lbdex/chapters/Chapter2/Cpu0InstrInfo.td : Cpu0InstrIndo.tdにはADDiuの実装と、Load/Store命令の定義が含まれる。
  • lbdex/chapters/Chapter3_3/Cpu0TargetMachine.cpp : createCpu0SEISelDag()を通じてPassを追加する。
  • lbdex/chapters/Chapter3_3/Cpu0ISelDAGToDAG.h, lbdex/chapters/Chapter3_3/Cpu0ISelDAGToDAG.cpp : DAGの変換を行う関数群。
    • Cpu0DAGToDAGISel::Select()は、Cpu0DAGToDAGISel::SelectAddr()(アドレス型時のデータDAG)のための"OP code DAG node"を選択する。

Handle return register $lr

MIPSは"jr $ra"で関数から戻ってくるため、$raをリターンアドレスとして使用する。そして$2に戻り値を保存している。しかし、LLVMは$2だけでなく任意の場所を戻り値として使用する。また、"jr $ra"も$raではなく任意の場所をリターンアドレスとして使用する可能性がある。$raを指定することでリターンアドレスは固定できるが、"jal $x"は任意の場所を指定できる。

  • lbdex/chapters/Chapter3_4/Cpu0CallingConv.td
  • lbdex/chapters/Chapter3_4/Cpu0InstrFormats.td
  • lbdex/chapters/Chapter3_4/Cpu0InstrInfo.td
  • lbdex/chapters/Chapter3_4/Cpu0ISelLowering.h
  • lbdex/chapters/Chapter3_4/Cpu0ISelLowering.cpp
  • lbdex/chapters/Chapter3_4/Cpu0MachineFunction.h
  • lbdex/chapters/Chapter3_4/Cpu0SEInstrInfo.cpp

しかしこのままではLocal変数のスタックが確保されていないためエラーとなる。

プロローグとエピローグの追加

Callee-savedレジスタを保存する。予約されているレジスタについてのケアを行う。 - emitPrologue() はプロローグコードを追加する。フレームポインタがある場合hあフレームレジスタ%a14に、古いスタックポインタを設定する。 - emitEpilogue()はスタックフレームの破壊とすべての保存したレジスタを復旧する。スタックポインタ%a10、リターンアドレス%a11、フレームポインタ%a14はひとつ前のコンテキストに復旧するが、これはemitEpilogue()の役目ではない。 - eliminateFrameIndex()は、スタックスロットにアクセスする命令のために生成される。これまでの関数内で、スタック上の変数にアクセスする場合は、全てフレームポインタと即値オフセットでのアクセスであった。この関数により、レジスタとオフセットのペアに変換する。

f:id:msyksphinz:20181218234638p:plain
図. 16 スタックフレームを使う場合の変数のアドレッシングと、フレームポイントを使った場合の変数のアドレッシング (https://jonathan2251.github.io/lbd/backendstructure.html#concept より抜粋)

実際にプロローグとエピローグを追加する。

  • lbdex/chapters/Chapter3_5/Cpu0SEFrameLowering.cpp
  • lbdex/chapters/Chapter3_5/Cpu0MachineFunction.h

簡単なコードから見てみる。以下のBitCodeにPrologueとEpilogueを追加すると、以下のようなコードが生成される。プロローグとしてスタックポインタの減算と、エピローグとしてスタックポインタの加算が組み込まれている。

define i32 @main() #0 {
  %1 = alloca i32, align 4
  store i32 0, i32* %1
  ret i32 0
}
# BB#0:
  addiu $sp, $sp, -8
$tmp1:
  .cfi_def_cfa_offset 8
  addiu $2, $zero, 0
  st  $2, 4($sp)
  addiu $sp, $sp, 8
  ret $lr

Handle stack slot for local variables

局所変数のスタック操作を行う。以下のスタックフレームオブジェクトは、$spを経由して参照される。 1. 外部引数 2. スタック領域に動的に割り当てられるポインタ 3. Callee-savedレジスタの場所

つぎに、保存すべきレジスタを決定する。これはdetermineCalleeSaves()により決定される。

最後に、storeRegToStack()によりレジスタをスタックに退避する。

Large Stack

単純な関数ではうまくいくが、32ビット長のスタック領域を考慮する。大きなサイズのスタックを処理するためには、再帰呼び出しを行う。 スタックサイズが、"0 ~ 0x7ff8", "0x8000 ~ 0xfff8" , "x10000 ~ 0xfffffff8", "x10000 ~ 0xfffffff8" に応じて使用するプロローグとエピローグのコードが異なる。

Data operands DAGs

DAGを追加するためには、既存のコードにはどのようなDAGが追加されているのかについて理解する必要がある。