FPGA開発日記

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

Rocket Chip Generatorのバス生成技術を読み解く(1. Diplomacyの論文を読む)

Rocket Chipのバス設計は非常に読み解くのが難しい。 Rocket Chipのフロントエンドを読み解いていこうと思ったが、フロントエンドのバス接続の部分でさっそく挫折してしまった。

特に、Chiselで記述されたバスプロトコルとLazyModuleを読み解くのが何だか良く分からない。前提知識ゼロで臨むのは厳しいので、基本的な知識を身に着けるために論文を読み始めた。

carrv.github.io

まずは前半部分。DiplomacyとTileLinkについて。


現代のSoC設計において、バスの接続というのは非常に時間がかかるもので、かつミスを起こしやすい。そこで、RISC-VベースのRocket-Chip GeneratorではInterconnectsを接続する環境を構築した。

Rocket-ChipのTileLinkの実装は、Diplomacyを使用して構築している。

1. Introduction

現代のSoC設計について、バス設計というのはとにかく大変なものである。

Rocket ChipのSoCを構築するために、2種類のツールを開発した。

  • インターコネクトをグラフ化する
  • 全ての機能が正しく接続されることを確認する。つまり、生成したデザインがバスプロトコルレベルでのデッドロックを発生しないことを確認する。

Diplomacyプロトコルのキモ:パラメタライズおよびネゴシエーションのためのフレームワークである。

  • マスタースレーブのペアとプロトコルのテンプレートを与えることで、Diplomacyはすべての接続されたデバイスの要件を確認し、設定可能なパラメータを確認し、最終的にアダプタとエンドポイントにバインドするパラメータを生成する。
  • システムのエンドポイントで排他的なアクセスが生じることを確認するために、Diplomacyは他のエンドポイントの情報を使用する。

例として、TileLinkを取り上げる。TileLinkの特徴としては

ここでは、TileLinkとDiplomacyを使用することで、プロトコルのパラメータを手動で設定しない。

  • システムインターコネクトのネットワークグラフからパラメータを推論する。
  • さらに、キューの挿入などのリタイミングを容易に実現する。

2. Diplomacy

DiplomacyはRocket Chip内のSoCバスプロトコルを構築するためのフレームワークで、以下の2種類のコンポーネントが含まれている。

DiplomacyはChiselで設計してあり、Scalaの機能を使用してパラメタライズを行っている。これはVerilogのパラメタライズよりも柔軟で、より細かなパラメタライズしたVerilogを生成することができる。

Diplomacyでは、インターコネクトを有効グラフで構築する。Diplomacyでは、プロトコル間の仕様についてクロスチェックを行っている。

Diplomacyは、特定のデータや制御信号の幅などといったプロトコル内の任意のフリーなパラメータを自動的に設定することができる。

  1. 最初のフェーズではパラメータの交渉であり、グラフのトポロジを作りだし、ノードはすべてのエッジにおいてパラメータの値を設定する。
  2. 2番目のフェーズは具体的なモジュールの生成であり、Chiselのコンパイラが起動しノードグラフに応じてモジュールを生成する。各Chiselのモジュールはエラボレートされ、事前に計算されたDiplomaticなパラメータを使用して構築される。

Diplomacyは、インターコネクトの要素として「ノード」と「エッジ」を作成する。

  • ノード : ハードウェアを生成するためにデザイン内のどこでDiplomaticなパラメータが使用されるかを表現する。
  • エッジ : マスターとスレーブのインタフェースの有向なペアを表現する。

  • ソース : マスターインタフェース

  • シンク : スレーブインタフェース

パラメータは、ソースからシンクに伝搬(outward)、もしくはシンクからソースに伝搬(inward)される。

パラメータのネゴシエーションには2つのサブプロセスが存在する。

  1. ソースエンドポイントノードから始まり、いくつかのパラメータフローが全てのシンクノードに到達するまで続ける。
  2. シンクエンドポイントノードから始まり、全てのソースノードに対してパラメータが伝搬するまで続ける。

したがって、すべてのエッジが双方向からのすべてのパラメータを受け取ることになる。

Diplomacyはバスプロトコルからは独立であり、どのようなプロトコルでもDiplomaticのコンポーネントを使用して構築可能である。

