FPGA開発日記

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

"Creating an LLVM Backend for the Cpu0 Architecture"をやってみる(11. グローバル変数のサポート)

Cpu0のバックエンドをLLVMに追加するプロジェクト、6章では、グローバル変数のアクセスをサポートする。

今回も非常にファイル量が多くて、とりあえずLLVMをビルドするためだけにパッチを作って当てているが、LLVM 7.0は未サポートになっている部分が多く、ソースコードを書き直す必要があった。

  • Chapter6の実装を追加したもの

github.com

今回はグローバル変数のサポート。グローバル変数のサポートには4種類があって、それぞれどのような処理が必要なのか見ていく。

  • リロケーションモード : static, Smallsection : 未使用
  • リロケーションモード : static, Smallsection : 使用
  • リロケーションモード : PIC, Smallsection : 未使用
  • リロケーションモード : PIC, Smallsection : 使用

読んだのは以下の章。


グローバル変数

これまではローカル変数についてみてきたが、今回はグローバル変数のアクセス変換について学ぶ。

グローバル変数のDAG変換は、過去に学んだDAG変換とは異なる。バックエンドのC++コードが、llc -relocation-moelオプションに基づいてIR DAGノードを作成数。一方で、他のDAGはIR DAGを直接マシンコードのDAGに変換する。マシンコードのIR DAGはIR DAGの入力ファイルに基づく(例外的に、 Chapter3_4で使用するRetLR疑似命令は除く)。加えて、バックエンドを持っている場合は、アセンブリディレクティブ(マクロ)に関連するグローバル変数のための関数を出力するためのマシン命令について考えておく必要がある。

Chapter6_1グローバル変数をサポートしている。ch6_1.cppコンパイルしてみよう。

./bin/clang -c -target mips-unknown-linux-gnu ../lbdex/input/ch6_1.cpp -emit-llvm -o ch6_1.bc
./bin/llvm-dis ch6_1.bc -o -

...
@gStart = dso_local global i32 3, align 4
@gI = dso_local global i32 100, align 4

; Function Attrs: noinline nounwind optnone
define dso_local i32 @_Z11test_globalv() #0 {
entry:
  %c = alloca i32, align 4
  store i32 0, i32* %c, align 4
  %0 = load i32, i32* @gI, align 4
  store i32 %0, i32* %c, align 4
  %1 = load i32, i32* %c, align 4
  ret i32 %1
}

Cpu0グローバル変数のオプション

Cpu0のグローバル変数の扱いオプションには、以下の4つが存在する。

  • llc -relocation-model=static -cpu0-use-small-section=false
        lui     $2, %hi(gI)
        ori     $2, $2, %lo(gI)
        ld      $2, 0($2)
        st      $2, 4($sp)
        ld      $2, 4($sp)
  • llc -relocation-model=static -cpu0-use-small-section=true
        ori     $2, $gp, %gp_rel(gI)
        ld      $2, 0($2)
        st      $2, 4($sp)
        ld      $2, 4($sp)
  • llc -relocation-model=pic -cpu0-use-small-section=false

    assembly lui $2, %got_hi(gI) addu $2, $2, $gp ld $2, %got_lo(gI)($2) ld $2, 0($2) st $2, 4($sp) ld $2, 4($sp)

  • llc -relocation-model=pic -cpu0-use-small-section=true

        ld      $2, %got(gI)($gp)
        ld      $2, 0($2)
        st      $2, 4($sp)
        ld      $2, 4($sp)

以下にオプションをまとめる:

オプション名 デフォルト 選択可能なオプション値 説明
-relocation-model pic static pic: 位置独立アドレス, static: 絶対アドレス
-cpu0-use-small-section false true false: .data or .bss, 32‐bitアドレス, true: .sdata or .sbss, 16‐bitアドレス
  • -reloction-model=static時のCpu0 DAGと命令
オプション: cpu0-use-small-section false true
アドレッシングモード 絶対 $gp 相対
アドレッシング 絶対 $gp+オフセット
Legalized selection DAG (add Cpu0ISD::Hi<gI offset Hi16> Cpu0ISD::Lo<gI offset Lo16>) (add register %GP, Cpu0ISD::GPRel<gI offset>)
Cpu0 lui $2, %hi(gI); ori $2, $2, %lo(gI); ori $2, $gp, %gp_rel(gI);
リロケーションレコードの解決 リンク時 リンク時
  • -relocation-model=pic時のCpu0 DAGと命令
