FPGA開発日記

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

LLVMのバックエンドを作るための第一歩 (45. Atomic命令を生成する)

f:id:msyksphinz:20190425001356p:plain

C++ではスレッド機能がサポートされており、ClangでもLLVM IRがサポートされている。例えば、以下のようなコードでIRを出力してみる。

  • cpp_atomic.cpp
#include <stdint.h>
#include <atomic>

int test_32()
{
  std::atomic<int> x(3);
  int before = x.fetch_add(2);
  return x.load();
}

今回は簡単化のため、-O3コンパイルする。余計な命令が出てこないので、こちらの方が見やすいからだ。

./bin/clang cpp_atomic.cpp -O3 -emit-llvm
./bin/llvm-dis cpp_atomic.bc -o -
; Function Attrs: nounwind uwtable
define dso_local i32 @_Z7test_32v() local_unnamed_addr #0 personality i32 (...)* @__gxx_personality_v0 {
entry:
  %x = alloca %"struct.std::atomic", align 4
  %0 = bitcast %"struct.std::atomic"* %x to i8*
  call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull %0) #2
  %1 = getelementptr inbounds %"struct.std::atomic", %"struct.std::atomic"* %x, i64 0, i32 0, i32 0
  store i32 3, i32* %1, align 4
  %2 = atomicrmw add i32* %1, i32 2 seq_cst
  %3 = load atomic i32, i32* %1 seq_cst, align 4
  call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull %0) #2
  ret i32 %3
}

atomicrmw addが生成されている。以下の表のように、IRを生成することができる。

IR DAG Opcode
load atomic AtomicLoad ATOMIC_CMP_SWAP_XXX
store atomic AtomicStore ATOMIC_SWAP_XXX
atomicrmw add AtomicLoadAdd ATOMIC_LOAD_ADD_XXX
atomicrmw sub AtomicLoadSub ATOMIC_LOAD_SUB_XXX
atomicrmw xor AtomicLoadXor ATOMIC_LOAD_XOR_XXX
atomicrmw and AtomicLoadAnd ATOMIC_LOAD_AND_XXX
atomicrmw nand AtomicLoadNand ATOMIC_LOAD_NAND_XXX
atomicrmw or AtomicLoadOr ATOMIC_LOAD_OR_XXX
cmpxchg AtomicCmpSwapWithSuccess ATOMIC_CMP_SWAP_XXX
atomicrmw xchg AtomicLoadSwap ATOMIC_SWAP_XXX

MYRISCVXInstrInfo.tdに、可能な限りの生成パタンを追加してみる。

  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXInstrInfo.td
...
// Atomic instructions with 2 source operands (ATOMIC_SWAP & ATOMIC_LOAD_*).
class Atomic2Ops<bits<7> opcode, bits<3> funct3, bits<7>funct7,
                 string instr_asm, PatFrag Op, RegisterClass RC> :
  MYRISCVX_R<opcode, funct3, funct7, (outs RC:$rd), (ins PtrRC:$rs1, RC:$rs2),
             !strconcat(instr_asm, "\t$rd, $rs1, (${rs2})"),
             [(set RC:$rd, (Op iPTR:$rs1, RC:$rs2))], IILoad>;
...
def ATOMIC_LOAD_ADD_I32  : Atomic2Ops<0b0101111, 0b010, 0b0000000, "amoadd.w" , atomic_load_add_32    , GPR>;
def ATOMIC_LOAD_AND_I32  : Atomic2Ops<0b0101111, 0b010, 0b0110000, "amoand.w" , atomic_load_and_32    , GPR>;
def ATOMIC_LOAD_OR_I32   : Atomic2Ops<0b0101111, 0b010, 0b0100000, "amoor.w"  , atomic_load_or_32     , GPR>;
def ATOMIC_LOAD_XOR_I32  : Atomic2Ops<0b0101111, 0b010, 0b0010000, "amoxor.w" , atomic_load_xor_32    , GPR>;

def ATOMIC_SWAP_I32      : Atomic2Ops<0b0101111, 0b010, 0b0000100, "amoswap.w", atomic_swap_32        , GPR>;

RISC-Vのamoadd.w, amoand.w, amoor, amoxor.w, amoswap.wを定義してみた。

  • amo{Op}.w rd1, rs1, (rs2) : rs2に格納されているアドレスからデータをロードし、その値をrdに示されるレジスタに格納する。ロードした値に対してrs1の値をOpで示される演算を適用し、その結果をrs2で格納されれているアドレスにストアする。上記操作をアトミックに行う。

とりあえずこの状態でテストコードをコンパイルしてみる。

./bin/llc -filetype=asm cpp_atomic.bc -mcpu=simple32 -march=myriscvx32 -o -

生成したアセンブリを見てみる。

        addi    x2, x2, -8
        .cfi_def_cfa_offset 8
        sw      x2, 4(x2)               # 4-byte Folded Spill
        .cfi_offset 2, -4
        addi    x10, zero, 3
        sw      x10, 0(x2)
        addi    x11, zero, 2
        addi    x10, x2, 0
        amoadd.w        x11, x10, (x11)
        addi    x11, zero, 0
        addi    x12, x11, 0
        j       __sync_val_compare_and_swap_4
        lw      x2, 4(x2)               # 4-byte Folded Reload
        addi    x2, x2, 8
        ret

amoswap.wが生成されているのが分かる。