Figure-1はシンプルなRocket ChipのDiplomaticなグラフを示している。黄色い箱がDiplomaticなノードであり、⇔がDiplomaticエッジである。2色のエッジはTileLinkとAXI4プロトコルを示しており、SoCインターコネクト上にデプロイされる。AXI4はシステムの外部とコミュニケーションするために使用され、TileLinkは内部の接続のために使用される。左上のノードはRocketプロセッサの命令キャッシュとデータキャッシュである。左下のノードはAXI4からTileLinkへのブリッジである。図の中心部のノードのシーケンスうはブートROMやデバッグユニットなどのブロックである。2種類のTileLink-AXI4変換が搭載されており、キャッシュ可能なアクセスとアンキャッシュなアクセスをそれぞれ表現している。

f:id:msyksphinz:20190618001735p:plain
本論文より抜粋。

LLVMのバックエンドを作るための第一歩 (25. int32型以外のサポート)

f:id:msyksphinz:20190425001356p:plain

現在は、int型(32ビット整数)のみLLVM IRをサポートしているが、それ以外の型をサポートしたいと思う。32ビット整数、16ビット整数、8ビット整数、そしてBool型をサポートする。

これらの値をメモリアクセスするために、AlignedLoadを拡張して以下のノードを追加する。

  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXInstrInfo.td
// Load/Store PatFrags.
def load_a          : AlignedLoad<load>;
def sextloadi8_a    : AlignedLoad<sextloadi8>;
def zextloadi8_a    : AlignedLoad<zextloadi8>;
def sextloadi16_a   : AlignedLoad<sextloadi16>;
def zextloadi16_a   : AlignedLoad<zextloadi16>;
def extloadi16_a    : AlignedLoad<extloadi16>;

def store_a         : AlignedStore<store>;
def truncstorei8_a  : AlignedStore<truncstorei8>;
def truncstorei16_a : AlignedStore<truncstorei16>;

32ビット以外でも、16ビットと8ビットでロードストアをするためのノードを定義する。このノードを使って、ロード命令とストア命令を定義する。

/// Load and Store Instructions
///  aligned
defm LW     : LoadM32 <0b0000011, 0b010, "lw",  load_a>;
defm SW     : StoreM32<0b0100011, 0b010, "sw",  store_a>;
defm LB     : LoadM32 <0b0000011, 0b000, "lb",  sextloadi8_a>;
defm LBU    : LoadM32 <0b0000011, 0b100, "lbu", zextloadi8_a>;
defm SB     : StoreM32<0b0100011, 0b000, "sb",  truncstorei8_a>;
defm LH     : LoadM32 <0b0000011, 0b001, "lh",  sextloadi16_a>;
defm LHU    : LoadM32 <0b0000011, 0b101, "lhu", zextloadi16_a>;
defm SH     : StoreM32<0b0100011, 0b001, "sh",  truncstorei16_a>;

さらに、MYRISCVXのレジスタはi32型なので、charなどの符号なし整数からi32型へのロードができるように以下のパタンを追加する。

def : Pat<(i32 (zextloadi1  addr:$src)), (LBU addr:$src)>;
def : Pat<(i32 (zextloadi8  addr:$src)), (LBU addr:$src)>;
def : Pat<(i32 (zextloadi16 addr:$src)), (LHU addr:$src)>;

def : Pat<(i32 (sextloadi1  addr:$src)), (LB addr:$src)>;
def : Pat<(i32 (sextloadi8  addr:$src)), (LB addr:$src)>;
def : Pat<(i32 (sextloadi16 addr:$src)), (LH addr:$src)>;

def : Pat<(i32 (extloadi1  addr:$src)), (LBU addr:$src)>;
def : Pat<(i32 (extloadi8  addr:$src)), (LBU addr:$src)>;
def : Pat<(i32 (extloadi16 addr:$src)), (LHU addr:$src)>;

さらに、符号なしの値を拡張して32ビットの符号付整数に設定するためのパタンを追加する。

def : Pat<(sext_inreg GPR:$ra, i8),  (SRAI (SLLI GPR:$ra, 24), 24)>;
def : Pat<(sext_inreg GPR:$ra, i16), (SRAI (SLLI GPR:$ra, 16), 16)>;

それぞれ、8ビット符号なし数の場合は24ビット左シフトしたのちに、24ビット符号付右シフトする。 一方で、16ビット符号なし数の場合は16ビット左シフトしたのちに、16ビット符号付右シフトする。

これでリビルドを行い、以下のソースをコンパイルする。

#include <stdint.h>

int8_t test_int8_type ()
{
  volatile int8_t i8  = 0xab;
  int8_t i8_2 = i8 + 4;
  return i8_2;
}