オプション: cpu0-use-small-section false true
アドレッシングモード $gp 相対 $gp 相対
アドレッシング $gp+オフセット $gp+オフセット
Legalized selection DAG (load (Cpu0ISD::Wrapper register %GP, <gI offset>)) (load EntryToken, (Cpu0ISD::Wrapper (add Cpu0ISD::Hi<gI offset Hi16>, Register %GP), Cpu0ISD::Lo<gI offset Lo16>))
Cpu0 ld $2, %got(gI)($gp); lui $2, %got_hi(gI); add $2, $2, $gp; ld $2, %got_lo(gI)($2);
リロケーションレコードの解決 リンク・ロード時 リンク・ロード時

-relocation-model=static-cpu0-use-small-section=falseの時には、レジスタ$g0はグローバル変数のスタート位置に設定されている。

To support global variable, first add UseSmallSectionOpt command variable to Cpu0Subtarget.cpp. After that, user can run llc with option llc -cpu0-use-small-section=false to specify UseSmallSectionOpt to false. The default of UseSmallSectionOpt is false if without specify it further. About the cl::opt command line variable, you can refer to here [1] further.

グローバル変数をサポートするためには、Cpu0Subtarget.cppにまずはUseSmallSectionOpt コマンド変数を追加する。そのあとに、llcを`llc -cpu0-use-small-section=false で実行することでUseSmallSectionOptをfalseに設定する。UseSmallSectionOptのデフォルト値はfalseである。cl::optコマンドライン変数についてはさらに1(https://jonathan2251.github.io/lbd/globalvar.html#id6) を参照のこと。

setOperationAction(ISD::GlobalAddress, MVT::i32, Custom)Cpu0TargetLowering::LowerOperation()で実装したグローバルアドレスの計算関数をllcに通知する。LLVMはこの関数を使ってIR DAGのグローバル変数のロード処理をマシンコードに変換するときにのみ使用する。

コンストラクト関数Cpu0TargetLowering()内のsetOperationAction(ISD::XXX, MVT::XXX, Custom)により設定されたすべてのIR操作のカスタムタイプが"Legalized selection DAG"ステージ中でargetLowering::LowerOperation()を呼び出したとしても、グローバルアドレスのアクセス操作は、DAG NodeがISD::GlobalAddressかどうかをチェックして識別することができる。

スタティックモード

Cpu0のグローバル変数のオプションでは、cpu0-use-small-section=falseを設定することにより、グローバル変数をdata/bssに配置する。一方で、cpu0-use-small-section=trueを設定すると、グローバル変数をsdata/sbssに配置される。sdataはsmall data areaの略である。セクションデータとsdataは初期値を持ったグローバル変数(int gl = 100のようなもの)が格納される。一方で、初期値を持たないグローバル変数(int gl;)はbssおよびsbssに格納される。

data or bss

スタティックモードかつ、cpu0-use-small-section=false、まずはglのPC相対アドレスの上位アドレスをレジスタ\$2に設定し、16ビットシフトする。レジスタ\$2にglのPC絶対アドレスの下位アドレスを設定する。

llc -relocation-model=static -cpu0-use-small-section=falseによりlowerGlobalAddress()はDAG(GlobalAddress<i32* @gI> 0)(add Cpu0ISD::Hi<gI offset Hi16> Cpu0ISD::Lo<gI offset Lo16>)に変換する。

Pat<(add CPURegs:\$hi, (Cpu0Lo tglobaladdr:\$lo)), (ORi CPURegs:\$hi, tglobaladdr:\$lo)>(add Cpu0ISD::Hi, Cpu0ISD::Lo)(ori Cpu0ISD::Hi, Cpu0ISD::Lo)に変換する。

sdata or sbss

sdata/sbssは16ビット長で表現できるアドレス領域であり、ELF内で高速アクセスできる領域に配置されている。cpu0-use-small-section=trueにて生成できる。

llc -relocation-model=static -cpu0-use-small-section=trueを実行することにより、DAG(GlobalAddress<i32* @gI> 0)(add register %GP Cpu0ISD::GPRel<gI offset>)に変換される。

PICモード

sdata or bss

llc -relocation-model=pic -cpu0-use-small-section=trueに設定することで、sdata,sbssを使用したPICのコードを出力できるようになる。

  ld  $2, %got(gI)($gp)

The .cpload is the assembly directive (macro) which will expand to several instructions. Issue .cpload before .set nomacro since the .set nomacro option causes the assembler to print a warning message whenever an assembler operation generates more than one machine language instruction, reference Mips ABI [2].

Following code will exspand .cpload into machine instructions as below. “0fa00000 09aa0000 13aa6000” is the .cpload machine instructions displayed in comments of Cpu0MCInstLower.cpp.

.cploadは複数の命令を展開するためのアセンブリディレクティブ(マクロ)である。.cploadは、.set nomacroの前に宣言する必要がある。.cploadはマシンコードに展開される。これは、gp_dispからの相対位置を計算し\$gpに格納される。

// Lower ".cpload $reg" to
//  "lui   $gp, %hi(_gp_disp)"
//  "addiu $gp, $gp, %lo(_gp_disp)"
//  "addu  $gp, $gp, $t9"

上記の_gp_dispはリロケーションレコードである。これは、アセンブリori $gp, $zero, %hi(_gp_disp)およびアセンブリori $gpと同等のマシン命令0da00000 (オフセット0) と0daa0000(オフセット8)に変換される。$gp, %lo(_gp_disp)は、_gp_dispに依存するリロケーションレコードである。ローダまたはOSは、動的関数をメモリxにロードするときに_gp_dispを(x - .dataの開始アドレス)で計算し、これら2つの命令を正しく調整する。この関数が呼び出されると共有関数がロードされるため、再配置レコードld $ 2, %got(gI)($gp)」はリンク時に解決できない。リロケーションレコードはロード時に解決されるが、リンカはメモリアドレスをローダに渡し、ローダはオフセットを直接計算するだけでこれを解決できるため、名前バインディングは静的である。メモリ参照は、リンク時に_gp_dispのオフセットでバインドされる。ELFの再配置記録は、ELFサポートの章で紹介する。

以下の対応するオプションllc -relocation-model=picとしてのlowerGlobalAddress()のコードフラグメントは、DAG(GlobalAddress <i32 * @gI> 0)(load EntryToken, (Cpu0ISD::Wrapper Register %GP, TargetGlobalAddress<i32* @gI> 0))に変換する。

最後に、Cpu0のld命令のパタンは、DAG(load EntryToken, (Cpu0ISD::Wrapper Register %GP, TargetGlobalAddress<i32* @gI> 0))ld $2, %got(gI)($gp)に変換する。

PICモードでは、Cpu0は".cpload"とld $2, %got(gl)($gp)を使用してグローバル変数にアクセスすることを思い出そう。コストは、レジスタ\$gpが常にアドレス.sdataに割り当てられ、そこで固定されるとは想定していないことから生じる。この関数に\$gpを予約していても、\$gpレジスタは他の関数で変更することができる。最後のサブセクションでは、\$gpはどの関数でも保存されると想定されいる。実行時に\$gpが固定されている場合は、ここで ".cpload"を削除し、グローバル変数アクセスで命令コストを1つだけにすることができる。" .cpload” を削除することの利点は、変数に割り当てることができる汎用レジスタ\$gpを使用しなくてもよいということである。最後のサブセクションである.sdataモードでは、静的リンクであるため".cpload"を削除する。PICモードでは、動的ロードは時間がかかり過ぎる。すべての関数で1つの汎用レジスターを失うことを犠牲にして".cpload"を削除することは、ここでは意味がない。この関数を静的リンクでリンクしたい場合はllc -relocation-model=picからの".cpload"の再配置レコードもリンク段階で解決できる。

data or bss

llc -relocation-model=picを指定した場合のlowerGlobalAddress()は、(GlobalAddress<i32* @gI> 0)DAGを(load EntryToken, (Cpu0ISD::Wrapper (add Cpu0ISD::Hi<gI offset Hi16>, Register %GP), TargetGlobalAddress<i32* @gI> 0))に変換する。

グローバル変数のプリントをサポートする

LowerSymbolOperand関数を追加して、グローバル変数オペランド出力をサポートする。

サマリ