FPGA開発日記

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

オリジナルLLVM Backendを追加しよう (28. Intrinsicのサポート2)

https://cdn-ak.f.st-hatena.com/images/fotolife/m/msyksphinz/20181123/20181123225150.png

LLVMにはすでにRISC-Vのバックエンドサポートが追加されている。しかし、勉強のために独自のRISC-V実装をLLVMに追加している。

jonathan2251.github.io

第11章では、アセンブラやIntrinsicをサポートする。今度はC言語の中にIntrinsicを埋め込んだアセンブラの拡張を行う。例えば、以下のようなコードをサポートする。

  __asm__ __volatile__("addi %0,%1,%2"
                       :"=r"(foo) // 15
                       :"r"(foo), "i"(5)
                       );

  __asm__ __volatile__("ld %0,%1"
                       :"=r"(c) // c=3
                       :"m"(g[2])
                       );

インラインアセンブリである。組み込みの業界ではよく使う手法なのだが。上記のaddiでは、

これらの表現をサポートするためには、MYRISCVXのAsmPrinterを改造する必要がある。 追加するのは、以下のコードだ。以下のコードをオーバライドし、MYRISCVX用に改造する。

// インラインアセンブリの式のオペランドをプリントする。  
bool PrintAsmOperand(const MachineInstr *MI, unsigned OpNo,
                       unsigned AsmVariant, const char *ExtraCode,
                       raw_ostream &O) override;
// メモリアドレスのオペランド
bool PrintAsmMemoryOperand(const MachineInstr *MI, unsigned OpNum,
                             unsigned AsmVariant, const char *ExtraCode,
                             raw_ostream &O) override;
// オペランドのプリント
void printOperand(const MachineInstr *MI, int opNum, raw_ostream &O);
  • MYRISCVXTargetLowering::getConstraintType () : ターゲットの制約を取得する。

    • デフォルトでは、最初から組み込まれているTargetLowering::getConstraintType(Constraint)が呼ばれる。

    • しかし、それ以外に特殊なケースについてswitch文が組み込まれている。

    • これはどこから来ているかというと、MIPSの規定として以下の特殊なイントリンジックの表記が使えるとあり、ここから来ているものと思われる。 https://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html#Machine-Constraints

    • このなかで、

      • c : A register suitable for use in an indirect jump. This will always be $25 for -mabicalls.
      • R : An address that can be used in a non-macro load or store.
      • cの場合はC_RegisterClass,Rの場合はC_Memory`を返す。
    • 例えば、以下のようにIntrinsicを使用してみる。

      int foo = 10;
      const int bar = 15;
    
      __asm__ __volatile__("add %0,%1,%2"
                           :"=r"(foo) // 5
                           :"c"(foo), "c"(bar)
                           );
            addi    10, zero, 15
            sw      10, 4(2)
            lw      5, 8(2)
            addi    5, 10, 0
            #APP
            add $11,$5,$5
            #NO_APP
  • このように、アーキテクチャ独自のIntrinsicの記法を導入することができるようだ。

  • MYRISCXVTargetLowering::getSingleConstraintMatchWeight ()` :

  • std::pair<unsigned, const TargetRegisterClass *> Cpu0TargetLowering::getRegForInlineAsmConstraint()
    • constraintに応じて、レジスタを取得しする。つまり、今回のケースだとrcの場合にMYRISCVX::GPRRegClassを返する。cの場合には、優先的にt0 (=x5)が使用されるようになる。
  • void Cpu0TargetLowering::LowerAsmOperandForConstraint()
    • オペランドの文字解析を行う。通常はTargetLowering::LowerAsmOperandForConstraintを使用するが、MIPSの特殊なレジスタ記法 :
      • I, J, K, L, N, O, P
    • の場合には特殊な処理を行う。
  • bool Cpu0TargetLowering::isLegalAddressingMode()
    • アドレッシングモードが正しく記述されているかどうかをチェックする。

上記の実装を追加した結果、以下のようにIntrisicを含んだソースコードコンパイルできるようになった。

./bin/clang -target mips-unknown-linux-gnu -c ../lbdex/input/ch11_2.cpp -emit-llvm
./bin/llc -march=myriscvx32 -relocation-model=pic -filetype=obj ch11_2.bc -o -
_Z16inlineasm_globalv:
        .frame  $8,16,$1
        .mask   0x00000100,-4
        .set    noreorder
        .cpload $t9
        .set    nomacro
# %bb.0:                                # %entry
        lui     10, %hi(_gp_disp)
        addi    10, 10, %lo(_gp_disp)
        addi    2, 2, -16
        sw      8, 12(2)                # 4-byte Folded Spill
        move    8, 2
        lui     10, %got_hi(g)
        add     10, 10, 3
        lw      10, %got_lo(g)(10)
        addi    11, 10, 8
        #APP
        lw $11,0($11)
        #NO_APP
        sw      11, 8(2)
        lw      11, 8(2)
        #APP
        addi $11,$11,1
        #NO_APP
        sw      11, 4(2)
        lw      10, 4(2)
        move    2, 8
        lw      8, 12(2)                # 4-byte Folded Reload
        addi    2, 2, 16
        jalr    1