uint8_t test_uint8_type ()
{
  volatile uint8_t  u8  = 0xab;
  uint8_t u8_2 = u8 + 4U;
  return u8_2;
}
./bin/clang -target mips-unknown-linux-gnu -c ../lbdex/input/ch7_1_other_type.cpp -emit-llvm
./bin/llc --debug -march=myriscvx32 -mcpu=simple32 -relocation-model=pic -filetype=asm ch7_1_other_type.bc -o -
_Z14test_int8_typev:
# %bb.0:                                # %entry
        addi    x2, x2, -8
        addi    x10, zero, 171
        sb      x10, 4(x2)
        lbu     x10, 4(x2)
        addi    x10, x10, 4
        slli    x10, x10, 24
        srai    x10, x10, 24
        addi    x2, x2, 8
        ret     x1

_Z15test_uint8_typev:
# %bb.0:                                # %entry
        addi    x2, x2, -8
        addi    x10, zero, 171
        sb      x10, 4(x2)
        lbu     x10, 4(x2)
        addi    x10, x10, 4
        andi    x10, x10, 255
        addi    x2, x2, 8
        ret     x1

RISC-V Workshop Zurichで発表されたRISC-V Vector Extension 0.7.1

RISC-V Workshop Zurichで発表されたRISC-V Vector Extension 0.7.1の資料がアップロードされたので、さっそく概要を把握してみる。

ちなみに、詳細な資料はこちらを参照した方がよい。

github.com


ベクトル命令のアップデート

RISC-Vの"V"拡張について。Ver 0.7が公開されている。詳細は https://github.com/riscv/riscv-v-spec を参照のこと。

RISC-VのVector拡張についての概要。32本のベクトルレジスタが定義される。各レジスタの要素はVLMAXで定義される要素までを最大で格納することができる。このVLMAXRISC-Vコアの実装に依存する。

  • 命令
    • ロードストア・スキャッタ・ギャザー
    • 整数・固定小数点・浮動小数点命令を備える。
    • ベクトル・ベクトル演算、ベクトル・スカラ演算、ベクトル・即値命令を備える
    • 複数精度のサポートおよび命令バンド幅を削減するために、複数のベクトルレジスタを合わせてより長いベクトルレジスタを定義可能。
    • カスタムデータ型およびカスタムデータ幅をサポートするための拡張領域が存在する。
  • 主要なCSR

    • vtypeレジスタ : 各ベクトルレジスタでどの型のデータ(16ビット整数、32ビット整数、など)を格納することができるのかを格納するためのCSRを備える。
      • データの型情報を命令側ではなくレジスタ側に持たせる。これにより命令フィールドのサイズを32ビットでキープする。
      • vsew フィールド : standard element width (SEW = 8, 16, 32, ... , 1024)
      • vlmul フィールド : vector length multiplier (LMUL = 1, 2, 4, 8)
      • vediv フィールド : vector element divider (EDIV = 1, 2, 4, 8)
    • vlレジスタ : 各ベクトル演算命令において、有効な要素が格納されている要素の長さを示す。したがってvl < VLMAXである必要がある。
    • vstart
    • fcsr (vxrm / vxsat) : 固定小数点の丸めモードおよびFP CSRのSaturationフラグを格納している。
  • ベクトル命令のパラメータ

    • ELEN : ビット内の最大要素
    • VLEN : ベクトルレジスタ内のビット数。VLEN >= ELEN
    • SLEN : ビット内のストリップ距離 VLEN >= SLEN >= ELEN
  • VLMAXVLEN(ベクトルレジスタビット長) / SEW(ベクトルレジスタの要素のビット幅)で算出される。
  • 現在の有効なベクトル長はvlレジスタによって取得できる。これは0 <= vl <= VLMAXである必要がある。

ベクトル制御命令

  • vlsetvli rd, rs1, imm : rs1にアプリケーションベクトル長(Application Vector Length : AVL)を指定する。immにvtypeエンコーディング<vsew, vlmul, vediv>を指定する。rdには算出されるvlが返されるmin(AVL, VLMAX)

  • 例 : memcpy

  # void*memcpy(void*dest,constvoid*src, size_tn)
  # a0=dest, a1=src, a2=n
  memcpy:
    mv a3, a0          # Copy destination
  loop:
    vsetvli t0, a2, e8  # ベクトルの要素を8-bitに設定 <11101000>
    vlb.v   v0, (a1)    # ベクトルレジスタv0にデータをロード
    add     a1, a1, t0  # ポインタを進める。
    sub     a2, a2, t0  # カウンタをデクリメントする。
    vsb.v   v0, (a3)    # ベクトルレジスタの値をメモリにストアする。
    add     a3, a3, t0  # ポインタを進める。
    bnez    a2, loop    # まだ要素が残っているか?
    ret                 # Return

混合精度をサポートするために

  1. 例えば32-bitのデータと16-bitのデータに対して演算を行うために必要なのは、VLMAXの値を保持するためにより多くのベクトルレジスタが必要となる。
  2. データパスを増大させないために、レジスタ内のデータを綺麗に並べておく必要がある。

これをサポートするために、vtypeレジスタ内のvlmulフィールドにより、ベクトルレジスタをグループ化することができる。LMUL=1,2,4,8VLMUX = LMUL * VLEN / SEWとなる。

ベクトル命令は、グループ化されたレジスタすべてに適用される。

セグメントロードストア

vlseg3b.v v4, (x10) という命令をサポートする。

RGBなどの3つの並んだデータ(Array of Struct)を、ベクトル要素毎に並べ替えて格納する(Struct of Array)。※あれ、この命令どこかで見たような...

例外のサポート

ベクトル要素のロード中に例外が発生した場合、vstartシステムレジスタが、どの要素まで処理を行っていたを記録しており、次のベクトル命令でその位置から操作を再開する。vstartシステムレジスタは、ベクトル命令が新たに実行される度に0に初期化されるので、ユーザプログラム自身はvstartを意識する必要はない。

Divide Elements

より小さなデータをサポートするためにSEWのデータをさらに分割することができる。EDIV=1, 2, 4, 8

ソフトウェアサポート

LLVMのバックエンドを作るための第一歩 (24. ポインタのサポート)

LLVM IRでポインタのサポートをする。ポインタをサポートするためには、該当するシンボルに対するアドレスを計算するというIRを追加する必要がある。これを、EffectiveAddressというクラスで追加する。

  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXInstrInfo.td
def mem_ea : Operand<iPTR> {
  let PrintMethod = "printMemOperandEA";
  let MIOperandInfo = (ops GPR, simm12);
  let EncoderMethod = "getMemEncoding";
}
class EffectiveAddress<string instr_asm, RegisterClass RC, Operand Mem> :
  MYRISCVX_I<0b0010011, 0b000, (outs RC:$ra), (ins Mem:$addr),
     instr_asm, [(set RC:$ra, addr:$addr)], IIAlu>;

このEffectiveAddressは、実際にはアドレス計算するためのaddi命令を生成している。addi命令により、スタックポインタからのオフセットで、アドレスを算出している。

def LEA_ADDI : EffectiveAddress<"addi\t$ra, $addr", GPR, mem_ea> {
  let isCodeGenOnly = 1;
}

例えば、以下のようなコードをコンパイルしてみる。

int test_local_pointer()
{
  int b = 3;

  int* p = &b;

  return *p;
}
./bin/clang -target mips-unknown-linux-gnu -c ../lbdex/input/ch7_1_localpointer.cpp -emit-llvm
./bin/llc -march=myriscvx32 -mcpu=simple32 -relocation-model=pic -filetype=asm ch7_1_localpointer.bc -o -
# %bb.0:                                # %entry
        addi    x2, x2, -8
        addi    x10, zero, 3
        sw      x10, 4(x2)
        addi    x10, x2, 4
        sw      x10, 0(x2)
        lw      x10, 0(x2)
        lw      x10, 0(x10)
        addi    x2, x2, 8
        ret     x1

読み解いていくと、まずはx10に3を格納する。それをスタックポインタ(x2)の2番目のスロットに格納する(4(x2))。

        addi    x10, zero, 3
        sw      x10, 4(x2)

次のaddix10bのポインタ&bを格納する。これには、さきほどのEffectiveAddressで作成したaddi命令を使用し、スタックの位置からのオフセットを計算して算出する。そして、そのポインタの値(=x10)をスタックポインタ(x2)の1番目のスロットに格納する。

        addi    x10, x2, 4
        sw      x10, 0(x2)

最後に、ポインタの値を返すために、ポインタ経由でロード命令を2回発行して、x10レジスタに格納する。

        lw      x10, 0(x2)
        lw      x10, 0(x10)
f:id:msyksphinz:20190615175107p:plain
スタックに格納されたポインタの配置

RISC-V Workshop in ETH Zurichの講演スライドが公開された

f:id:msyksphinz:20190614004124p:plain

まだすべてチェックしていないが、RISC-V Workshop Zurichのスライドがすべて公開されている。

riscv.org

ざっくりとみてみたいのはこのあたりかな。

SiFive社 Official ReleaseのCoremarkをHiFive Unleashedで動かす

f:id:msyksphinz:20190419223924p:plain

SiFive Tech Symposiumの時に聞いたのだが、Freedom-E-SDKの時期リリースバージョンではCoremarkが同梱されているらしい。

freedom-e-sdkのブランチをdevelopment-19.05に変更すれば出てくる。

github.com

freedom-e-sdkの標準のビルド方法ではビルドできない。まあ、クロスコンパイルが前提だから当たり前か。

git clone https://github.com/sifive/freedom-e-sdk.git -c development-19.05
make software TARGET=sifive-hifive-unleashed PROGRAM=coremark   # これはクロスコンパイルできない。

普通にターゲットディレクトリに移動して実行する。

cd software/coremark
make PORT_DIR=linux64

結果を確認する。オリジナルのバージョンと比べて、オプションが追加されているようだ。

  • development-19.05のCoremarkの結果
2K performance run parameters for coremark.
CoreMark Size    : 666
Total ticks      : 14745
Total time (secs): 14.745000
Iterations/Sec   : 2712.783995
Iterations       : 40000
Compiler version : GCC8.3.0
Compiler flags   : -O2 -fno-common -funroll-loops -finline-functions --param max-inline-insns-auto=20 -falign-functions=4 -falign-jumps=4 -falign-loops=4 --param inline-min-speedup=10 -DPERFORMANCE_RUN=1  -lrt
Memory location  : Please put data memory location here
                        (e.g. code in flash, data on heap etc)
seedcrc          : 0xe9f5
[0]crclist       : 0xe714
[0]crcmatrix     : 0x1fd7
[0]crcstate      : 0x8e3a
[0]crcfinal      : 0x25b5
Correct operation validated. See README.md for run and reporting rules.
CoreMark 1.0 : 2712.783995 / GCC8.3.0 -O2 -fno-common -funroll-loops -finline-functions --param max-inline-insns-auto=20 -falign-functions=4 -falign-jumps=4 -falign-loops=4 --param inline-min-speedup=10 -DPERFORMANCE_RUN=1  -lrt / Heap
  • オリジナルのCoremarkの結果
2K performance run parameters for coremark.
CoreMark Size    : 666
Total ticks      : 14386
Total time (secs): 14.386000
Iterations/Sec   : 2085.360767
Iterations       : 30000
Compiler version : GCC8.3.0
Compiler flags   : -O2 -DPERFORMANCE_RUN=1  -lrt
Memory location  : Please put data memory location here
                        (e.g. code in flash, data on heap etc)
seedcrc          : 0xe9f5
[0]crclist       : 0xe714
[0]crcmatrix     : 0x1fd7
[0]crcstate      : 0x8e3a
[0]crcfinal      : 0x5275
Correct operation validated. See readme.txt for run and reporting rules.
CoreMark 1.0 : 2085.360767 / GCC8.3.0 -O2 -DPERFORMANCE_RUN=1  -lrt / Heap

Iteration/Secだけ抜き出すと、

  • development-19.05のCoremark : 2712.783995
  • オリジナルのCoremark : 2085.360767

となり、30%程度向上している。まあ、オプションの成果だとは思いますが。。。

HiFive Unleashedの正確な動作周波数が分からないのだが、1.0GHzで動いているとすると、2.712CMK/MHzとなり、公称値とほぼ一致する、かな?

LLVMのバックエンドを作るための第一歩 (23. グローバル変数の取り扱い)

今回は、グローバル変数を取り扱うための手法について調査する。 現状では、グローバル変数の入っているプログラムをコンパイルすると以下のようにエラーが発生する。

int gStart = 3;
int gI = 100;
int test_global()
{
  int c = 0;

  c = gI;

  return c;
}
./bin/clang -target mips-unknown-linux-gnu -c ../lbdex/input/ch6_1.cpp -emit-llvm
./bin/llc -debug -march=myriscvx32 -mcpu=simple32 -mattr=+64bit -relocation-model=pic -filetype=asm ch6_1.bc
LLVM ERROR: Cannot select: t5: i32 = GlobalAddress<i32* @gI> 0
In function: _Z11test_globalv

GlobalAddressというIRを処理する必要がありそうだ。

このグローバル変数を処理するためには、コンパイラには2種類のポリシーがある。

  • PIC(Position Independent Code) : ポジション独立。グローバル変数のアドレスはランタイムに計算される。
  • Static : グローバル変数のアドレスは固定で計算される。

ライブラリとして提供する場合、グローバル変数はPICモードでコンパイル必要があるわけだが、そうでない場合はstaticでコンパイルしても良いわけだ。

  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXISelLowering.cpp まず、setOperationAction(Custom)で、GlobalAddress IRの場合はカスタム実装したLower処理を実行するように設定する。
//@MYRISCVXTargetLowering {
MYRISCVXTargetLowering::MYRISCVXTargetLowering(const MYRISCVXTargetMachine &TM,
                                               const MYRISCVXSubtarget &STI)
    : TargetLowering(TM), Subtarget(STI), ABI(TM.getABI()) {
  setOperationAction(ISD::GlobalAddress,      MVT::i32,   Custom);
}
SDValue MYRISCVXTargetLowering::
LowerOperation(SDValue Op, SelectionDAG &DAG) const
{
  switch (Op.getOpcode())
  {
    case ISD::GlobalAddress    : return lowerGlobalAddress(Op, DAG);
  }
  return SDValue();
}

lowerGlobalAddressでは、PICモードとStaticモードの両方に対してDAGの生成を行う。

SDValue MYRISCVXTargetLowering::lowerGlobalAddress(SDValue Op,
                                                   SelectionDAG &DAG) const {
  • PICモードの場合:
  const GlobalObject *GO = GV->getBaseObject();
  return getAddrGlobalLargeGOT(
      N, Ty, DAG, MYRISCVXII::MO_GOT_HI16, MYRISCVXII::MO_GOT_LO16,
      DAG.getEntryNode(),
      MachinePointerInfo::getGOT(DAG.getMachineFunction()));
  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXISelLowering.h
    //@getAddrGlobalLargeGOT {
    // This method creates the following nodes, which are necessary for
    // computing a global symbol's address in large-GOT mode:
    //
    // (load (wrapper (add %hi(sym), $gp), %lo(sym)))
    template<class NodeTy>
    SDValue getAddrGlobalLargeGOT(NodeTy *N, EVT Ty, SelectionDAG &DAG,
                                  unsigned HiFlag, unsigned LoFlag,
                                  SDValue Chain,
                                  const MachinePointerInfo &PtrInfo) const {
      SDLoc DL(N);
      SDValue Hi = DAG.getNode(MYRISCVXISD::Hi, DL, Ty,
                               getTargetNode(N, Ty, DAG, HiFlag));
      Hi = DAG.getNode(ISD::ADD, DL, Ty, Hi, getGlobalReg(DAG, Ty));
      SDValue Wrapper = DAG.getNode(MYRISCVXISD::Wrapper, DL, Ty, Hi,
                                    getTargetNode(N, Ty, DAG, LoFlag));
      return DAG.getLoad(Ty, DL, Chain, Wrapper, PtrInfo);
    }
    //@getAddrGlobalLargeGOT }
  • Staticモードの場合
    //@ %hi/%lo relocation
    return getAddrNonPIC(N, Ty, DAG);
  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXISelLowering.h
    //@getAddrNonPIC
    // This method creates the following nodes, which are necessary for
    // computing a symbol's address in non-PIC mode:
    //
    // (add %hi(sym), %lo(sym))
    template<class NodeTy>
    SDValue getAddrNonPIC(NodeTy *N, EVT Ty, SelectionDAG &DAG) const {
      SDLoc DL(N);
      SDValue Hi = getTargetNode(N, Ty, DAG, MYRISCVXII::MO_ABS_HI);
      SDValue Lo = getTargetNode(N, Ty, DAG, MYRISCVXII::MO_ABS_LO);
      return DAG.getNode(ISD::ADD, DL, Ty,
                         DAG.getNode(MYRISCVXISD::Hi, DL, Ty, Hi),
                         DAG.getNode(MYRISCVXISD::Lo, DL, Ty, Lo));
    }

それぞれのコードは以下のようになる。

  • PICモードの場合
        lui     x10, %got_hi(gI)
        add     x10, x10, x3
        lw      x10, %got_lo(gI)(x10)
        lw      x10, 0(x10)
  • Staticモードの場合
        lui     x10, %hi(gI)
        ori     x10, x10, %lo(gI)
        lw      x10, 0(x10)
f:id:msyksphinz:20190612003723p:plain
グローバル変数をPICで生成した場合。GOTに変数の情報が書き込まれる。